diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b70428d --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ +### Environment constants + +ARCH ?= +CROSS_COMPILE ?= +export + +### general build targets + +.PHONY: all clean install install_conf libtools libloragw packet_forwarder util_net_downlink util_chip_id + +all: libtools libloragw packet_forwarder util_net_downlink util_chip_id + +libtools: + $(MAKE) all -e -C $@ + +libloragw: libtools + $(MAKE) all -e -C $@ + +packet_forwarder: libloragw + $(MAKE) all -e -C $@ + +util_net_downlink: libtools + $(MAKE) all -e -C $@ + +util_chip_id: libloragw + $(MAKE) all -e -C $@ + +clean: + $(MAKE) clean -e -C libtools + $(MAKE) clean -e -C libloragw + $(MAKE) clean -e -C packet_forwarder + $(MAKE) clean -e -C util_net_downlink + $(MAKE) clean -e -C util_chip_id + +install: + $(MAKE) install -e -C libloragw + $(MAKE) install -e -C packet_forwarder + $(MAKE) install -e -C util_net_downlink + $(MAKE) install -e -C util_chip_id + +install_conf: + $(MAKE) install_conf -e -C packet_forwarder + +### EOF diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.0 diff --git a/libloragw/Makefile b/libloragw/Makefile new file mode 100644 index 0000000..addac22 --- /dev/null +++ b/libloragw/Makefile @@ -0,0 +1,126 @@ +### get external defined data + +LIBLORAGW_VERSION := `cat ../VERSION` +include library.cfg +include ../target.cfg + +### constant symbols + +ARCH ?= +CROSS_COMPILE ?= +CC := $(CROSS_COMPILE)gcc +AR := $(CROSS_COMPILE)ar + +CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I. -I../libtools/inc + +OBJDIR = obj +INCLUDES = $(wildcard inc/*.h) $(wildcard ../libtools/inc/*.h) + +### linking options + +LIBS := -lloragw -ltinymt32 -lrt -lm + +### general build targets + +all: libloragw.a test_loragw_spi test_loragw_i2c test_loragw_reg test_loragw_hal_tx test_loragw_hal_rx test_loragw_cal test_loragw_capture_ram test_loragw_spi_sx1250 test_loragw_counter test_loragw_gps + +clean: + rm -f libloragw.a + rm -f test_loragw_* + rm -f $(OBJDIR)/*.o + rm -f inc/config.h + +install: +ifneq ($(strip $(TARGET_IP)),) + ifneq ($(strip $(TARGET_DIR)),) + ifneq ($(strip $(TARGET_USR)),) + @echo "---- Copying libloragw files to $(TARGET_IP):$(TARGET_DIR)" + @ssh $(TARGET_USR)@$(TARGET_IP) "mkdir -p $(TARGET_DIR)" + @scp test_loragw_* $(TARGET_USR)@$(TARGET_IP):$(TARGET_DIR) + @scp ../tools/reset_lgw.sh $(TARGET_USR)@$(TARGET_IP):$(TARGET_DIR) + else + @echo "ERROR: TARGET_USR is not configured in target.cfg" + endif + else + @echo "ERROR: TARGET_DIR is not configured in target.cfg" + endif +else + @echo "ERROR: TARGET_IP is not configured in target.cfg" +endif + +### transpose library.cfg into a C header file : config.h + +inc/config.h: ../VERSION library.cfg + @echo "*** Checking libloragw library configuration ***" + @rm -f $@ + #File initialization + @echo "#ifndef _LORAGW_CONFIGURATION_H" >> $@ + @echo "#define _LORAGW_CONFIGURATION_H" >> $@ + # Release version + @echo "Release version : $(LIBLORAGW_VERSION)" + @echo " #define LIBLORAGW_VERSION "\"$(LIBLORAGW_VERSION)\""" >> $@ + # Debug options + @echo " #define DEBUG_AUX $(DEBUG_AUX)" >> $@ + @echo " #define DEBUG_SPI $(DEBUG_SPI)" >> $@ + @echo " #define DEBUG_I2C $(DEBUG_SPI)" >> $@ + @echo " #define DEBUG_REG $(DEBUG_REG)" >> $@ + @echo " #define DEBUG_HAL $(DEBUG_HAL)" >> $@ + @echo " #define DEBUG_GPS $(DEBUG_GPS)" >> $@ + @echo " #define DEBUG_GPIO $(DEBUG_GPIO)" >> $@ + @echo " #define DEBUG_LBT $(DEBUG_LBT)" >> $@ + @echo " #define DEBUG_RAD $(DEBUG_RAD)" >> $@ + @echo " #define DEBUG_CAL $(DEBUG_CAL)" >> $@ + @echo " #define DEBUG_SX1302 $(DEBUG_SX1302)" >> $@ + # Configuration options + @echo " #define BYPASS_FW_INIT $(BYPASS_FW_INIT)" >> $@ + @echo " #define FPGA_BOARD_16_CH $(FPGA_BOARD_16_CH)" >> $@ + # end of file + @echo "#endif" >> $@ + @echo "*** Configuration seems ok ***" + +### library module target + +$(OBJDIR): + mkdir -p $(OBJDIR) + +$(OBJDIR)/%.o: src/%.c $(INCLUDES) inc/config.h | $(OBJDIR) + $(CC) -c $(CFLAGS) $< -o $@ + +### static library + +libloragw.a: $(OBJDIR)/loragw_spi.o $(OBJDIR)/loragw_i2c.o $(OBJDIR)/loragw_aux.o $(OBJDIR)/loragw_reg.o $(OBJDIR)/loragw_sx1250.o $(OBJDIR)/loragw_sx125x.o $(OBJDIR)/loragw_sx1302.o $(OBJDIR)/loragw_cal.o $(OBJDIR)/loragw_debug.o $(OBJDIR)/loragw_hal.o $(OBJDIR)/loragw_stts751.o $(OBJDIR)/loragw_gps.o $(OBJDIR)/loragw_sx1302_timestamp.o $(OBJDIR)/loragw_sx1302_rx.o + $(AR) rcs $@ $^ + +### test programs + +test_loragw_spi: tst/test_loragw_spi.c libloragw.a + $(CC) $(CFLAGS) -L. -L../libtools $< -o $@ $(LIBS) + +test_loragw_i2c: tst/test_loragw_i2c.c libloragw.a + $(CC) $(CFLAGS) -L. -L../libtools $< -o $@ $(LIBS) + +test_loragw_reg: tst/test_loragw_reg.c libloragw.a + $(CC) $(CFLAGS) -L. -L../libtools $< -o $@ $(LIBS) + +test_loragw_hal_tx: tst/test_loragw_hal_tx.c libloragw.a + $(CC) $(CFLAGS) -L. -L../libtools $< -o $@ $(LIBS) + +test_loragw_hal_rx: tst/test_loragw_hal_rx.c libloragw.a + $(CC) $(CFLAGS) -L. -L../libtools $< -o $@ $(LIBS) + +test_loragw_capture_ram: tst/test_loragw_capture_ram.c libloragw.a + $(CC) $(CFLAGS) -L. -L../libtools $< -o $@ $(LIBS) + +test_loragw_cal: tst/test_loragw_cal.c libloragw.a + $(CC) $(CFLAGS) -L. -L../libtools $< -o $@ $(LIBS) + +test_loragw_spi_sx1250: tst/test_loragw_spi_sx1250.c libloragw.a + $(CC) $(CFLAGS) -L. -L../libtools $< -o $@ $(LIBS) + +test_loragw_counter: tst/test_loragw_counter.c libloragw.a + $(CC) $(CFLAGS) -L. -L../libtools $< -o $@ $(LIBS) + +test_loragw_gps: tst/test_loragw_gps.c libloragw.a + $(CC) $(CFLAGS) -L. -L../libtools $< -o $@ $(LIBS) + +### EOF diff --git a/libloragw/inc/loragw_agc_params.h b/libloragw/inc/loragw_agc_params.h new file mode 100644 index 0000000..1f72241 --- /dev/null +++ b/libloragw/inc/loragw_agc_params.h @@ -0,0 +1,84 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + SX1302 AGC parameters definition. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORAGW_AGC_PARAMS_H +#define _LORAGW_AGC_PARAMS_H + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE TYPES -------------------------------------------------------- */ + +struct agc_gain_params_s { + uint8_t ana_min; + uint8_t ana_max; + uint8_t ana_thresh_l; + uint8_t ana_thresh_h; + uint8_t dec_attn_min; + uint8_t dec_attn_max; + uint8_t dec_thresh_l; + uint8_t dec_thresh_h1; + uint8_t dec_thresh_h2; + uint8_t chan_attn_min; + uint8_t chan_attn_max; + uint8_t chan_thresh_l; + uint8_t chan_thresh_h; + uint8_t deviceSel; /* sx1250 only */ + uint8_t hpMax; /* sx1250 only */ + uint8_t paDutyCycle; /* sx1250 only */ +}; + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +const struct agc_gain_params_s agc_params_sx1250 = { + .ana_min = 1, + .ana_max = 13, + .ana_thresh_l = 3, + .ana_thresh_h = 12, + .dec_attn_min = 4, + .dec_attn_max = 15, + .dec_thresh_l = 40, + .dec_thresh_h1 = 80, + .dec_thresh_h2 = 90, + .chan_attn_min = 4, + .chan_attn_max = 14, + .chan_thresh_l = 52, + .chan_thresh_h = 132, + .deviceSel = 0, + .hpMax = 7, + .paDutyCycle = 4 +}; + +const struct agc_gain_params_s agc_params_sx125x = { + .ana_min = 0, + .ana_max = 9, + .ana_thresh_l = 16, + .ana_thresh_h = 35, + .dec_attn_min = 7, + .dec_attn_max = 11, + .dec_thresh_l = 45, + .dec_thresh_h1 = 100, + .dec_thresh_h2 = 115, + .chan_attn_min = 4, + .chan_attn_max = 14, + .chan_thresh_l = 52, + .chan_thresh_h = 132, + .deviceSel = 0, + .hpMax = 0, + .paDutyCycle = 0 +}; + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_aux.h b/libloragw/inc/loragw_aux.h new file mode 100644 index 0000000..fac21fe --- /dev/null +++ b/libloragw/inc/loragw_aux.h @@ -0,0 +1,47 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + LoRa concentrator HAL common auxiliary functions + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORAGW_AUX_H +#define _LORAGW_AUX_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +/** +@brief Get a particular bit value from a byte +@param b [in] Any byte from which we want a bit value +@param p [in] Position of the bit in the byte [0..7] +@param n [in] Number of bits we want to get +@return The value corresponding the requested bits +*/ +#define TAKE_N_BITS_FROM(b, p, n) (((b) >> (p)) & ((1 << (n)) - 1)) + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +/** +@brief Wait for a certain time (millisecond accuracy) +@param t number of milliseconds to wait. +*/ +void wait_ms(unsigned long t); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_cal.h b/libloragw/inc/loragw_cal.h new file mode 100644 index 0000000..08bdbcc --- /dev/null +++ b/libloragw/inc/loragw_cal.h @@ -0,0 +1,59 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + LoRa concentrator radio calibration functions + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORAGW_CAL_H +#define _LORAGW_CAL_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types*/ + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +struct lgw_sx125x_cal_rx_result_s { + int8_t amp; + int8_t phi; + uint16_t rej; + uint16_t rej_init; + uint16_t snr; +}; + +struct lgw_sx125x_cal_tx_result_s { + uint8_t dac_gain; + uint8_t mix_gain; + int8_t offset_i; + int8_t offset_q; + uint16_t rej; + uint16_t sig; +}; + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +int sx1302_cal_start(uint8_t version, struct lgw_conf_rxrf_s * rf_chain_cfg, struct lgw_tx_gain_lut_s * txgain_lut); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_debug.h b/libloragw/inc/loragw_debug.h new file mode 100644 index 0000000..8b2c76b --- /dev/null +++ b/libloragw/inc/loragw_debug.h @@ -0,0 +1,74 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + LoRa concentrator HAL debug functions + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORAGW_DBG_H +#define _LORAGW_DBG_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +/** +@brief +@param +*/ +void dbg_log_buffer_to_file(FILE * file, uint8_t * buffer, uint16_t size); + +/** +@brief +@param +*/ +void dbg_log_payload_diff_to_file(FILE * file, uint8_t * buffer1, uint8_t * buffer2, uint16_t size); + +/** +@brief +@param +*/ +void dbg_init_random(void); + +/** +@brief +@param +*/ +void dbg_generate_random_payload(uint32_t pkt_cnt, uint8_t * buffer_expected, uint8_t size); + +/** +@brief +@param +*/ +int dbg_check_payload(struct lgw_conf_debug_s * context, FILE * file, uint8_t * payload_received, uint8_t size, uint8_t ref_payload_idx, uint8_t sf); + +/** +@brief +@param +*/ +void dbg_init_gpio(void); + +/** +@brief +@param +*/ +void dbg_toggle_gpio(void); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_gps.h b/libloragw/inc/loragw_gps.h new file mode 100644 index 0000000..a790a6b --- /dev/null +++ b/libloragw/inc/loragw_gps.h @@ -0,0 +1,234 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Library of functions to manage a GNSS module (typically GPS) for accurate + timestamping of packets and synchronisation of gateways. + A limited set of module brands/models are supported. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORAGW_GPS_H +#define _LORAGW_GPS_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#define _GNU_SOURCE +#include /* C99 types */ +#include /* time library */ +#include /* speed_t */ +#include /* ssize_t */ + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +/** +@struct coord_s +@brief Time solution required for timestamp to absolute time conversion +*/ +struct tref { + time_t systime; /*!> system time when solution was calculated */ + uint32_t count_us; /*!> reference concentrator internal timestamp */ + struct timespec utc; /*!> reference UTC time (from GPS/NMEA) */ + struct timespec gps; /*!> reference GPS time (since 01.Jan.1980) */ + double xtal_err; /*!> raw clock error (eg. <1 'slow' XTAL) */ +}; + +/** +@struct coord_s +@brief Geodesic coordinates +*/ +struct coord_s { + double lat; /*!> latitude [-90,90] (North +, South -) */ + double lon; /*!> longitude [-180,180] (East +, West -)*/ + short alt; /*!> altitude in meters (WGS 84 geoid ref.) */ +}; + +/** +@enum gps_msg +@brief Type of GPS (and other GNSS) sentences +*/ +enum gps_msg { + UNKNOWN, /*!> neutral value */ + IGNORED, /*!> frame was not parsed by the system */ + INVALID, /*!> system try to parse frame but failed */ + INCOMPLETE, /*!> frame parsed was missing bytes */ + /* NMEA messages of interest */ + NMEA_RMC, /*!> Recommended Minimum data (time + date) */ + NMEA_GGA, /*!> Global positioning system fix data (pos + alt) */ + NMEA_GNS, /*!> GNSS fix data (pos + alt, sat number) */ + NMEA_ZDA, /*!> Time and Date */ + /* NMEA message useful for time reference quality assessment */ + NMEA_GBS, /*!> GNSS Satellite Fault Detection */ + NMEA_GST, /*!> GNSS Pseudo Range Error Statistics */ + NMEA_GSA, /*!> GNSS DOP and Active Satellites (sat number) */ + NMEA_GSV, /*!> GNSS Satellites in View (sat SNR) */ + /* Misc. NMEA messages */ + NMEA_GLL, /*!> Latitude and longitude, with time fix and status */ + NMEA_TXT, /*!> Text Transmission */ + NMEA_VTG, /*!> Course over ground and Ground speed */ + /* uBlox proprietary NMEA messages of interest */ + UBX_NAV_TIMEGPS, /*!> GPS Time Solution */ + UBX_NAV_TIMEUTC /*!> UTC Time Solution */ +}; + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +#define LGW_GPS_SUCCESS 0 +#define LGW_GPS_ERROR -1 + +#define LGW_GPS_MIN_MSG_SIZE (8) +#define LGW_GPS_UBX_SYNC_CHAR (0xB5) +#define LGW_GPS_NMEA_SYNC_CHAR (0x24) + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +/** +@brief Configure a GPS module + +@param tty_path path to the TTY connected to the GPS +@param gps_familly parameter (eg. ubx6 for uBlox gen.6) +@param target_brate target baudrate for communication (0 keeps default target baudrate) +@param fd_ptr pointer to a variable to receive file descriptor on GPS tty +@return success if the function was able to connect and configure a GPS module +*/ +int lgw_gps_enable(char* tty_path, char* gps_familly, speed_t target_brate, int* fd_ptr); + +/** +@brief Restore GPS serial configuration and close serial device + +@param fd file descriptor on GPS tty +@return success if the function was able to complete +*/ +int lgw_gps_disable(int fd); + +/** +@brief Parse messages coming from the GPS system (or other GNSS) + +@param serial_buff pointer to the string to be parsed +@param buff_size maximum string lengths for NMEA parsing (incl. null char) +@return type of frame parsed + +The RAW NMEA sentences are parsed to a global set of variables shared with the +lgw_gps_get function. +If the lgw_parse_nmea and lgw_gps_get are used in different threads, a mutex +lock must be acquired before calling either function. +*/ +enum gps_msg lgw_parse_nmea(const char* serial_buff, int buff_size); + +/** +@brief Parse Ublox proprietary messages coming from the GPS system + +@param serial_buff pointer to the string to be parsed +@param buff_size maximum string lengths for UBX parsing (incl. null char) +@param msg_size number of bytes parsed as UBX message if found +@return type of frame parsed + +The RAW UBX sentences are parsed to a global set of variables shared with the +lgw_gps_get function. +If the lgw_parse_ubx and lgw_gps_get are used in different threads, a mutex +lock must be acquired before calling either function. +*/ +enum gps_msg lgw_parse_ubx(const char* serial_buff, size_t buff_size, size_t *msg_size); + +/** +@brief Get the GPS solution (space & time) for the concentrator + +@param utc pointer to store UTC time, with ns precision (NULL to ignore) +@param gps_time pointer to store GPS time, with ns precision (NULL to ignore) +@param loc pointer to store coordinates (NULL to ignore) +@param err pointer to store coordinates standard deviation (NULL to ignore) +@return success if the chosen elements could be returned + +This function read the global variables generated by the NMEA/UBX parsing +functions lgw_parse_nmea/lgw_parse_ubx. It returns time and location data in a +format that is exploitable by other functions in that library sub-module. +If the lgw_parse_nmea/lgw_parse_ubx and lgw_gps_get are used in different +threads, a mutex lock must be acquired before calling either function. +*/ +int lgw_gps_get(struct timespec *utc, struct timespec *gps_time, struct coord_s *loc, struct coord_s *err); + +/** +@brief Get time and position information from the serial GPS last message received +@param utc UTC time, with ns precision (leap seconds are ignored) +@param gps_time timestamp of last time pulse from the GPS module converted to the UNIX epoch + (leap seconds are ignored) +@param loc location information +@param err location error estimate if supported +@return success if timestamp was read and time reference could be refreshed + +Set systime to 0 in ref to trigger initial synchronization. +*/ +int lgw_gps_sync(struct tref *ref, uint32_t count_us, struct timespec utc, struct timespec gps_time); + +/** +@brief Convert concentrator timestamp counter value to UTC time + +@param ref time reference structure required for time conversion +@param count_us internal timestamp counter of the LoRa concentrator +@param utc pointer to store UTC time, with ns precision (leap seconds ignored) +@return success if the function was able to convert timestamp to UTC + +This function is typically used when a packet is received to transform the +internal counter-based timestamp in an absolute timestamp with an accuracy in +the order of a couple microseconds (ns resolution). +*/ +int lgw_cnt2utc(struct tref ref, uint32_t count_us, struct timespec* utc); + +/** +@brief Convert UTC time to concentrator timestamp counter value + +@param ref time reference structure required for time conversion +@param utc UTC time, with ns precision (leap seconds are ignored) +@param count_us pointer to store internal timestamp counter of LoRa concentrator +@return success if the function was able to convert UTC to timestamp + +This function is typically used when a packet must be sent at an accurate time +(eg. to send a piggy-back response after receiving a packet from a node) to +transform an absolute UTC time into a matching internal concentrator timestamp. +*/ +int lgw_utc2cnt(struct tref ref,struct timespec utc, uint32_t* count_us); + +/** +@brief Convert concentrator timestamp counter value to GPS time + +@param ref time reference structure required for time conversion +@param count_us internal timestamp counter of the LoRa concentrator +@param gps_time pointer to store GPS time, with ns precision (leap seconds ignored) +@return success if the function was able to convert timestamp to GPS time + +This function is typically used when a packet is received to transform the +internal counter-based timestamp in an absolute timestamp with an accuracy in +the order of a millisecond. +*/ +int lgw_cnt2gps(struct tref ref, uint32_t count_us, struct timespec* gps_time); + +/** +@brief Convert GPS time to concentrator timestamp counter value + +@param ref time reference structure required for time conversion +@param gps_time GPS time, with ns precision (leap seconds are ignored) +@param count_us pointer to store internal timestamp counter of LoRa concentrator +@return success if the function was able to convert GPS time to timestamp + +This function is typically used when a packet must be sent at an accurate time +(eg. to send a piggy-back response after receiving a packet from a node) to +transform an absolute GPS time into a matching internal concentrator timestamp. +*/ +int lgw_gps2cnt(struct tref ref, struct timespec gps_time, uint32_t* count_us); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_hal.h b/libloragw/inc/loragw_hal.h new file mode 100644 index 0000000..be6ce35 --- /dev/null +++ b/libloragw/inc/loragw_hal.h @@ -0,0 +1,463 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + LoRa concentrator Hardware Abstraction Layer + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORAGW_HAL_H +#define _LORAGW_HAL_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* bool type */ + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +#define IS_LORA_BW(bw) ((bw == BW_125KHZ) || (bw == BW_250KHZ) || (bw == BW_500KHZ)) +#define IS_LORA_DR(dr) ((dr == DR_LORA_SF5) || (dr == DR_LORA_SF6) || (dr == DR_LORA_SF7) || (dr == DR_LORA_SF8) || (dr == DR_LORA_SF9) || (dr == DR_LORA_SF10) || (dr == DR_LORA_SF11) || (dr == DR_LORA_SF12)) +#define IS_LORA_CR(cr) ((cr == CR_LORA_4_5) || (cr == CR_LORA_4_6) || (cr == CR_LORA_4_7) || (cr == CR_LORA_4_8)) + +#define IS_FSK_BW(bw) ((bw >= 1) && (bw <= 7)) +#define IS_FSK_DR(dr) ((dr >= DR_FSK_MIN) && (dr <= DR_FSK_MAX)) + +#define IS_TX_MODE(mode) ((mode == IMMEDIATE) || (mode == TIMESTAMPED) || (mode == ON_GPS)) + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +/* return status code */ +#define LGW_HAL_SUCCESS 0 +#define LGW_HAL_ERROR -1 +#define LGW_LBT_ISSUE 1 + +/* radio-specific parameters */ +#define LGW_XTAL_FREQU 32000000 /* frequency of the RF reference oscillator */ +#define LGW_RF_CHAIN_NB 2 /* number of RF chains */ +#define LGW_RF_RX_BANDWIDTH {1000000, 1000000} /* bandwidth of the radios */ + +/* concentrator chipset-specific parameters */ +/* to use array parameters, declare a local const and use 'if_chain' as index */ +#define LGW_IF_CHAIN_NB 10 /* number of IF+modem RX chains */ +#define LGW_REF_BW 125000 /* typical bandwidth of data channel */ +#define LGW_MULTI_NB 8 /* number of LoRa 'multi SF' chains */ + +/* values available for the 'modulation' parameters */ +/* NOTE: arbitrary values */ +#define MOD_UNDEFINED 0 +#define MOD_CW 0x08 +#define MOD_LORA 0x10 +#define MOD_FSK 0x20 + +/* values available for the 'bandwidth' parameters (LoRa & FSK) */ +/* NOTE: directly encode FSK RX bandwidth, do not change */ +#define BW_UNDEFINED 0 +#define BW_500KHZ 0x06 +#define BW_250KHZ 0x05 +#define BW_125KHZ 0x04 + +/* values available for the 'datarate' parameters */ +/* NOTE: LoRa values used directly to code SF bitmask in 'multi' modem, do not change */ +#define DR_UNDEFINED 0 +#define DR_LORA_SF5 5 +#define DR_LORA_SF6 6 +#define DR_LORA_SF7 7 +#define DR_LORA_SF8 8 +#define DR_LORA_SF9 9 +#define DR_LORA_SF10 10 +#define DR_LORA_SF11 11 +#define DR_LORA_SF12 12 +/* NOTE: for FSK directly use baudrate between 500 bauds and 250 kbauds */ +#define DR_FSK_MIN 500 +#define DR_FSK_MAX 250000 + +/* values available for the 'coderate' parameters (LoRa only) */ +/* NOTE: arbitrary values */ +#define CR_UNDEFINED 0 +#define CR_LORA_4_5 0x01 +#define CR_LORA_4_6 0x02 +#define CR_LORA_4_7 0x03 +#define CR_LORA_4_8 0x04 + +/* values available for the 'status' parameter */ +/* NOTE: values according to hardware specification */ +#define STAT_UNDEFINED 0x00 +#define STAT_NO_CRC 0x01 +#define STAT_CRC_BAD 0x11 +#define STAT_CRC_OK 0x10 + +/* values available for the 'tx_mode' parameter */ +#define IMMEDIATE 0 +#define TIMESTAMPED 1 +#define ON_GPS 2 + +/* values available for 'select' in the status function */ +#define TX_STATUS 1 +#define RX_STATUS 2 + +/* status code for TX_STATUS */ +/* NOTE: arbitrary values */ +#define TX_STATUS_UNKNOWN 0 +#define TX_OFF 1 /* TX modem disabled, it will ignore commands */ +#define TX_FREE 2 /* TX modem is free, ready to receive a command */ +#define TX_SCHEDULED 3 /* TX modem is loaded, ready to send the packet after an event and/or delay */ +#define TX_EMITTING 4 /* TX modem is emitting */ + +/* status code for RX_STATUS */ +/* NOTE: arbitrary values */ +#define RX_STATUS_UNKNOWN 0 +#define RX_OFF 1 /* RX modem is disabled, it will ignore commands */ +#define RX_ON 2 /* RX modem is receiving */ +#define RX_SUSPENDED 3 /* RX is suspended while a TX is ongoing */ + +/* Maximum size of Tx gain LUT */ +#define TX_GAIN_LUT_SIZE_MAX 16 + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +/** +@enum lgw_radio_type_t +@brief Radio types that can be found on the LoRa Gateway +*/ +typedef enum { + LGW_RADIO_TYPE_NONE, + LGW_RADIO_TYPE_SX1255, + LGW_RADIO_TYPE_SX1257, + LGW_RADIO_TYPE_SX1272, + LGW_RADIO_TYPE_SX1276, + LGW_RADIO_TYPE_SX1250 +} lgw_radio_type_t; + +/** +@struct lgw_conf_board_s +@brief Configuration structure for board specificities +*/ +struct lgw_conf_board_s { + bool lorawan_public; /*!> Enable ONLY for *public* networks using the LoRa MAC protocol */ + uint8_t clksrc; /*!> Index of RF chain which provides clock to concentrator */ + bool full_duplex; /*!> Indicates if the gateway operates in full duplex mode or not */ + char spidev_path[64];/*!> Path to access the SPI device to connect to the SX1302 */ +}; + +/** +@struct lgw_rssi_tcomp_s +@brief Structure containing all coefficients necessary to compute the offset to be applied on RSSI for current temperature +*/ +struct lgw_rssi_tcomp_s { + float coeff_a; + float coeff_b; + float coeff_c; + float coeff_d; + float coeff_e; +}; + +/** +@struct lgw_conf_rxrf_s +@brief Configuration structure for a RF chain +*/ +struct lgw_conf_rxrf_s { + bool enable; /*!> enable or disable that RF chain */ + uint32_t freq_hz; /*!> center frequency of the radio in Hz */ + float rssi_offset; /*!> Board-specific RSSI correction factor */ + struct lgw_rssi_tcomp_s rssi_tcomp; /*!> Board-specific RSSI temperature compensation coefficients */ + lgw_radio_type_t type; /*!> Radio type for that RF chain (SX1255, SX1257....) */ + bool tx_enable; /*!> enable or disable TX on that RF chain */ +}; + +/** +@struct lgw_conf_rxif_s +@brief Configuration structure for an IF chain +*/ +struct lgw_conf_rxif_s { + bool enable; /*!> enable or disable that IF chain */ + uint8_t rf_chain; /*!> to which RF chain is that IF chain associated */ + int32_t freq_hz; /*!> center frequ of the IF chain, relative to RF chain frequency */ + uint8_t bandwidth; /*!> RX bandwidth, 0 for default */ + uint32_t datarate; /*!> RX datarate, 0 for default */ + uint8_t sync_word_size; /*!> size of FSK sync word (number of bytes, 0 for default) */ + uint64_t sync_word; /*!> FSK sync word (ALIGN RIGHT, eg. 0xC194C1) */ + bool implicit_hdr; /*!> LoRa Service implicit header */ + uint8_t implicit_payload_length; /*!> LoRa Service implicit header payload length (number of bytes, 0 for default) */ + bool implicit_crc_en; /*!> LoRa Service implicit header CRC enable */ + uint8_t implicit_coderate; /*!> LoRa Service implicit header coding rate */ +}; + +/** +@struct lgw_pkt_rx_s +@brief Structure containing the metadata of a packet that was received and a pointer to the payload +*/ +struct lgw_pkt_rx_s { + uint32_t freq_hz; /*!> central frequency of the IF chain */ + int32_t freq_offset; + uint8_t if_chain; /*!> by which IF chain was packet received */ + uint8_t status; /*!> status of the received packet */ + uint32_t count_us; /*!> internal concentrator counter for timestamping, 1 microsecond resolution */ + uint8_t rf_chain; /*!> through which RF chain the packet was received */ + uint8_t modem_id; + uint8_t modulation; /*!> modulation used by the packet */ + uint8_t bandwidth; /*!> modulation bandwidth (LoRa only) */ + uint32_t datarate; /*!> RX datarate of the packet (SF for LoRa) */ + uint8_t coderate; /*!> error-correcting code of the packet (LoRa only) */ + float rssic; /*!> average RSSI of the channel in dB */ + float rssis; /*!> average RSSI of the signal in dB */ + float snr; /*!> average packet SNR, in dB (LoRa only) */ + float snr_min; /*!> minimum packet SNR, in dB (LoRa only) */ + float snr_max; /*!> maximum packet SNR, in dB (LoRa only) */ + uint16_t crc; /*!> CRC that was received in the payload */ + uint16_t size; /*!> payload size in bytes */ + uint8_t payload[256]; /*!> buffer containing the payload */ +}; + +/** +@struct lgw_pkt_tx_s +@brief Structure containing the configuration of a packet to send and a pointer to the payload +*/ +struct lgw_pkt_tx_s { + uint32_t freq_hz; /*!> center frequency of TX */ + uint8_t tx_mode; /*!> select on what event/time the TX is triggered */ + uint32_t count_us; /*!> timestamp or delay in microseconds for TX trigger */ + uint8_t rf_chain; /*!> through which RF chain will the packet be sent */ + int8_t rf_power; /*!> TX power, in dBm */ + uint8_t modulation; /*!> modulation to use for the packet */ + int8_t freq_offset; /*!> frequency offset from Radio Tx frequency (CW mode) */ + uint8_t bandwidth; /*!> modulation bandwidth (LoRa only) */ + uint32_t datarate; /*!> TX datarate (baudrate for FSK, SF for LoRa) */ + uint8_t coderate; /*!> error-correcting code of the packet (LoRa only) */ + bool invert_pol; /*!> invert signal polarity, for orthogonal downlinks (LoRa only) */ + uint8_t f_dev; /*!> frequency deviation, in kHz (FSK only) */ + uint16_t preamble; /*!> set the preamble length, 0 for default */ + bool no_crc; /*!> if true, do not send a CRC in the packet */ + bool no_header; /*!> if true, enable implicit header mode (LoRa), fixed length (FSK) */ + uint16_t size; /*!> payload size in bytes */ + uint8_t payload[256]; /*!> buffer containing the payload */ +}; + +/** +@struct lgw_tx_gain_s +@brief Structure containing all gains of Tx chain +*/ +struct lgw_tx_gain_s { + int8_t rf_power; /*!> measured TX power at the board connector, in dBm */ + uint8_t dig_gain; /*!> (sx125x) 2 bits: control of the digital gain of SX1302 */ + uint8_t pa_gain; /*!> (sx125x) 2 bits: control of the external PA (SX1302 I/O) + (sx1250) 1 bits: enable/disable the external PA (SX1302 I/O) */ + uint8_t dac_gain; /*!> (sx125x) 2 bits: control of the radio DAC */ + uint8_t mix_gain; /*!> (sx125x) 4 bits: control of the radio mixer */ + int8_t offset_i; /*!> (sx125x) calibrated I offset */ + int8_t offset_q; /*!> (sx125x) calibrated Q offset */ + uint8_t pwr_idx; /*!> (sx1250) 6 bits: control the radio power index to be used for configuration */ +}; + +/** +@struct lgw_tx_gain_lut_s +@brief Structure defining the Tx gain LUT +*/ +struct lgw_tx_gain_lut_s { + struct lgw_tx_gain_s lut[TX_GAIN_LUT_SIZE_MAX]; /*!> Array of Tx gain struct */ + uint8_t size; /*!> Number of LUT indexes */ +}; + +/** +@struct lgw_conf_debug_s +@brief Configuration structure for debug +*/ +struct conf_ref_payload_s { + uint32_t id; + uint8_t payload[255]; + uint32_t prev_cnt; +}; +struct lgw_conf_debug_s { + uint8_t nb_ref_payload; + struct conf_ref_payload_s ref_payload[16]; + char log_file_name[128]; +}; + +/** +@struct lgw_conf_debug_s +@brief Configuration structure for debug +*/ +struct lgw_conf_timestamp_s { + bool enable_precision_ts; + uint8_t max_ts_metrics; + uint8_t nb_symbols; +}; + +/** +@struct lgw_context_s +@brief Configuration context shared across modules +*/ +typedef struct lgw_context_s { + /* Global context */ + bool is_started; + struct lgw_conf_board_s board_cfg; + /* RX context */ + struct lgw_conf_rxrf_s rf_chain_cfg[LGW_RF_CHAIN_NB]; + struct lgw_conf_rxif_s if_chain_cfg[LGW_IF_CHAIN_NB]; + struct lgw_conf_rxif_s lora_service_cfg; /* LoRa service channel config parameters */ + struct lgw_conf_rxif_s fsk_cfg; /* FSK channel config parameters */ + /* TX context */ + struct lgw_tx_gain_lut_s tx_gain_lut[LGW_RF_CHAIN_NB]; + /* Misc */ + struct lgw_conf_timestamp_s timestamp_cfg; + /* Debug */ + struct lgw_conf_debug_s debug_cfg; +} lgw_context_t; + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +/** +@brief Configure the gateway board +@param conf structure containing the configuration parameters +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_board_setconf(struct lgw_conf_board_s * conf); + +/** +@brief Configure an RF chain (must configure before start) +@param rf_chain number of the RF chain to configure [0, LGW_RF_CHAIN_NB - 1] +@param conf structure containing the configuration parameters +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_rxrf_setconf(uint8_t rf_chain, struct lgw_conf_rxrf_s * conf); + +/** +@brief Configure an IF chain + modem (must configure before start) +@param if_chain number of the IF chain + modem to configure [0, LGW_IF_CHAIN_NB - 1] +@param conf structure containing the configuration parameters +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_rxif_setconf(uint8_t if_chain, struct lgw_conf_rxif_s * conf); + +/** +@brief Configure the Tx gain LUT +@param pointer to structure defining the LUT +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_txgain_setconf(uint8_t rf_chain, struct lgw_tx_gain_lut_s * conf); + +/** +@brief Configure the precision timestamp +@param pointer to structure defining the config to be applied +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_timestamp_setconf(struct lgw_conf_timestamp_s * conf); + +/** +@brief Configure the debug context +@param pointer to structure defining the config to be applied +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_debug_setconf(struct lgw_conf_debug_s * conf); + +/** +@brief Connect to the LoRa concentrator, reset it and configure it according to previously set parameters +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_start(void); + +/** +@brief Stop the LoRa concentrator and disconnect it +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_stop(void); + +/** +@brief A non-blocking function that will fetch up to 'max_pkt' packets from the LoRa concentrator FIFO and data buffer +@param max_pkt maximum number of packet that must be retrieved (equal to the size of the array of struct) +@param pkt_data pointer to an array of struct that will receive the packet metadata and payload pointers +@return LGW_HAL_ERROR id the operation failed, else the number of packets retrieved +*/ +int lgw_receive(uint8_t max_pkt, struct lgw_pkt_rx_s * pkt_data); + +/** +@brief Schedule a packet to be send immediately or after a delay depending on tx_mode +@param pkt_data structure containing the data and metadata for the packet to send +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else + +/!\ When sending a packet, there is a delay (approx 1.5ms) for the analog +circuitry to start and be stable. This delay is adjusted by the HAL depending +on the board version (lgw_i_tx_start_delay_us). + +In 'timestamp' mode, this is transparent: the modem is started +lgw_i_tx_start_delay_us microseconds before the user-set timestamp value is +reached, the preamble of the packet start right when the internal timestamp +counter reach target value. + +In 'immediate' mode, the packet is emitted as soon as possible: transferring the +packet (and its parameters) from the host to the concentrator takes some time, +then there is the lgw_i_tx_start_delay_us, then the packet is emitted. + +In 'triggered' mode (aka PPS/GPS mode), the packet, typically a beacon, is +emitted lgw_i_tx_start_delay_us microsenconds after a rising edge of the +trigger signal. Because there is no way to anticipate the triggering event and +start the analog circuitry beforehand, that delay must be taken into account in +the protocol. +*/ +int lgw_send(struct lgw_pkt_tx_s * pkt_data); + +/** +@brief Give the the status of different part of the LoRa concentrator +@param select is used to select what status we want to know +@param code is used to return the status code +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_status(uint8_t rf_chain, uint8_t select, uint8_t * code); + +/** +@brief Abort a currently scheduled or ongoing TX +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_abort_tx(uint8_t rf_chain); + +/** +@brief Return value of internal counter when latest event (eg GPS pulse) was captured +@param trig_cnt_us pointer to receive timestamp value +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_get_trigcnt(uint32_t * trig_cnt_us); + +/** +@brief Return instateneous value of internal counter +@param inst_cnt_us pointer to receive timestamp value +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_get_instcnt(uint32_t * inst_cnt_us); + +/** +@brief Return the LoRa concentrator EUI +@param eui pointer to receive eui +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_get_eui(uint64_t * eui); + +/** +@brief Allow user to check the version/options of the library once compiled +@return pointer on a human-readable null terminated string +*/ +const char* lgw_version_info(void); + +/** +@brief Return time on air of given packet, in milliseconds +@param packet is a pointer to the packet structure +@return the packet time on air in milliseconds +*/ +uint32_t lgw_time_on_air(struct lgw_pkt_tx_s * packet); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_i2c.h b/libloragw/inc/loragw_i2c.h new file mode 100644 index 0000000..da2539b --- /dev/null +++ b/libloragw/inc/loragw_i2c.h @@ -0,0 +1,75 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Host specific functions to address the LoRa concentrator I2C peripherals. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORAGW_I2C_H +#define _LORAGW_I2C_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types*/ + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +#define LGW_I2C_SUCCESS 0 +#define LGW_I2C_ERROR -1 + +#define I2C_DEVICE "/dev/i2c-1" + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +/** +@brief Open I2C port +@param path Path to the I2C device driver (absolute or relative) +@param device_addr Address of the device +@param i2c_fd Pointer to receive I2C port file descriptor index +@return 0 if I2C port was open successfully, -1 else +*/ +int i2c_linuxdev_open(const char *path, uint8_t device_addr, int *i2c_fd); + +/** +@brief Close I2C port +@param i2c_fd I2C port file descriptor index +@return 0 if I2C port was closed successfully, -1 else +*/ +int i2c_linuxdev_close(int i2c_fd); + +/** +@brief Read data from an I2C port +@param i2c_fd I2C port file descriptor index +@param device_addr I2C device address +@param reg_addr Address of the register to be read +@param data Pointer to a buffer to store read data +@return 0 if I2C data read is successful, -1 else +*/ +int i2c_linuxdev_read(int i2c_fd, uint8_t device_addr, uint8_t reg_addr, uint8_t *data); + +/** +@brief Write data to an I2C port +@param i2c_fd I2C port file descriptor index +@param device_addr I2C device address +@param reg_addr Address of the register to write to +@param data byte to write in the register +@return 0 if I2C data write is successful, -1 else +*/ +int i2c_linuxdev_write(int i2c_fd, uint8_t device_addr, uint8_t reg_addr, uint8_t data); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_reg.h b/libloragw/inc/loragw_reg.h new file mode 100644 index 0000000..6f3b519 --- /dev/null +++ b/libloragw/inc/loragw_reg.h @@ -0,0 +1,1493 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle a single LoRa SX1302 concentrator. + Registers are addressed by name. + Multi-bytes registers are handled automatically. + Read-modify-write is handled automatically. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORAGW_REG_H +#define _LORAGW_REG_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* bool type */ + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- INTERNAL SHARED TYPES ------------------------------------------------ */ + +struct lgw_reg_s { + int8_t page; /*!< page containing the register (-1 for all pages) */ + uint16_t addr; /*!< base address of the register (15 bit) */ + uint8_t offs; /*!< position of the register LSB (between 0 to 7) */ + bool sign; /*!< 1 indicates the register is signed (2 complem.) */ + uint8_t leng; /*!< number of bits in the register */ + bool rdon; /*!< 1 indicates a read-only register */ + bool chck; /*!< register can be checked or not: (pulse, w0clr, w1clr) */ + int32_t dflt; /*!< register default value */ +}; + +/* -------------------------------------------------------------------------- */ +/* --- INTERNAL SHARED FUNCTIONS -------------------------------------------- */ + +int reg_w_align32(void *spi_target, uint8_t spi_mux_target, struct lgw_reg_s r, int32_t reg_value); +int reg_r_align32(void *spi_target, uint8_t spi_mux_target, struct lgw_reg_s r, int32_t *reg_value); + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +#define LGW_REG_SUCCESS 0 +#define LGW_REG_ERROR -1 + +#define SX1302_REG_COMMON_PAGE_PAGE 0 +#define SX1302_REG_COMMON_CTRL0_CLK32_RIF_CTRL 1 +#define SX1302_REG_COMMON_CTRL0_HOST_RADIO_CTRL 2 +#define SX1302_REG_COMMON_CTRL0_RADIO_MISC_EN 3 +#define SX1302_REG_COMMON_CTRL0_SX1261_MODE_RADIO_B 4 +#define SX1302_REG_COMMON_CTRL0_SX1261_MODE_RADIO_A 5 +#define SX1302_REG_COMMON_CTRL1_SWAP_IQ_RADIO_B 6 +#define SX1302_REG_COMMON_CTRL1_SAMPLING_EDGE_RADIO_B 7 +#define SX1302_REG_COMMON_CTRL1_SWAP_IQ_RADIO_A 8 +#define SX1302_REG_COMMON_CTRL1_SAMPLING_EDGE_RADIO_A 9 +#define SX1302_REG_COMMON_SPI_DIV_RATIO_SPI_HALF_PERIOD 10 +#define SX1302_REG_COMMON_RADIO_SELECT_RADIO_SELECT 11 +#define SX1302_REG_COMMON_GEN_GLOBAL_EN 12 +#define SX1302_REG_COMMON_GEN_FSK_MODEM_ENABLE 13 +#define SX1302_REG_COMMON_GEN_CONCENTRATOR_MODEM_ENABLE 14 +#define SX1302_REG_COMMON_GEN_MBWSSF_MODEM_ENABLE 15 +#define SX1302_REG_COMMON_VERSION_VERSION 16 +#define SX1302_REG_COMMON_DUMMY_DUMMY 17 +#define SX1302_REG_AGC_MCU_CTRL_CLK_EN 18 +#define SX1302_REG_AGC_MCU_CTRL_FORCE_HOST_FE_CTRL 19 +#define SX1302_REG_AGC_MCU_CTRL_MCU_CLEAR 20 +#define SX1302_REG_AGC_MCU_CTRL_HOST_PROG 21 +#define SX1302_REG_AGC_MCU_CTRL_PARITY_ERROR 22 +#define SX1302_REG_AGC_MCU_MCU_AGC_STATUS_MCU_AGC_STATUS 23 +#define SX1302_REG_AGC_MCU_PA_GAIN_PA_B_GAIN 24 +#define SX1302_REG_AGC_MCU_PA_GAIN_PA_A_GAIN 25 +#define SX1302_REG_AGC_MCU_RF_EN_A_RADIO_RST 26 +#define SX1302_REG_AGC_MCU_RF_EN_A_RADIO_EN 27 +#define SX1302_REG_AGC_MCU_RF_EN_A_PA_EN 28 +#define SX1302_REG_AGC_MCU_RF_EN_A_LNA_EN 29 +#define SX1302_REG_AGC_MCU_RF_EN_B_RADIO_RST 30 +#define SX1302_REG_AGC_MCU_RF_EN_B_RADIO_EN 31 +#define SX1302_REG_AGC_MCU_RF_EN_B_PA_EN 32 +#define SX1302_REG_AGC_MCU_RF_EN_B_LNA_EN 33 +#define SX1302_REG_AGC_MCU_LUT_TABLE_A_PA_LUT 34 +#define SX1302_REG_AGC_MCU_LUT_TABLE_A_LNA_LUT 35 +#define SX1302_REG_AGC_MCU_LUT_TABLE_B_PA_LUT 36 +#define SX1302_REG_AGC_MCU_LUT_TABLE_B_LNA_LUT 37 +#define SX1302_REG_AGC_MCU_UART_CFG_MSBF 38 +#define SX1302_REG_AGC_MCU_UART_CFG_PAR_EN 39 +#define SX1302_REG_AGC_MCU_UART_CFG_PAR_MODE 40 +#define SX1302_REG_AGC_MCU_UART_CFG_START_LEN 41 +#define SX1302_REG_AGC_MCU_UART_CFG_STOP_LEN 42 +#define SX1302_REG_AGC_MCU_UART_CFG_WORD_LEN 43 +#define SX1302_REG_AGC_MCU_UART_CFG2_BIT_RATE 44 +#define SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE3_MCU_MAIL_BOX_WR_DATA 45 +#define SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE2_MCU_MAIL_BOX_WR_DATA 46 +#define SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE1_MCU_MAIL_BOX_WR_DATA 47 +#define SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE0_MCU_MAIL_BOX_WR_DATA 48 +#define SX1302_REG_AGC_MCU_MCU_MAIL_BOX_RD_DATA_BYTE3_MCU_MAIL_BOX_RD_DATA 49 +#define SX1302_REG_AGC_MCU_MCU_MAIL_BOX_RD_DATA_BYTE2_MCU_MAIL_BOX_RD_DATA 50 +#define SX1302_REG_AGC_MCU_MCU_MAIL_BOX_RD_DATA_BYTE1_MCU_MAIL_BOX_RD_DATA 51 +#define SX1302_REG_AGC_MCU_MCU_MAIL_BOX_RD_DATA_BYTE0_MCU_MAIL_BOX_RD_DATA 52 +#define SX1302_REG_AGC_MCU_DUMMY_DUMMY3 53 +#define SX1302_REG_CLK_CTRL_CLK_SEL_CLKDIV_EN 54 +#define SX1302_REG_CLK_CTRL_CLK_SEL_CLK_RADIO_B_SEL 55 +#define SX1302_REG_CLK_CTRL_CLK_SEL_CLK_RADIO_A_SEL 56 +#define SX1302_REG_CLK_CTRL_DUMMY_DUMMY 57 +#define SX1302_REG_TX_TOP_A_TX_TRIG_TX_FSM_CLR 58 +#define SX1302_REG_TX_TOP_A_TX_TRIG_TX_TRIG_GPS 59 +#define SX1302_REG_TX_TOP_A_TX_TRIG_TX_TRIG_DELAYED 60 +#define SX1302_REG_TX_TOP_A_TX_TRIG_TX_TRIG_IMMEDIATE 61 +#define SX1302_REG_TX_TOP_A_TIMER_TRIG_BYTE3_TIMER_DELAYED_TRIG 62 +#define SX1302_REG_TX_TOP_A_TIMER_TRIG_BYTE2_TIMER_DELAYED_TRIG 63 +#define SX1302_REG_TX_TOP_A_TIMER_TRIG_BYTE1_TIMER_DELAYED_TRIG 64 +#define SX1302_REG_TX_TOP_A_TIMER_TRIG_BYTE0_TIMER_DELAYED_TRIG 65 +#define SX1302_REG_TX_TOP_A_TX_START_DELAY_MSB_TX_START_DELAY 66 +#define SX1302_REG_TX_TOP_A_TX_START_DELAY_LSB_TX_START_DELAY 67 +#define SX1302_REG_TX_TOP_A_TX_CTRL_WRITE_BUFFER 68 +#define SX1302_REG_TX_TOP_A_TX_RAMP_DURATION_TX_RAMP_DURATION 69 +#define SX1302_REG_TX_TOP_A_GEN_CFG_0_MODULATION_TYPE 70 +#define SX1302_REG_TX_TOP_A_TEST_0_TX_ACTIVE_CTRL 71 +#define SX1302_REG_TX_TOP_A_TEST_0_TX_ACTIVE_SEL 72 +#define SX1302_REG_TX_TOP_A_TX_FLAG_TX_TIMEOUT 73 +#define SX1302_REG_TX_TOP_A_TX_FLAG_PKT_DONE 74 +#define SX1302_REG_TX_TOP_A_AGC_TX_BW_AGC_TX_BW 75 +#define SX1302_REG_TX_TOP_A_AGC_TX_PWR_AGC_TX_PWR 76 +#define SX1302_REG_TX_TOP_A_TIMEOUT_CNT_BYTE_2_TIMEOUT_CNT 77 +#define SX1302_REG_TX_TOP_A_TIMEOUT_CNT_BYTE_1_TIMEOUT_CNT 78 +#define SX1302_REG_TX_TOP_A_TIMEOUT_CNT_BYTE_0_TIMEOUT_CNT 79 +#define SX1302_REG_TX_TOP_A_TX_FSM_STATUS_TX_STATUS 80 +#define SX1302_REG_TX_TOP_A_DUMMY_CONTROL_DUMMY 81 +#define SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_PLL_DIV_CTRL 82 +#define SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_CLK_EDGE 83 +#define SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_MODE 84 +#define SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_IF_DST 85 +#define SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_IF_SRC 86 +#define SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL2_SX125X_IQ_INVERT 87 +#define SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL2_PLL_DIV_CTRL_AGC 88 +#define SX1302_REG_TX_TOP_A_TX_RFFE_IF_IQ_GAIN_IQ_GAIN 89 +#define SX1302_REG_TX_TOP_A_TX_RFFE_IF_I_OFFSET_I_OFFSET 90 +#define SX1302_REG_TX_TOP_A_TX_RFFE_IF_Q_OFFSET_Q_OFFSET 91 +#define SX1302_REG_TX_TOP_A_TX_RFFE_IF_FREQ_RF_H_FREQ_RF 92 +#define SX1302_REG_TX_TOP_A_TX_RFFE_IF_FREQ_RF_M_FREQ_RF 93 +#define SX1302_REG_TX_TOP_A_TX_RFFE_IF_FREQ_RF_L_FREQ_RF 94 +#define SX1302_REG_TX_TOP_A_TX_RFFE_IF_FREQ_DEV_H_FREQ_DEV 95 +#define SX1302_REG_TX_TOP_A_TX_RFFE_IF_FREQ_DEV_L_FREQ_DEV 96 +#define SX1302_REG_TX_TOP_A_TX_RFFE_IF_TEST_MOD_FREQ 97 +#define SX1302_REG_TX_TOP_A_DUMMY_MODULATOR_DUMMY 98 +#define SX1302_REG_TX_TOP_A_FSK_PKT_LEN_PKT_LENGTH 99 +#define SX1302_REG_TX_TOP_A_FSK_CFG_0_TX_CONT 100 +#define SX1302_REG_TX_TOP_A_FSK_CFG_0_CRC_IBM 101 +#define SX1302_REG_TX_TOP_A_FSK_CFG_0_DCFREE_ENC 102 +#define SX1302_REG_TX_TOP_A_FSK_CFG_0_CRC_EN 103 +#define SX1302_REG_TX_TOP_A_FSK_CFG_0_PKT_MODE 104 +#define SX1302_REG_TX_TOP_A_FSK_PREAMBLE_SIZE_MSB_PREAMBLE_SIZE 105 +#define SX1302_REG_TX_TOP_A_FSK_PREAMBLE_SIZE_LSB_PREAMBLE_SIZE 106 +#define SX1302_REG_TX_TOP_A_FSK_BIT_RATE_MSB_BIT_RATE 107 +#define SX1302_REG_TX_TOP_A_FSK_BIT_RATE_LSB_BIT_RATE 108 +#define SX1302_REG_TX_TOP_A_FSK_MOD_FSK_REF_PATTERN_SIZE 109 +#define SX1302_REG_TX_TOP_A_FSK_MOD_FSK_PREAMBLE_SEQ 110 +#define SX1302_REG_TX_TOP_A_FSK_MOD_FSK_REF_PATTERN_EN 111 +#define SX1302_REG_TX_TOP_A_FSK_MOD_FSK_GAUSSIAN_SELECT_BT 112 +#define SX1302_REG_TX_TOP_A_FSK_MOD_FSK_GAUSSIAN_EN 113 +#define SX1302_REG_TX_TOP_A_FSK_REF_PATTERN_BYTE7_FSK_REF_PATTERN 114 +#define SX1302_REG_TX_TOP_A_FSK_REF_PATTERN_BYTE6_FSK_REF_PATTERN 115 +#define SX1302_REG_TX_TOP_A_FSK_REF_PATTERN_BYTE5_FSK_REF_PATTERN 116 +#define SX1302_REG_TX_TOP_A_FSK_REF_PATTERN_BYTE4_FSK_REF_PATTERN 117 +#define SX1302_REG_TX_TOP_A_FSK_REF_PATTERN_BYTE3_FSK_REF_PATTERN 118 +#define SX1302_REG_TX_TOP_A_FSK_REF_PATTERN_BYTE2_FSK_REF_PATTERN 119 +#define SX1302_REG_TX_TOP_A_FSK_REF_PATTERN_BYTE1_FSK_REF_PATTERN 120 +#define SX1302_REG_TX_TOP_A_FSK_REF_PATTERN_BYTE0_FSK_REF_PATTERN 121 +#define SX1302_REG_TX_TOP_A_DUMMY_GSFK_DUMMY 122 +#define SX1302_REG_TX_TOP_A_TXRX_CFG0_0_MODEM_BW 123 +#define SX1302_REG_TX_TOP_A_TXRX_CFG0_0_MODEM_SF 124 +#define SX1302_REG_TX_TOP_A_TXRX_CFG0_1_PPM_OFFSET_HDR_CTRL 125 +#define SX1302_REG_TX_TOP_A_TXRX_CFG0_1_PPM_OFFSET 126 +#define SX1302_REG_TX_TOP_A_TXRX_CFG0_1_POST_PREAMBLE_GAP_LONG 127 +#define SX1302_REG_TX_TOP_A_TXRX_CFG0_1_CODING_RATE 128 +#define SX1302_REG_TX_TOP_A_TXRX_CFG0_2_FINE_SYNCH_EN 129 +#define SX1302_REG_TX_TOP_A_TXRX_CFG0_2_MODEM_EN 130 +#define SX1302_REG_TX_TOP_A_TXRX_CFG0_2_CADRXTX 131 +#define SX1302_REG_TX_TOP_A_TXRX_CFG0_2_IMPLICIT_HEADER 132 +#define SX1302_REG_TX_TOP_A_TXRX_CFG0_2_CRC_EN 133 +#define SX1302_REG_TX_TOP_A_TXRX_CFG0_3_PAYLOAD_LENGTH 134 +#define SX1302_REG_TX_TOP_A_TXRX_CFG1_0_INT_STEP_ORIDE_EN 135 +#define SX1302_REG_TX_TOP_A_TXRX_CFG1_0_INT_STEP_ORIDE 136 +#define SX1302_REG_TX_TOP_A_TXRX_CFG1_1_MODEM_START 137 +#define SX1302_REG_TX_TOP_A_TXRX_CFG1_1_HEADER_DIFF_MODE 138 +#define SX1302_REG_TX_TOP_A_TXRX_CFG1_1_ZERO_PAD 139 +#define SX1302_REG_TX_TOP_A_TXRX_CFG1_2_PREAMBLE_SYMB_NB 140 +#define SX1302_REG_TX_TOP_A_TXRX_CFG1_3_PREAMBLE_SYMB_NB 141 +#define SX1302_REG_TX_TOP_A_TXRX_CFG1_4_AUTO_ACK_INT_DELAY 142 +#define SX1302_REG_TX_TOP_A_TXRX_CFG1_4_AUTO_ACK_RX 143 +#define SX1302_REG_TX_TOP_A_TXRX_CFG1_4_AUTO_ACK_TX 144 +#define SX1302_REG_TX_TOP_A_TX_CFG0_0_CHIRP_LOWPASS 145 +#define SX1302_REG_TX_TOP_A_TX_CFG0_0_PPM_OFFSET_SIG 146 +#define SX1302_REG_TX_TOP_A_TX_CFG0_0_CONTCHIRP 147 +#define SX1302_REG_TX_TOP_A_TX_CFG0_0_CHIRP_INVERT 148 +#define SX1302_REG_TX_TOP_A_TX_CFG0_0_CONTINUOUS 149 +#define SX1302_REG_TX_TOP_A_TX_CFG0_1_POWER_RANGING 150 +#define SX1302_REG_TX_TOP_A_TX_CFG1_0_FRAME_NB 151 +#define SX1302_REG_TX_TOP_A_TX_CFG1_1_HOP_CTRL 152 +#define SX1302_REG_TX_TOP_A_TX_CFG1_1_IFS 153 +#define SX1302_REG_TX_TOP_A_FRAME_SYNCH_0_AUTO_SCALE 154 +#define SX1302_REG_TX_TOP_A_FRAME_SYNCH_0_DROP_ON_SYNCH 155 +#define SX1302_REG_TX_TOP_A_FRAME_SYNCH_0_GAIN 156 +#define SX1302_REG_TX_TOP_A_FRAME_SYNCH_0_PEAK1_POS 157 +#define SX1302_REG_TX_TOP_A_FRAME_SYNCH_1_FINETIME_ON_LAST 158 +#define SX1302_REG_TX_TOP_A_FRAME_SYNCH_1_TIMEOUT_OPT 159 +#define SX1302_REG_TX_TOP_A_FRAME_SYNCH_1_PEAK2_POS 160 +#define SX1302_REG_TX_TOP_A_LORA_TX_STATE_STATUS 161 +#define SX1302_REG_TX_TOP_A_LORA_TX_FLAG_FRAME_DONE 162 +#define SX1302_REG_TX_TOP_A_LORA_TX_FLAG_CONT_DONE 163 +#define SX1302_REG_TX_TOP_A_LORA_TX_FLAG_PLD_DONE 164 +#define SX1302_REG_TX_TOP_A_DUMMY_LORA_DUMMY 165 +#define SX1302_REG_TX_TOP_B_TX_TRIG_TX_FSM_CLR 166 +#define SX1302_REG_TX_TOP_B_TX_TRIG_TX_TRIG_GPS 167 +#define SX1302_REG_TX_TOP_B_TX_TRIG_TX_TRIG_DELAYED 168 +#define SX1302_REG_TX_TOP_B_TX_TRIG_TX_TRIG_IMMEDIATE 169 +#define SX1302_REG_TX_TOP_B_TIMER_TRIG_BYTE3_TIMER_DELAYED_TRIG 170 +#define SX1302_REG_TX_TOP_B_TIMER_TRIG_BYTE2_TIMER_DELAYED_TRIG 171 +#define SX1302_REG_TX_TOP_B_TIMER_TRIG_BYTE1_TIMER_DELAYED_TRIG 172 +#define SX1302_REG_TX_TOP_B_TIMER_TRIG_BYTE0_TIMER_DELAYED_TRIG 173 +#define SX1302_REG_TX_TOP_B_TX_START_DELAY_MSB_TX_START_DELAY 174 +#define SX1302_REG_TX_TOP_B_TX_START_DELAY_LSB_TX_START_DELAY 175 +#define SX1302_REG_TX_TOP_B_TX_CTRL_WRITE_BUFFER 176 +#define SX1302_REG_TX_TOP_B_TX_RAMP_DURATION_TX_RAMP_DURATION 177 +#define SX1302_REG_TX_TOP_B_GEN_CFG_0_MODULATION_TYPE 178 +#define SX1302_REG_TX_TOP_B_TEST_0_TX_ACTIVE_CTRL 179 +#define SX1302_REG_TX_TOP_B_TEST_0_TX_ACTIVE_SEL 180 +#define SX1302_REG_TX_TOP_B_TX_FLAG_TX_TIMEOUT 181 +#define SX1302_REG_TX_TOP_B_TX_FLAG_PKT_DONE 182 +#define SX1302_REG_TX_TOP_B_AGC_TX_BW_AGC_TX_BW 183 +#define SX1302_REG_TX_TOP_B_AGC_TX_PWR_AGC_TX_PWR 184 +#define SX1302_REG_TX_TOP_B_TIMEOUT_CNT_BYTE_2_TIMEOUT_CNT 185 +#define SX1302_REG_TX_TOP_B_TIMEOUT_CNT_BYTE_1_TIMEOUT_CNT 186 +#define SX1302_REG_TX_TOP_B_TIMEOUT_CNT_BYTE_0_TIMEOUT_CNT 187 +#define SX1302_REG_TX_TOP_B_TX_FSM_STATUS_TX_STATUS 188 +#define SX1302_REG_TX_TOP_B_DUMMY_CONTROL_DUMMY 189 +#define SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_PLL_DIV_CTRL 190 +#define SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_CLK_EDGE 191 +#define SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_MODE 192 +#define SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_IF_DST 193 +#define SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_IF_SRC 194 +#define SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL2_SX125X_IQ_INVERT 195 +#define SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL2_PLL_DIV_CTRL_AGC 196 +#define SX1302_REG_TX_TOP_B_TX_RFFE_IF_IQ_GAIN_IQ_GAIN 197 +#define SX1302_REG_TX_TOP_B_TX_RFFE_IF_I_OFFSET_I_OFFSET 198 +#define SX1302_REG_TX_TOP_B_TX_RFFE_IF_Q_OFFSET_Q_OFFSET 199 +#define SX1302_REG_TX_TOP_B_TX_RFFE_IF_FREQ_RF_H_FREQ_RF 200 +#define SX1302_REG_TX_TOP_B_TX_RFFE_IF_FREQ_RF_M_FREQ_RF 201 +#define SX1302_REG_TX_TOP_B_TX_RFFE_IF_FREQ_RF_L_FREQ_RF 202 +#define SX1302_REG_TX_TOP_B_TX_RFFE_IF_FREQ_DEV_H_FREQ_DEV 203 +#define SX1302_REG_TX_TOP_B_TX_RFFE_IF_FREQ_DEV_L_FREQ_DEV 204 +#define SX1302_REG_TX_TOP_B_TX_RFFE_IF_TEST_MOD_FREQ 205 +#define SX1302_REG_TX_TOP_B_DUMMY_MODULATOR_DUMMY 206 +#define SX1302_REG_TX_TOP_B_FSK_PKT_LEN_PKT_LENGTH 207 +#define SX1302_REG_TX_TOP_B_FSK_CFG_0_TX_CONT 208 +#define SX1302_REG_TX_TOP_B_FSK_CFG_0_CRC_IBM 209 +#define SX1302_REG_TX_TOP_B_FSK_CFG_0_DCFREE_ENC 210 +#define SX1302_REG_TX_TOP_B_FSK_CFG_0_CRC_EN 211 +#define SX1302_REG_TX_TOP_B_FSK_CFG_0_PKT_MODE 212 +#define SX1302_REG_TX_TOP_B_FSK_PREAMBLE_SIZE_MSB_PREAMBLE_SIZE 213 +#define SX1302_REG_TX_TOP_B_FSK_PREAMBLE_SIZE_LSB_PREAMBLE_SIZE 214 +#define SX1302_REG_TX_TOP_B_FSK_BIT_RATE_MSB_BIT_RATE 215 +#define SX1302_REG_TX_TOP_B_FSK_BIT_RATE_LSB_BIT_RATE 216 +#define SX1302_REG_TX_TOP_B_FSK_MOD_FSK_REF_PATTERN_SIZE 217 +#define SX1302_REG_TX_TOP_B_FSK_MOD_FSK_PREAMBLE_SEQ 218 +#define SX1302_REG_TX_TOP_B_FSK_MOD_FSK_REF_PATTERN_EN 219 +#define SX1302_REG_TX_TOP_B_FSK_MOD_FSK_GAUSSIAN_SELECT_BT 220 +#define SX1302_REG_TX_TOP_B_FSK_MOD_FSK_GAUSSIAN_EN 221 +#define SX1302_REG_TX_TOP_B_FSK_REF_PATTERN_BYTE7_FSK_REF_PATTERN 222 +#define SX1302_REG_TX_TOP_B_FSK_REF_PATTERN_BYTE6_FSK_REF_PATTERN 223 +#define SX1302_REG_TX_TOP_B_FSK_REF_PATTERN_BYTE5_FSK_REF_PATTERN 224 +#define SX1302_REG_TX_TOP_B_FSK_REF_PATTERN_BYTE4_FSK_REF_PATTERN 225 +#define SX1302_REG_TX_TOP_B_FSK_REF_PATTERN_BYTE3_FSK_REF_PATTERN 226 +#define SX1302_REG_TX_TOP_B_FSK_REF_PATTERN_BYTE2_FSK_REF_PATTERN 227 +#define SX1302_REG_TX_TOP_B_FSK_REF_PATTERN_BYTE1_FSK_REF_PATTERN 228 +#define SX1302_REG_TX_TOP_B_FSK_REF_PATTERN_BYTE0_FSK_REF_PATTERN 229 +#define SX1302_REG_TX_TOP_B_DUMMY_GSFK_DUMMY 230 +#define SX1302_REG_TX_TOP_B_TXRX_CFG0_0_MODEM_BW 231 +#define SX1302_REG_TX_TOP_B_TXRX_CFG0_0_MODEM_SF 232 +#define SX1302_REG_TX_TOP_B_TXRX_CFG0_1_PPM_OFFSET_HDR_CTRL 233 +#define SX1302_REG_TX_TOP_B_TXRX_CFG0_1_PPM_OFFSET 234 +#define SX1302_REG_TX_TOP_B_TXRX_CFG0_1_POST_PREAMBLE_GAP_LONG 235 +#define SX1302_REG_TX_TOP_B_TXRX_CFG0_1_CODING_RATE 236 +#define SX1302_REG_TX_TOP_B_TXRX_CFG0_2_FINE_SYNCH_EN 237 +#define SX1302_REG_TX_TOP_B_TXRX_CFG0_2_MODEM_EN 238 +#define SX1302_REG_TX_TOP_B_TXRX_CFG0_2_CADRXTX 239 +#define SX1302_REG_TX_TOP_B_TXRX_CFG0_2_IMPLICIT_HEADER 240 +#define SX1302_REG_TX_TOP_B_TXRX_CFG0_2_CRC_EN 241 +#define SX1302_REG_TX_TOP_B_TXRX_CFG0_3_PAYLOAD_LENGTH 242 +#define SX1302_REG_TX_TOP_B_TXRX_CFG1_0_INT_STEP_ORIDE_EN 243 +#define SX1302_REG_TX_TOP_B_TXRX_CFG1_0_INT_STEP_ORIDE 244 +#define SX1302_REG_TX_TOP_B_TXRX_CFG1_1_MODEM_START 245 +#define SX1302_REG_TX_TOP_B_TXRX_CFG1_1_HEADER_DIFF_MODE 246 +#define SX1302_REG_TX_TOP_B_TXRX_CFG1_1_ZERO_PAD 247 +#define SX1302_REG_TX_TOP_B_TXRX_CFG1_2_PREAMBLE_SYMB_NB 248 +#define SX1302_REG_TX_TOP_B_TXRX_CFG1_3_PREAMBLE_SYMB_NB 249 +#define SX1302_REG_TX_TOP_B_TXRX_CFG1_4_AUTO_ACK_INT_DELAY 250 +#define SX1302_REG_TX_TOP_B_TXRX_CFG1_4_AUTO_ACK_RX 251 +#define SX1302_REG_TX_TOP_B_TXRX_CFG1_4_AUTO_ACK_TX 252 +#define SX1302_REG_TX_TOP_B_TX_CFG0_0_CHIRP_LOWPASS 253 +#define SX1302_REG_TX_TOP_B_TX_CFG0_0_PPM_OFFSET_SIG 254 +#define SX1302_REG_TX_TOP_B_TX_CFG0_0_CONTCHIRP 255 +#define SX1302_REG_TX_TOP_B_TX_CFG0_0_CHIRP_INVERT 256 +#define SX1302_REG_TX_TOP_B_TX_CFG0_0_CONTINUOUS 257 +#define SX1302_REG_TX_TOP_B_TX_CFG0_1_POWER_RANGING 258 +#define SX1302_REG_TX_TOP_B_TX_CFG1_0_FRAME_NB 259 +#define SX1302_REG_TX_TOP_B_TX_CFG1_1_HOP_CTRL 260 +#define SX1302_REG_TX_TOP_B_TX_CFG1_1_IFS 261 +#define SX1302_REG_TX_TOP_B_FRAME_SYNCH_0_AUTO_SCALE 262 +#define SX1302_REG_TX_TOP_B_FRAME_SYNCH_0_DROP_ON_SYNCH 263 +#define SX1302_REG_TX_TOP_B_FRAME_SYNCH_0_GAIN 264 +#define SX1302_REG_TX_TOP_B_FRAME_SYNCH_0_PEAK1_POS 265 +#define SX1302_REG_TX_TOP_B_FRAME_SYNCH_1_FINETIME_ON_LAST 266 +#define SX1302_REG_TX_TOP_B_FRAME_SYNCH_1_TIMEOUT_OPT 267 +#define SX1302_REG_TX_TOP_B_FRAME_SYNCH_1_PEAK2_POS 268 +#define SX1302_REG_TX_TOP_B_LORA_TX_STATE_STATUS 269 +#define SX1302_REG_TX_TOP_B_LORA_TX_FLAG_FRAME_DONE 270 +#define SX1302_REG_TX_TOP_B_LORA_TX_FLAG_CONT_DONE 271 +#define SX1302_REG_TX_TOP_B_LORA_TX_FLAG_PLD_DONE 272 +#define SX1302_REG_TX_TOP_B_DUMMY_LORA_DUMMY 273 +#define SX1302_REG_GPIO_GPIO_DIR_H_DIRECTION 274 +#define SX1302_REG_GPIO_GPIO_DIR_L_DIRECTION 275 +#define SX1302_REG_GPIO_GPIO_OUT_H_OUT_VALUE 276 +#define SX1302_REG_GPIO_GPIO_OUT_L_OUT_VALUE 277 +#define SX1302_REG_GPIO_GPIO_IN_H_IN_VALUE 278 +#define SX1302_REG_GPIO_GPIO_IN_L_IN_VALUE 279 +#define SX1302_REG_GPIO_GPIO_PD_H_PD_VALUE 280 +#define SX1302_REG_GPIO_GPIO_PD_L_PD_VALUE 281 +#define SX1302_REG_GPIO_GPIO_SEL_0_SELECTION 282 +#define SX1302_REG_GPIO_GPIO_SEL_1_SELECTION 283 +#define SX1302_REG_GPIO_GPIO_SEL_2_SELECTION 284 +#define SX1302_REG_GPIO_GPIO_SEL_3_SELECTION 285 +#define SX1302_REG_GPIO_GPIO_SEL_4_SELECTION 286 +#define SX1302_REG_GPIO_GPIO_SEL_5_SELECTION 287 +#define SX1302_REG_GPIO_GPIO_SEL_6_SELECTION 288 +#define SX1302_REG_GPIO_GPIO_SEL_7_SELECTION 289 +#define SX1302_REG_GPIO_GPIO_SEL_8_11_GPIO_11_9_SEL 290 +#define SX1302_REG_GPIO_GPIO_SEL_8_11_GPIO_8_SEL 291 +#define SX1302_REG_GPIO_HOST_IRQ_TX_TIMEOUT_B 292 +#define SX1302_REG_GPIO_HOST_IRQ_TX_TIMEOUT_A 293 +#define SX1302_REG_GPIO_HOST_IRQ_TX_DONE_B 294 +#define SX1302_REG_GPIO_HOST_IRQ_TX_DONE_A 295 +#define SX1302_REG_GPIO_HOST_IRQ_TIMESTAMP 296 +#define SX1302_REG_GPIO_HOST_IRQ_RX_BUFFER_WATERMARK 297 +#define SX1302_REG_GPIO_HOST_IRQ_EN_TX_TIMEOUT_B 298 +#define SX1302_REG_GPIO_HOST_IRQ_EN_TX_TIMEOUT_A 299 +#define SX1302_REG_GPIO_HOST_IRQ_EN_TX_DONE_B 300 +#define SX1302_REG_GPIO_HOST_IRQ_EN_TX_DONE_A 301 +#define SX1302_REG_GPIO_HOST_IRQ_EN_TIMESTAMP 302 +#define SX1302_REG_GPIO_HOST_IRQ_EN_RX_BUFFER_WATERMARK 303 +#define SX1302_REG_GPIO_DUMMY_DUMMY 304 +#define SX1302_REG_TIMESTAMP_GPS_CTRL_GPS_POL 305 +#define SX1302_REG_TIMESTAMP_GPS_CTRL_GPS_EN 306 +#define SX1302_REG_TIMESTAMP_TIMESTAMP_PPS_MSB2_TIMESTAMP_PPS 307 +#define SX1302_REG_TIMESTAMP_TIMESTAMP_PPS_MSB1_TIMESTAMP_PPS 308 +#define SX1302_REG_TIMESTAMP_TIMESTAMP_PPS_LSB2_TIMESTAMP_PPS 309 +#define SX1302_REG_TIMESTAMP_TIMESTAMP_PPS_LSB1_TIMESTAMP_PPS 310 +#define SX1302_REG_TIMESTAMP_TIMESTAMP_MSB2_TIMESTAMP 311 +#define SX1302_REG_TIMESTAMP_TIMESTAMP_MSB1_TIMESTAMP 312 +#define SX1302_REG_TIMESTAMP_TIMESTAMP_LSB2_TIMESTAMP 313 +#define SX1302_REG_TIMESTAMP_TIMESTAMP_LSB1_TIMESTAMP 314 +#define SX1302_REG_TIMESTAMP_TIMESTAMP_SET3_TIMESTAMP 315 +#define SX1302_REG_TIMESTAMP_TIMESTAMP_SET2_TIMESTAMP 316 +#define SX1302_REG_TIMESTAMP_TIMESTAMP_SET1_TIMESTAMP 317 +#define SX1302_REG_TIMESTAMP_TIMESTAMP_SET0_TIMESTAMP 318 +#define SX1302_REG_TIMESTAMP_TIMESTAMP_IRQ_3_TIMESTAMP 319 +#define SX1302_REG_TIMESTAMP_TIMESTAMP_IRQ_2_TIMESTAMP 320 +#define SX1302_REG_TIMESTAMP_TIMESTAMP_IRQ_1_TIMESTAMP 321 +#define SX1302_REG_TIMESTAMP_TIMESTAMP_IRQ_0_TIMESTAMP 322 +#define SX1302_REG_TIMESTAMP_DUMMY_DUMMY 323 +#define SX1302_REG_RX_TOP_FREQ_0_MSB_IF_FREQ_0 324 +#define SX1302_REG_RX_TOP_FREQ_0_LSB_IF_FREQ_0 325 +#define SX1302_REG_RX_TOP_FREQ_1_MSB_IF_FREQ_1 326 +#define SX1302_REG_RX_TOP_FREQ_1_LSB_IF_FREQ_1 327 +#define SX1302_REG_RX_TOP_FREQ_2_MSB_IF_FREQ_2 328 +#define SX1302_REG_RX_TOP_FREQ_2_LSB_IF_FREQ_2 329 +#define SX1302_REG_RX_TOP_FREQ_3_MSB_IF_FREQ_3 330 +#define SX1302_REG_RX_TOP_FREQ_3_LSB_IF_FREQ_3 331 +#define SX1302_REG_RX_TOP_FREQ_4_MSB_IF_FREQ_4 332 +#define SX1302_REG_RX_TOP_FREQ_4_LSB_IF_FREQ_4 333 +#define SX1302_REG_RX_TOP_FREQ_5_MSB_IF_FREQ_5 334 +#define SX1302_REG_RX_TOP_FREQ_5_LSB_IF_FREQ_5 335 +#define SX1302_REG_RX_TOP_FREQ_6_MSB_IF_FREQ_6 336 +#define SX1302_REG_RX_TOP_FREQ_6_LSB_IF_FREQ_6 337 +#define SX1302_REG_RX_TOP_FREQ_7_MSB_IF_FREQ_7 338 +#define SX1302_REG_RX_TOP_FREQ_7_LSB_IF_FREQ_7 339 +#define SX1302_REG_RX_TOP_RADIO_SELECT_RADIO_SELECT 340 +#define SX1302_REG_RX_TOP_RSSI_CONTROL_RSSI_FILTER_ALPHA 341 +#define SX1302_REG_RX_TOP_RSSI_CONTROL_SELECT_RSSI 342 +#define SX1302_REG_RX_TOP_RSSI_DEF_VALUE_CHAN_RSSI_DEF_VALUE 343 +#define SX1302_REG_RX_TOP_CHANN_DAGC_CFG1_CHAN_DAGC_THRESHOLD_HIGH 344 +#define SX1302_REG_RX_TOP_CHANN_DAGC_CFG2_CHAN_DAGC_THRESHOLD_LOW 345 +#define SX1302_REG_RX_TOP_CHANN_DAGC_CFG3_CHAN_DAGC_MAX_ATTEN 346 +#define SX1302_REG_RX_TOP_CHANN_DAGC_CFG3_CHAN_DAGC_MIN_ATTEN 347 +#define SX1302_REG_RX_TOP_CHANN_DAGC_CFG4_CHAN_DAGC_STEP 348 +#define SX1302_REG_RX_TOP_CHANN_DAGC_CFG5_CHAN_DAGC_MODE 349 +#define SX1302_REG_RX_TOP_RSSI_VALUE_CHAN_RSSI 350 +#define SX1302_REG_RX_TOP_GAIN_CONTROL_CHAN_GAIN_VALID 351 +#define SX1302_REG_RX_TOP_GAIN_CONTROL_CHAN_GAIN 352 +#define SX1302_REG_RX_TOP_CLK_CONTROL_CHAN_CLK_EN 353 +#define SX1302_REG_RX_TOP_DUMMY0_DUMMY0 354 +#define SX1302_REG_RX_TOP_CORR_CLOCK_ENABLE_CLK_EN 355 +#define SX1302_REG_RX_TOP_CORRELATOR_EN_CORR_EN 356 +#define SX1302_REG_RX_TOP_CORRELATOR_SF_EN_CORR_SF_EN 357 +#define SX1302_REG_RX_TOP_CORRELATOR_ENABLE_ONLY_FIRST_DET_EDGE_ENABLE_ONLY_FIRST_DET_EDGE 358 +#define SX1302_REG_RX_TOP_CORRELATOR_ENABLE_ACC_CLEAR_ENABLE_CORR_ACC_CLEAR 359 +#define SX1302_REG_RX_TOP_SF5_CFG1_ACC_WIN_LEN 360 +#define SX1302_REG_RX_TOP_SF5_CFG1_ACC_PEAK_SUM_EN 361 +#define SX1302_REG_RX_TOP_SF5_CFG1_ACC_PEAK_POS_SEL 362 +#define SX1302_REG_RX_TOP_SF5_CFG1_ACC_COEFF 363 +#define SX1302_REG_RX_TOP_SF5_CFG1_ACC_AUTO_RESCALE 364 +#define SX1302_REG_RX_TOP_SF5_CFG1_ACC_2_SAME_PEAKS 365 +#define SX1302_REG_RX_TOP_SF5_CFG2_ACC_MIN2 366 +#define SX1302_REG_RX_TOP_SF5_CFG2_ACC_PNR 367 +#define SX1302_REG_RX_TOP_SF5_CFG3_MIN_SINGLE_PEAK 368 +#define SX1302_REG_RX_TOP_SF5_CFG4_MSP_PNR 369 +#define SX1302_REG_RX_TOP_SF5_CFG5_MSP2_PNR 370 +#define SX1302_REG_RX_TOP_SF5_CFG6_MSP_PEAK_NB 371 +#define SX1302_REG_RX_TOP_SF5_CFG6_MSP_CNT_MODE 372 +#define SX1302_REG_RX_TOP_SF5_CFG6_MSP_POS_SEL 373 +#define SX1302_REG_RX_TOP_SF5_CFG7_MSP2_PEAK_NB 374 +#define SX1302_REG_RX_TOP_SF5_CFG7_NOISE_COEFF 375 +#define SX1302_REG_RX_TOP_SF6_CFG1_ACC_WIN_LEN 376 +#define SX1302_REG_RX_TOP_SF6_CFG1_ACC_PEAK_SUM_EN 377 +#define SX1302_REG_RX_TOP_SF6_CFG1_ACC_PEAK_POS_SEL 378 +#define SX1302_REG_RX_TOP_SF6_CFG1_ACC_COEFF 379 +#define SX1302_REG_RX_TOP_SF6_CFG1_ACC_AUTO_RESCALE 380 +#define SX1302_REG_RX_TOP_SF6_CFG1_ACC_2_SAME_PEAKS 381 +#define SX1302_REG_RX_TOP_SF6_CFG2_ACC_MIN2 382 +#define SX1302_REG_RX_TOP_SF6_CFG2_ACC_PNR 383 +#define SX1302_REG_RX_TOP_SF6_CFG3_MIN_SINGLE_PEAK 384 +#define SX1302_REG_RX_TOP_SF6_CFG4_MSP_PNR 385 +#define SX1302_REG_RX_TOP_SF6_CFG5_MSP2_PNR 386 +#define SX1302_REG_RX_TOP_SF6_CFG6_MSP_PEAK_NB 387 +#define SX1302_REG_RX_TOP_SF6_CFG6_MSP_CNT_MODE 388 +#define SX1302_REG_RX_TOP_SF6_CFG6_MSP_POS_SEL 389 +#define SX1302_REG_RX_TOP_SF6_CFG7_MSP2_PEAK_NB 390 +#define SX1302_REG_RX_TOP_SF6_CFG7_NOISE_COEFF 391 +#define SX1302_REG_RX_TOP_SF7_CFG1_ACC_WIN_LEN 392 +#define SX1302_REG_RX_TOP_SF7_CFG1_ACC_PEAK_SUM_EN 393 +#define SX1302_REG_RX_TOP_SF7_CFG1_ACC_PEAK_POS_SEL 394 +#define SX1302_REG_RX_TOP_SF7_CFG1_ACC_COEFF 395 +#define SX1302_REG_RX_TOP_SF7_CFG1_ACC_AUTO_RESCALE 396 +#define SX1302_REG_RX_TOP_SF7_CFG1_ACC_2_SAME_PEAKS 397 +#define SX1302_REG_RX_TOP_SF7_CFG2_ACC_MIN2 398 +#define SX1302_REG_RX_TOP_SF7_CFG2_ACC_PNR 399 +#define SX1302_REG_RX_TOP_SF7_CFG3_MIN_SINGLE_PEAK 400 +#define SX1302_REG_RX_TOP_SF7_CFG4_MSP_PNR 401 +#define SX1302_REG_RX_TOP_SF7_CFG5_MSP2_PNR 402 +#define SX1302_REG_RX_TOP_SF7_CFG6_MSP_PEAK_NB 403 +#define SX1302_REG_RX_TOP_SF7_CFG6_MSP_CNT_MODE 404 +#define SX1302_REG_RX_TOP_SF7_CFG6_MSP_POS_SEL 405 +#define SX1302_REG_RX_TOP_SF7_CFG7_MSP2_PEAK_NB 406 +#define SX1302_REG_RX_TOP_SF7_CFG7_NOISE_COEFF 407 +#define SX1302_REG_RX_TOP_SF8_CFG1_ACC_WIN_LEN 408 +#define SX1302_REG_RX_TOP_SF8_CFG1_ACC_PEAK_SUM_EN 409 +#define SX1302_REG_RX_TOP_SF8_CFG1_ACC_PEAK_POS_SEL 410 +#define SX1302_REG_RX_TOP_SF8_CFG1_ACC_COEFF 411 +#define SX1302_REG_RX_TOP_SF8_CFG1_ACC_AUTO_RESCALE 412 +#define SX1302_REG_RX_TOP_SF8_CFG1_ACC_2_SAME_PEAKS 413 +#define SX1302_REG_RX_TOP_SF8_CFG2_ACC_MIN2 414 +#define SX1302_REG_RX_TOP_SF8_CFG2_ACC_PNR 415 +#define SX1302_REG_RX_TOP_SF8_CFG3_MIN_SINGLE_PEAK 416 +#define SX1302_REG_RX_TOP_SF8_CFG4_MSP_PNR 417 +#define SX1302_REG_RX_TOP_SF8_CFG5_MSP2_PNR 418 +#define SX1302_REG_RX_TOP_SF8_CFG6_MSP_PEAK_NB 419 +#define SX1302_REG_RX_TOP_SF8_CFG6_MSP_CNT_MODE 420 +#define SX1302_REG_RX_TOP_SF8_CFG6_MSP_POS_SEL 421 +#define SX1302_REG_RX_TOP_SF8_CFG7_MSP2_PEAK_NB 422 +#define SX1302_REG_RX_TOP_SF8_CFG7_NOISE_COEFF 423 +#define SX1302_REG_RX_TOP_SF9_CFG1_ACC_WIN_LEN 424 +#define SX1302_REG_RX_TOP_SF9_CFG1_ACC_PEAK_SUM_EN 425 +#define SX1302_REG_RX_TOP_SF9_CFG1_ACC_PEAK_POS_SEL 426 +#define SX1302_REG_RX_TOP_SF9_CFG1_ACC_COEFF 427 +#define SX1302_REG_RX_TOP_SF9_CFG1_ACC_AUTO_RESCALE 428 +#define SX1302_REG_RX_TOP_SF9_CFG1_ACC_2_SAME_PEAKS 429 +#define SX1302_REG_RX_TOP_SF9_CFG2_ACC_MIN2 430 +#define SX1302_REG_RX_TOP_SF9_CFG2_ACC_PNR 431 +#define SX1302_REG_RX_TOP_SF9_CFG3_MIN_SINGLE_PEAK 432 +#define SX1302_REG_RX_TOP_SF9_CFG4_MSP_PNR 433 +#define SX1302_REG_RX_TOP_SF9_CFG5_MSP2_PNR 434 +#define SX1302_REG_RX_TOP_SF9_CFG6_MSP_PEAK_NB 435 +#define SX1302_REG_RX_TOP_SF9_CFG6_MSP_CNT_MODE 436 +#define SX1302_REG_RX_TOP_SF9_CFG6_MSP_POS_SEL 437 +#define SX1302_REG_RX_TOP_SF9_CFG7_MSP2_PEAK_NB 438 +#define SX1302_REG_RX_TOP_SF9_CFG7_NOISE_COEFF 439 +#define SX1302_REG_RX_TOP_SF10_CFG1_ACC_WIN_LEN 440 +#define SX1302_REG_RX_TOP_SF10_CFG1_ACC_PEAK_SUM_EN 441 +#define SX1302_REG_RX_TOP_SF10_CFG1_ACC_PEAK_POS_SEL 442 +#define SX1302_REG_RX_TOP_SF10_CFG1_ACC_COEFF 443 +#define SX1302_REG_RX_TOP_SF10_CFG1_ACC_AUTO_RESCALE 444 +#define SX1302_REG_RX_TOP_SF10_CFG1_ACC_2_SAME_PEAKS 445 +#define SX1302_REG_RX_TOP_SF10_CFG2_ACC_MIN2 446 +#define SX1302_REG_RX_TOP_SF10_CFG2_ACC_PNR 447 +#define SX1302_REG_RX_TOP_SF10_CFG3_MIN_SINGLE_PEAK 448 +#define SX1302_REG_RX_TOP_SF10_CFG4_MSP_PNR 449 +#define SX1302_REG_RX_TOP_SF10_CFG5_MSP2_PNR 450 +#define SX1302_REG_RX_TOP_SF10_CFG6_MSP_PEAK_NB 451 +#define SX1302_REG_RX_TOP_SF10_CFG6_MSP_CNT_MODE 452 +#define SX1302_REG_RX_TOP_SF10_CFG6_MSP_POS_SEL 453 +#define SX1302_REG_RX_TOP_SF10_CFG7_MSP2_PEAK_NB 454 +#define SX1302_REG_RX_TOP_SF10_CFG7_NOISE_COEFF 455 +#define SX1302_REG_RX_TOP_SF11_CFG1_ACC_WIN_LEN 456 +#define SX1302_REG_RX_TOP_SF11_CFG1_ACC_PEAK_SUM_EN 457 +#define SX1302_REG_RX_TOP_SF11_CFG1_ACC_PEAK_POS_SEL 458 +#define SX1302_REG_RX_TOP_SF11_CFG1_ACC_COEFF 459 +#define SX1302_REG_RX_TOP_SF11_CFG1_ACC_AUTO_RESCALE 460 +#define SX1302_REG_RX_TOP_SF11_CFG1_ACC_2_SAME_PEAKS 461 +#define SX1302_REG_RX_TOP_SF11_CFG2_ACC_MIN2 462 +#define SX1302_REG_RX_TOP_SF11_CFG2_ACC_PNR 463 +#define SX1302_REG_RX_TOP_SF11_CFG3_MIN_SINGLE_PEAK 464 +#define SX1302_REG_RX_TOP_SF11_CFG4_MSP_PNR 465 +#define SX1302_REG_RX_TOP_SF11_CFG5_MSP2_PNR 466 +#define SX1302_REG_RX_TOP_SF11_CFG6_MSP_PEAK_NB 467 +#define SX1302_REG_RX_TOP_SF11_CFG6_MSP_CNT_MODE 468 +#define SX1302_REG_RX_TOP_SF11_CFG6_MSP_POS_SEL 469 +#define SX1302_REG_RX_TOP_SF11_CFG7_MSP2_PEAK_NB 470 +#define SX1302_REG_RX_TOP_SF11_CFG7_NOISE_COEFF 471 +#define SX1302_REG_RX_TOP_SF12_CFG1_ACC_WIN_LEN 472 +#define SX1302_REG_RX_TOP_SF12_CFG1_ACC_PEAK_SUM_EN 473 +#define SX1302_REG_RX_TOP_SF12_CFG1_ACC_PEAK_POS_SEL 474 +#define SX1302_REG_RX_TOP_SF12_CFG1_ACC_COEFF 475 +#define SX1302_REG_RX_TOP_SF12_CFG1_ACC_AUTO_RESCALE 476 +#define SX1302_REG_RX_TOP_SF12_CFG1_ACC_2_SAME_PEAKS 477 +#define SX1302_REG_RX_TOP_SF12_CFG2_ACC_MIN2 478 +#define SX1302_REG_RX_TOP_SF12_CFG2_ACC_PNR 479 +#define SX1302_REG_RX_TOP_SF12_CFG3_MIN_SINGLE_PEAK 480 +#define SX1302_REG_RX_TOP_SF12_CFG4_MSP_PNR 481 +#define SX1302_REG_RX_TOP_SF12_CFG5_MSP2_PNR 482 +#define SX1302_REG_RX_TOP_SF12_CFG6_MSP_PEAK_NB 483 +#define SX1302_REG_RX_TOP_SF12_CFG6_MSP_CNT_MODE 484 +#define SX1302_REG_RX_TOP_SF12_CFG6_MSP_POS_SEL 485 +#define SX1302_REG_RX_TOP_SF12_CFG7_MSP2_PEAK_NB 486 +#define SX1302_REG_RX_TOP_SF12_CFG7_NOISE_COEFF 487 +#define SX1302_REG_RX_TOP_DUMMY1_DUMMY1 488 +#define SX1302_REG_RX_TOP_DC_NOTCH_CFG1_BW_START 489 +#define SX1302_REG_RX_TOP_DC_NOTCH_CFG1_AUTO_BW_RED 490 +#define SX1302_REG_RX_TOP_DC_NOTCH_CFG1_NO_FAST_START 491 +#define SX1302_REG_RX_TOP_DC_NOTCH_CFG1_BYPASS 492 +#define SX1302_REG_RX_TOP_DC_NOTCH_CFG1_ENABLE 493 +#define SX1302_REG_RX_TOP_DC_NOTCH_CFG2_BW_LOCKED 494 +#define SX1302_REG_RX_TOP_DC_NOTCH_CFG2_BW 495 +#define SX1302_REG_RX_TOP_DC_NOTCH_CFG3_BW_RED 496 +#define SX1302_REG_RX_TOP_DC_NOTCH_CFG4_IIR_DCC_TIME 497 +#define SX1302_REG_RX_TOP_RX_DFE_FIR1_0_FIR1_COEFF_0 498 +#define SX1302_REG_RX_TOP_RX_DFE_FIR1_1_FIR1_COEFF_1 499 +#define SX1302_REG_RX_TOP_RX_DFE_FIR1_2_FIR1_COEFF_2 500 +#define SX1302_REG_RX_TOP_RX_DFE_FIR1_3_FIR1_COEFF_3 501 +#define SX1302_REG_RX_TOP_RX_DFE_FIR1_4_FIR1_COEFF_4 502 +#define SX1302_REG_RX_TOP_RX_DFE_FIR1_5_FIR1_COEFF_5 503 +#define SX1302_REG_RX_TOP_RX_DFE_FIR1_6_FIR1_COEFF_6 504 +#define SX1302_REG_RX_TOP_RX_DFE_FIR1_7_FIR1_COEFF_7 505 +#define SX1302_REG_RX_TOP_RX_DFE_FIR2_0_FIR2_COEFF_0 506 +#define SX1302_REG_RX_TOP_RX_DFE_FIR2_1_FIR2_COEFF_1 507 +#define SX1302_REG_RX_TOP_RX_DFE_FIR2_2_FIR2_COEFF_2 508 +#define SX1302_REG_RX_TOP_RX_DFE_FIR2_3_FIR2_COEFF_3 509 +#define SX1302_REG_RX_TOP_RX_DFE_FIR2_4_FIR2_COEFF_4 510 +#define SX1302_REG_RX_TOP_RX_DFE_FIR2_5_FIR2_COEFF_5 511 +#define SX1302_REG_RX_TOP_RX_DFE_FIR2_6_FIR2_COEFF_6 512 +#define SX1302_REG_RX_TOP_RX_DFE_FIR2_7_FIR2_COEFF_7 513 +#define SX1302_REG_RX_TOP_RX_DFE_AGC0_RADIO_GAIN_RED_SEL 514 +#define SX1302_REG_RX_TOP_RX_DFE_AGC0_RADIO_GAIN_RED_DB 515 +#define SX1302_REG_RX_TOP_RX_DFE_AGC1_DC_COMP_EN 516 +#define SX1302_REG_RX_TOP_RX_DFE_AGC1_FORCE_DEFAULT_FIR 517 +#define SX1302_REG_RX_TOP_RX_DFE_AGC1_RSSI_EARLY_LATCH 518 +#define SX1302_REG_RX_TOP_RX_DFE_AGC1_FREEZE_ON_SYNC 519 +#define SX1302_REG_RX_TOP_RX_DFE_AGC2_DAGC_IN_COMP 520 +#define SX1302_REG_RX_TOP_RX_DFE_AGC2_DAGC_FIR_HYST 521 +#define SX1302_REG_RX_TOP_RX_DFE_AGC2_RSSI_MAX_SAMPLE 522 +#define SX1302_REG_RX_TOP_RX_DFE_AGC2_RSSI_MIN_SAMPLE 523 +#define SX1302_REG_RX_TOP_RX_DFE_GAIN0_DAGC_FIR_FAST 524 +#define SX1302_REG_RX_TOP_RX_DFE_GAIN0_FORCE_GAIN_FIR 525 +#define SX1302_REG_RX_TOP_RX_DFE_GAIN0_GAIN_FIR1 526 +#define SX1302_REG_RX_TOP_RX_DFE_GAIN0_GAIN_FIR2 527 +#define SX1302_REG_RX_TOP_DAGC_CFG_TARGET_LVL 528 +#define SX1302_REG_RX_TOP_DAGC_CFG_GAIN_INCR_STEP 529 +#define SX1302_REG_RX_TOP_DAGC_CFG_GAIN_DROP_COMP 530 +#define SX1302_REG_RX_TOP_DAGC_CFG_COMB_FILTER_EN 531 +#define SX1302_REG_RX_TOP_DAGC_CFG_NO_FREEZE_START 532 +#define SX1302_REG_RX_TOP_DAGC_CFG_FREEZE_ON_SYNC 533 +#define SX1302_REG_RX_TOP_DAGC_CNT0_SAMPLE 534 +#define SX1302_REG_RX_TOP_DAGC_CNT1_THR_M6 535 +#define SX1302_REG_RX_TOP_DAGC_CNT2_THR_M12 536 +#define SX1302_REG_RX_TOP_DAGC_CNT3_THR_M18 537 +#define SX1302_REG_RX_TOP_DAGC_CNT4_GAIN 538 +#define SX1302_REG_RX_TOP_DAGC_CNT4_FORCE_GAIN 539 +#define SX1302_REG_RX_TOP_TXRX_CFG1_PPM_OFFSET_HDR_CTRL 540 +#define SX1302_REG_RX_TOP_TXRX_CFG1_PPM_OFFSET 541 +#define SX1302_REG_RX_TOP_TXRX_CFG1_MODEM_EN 542 +#define SX1302_REG_RX_TOP_TXRX_CFG1_CODING_RATE 543 +#define SX1302_REG_RX_TOP_TXRX_CFG2_MODEM_START 544 +#define SX1302_REG_RX_TOP_TXRX_CFG2_CADRXTX 545 +#define SX1302_REG_RX_TOP_TXRX_CFG2_IMPLICIT_HEADER 546 +#define SX1302_REG_RX_TOP_TXRX_CFG2_CRC_EN 547 +#define SX1302_REG_RX_TOP_TXRX_CFG3_PAYLOAD_LENGTH 548 +#define SX1302_REG_RX_TOP_TXRX_CFG4_INT_STEP_ORIDE_EN 549 +#define SX1302_REG_RX_TOP_TXRX_CFG4_INT_STEP_ORIDE 550 +#define SX1302_REG_RX_TOP_TXRX_CFG5_HEADER_DIFF_MODE 551 +#define SX1302_REG_RX_TOP_TXRX_CFG5_ZERO_PAD 552 +#define SX1302_REG_RX_TOP_TXRX_CFG6_PREAMBLE_SYMB_NB 553 +#define SX1302_REG_RX_TOP_TXRX_CFG7_PREAMBLE_SYMB_NB 554 +#define SX1302_REG_RX_TOP_TXRX_CFG8_AUTO_ACK_INT_DELAY 555 +#define SX1302_REG_RX_TOP_TXRX_CFG8_AUTO_ACK_RX 556 +#define SX1302_REG_RX_TOP_TXRX_CFG8_AUTO_ACK_TX 557 +#define SX1302_REG_RX_TOP_TXRX_CFG8_POST_PREAMBLE_GAP_LONG 558 +#define SX1302_REG_RX_TOP_TXRX_CFG9_FINE_SYNCH_EN_SF12 559 +#define SX1302_REG_RX_TOP_TXRX_CFG9_FINE_SYNCH_EN_SF11 560 +#define SX1302_REG_RX_TOP_TXRX_CFG9_FINE_SYNCH_EN_SF10 561 +#define SX1302_REG_RX_TOP_TXRX_CFG9_FINE_SYNCH_EN_SF9 562 +#define SX1302_REG_RX_TOP_TXRX_CFG9_FINE_SYNCH_EN_SF8 563 +#define SX1302_REG_RX_TOP_TXRX_CFG9_FINE_SYNCH_EN_SF7 564 +#define SX1302_REG_RX_TOP_TXRX_CFG9_FINE_SYNCH_EN_SF6 565 +#define SX1302_REG_RX_TOP_TXRX_CFG9_FINE_SYNCH_EN_SF5 566 +#define SX1302_REG_RX_TOP_RX_CFG0_DFT_PEAK_EN 567 +#define SX1302_REG_RX_TOP_RX_CFG0_CHIRP_INVERT 568 +#define SX1302_REG_RX_TOP_RX_CFG0_SWAP_IQ 569 +#define SX1302_REG_RX_TOP_RX_CFG0_CONTINUOUS 570 +#define SX1302_REG_RX_TOP_RX_CFG1_DETECT_TIMEOUT 571 +#define SX1302_REG_RX_TOP_RX_CFG2_CLK_EN_RESYNC_DIN 572 +#define SX1302_REG_RX_TOP_RX_CFG2_LLR_SCALE 573 +#define SX1302_REG_RX_TOP_FRAME_SYNCH0_SF5_PEAK1_POS_SF5 574 +#define SX1302_REG_RX_TOP_FRAME_SYNCH1_SF5_PEAK2_POS_SF5 575 +#define SX1302_REG_RX_TOP_FRAME_SYNCH0_SF6_PEAK1_POS_SF6 576 +#define SX1302_REG_RX_TOP_FRAME_SYNCH1_SF6_PEAK2_POS_SF6 577 +#define SX1302_REG_RX_TOP_FRAME_SYNCH0_SF7TO12_PEAK1_POS_SF7TO12 578 +#define SX1302_REG_RX_TOP_FRAME_SYNCH1_SF7TO12_PEAK2_POS_SF7TO12 579 +#define SX1302_REG_RX_TOP_FRAME_SYNCH2_FINETIME_ON_LAST 580 +#define SX1302_REG_RX_TOP_FRAME_SYNCH2_AUTO_SCALE 581 +#define SX1302_REG_RX_TOP_FRAME_SYNCH2_DROP_ON_SYNCH 582 +#define SX1302_REG_RX_TOP_FRAME_SYNCH2_GAIN 583 +#define SX1302_REG_RX_TOP_FRAME_SYNCH2_TIMEOUT_OPT 584 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_0_GAIN_P_HDR_RED 585 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_0_ROUNDING 586 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_0_POS_LIMIT 587 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_0_SUM_SIZE 588 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_0_MODE 589 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_1_GAIN_P_AUTO 590 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_1_GAIN_P_PAYLOAD 591 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_1_GAIN_P_PREAMB 592 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_2_GAIN_I_AUTO 593 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_2_GAIN_I_PAYLOAD 594 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_2_GAIN_I_PREAMB 595 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_3_FINESYNCH_SUM 596 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_3_FINESYNCH_GAIN 597 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_4_GAIN_I_EN_SF8 598 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_4_GAIN_I_EN_SF7 599 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_4_GAIN_I_EN_SF6 600 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_4_GAIN_I_EN_SF5 601 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_5_GAIN_I_EN_SF12 602 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_5_GAIN_I_EN_SF11 603 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_5_GAIN_I_EN_SF10 604 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_5_GAIN_I_EN_SF9 605 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_6_GAIN_P_PREAMB_SF12 606 +#define SX1302_REG_RX_TOP_FINE_TIMING_A_6_GAIN_P_PREAMB_SF5_6 607 +#define SX1302_REG_RX_TOP_FINE_TIMING_7_GAIN_I_AUTO_MAX 608 +#define SX1302_REG_RX_TOP_FINE_TIMING_7_GAIN_P_AUTO_MAX 609 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_0_GAIN_P_HDR_RED 610 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_0_ROUNDING 611 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_0_POS_LIMIT 612 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_0_SUM_SIZE 613 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_0_MODE 614 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_1_GAIN_P_AUTO 615 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_1_GAIN_P_PAYLOAD 616 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_1_GAIN_P_PREAMB 617 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_2_GAIN_I_AUTO 618 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_2_GAIN_I_PAYLOAD 619 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_2_GAIN_I_PREAMB 620 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_3_FINESYNCH_SUM 621 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_3_FINESYNCH_GAIN 622 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_4_GAIN_I_EN_SF8 623 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_4_GAIN_I_EN_SF7 624 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_4_GAIN_I_EN_SF6 625 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_4_GAIN_I_EN_SF5 626 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_5_GAIN_I_EN_SF12 627 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_5_GAIN_I_EN_SF11 628 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_5_GAIN_I_EN_SF10 629 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_5_GAIN_I_EN_SF9 630 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_6_GAIN_P_PREAMB_SF12 631 +#define SX1302_REG_RX_TOP_FINE_TIMING_B_6_GAIN_P_PREAMB_SF5_6 632 +#define SX1302_REG_RX_TOP_FREQ_TO_TIME0_FREQ_TO_TIME_DRIFT_MANT 633 +#define SX1302_REG_RX_TOP_FREQ_TO_TIME1_FREQ_TO_TIME_DRIFT_MANT 634 +#define SX1302_REG_RX_TOP_FREQ_TO_TIME2_FREQ_TO_TIME_DRIFT_EXP 635 +#define SX1302_REG_RX_TOP_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_FREQ_DELTA 636 +#define SX1302_REG_RX_TOP_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_FINE_DELTA 637 +#define SX1302_REG_RX_TOP_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_FREQ_ERROR 638 +#define SX1302_REG_RX_TOP_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_TIME_SYMB 639 +#define SX1302_REG_RX_TOP_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_TIME_OFFSET 640 +#define SX1302_REG_RX_TOP_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_DETECT 641 +#define SX1302_REG_RX_TOP_FREQ_TO_TIME4_FREQ_TO_TIME_INVERT_RNG 642 +#define SX1302_REG_RX_TOP_FREQ_TRACK_A_0_FREQ_TRACK_EN_SF8 643 +#define SX1302_REG_RX_TOP_FREQ_TRACK_A_0_FREQ_TRACK_EN_SF7 644 +#define SX1302_REG_RX_TOP_FREQ_TRACK_A_0_FREQ_TRACK_EN_SF6 645 +#define SX1302_REG_RX_TOP_FREQ_TRACK_A_0_FREQ_TRACK_EN_SF5 646 +#define SX1302_REG_RX_TOP_FREQ_TRACK_A_1_FREQ_TRACK_EN_SF12 647 +#define SX1302_REG_RX_TOP_FREQ_TRACK_A_1_FREQ_TRACK_EN_SF11 648 +#define SX1302_REG_RX_TOP_FREQ_TRACK_A_1_FREQ_TRACK_EN_SF10 649 +#define SX1302_REG_RX_TOP_FREQ_TRACK_A_1_FREQ_TRACK_EN_SF9 650 +#define SX1302_REG_RX_TOP_FREQ_TRACK_B_0_FREQ_TRACK_EN_SF8 651 +#define SX1302_REG_RX_TOP_FREQ_TRACK_B_0_FREQ_TRACK_EN_SF7 652 +#define SX1302_REG_RX_TOP_FREQ_TRACK_B_0_FREQ_TRACK_EN_SF6 653 +#define SX1302_REG_RX_TOP_FREQ_TRACK_B_0_FREQ_TRACK_EN_SF5 654 +#define SX1302_REG_RX_TOP_FREQ_TRACK_B_1_FREQ_TRACK_EN_SF12 655 +#define SX1302_REG_RX_TOP_FREQ_TRACK_B_1_FREQ_TRACK_EN_SF11 656 +#define SX1302_REG_RX_TOP_FREQ_TRACK_B_1_FREQ_TRACK_EN_SF10 657 +#define SX1302_REG_RX_TOP_FREQ_TRACK_B_1_FREQ_TRACK_EN_SF9 658 +#define SX1302_REG_RX_TOP_FREQ_TRACK2_FREQ_TRACK_FINE 659 +#define SX1302_REG_RX_TOP_FREQ_TRACK2_FREQ_TRACK_HDR_SKIP 660 +#define SX1302_REG_RX_TOP_FREQ_TRACK3_FREQ_SYNCH_GAIN 661 +#define SX1302_REG_RX_TOP_FREQ_TRACK3_FREQ_TRACK_AUTO_THR 662 +#define SX1302_REG_RX_TOP_FREQ_TRACK4_SNR_MIN_WINDOW 663 +#define SX1302_REG_RX_TOP_FREQ_TRACK4_GAIN_AUTO_SNR_MIN 664 +#define SX1302_REG_RX_TOP_FREQ_TRACK4_FREQ_SYNCH_THR 665 +#define SX1302_REG_RX_TOP_DETECT_MSP0_MSP_PNR 666 +#define SX1302_REG_RX_TOP_DETECT_MSP1_MSP2_PNR 667 +#define SX1302_REG_RX_TOP_DETECT_MSP2_MSP2_PEAK_NB 668 +#define SX1302_REG_RX_TOP_DETECT_MSP2_MSP_PEAK_NB 669 +#define SX1302_REG_RX_TOP_DETECT_MSP3_ACC_MIN2 670 +#define SX1302_REG_RX_TOP_DETECT_MSP3_ACC_WIN_LEN 671 +#define SX1302_REG_RX_TOP_DETECT_MSP3_MSP_POS_SEL 672 +#define SX1302_REG_RX_TOP_DETECT_MSP3_MSP_CNT_MODE 673 +#define SX1302_REG_RX_TOP_DETECT_ACC1_USE_GAIN_SYMB 674 +#define SX1302_REG_RX_TOP_DETECT_ACC1_ACC_PNR 675 +#define SX1302_REG_RX_TOP_DETECT_ACC2_NOISE_COEFF 676 +#define SX1302_REG_RX_TOP_DETECT_ACC2_ACC_COEFF 677 +#define SX1302_REG_RX_TOP_DETECT_ACC2_ACC_2_SAME_PEAKS 678 +#define SX1302_REG_RX_TOP_DETECT_ACC2_ACC_AUTO_RESCALE 679 +#define SX1302_REG_RX_TOP_DETECT_ACC2_ACC_PEAK_POS_SEL 680 +#define SX1302_REG_RX_TOP_DETECT_ACC2_ACC_PEAK_SUM_EN 681 +#define SX1302_REG_RX_TOP_DETECT_ACC3_MIN_SINGLE_PEAK 682 +#define SX1302_REG_RX_TOP_TIMESTAMP_SEL_SNR_MIN 683 +#define SX1302_REG_RX_TOP_TIMESTAMP_ENABLE 684 +#define SX1302_REG_RX_TOP_TIMESTAMP_NB_SYMB 685 +#define SX1302_REG_RX_TOP_MODEM_BUSY_MSB_RX_MODEM_BUSY 686 +#define SX1302_REG_RX_TOP_MODEM_BUSY_LSB_RX_MODEM_BUSY 687 +#define SX1302_REG_RX_TOP_MODEM_STATE_RX_MODEM_STS_SPARE 688 +#define SX1302_REG_RX_TOP_MODEM_STATE_RX_MODEM_STATE 689 +#define SX1302_REG_RX_TOP_MODEM_SYNC_DELTA_MSB_PEAK_POS_FINE_GAIN_H 690 +#define SX1302_REG_RX_TOP_MODEM_SYNC_DELTA_MSB_PEAK_POS_FINE_GAIN_L 691 +#define SX1302_REG_RX_TOP_MODEM_SYNC_DELTA_MSB_PEAK_POS_FINE_SIGN 692 +#define SX1302_REG_RX_TOP_MODEM_SYNC_DELTA_MSB_MODEM_SYNC_DELTA 693 +#define SX1302_REG_RX_TOP_MODEM_SYNC_DELTA_LSB_MODEM_SYNC_DELTA 694 +#define SX1302_REG_RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF8 695 +#define SX1302_REG_RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF7 696 +#define SX1302_REG_RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF6 697 +#define SX1302_REG_RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF5 698 +#define SX1302_REG_RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF12 699 +#define SX1302_REG_RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF11 700 +#define SX1302_REG_RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF10 701 +#define SX1302_REG_RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF9 702 +#define SX1302_REG_RX_TOP_MODEM_CLOCK_GATE_OVERRIDE_3_CLK_OVERRIDE 703 +#define SX1302_REG_RX_TOP_MODEM_CLOCK_GATE_OVERRIDE_2_CLK_OVERRIDE 704 +#define SX1302_REG_RX_TOP_MODEM_CLOCK_GATE_OVERRIDE_1_CLK_OVERRIDE 705 +#define SX1302_REG_RX_TOP_MODEM_CLOCK_GATE_OVERRIDE_0_CLK_OVERRIDE 706 +#define SX1302_REG_RX_TOP_DUMMY2_DUMMY2 707 +#define SX1302_REG_RX_TOP_RX_BUFFER_DEBUG_MODE 708 +#define SX1302_REG_RX_TOP_RX_BUFFER_DIRECT_RAM_IF 709 +#define SX1302_REG_RX_TOP_RX_BUFFER_LEGACY_TIMESTAMP 710 +#define SX1302_REG_RX_TOP_RX_BUFFER_STORE_HEADER_ERR_META 711 +#define SX1302_REG_RX_TOP_RX_BUFFER_STORE_SYNC_FAIL_META 712 +#define SX1302_REG_RX_TOP_RX_BUFFER_TIMESTAMP_CFG_MAX_TS_METRICS 713 +#define SX1302_REG_RX_TOP_RX_BUFFER_IRQ_CTRL_MSB_RX_BUFFER_IRQ_THRESHOLD 714 +#define SX1302_REG_RX_TOP_RX_BUFFER_IRQ_CTRL_LSB_RX_BUFFER_IRQ_THRESHOLD 715 +#define SX1302_REG_RX_TOP_RX_BUFFER_LAST_ADDR_READ_MSB_LAST_ADDR_READ 716 +#define SX1302_REG_RX_TOP_RX_BUFFER_LAST_ADDR_READ_LSB_LAST_ADDR_READ 717 +#define SX1302_REG_RX_TOP_RX_BUFFER_LAST_ADDR_WRITE_MSB_LAST_ADDR_WRITE 718 +#define SX1302_REG_RX_TOP_RX_BUFFER_LAST_ADDR_WRITE_LSB_LAST_ADDR_WRITE 719 +#define SX1302_REG_RX_TOP_RX_BUFFER_NB_BYTES_MSB_RX_BUFFER_NB_BYTES 720 +#define SX1302_REG_RX_TOP_RX_BUFFER_NB_BYTES_LSB_RX_BUFFER_NB_BYTES 721 +#define SX1302_REG_RX_TOP_MULTI_SF_SYNC_ERR_PKT_CNT_MULTI_SF_SYNC_ERR_PKTS 722 +#define SX1302_REG_RX_TOP_MULTI_SF_PLD_ERR_PKT_CNT_MULTI_SF_PLD_ERR_PKTS 723 +#define SX1302_REG_RX_TOP_MULTI_SF_GOOD_PKT_CNT_MULTI_SF_GOOD_PKTS 724 +#define SX1302_REG_RX_TOP_SERV_MODEM_SYNC_ERR_PKT_CNT_SERV_MODEM_SYNC_ERR_PKTS 725 +#define SX1302_REG_RX_TOP_SERV_MODEM_PLD_ERR_PKT_CNT_SERV_MODEM_PLD_ERR_PKTS 726 +#define SX1302_REG_RX_TOP_SERV_MODEM_GOOD_PKT_CNT_SERV_MODEM_GOOD_PKTS 727 +#define SX1302_REG_RX_TOP_GFSK_MODEM_SYNC_ERR_PKT_CNT_GFSK_MODEM_SYNC_ERR_PKTS 728 +#define SX1302_REG_RX_TOP_GFSK_MODEM_PLD_ERR_PKT_CNT_GFSK_MODEM_PLD_ERR_PKTS 729 +#define SX1302_REG_RX_TOP_GFSK_MODEM_GOOD_PKT_CNT_GFSK_MODEM_GOOD_PKTS 730 +#define SX1302_REG_RX_TOP_BAD_MODEM_ID_WRITE_0_BAD_MODEM_ID_WRITE 731 +#define SX1302_REG_RX_TOP_BAD_MODEM_ID_WRITE_1_BAD_MODEM_ID_WRITE 732 +#define SX1302_REG_RX_TOP_BAD_MODEM_ID_WRITE_2_BAD_MODEM_ID_WRITE 733 +#define SX1302_REG_RX_TOP_BAD_MODEM_ID_READ_0_BAD_MODEM_ID_READ 734 +#define SX1302_REG_RX_TOP_BAD_MODEM_ID_READ_1_BAD_MODEM_ID_READ 735 +#define SX1302_REG_RX_TOP_BAD_MODEM_ID_READ_2_BAD_MODEM_ID_READ 736 +#define SX1302_REG_RX_TOP_CLOCK_GATE_OVERRIDE_0_CLK_OVERRIDE 737 +#define SX1302_REG_RX_TOP_SAMPLE_4_MSPS_LATCHED_125K_SAMPLE_4_MSPS_LATCHED_125K 738 +#define SX1302_REG_RX_TOP_DUMMY3_DUMMY3 739 +#define SX1302_REG_ARB_MCU_CTRL_CLK_EN 740 +#define SX1302_REG_ARB_MCU_CTRL_RADIO_RST 741 +#define SX1302_REG_ARB_MCU_CTRL_FORCE_HOST_FE_CTRL 742 +#define SX1302_REG_ARB_MCU_CTRL_MCU_CLEAR 743 +#define SX1302_REG_ARB_MCU_CTRL_HOST_PROG 744 +#define SX1302_REG_ARB_MCU_CTRL_PARITY_ERROR 745 +#define SX1302_REG_ARB_MCU_MCU_ARB_STATUS_MCU_ARB_STATUS 746 +#define SX1302_REG_ARB_MCU_UART_CFG_MSBF 747 +#define SX1302_REG_ARB_MCU_UART_CFG_PAR_EN 748 +#define SX1302_REG_ARB_MCU_UART_CFG_PAR_MODE 749 +#define SX1302_REG_ARB_MCU_UART_CFG_START_LEN 750 +#define SX1302_REG_ARB_MCU_UART_CFG_STOP_LEN 751 +#define SX1302_REG_ARB_MCU_UART_CFG_WORD_LEN 752 +#define SX1302_REG_ARB_MCU_UART_CFG2_BIT_RATE 753 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_CFG_0_ARB_DEBUG_CFG_0 754 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_CFG_1_ARB_DEBUG_CFG_1 755 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_CFG_2_ARB_DEBUG_CFG_2 756 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_CFG_3_ARB_DEBUG_CFG_3 757 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_STS_0_ARB_DEBUG_STS_0 758 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_STS_1_ARB_DEBUG_STS_1 759 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_STS_2_ARB_DEBUG_STS_2 760 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_STS_3_ARB_DEBUG_STS_3 761 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_STS_4_ARB_DEBUG_STS_4 762 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_STS_5_ARB_DEBUG_STS_5 763 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_STS_6_ARB_DEBUG_STS_6 764 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_STS_7_ARB_DEBUG_STS_7 765 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_STS_8_ARB_DEBUG_STS_8 766 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_STS_9_ARB_DEBUG_STS_9 767 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_STS_10_ARB_DEBUG_STS_10 768 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_STS_11_ARB_DEBUG_STS_11 769 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_STS_12_ARB_DEBUG_STS_12 770 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_STS_13_ARB_DEBUG_STS_13 771 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_STS_14_ARB_DEBUG_STS_14 772 +#define SX1302_REG_ARB_MCU_ARB_DEBUG_STS_15_ARB_DEBUG_STS_15 773 +#define SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_01_CHANNEL_1_OFFSET 774 +#define SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_01_CHANNEL_0_OFFSET 775 +#define SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_23_CHANNEL_3_OFFSET 776 +#define SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_23_CHANNEL_2_OFFSET 777 +#define SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_45_CHANNEL_5_OFFSET 778 +#define SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_45_CHANNEL_4_OFFSET 779 +#define SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_67_CHANNEL_7_OFFSET 780 +#define SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_67_CHANNEL_6_OFFSET 781 +#define SX1302_REG_ARB_MCU_DUMMY_DUMMY3 782 +#define SX1302_REG_RADIO_FE_GLBL_CTRL_DECIM_B_CLR 783 +#define SX1302_REG_RADIO_FE_GLBL_CTRL_DECIM_A_CLR 784 +#define SX1302_REG_RADIO_FE_CTRL0_RADIO_A_DC_NOTCH_EN 785 +#define SX1302_REG_RADIO_FE_CTRL0_RADIO_A_FORCE_HOST_FILTER_GAIN 786 +#define SX1302_REG_RADIO_FE_CTRL0_RADIO_A_HOST_FILTER_GAIN 787 +#define SX1302_REG_RADIO_FE_RSSI_DB_DEF_RADIO_A_RSSI_DB_DEFAULT_VALUE 788 +#define SX1302_REG_RADIO_FE_RSSI_DEC_DEF_RADIO_A_RSSI_DEC_DEFAULT_VALUE 789 +#define SX1302_REG_RADIO_FE_RSSI_DEC_RD_RADIO_A_RSSI_DEC_OUT 790 +#define SX1302_REG_RADIO_FE_RSSI_BB_RD_RADIO_A_RSSI_BB_OUT 791 +#define SX1302_REG_RADIO_FE_DEC_FILTER_RD_RADIO_A_DEC_FILTER_GAIN 792 +#define SX1302_REG_RADIO_FE_RSSI_BB_FILTER_ALPHA_RADIO_A_RSSI_BB_FILTER_ALPHA 793 +#define SX1302_REG_RADIO_FE_RSSI_DEC_FILTER_ALPHA_RADIO_A_RSSI_DEC_FILTER_ALPHA 794 +#define SX1302_REG_RADIO_FE_IQ_COMP_AMP_COEFF_RADIO_A_AMP_COEFF 795 +#define SX1302_REG_RADIO_FE_IQ_COMP_PHI_COEFF_RADIO_A_PHI_COEFF 796 +#define SX1302_REG_RADIO_FE_RADIO_DIO_TEST_MODE_RADIO_A_DIO_TEST_MODE 797 +#define SX1302_REG_RADIO_FE_RADIO_DIO_TEST_DIR_RADIO_A_DIO_TEST_DIR 798 +#define SX1302_REG_RADIO_FE_RADIO_DIO_DIR_RADIO_A_DIO_DIR 799 +#define SX1302_REG_RADIO_FE_CTRL0_RADIO_B_DC_NOTCH_EN 800 +#define SX1302_REG_RADIO_FE_CTRL0_RADIO_B_FORCE_HOST_FILTER_GAIN 801 +#define SX1302_REG_RADIO_FE_CTRL0_RADIO_B_HOST_FILTER_GAIN 802 +#define SX1302_REG_RADIO_FE_RSSI_DB_DEF_RADIO_B_RSSI_DB_DEFAULT_VALUE 803 +#define SX1302_REG_RADIO_FE_RSSI_DEC_DEF_RADIO_B_RSSI_DEC_DEFAULT_VALUE 804 +#define SX1302_REG_RADIO_FE_RSSI_DEC_RD_RADIO_B_RSSI_DEC_OUT 805 +#define SX1302_REG_RADIO_FE_RSSI_BB_RD_RADIO_B_RSSI_BB_OUT 806 +#define SX1302_REG_RADIO_FE_DEC_FILTER_RD_RADIO_B_DEC_FILTER_GAIN 807 +#define SX1302_REG_RADIO_FE_RSSI_BB_FILTER_ALPHA_RADIO_B_RSSI_BB_FILTER_ALPHA 808 +#define SX1302_REG_RADIO_FE_RSSI_DEC_FILTER_ALPHA_RADIO_B_RSSI_DEC_FILTER_ALPHA 809 +#define SX1302_REG_RADIO_FE_IQ_COMP_AMP_COEFF_RADIO_B_AMP_COEFF 810 +#define SX1302_REG_RADIO_FE_IQ_COMP_PHI_COEFF_RADIO_B_PHI_COEFF 811 +#define SX1302_REG_RADIO_FE_RADIO_DIO_TEST_MODE_RADIO_B_DIO_TEST_MODE 812 +#define SX1302_REG_RADIO_FE_RADIO_DIO_TEST_DIR_RADIO_B_DIO_TEST_DIR 813 +#define SX1302_REG_RADIO_FE_RADIO_DIO_DIR_RADIO_B_DIO_DIR 814 +#define SX1302_REG_RADIO_FE_SIG_ANA_CFG_VALID 815 +#define SX1302_REG_RADIO_FE_SIG_ANA_CFG_BUSY 816 +#define SX1302_REG_RADIO_FE_SIG_ANA_CFG_DURATION 817 +#define SX1302_REG_RADIO_FE_SIG_ANA_CFG_FORCE_HAL_CTRL 818 +#define SX1302_REG_RADIO_FE_SIG_ANA_CFG_START 819 +#define SX1302_REG_RADIO_FE_SIG_ANA_CFG_RADIO_SEL 820 +#define SX1302_REG_RADIO_FE_SIG_ANA_CFG_EN 821 +#define SX1302_REG_RADIO_FE_SIG_ANA_FREQ_FREQ 822 +#define SX1302_REG_RADIO_FE_SIG_ANA_ABS_MSB_CORR_ABS_OUT 823 +#define SX1302_REG_RADIO_FE_SIG_ANA_ABS_LSB_CORR_ABS_OUT 824 +#define SX1302_REG_RADIO_FE_DUMMY_DUMMY 825 +#define SX1302_REG_OTP_BYTE_ADDR_ADDR 826 +#define SX1302_REG_OTP_RD_DATA_RD_DATA 827 +#define SX1302_REG_OTP_STATUS_CHECKSUM_STATUS 828 +#define SX1302_REG_OTP_STATUS_FSM_READY 829 +#define SX1302_REG_OTP_CFG_ACCESS_MODE 830 +#define SX1302_REG_OTP_BIT_POS_POS 831 +#define SX1302_REG_OTP_PIN_CTRL_0_TM 832 +#define SX1302_REG_OTP_PIN_CTRL_0_STROBE 833 +#define SX1302_REG_OTP_PIN_CTRL_0_PGENB 834 +#define SX1302_REG_OTP_PIN_CTRL_0_LOAD 835 +#define SX1302_REG_OTP_PIN_CTRL_0_CSB 836 +#define SX1302_REG_OTP_PIN_CTRL_1_FSCK 837 +#define SX1302_REG_OTP_PIN_CTRL_1_FSI 838 +#define SX1302_REG_OTP_PIN_CTRL_1_FRST 839 +#define SX1302_REG_OTP_PIN_STATUS_FSO 840 +#define SX1302_REG_OTP_MODEM_EN_0_MODEM_EN 841 +#define SX1302_REG_OTP_MODEM_EN_1_MODEM_EN 842 +#define SX1302_REG_OTP_MODEM_SF_EN_SF_EN 843 +#define SX1302_REG_OTP_TIMESTAMP_EN_TIMESTAMP_EN 844 +#define SX1302_REG_OTP_DUMMY_DUMMY 845 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_LORA_SERVICE_FREQ_MSB_IF_FREQ_0 846 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_LORA_SERVICE_FREQ_LSB_IF_FREQ_0 847 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_LORA_SERVICE_RADIO_SEL_RADIO_SELECT 848 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG1_BW_START 849 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG1_AUTO_BW_RED 850 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG1_NO_FAST_START 851 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG1_BYPASS 852 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG1_ENABLE 853 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG2_BW_LOCKED 854 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG2_BW 855 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG3_BW_RED 856 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG4_IIR_DCC_TIME 857 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR1_0_FIR1_COEFF_0 858 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR1_1_FIR1_COEFF_1 859 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR1_2_FIR1_COEFF_2 860 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR1_3_FIR1_COEFF_3 861 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR1_4_FIR1_COEFF_4 862 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR1_5_FIR1_COEFF_5 863 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR1_6_FIR1_COEFF_6 864 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR1_7_FIR1_COEFF_7 865 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR2_0_FIR2_COEFF_0 866 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR2_1_FIR2_COEFF_1 867 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR2_2_FIR2_COEFF_2 868 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR2_3_FIR2_COEFF_3 869 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR2_4_FIR2_COEFF_4 870 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR2_5_FIR2_COEFF_5 871 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR2_6_FIR2_COEFF_6 872 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR2_7_FIR2_COEFF_7 873 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC0_RADIO_GAIN_RED_SEL 874 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC0_RADIO_GAIN_RED_DB 875 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC1_DC_COMP_EN 876 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC1_FORCE_DEFAULT_FIR 877 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC1_RSSI_EARLY_LATCH 878 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC1_FREEZE_ON_SYNC 879 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC2_DAGC_IN_COMP 880 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC2_DAGC_FIR_HYST 881 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC2_RSSI_MAX_SAMPLE 882 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC2_RSSI_MIN_SAMPLE 883 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_GAIN0_DAGC_FIR_FAST 884 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_GAIN0_FORCE_GAIN_FIR 885 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_GAIN0_GAIN_FIR1 886 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_GAIN0_GAIN_FIR2 887 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DAGC_CFG_TARGET_LVL 888 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DAGC_CFG_GAIN_INCR_STEP 889 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DAGC_CFG_GAIN_DROP_COMP 890 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DAGC_CFG_COMB_FILTER_EN 891 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DAGC_CFG_NO_FREEZE_START 892 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DAGC_CFG_FREEZE_ON_SYNC 893 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DAGC_CNT0_SAMPLE 894 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DAGC_CNT1_THR_M6 895 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DAGC_CNT2_THR_M12 896 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DAGC_CNT3_THR_M18 897 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DAGC_CNT4_GAIN 898 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DAGC_CNT4_FORCE_GAIN 899 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG0_MODEM_BW 900 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG0_MODEM_SF 901 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG1_PPM_OFFSET_HDR_CTRL 902 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG1_PPM_OFFSET 903 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG1_MODEM_EN 904 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG1_CODING_RATE 905 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN 906 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_MODEM_START 907 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_CADRXTX 908 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_IMPLICIT_HEADER 909 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_CRC_EN 910 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG3_PAYLOAD_LENGTH 911 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG4_INT_STEP_ORIDE_EN 912 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG4_INT_STEP_ORIDE 913 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG5_HEADER_DIFF_MODE 914 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG5_ZERO_PAD 915 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG6_PREAMBLE_SYMB_NB 916 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG7_PREAMBLE_SYMB_NB 917 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG8_AUTO_ACK_INT_DELAY 918 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG8_AUTO_ACK_RX 919 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG8_AUTO_ACK_TX 920 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG8_POST_PREAMBLE_GAP_LONG 921 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_CFG0_DFT_PEAK_EN 922 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_CFG0_CHIRP_INVERT 923 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_CFG0_SWAP_IQ 924 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_CFG0_CONTINUOUS 925 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_CFG1_DETECT_TIMEOUT 926 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_CFG2_AUTO_ACK_RANGE 927 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_CFG2_AUTO_ACK_DELAY 928 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_CFG3_RESTART_ON_HDR_ERR 929 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_CFG3_CLK_EN_RESYNC_DIN 930 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_CFG3_LLR_SCALE 931 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH0_PEAK1_POS 932 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH1_PEAK2_POS 933 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH2_FINETIME_ON_LAST 934 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH2_AUTO_SCALE 935 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH2_DROP_ON_SYNCH 936 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH2_GAIN 937 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH2_TIMEOUT_OPT 938 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING0_GAIN_P_HDR_RED 939 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING0_ROUNDING 940 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING0_POS_LIMIT 941 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING0_SUM_SIZE 942 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING0_MODE 943 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING1_GAIN_P_AUTO 944 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING1_GAIN_P_PAYLOAD 945 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING1_GAIN_P_PREAMB 946 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_EN 947 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_PAYLOAD 948 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_PREAMB 949 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING3_FINESYNCH_SUM 950 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING3_FINESYNCH_GAIN 951 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING3_GAIN_I_AUTO 952 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING4_GAIN_I_AUTO_MAX 953 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING4_GAIN_P_AUTO_MAX 954 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME0_FREQ_TO_TIME_DRIFT_MANT 955 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME1_FREQ_TO_TIME_DRIFT_MANT 956 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME2_FREQ_TO_TIME_DRIFT_EXP 957 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_FREQ_DELTA 958 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_FINE_DELTA 959 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_FREQ_ERROR 960 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_TIME_SYMB 961 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_TIME_OFFSET 962 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_DETECT 963 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME4_FREQ_TO_TIME_INVERT_RNG 964 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TRACK0_FREQ_TRACK_FINE 965 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TRACK0_FREQ_TRACK_HDR_SKIP 966 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TRACK0_FREQ_TRACK_EN 967 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TRACK1_FREQ_SYNCH_GAIN 968 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TRACK1_FREQ_TRACK_AUTO_THR 969 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TRACK2_SNR_MIN_WINDOW 970 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TRACK2_GAIN_AUTO_SNR_MIN 971 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TRACK2_FREQ_SYNCH_THR 972 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_MSP0_MSP_PNR 973 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_MSP1_MSP2_PNR 974 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_MSP2_MSP2_PEAK_NB 975 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_MSP2_MSP_PEAK_NB 976 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_MSP3_ACC_MIN2 977 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_MSP3_ACC_WIN_LEN 978 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_MSP3_MSP_POS_SEL 979 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_MSP3_MSP_CNT_MODE 980 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_USE_GAIN_SYMB 981 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR 982 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC2_NOISE_COEFF 983 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC2_ACC_COEFF 984 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC2_ACC_2_SAME_PEAKS 985 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC2_ACC_AUTO_RESCALE 986 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC2_ACC_PEAK_POS_SEL 987 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC2_ACC_PEAK_SUM_EN 988 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC3_MIN_SINGLE_PEAK 989 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TIMESTAMP_SEL_SNR_MIN 990 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TIMESTAMP_ENABLE 991 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TIMESTAMP_NB_SYMB 992 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_CLOCK_GATE_OVERRIDE_FSK_TRANSPOSE_CLK_OVERRIDE 993 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_CLOCK_GATE_OVERRIDE_FSK_MODEM_CLK_OVERRIDE 994 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_CLOCK_GATE_OVERRIDE_TRANSPOSE_CLK_OVERRIDE 995 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_CLOCK_GATE_OVERRIDE_MODEM_CLK_OVERRIDE 996 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DUMMY0_DUMMY0 997 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_FREQ_MSB_IF_FREQ_0 998 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_FREQ_LSB_IF_FREQ_0 999 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_CRC_IBM 1000 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_DCFREE_ENC 1001 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_CRC_EN 1002 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_PKT_MODE 1003 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_1_ADRS_COMP 1004 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_1_PSIZE 1005 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_1_CH_BW_EXPO 1006 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_MODEM_INVERT_IQ 1007 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_AUTO_AFC 1008 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_RADIO_SELECT 1009 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_RX_INVERT 1010 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_4_RSSI_LENGTH 1011 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_4_ERROR_OSR_TOL 1012 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_NODE_ADRS_NODE_ADRS 1013 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_BROADCAST_BROADCAST 1014 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_PKT_LENGTH_PKT_LENGTH 1015 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_TIMEOUT_MSB_TIMEOUT 1016 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_TIMEOUT_LSB_TIMEOUT 1017 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BIT_RATE_MSB_BIT_RATE 1018 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BIT_RATE_LSB_BIT_RATE 1019 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE7_FSK_REF_PATTERN 1020 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE6_FSK_REF_PATTERN 1021 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE5_FSK_REF_PATTERN 1022 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE4_FSK_REF_PATTERN 1023 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE3_FSK_REF_PATTERN 1024 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE2_FSK_REF_PATTERN 1025 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE1_FSK_REF_PATTERN 1026 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE0_FSK_REF_PATTERN 1027 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_RSSI_FILTER_ALPHA_FSK_RSSI_FILTER_ALPHA 1028 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DUMMY1_DUMMY1 1029 +#define SX1302_REG_CAPTURE_RAM_CAPTURE_CFG_ENABLE 1030 +#define SX1302_REG_CAPTURE_RAM_CAPTURE_CFG_CAPTUREWRAP 1031 +#define SX1302_REG_CAPTURE_RAM_CAPTURE_CFG_CAPTUREFORCETRIGGER 1032 +#define SX1302_REG_CAPTURE_RAM_CAPTURE_CFG_CAPTURESTART 1033 +#define SX1302_REG_CAPTURE_RAM_CAPTURE_CFG_RAMCONFIG 1034 +#define SX1302_REG_CAPTURE_RAM_CAPTURE_SOURCE_A_SOURCEMUX 1035 +#define SX1302_REG_CAPTURE_RAM_CAPTURE_SOURCE_B_SOURCEMUX 1036 +#define SX1302_REG_CAPTURE_RAM_CAPTURE_PERIOD_0_CAPTUREPERIOD 1037 +#define SX1302_REG_CAPTURE_RAM_CAPTURE_PERIOD_1_CAPTUREPERIOD 1038 +#define SX1302_REG_CAPTURE_RAM_STATUS_CAPCOMPLETE 1039 +#define SX1302_REG_CAPTURE_RAM_LAST_RAM_ADDR_0_LASTRAMADDR 1040 +#define SX1302_REG_CAPTURE_RAM_LAST_RAM_ADDR_1_LASTRAMADDR 1041 +#define SX1302_REG_CAPTURE_RAM_CLOCK_GATE_OVERRIDE_CLK_OVERRIDE 1042 +#define SX1302_REG_CAPTURE_RAM_DUMMY0_DUMMY0 1043 + +#define LGW_TOTALREGS 1044 + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +#define SX1302_REG_TX_TOP_TX_TRIG_TX_FSM_CLR(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_TRIG_TX_FSM_CLR : \ + SX1302_REG_TX_TOP_B_TX_TRIG_TX_FSM_CLR) +#define SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_GPS(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_TRIG_TX_TRIG_GPS : \ + SX1302_REG_TX_TOP_B_TX_TRIG_TX_TRIG_GPS) +#define SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_DELAYED(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_TRIG_TX_TRIG_DELAYED : \ + SX1302_REG_TX_TOP_B_TX_TRIG_TX_TRIG_DELAYED) +#define SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_IMMEDIATE(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_TRIG_TX_TRIG_IMMEDIATE : \ + SX1302_REG_TX_TOP_B_TX_TRIG_TX_TRIG_IMMEDIATE) +#define SX1302_REG_TX_TOP_TIMER_TRIG_BYTE3_TIMER_DELAYED_TRIG(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TIMER_TRIG_BYTE3_TIMER_DELAYED_TRIG : \ + SX1302_REG_TX_TOP_B_TIMER_TRIG_BYTE3_TIMER_DELAYED_TRIG) +#define SX1302_REG_TX_TOP_TIMER_TRIG_BYTE2_TIMER_DELAYED_TRIG(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TIMER_TRIG_BYTE2_TIMER_DELAYED_TRIG : \ + SX1302_REG_TX_TOP_B_TIMER_TRIG_BYTE2_TIMER_DELAYED_TRIG) +#define SX1302_REG_TX_TOP_TIMER_TRIG_BYTE1_TIMER_DELAYED_TRIG(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TIMER_TRIG_BYTE1_TIMER_DELAYED_TRIG : \ + SX1302_REG_TX_TOP_B_TIMER_TRIG_BYTE1_TIMER_DELAYED_TRIG) +#define SX1302_REG_TX_TOP_TIMER_TRIG_BYTE0_TIMER_DELAYED_TRIG(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TIMER_TRIG_BYTE0_TIMER_DELAYED_TRIG : \ + SX1302_REG_TX_TOP_B_TIMER_TRIG_BYTE0_TIMER_DELAYED_TRIG) +#define SX1302_REG_TX_TOP_TX_START_DELAY_MSB_TX_START_DELAY(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_START_DELAY_MSB_TX_START_DELAY : \ + SX1302_REG_TX_TOP_B_TX_START_DELAY_MSB_TX_START_DELAY) +#define SX1302_REG_TX_TOP_TX_START_DELAY_LSB_TX_START_DELAY(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_START_DELAY_LSB_TX_START_DELAY : \ + SX1302_REG_TX_TOP_B_TX_START_DELAY_LSB_TX_START_DELAY) +#define SX1302_REG_TX_TOP_TX_CTRL_WRITE_BUFFER(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_CTRL_WRITE_BUFFER : \ + SX1302_REG_TX_TOP_B_TX_CTRL_WRITE_BUFFER) +#define SX1302_REG_TX_TOP_TX_RAMP_DURATION_TX_RAMP_DURATION(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_RAMP_DURATION_TX_RAMP_DURATION : \ + SX1302_REG_TX_TOP_B_TX_RAMP_DURATION_TX_RAMP_DURATION) +#define SX1302_REG_TX_TOP_GEN_CFG_0_MODULATION_TYPE(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_GEN_CFG_0_MODULATION_TYPE : \ + SX1302_REG_TX_TOP_B_GEN_CFG_0_MODULATION_TYPE) +#define SX1302_REG_TX_TOP_TEST_0_TX_ACTIVE_CTRL(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TEST_0_TX_ACTIVE_CTRL : \ + SX1302_REG_TX_TOP_B_TEST_0_TX_ACTIVE_CTRL) +#define SX1302_REG_TX_TOP_TEST_0_TX_ACTIVE_SEL(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TEST_0_TX_ACTIVE_SEL : \ + SX1302_REG_TX_TOP_B_TEST_0_TX_ACTIVE_SEL) +#define SX1302_REG_TX_TOP_TX_FLAG_TX_TIMEOUT(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_FLAG_TX_TIMEOUT : \ + SX1302_REG_TX_TOP_B_TX_FLAG_TX_TIMEOUT) +#define SX1302_REG_TX_TOP_TX_FLAG_PKT_DONE(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_FLAG_PKT_DONE : \ + SX1302_REG_TX_TOP_B_TX_FLAG_PKT_DONE) +#define SX1302_REG_TX_TOP_AGC_TX_BW_AGC_TX_BW(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_AGC_TX_BW_AGC_TX_BW : \ + SX1302_REG_TX_TOP_B_AGC_TX_BW_AGC_TX_BW) +#define SX1302_REG_TX_TOP_AGC_TX_PWR_AGC_TX_PWR(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_AGC_TX_PWR_AGC_TX_PWR : \ + SX1302_REG_TX_TOP_B_AGC_TX_PWR_AGC_TX_PWR) +#define SX1302_REG_TX_TOP_TIMEOUT_CNT_BYTE_2_TIMEOUT_CNT(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TIMEOUT_CNT_BYTE_2_TIMEOUT_CNT : \ + SX1302_REG_TX_TOP_B_TIMEOUT_CNT_BYTE_2_TIMEOUT_CNT) +#define SX1302_REG_TX_TOP_TIMEOUT_CNT_BYTE_1_TIMEOUT_CNT(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TIMEOUT_CNT_BYTE_1_TIMEOUT_CNT : \ + SX1302_REG_TX_TOP_B_TIMEOUT_CNT_BYTE_1_TIMEOUT_CNT) +#define SX1302_REG_TX_TOP_TIMEOUT_CNT_BYTE_0_TIMEOUT_CNT(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TIMEOUT_CNT_BYTE_0_TIMEOUT_CNT : \ + SX1302_REG_TX_TOP_B_TIMEOUT_CNT_BYTE_0_TIMEOUT_CNT) +#define SX1302_REG_TX_TOP_TX_FSM_STATUS_TX_STATUS(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_FSM_STATUS_TX_STATUS : \ + SX1302_REG_TX_TOP_B_TX_FSM_STATUS_TX_STATUS) +#define SX1302_REG_TX_TOP_DUMMY_CONTROL_DUMMY(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_DUMMY_CONTROL_DUMMY : \ + SX1302_REG_TX_TOP_B_DUMMY_CONTROL_DUMMY) +#define SX1302_REG_TX_TOP_TX_RFFE_IF_CTRL_PLL_DIV_CTRL(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_PLL_DIV_CTRL : \ + SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_PLL_DIV_CTRL) +#define SX1302_REG_TX_TOP_TX_RFFE_IF_CTRL_TX_CLK_EDGE(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_CLK_EDGE : \ + SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_CLK_EDGE) +#define SX1302_REG_TX_TOP_TX_RFFE_IF_CTRL_TX_MODE(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_MODE : \ + SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_MODE) +#define SX1302_REG_TX_TOP_TX_RFFE_IF_CTRL_TX_IF_DST(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_IF_DST : \ + SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_IF_DST) +#define SX1302_REG_TX_TOP_TX_RFFE_IF_CTRL_TX_IF_SRC(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_IF_SRC : \ + SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_IF_SRC) +#define SX1302_REG_TX_TOP_TX_RFFE_IF_CTRL2_SX125X_IQ_INVERT(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL2_SX125X_IQ_INVERT : \ + SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL2_SX125X_IQ_INVERT) +#define SX1302_REG_TX_TOP_TX_RFFE_IF_CTRL2_PLL_DIV_CTRL_AGC(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL2_PLL_DIV_CTRL_AGC : \ + SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL2_PLL_DIV_CTRL_AGC) +#define SX1302_REG_TX_TOP_TX_RFFE_IF_IQ_GAIN_IQ_GAIN(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_RFFE_IF_IQ_GAIN_IQ_GAIN : \ + SX1302_REG_TX_TOP_B_TX_RFFE_IF_IQ_GAIN_IQ_GAIN) +#define SX1302_REG_TX_TOP_TX_RFFE_IF_I_OFFSET_I_OFFSET(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_RFFE_IF_I_OFFSET_I_OFFSET : \ + SX1302_REG_TX_TOP_B_TX_RFFE_IF_I_OFFSET_I_OFFSET) +#define SX1302_REG_TX_TOP_TX_RFFE_IF_Q_OFFSET_Q_OFFSET(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_RFFE_IF_Q_OFFSET_Q_OFFSET : \ + SX1302_REG_TX_TOP_B_TX_RFFE_IF_Q_OFFSET_Q_OFFSET) +#define SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_RF_H_FREQ_RF(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_RFFE_IF_FREQ_RF_H_FREQ_RF : \ + SX1302_REG_TX_TOP_B_TX_RFFE_IF_FREQ_RF_H_FREQ_RF) +#define SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_RF_M_FREQ_RF(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_RFFE_IF_FREQ_RF_M_FREQ_RF : \ + SX1302_REG_TX_TOP_B_TX_RFFE_IF_FREQ_RF_M_FREQ_RF) +#define SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_RF_L_FREQ_RF(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_RFFE_IF_FREQ_RF_L_FREQ_RF : \ + SX1302_REG_TX_TOP_B_TX_RFFE_IF_FREQ_RF_L_FREQ_RF) +#define SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_H_FREQ_DEV(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_RFFE_IF_FREQ_DEV_H_FREQ_DEV : \ + SX1302_REG_TX_TOP_B_TX_RFFE_IF_FREQ_DEV_H_FREQ_DEV) +#define SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_L_FREQ_DEV(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_RFFE_IF_FREQ_DEV_L_FREQ_DEV : \ + SX1302_REG_TX_TOP_B_TX_RFFE_IF_FREQ_DEV_L_FREQ_DEV) +#define SX1302_REG_TX_TOP_TX_RFFE_IF_TEST_MOD_FREQ(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_RFFE_IF_TEST_MOD_FREQ : \ + SX1302_REG_TX_TOP_B_TX_RFFE_IF_TEST_MOD_FREQ) +#define SX1302_REG_TX_TOP_DUMMY_MODULATOR_DUMMY(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_DUMMY_MODULATOR_DUMMY : \ + SX1302_REG_TX_TOP_B_DUMMY_MODULATOR_DUMMY) +#define SX1302_REG_TX_TOP_FSK_PKT_LEN_PKT_LENGTH(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_PKT_LEN_PKT_LENGTH : \ + SX1302_REG_TX_TOP_B_FSK_PKT_LEN_PKT_LENGTH) +#define SX1302_REG_TX_TOP_FSK_CFG_0_TX_CONT(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_CFG_0_TX_CONT : \ + SX1302_REG_TX_TOP_B_FSK_CFG_0_TX_CONT) +#define SX1302_REG_TX_TOP_FSK_CFG_0_CRC_IBM(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_CFG_0_CRC_IBM : \ + SX1302_REG_TX_TOP_B_FSK_CFG_0_CRC_IBM) +#define SX1302_REG_TX_TOP_FSK_CFG_0_DCFREE_ENC(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_CFG_0_DCFREE_ENC : \ + SX1302_REG_TX_TOP_B_FSK_CFG_0_DCFREE_ENC) +#define SX1302_REG_TX_TOP_FSK_CFG_0_CRC_EN(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_CFG_0_CRC_EN : \ + SX1302_REG_TX_TOP_B_FSK_CFG_0_CRC_EN) +#define SX1302_REG_TX_TOP_FSK_CFG_0_PKT_MODE(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_CFG_0_PKT_MODE : \ + SX1302_REG_TX_TOP_B_FSK_CFG_0_PKT_MODE) +#define SX1302_REG_TX_TOP_FSK_PREAMBLE_SIZE_MSB_PREAMBLE_SIZE(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_PREAMBLE_SIZE_MSB_PREAMBLE_SIZE : \ + SX1302_REG_TX_TOP_B_FSK_PREAMBLE_SIZE_MSB_PREAMBLE_SIZE) +#define SX1302_REG_TX_TOP_FSK_PREAMBLE_SIZE_LSB_PREAMBLE_SIZE(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_PREAMBLE_SIZE_LSB_PREAMBLE_SIZE : \ + SX1302_REG_TX_TOP_B_FSK_PREAMBLE_SIZE_LSB_PREAMBLE_SIZE) +#define SX1302_REG_TX_TOP_FSK_BIT_RATE_MSB_BIT_RATE(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_BIT_RATE_MSB_BIT_RATE : \ + SX1302_REG_TX_TOP_B_FSK_BIT_RATE_MSB_BIT_RATE) +#define SX1302_REG_TX_TOP_FSK_BIT_RATE_LSB_BIT_RATE(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_BIT_RATE_LSB_BIT_RATE : \ + SX1302_REG_TX_TOP_B_FSK_BIT_RATE_LSB_BIT_RATE) +#define SX1302_REG_TX_TOP_FSK_MOD_FSK_REF_PATTERN_SIZE(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_MOD_FSK_REF_PATTERN_SIZE : \ + SX1302_REG_TX_TOP_B_FSK_MOD_FSK_REF_PATTERN_SIZE) +#define SX1302_REG_TX_TOP_FSK_MOD_FSK_PREAMBLE_SEQ(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_MOD_FSK_PREAMBLE_SEQ : \ + SX1302_REG_TX_TOP_B_FSK_MOD_FSK_PREAMBLE_SEQ) +#define SX1302_REG_TX_TOP_FSK_MOD_FSK_REF_PATTERN_EN(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_MOD_FSK_REF_PATTERN_EN : \ + SX1302_REG_TX_TOP_B_FSK_MOD_FSK_REF_PATTERN_EN) +#define SX1302_REG_TX_TOP_FSK_MOD_FSK_GAUSSIAN_SELECT_BT(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_MOD_FSK_GAUSSIAN_SELECT_BT : \ + SX1302_REG_TX_TOP_B_FSK_MOD_FSK_GAUSSIAN_SELECT_BT) +#define SX1302_REG_TX_TOP_FSK_MOD_FSK_GAUSSIAN_EN(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_MOD_FSK_GAUSSIAN_EN : \ + SX1302_REG_TX_TOP_B_FSK_MOD_FSK_GAUSSIAN_EN) +#define SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE7_FSK_REF_PATTERN(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_REF_PATTERN_BYTE7_FSK_REF_PATTERN : \ + SX1302_REG_TX_TOP_B_FSK_REF_PATTERN_BYTE7_FSK_REF_PATTERN) +#define SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE6_FSK_REF_PATTERN(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_REF_PATTERN_BYTE6_FSK_REF_PATTERN : \ + SX1302_REG_TX_TOP_B_FSK_REF_PATTERN_BYTE6_FSK_REF_PATTERN) +#define SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE5_FSK_REF_PATTERN(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_REF_PATTERN_BYTE5_FSK_REF_PATTERN : \ + SX1302_REG_TX_TOP_B_FSK_REF_PATTERN_BYTE5_FSK_REF_PATTERN) +#define SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE4_FSK_REF_PATTERN(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_REF_PATTERN_BYTE4_FSK_REF_PATTERN : \ + SX1302_REG_TX_TOP_B_FSK_REF_PATTERN_BYTE4_FSK_REF_PATTERN) +#define SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE3_FSK_REF_PATTERN(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_REF_PATTERN_BYTE3_FSK_REF_PATTERN : \ + SX1302_REG_TX_TOP_B_FSK_REF_PATTERN_BYTE3_FSK_REF_PATTERN) +#define SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE2_FSK_REF_PATTERN(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_REF_PATTERN_BYTE2_FSK_REF_PATTERN : \ + SX1302_REG_TX_TOP_B_FSK_REF_PATTERN_BYTE2_FSK_REF_PATTERN) +#define SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE1_FSK_REF_PATTERN(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_REF_PATTERN_BYTE1_FSK_REF_PATTERN : \ + SX1302_REG_TX_TOP_B_FSK_REF_PATTERN_BYTE1_FSK_REF_PATTERN) +#define SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE0_FSK_REF_PATTERN(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FSK_REF_PATTERN_BYTE0_FSK_REF_PATTERN : \ + SX1302_REG_TX_TOP_B_FSK_REF_PATTERN_BYTE0_FSK_REF_PATTERN) +#define SX1302_REG_TX_TOP_DUMMY_GSFK_DUMMY(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_DUMMY_GSFK_DUMMY : \ + SX1302_REG_TX_TOP_B_DUMMY_GSFK_DUMMY) +#define SX1302_REG_TX_TOP_TXRX_CFG0_0_MODEM_BW(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG0_0_MODEM_BW : \ + SX1302_REG_TX_TOP_B_TXRX_CFG0_0_MODEM_BW) +#define SX1302_REG_TX_TOP_TXRX_CFG0_0_MODEM_SF(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG0_0_MODEM_SF : \ + SX1302_REG_TX_TOP_B_TXRX_CFG0_0_MODEM_SF) +#define SX1302_REG_TX_TOP_TXRX_CFG0_1_PPM_OFFSET_HDR_CTRL(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG0_1_PPM_OFFSET_HDR_CTRL : \ + SX1302_REG_TX_TOP_B_TXRX_CFG0_1_PPM_OFFSET_HDR_CTRL) +#define SX1302_REG_TX_TOP_TXRX_CFG0_1_PPM_OFFSET(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG0_1_PPM_OFFSET : \ + SX1302_REG_TX_TOP_B_TXRX_CFG0_1_PPM_OFFSET) +#define SX1302_REG_TX_TOP_TXRX_CFG0_1_POST_PREAMBLE_GAP_LONG(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG0_1_POST_PREAMBLE_GAP_LONG : \ + SX1302_REG_TX_TOP_B_TXRX_CFG0_1_POST_PREAMBLE_GAP_LONG) +#define SX1302_REG_TX_TOP_TXRX_CFG0_1_CODING_RATE(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG0_1_CODING_RATE : \ + SX1302_REG_TX_TOP_B_TXRX_CFG0_1_CODING_RATE) +#define SX1302_REG_TX_TOP_TXRX_CFG0_2_FINE_SYNCH_EN(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG0_2_FINE_SYNCH_EN : \ + SX1302_REG_TX_TOP_B_TXRX_CFG0_2_FINE_SYNCH_EN) +#define SX1302_REG_TX_TOP_TXRX_CFG0_2_MODEM_EN(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG0_2_MODEM_EN : \ + SX1302_REG_TX_TOP_B_TXRX_CFG0_2_MODEM_EN) +#define SX1302_REG_TX_TOP_TXRX_CFG0_2_CADRXTX(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG0_2_CADRXTX : \ + SX1302_REG_TX_TOP_B_TXRX_CFG0_2_CADRXTX) +#define SX1302_REG_TX_TOP_TXRX_CFG0_2_IMPLICIT_HEADER(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG0_2_IMPLICIT_HEADER : \ + SX1302_REG_TX_TOP_B_TXRX_CFG0_2_IMPLICIT_HEADER) +#define SX1302_REG_TX_TOP_TXRX_CFG0_2_CRC_EN(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG0_2_CRC_EN : \ + SX1302_REG_TX_TOP_B_TXRX_CFG0_2_CRC_EN) +#define SX1302_REG_TX_TOP_TXRX_CFG0_3_PAYLOAD_LENGTH(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG0_3_PAYLOAD_LENGTH : \ + SX1302_REG_TX_TOP_B_TXRX_CFG0_3_PAYLOAD_LENGTH) +#define SX1302_REG_TX_TOP_TXRX_CFG1_0_INT_STEP_ORIDE_EN(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG1_0_INT_STEP_ORIDE_EN : \ + SX1302_REG_TX_TOP_B_TXRX_CFG1_0_INT_STEP_ORIDE_EN) +#define SX1302_REG_TX_TOP_TXRX_CFG1_0_INT_STEP_ORIDE(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG1_0_INT_STEP_ORIDE : \ + SX1302_REG_TX_TOP_B_TXRX_CFG1_0_INT_STEP_ORIDE) +#define SX1302_REG_TX_TOP_TXRX_CFG1_1_MODEM_START(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG1_1_MODEM_START : \ + SX1302_REG_TX_TOP_B_TXRX_CFG1_1_MODEM_START) +#define SX1302_REG_TX_TOP_TXRX_CFG1_1_HEADER_DIFF_MODE(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG1_1_HEADER_DIFF_MODE : \ + SX1302_REG_TX_TOP_B_TXRX_CFG1_1_HEADER_DIFF_MODE) +#define SX1302_REG_TX_TOP_TXRX_CFG1_1_ZERO_PAD(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG1_1_ZERO_PAD : \ + SX1302_REG_TX_TOP_B_TXRX_CFG1_1_ZERO_PAD) +#define SX1302_REG_TX_TOP_TXRX_CFG1_2_PREAMBLE_SYMB_NB(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG1_2_PREAMBLE_SYMB_NB : \ + SX1302_REG_TX_TOP_B_TXRX_CFG1_2_PREAMBLE_SYMB_NB) +#define SX1302_REG_TX_TOP_TXRX_CFG1_3_PREAMBLE_SYMB_NB(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG1_3_PREAMBLE_SYMB_NB : \ + SX1302_REG_TX_TOP_B_TXRX_CFG1_3_PREAMBLE_SYMB_NB) +#define SX1302_REG_TX_TOP_TXRX_CFG1_4_AUTO_ACK_INT_DELAY(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG1_4_AUTO_ACK_INT_DELAY : \ + SX1302_REG_TX_TOP_B_TXRX_CFG1_4_AUTO_ACK_INT_DELAY) +#define SX1302_REG_TX_TOP_TXRX_CFG1_4_AUTO_ACK_RX(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG1_4_AUTO_ACK_RX : \ + SX1302_REG_TX_TOP_B_TXRX_CFG1_4_AUTO_ACK_RX) +#define SX1302_REG_TX_TOP_TXRX_CFG1_4_AUTO_ACK_TX(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TXRX_CFG1_4_AUTO_ACK_TX : \ + SX1302_REG_TX_TOP_B_TXRX_CFG1_4_AUTO_ACK_TX) +#define SX1302_REG_TX_TOP_TX_CFG0_0_CHIRP_LOWPASS(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_CFG0_0_CHIRP_LOWPASS : \ + SX1302_REG_TX_TOP_B_TX_CFG0_0_CHIRP_LOWPASS) +#define SX1302_REG_TX_TOP_TX_CFG0_0_PPM_OFFSET_SIG(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_CFG0_0_PPM_OFFSET_SIG : \ + SX1302_REG_TX_TOP_B_TX_CFG0_0_PPM_OFFSET_SIG) +#define SX1302_REG_TX_TOP_TX_CFG0_0_CONTCHIRP(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_CFG0_0_CONTCHIRP : \ + SX1302_REG_TX_TOP_B_TX_CFG0_0_CONTCHIRP) +#define SX1302_REG_TX_TOP_TX_CFG0_0_CHIRP_INVERT(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_CFG0_0_CHIRP_INVERT : \ + SX1302_REG_TX_TOP_B_TX_CFG0_0_CHIRP_INVERT) +#define SX1302_REG_TX_TOP_TX_CFG0_0_CONTINUOUS(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_CFG0_0_CONTINUOUS : \ + SX1302_REG_TX_TOP_B_TX_CFG0_0_CONTINUOUS) +#define SX1302_REG_TX_TOP_TX_CFG0_1_POWER_RANGING(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_CFG0_1_POWER_RANGING : \ + SX1302_REG_TX_TOP_B_TX_CFG0_1_POWER_RANGING) +#define SX1302_REG_TX_TOP_TX_CFG1_0_FRAME_NB(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_CFG1_0_FRAME_NB : \ + SX1302_REG_TX_TOP_B_TX_CFG1_0_FRAME_NB) +#define SX1302_REG_TX_TOP_TX_CFG1_1_HOP_CTRL(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_CFG1_1_HOP_CTRL : \ + SX1302_REG_TX_TOP_B_TX_CFG1_1_HOP_CTRL) +#define SX1302_REG_TX_TOP_TX_CFG1_1_IFS(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_TX_CFG1_1_IFS : \ + SX1302_REG_TX_TOP_B_TX_CFG1_1_IFS) +#define SX1302_REG_TX_TOP_FRAME_SYNCH_0_AUTO_SCALE(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FRAME_SYNCH_0_AUTO_SCALE : \ + SX1302_REG_TX_TOP_B_FRAME_SYNCH_0_AUTO_SCALE) +#define SX1302_REG_TX_TOP_FRAME_SYNCH_0_DROP_ON_SYNCH(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FRAME_SYNCH_0_DROP_ON_SYNCH : \ + SX1302_REG_TX_TOP_B_FRAME_SYNCH_0_DROP_ON_SYNCH) +#define SX1302_REG_TX_TOP_FRAME_SYNCH_0_GAIN(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FRAME_SYNCH_0_GAIN : \ + SX1302_REG_TX_TOP_B_FRAME_SYNCH_0_GAIN) +#define SX1302_REG_TX_TOP_FRAME_SYNCH_0_PEAK1_POS(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FRAME_SYNCH_0_PEAK1_POS : \ + SX1302_REG_TX_TOP_B_FRAME_SYNCH_0_PEAK1_POS) +#define SX1302_REG_TX_TOP_FRAME_SYNCH_1_FINETIME_ON_LAST(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FRAME_SYNCH_1_FINETIME_ON_LAST : \ + SX1302_REG_TX_TOP_B_FRAME_SYNCH_1_FINETIME_ON_LAST) +#define SX1302_REG_TX_TOP_FRAME_SYNCH_1_TIMEOUT_OPT(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FRAME_SYNCH_1_TIMEOUT_OPT : \ + SX1302_REG_TX_TOP_B_FRAME_SYNCH_1_TIMEOUT_OPT) +#define SX1302_REG_TX_TOP_FRAME_SYNCH_1_PEAK2_POS(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_FRAME_SYNCH_1_PEAK2_POS : \ + SX1302_REG_TX_TOP_B_FRAME_SYNCH_1_PEAK2_POS) +#define SX1302_REG_TX_TOP_LORA_TX_STATE_STATUS(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_LORA_TX_STATE_STATUS : \ + SX1302_REG_TX_TOP_B_LORA_TX_STATE_STATUS) +#define SX1302_REG_TX_TOP_LORA_TX_FLAG_FRAME_DONE(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_LORA_TX_FLAG_FRAME_DONE : \ + SX1302_REG_TX_TOP_B_LORA_TX_FLAG_FRAME_DONE) +#define SX1302_REG_TX_TOP_LORA_TX_FLAG_CONT_DONE(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_LORA_TX_FLAG_CONT_DONE : \ + SX1302_REG_TX_TOP_B_LORA_TX_FLAG_CONT_DONE) +#define SX1302_REG_TX_TOP_LORA_TX_FLAG_PLD_DONE(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_LORA_TX_FLAG_PLD_DONE : \ + SX1302_REG_TX_TOP_B_LORA_TX_FLAG_PLD_DONE) +#define SX1302_REG_TX_TOP_DUMMY_LORA_DUMMY(rf_chain) ((rf_chain == 0) ? \ + SX1302_REG_TX_TOP_A_DUMMY_LORA_DUMMY : \ + SX1302_REG_TX_TOP_B_DUMMY_LORA_DUMMY) + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +/** +@brief Connect LoRa concentrator by opening SPI link +@param spidev_path path to the SPI device to be used to connect to the SX1302 +@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR) +*/ +int lgw_connect(const char * spidev_path); + +/** +@brief Disconnect LoRa concentrator by closing SPI link +@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR) +*/ +int lgw_disconnect(void); + +/** +@brief LoRa concentrator register write +@param register_id register number in the data structure describing registers +@param reg_value signed value to write to the register (for u32, use cast) +@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR) +*/ +int lgw_reg_w(uint16_t register_id, int32_t reg_value); + +/** +@brief LoRa concentrator register read +@param register_id register number in the data structure describing registers +@param reg_value pointer to a variable where to write register read value +@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR) +*/ +int lgw_reg_r(uint16_t register_id, int32_t *reg_value); + +/** +@brief LoRa concentrator register burst write +@param register_id register number in the data structure describing registers +@param data pointer to byte array that will be sent to the LoRa concentrator +@param size size of the transfer, in byte(s) +@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR) +*/ +int lgw_reg_wb(uint16_t register_id, uint8_t *data, uint16_t size); + +/** +@brief LoRa concentrator register burst read +@param register_id register number in the data structure describing registers +@param data pointer to byte array that will be written from the LoRa concentrator +@param size size of the transfer, in byte(s) +@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR) +*/ +int lgw_reg_rb(uint16_t register_id, uint8_t *data, uint16_t size); + +/** +TODO +*/ +int lgw_mem_wb(uint16_t mem_addr, const uint8_t *data, uint16_t size); + +/** +TODO +*/ +int lgw_mem_rb(uint16_t mem_addr, uint8_t *data, uint16_t size, bool fifo_mode); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_spi.h b/libloragw/inc/loragw_spi.h new file mode 100644 index 0000000..c3f11ec --- /dev/null +++ b/libloragw/inc/loragw_spi.h @@ -0,0 +1,102 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Host specific functions to address the LoRa concentrator registers through + a SPI interface. + Single-byte read/write and burst read/write. + Could be used with multiple SPI ports in parallel (explicit file descriptor) + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORAGW_SPI_H +#define _LORAGW_SPI_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types*/ + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +#define LGW_SPI_SUCCESS 0 +#define LGW_SPI_ERROR -1 +#define LGW_BURST_CHUNK 1024 + +#define SPI_SPEED 2000000 + +#define LGW_SPI_MUX_TARGET_SX1302 0x00 +#define LGW_SPI_MUX_TARGET_RADIOA 0x01 +#define LGW_SPI_MUX_TARGET_RADIOB 0x02 + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +/** +@brief LoRa concentrator SPI setup (configure I/O and peripherals) +@param spidev_path path to the SPI device to be used to connect to the SX1302 +@param spi_target_ptr pointer on a generic pointer to SPI target (implementation dependant) +@return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR) +*/ + +int lgw_spi_open(const char * spidev_path, void **spi_target_ptr); + +/** +@brief LoRa concentrator SPI close +@param spi_target generic pointer to SPI target (implementation dependant) +@return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR) +*/ + +int lgw_spi_close(void *spi_target); + +/** +@brief LoRa concentrator SPI single-byte write +@param spi_target generic pointer to SPI target (implementation dependant) +@param address 7-bit register address +@param data data byte to write +@return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR) +*/ +int lgw_spi_w(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8_t data); + +/** +@brief LoRa concentrator SPI single-byte read +@param spi_target generic pointer to SPI target (implementation dependant) +@param address 7-bit register address +@param data data byte to write +@return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR) +*/ +int lgw_spi_r(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8_t *data); + +/** +@brief LoRa concentrator SPI burst (multiple-byte) write +@param spi_target generic pointer to SPI target (implementation dependant) +@param address 7-bit register address +@param data pointer to byte array that will be sent to the LoRa concentrator +@param size size of the transfer, in byte(s) +@return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR) +*/ +int lgw_spi_wb(void *spi_target, uint8_t spi_mux_target, uint16_t address, const uint8_t *data, uint16_t size); + +/** +@brief LoRa concentrator SPI burst (multiple-byte) read +@param spi_target generic pointer to SPI target (implementation dependant) +@param address 7-bit register address +@param data pointer to byte array that will be written from the LoRa concentrator +@param size size of the transfer, in byte(s) +@return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR) +*/ +int lgw_spi_rb(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8_t *data, uint16_t size); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_stts751.h b/libloragw/inc/loragw_stts751.h new file mode 100644 index 0000000..54ae164 --- /dev/null +++ b/libloragw/inc/loragw_stts751.h @@ -0,0 +1,57 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Basic driver for ST ts751 temperature sensor + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORAGW_STTS751_H +#define _LORAGW_STTS751_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* bool type */ + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- INTERNAL SHARED TYPES ------------------------------------------------ */ + +/* -------------------------------------------------------------------------- */ +/* --- INTERNAL SHARED FUNCTIONS -------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +#define I2C_PORT_TEMP_SENSOR 0x39 + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS ----------------------------------------------------- */ + +/** +@brief TODO +@param TODO +@return TODO +*/ +int lgw_stts751_configure(void); + +/** +@brief TODO +@param TODO +@return TODO +*/ +int lgw_stts751_get_temperature(float * temperature); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_sx1250.h b/libloragw/inc/loragw_sx1250.h new file mode 100644 index 0000000..94cc094 --- /dev/null +++ b/libloragw/inc/loragw_sx1250.h @@ -0,0 +1,101 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1250 radios. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORAGW_SX1250_H +#define _LORAGW_SX1250_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types*/ + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +#define SX1250_FREQ_TO_REG(f) (uint32_t)((uint64_t)f * (1 << 25) / 32000000U) + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +typedef enum { + CALIBRATE_IMAGE = 0x98, + CLR_IRQ_STATUS = 0x02, + STOP_TIMER_ON_PREAMBLE = 0x9F, + SET_RFSWITCHMODE = 0x9D, + GET_IRQ_STATUS = 0x12, + GET_RX_BUFFER_STATUS = 0x13, + GET_PACKET_STATUS = 0x14, + READ_BUFFER = 0x1E, + READ_REGISTER = 0x1D, + SET_DIO_IRQ_PARAMS = 0x08, + SET_MODULATION_PARAMS = 0x8B, + SET_PA_CONFIG = 0x95, + SET_PACKET_PARAMS = 0x8C, + SET_PACKET_TYPE = 0x8A, + SET_RF_FREQUENCY = 0x86, + SET_BUFFER_BASE_ADDRESS = 0x8F, + SET_SLEEP = 0x84, + SET_STANDBY = 0x80, + SET_RX = 0x82, + SET_TX = 0x83, + SET_TX_PARAMS = 0x8E, + WRITE_BUFFER = 0x0E, + WRITE_REGISTER = 0x0D, + SET_TXCONTINUOUSWAVE = 0xD1, + SET_TXCONTINUOUSPREAMBLE= 0xD2, + GET_STATUS = 0xC0, + SET_REGULATORMODE = 0x96, + SET_FS = 0xC1, + GET_DEVICE_ERRORS = 0x17 +} sx1250_op_code_t; + +typedef enum { + STDBY_RC = 0x00, + STDBY_XOSC = 0x01 +} sx1250_standby_modes_t; + +typedef enum { + PACKET_TYPE_GFSK = 0x00, + PACKET_TYPE_LORA = 0x01 +} sx1250_packet_type_t; + +typedef enum { + SET_RAMP_10U = 0x00, + SET_RAMP_20U = 0x01, + SET_RAMP_40U = 0x02, + SET_RAMP_80U = 0x03, + SET_RAMP_200U = 0x04, + SET_RAMP_800U = 0x05, + SET_RAMP_1700U = 0x06, + SET_RAMP_3400U = 0x07 +} sx1250_ramp_time_t; + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +int sx1250_write_command(uint8_t rf_chain, sx1250_op_code_t op_code, uint8_t *data, uint16_t size); +int sx1250_read_command(uint8_t rf_chain, sx1250_op_code_t op_code, uint8_t *data, uint16_t size); + +int sx1250_calibrate(uint8_t rf_chain, uint32_t freq_hz); +int sx1250_setup(uint8_t rf_chain, uint32_t freq_hz); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_sx125x.h b/libloragw/inc/loragw_sx125x.h new file mode 100644 index 0000000..f67fb20 --- /dev/null +++ b/libloragw/inc/loragw_sx125x.h @@ -0,0 +1,148 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1255/SX1257 radios. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + +#ifndef _LORAGW_SX125X_H +#define _LORAGW_SX125X_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* bool type */ + +/* -------------------------------------------------------------------------- */ +/* --- INTERNAL SHARED TYPES ------------------------------------------------ */ + +struct radio_reg_s +{ + uint8_t addr; /* base address of the register */ + uint8_t offs; /* position of the register LSB (between 0 to 7) */ + uint8_t leng; /* number of bits in the register */ +}; + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +#define SX1257_FREQ_TO_REG(f) (uint32_t)((uint64_t)f * (1 << 19) / 32000000U) +#define SX1255_FREQ_TO_REG(f) (uint32_t)((uint64_t)f * (1 << 20) / 32000000U) + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +#define LGW_REG_SUCCESS 0 +#define LGW_REG_ERROR -1 + +#define SX125x_32MHz_FRAC 15625 /* irreductible fraction for PLL register caculation */ + +#define SX125x_TX_DAC_CLK_SEL 0 /* 0:int, 1:ext */ +#define SX125x_TX_DAC_GAIN 2 /* 3:0, 2:-3, 1:-6, 0:-9 dBFS (default 2) */ +#define SX125x_TX_MIX_GAIN 14 /* -38 + 2*TxMixGain dB (default 14) */ +#define SX125x_TX_PLL_BW 1 /* 0:75, 1:150, 2:225, 3:300 kHz (default 3) */ +#define SX125x_TX_ANA_BW 0 /* 17.5 / 2*(41-TxAnaBw) MHz (default 0) */ +#define SX125x_TX_DAC_BW 5 /* 24 + 8*TxDacBw Nb FIR taps (default 2) */ +#define SX125x_RX_LNA_GAIN 1 /* 1 to 6, 1 highest gain */ +#define SX125x_RX_BB_GAIN 15 /* 0 to 15 , 15 highest gain */ +#define SX125x_LNA_ZIN 0 /* 0:50, 1:200 Ohms (default 1) */ +#define SX125x_RX_ADC_BW 7 /* 0 to 7, 2:100 /* C99 types*/ + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +/* Default values */ +#define SX1302_AGC_RADIO_GAIN_AUTO 0xFF +#define TX_START_DELAY_DEFAULT 1500 /* Calibrated value for 500KHz BW */ + +/* type of if_chain + modem */ +#define IF_UNDEFINED 0 +#define IF_LORA_STD 0x10 /* if + standard single-SF LoRa modem */ +#define IF_LORA_MULTI 0x11 /* if + LoRa receiver with multi-SF capability */ +#define IF_FSK_STD 0x20 /* if + standard FSK modem */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +#define REG_SELECT(rf_chain, a, b) ((rf_chain == 0) ? a : b) + +#define SET_PPM_ON(bw,dr) (((bw == BW_125KHZ) && ((dr == DR_LORA_SF11) || (dr == DR_LORA_SF12))) || ((bw == BW_250KHZ) && (dr == DR_LORA_SF12))) + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +/** +@struct sx1302_if_cfg_t +@brief TODO +*/ +typedef struct { + bool if_enable; + bool if_rf_chain; /* for each IF, 0 -> radio A, 1 -> radio B */ + int32_t if_freq; /* relative to radio frequency, +/- in Hz */ +} sx1302_if_cfg_t; + +/** +@struct sx1302_lora_service_cfg_t +@brief TODO +*/ +typedef struct { + uint8_t lora_rx_bw; /* bandwidth setting for LoRa standalone modem */ + uint8_t lora_rx_sf; /* spreading factor setting for LoRa standalone modem */ + bool lora_rx_implicit_hdr; /* implicit header setting for LoRa standalone modem */ + uint8_t lora_rx_implicit_length; /* implicit header payload length setting for LoRa standalone modem */ + bool lora_rx_implicit_crc_en; /* implicit header payload crc enable setting for LoRa standalone modem */ + uint8_t lora_rx_implicit_coderate; /* implicit header payload coderate setting for LoRa standalone modem */ +} sx1302_lora_service_cfg_t; + +/** +@struct sx1302_fsk_cfg_t +@brief TODO +*/ +typedef struct { + uint8_t fsk_rx_bw; /* bandwidth setting of FSK modem */ + uint32_t fsk_rx_dr; /* FSK modem datarate in bauds */ + uint8_t fsk_sync_word_size; /* default number of bytes for FSK sync word */ + uint64_t fsk_sync_word; /* default FSK sync word (ALIGNED RIGHT, MSbit first) */ +} sx1302_fsk_cfg_t; + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +/** +@brief TODO +@param TODO +@return TODO +*/ +void sx1302_init(struct lgw_conf_timestamp_s *conf); + +/** +@brief Get the SX1302 unique identifier +@param eui pointerto the memory holding the concentrator EUI +@return LGW_REG_SUCCESS if no error, LGW_REG_ERROR otherwise +*/ +int sx1302_get_eui(uint64_t * eui); + +/** +@brief Check AGC & ARB MCUs parity error, and update timestamp counter wraping status +@brief This function needs to be called regularly (every few seconds) by the upper layer +@param N/A +@return LGW_REG_SUCCESS if no error, LGW_REG_ERROR otherwise +*/ +int sx1302_update(void); + +/** +@brief Select the clock source radio +@param rf_chain The RF chain index from which to get the clock source +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise +*/ +int sx1302_radio_clock_select(uint8_t rf_chain); + +/** +@brief Apply the radio reset sequence to the required RF chain index +@param rf_chain The RF chain index of the radio to be reset +@param type The type of radio to be reset +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise +*/ +int sx1302_radio_reset(uint8_t rf_chain, lgw_radio_type_t type); + +/** +@brief Configure the radio type for the given RF chain +@param rf_chain The RF chain index to be configured +@param type The type of radio to be set for the given RF chain +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise +*/ +int sx1302_radio_set_mode(uint8_t rf_chain, lgw_radio_type_t type); + +/** +@brief Give/Release control over the radios to/from the Host +@param host_ctrl Set to true to give control to the host, false otherwise +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise +*/ +int sx1302_radio_host_ctrl(bool host_ctrl); + +/** +@brief Perform the radio calibration sequence and fill the TX gain LUT with calibration offsets +@param context_rf_chain The RF chains array from which to get RF chains current configuration +@param clksrc The RF chain index which provides the clock source +@param txgain_lut A pointer to the TX gain LUT to be filled +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise +*/ +int sx1302_radio_calibrate(struct lgw_conf_rxrf_s * context_rf_chain, uint8_t clksrc, struct lgw_tx_gain_lut_s * txgain_lut); + +/** +@brief Configure the PA and LNA LUTs +@param N/A +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise +*/ +int sx1302_pa_lna_lut_configure(void); + +/** +@brief Configure the Radio Front-End stage of the SX1302 +@param N/A +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise +*/ +int sx1302_radio_fe_configure(void); + +/** +@brief TODO +@param TODO +@return TODO +*/ +uint8_t sx1302_get_ifmod_config(uint8_t if_chain); + +/** +@brief Configure the channelizer stage of the SX1302 +@param if_cfg A pointer to the channels configuration +@param fix_gain Set to true to force the channelizer to a fixed gain, false to let the AGC controlling it +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise +*/ +int sx1302_channelizer_configure(struct lgw_conf_rxif_s * if_cfg, bool fix_gain); + +/** +@brief Configure the correlator stage of the SX1302 LoRa multi-SF modems +@param N/A +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise +*/ +int sx1302_lora_correlator_configure(void); + +/** +@brief Configure the correlator stage of the SX1302 LoRa single-SF modem +@param cfg A pointer to the channel configuration +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise +*/ +int sx1302_lora_service_correlator_configure(struct lgw_conf_rxif_s * cfg); + +/** +@brief Configure the syncword to be used by LoRa modems (public:0x34, private:0x12) +@param public Set to true to use the "public" syncword, false to use the private one +@param lora_service_sf The spreading factor configured for the single-SF LoRa modem +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise +*/ +int sx1302_lora_syncword(bool public, uint8_t lora_service_sf); + +/** +@brief Configure the LoRa multi-SF modems +@param radio_freq_hz The center frequency of the RF chain (0 or 1) +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise +*/ +int sx1302_lora_modem_configure(uint32_t radio_freq_hz); + +/** +@brief Configure the LoRa single-SF modem +@param cfg A pointer to the channel configuration +@param radio_freq_hz The center frequency of the RF chain (0 or 1) +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise +*/ +int sx1302_lora_service_modem_configure(struct lgw_conf_rxif_s * cfg, uint32_t radio_freq_hz); + +/** +@brief Configure the FSK modem +@param cfg A pointer to the channel configuration +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise +*/ +int sx1302_fsk_configure(struct lgw_conf_rxif_s * cfg); + +/** +@brief Enable the modems +@param N/A +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise +*/ +int sx1302_modem_enable(void); + +/** +@brief Enable/Disable the GPS to allow PPS trigger and counter sampling +@param enbale Set to true to enable, false otherwise +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise +*/ +int sx1302_gps_enable(bool enable); + +/** +@brief Get the current SX1302 internal counter value +@param pps True for getting the counter value at last PPS +@return the counter value in mciroseconds (32-bits) +*/ +uint32_t sx1302_timestamp_counter(bool pps); + +/** +@brief TODO +@param TODO +@return TODO +*/ +int sx1302_agc_load_firmware(const uint8_t *firmware); + +/** +@brief TODO +@param TODO +@return TODO +*/ +int sx1302_agc_status(uint8_t* status); + +/** +@brief TODO +@param TODO +@return TODO +*/ +int sx1302_agc_wait_status(uint8_t status); + +/** +@brief TODO +@param TODO +@return TODO +*/ +int sx1302_agc_mailbox_read(uint8_t mailbox, uint8_t* value); + +/** +@brief TODO +@param TODO +@return TODO +*/ +int sx1302_agc_mailbox_write(uint8_t mailbox, uint8_t value); + +/** +@brief TODO +@param TODO +@return TODO +*/ +int sx1302_agc_start(uint8_t version, lgw_radio_type_t radio_type, uint8_t ana_gain, uint8_t dec_gain, uint8_t fdd_mode); + +/** +@brief TODO +@param TODO +@return TODO +*/ +int sx1302_arb_load_firmware(const uint8_t *firmware); + +/** +@brief TODO +@param TODO +@return TODO +*/ +int sx1302_arb_status(uint8_t* status); + +/** +@brief TODO +@param TODO +@return TODO +*/ +int sx1302_arb_wait_status(uint8_t status); + +/** +@brief TODO +@param TODO +@return TODO +*/ +int sx1302_arb_debug_read(uint8_t reg_id, uint8_t* value); + +/** +@brief TODO +@param TODO +@return TODO +*/ +int sx1302_arb_debug_write(uint8_t reg_id, uint8_t value); + +/** +@brief TODO +@param TODO +@return TODO +*/ +int sx1302_arb_start(uint8_t version); + +/** +@brief TODO +@param TODO +@return TODO +*/ +uint8_t sx1302_arb_get_debug_stats_detect(uint8_t channel); + +/** +@brief TODO +@param TODO +@return TODO +*/ +uint8_t sx1302_arb_get_debug_stats_alloc(uint8_t channel); + +/** +@brief TODO +@param TODO +@return TODO +*/ +void sx1302_arb_print_debug_stats(void); + +/** +@brief TODO +@param TODO +@return TODO +*/ +uint16_t sx1302_lora_payload_crc(const uint8_t * data, uint8_t size); + +/** +@brief TODO +@param TODO +@return TODO +*/ +uint16_t sx1302_rx_buffer_read_ptr_addr(void); + +/** +@brief TODO +@param TODO +@return TODO +*/ +uint16_t sx1302_rx_buffer_write_ptr_addr(void); + +/** +@brief Check if any data to be fetched from the SX1302 RX buffer and fetch it if any. +@param nb_bytes A pointer to allocated memory to hold the number of bytes fetched +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise +*/ +int sx1302_fetch(uint16_t * nb_bytes); + +/** +@brief Parse and return the next packet available in the fetched RX buffer. +@param context Gateway configuration context +@param p The structure to get the packet parsed +@return LGW_REG_SUCCESS if a packet could be parsed, LGW_REG_ERROR otherwise +*/ +int sx1302_parse(lgw_context_t * context, struct lgw_pkt_rx_s * p); + +/** +@brief Configure the delay to be applied by the SX1302 for TX to start +@param rf_chain The RF chain index to be configured +@param radio_type The type of radio for this RF chain +@param modulation The modulation used for the TX +@param bandwidth The bandwidth used for the TX +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise +*/ +int sx1302_tx_set_start_delay(uint8_t rf_chain, lgw_radio_type_t radio_type, uint8_t modulation, uint8_t bandwidth); + +/** +@brief Compute the offset to be applied on RSSI for temperature compensation +@param context a pointer to the memory that holds the current temp comp context +@param temperature the temperature for which to compute the offset to be applied +@return the offset to be applied to RSSI +*/ +float sx1302_rssi_get_temperature_offset(struct lgw_rssi_tcomp_s * context, float temperature); + +/** +@brief Get current TX status of the SX1302 +@param rf_chain the TX chain we want to get the status from +@return current status +*/ +uint8_t sx1302_tx_status(uint8_t rf_chain); + +/** +@brief Get current RX status of the SX1302 +@param rf_chain the RX chain we want to get the status from +@return current status +@note NOT IMPLEMENTED +*/ +uint8_t sx1302_rx_status(uint8_t rf_chain); + +/** +@brief Abort current transmit +@param rf_chain the TX chain on which we want to abort transmit +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise +*/ +int sx1302_tx_abort(uint8_t rf_chain); + +/** +@brief TODO +@param TODO +@return TODO +*/ +int sx1302_tx_configure(lgw_radio_type_t radio_type); + +/** +@brief TODO +@param TODO +@return TODO +*/ +int sx1302_send(lgw_radio_type_t radio_type, struct lgw_tx_gain_lut_s * tx_lut, bool lwan_public, struct lgw_conf_rxif_s * context_fsk, struct lgw_pkt_tx_s * pkt_data); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_sx1302_rx.h b/libloragw/inc/loragw_sx1302_rx.h new file mode 100644 index 0000000..8953cf5 --- /dev/null +++ b/libloragw/inc/loragw_sx1302_rx.h @@ -0,0 +1,134 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + SX1302 RX buffer Hardware Abstraction Layer + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORAGW_SX1302_RX_H +#define _LORAGW_SX1302_RX_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types*/ + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +/** +@struct rx_packet_s +@brief packet structure as contained in the sx1302 RX packet engine +*/ +typedef struct rx_packet_s { + uint8_t rxbytenb_modem; + uint8_t rx_channel_in; + bool crc_en; + uint8_t coding_rate; /* LoRa only */ + uint8_t rx_rate_sf; /* LoRa only */ + uint8_t modem_id; + int32_t frequency_offset_error; /* LoRa only */ + uint8_t payload[255]; + bool payload_crc_error; + bool sync_error; /* LoRa only */ + bool header_error; /* LoRa only */ + bool timing_set; /* LoRa only */ + int8_t snr_average; /* LoRa only */ + uint8_t rssi_chan_avg; + uint8_t rssi_signal_avg; /* LoRa only */ + uint8_t rssi_chan_max_neg_delta; + uint8_t rssi_chan_max_pos_delta; + uint8_t rssi_sig_max_neg_delta; /* LoRa only */ + uint8_t rssi_sig_max_pos_delta; /* LoRa only */ + uint32_t timestamp_cnt; + uint16_t rx_crc16_value; /* LoRa only */ + uint8_t num_ts_metrics_stored; /* LoRa only */ + uint8_t timestamp_avg[255]; /* LoRa only */ + uint8_t timestamp_stddev[255]; /* LoRa only */ + uint8_t packet_checksum; +} rx_packet_t; + +/** +@struct rx_buffer_s +@brief buffer to hold the data fetched from the sx1302 RX buffer +*/ +typedef struct rx_buffer_s { + uint8_t buffer[4096]; /*!> byte array to hald the data fetched from the RX buffer */ + uint16_t buffer_size; /*!> The number of bytes currently stored in the buffer */ + int buffer_index; /*!> Current parsing index in the buffer */ +} rx_buffer_t; + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +/** +@brief TODO +@param TODO +@return TODO +*/ +int rx_buffer_new(rx_buffer_t * self); + +/** +@brief TODO +@param TODO +@return TODO +*/ +int rx_buffer_del(rx_buffer_t * self); + +/** +@brief TODO +@param TODO +@return TODO +*/ +int rx_buffer_fetch(rx_buffer_t * self); + +/** +@brief TODO +@param TODO +@return TODO +*/ +int rx_buffer_pop(rx_buffer_t * self, rx_packet_t * pkt); + +/* -------------------------------------------------------------------------- */ +/* --- DEBUG FUNCTIONS PROTOTYPES ------------------------------------------- */ + +/** +@brief TODO +@param TODO +@return TODO +*/ +uint16_t rx_buffer_read_ptr_addr(void); + +/** +@brief TODO +@param TODO +@return TODO +*/ +uint16_t rx_buffer_write_ptr_addr(void); + +/** +@brief TODO +@param TODO +@return TODO +*/ +void rx_buffer_dump(FILE * file, uint16_t start_addr, uint16_t end_addr); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_sx1302_timestamp.h b/libloragw/inc/loragw_sx1302_timestamp.h new file mode 100644 index 0000000..92c9245 --- /dev/null +++ b/libloragw/inc/loragw_sx1302_timestamp.h @@ -0,0 +1,117 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + SX1302 timestamp counter Hardware Abstraction Layer + Handles the conversion of a 32-bits 32MHz counter into a 32-bits 1 MHz counter. + This modules MUST be called regularly by the application to maintain counter + wrapping handling for conversion in 1MHz counter. + Provides function to compute the correction to be applied to the received + timestamp for demodulation processing time. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORAGW_SX1302_TIMESTAMP_H +#define _LORAGW_SX1302_TIMESTAMP_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types*/ +#include /* boolean type */ + + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +/** +@struct timestamp_counter_s +@brief context to maintain the internal counters (inst and pps trig) wrapping +*/ +typedef struct timestamp_counter_s { + uint32_t counter_us_raw_27bits_inst_prev; + uint32_t counter_us_raw_27bits_pps_prev; + uint8_t counter_us_raw_27bits_inst_wrap; + uint8_t counter_us_raw_27bits_pps_wrap; +} timestamp_counter_t; + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS ----------------------------------------------------- */ + +/** +@brief TODO +@param TODO +@return TODO +*/ +void timestamp_counter_new(timestamp_counter_t * self); + +/** +@brief TODO +@param TODO +@return TODO +*/ +void timestamp_counter_delete(timestamp_counter_t * self); + +/** +@brief Update the counter wrapping status based on given current counter +@param self Pointer to the counter handler +@param pps Set to true to update the PPS trig counter status +@param cnt Current value of the counter to be used for the update +@return N/A +*/ +void timestamp_counter_update(timestamp_counter_t * self, bool pps, uint32_t cnt); + +/** +@brief Convert the 27-bits counter given by the SX1302 to a 32-bits counter which wraps on a uint32_t. +@param self Pointer to the counter handler +@param pps Set to true to expand the counter based on the PPS trig wrapping status +@param cnt_us The 27-bits counter to be expanded +@return the 32-bits counter +*/ +uint32_t timestamp_counter_expand(timestamp_counter_t * self, bool pps, uint32_t cnt_us); + +/** +@brief Reads the SX1302 internal counter register, and return the 32-bits 1 MHz counter +@param self Pointer to the counter handler +@param pps Set to true to expand the counter based on the PPS trig wrapping status +@return the current 32-bits counter +*/ +uint32_t timestamp_counter_get(timestamp_counter_t * self, bool pps); + +/** +@brief Get the timestamp correction to applied to the packet timestamp +@param ifmod modem type +@param bandwidth modulation bandwidth +@param datarate modulation datarate +@param coderate modulation coding rate +@param crc_en indicates if CRC is enabled or disabled +@param payload_length payload length +@return The correction to be applied to the packet timestamp, in microseconds +*/ +uint32_t timestamp_counter_correction(int ifmod, uint8_t bandwidth, uint8_t datarate, uint8_t coderate, uint32_t crc_en, uint16_t payload_length); + +/** +@brief TODO +@param TODO +@return TODO +*/ +int timestamp_counter_mode(bool enable_precision_ts, uint8_t max_ts_metrics, uint8_t nb_symbols); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/library.cfg b/libloragw/library.cfg new file mode 100644 index 0000000..aa48be0 --- /dev/null +++ b/libloragw/library.cfg @@ -0,0 +1,19 @@ +# That file will be included in the Makefile files that have hardware dependencies + +### Debug options ### +# Set the DEBUG_* to 1 to activate debug mode in individual modules. +# Warning: that makes the module *very verbose*, do not use for production + +DEBUG_AUX= 0 +DEBUG_SPI= 0 +DEBUG_REG= 0 +DEBUG_HAL= 0 +DEBUG_LBT= 0 +DEBUG_GPS= 0 +DEBUG_RAD= 0 +DEBUG_CAL= 0 +DEBUG_SX1302= 0 + +### Configuration options ### +BYPASS_FW_INIT = 0 +FPGA_BOARD_16_CH = 1 diff --git a/libloragw/readme.md b/libloragw/readme.md new file mode 100644 index 0000000..7b546e8 --- /dev/null +++ b/libloragw/readme.md @@ -0,0 +1,393 @@ + / _____) _ | | + ( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | + (______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +LoRa concentrator HAL user manual +================================= + +## 1. Introduction + +The LoRa concentrator Hardware Abstraction Layer is a C library that allow you +to use a Semtech concentrator chip through a reduced number of high level C +functions to configure the hardware, send and receive packets. + +The Semtech LoRa concentrator is a digital multi-channel multi-standard packet +radio used to send and receive packets wirelessly using LoRa or FSK modulations. + +## 2. Components of the library + +The library is composed of the following modules: + +* loragw_hal +* loragw_reg +* loragw_spi +* loragw_i2c +* loragw_aux +* loragw_gps +* loragw_sx125x +* loragw_sx1250 +* loragw_sx1302 +* loragw_sx1302_rx +* loragw_sx1302_timestamp +* loragw_stts751 + +The library also contains basic test programs to demonstrate code use and check +functionality. + +### 2.1. loragw_hal + +This is the main module and contains the high level functions to configure and +use the LoRa concentrator: + +* lgw_board_setconf, to set the configuration of the concentrator +* lgw_rxrf_setconf, to set the configuration of the radio channels +* lgw_rxif_setconf, to set the configuration of the IF+modem channels +* lgw_txgain_setconf, to set the configuration of the concentrator gain table +* lgw_start, to apply the set configuration to the hardware and start it +* lgw_stop, to stop the hardware +* lgw_receive, to fetch packets if any was received +* lgw_send, to send a single packet (non-blocking, see warning in usage section) +* lgw_status, to check when a packet has effectively been sent + +For an standard application, include only this module. +The use of this module is detailed on the usage section. + +/!\ When sending a packet, there is a delay (approx 1.5ms) for the analog +circuitry to start and be stable. This delay is adjusted by the HAL depending +on the board version (lgw_i_tx_start_delay_us). + +In 'timestamp' mode, this is transparent: the modem is started +lgw_i_tx_start_delay_us microseconds before the user-set timestamp value is +reached, the preamble of the packet start right when the internal timestamp +counter reach target value. + +In 'immediate' mode, the packet is emitted as soon as possible: transferring the +packet (and its parameters) from the host to the concentrator takes some time, +then there is the lgw_i_tx_start_delay_us, then the packet is emitted. + +In 'triggered' mode (aka PPS/GPS mode), the packet, typically a beacon, is +emitted lgw_i_tx_start_delay_us microsenconds after a rising edge of the +trigger signal. Because there is no way to anticipate the triggering event and +start the analog circuitry beforehand, that delay must be taken into account in +the protocol. + +### 2.2. loragw_reg + +This module is used to access to the LoRa concentrator registers by name instead +of by address: + +* lgw_connect, to initialise and check the connection with the hardware +* lgw_disconnect, to disconnect the hardware +* lgw_reg_r, read a named register +* lgw_reg_w, write a named register +* lgw_reg_rb, read a name register in burst +* lgw_reg_wb, write a named register in burst + +This module handles read-only registers protection, multi-byte registers +management, signed registers management, read-modify-write routines for +sub-byte registers and read/write burst fragmentation to respect SPI maximum +burst length constraints. + +It make the code much easier to read and to debug. +Moreover, if registers are relocated between different hardware revisions but +keep the same function, the code written using register names can be reused "as +is". + +If you need access to all the registers, include this module in your +application. + +**/!\ Warning** please be sure to have a good understanding of the LoRa +concentrator inner working before accessing the internal registers directly. + +### 2.3. loragw_spi + +This module contains the functions to access the LoRa concentrator register +array through the SPI interface: + +* lgw_spi_r to read one byte +* lgw_spi_w to write one byte +* lgw_spi_rb to read two bytes or more +* lgw_spi_wb to write two bytes or more + +Please *do not* include that module directly into your application. + +**/!\ Warning** Accessing the LoRa concentrator register array without the +checks and safety provided by the functions in loragw_reg is not recommended. + +### 2.4. loragw_aux + +This module contains a single host-dependant function wait_ms to pause for a +defined amount of milliseconds. + +The procedure to start and configure the LoRa concentrator hardware contained in +the loragw_hal module requires to wait for several milliseconds at certain +steps, typically to allow for supply voltages or clocks to stabilize after been +switched on. + +An accuracy of 1 ms or less is ideal. +If your system does not allow that level of accuracy, make sure that the actual +delay is *longer* that the time specified when the function is called (ie. +wait_ms(X) **MUST NOT** before X milliseconds under any circumstance). + +If the minimum delays are not guaranteed during the configuration and start +procedure, the hardware might not work at nominal performance. +Most likely, it will not work at all. + +### 2.5. loragw_gps + +This module contains functions to synchronize the concentrator internal +counter with an absolute time reference, in our case a GPS satellite receiver. + +The internal concentrator counter is used to timestamp incoming packets and to +triggers outgoing packets with a microsecond accuracy. +In some cases, it might be useful to be able to transform that internal +timestamp (that is independent for each concentrator running in a typical +networked system) into an absolute GPS time. + +In a typical implementation a GPS specific thread will be called, doing the +following things after opening the serial port: + +* blocking reads on the serial port (using system read() function) +* parse UBX messages (using lgw_parse_ubx) to get actual native GPS time +* parse NMEA sentences (using lgw_parse_nmea) to get location and UTC time +Note: the RMC sentence gives UTC time, not native GPS time. + +And each time an NAV-TIMEGPS UBX message has been received: + +* get the concentrator timestamp (using lgw_get_trigcnt, mutex needed to + protect access to the concentrator) +* get the GPS time contained in the UBX message (using lgw_gps_get) +* call the lgw_gps_sync function (use mutex to protect the time reference that + should be a global shared variable). + +Then, in other threads, you can simply used that continuously adjusted time +reference to convert internal timestamps to GPS time (using lgw_cnt2gps) or +the other way around (using lgw_gps2cnt). Inernal concentrator timestamp can +also be converted to/from UTC time using lgw_cnt2utc/lgw_utc2cnt functions. + +### 2.6. loragw_sx125x + +This module contains functions to handle the configuration of SX1255 and +SX1257 radios. + +### 2.7. loragw_sx1250 + +This module contains functions to handle the configuration of SX1250 radios. + +### 2.8. loragw_sx1302 + +This module contains functions to abstract SX1302 concentrator capabilities. + +### 2.9. loragw_sx1302_rx + +This module is a sub-module of the loragw_sx1302 module focusing on abstracting +the RX buffer of the SX1302. + +### 2.10. loragw_sx1302_timestamp + +This module is a sub-module of the loragw_sx1302 module focusing on abstracting +the timestamp counter of the SX1302. +It converts the 32-bits 32MHz internal counter of the SX1302 to a 32-bits 1MHz +counter. +This module needs to be called regularly by upper layers to maintain counter +wrapping when converting from 32MHz to 1MHz. +It also provides function to add correction to the timestamp counter to take +into account the LoRa demodulation processing time. + +### 2.11. loragw_stts751 + +This module contains a very basic driver for the STmicroelectronics ST751 +temeprature sensor which is on the CoreCell reference design. + +### 2.12. loragw_i2c + +This module provides basic function to communicate with I2C devices on the board. +It is used in this project for accessing the temperature sensor. + +## 3. Software build process + +### 3.1. Details of the software + +The library is written following ANSI C conventions but using C99 explicit +length data type for all data exchanges with hardware and for parameters. + +The loragw_aux module contains POSIX dependant functions for millisecond +accuracy pause. +For embedded platforms, the function could be rewritten using hardware timers. + +### 3.2. Building options + +All modules use a fprintf(stderr,...) function to display debug diagnostic +messages if the DEBUG_xxx is set to 1 in library.cfg + +### 3.3. Building procedures + +For cross-compilation set the ARCH and CROSS_COMPILE variables in the Makefile, +or in your shell environment, with the correct toolchain name and path. +ex: +export PATH=/home/foo/rpi-toolchain/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin:$PATH +export ARCH=arm +export CROSS_COMPILE=arm-linux-gnueabihf- + +The Makefile in the libloragw directory will parse the library.cfg file and +generate a config.h C header file containing #define options. +Those options enables and disables sections of code in the loragw_xxx.h files +and the *.c source files. + +The library.cfg is also used directly to select the proper set of dynamic +libraries to be linked with. + +### 3.4. Export + +Once build, to use that library on another system, you need to export the +following files : + +* libloragw/library.cfg -> root configuration file +* libloragw/libloragw.a -> static library, to be linked with a program +* libloragw/readme.md -> required for license compliance +* libloragw/inc/config.h -> C configuration flags, derived from library.cfg +* libloragw/inc/loragw_*.h -> take only the ones you need (eg. _hal and _gps) + +After statically linking the library to your application, only the license +is required to be kept or copied inside your program documentation. + +## 4. Hardware dependencies + +### 4.1. Hardware revision + +The loragw_reg and loragw_hal are written for a specific version on the Semtech +hardware (IP and/or silicon revision). + +This code has been written for: + +* Semtech SX1302 chip +* Semtech SX1250, SX1257 or SX1255 I/Q transceivers + +The library will not work if there is a mismatch between the hardware version +and the library version. You can use the test program test_loragw_reg to check +if the hardware registers match their software declaration. + +### 4.2. SPI communication + +loragw_spi contains 4 SPI functions (read, write, burst read, burst write) that +are platform-dependant. +The functions must be rewritten depending on the SPI bridge you use: + +* SPI master matched to the Linux SPI device driver (provided) +* SPI over USB using FTDI components (not provided) +* native SPI using a microcontroller peripheral (not provided) + +You can use the test program test_loragw_spi to check with a logic analyser +that the SPI communication is working + +### 4.3. GPS receiver (or other GNSS system) + +To use the GPS module of the library, the host must be connected to a GPS +receiver via a serial link (or an equivalent receiver using a different +satellite constellation). +The serial link must appear as a "tty" device in the /dev/ directory, and the +user launching the program must have the proper system rights to read and +write on that device. +Use `chmod a+rw` to allow all users to access that specific tty device, or use +sudo to run all your programs (eg. `sudo ./test_loragw_gps`). + +In the current revision, the library only reads data from the serial port, +expecting to receive NMEA frames that are generally sent by GPS receivers as +soon as they are powered up, and UBX messages which are proprietary to u-blox +modules. + +The GPS receiver **MUST** send UBX messages shortly after sending a PPS pulse +on to allow internal concentrator timestamps to be converted to absolute GPS time. +If the GPS receiver sends a GGA NMEA sentence, the gateway 3D position will +also be available. + +## 5. Usage + +### 5.1. Setting the software environment + +For a typical application you need to: + +* include loragw_hal.h in your program source +* link to the libloragw.a static library during compilation +* link to the librt library due to loragw_aux dependencies (timing functions) + +For an application that will also access the concentrator configuration +registers directly (eg. for advanced configuration) you also need to: + +* include loragw_reg.h in your program source + +### 5.2. Using the software API + +To use the HAL in your application, you must follow some basic rules: + +* configure the radios path and IF+modem path before starting the radio +* the configuration is only transferred to hardware when you call the *start* + function +* you cannot receive packets until one (or +) radio is enabled AND one (or +) + IF+modem part is enabled AND the concentrator is started +* you cannot send packets until one (or +) radio is enabled AND the concentrator + is started +* you must stop the concentrator before changing the configuration + +A typical application flow for using the HAL is the following: + + + + loop { + + + + } + + +**/!\ Warning** The lgw_send function is non-blocking and returns while the +LoRa concentrator is still sending the packet, or even before the packet has +started to be transmitted if the packet is triggered on a future event. +While a packet is emitted, no packet can be received (limitation intrinsic to +most radio frequency systems). + +Your application *must* take into account the time it takes to send a packet or +check the status (using lgw_status) before attempting to send another packet. + +Trying to send a packet while the previous packet has not finished being send +will result in the previous packet not being sent or being sent only partially +(resulting in a CRC error in the receiver). + +### 5.3. Debugging mode + +To debug your application, it might help to compile the loragw_hal function +with the debug messages activated (set DEBUG_HAL=1 in library.cfg). +It then send a lot of details, including detailed error messages to *stderr*. + +## 6. License + +Copyright (c) 2019, SEMTECH S.A. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of the Semtech corporation nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL SEMTECH S.A. BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*EOF* diff --git a/libloragw/src/agc_fw_sx1250.var b/libloragw/src/agc_fw_sx1250.var new file mode 100644 index 0000000..04ca5ec --- /dev/null +++ b/libloragw/src/agc_fw_sx1250.var @@ -0,0 +1,514 @@ +static uint8_t agc_firmware_sx1250[8192] = { +0x8A, 0x51, 0xF0, 0x6F, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0x64, 0xC0, 0x80, 0x81, 0x84, 0x0A, 0x04, 0xC6, 0x03, 0x59, 0x00, 0xF4, 0x04, 0xC6, +0x12, 0x28, 0xE5, 0x40, 0xCA, 0x00, 0x61, 0x08, 0xCB, 0x40, 0x62, 0x08, 0xCC, 0x00, 0x63, 0x48, +0xCD, 0x40, 0x4A, 0x70, 0xDC, 0x40, 0x64, 0x08, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0x2F, 0x1D, 0xB0, +0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0x8B, 0xB0, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xCE, 0x81, +0xCF, 0xC1, 0xD0, 0x01, 0xD1, 0x41, 0xDC, 0x40, 0x08, 0xF0, 0xDD, 0xC1, 0x6B, 0x67, 0x8A, 0x51, +0x4D, 0x48, 0x83, 0x96, 0xBA, 0x40, 0x83, 0x52, 0x4C, 0x08, 0x83, 0x96, 0xB9, 0x40, 0x83, 0x52, +0x4B, 0x48, 0x83, 0x96, 0xB8, 0x00, 0x83, 0x52, 0x4A, 0x08, 0x83, 0x96, 0xB7, 0x80, 0x1D, 0xB0, +0x83, 0x52, 0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0x8B, 0xB0, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, +0xCE, 0x81, 0xCF, 0xC1, 0xD0, 0x01, 0xD1, 0x41, 0xDC, 0x40, 0x08, 0xF0, 0xDD, 0xC1, 0xDD, 0x0A, +0x6B, 0x67, 0x4D, 0x48, 0x83, 0x96, 0xBE, 0x80, 0x83, 0x52, 0x4C, 0x08, 0x83, 0x96, 0xBD, 0x80, +0x83, 0x52, 0x4B, 0x48, 0x83, 0x96, 0xBC, 0x40, 0x83, 0x52, 0x4A, 0x08, 0x83, 0x96, 0xBB, 0x80, +0x08, 0x40, 0xE3, 0x40, 0xC1, 0x70, 0xCA, 0x00, 0x4A, 0x70, 0xDC, 0x40, 0x63, 0x48, 0xDD, 0x80, +0x01, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x07, 0x70, 0xE2, 0x00, 0x3A, 0xB0, 0xE1, 0x00, 0xE1, 0xCB, +0x77, 0x28, 0xE2, 0xCB, 0x77, 0x28, 0x00, 0x00, 0x0D, 0x70, 0x83, 0x52, 0x03, 0x53, 0xCA, 0x00, +0x05, 0x30, 0xCB, 0x40, 0x84, 0x30, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xDC, 0x40, 0x63, 0x48, +0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x0D, 0x70, 0xCA, 0x00, 0x05, 0x30, 0xCB, 0x40, +0x85, 0x70, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xDC, 0x40, 0x63, 0x48, 0xDD, 0x80, 0x04, 0xF0, +0x32, 0xE7, 0x8A, 0x51, 0x0D, 0x70, 0xCA, 0x00, 0x05, 0x30, 0xCB, 0x40, 0x82, 0x30, 0xCC, 0x00, +0x4A, 0x70, 0xCD, 0x81, 0xDC, 0x40, 0x63, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, +0x0D, 0x70, 0xCA, 0x00, 0x05, 0x30, 0xCB, 0x40, 0x80, 0xF0, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, +0xDC, 0x40, 0x63, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x0D, 0x70, 0xCA, 0x00, +0x05, 0x30, 0xCB, 0x40, 0x83, 0x70, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xDC, 0x40, 0x63, 0x48, +0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x63, 0x83, 0x03, 0x9D, 0xC9, 0xA8, 0x9C, 0x91, +0xCA, 0xA8, 0x1C, 0x51, 0x05, 0x30, 0xE1, 0x00, 0xE1, 0xCB, 0xCC, 0xA8, 0x0D, 0x70, 0x83, 0x52, +0x03, 0x53, 0xCA, 0x00, 0x05, 0x30, 0xCB, 0x40, 0x87, 0xB0, 0xCC, 0x00, 0x0B, 0x70, 0xCD, 0x40, +0x4A, 0x70, 0xDC, 0x40, 0x63, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x00, 0xB0, +0xE3, 0x88, 0x03, 0x9D, 0xE8, 0xA8, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0xF3, 0x30, 0xEC, 0xE8, +0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x3F, 0x30, 0xC9, 0x85, 0x49, 0x08, 0xDC, 0x40, 0x00, 0xB0, +0x22, 0xA7, 0x8A, 0x51, 0x63, 0x98, 0xFB, 0x68, 0x00, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, +0xFE, 0xF9, 0x01, 0x38, 0x01, 0x29, 0x00, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0xEF, 0xF9, +0x10, 0x38, 0xC9, 0x00, 0xDC, 0x40, 0x00, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x00, 0xB0, 0x2A, 0xE7, +0x8A, 0x51, 0xC9, 0x00, 0xC9, 0x50, 0x49, 0x08, 0xDC, 0x40, 0x00, 0xB0, 0x22, 0xA7, 0x8A, 0x51, +0x00, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0xC9, 0x92, 0x49, 0x08, 0xDC, 0x40, 0x00, 0xB0, +0x22, 0xA7, 0x8A, 0x51, 0x86, 0x70, 0xCA, 0x00, 0xE3, 0x88, 0x03, 0x9D, 0x2E, 0xE9, 0x83, 0x96, +0x3A, 0x48, 0x83, 0x52, 0xCB, 0x40, 0x83, 0x96, 0x39, 0x48, 0x83, 0x52, 0xCC, 0x00, 0x83, 0x96, +0x38, 0x08, 0x83, 0x52, 0xCD, 0x40, 0x83, 0x96, 0x37, 0x88, 0x3C, 0xE9, 0x83, 0x96, 0x3E, 0x88, +0x83, 0x52, 0xCB, 0x40, 0x83, 0x96, 0x3D, 0x88, 0x83, 0x52, 0xCC, 0x00, 0x83, 0x96, 0x3C, 0x48, +0x83, 0x52, 0xCD, 0x40, 0x83, 0x96, 0x3B, 0x88, 0x83, 0x52, 0xCE, 0x40, 0x4A, 0x70, 0xDC, 0x40, +0x63, 0x48, 0xDD, 0x80, 0x05, 0x30, 0x32, 0xE7, 0x8A, 0x51, 0x0D, 0x70, 0xCA, 0x00, 0x08, 0xF0, +0xCB, 0x40, 0x8F, 0xF0, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xCE, 0x81, 0xCF, 0xC1, 0xDC, 0x40, +0x63, 0x48, 0xDD, 0x80, 0x06, 0x30, 0x32, 0xE7, 0x8A, 0x51, 0x82, 0x30, 0xCA, 0x00, 0xFF, 0xB0, +0xCB, 0x40, 0xCC, 0x00, 0xCD, 0x40, 0x4A, 0x70, 0xDC, 0x40, 0x63, 0x48, 0xDD, 0x80, 0x04, 0xF0, +0x32, 0xE7, 0x8A, 0x51, 0xC7, 0xF0, 0xE1, 0x00, 0x00, 0x00, 0xE1, 0xCB, 0x64, 0xA9, 0x68, 0xA9, +0x00, 0x00, 0x08, 0x40, 0xEC, 0x40, 0xC1, 0x70, 0xCA, 0x00, 0x4A, 0x70, 0xDC, 0x40, 0x6C, 0x48, +0xDD, 0x80, 0x01, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x9F, 0x30, 0xE1, 0x00, 0xE1, 0xCB, 0x76, 0x29, +0x79, 0x29, 0x95, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xCA, 0x00, 0x42, 0xC8, 0xCB, 0x40, 0x41, 0xC8, +0xCC, 0x00, 0x40, 0x88, 0xCD, 0x40, 0x4A, 0x70, 0xCE, 0x81, 0xCE, 0xCA, 0xDC, 0x40, 0x6C, 0x48, +0xDD, 0x80, 0x05, 0x30, 0x32, 0xE7, 0x8A, 0x51, 0xEC, 0x88, 0x03, 0x9D, 0x91, 0xA9, 0x25, 0x70, +0x92, 0xA9, 0x2A, 0x70, 0x2A, 0xE7, 0x8A, 0x51, 0xEB, 0x80, 0xEA, 0x40, 0x3F, 0x30, 0xEA, 0xC5, +0x8E, 0xB0, 0xCA, 0x00, 0x6A, 0x48, 0xCB, 0x40, 0x02, 0xF0, 0xCC, 0x00, 0x4A, 0x70, 0xDC, 0x40, +0x6C, 0x48, 0xDD, 0x80, 0x03, 0x30, 0x32, 0xE7, 0x8A, 0x51, 0x6B, 0x88, 0xE1, 0x00, 0x06, 0x30, +0x03, 0xD0, 0xE1, 0x8C, 0xFF, 0x7E, 0x03, 0x9D, 0xA8, 0xA9, 0x61, 0x08, 0xEA, 0x40, 0x03, 0x30, +0xEA, 0xC5, 0x00, 0xB0, 0xEC, 0x88, 0x03, 0x9D, 0xC1, 0xA9, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, +0x6A, 0x48, 0xE1, 0x00, 0x03, 0xD0, 0xE1, 0xCD, 0x03, 0xD0, 0xE1, 0xCD, 0x49, 0x08, 0xF3, 0xB9, +0xCE, 0x29, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x6A, 0x48, 0xE1, 0x00, 0x06, 0x30, 0x03, 0xD0, +0xE1, 0xCD, 0xFF, 0x7E, 0x03, 0x9D, 0xC7, 0x29, 0x49, 0x08, 0x3F, 0xB9, 0x61, 0x04, 0xC9, 0x00, +0xDC, 0x40, 0x00, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0xEC, 0x88, 0x03, 0x9D, 0xE1, 0xE9, 0x27, 0xB0, +0x2A, 0xE7, 0x8A, 0x51, 0xEA, 0x40, 0x28, 0x30, 0x2A, 0xE7, 0x8A, 0x51, 0xEB, 0x80, 0x29, 0x70, +0xEA, 0x29, 0x2C, 0x70, 0x2A, 0xE7, 0x8A, 0x51, 0xEA, 0x40, 0x2D, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, +0xEB, 0x80, 0x2E, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xE3, 0x40, 0x86, 0x70, 0xCA, 0x00, 0x03, 0xD0, +0x6A, 0x8C, 0xCB, 0x40, 0x6B, 0x88, 0xE1, 0x00, 0x03, 0xD0, 0xE1, 0x8C, 0x6A, 0x48, 0xE2, 0x00, +0x06, 0x30, 0x03, 0xD0, 0xE2, 0xCD, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xFA, 0x69, 0x62, 0x8D, +0x61, 0x04, 0xCC, 0x00, 0x63, 0x48, 0xE1, 0x00, 0x03, 0xD0, 0xE1, 0x8C, 0x6B, 0x88, 0xE2, 0x00, +0x06, 0x30, 0x03, 0xD0, 0xE2, 0xCD, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x0A, 0x6A, 0x62, 0x8D, +0x61, 0x04, 0xCD, 0x40, 0x63, 0x48, 0xE1, 0x00, 0x06, 0x30, 0x03, 0xD0, 0xE1, 0xCD, 0xFF, 0x7E, +0x03, 0xD0, 0x03, 0x9D, 0x16, 0xAA, 0x61, 0x8D, 0xCE, 0x40, 0x4A, 0x70, 0xDC, 0x40, 0x6C, 0x48, +0xDD, 0x80, 0x05, 0x30, 0x32, 0xE7, 0x8A, 0x51, 0xE6, 0x81, 0x02, 0xF0, 0x66, 0x42, 0x03, 0x18, +0x45, 0xAA, 0xC7, 0xF0, 0xE1, 0x00, 0x00, 0x00, 0xE1, 0xCB, 0x2B, 0xEA, 0x2F, 0x2A, 0x00, 0x00, +0x1D, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0xC8, 0x70, 0xCC, 0x00, +0x4A, 0x70, 0xCD, 0x81, 0xCE, 0x81, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x05, 0x30, 0x6B, 0x67, +0x8A, 0x51, 0xCA, 0xDA, 0x45, 0xAA, 0xE6, 0xCA, 0x25, 0xAA, 0x4A, 0x08, 0x20, 0x79, 0x66, 0x44, +0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x1D, 0xB0, 0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, +0xDD, 0x30, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xCE, 0x81, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, +0x05, 0x30, 0x6B, 0x67, 0x8A, 0x51, 0x4A, 0x08, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, +0x4A, 0x08, 0xE9, 0x40, 0x07, 0x70, 0xE9, 0xC5, 0x04, 0xF0, 0x69, 0x42, 0x03, 0x5C, 0x6A, 0xEA, +0x04, 0xF0, 0xE9, 0x40, 0xEC, 0x88, 0x03, 0x9D, 0x74, 0xEA, 0x69, 0x48, 0xDC, 0x40, 0x1D, 0xB0, +0x22, 0xA7, 0x8A, 0x51, 0x26, 0x70, 0x7B, 0x6A, 0x69, 0xCE, 0xF0, 0x39, 0xDC, 0x40, 0x1D, 0xB0, +0x22, 0xA7, 0x8A, 0x51, 0x2B, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xEA, 0x40, 0xEA, 0x9F, 0x39, 0x2B, +0x3D, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, 0xEB, 0x80, 0x3E, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, 0xEA, 0x40, +0x06, 0x30, 0xDC, 0x40, 0x69, 0x48, 0xC6, 0x27, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, +0x8A, 0x51, 0xE8, 0x00, 0x06, 0x30, 0xDC, 0x40, 0x69, 0x48, 0xC6, 0x27, 0x02, 0xBE, 0x84, 0x80, +0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, 0xE7, 0x80, 0x6B, 0x88, 0x68, 0x02, 0x03, 0x5C, 0xA8, 0xAA, +0x6B, 0x88, 0x68, 0x46, 0x03, 0x9D, 0xCD, 0x2A, 0x6A, 0x48, 0x67, 0x82, 0x03, 0x18, 0xCD, 0x2A, +0x01, 0xF0, 0x9E, 0x40, 0x1D, 0xB0, 0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0xC1, 0x70, 0xCC, 0x00, +0x4A, 0x70, 0xCD, 0x81, 0xCE, 0x81, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x05, 0x30, 0x6B, 0x67, +0x8A, 0x51, 0x4A, 0x08, 0xEB, 0x80, 0x1F, 0x79, 0xE0, 0xB8, 0xEB, 0x80, 0x0D, 0x70, 0xCA, 0x00, +0x08, 0xF0, 0xCB, 0x40, 0xC1, 0x70, 0xCC, 0x00, 0x6B, 0x88, 0xCD, 0x40, 0x4A, 0x70, 0xDC, 0x40, +0x6C, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x06, 0x30, 0xDC, 0x40, 0x69, 0x48, +0xC6, 0x27, 0x03, 0xFE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, 0xE8, 0x00, 0x06, 0x30, +0xDC, 0x40, 0x69, 0x48, 0xC6, 0x27, 0x04, 0xBE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, +0xE7, 0x80, 0x6B, 0x88, 0x68, 0x02, 0x03, 0x5C, 0xED, 0x6A, 0x6B, 0x88, 0x68, 0x46, 0x03, 0x9D, +0xFF, 0x2B, 0x6A, 0x48, 0x67, 0x82, 0x03, 0x18, 0xFF, 0x2B, 0x06, 0x30, 0xDC, 0x40, 0x69, 0x48, +0xC6, 0x27, 0x05, 0xFE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, 0xE8, 0x00, 0x06, 0x30, +0xDC, 0x40, 0x69, 0x48, 0xC6, 0x27, 0x06, 0xFE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, +0xE7, 0x80, 0x6B, 0x88, 0x68, 0x02, 0x03, 0x5C, 0x0D, 0xEB, 0x6B, 0x88, 0x68, 0x46, 0x03, 0x9D, +0x23, 0xEB, 0x6A, 0x48, 0x67, 0x82, 0x03, 0x18, 0x23, 0xEB, 0x03, 0x30, 0x9E, 0x40, 0x1D, 0xB0, +0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0xC2, 0x70, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xCE, 0x81, +0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x05, 0x30, 0x6B, 0x67, 0x8A, 0x51, 0x4A, 0x08, 0xEB, 0x80, +0xE1, 0x39, 0x0E, 0xB8, 0xEF, 0xEB, 0x02, 0xF0, 0x9E, 0x40, 0x1D, 0xB0, 0xCA, 0x00, 0x08, 0xF0, +0xCB, 0x40, 0xC2, 0x70, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xCE, 0x81, 0xDC, 0x40, 0x6C, 0x48, +0xDD, 0x80, 0x05, 0x30, 0x6B, 0x67, 0x8A, 0x51, 0x4A, 0x08, 0xEB, 0x80, 0xE1, 0x39, 0x06, 0x78, +0xEF, 0xEB, 0x07, 0x70, 0xEA, 0xC5, 0x05, 0x30, 0x6A, 0x42, 0x03, 0x18, 0x49, 0xEB, 0x69, 0x48, +0x3C, 0x7E, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, 0xE4, 0x00, 0x69, 0x48, 0x2D, 0x7E, +0x64, 0xEB, 0x6A, 0x48, 0x05, 0xBA, 0x03, 0x9D, 0x57, 0x6B, 0x69, 0x48, 0x41, 0xFE, 0x84, 0x80, +0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, 0xE4, 0x00, 0x69, 0x48, 0x32, 0x3E, 0x64, 0xEB, 0x06, 0x30, +0x6A, 0x42, 0x03, 0x5C, 0x69, 0x2B, 0x69, 0x48, 0x46, 0x3E, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, +0x8A, 0x51, 0xE4, 0x00, 0x69, 0x48, 0x37, 0xBE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, +0xE5, 0x40, 0x0D, 0x70, 0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0x89, 0x70, 0xCC, 0x00, 0x06, 0x30, +0x6A, 0x42, 0x03, 0x5C, 0x75, 0x6B, 0x09, 0x30, 0x76, 0x6B, 0x0D, 0x70, 0xCD, 0x40, 0x4A, 0x70, +0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x06, 0x30, 0x6A, 0x42, +0x03, 0x5C, 0xA5, 0x2B, 0x1D, 0xB0, 0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0xC2, 0x70, 0xCC, 0x00, +0x4A, 0x70, 0xCD, 0x81, 0xCE, 0x81, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x05, 0x30, 0x6B, 0x67, +0x8A, 0x51, 0x4A, 0x08, 0xEB, 0x80, 0x9F, 0xB9, 0x20, 0x38, 0xEB, 0x80, 0x0D, 0x70, 0xCA, 0x00, +0x08, 0xF0, 0xCB, 0x40, 0xC2, 0x70, 0xCC, 0x00, 0x6B, 0x88, 0xCD, 0x40, 0x4A, 0x70, 0xDC, 0x40, +0x6C, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x64, 0x08, 0x03, 0x59, 0xD4, 0x2B, +0x1D, 0xB0, 0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0xC1, 0x70, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, +0xCE, 0x81, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x05, 0x30, 0x6B, 0x67, 0x8A, 0x51, 0x4A, 0x08, +0xEB, 0x80, 0x64, 0x08, 0xE1, 0x00, 0x05, 0x30, 0x03, 0xD0, 0xE1, 0xCD, 0xFF, 0x7E, 0x03, 0x9D, +0xBC, 0x6B, 0x6B, 0x88, 0x1F, 0x79, 0x61, 0x04, 0xEB, 0x80, 0x0D, 0x70, 0xCA, 0x00, 0x08, 0xF0, +0xCB, 0x40, 0xC1, 0x70, 0xCC, 0x00, 0x6B, 0x88, 0xCD, 0x40, 0x4A, 0x70, 0xDC, 0x40, 0x6C, 0x48, +0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x65, 0x48, 0x03, 0x59, 0xFF, 0x2B, 0x1D, 0xB0, +0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0xC2, 0x70, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xCE, 0x81, +0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x05, 0x30, 0x6B, 0x67, 0x8A, 0x51, 0x4A, 0x08, 0xEB, 0x80, +0x65, 0x48, 0xE1, 0x00, 0x03, 0xD0, 0xE1, 0xCD, 0x6B, 0x88, 0xE1, 0x39, 0x61, 0x04, 0xEB, 0x80, +0x0D, 0x70, 0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0xC2, 0x70, 0xCC, 0x00, 0x6B, 0x88, 0xCD, 0x40, +0x4A, 0x70, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x6C, 0xCB, +0x07, 0xAC, 0x00, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x49, 0x52, 0x0C, 0x6C, 0x00, 0xB0, +0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x49, 0x10, 0x49, 0x08, 0xDC, 0x40, 0x00, 0xB0, 0x22, 0xA7, +0x8A, 0x51, 0x05, 0x30, 0xE1, 0x00, 0xE1, 0xCB, 0x13, 0xAC, 0x00, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, +0xC9, 0x00, 0xFD, 0xF9, 0x02, 0x38, 0xC9, 0x00, 0xDC, 0x40, 0x00, 0xB0, 0x22, 0xA7, 0x8A, 0x51, +0x00, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0xDF, 0xF9, 0x20, 0x38, 0xC9, 0x00, 0x49, 0x08, +0xDC, 0x40, 0x00, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x15, 0x70, 0xE2, 0x00, 0xC6, 0xB0, 0xE1, 0x00, +0xE1, 0xCB, 0x30, 0x6C, 0xE2, 0xCB, 0x30, 0x6C, 0x00, 0x00, 0x0D, 0x70, 0x83, 0x52, 0x03, 0x53, +0xCA, 0x00, 0x05, 0x30, 0xCB, 0x40, 0x84, 0x30, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xDC, 0x40, +0x6C, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x0D, 0x70, 0xCA, 0x00, 0x05, 0x30, +0xCB, 0x40, 0x85, 0x70, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, +0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x0D, 0x70, 0xCA, 0x00, 0x05, 0x30, 0xCB, 0x40, 0x82, 0x30, +0xCC, 0x00, 0x3F, 0x30, 0xCD, 0x40, 0x4A, 0x70, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x04, 0xF0, +0x32, 0xE7, 0x8A, 0x51, 0x0D, 0x70, 0xCA, 0x00, 0x05, 0x30, 0xCB, 0x40, 0x80, 0xF0, 0xCC, 0x00, +0x3E, 0xF0, 0xCD, 0x40, 0x4A, 0x70, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, +0x8A, 0x51, 0x0D, 0x70, 0xCA, 0x00, 0x05, 0x30, 0xCB, 0x40, 0x83, 0x70, 0xCC, 0x00, 0x3E, 0xF0, +0xCD, 0x40, 0x4A, 0x70, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, +0x6C, 0x83, 0x03, 0x9D, 0x85, 0xAC, 0x9C, 0xD5, 0x86, 0xAC, 0x1C, 0x95, 0x05, 0x30, 0xE1, 0x00, +0xE1, 0xCB, 0x88, 0x6C, 0x0D, 0x70, 0x83, 0x52, 0x03, 0x53, 0xCA, 0x00, 0x05, 0x30, 0xCB, 0x40, +0x87, 0xB0, 0xCC, 0x00, 0x09, 0x30, 0xCD, 0x40, 0x4A, 0x70, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, +0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0xD1, 0xB0, 0xCA, 0x00, 0x4A, 0x70, 0xDC, 0x40, 0x6C, 0x48, +0xDD, 0x80, 0x01, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x04, 0xF0, 0xE2, 0x00, 0x1C, 0x70, 0xE1, 0x00, +0xE1, 0xCB, 0xA8, 0xAC, 0xE2, 0xCB, 0xA8, 0xAC, 0x00, 0x00, 0x08, 0x40, 0x55, 0xB0, 0x83, 0x52, +0x9B, 0x40, 0xC8, 0x01, 0x02, 0xF0, 0x48, 0xC2, 0x03, 0x18, 0x72, 0x2D, 0x48, 0xC8, 0x01, 0xBE, +0x9B, 0x40, 0x48, 0xC8, 0xB2, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x48, 0xC8, 0xB2, 0x7E, +0x84, 0x80, 0x80, 0x88, 0x03, 0x9D, 0xEA, 0x2C, 0x3C, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, +0x48, 0x18, 0xD6, 0x2C, 0x48, 0xC8, 0xB2, 0x7E, 0x84, 0x80, 0x49, 0x08, 0xDE, 0x80, 0x07, 0x70, +0x03, 0xD0, 0xDE, 0x0C, 0xFF, 0x7E, 0x03, 0x9D, 0xD0, 0xAC, 0xE1, 0xEC, 0x48, 0xC8, 0xB2, 0x7E, +0x84, 0x80, 0x49, 0x08, 0xDE, 0x80, 0x05, 0x30, 0x03, 0xD0, 0xDE, 0x0C, 0xFF, 0x7E, 0x03, 0x9D, +0xDC, 0x2C, 0x5E, 0x88, 0x83, 0x93, 0x80, 0x40, 0x48, 0xC8, 0xB2, 0x7E, 0x84, 0x80, 0x01, 0xF0, +0x80, 0xC5, 0xBE, 0x6C, 0x3E, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x49, 0x08, 0x03, 0x59, +0x0A, 0xAD, 0x10, 0xF0, 0x49, 0x02, 0x03, 0x18, 0x0A, 0xAD, 0x48, 0xC8, 0xAE, 0xBE, 0x84, 0x80, +0x49, 0x08, 0x83, 0x93, 0x80, 0x40, 0x48, 0xC8, 0xAE, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, +0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x48, 0xC8, 0xAC, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, +0x80, 0xCA, 0x29, 0xED, 0x49, 0x08, 0xFF, 0x3A, 0x03, 0x9D, 0x16, 0xED, 0x48, 0xC8, 0xAE, 0xBE, +0x84, 0x80, 0x04, 0xF0, 0x83, 0x93, 0x80, 0x40, 0xFF, 0xB0, 0x20, 0x6D, 0x48, 0xC8, 0xAE, 0xBE, +0x84, 0x80, 0x04, 0xF0, 0x83, 0x93, 0x80, 0x40, 0x48, 0xC8, 0xAE, 0xBE, 0x84, 0x80, 0x00, 0x48, +0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x48, 0xC8, 0xAC, 0x7E, 0x84, 0x80, 0x83, 0x93, +0x80, 0x81, 0x3F, 0x30, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x49, 0x08, 0x03, 0x59, 0x49, 0xED, +0x0E, 0x70, 0x49, 0x02, 0x03, 0x18, 0x49, 0xED, 0x48, 0xC8, 0xA8, 0x3E, 0x84, 0x80, 0x49, 0x08, +0x83, 0x93, 0x80, 0x40, 0x48, 0xC8, 0xA8, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, 0x1C, 0x70, +0x22, 0xA7, 0x8A, 0x51, 0x48, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x80, 0xCA, +0x68, 0xED, 0x49, 0x08, 0xFF, 0x3A, 0x03, 0x9D, 0x55, 0x2D, 0x48, 0xC8, 0xA8, 0x3E, 0x84, 0x80, +0x0D, 0x70, 0x83, 0x93, 0x80, 0x40, 0xFF, 0xB0, 0x5F, 0xAD, 0x48, 0xC8, 0xA8, 0x3E, 0x84, 0x80, +0x0D, 0x70, 0x83, 0x93, 0x80, 0x40, 0x48, 0xC8, 0xA8, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, +0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x48, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, +0x48, 0xC8, 0xB0, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, 0x1A, 0x70, 0x22, 0xA7, 0x8A, 0x51, +0xC8, 0x4A, 0xB2, 0xEC, 0x03, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, +0x03, 0xBA, 0x03, 0x9D, 0x74, 0x2D, 0x3E, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, 0xAA, 0x00, 0x3F, 0x30, +0x2A, 0xE7, 0x8A, 0x51, 0xAC, 0x00, 0x2A, 0x02, 0x03, 0x5C, 0x9C, 0x2D, 0x0E, 0x70, 0x2A, 0x02, +0x03, 0x18, 0x9C, 0x2D, 0x2C, 0x08, 0x03, 0x59, 0x9C, 0x2D, 0x2A, 0x08, 0xDC, 0x40, 0x1B, 0xB0, +0x22, 0xA7, 0x8A, 0x51, 0x2C, 0x08, 0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x2A, 0x08, +0xA9, 0x00, 0x2C, 0x08, 0xAB, 0x40, 0xAA, 0x2D, 0x0D, 0x70, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, +0x8A, 0x51, 0x1C, 0x70, 0xDC, 0x81, 0xDC, 0xCA, 0x22, 0xA7, 0x8A, 0x51, 0x0D, 0x70, 0xA9, 0x00, +0xAB, 0x81, 0xAB, 0xCA, 0x04, 0xF0, 0x9B, 0x40, 0x3C, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, +0x04, 0x7A, 0x03, 0x9D, 0xAC, 0x2D, 0x3E, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, 0xAE, 0x40, 0x3F, 0x30, +0x2A, 0xE7, 0x8A, 0x51, 0xB0, 0xC0, 0x2E, 0x42, 0x03, 0x5C, 0xD0, 0xED, 0x02, 0xF0, 0x30, 0xC2, +0x03, 0x5C, 0xD0, 0xED, 0x2E, 0x48, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x30, 0xC8, +0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x2E, 0x48, 0xAD, 0x40, 0x30, 0xC8, 0xDD, 0xAD, +0x0C, 0x30, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x03, 0x30, 0xDC, 0x40, 0x1C, 0x70, +0x22, 0xA7, 0x8A, 0x51, 0x0C, 0x30, 0xAD, 0x40, 0x03, 0x30, 0xAF, 0x80, 0x05, 0x30, 0x9B, 0x40, +0x3C, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x05, 0xBA, 0x03, 0x9D, 0xE0, 0xED, 0x3E, 0xF0, +0x2A, 0xE7, 0x8A, 0x51, 0xB7, 0x80, 0x3F, 0x30, 0x2A, 0xE7, 0x8A, 0x51, 0xB9, 0x40, 0x37, 0x82, +0x03, 0x5C, 0x07, 0xEE, 0x10, 0xF0, 0x37, 0x82, 0x03, 0x18, 0x07, 0xEE, 0x39, 0x48, 0x03, 0x59, +0x07, 0xEE, 0x37, 0x88, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x39, 0x48, 0xDC, 0x40, +0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x37, 0x88, 0xB6, 0x40, 0x39, 0x48, 0x14, 0xAE, 0x0F, 0xB0, +0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x04, 0xF0, 0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, +0x8A, 0x51, 0x0F, 0xB0, 0xB6, 0x40, 0x04, 0xF0, 0xB8, 0x00, 0x06, 0x30, 0x9B, 0x40, 0x3C, 0xB0, +0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x06, 0xBA, 0x03, 0x9D, 0x17, 0x2E, 0x3D, 0xF0, 0x2A, 0xE7, +0x8A, 0x51, 0xBD, 0x80, 0x3E, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, 0xBB, 0x80, 0x3F, 0x30, 0x2A, 0xE7, +0x8A, 0x51, 0xBF, 0xC0, 0x3B, 0x88, 0x3D, 0x82, 0x03, 0x5C, 0x4A, 0xEE, 0x3F, 0xC8, 0x3B, 0x82, +0x03, 0x5C, 0x4A, 0xEE, 0x3F, 0xC8, 0x03, 0x59, 0x4A, 0xEE, 0x3D, 0x88, 0xDC, 0x40, 0x1A, 0x70, +0x22, 0xA7, 0x8A, 0x51, 0x3B, 0x88, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x3F, 0xC8, +0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x3D, 0x88, 0xBC, 0x40, 0x3B, 0x88, 0xBA, 0x40, +0x3F, 0xC8, 0x5E, 0x6E, 0x5A, 0xB0, 0xDC, 0x40, 0x1A, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x50, 0x30, +0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x28, 0x30, 0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, +0x8A, 0x51, 0x5A, 0xB0, 0xBC, 0x40, 0x50, 0x30, 0xBA, 0x40, 0x28, 0x30, 0xBE, 0x80, 0x07, 0x70, +0x9B, 0x40, 0x3C, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x07, 0xFA, 0x03, 0x9D, 0x61, 0xEE, +0x3E, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, 0xB1, 0x00, 0x3F, 0x30, 0x2A, 0xE7, 0x8A, 0x51, 0xB3, 0x40, +0x31, 0x02, 0x03, 0x5C, 0x8A, 0xEE, 0x10, 0xF0, 0x31, 0x02, 0x03, 0x18, 0x8A, 0xEE, 0x33, 0x48, +0x03, 0x59, 0x8A, 0xEE, 0x31, 0x08, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x33, 0x48, +0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x31, 0x08, 0x83, 0x96, 0xB4, 0x00, 0x83, 0x52, +0x33, 0x48, 0x99, 0x2E, 0x0E, 0x70, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x04, 0xF0, +0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x0E, 0x70, 0x83, 0x96, 0xB4, 0x00, 0x04, 0xF0, +0x83, 0x52, 0xB2, 0x00, 0x08, 0xF0, 0x9B, 0x40, 0x3C, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, +0x08, 0x7A, 0x03, 0x9D, 0x9C, 0x2E, 0x3E, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, 0xB4, 0x00, 0x3F, 0x30, +0x2A, 0xE7, 0x8A, 0x51, 0xB5, 0x40, 0x34, 0x08, 0x35, 0x42, 0x03, 0x18, 0xC4, 0xEE, 0x02, 0xF0, +0x35, 0x42, 0x03, 0x5C, 0xC4, 0xEE, 0x34, 0x08, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, +0x35, 0x48, 0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x34, 0x08, 0x83, 0x96, 0xB5, 0x40, +0x83, 0x52, 0x35, 0x48, 0x83, 0x96, 0xD2, 0x2E, 0x84, 0x30, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, +0x8A, 0x51, 0x34, 0x70, 0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x84, 0x30, 0x83, 0x96, +0xB5, 0x40, 0x34, 0x70, 0xB6, 0x40, 0x09, 0x30, 0x83, 0x52, 0x9B, 0x40, 0x3C, 0xB0, 0x2A, 0xE7, +0x8A, 0x51, 0xC9, 0x00, 0x09, 0xBA, 0x03, 0x9D, 0xD6, 0x6E, 0x3D, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, +0xC2, 0xC0, 0x3E, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, 0xC1, 0xC0, 0x3F, 0x30, 0x2A, 0xE7, 0x8A, 0x51, +0xC0, 0x80, 0x07, 0x70, 0x42, 0xC2, 0x03, 0x18, 0x05, 0xEF, 0x08, 0xF0, 0x41, 0xC2, 0x03, 0x18, +0x05, 0xEF, 0x02, 0xF0, 0x40, 0x82, 0x03, 0x18, 0x05, 0xEF, 0x42, 0xC8, 0xDC, 0x40, 0x1A, 0x70, +0x22, 0xA7, 0x8A, 0x51, 0x41, 0xC8, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x40, 0x88, +0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x18, 0xEF, 0x1A, 0x70, 0xDC, 0x81, 0xDC, 0xCA, +0x22, 0xA7, 0x8A, 0x51, 0x02, 0xF0, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x1C, 0x70, +0xDC, 0x81, 0x22, 0xA7, 0x8A, 0x51, 0x02, 0xF0, 0xC2, 0x01, 0xC2, 0x4A, 0xC1, 0xC0, 0xC0, 0xC1, +0x0A, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x0A, 0xBA, 0x03, 0x59, +0x08, 0x40, 0x1A, 0x2F, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x08, 0x40, 0x83, 0x52, 0x03, 0x53, 0xDC, 0x40, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0x08, 0x40, 0xDF, 0xC0, 0x5D, 0xD8, 0x50, 0xEF, 0xE0, 0x01, 0x5F, 0xC8, 0x60, 0xC2, +0x03, 0x18, 0x45, 0x2F, 0x60, 0xC8, 0x5C, 0xC7, 0xDE, 0x80, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, +0x99, 0x00, 0x18, 0x14, 0x18, 0xD0, 0xE0, 0x4A, 0x36, 0x6F, 0x18, 0x55, 0x18, 0x11, 0x83, 0x52, +0x03, 0x53, 0x11, 0x5C, 0x08, 0x40, 0x05, 0x30, 0xDE, 0x80, 0xDE, 0x4B, 0x4D, 0x6F, 0x47, 0x6F, +0xE0, 0x01, 0x5F, 0xC8, 0x60, 0xC2, 0x03, 0x18, 0x60, 0xEF, 0x60, 0xC8, 0x5C, 0xC7, 0xDE, 0x80, +0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x9A, 0x00, 0x98, 0x95, 0x98, 0x51, 0xE0, 0x4A, 0x51, 0x2F, +0x98, 0x96, 0x98, 0x52, 0x83, 0x52, 0x03, 0x53, 0x91, 0x9C, 0x08, 0x40, 0x05, 0x30, 0xDE, 0x80, +0xDE, 0x4B, 0x68, 0x2F, 0x62, 0x2F, 0xDF, 0xC0, 0x5D, 0xD8, 0x9A, 0x6F, 0xE0, 0x01, 0x5F, 0xC8, +0x60, 0xC2, 0x03, 0x18, 0x7E, 0xEF, 0x60, 0xC8, 0x5C, 0xC7, 0xDE, 0x80, 0x84, 0x80, 0x83, 0x93, +0x00, 0x48, 0x99, 0x00, 0x18, 0x14, 0x18, 0xD0, 0xE0, 0x4A, 0x6F, 0xEF, 0x18, 0x55, 0x18, 0x11, +0x83, 0x52, 0x03, 0x53, 0x11, 0x5C, 0x8A, 0x2F, 0x2A, 0x70, 0xDE, 0x80, 0xDE, 0x4B, 0x86, 0x2F, +0x00, 0x00, 0x80, 0xAF, 0xE0, 0x01, 0x5F, 0xC8, 0x60, 0xC2, 0x03, 0x18, 0x08, 0x40, 0x60, 0xC8, +0x5C, 0xC7, 0xDE, 0x80, 0x84, 0x80, 0x0F, 0x48, 0x83, 0x93, 0x80, 0x40, 0x98, 0x54, 0x98, 0x10, +0xE0, 0x4A, 0x8B, 0x6F, 0xE0, 0x01, 0x5F, 0xC8, 0x60, 0xC2, 0x03, 0x18, 0xAA, 0x6F, 0x60, 0xC8, +0x5C, 0xC7, 0xDE, 0x80, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x9A, 0x00, 0x98, 0x95, 0x98, 0x51, +0xE0, 0x4A, 0x9B, 0xAF, 0x98, 0x96, 0x98, 0x52, 0x83, 0x52, 0x03, 0x53, 0x91, 0x9C, 0xB6, 0xAF, +0x2A, 0x70, 0xDE, 0x80, 0xDE, 0x4B, 0xB2, 0x6F, 0x00, 0x00, 0xAC, 0x6F, 0xE0, 0x01, 0x5F, 0xC8, +0x60, 0xC2, 0x03, 0x18, 0x08, 0x40, 0x60, 0xC8, 0x5C, 0xC7, 0xDE, 0x80, 0x84, 0x80, 0x10, 0x88, +0x83, 0x93, 0x80, 0x40, 0x18, 0x56, 0x18, 0x12, 0xE0, 0x4A, 0xB7, 0xEF, 0xDE, 0x80, 0xDD, 0xC1, +0x5C, 0x48, 0x5E, 0xD8, 0xDD, 0x47, 0x03, 0xD0, 0xDC, 0x0D, 0x5C, 0x48, 0xDE, 0x18, 0xDD, 0x47, +0x03, 0xD0, 0xDC, 0x0D, 0x5C, 0x48, 0x5E, 0x19, 0xDD, 0x47, 0x03, 0xD0, 0xDC, 0x0D, 0x5C, 0x48, +0xDE, 0x59, 0xDD, 0x47, 0x03, 0xD0, 0xDC, 0x0D, 0x5C, 0x48, 0x5E, 0x1A, 0xDD, 0x47, 0x03, 0xD0, +0xDC, 0x0D, 0x5C, 0x48, 0xDE, 0x5A, 0xDD, 0x47, 0x03, 0xD0, 0xDC, 0x0D, 0x5C, 0x48, 0x5E, 0x5B, +0xDD, 0x47, 0x03, 0xD0, 0xDC, 0x0D, 0x5C, 0x48, 0xDE, 0x9B, 0xDD, 0x47, 0x5D, 0x88, 0x08, 0x40, +0xEF, 0x01, 0x83, 0x93, 0x21, 0x30, 0x84, 0x80, 0x5C, 0xB0, 0x8A, 0x51, 0x11, 0xE0, 0x8A, 0x51, +0xA0, 0x30, 0x84, 0x80, 0xC2, 0x70, 0x8A, 0x51, 0x11, 0xE0, 0x83, 0x01, 0x8A, 0x95, 0x5F, 0xAB, +0x08, 0xF0, 0x8A, 0xC0, 0x04, 0x88, 0x84, 0x0A, 0x82, 0x47, 0x00, 0xF4, 0x01, 0x34, 0x48, 0x74, +0x02, 0x34, 0x3D, 0x34, 0x03, 0x74, 0x33, 0xF4, 0x00, 0xF4, 0xA4, 0xB4, 0x00, 0xF4, 0xF6, 0x74, +0x03, 0x74, 0x33, 0xF4, 0x00, 0xF4, 0x52, 0xB4, 0x00, 0xF4, 0x52, 0xB4, 0x00, 0xF4, 0xCD, 0x34, +0x00, 0xF4, 0x29, 0xB4, 0x00, 0xF4, 0x29, 0xB4, 0x00, 0xF4, 0x66, 0xF4, 0x00, 0xF4, 0x15, 0xB4, +0x00, 0xF4, 0x15, 0xB4, 0x00, 0xF4, 0x29, 0xB4, 0x7F, 0xB4, 0x34, 0xB4, 0x2F, 0x34, 0x2A, 0xB4, +0x26, 0xB4, 0x21, 0x74, 0x1A, 0xB4, 0x16, 0xB4, 0x0F, 0xF4, 0x09, 0x74, 0x04, 0x34, 0x02, 0x34, +0x01, 0x34, 0x00, 0xF4, 0x00, 0xF4, 0x00, 0xF4, 0x00, 0xF4, 0x03, 0x74, 0x03, 0x74, 0x00, 0xF4, +0x00, 0xF4, 0x03, 0x74, 0x03, 0x74, 0x07, 0xB4, 0x01, 0x34, 0x01, 0x34, 0x01, 0x34, 0x07, 0xB4, +0x07, 0xB4, 0x00, 0xF4, 0x00, 0xF4, 0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, 0x00, 0xF4, 0x07, 0xB4, +0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xC8, 0x01, 0x02, 0xF0, 0x48, 0xC2, +0x03, 0x18, 0x3D, 0x6B, 0x48, 0xC8, 0xB0, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x48, 0xC8, +0xAE, 0xBE, 0x84, 0x80, 0x80, 0x81, 0x48, 0xC8, 0xA8, 0x3E, 0x84, 0x80, 0x80, 0x81, 0x48, 0xC8, +0xB2, 0x7E, 0x84, 0x80, 0x80, 0x81, 0x48, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x80, 0x81, 0x48, 0xC8, +0xAC, 0x7E, 0x84, 0x80, 0x80, 0x81, 0xC8, 0x4A, 0x1E, 0x2B, 0x0D, 0x70, 0xAB, 0x81, 0xAB, 0xCA, +0xA9, 0x00, 0x03, 0x30, 0xAF, 0x80, 0x0C, 0x30, 0xAD, 0x40, 0x04, 0xF0, 0xB8, 0x00, 0x0F, 0xB0, +0xB6, 0x40, 0x28, 0x30, 0xBE, 0x80, 0x50, 0x30, 0xBA, 0x40, 0x5A, 0xB0, 0xBC, 0x40, 0x04, 0xF0, +0xB2, 0x00, 0x0E, 0x70, 0x83, 0x96, 0xB4, 0x00, 0x34, 0x70, 0xB6, 0x40, 0x84, 0x30, 0xB5, 0x40, +0x83, 0x52, 0x02, 0xF0, 0xC2, 0x01, 0xC2, 0x4A, 0xC1, 0xC0, 0xC0, 0xC1, 0x08, 0x40, 0x9B, 0x81, +0x01, 0xF0, 0xA0, 0x80, 0x95, 0x41, 0x96, 0x41, 0x97, 0x81, 0x98, 0x01, 0x99, 0x41, 0x9A, 0x41, +0x9B, 0x81, 0x9C, 0x41, 0x9E, 0x81, 0x55, 0xB0, 0x9E, 0x40, 0x9B, 0x81, 0x1C, 0x70, 0xDC, 0x81, +0xDC, 0xCA, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x8A, 0x95, 0x1D, 0xE3, 0x8A, 0x95, 0x3D, 0xF0, +0x8A, 0x51, 0x2A, 0xE7, 0x8A, 0x95, 0xC9, 0x00, 0xF7, 0xFA, 0x03, 0x9D, 0x82, 0xAB, 0x83, 0x96, +0xBF, 0x01, 0x85, 0xEB, 0x83, 0x96, 0xBF, 0x01, 0xBF, 0x4A, 0x3F, 0xC8, 0x03, 0x59, 0x8C, 0xEB, +0x8A, 0x51, 0xAE, 0xE4, 0x8A, 0x95, 0x8F, 0x6B, 0x01, 0xF0, 0x83, 0x52, 0x9B, 0x40, 0x8A, 0x51, +0x27, 0x60, 0x8A, 0x95, 0x00, 0xB0, 0x8A, 0x51, 0x2A, 0xE7, 0x8A, 0x95, 0xC9, 0x00, 0xFE, 0xF9, +0x01, 0x38, 0xC9, 0x00, 0xDC, 0x40, 0x00, 0xB0, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x00, 0xB0, +0x8A, 0x51, 0x2A, 0xE7, 0x8A, 0x95, 0xC9, 0x00, 0xEF, 0xF9, 0x10, 0x38, 0xC9, 0x00, 0x49, 0x08, +0xDC, 0x40, 0x00, 0xB0, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x1C, 0x54, 0x9C, 0x94, 0x9C, 0xD6, +0x35, 0xB0, 0xED, 0x80, 0xED, 0x4B, 0xB2, 0x2B, 0x83, 0x52, 0x03, 0x53, 0x9C, 0x92, 0x9C, 0x17, +0x35, 0xB0, 0xED, 0x80, 0xED, 0x4B, 0xBA, 0x6B, 0x83, 0x52, 0x03, 0x53, 0x9C, 0xD3, 0xC8, 0x01, +0x02, 0xF0, 0x48, 0xC2, 0x03, 0x18, 0x8D, 0xEC, 0x08, 0xF0, 0xE1, 0x00, 0xAC, 0xB0, 0xE2, 0x00, +0x03, 0x30, 0xE3, 0x40, 0x48, 0xC8, 0xE4, 0x00, 0x0D, 0x70, 0x8A, 0x51, 0x19, 0x20, 0x8A, 0x95, +0x48, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0xCB, 0xE0, 0xEB, 0x48, 0xC8, 0xA8, 0x3E, +0x84, 0x80, 0x00, 0x48, 0xED, 0x80, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, 0xE4, 0x2B, +0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x29, 0x08, 0x80, 0x40, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, +0x00, 0x48, 0x1F, 0xBE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x95, 0xED, 0x80, 0x48, 0xC8, +0x27, 0x7E, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x08, 0xF0, 0xE1, 0x00, 0xB6, 0xF0, 0xE2, 0x00, +0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x20, 0x38, 0xE3, 0x40, 0x48, 0xC8, 0xE4, 0x00, +0x0D, 0x70, 0x8A, 0x51, 0x19, 0x20, 0x8A, 0x95, 0x48, 0xC8, 0xAC, 0x7E, 0x84, 0x80, 0x83, 0x93, +0x00, 0xCB, 0x14, 0x6C, 0x48, 0xC8, 0xAE, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xED, 0x80, 0x48, 0xC8, +0x5A, 0x7E, 0x84, 0x80, 0x6D, 0x88, 0x18, 0x6C, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x38, 0x08, +0x80, 0x40, 0x06, 0x30, 0xDC, 0x40, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x38, 0x08, 0x00, 0x42, +0x8A, 0x51, 0xC6, 0x27, 0x8A, 0x95, 0xED, 0x80, 0x48, 0xC8, 0xAA, 0x7E, 0x84, 0x80, 0x6D, 0x88, +0x83, 0x93, 0x80, 0x40, 0x48, 0xC8, 0xAA, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xED, 0x80, 0x48, 0xC8, +0x27, 0x7E, 0x84, 0x80, 0x00, 0x48, 0x6D, 0x07, 0xEE, 0x80, 0x48, 0xC8, 0x25, 0x3E, 0x84, 0x80, +0x6E, 0x88, 0x80, 0x40, 0x48, 0xC8, 0x25, 0x3E, 0x84, 0x80, 0x7F, 0x70, 0x00, 0x42, 0x03, 0x5C, +0x46, 0xAC, 0x48, 0xC8, 0x25, 0x3E, 0x84, 0x80, 0x7F, 0x70, 0x80, 0x40, 0x48, 0x18, 0x4F, 0x2C, +0x48, 0xC8, 0x25, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, 0x03, 0x30, 0x55, 0xEC, 0x48, 0xC8, +0x25, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, 0x04, 0xF0, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, +0x48, 0x18, 0x71, 0xEC, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0xED, 0x80, +0x01, 0xF0, 0x03, 0xD0, 0xED, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x62, 0xAC, 0x6D, 0x0D, +0x02, 0xBE, 0xC9, 0x00, 0xDC, 0x40, 0x01, 0xF0, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x01, 0xF0, +0x87, 0xEC, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0xED, 0x80, 0x01, 0xF0, +0x03, 0xD0, 0xED, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x79, 0x2C, 0x6D, 0x0D, 0x02, 0xBE, +0xC9, 0x00, 0xDC, 0x40, 0x02, 0xF0, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x02, 0xF0, 0xDC, 0x81, +0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0xC8, 0x4A, 0xC0, 0xAB, 0xC4, 0x01, 0x08, 0xF0, 0x44, 0xC2, +0x03, 0x18, 0xB0, 0xAC, 0x44, 0xC8, 0xDC, 0x40, 0x05, 0x30, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, +0x44, 0xC8, 0xA0, 0xFE, 0x84, 0x80, 0x32, 0x08, 0x83, 0x93, 0x80, 0x40, 0x44, 0xC8, 0xA0, 0xFE, +0x84, 0x80, 0x03, 0x14, 0x00, 0xCD, 0xC9, 0x00, 0xDC, 0x40, 0x06, 0x30, 0x8A, 0x51, 0x22, 0xA7, +0x8A, 0x95, 0x06, 0x30, 0xDC, 0x81, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0xC4, 0x4A, 0x8E, 0xEC, +0x34, 0x70, 0x9E, 0x40, 0x83, 0x52, 0x12, 0x18, 0x1C, 0x9C, 0xBC, 0x2C, 0x1C, 0x10, 0x9B, 0xD4, +0x00, 0xB0, 0x8A, 0x51, 0x6A, 0xA1, 0x8A, 0x95, 0x83, 0x52, 0x03, 0x53, 0x12, 0x5C, 0x1C, 0x58, +0xC9, 0xEC, 0x00, 0xB0, 0x8A, 0x51, 0x69, 0x60, 0x8A, 0x95, 0x83, 0x52, 0x03, 0x53, 0x1C, 0x54, +0x9B, 0x90, 0x92, 0x58, 0x9C, 0xDC, 0xD2, 0xEC, 0x9C, 0x50, 0x9B, 0xD4, 0x01, 0xF0, 0x8A, 0x51, +0x6A, 0xA1, 0x8A, 0x95, 0x83, 0x52, 0x03, 0x53, 0x92, 0x9C, 0x9C, 0x98, 0xDF, 0xAC, 0x01, 0xF0, +0x8A, 0x51, 0x69, 0x60, 0x8A, 0x95, 0x83, 0x52, 0x03, 0x53, 0x9C, 0x94, 0x9B, 0x90, 0xC4, 0x01, +0x08, 0xF0, 0x44, 0xC2, 0x03, 0x18, 0x2E, 0x2D, 0x44, 0xC8, 0xDC, 0x40, 0x05, 0x30, 0x8A, 0x51, +0x22, 0xA7, 0x8A, 0x95, 0x24, 0x30, 0x8A, 0x51, 0x2A, 0xE7, 0x8A, 0x95, 0xC5, 0x00, 0x83, 0x96, +0x35, 0x42, 0x03, 0x18, 0x04, 0x6D, 0x83, 0x52, 0x44, 0xC8, 0xA0, 0xFE, 0x84, 0x80, 0x83, 0x96, +0x34, 0x08, 0x83, 0x93, 0x00, 0x42, 0x03, 0x18, 0x04, 0x6D, 0x83, 0x52, 0x44, 0xC8, 0xA0, 0xFE, +0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0x16, 0xED, 0x36, 0x48, 0x83, 0x52, 0x45, 0x02, 0x03, 0x18, +0x2C, 0xED, 0x44, 0xC8, 0xA0, 0xFE, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x32, 0x02, 0x03, 0x18, +0x2C, 0xED, 0x44, 0xC8, 0xA0, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xED, 0x80, 0x44, 0xC8, +0xA0, 0xFE, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x44, 0xC8, 0xA0, 0xFE, 0x84, 0x80, 0x03, 0x14, +0x00, 0xCD, 0xC9, 0x00, 0xDC, 0x40, 0x06, 0x30, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x06, 0x30, +0xDC, 0x81, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0xC4, 0x4A, 0xE0, 0xAC, 0x00, 0xB0, 0x14, 0x59, +0x80, 0xF0, 0x94, 0x99, 0x40, 0x38, 0x14, 0x18, 0x20, 0x38, 0x94, 0x58, 0x10, 0x38, 0x21, 0xC4, +0x9E, 0x40, 0x83, 0x96, 0xC0, 0xC1, 0x83, 0x52, 0x21, 0x30, 0xC8, 0x01, 0x8A, 0x51, 0x2A, 0xE7, +0x8A, 0x95, 0xC6, 0x00, 0x20, 0xF0, 0x8A, 0x51, 0x2A, 0xE7, 0x8A, 0x95, 0xC3, 0x00, 0x94, 0x9C, +0x9B, 0x6D, 0x48, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x88, 0x03, 0x9D, 0x90, 0xAD, +0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x2B, 0x42, 0x03, 0x18, 0x90, 0xAD, 0x48, 0xC8, +0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xED, 0x80, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, +0x6D, 0x88, 0x80, 0x40, 0x08, 0xF0, 0xE1, 0x00, 0xB6, 0xF0, 0xE2, 0x00, 0x48, 0xC8, 0x21, 0xFE, +0x84, 0x80, 0x00, 0x48, 0x20, 0x38, 0xE3, 0x40, 0x48, 0xC8, 0xE4, 0x00, 0x0D, 0x70, 0x8A, 0x51, +0x19, 0x20, 0x8A, 0x95, 0x01, 0xF0, 0xDC, 0x81, 0xDC, 0xCA, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, +0x01, 0xF0, 0xDC, 0x81, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, +0x83, 0x93, 0x00, 0x48, 0x1F, 0xBE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x95, 0xED, 0x80, +0x48, 0xC8, 0x27, 0x7E, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x83, 0x96, 0xC0, 0xC1, 0xC0, 0x0A, +0x83, 0x52, 0x9C, 0x92, 0x1C, 0x96, 0x05, 0x30, 0xED, 0x80, 0xED, 0x4B, 0x95, 0x2D, 0x83, 0x52, +0x03, 0x53, 0x1C, 0x52, 0x71, 0x2E, 0x48, 0xC8, 0xAC, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x88, +0x03, 0x9D, 0x15, 0xEE, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x29, 0x46, 0x03, 0x9D, +0xAB, 0x6D, 0x3A, 0x48, 0xAC, 0x2D, 0x3C, 0x48, 0xC7, 0x40, 0x46, 0x08, 0x47, 0x42, 0x03, 0x18, +0xD0, 0xED, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x36, 0x48, 0x00, 0x42, 0x03, 0x18, 0xD0, 0xED, +0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0xED, 0x80, 0x48, 0xC8, 0x5A, 0x7E, +0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xED, 0x80, +0x01, 0xF0, 0x03, 0xD0, 0xED, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xCA, 0x2D, 0xF2, 0x6D, +0x3E, 0x88, 0x46, 0x02, 0x48, 0xC8, 0x03, 0x18, 0x16, 0xEE, 0x5A, 0x7E, 0x84, 0x80, 0x00, 0x48, +0x38, 0x02, 0x48, 0xC8, 0x03, 0x18, 0x16, 0xEE, 0x5A, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, +0xED, 0x80, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x48, 0xC8, 0x5A, 0x7E, +0x84, 0x80, 0x00, 0x48, 0xED, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xED, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, +0x03, 0x9D, 0xED, 0xAD, 0x6D, 0x0D, 0x02, 0xBE, 0xC9, 0x00, 0xDC, 0x40, 0x01, 0xF0, 0x8A, 0x51, +0x22, 0xA7, 0x8A, 0x95, 0x01, 0xF0, 0xDC, 0x81, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x83, 0x96, +0x06, 0x30, 0xC0, 0xC1, 0xC0, 0x0A, 0x83, 0x52, 0xDC, 0x40, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, +0x38, 0x08, 0x83, 0x93, 0x00, 0x42, 0x8A, 0x51, 0xC6, 0x27, 0x8A, 0x95, 0xED, 0x80, 0x48, 0xC8, +0xAA, 0x7E, 0x84, 0x80, 0x6D, 0x88, 0x83, 0x93, 0x80, 0x40, 0x48, 0xC8, 0x23, 0x3E, 0x84, 0x80, +0x80, 0x88, 0x03, 0x9D, 0x71, 0x2E, 0x43, 0x08, 0x2D, 0x42, 0x03, 0x18, 0x2C, 0xEE, 0x48, 0xC8, +0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x2B, 0x42, 0x03, 0x18, 0x2C, 0xEE, 0x48, 0xC8, 0x21, 0xFE, +0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0x3C, 0x2E, 0x2F, 0x88, 0x43, 0x02, 0x03, 0x18, 0x71, 0x2E, +0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x29, 0x08, 0x00, 0x42, 0x03, 0x18, 0x71, 0x2E, 0x48, 0xC8, +0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0xED, 0x80, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, +0x6D, 0x88, 0x80, 0x40, 0x08, 0xF0, 0xE1, 0x00, 0xB6, 0xF0, 0xE2, 0x00, 0x48, 0xC8, 0x21, 0xFE, +0x84, 0x80, 0x00, 0x48, 0x20, 0x38, 0xE3, 0x40, 0x48, 0xC8, 0xE4, 0x00, 0x0D, 0x70, 0x8A, 0x51, +0x19, 0x20, 0x8A, 0x95, 0x01, 0xF0, 0xDC, 0x81, 0xDC, 0xCA, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, +0x01, 0xF0, 0xDC, 0x81, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x83, 0x96, 0xC0, 0xC1, 0xC0, 0x0A, +0x83, 0x52, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x1F, 0xBE, 0x84, 0x80, +0x8A, 0x95, 0x00, 0x60, 0x8A, 0x95, 0xED, 0x80, 0x48, 0xC8, 0x27, 0x7E, 0x84, 0x80, 0x6D, 0x88, +0x80, 0x40, 0x83, 0x96, 0x40, 0x0B, 0x9B, 0x6E, 0x83, 0x52, 0x48, 0xC8, 0xAA, 0x7E, 0x84, 0x80, +0x83, 0x93, 0x00, 0x48, 0xED, 0x80, 0x48, 0xC8, 0x27, 0x7E, 0x84, 0x80, 0x00, 0x48, 0x6D, 0x07, +0xEE, 0x80, 0x48, 0xC8, 0x25, 0x3E, 0x84, 0x80, 0x6E, 0x88, 0x80, 0x40, 0x48, 0xC8, 0x25, 0x3E, +0x84, 0x80, 0x7F, 0x70, 0x00, 0x42, 0x48, 0xC8, 0x03, 0x5C, 0x93, 0x2E, 0x25, 0x3E, 0x84, 0x80, +0x7F, 0x70, 0x80, 0x40, 0x48, 0xC8, 0x25, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, 0x03, 0x30, +0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x83, 0x52, 0x23, 0x70, 0xC8, 0x01, 0xC8, 0x4A, 0x8A, 0x51, +0x2A, 0xE7, 0x8A, 0x95, 0xC6, 0x00, 0x22, 0x30, 0x8A, 0x51, 0x2A, 0xE7, 0x8A, 0x95, 0xC3, 0x00, +0x83, 0x96, 0xC1, 0x01, 0x83, 0x52, 0x94, 0xDD, 0xFF, 0x2E, 0x48, 0xC8, 0x23, 0x3E, 0x84, 0x80, +0x83, 0x93, 0x80, 0x88, 0x03, 0x9D, 0xF4, 0x6E, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, +0x2B, 0x42, 0x03, 0x18, 0xF4, 0x6E, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, +0xED, 0x80, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x08, 0xF0, 0xE1, 0x00, +0xB6, 0xF0, 0xE2, 0x00, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x20, 0x38, 0xE3, 0x40, +0x48, 0xC8, 0xE4, 0x00, 0x0D, 0x70, 0x8A, 0x51, 0x19, 0x20, 0x8A, 0x95, 0x02, 0xF0, 0xDC, 0x81, +0xDC, 0xCA, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x02, 0xF0, 0xDC, 0x81, 0x8A, 0x51, 0x22, 0xA7, +0x8A, 0x95, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x1F, 0xBE, 0x84, 0x80, +0x8A, 0x95, 0x00, 0x60, 0x8A, 0x95, 0xED, 0x80, 0x48, 0xC8, 0x27, 0x7E, 0x84, 0x80, 0x6D, 0x88, +0x80, 0x40, 0x83, 0x96, 0xC1, 0x01, 0xC1, 0x4A, 0x83, 0x52, 0x9C, 0xD3, 0x1C, 0xD7, 0x05, 0x30, +0xED, 0x80, 0xED, 0x4B, 0xF9, 0xAE, 0x83, 0x52, 0x03, 0x53, 0x1C, 0x93, 0xD5, 0xAF, 0x48, 0xC8, +0xAC, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x88, 0x03, 0x9D, 0x79, 0xAF, 0x48, 0xC8, 0x21, 0xFE, +0x84, 0x80, 0x00, 0x48, 0x29, 0x46, 0x03, 0x9D, 0x0F, 0x6F, 0x3A, 0x48, 0x10, 0xAF, 0x3C, 0x48, +0xC7, 0x40, 0x46, 0x08, 0x47, 0x42, 0x03, 0x18, 0x34, 0x2F, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, +0x36, 0x48, 0x00, 0x42, 0x03, 0x18, 0x34, 0x2F, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x00, 0x48, +0x01, 0xBE, 0xED, 0x80, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x48, 0xC8, +0x5A, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xED, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xED, 0x4D, 0xFF, 0x7E, +0x03, 0xD0, 0x03, 0x9D, 0x2E, 0x6F, 0x56, 0x6F, 0x3E, 0x88, 0x46, 0x02, 0x48, 0xC8, 0x03, 0x18, +0x7A, 0xAF, 0x5A, 0x7E, 0x84, 0x80, 0x00, 0x48, 0x38, 0x02, 0x48, 0xC8, 0x03, 0x18, 0x7A, 0xAF, +0x5A, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xED, 0x80, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, +0x6D, 0x88, 0x80, 0x40, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xED, 0x80, 0x01, 0xF0, +0x03, 0xD0, 0xED, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x51, 0x2F, 0x6D, 0x0D, 0x02, 0xBE, +0xC9, 0x00, 0xDC, 0x40, 0x02, 0xF0, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x02, 0xF0, 0xDC, 0x81, +0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x83, 0x96, 0x06, 0x30, 0xC1, 0x01, 0xC1, 0x4A, 0x83, 0x52, +0xDC, 0x40, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x38, 0x08, 0x83, 0x93, 0x00, 0x42, 0x8A, 0x51, +0xC6, 0x27, 0x8A, 0x95, 0xED, 0x80, 0x48, 0xC8, 0xAA, 0x7E, 0x84, 0x80, 0x6D, 0x88, 0x83, 0x93, +0x80, 0x40, 0x48, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x80, 0x88, 0x03, 0x9D, 0xD5, 0xAF, 0x43, 0x08, +0x2D, 0x42, 0x03, 0x18, 0x90, 0xEF, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x2B, 0x42, +0x03, 0x18, 0x90, 0xEF, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xA0, 0xEF, +0x2F, 0x88, 0x43, 0x02, 0x03, 0x18, 0xD5, 0xAF, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x29, 0x08, +0x00, 0x42, 0x03, 0x18, 0xD5, 0xAF, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, +0xED, 0x80, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x08, 0xF0, 0xE1, 0x00, +0xB6, 0xF0, 0xE2, 0x00, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x20, 0x38, 0xE3, 0x40, +0x48, 0xC8, 0xE4, 0x00, 0x0D, 0x70, 0x8A, 0x51, 0x19, 0x20, 0x8A, 0x95, 0x02, 0xF0, 0xDC, 0x81, +0xDC, 0xCA, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x02, 0xF0, 0xDC, 0x81, 0x8A, 0x51, 0x22, 0xA7, +0x8A, 0x95, 0x83, 0x96, 0xC1, 0x01, 0xC1, 0x4A, 0x83, 0x52, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, +0x83, 0x93, 0x00, 0x48, 0x1F, 0xBE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x95, 0xED, 0x80, +0x48, 0xC8, 0x27, 0x7E, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x83, 0x96, 0x41, 0x4B, 0xB2, 0xEC, +0x83, 0x52, 0x48, 0xC8, 0xAA, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0xED, 0x80, 0x48, 0xC8, +0x27, 0x7E, 0x84, 0x80, 0x00, 0x48, 0x6D, 0x07, 0xEE, 0x80, 0x48, 0xC8, 0x25, 0x3E, 0x84, 0x80, +0x6E, 0x88, 0x80, 0x40, 0x48, 0xC8, 0x25, 0x3E, 0x84, 0x80, 0x7F, 0x70, 0x00, 0x42, 0x48, 0xC8, +0x03, 0x5C, 0xF7, 0x2F, 0x25, 0x3E, 0x84, 0x80, 0x7F, 0x70, 0x80, 0x40, 0x48, 0xC8, 0x25, 0x3E, +0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, 0x04, 0xF0, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0xB2, 0xEC +}; \ No newline at end of file diff --git a/libloragw/src/agc_fw_sx1257.var b/libloragw/src/agc_fw_sx1257.var new file mode 100644 index 0000000..73a0b0b --- /dev/null +++ b/libloragw/src/agc_fw_sx1257.var @@ -0,0 +1,515 @@ +static uint8_t agc_firmware_sx125x[8192] = { +0x8A, 0x51, 0xCF, 0xEF, 0x00, 0xB0, 0x8A, 0xC0, 0x04, 0x88, 0x84, 0x0A, 0x82, 0x47, 0x00, 0xF4, +0x0F, 0xF4, 0x0C, 0x74, 0x09, 0x74, 0x09, 0x74, 0x09, 0x74, 0x0C, 0x74, 0x0F, 0xF4, 0x0C, 0x74, +0x0F, 0xF4, 0x0C, 0x74, 0x00, 0xF4, 0x06, 0x74, 0x0C, 0x74, 0x12, 0x74, 0x18, 0x74, 0x1E, 0xF4, +0x24, 0x74, 0x2A, 0xB4, 0x30, 0x74, 0x36, 0xF4, 0x01, 0x34, 0x01, 0x34, 0x01, 0x34, 0x02, 0x34, +0x03, 0x74, 0x04, 0x34, 0x05, 0x74, 0x05, 0x74, 0x06, 0x74, 0x06, 0x74, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0x64, 0xC0, 0x80, 0x81, 0x84, 0x0A, +0x04, 0xC6, 0x03, 0x59, 0x00, 0xF4, 0x04, 0xC6, 0xB6, 0x29, 0xDF, 0xC0, 0xDE, 0xC1, 0x5D, 0x88, +0x5F, 0x18, 0xDE, 0x47, 0x03, 0xD0, 0xDD, 0x4D, 0x5D, 0x88, 0xDF, 0x58, 0xDE, 0x47, 0x03, 0xD0, +0xDD, 0x4D, 0x5D, 0x88, 0x5F, 0x59, 0xDE, 0x47, 0x03, 0xD0, 0xDD, 0x4D, 0x5D, 0x88, 0xDF, 0x99, +0xDE, 0x47, 0x03, 0xD0, 0xDD, 0x4D, 0x5D, 0x88, 0x5F, 0x5A, 0xDE, 0x47, 0x03, 0xD0, 0xDD, 0x4D, +0x5D, 0x88, 0xDF, 0x9A, 0xDE, 0x47, 0x03, 0xD0, 0xDD, 0x4D, 0x5D, 0x88, 0x5F, 0x9B, 0xDE, 0x47, +0x03, 0xD0, 0xDD, 0x4D, 0x5D, 0x88, 0xDF, 0xDB, 0xDE, 0x47, 0x5E, 0x88, 0x08, 0x40, 0xD7, 0xC1, +0x02, 0xF0, 0x57, 0x82, 0x03, 0x18, 0x07, 0xAA, 0x57, 0x88, 0x2D, 0x7E, 0x84, 0x80, 0x83, 0x93, +0x80, 0x81, 0x57, 0x88, 0x2B, 0x7E, 0x84, 0x80, 0x80, 0x81, 0x57, 0x88, 0x25, 0x3E, 0x84, 0x80, +0x80, 0x81, 0x57, 0x88, 0x33, 0x7E, 0x84, 0x80, 0x80, 0x81, 0x57, 0x88, 0xA8, 0x3E, 0x84, 0x80, +0x80, 0x81, 0x57, 0x88, 0x29, 0x3E, 0x84, 0x80, 0x80, 0x81, 0xD7, 0x0A, 0xE8, 0xE9, 0x09, 0x30, +0xB7, 0xC1, 0xB5, 0x40, 0x10, 0xF0, 0xBB, 0x80, 0x23, 0x70, 0xB9, 0x40, 0x07, 0x70, 0xC7, 0x40, +0x0B, 0x70, 0xC5, 0x00, 0x2D, 0xB0, 0xCD, 0x40, 0x64, 0x70, 0xC9, 0x00, 0x73, 0xF0, 0xCB, 0x40, +0x04, 0xF0, 0xBF, 0xC0, 0x0E, 0x70, 0xBD, 0x80, 0x34, 0x70, 0xC3, 0x00, 0x84, 0x30, 0xC1, 0xC0, +0x08, 0x40, 0x01, 0xF0, 0xA0, 0x80, 0x95, 0x41, 0x96, 0x41, 0x97, 0x81, 0x98, 0x01, 0x99, 0x41, +0x9A, 0x41, 0x9B, 0x81, 0x9C, 0x41, 0x9E, 0x81, 0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0xFE, 0xF9, 0x01, 0x38, 0xEF, 0xC0, +0x6F, 0xC8, 0xDD, 0x80, 0x00, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0xEF, 0xF9, 0x10, 0x38, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, +0x00, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x1C, 0x54, 0x9C, 0x94, 0x83, 0x96, 0xAC, 0x41, 0xAD, 0x81, 0x83, 0x52, 0xB3, 0x81, 0xB4, 0x41, +0x9B, 0x81, 0x1C, 0x70, 0xDD, 0xC1, 0xDD, 0x0A, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x8A, 0x51, 0xE7, 0x21, 0x8A, 0x51, 0x3D, 0xF0, 0xDD, 0x80, +0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0xF7, 0xFA, +0x03, 0x9D, 0x7C, 0x2A, 0xCF, 0xC1, 0x7E, 0x6A, 0xCF, 0xC1, 0xCF, 0x0A, 0x4F, 0x88, 0x03, 0x59, +0x85, 0xAA, 0x8A, 0x95, 0xB1, 0xA4, 0x8A, 0x51, 0x87, 0xEA, 0x01, 0xF0, 0x9B, 0x40, 0xD7, 0xC1, +0x02, 0xF0, 0x57, 0x82, 0x03, 0x18, 0x94, 0xEB, 0x57, 0x88, 0xA8, 0x3E, 0x84, 0x80, 0x83, 0x93, +0x00, 0xCB, 0x9C, 0xEA, 0x57, 0x88, 0x25, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xEB, 0x80, 0x57, 0x88, +0x23, 0x3E, 0x84, 0x80, 0x6B, 0x88, 0xA0, 0x6A, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x37, 0x88, +0x80, 0x40, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, +0x02, 0xA0, 0x8A, 0x51, 0x0F, 0x39, 0xEB, 0x80, 0x03, 0xD0, 0xEB, 0x4D, 0x57, 0x88, 0x23, 0x3E, +0x84, 0x80, 0x00, 0x48, 0x15, 0x3E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x07, 0xF9, +0xEC, 0x40, 0x04, 0xF0, 0x03, 0xD0, 0xEC, 0x0D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xBB, 0x6A, +0x6C, 0xCD, 0x6B, 0x84, 0x01, 0x38, 0xED, 0x80, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, +0x80, 0x40, 0x8C, 0x70, 0x83, 0x96, 0xB5, 0x40, 0x83, 0x52, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, +0x00, 0x48, 0x83, 0x96, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x57, 0x88, 0xDE, 0x80, +0x02, 0xF0, 0x8A, 0x95, 0x15, 0x64, 0x8A, 0x51, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x83, 0x93, +0x00, 0x48, 0x0B, 0x3E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xEB, 0x80, 0x57, 0x88, +0x31, 0x3E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0x29, 0x3E, 0x84, 0x80, 0x00, 0xCB, +0xFB, 0xAA, 0x57, 0x88, 0x2B, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xEB, 0x80, 0x57, 0x88, 0x59, 0x7E, +0x84, 0x80, 0x6B, 0x88, 0xFF, 0xEA, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x47, 0x48, 0x80, 0x40, +0x06, 0x30, 0xDD, 0x80, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x47, 0x48, 0x00, 0x42, 0x8A, 0x51, +0xBD, 0x21, 0x8A, 0x51, 0xEB, 0x80, 0x57, 0x88, 0x27, 0x7E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, +0x57, 0x88, 0x27, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xEB, 0x80, 0x57, 0x88, 0x31, 0x3E, 0x84, 0x80, +0x00, 0x48, 0x6B, 0x07, 0xEC, 0x40, 0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, 0x6C, 0x48, 0x80, 0x40, +0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, 0x7F, 0x70, 0x00, 0x42, 0x03, 0x5C, 0x2C, 0xEB, 0x57, 0x88, +0x2F, 0xBE, 0x84, 0x80, 0x7F, 0x70, 0x80, 0x40, 0x57, 0xD8, 0x62, 0xEB, 0x57, 0x88, 0x2F, 0xBE, +0x84, 0x80, 0x00, 0x48, 0xDD, 0x80, 0x03, 0x30, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x57, 0xD8, 0x70, 0xEB, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, +0x83, 0x93, 0x00, 0x48, 0xEB, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xEB, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, +0x03, 0x9D, 0x45, 0xEB, 0x6B, 0x0D, 0x02, 0xBE, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, 0x01, 0xF0, +0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x01, 0xF0, +0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0xD7, 0x0A, 0x88, 0x6A, 0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xDD, 0x80, 0x04, 0xF0, +0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3B, 0x6B, +0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0xEB, 0x80, 0x01, 0xF0, 0x03, 0xD0, +0xEB, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x78, 0x2B, 0x6B, 0x0D, 0x02, 0xBE, 0xEF, 0xC0, +0x6F, 0xC8, 0xDD, 0x80, 0x02, 0xF0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x02, 0xF0, 0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x60, 0xAB, 0xD1, 0x41, 0x51, 0x08, 0xDD, 0x80, 0x05, 0x30, +0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x51, 0x08, +0xA0, 0xFE, 0x84, 0x80, 0x3F, 0xC8, 0x83, 0x93, 0x80, 0x40, 0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, +0x03, 0x14, 0x00, 0xCD, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, 0x06, 0x30, 0xDE, 0x80, 0x5D, 0x88, +0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x06, 0x30, 0xDD, 0xC1, 0xDE, 0x80, +0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x08, 0xF0, 0xD1, 0x8A, +0x51, 0x02, 0x03, 0x5C, 0x95, 0x2B, 0x12, 0x18, 0x1C, 0x9C, 0xCC, 0x2B, 0x1C, 0x10, 0x9B, 0xD4, +0x00, 0xB0, 0x8A, 0x95, 0x0F, 0xA2, 0x8A, 0x51, 0x12, 0x5C, 0x1C, 0x58, 0xD5, 0x6B, 0x00, 0xB0, +0x8A, 0x95, 0x75, 0xE1, 0x8A, 0x51, 0x1C, 0x54, 0x9B, 0x90, 0x92, 0x58, 0x9C, 0xDC, 0xDE, 0xAB, +0x9C, 0x50, 0x9B, 0xD4, 0x01, 0xF0, 0x8A, 0x95, 0x0F, 0xA2, 0x8A, 0x51, 0x92, 0x9C, 0x9C, 0x98, +0xE7, 0xAB, 0x01, 0xF0, 0x8A, 0x95, 0x75, 0xE1, 0x8A, 0x51, 0x9C, 0x94, 0x9B, 0x90, 0xD1, 0x41, +0x51, 0x08, 0xDD, 0x80, 0x05, 0x30, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x24, 0x30, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xD2, 0x00, 0x52, 0x08, 0x41, 0xC2, 0x03, 0x18, 0x4D, 0xEC, 0x51, 0x08, 0xA0, 0xFE, +0x84, 0x80, 0x3D, 0x88, 0x83, 0x93, 0x00, 0x42, 0x03, 0x18, 0x4D, 0xEC, 0x51, 0x08, 0xA0, 0xFE, +0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0xEB, 0x80, 0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, 0x6B, 0x88, +0x80, 0x40, 0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, 0x03, 0x14, 0x00, 0xCD, 0xEF, 0xC0, 0x6F, 0xC8, +0xDD, 0x80, 0x06, 0x30, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x06, 0x30, 0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x08, 0xF0, 0xD1, 0x8A, 0x51, 0x02, 0x03, 0x5C, 0xE8, 0x2B, 0x59, 0xCE, +0xF0, 0x39, 0x23, 0x04, 0x9E, 0x40, 0x21, 0x30, 0xD7, 0xC1, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD3, 0x40, 0x20, 0xF0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD0, 0xC0, 0xD5, 0x81, 0x57, 0x88, 0x29, 0x3E, 0x84, 0x80, +0x83, 0x93, 0x80, 0x88, 0x03, 0x59, 0x7E, 0x6C, 0x0D, 0xED, 0x43, 0x08, 0x52, 0x02, 0x03, 0x18, +0x2A, 0xAC, 0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x3F, 0xC2, 0x03, 0x18, +0x2A, 0xAC, 0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xEB, 0x80, 0x51, 0x08, +0xA0, 0xFE, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, 0x03, 0x14, +0x00, 0xCD, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, 0x06, 0x30, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, +0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x06, 0x30, 0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, +0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x2A, 0xAC, 0x57, 0x88, 0x23, 0x3E, +0x84, 0x80, 0x00, 0x48, 0x37, 0xC6, 0x03, 0x9D, 0x87, 0xEC, 0x49, 0x08, 0x88, 0x6C, 0x4B, 0x48, +0xD4, 0x00, 0x53, 0x48, 0x54, 0x02, 0x03, 0x18, 0xC2, 0xAC, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, +0x45, 0x08, 0x00, 0x42, 0x03, 0x18, 0xC2, 0xAC, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x00, 0x48, +0x01, 0xBE, 0xEB, 0x80, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, +0x59, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xEB, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xEB, 0x4D, 0xFF, 0x7E, +0x03, 0xD0, 0x03, 0x9D, 0xA6, 0xEC, 0x6B, 0x0D, 0x02, 0xBE, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, +0x01, 0xF0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x01, 0xF0, 0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0xFA, 0x6C, 0x4D, 0x48, 0x53, 0x42, 0x57, 0x88, 0x03, 0x18, 0x0E, 0xED, 0x59, 0x7E, +0x84, 0x80, 0x00, 0x48, 0x47, 0x42, 0x57, 0x88, 0x03, 0x18, 0x0E, 0xED, 0x59, 0x7E, 0x84, 0x80, +0x00, 0x48, 0xFF, 0x7E, 0xEB, 0x80, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, +0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xEB, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xEB, 0x4D, +0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xDF, 0xAC, 0x6B, 0x0D, 0x02, 0xBE, 0xEF, 0xC0, 0x6F, 0xC8, +0xDD, 0x80, 0x01, 0xF0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x01, 0xF0, 0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x06, 0x30, 0xD5, 0x81, 0xD5, 0xCA, 0xDD, 0x80, 0x57, 0x88, 0x59, 0x7E, +0x84, 0x80, 0x47, 0x48, 0x83, 0x93, 0x00, 0x42, 0x8A, 0x51, 0xBD, 0x21, 0x8A, 0x51, 0xEB, 0x80, +0x57, 0x88, 0x27, 0x7E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0xA8, 0x3E, 0x84, 0x80, +0x80, 0x88, 0x03, 0x9D, 0xEE, 0xAD, 0x50, 0xC8, 0x39, 0x42, 0x03, 0x18, 0x78, 0x2D, 0x57, 0x88, +0x23, 0x3E, 0x84, 0x80, 0x35, 0x48, 0x00, 0x42, 0x03, 0x18, 0x78, 0x2D, 0x57, 0x88, 0x23, 0x3E, +0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0xEB, 0x80, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x6B, 0x88, +0x80, 0x40, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, +0x02, 0xA0, 0x8A, 0x51, 0x0F, 0x39, 0xEB, 0x80, 0x03, 0xD0, 0xEB, 0x4D, 0x57, 0x88, 0x23, 0x3E, +0x84, 0x80, 0x00, 0x48, 0x15, 0x3E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x07, 0xF9, +0xEC, 0x40, 0x04, 0xF0, 0x03, 0xD0, 0xEC, 0x0D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x43, 0xED, +0x6C, 0xCD, 0x6B, 0x84, 0x01, 0x38, 0xED, 0x80, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, +0x80, 0x40, 0x8C, 0x70, 0x83, 0x96, 0xB5, 0x40, 0x83, 0x52, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, +0x00, 0x48, 0x83, 0x96, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x57, 0x88, 0xDE, 0x80, +0x02, 0xF0, 0x8A, 0x95, 0x15, 0x64, 0x8A, 0x51, 0x01, 0xF0, 0xDD, 0xC1, 0xDD, 0x0A, 0xDE, 0x80, +0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x01, 0xF0, 0xDD, 0xC1, +0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xDC, 0x6D, +0x3B, 0x88, 0x50, 0xC2, 0x03, 0x18, 0xEE, 0xAD, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, +0x37, 0x82, 0x03, 0x18, 0xEE, 0xAD, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, +0xEB, 0x80, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0x23, 0x3E, +0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x0F, 0x39, +0xEB, 0x80, 0x03, 0xD0, 0xEB, 0x4D, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x15, 0x3E, +0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x07, 0xF9, 0xEC, 0x40, 0x04, 0xF0, 0x03, 0xD0, +0xEC, 0x0D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xA8, 0xED, 0x6C, 0xCD, 0x6B, 0x84, 0x01, 0x38, +0xED, 0x80, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x8C, 0x70, 0x83, 0x96, +0xB5, 0x40, 0x83, 0x52, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x83, 0x96, 0xB6, 0x40, +0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x57, 0x88, 0xDE, 0x80, 0x02, 0xF0, 0x8A, 0x95, 0x15, 0x64, +0x8A, 0x51, 0x01, 0xF0, 0xDD, 0xC1, 0xDD, 0x0A, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x01, 0xF0, 0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, +0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xD5, 0x81, 0xD5, 0xCA, 0x57, 0x88, 0x23, 0x3E, +0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x0B, 0x3E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, +0xEB, 0x80, 0x57, 0x88, 0x31, 0x3E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x55, 0xCB, 0x19, 0xEE, +0x57, 0x88, 0x27, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xEB, 0x80, 0x57, 0x88, 0x31, 0x3E, 0x84, 0x80, +0x00, 0x48, 0x6B, 0x07, 0xEC, 0x40, 0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, 0x6C, 0x48, 0x80, 0x40, +0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, 0x7F, 0x70, 0x00, 0x42, 0x57, 0x88, 0x03, 0x5C, 0x0D, 0xEE, +0x2F, 0xBE, 0x84, 0x80, 0x7F, 0x70, 0x80, 0x40, 0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, 0x00, 0x48, +0xDD, 0x80, 0x03, 0x30, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x23, 0x70, 0xD7, 0xC1, 0xD7, 0x0A, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xD3, 0x40, 0x22, 0x30, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xD0, 0xC0, 0xD6, 0x81, 0x57, 0x88, 0x29, 0x3E, 0x84, 0x80, 0x83, 0x93, +0x80, 0x88, 0x03, 0x9D, 0xC2, 0xEE, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x37, 0xC6, +0x03, 0x9D, 0x3C, 0x2E, 0x49, 0x08, 0x3D, 0x6E, 0x4B, 0x48, 0xD4, 0x00, 0x53, 0x48, 0x54, 0x02, +0x03, 0x18, 0x77, 0xAE, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x45, 0x08, 0x00, 0x42, 0x03, 0x18, +0x77, 0xAE, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0xEB, 0x80, 0x57, 0x88, +0x59, 0x7E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x00, 0x48, +0xEB, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xEB, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x5B, 0x6E, +0x6B, 0x0D, 0x02, 0xBE, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, 0x02, 0xF0, 0xDE, 0x80, 0x5D, 0x88, +0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x02, 0xF0, 0xDD, 0xC1, 0xDE, 0x80, +0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xAF, 0xAE, 0x4D, 0x48, +0x53, 0x42, 0x57, 0x88, 0x03, 0x18, 0xC3, 0x2E, 0x59, 0x7E, 0x84, 0x80, 0x00, 0x48, 0x47, 0x42, +0x57, 0x88, 0x03, 0x18, 0xC3, 0x2E, 0x59, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xEB, 0x80, +0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, +0x00, 0x48, 0xEB, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xEB, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, +0x94, 0xEE, 0x6B, 0x0D, 0x02, 0xBE, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, 0x02, 0xF0, 0xDE, 0x80, +0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x02, 0xF0, 0xDD, 0xC1, +0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x06, 0x30, +0xD6, 0x81, 0xD6, 0xCA, 0xDD, 0x80, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x47, 0x48, 0x83, 0x93, +0x00, 0x42, 0x8A, 0x51, 0xBD, 0x21, 0x8A, 0x51, 0xEB, 0x80, 0x57, 0x88, 0x27, 0x7E, 0x84, 0x80, +0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0xA8, 0x3E, 0x84, 0x80, 0x80, 0x88, 0x03, 0x9D, 0xA3, 0x6F, +0x50, 0xC8, 0x39, 0x42, 0x03, 0x18, 0x2D, 0x6F, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x35, 0x48, +0x00, 0x42, 0x03, 0x18, 0x2D, 0x6F, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, +0xEB, 0x80, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0x23, 0x3E, +0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x0F, 0x39, +0xEB, 0x80, 0x03, 0xD0, 0xEB, 0x4D, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x15, 0x3E, +0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x07, 0xF9, 0xEC, 0x40, 0x04, 0xF0, 0x03, 0xD0, +0xEC, 0x0D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xF8, 0x6E, 0x6C, 0xCD, 0x6B, 0x84, 0x01, 0x38, +0xED, 0x80, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x8C, 0x70, 0x83, 0x96, +0xB5, 0x40, 0x83, 0x52, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x83, 0x96, 0xB6, 0x40, +0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x57, 0x88, 0xDE, 0x80, 0x02, 0xF0, 0x8A, 0x95, 0x15, 0x64, +0x8A, 0x51, 0x02, 0xF0, 0xDD, 0xC1, 0xDD, 0x0A, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x02, 0xF0, 0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, +0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x91, 0x2F, 0x3B, 0x88, 0x50, 0xC2, 0x03, 0x18, +0xA3, 0x6F, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x37, 0x82, 0x03, 0x18, 0xA3, 0x6F, +0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xEB, 0x80, 0x57, 0x88, 0x23, 0x3E, +0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, +0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x0F, 0x39, 0xEB, 0x80, 0x03, 0xD0, 0xEB, 0x4D, +0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x15, 0x3E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, +0x8A, 0x51, 0x07, 0xF9, 0xEC, 0x40, 0x04, 0xF0, 0x03, 0xD0, 0xEC, 0x0D, 0xFF, 0x7E, 0x03, 0xD0, +0x03, 0x9D, 0x5D, 0xAF, 0x6C, 0xCD, 0x6B, 0x84, 0x01, 0x38, 0xED, 0x80, 0x57, 0x88, 0x21, 0xFE, +0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x8C, 0x70, 0x83, 0x96, 0xB5, 0x40, 0x83, 0x52, 0x57, 0x88, +0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x83, 0x96, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, +0x57, 0x88, 0xDE, 0x80, 0x02, 0xF0, 0x8A, 0x95, 0x15, 0x64, 0x8A, 0x51, 0x02, 0xF0, 0xDD, 0xC1, +0xDD, 0x0A, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x02, 0xF0, 0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0xD6, 0x81, 0xD6, 0xCA, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, +0x0B, 0x3E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xEB, 0x80, 0x57, 0x88, 0x31, 0x3E, +0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x56, 0xCB, 0xC3, 0x2B, 0x57, 0x88, 0x27, 0x7E, 0x84, 0x80, +0x00, 0x48, 0xEB, 0x80, 0x57, 0x88, 0x31, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x6B, 0x07, 0xEC, 0x40, +0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, 0x6C, 0x48, 0x80, 0x40, 0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, +0x7F, 0x70, 0x00, 0x42, 0x57, 0x88, 0x03, 0x5C, 0xC2, 0x2F, 0x2F, 0xBE, 0x84, 0x80, 0x7F, 0x70, +0x80, 0x40, 0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0xDE, 0x80, +0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xC3, 0x2B, 0xEE, 0xC1, +0x83, 0x93, 0x23, 0x70, 0x84, 0x80, 0x5D, 0xF0, 0x8A, 0x51, 0xB5, 0xE1, 0x8A, 0x51, 0xA0, 0x30, +0x84, 0x80, 0xD5, 0xF0, 0x8A, 0x51, 0xB5, 0xE1, 0x83, 0x96, 0x38, 0x70, 0xD5, 0x40, 0x3A, 0xB0, +0xD6, 0x40, 0x3C, 0xB0, 0xD7, 0x80, 0x78, 0xB0, 0xD8, 0x00, 0x7A, 0xF0, 0xD9, 0x40, 0x7C, 0xF0, +0xDA, 0x40, 0x7D, 0x30, 0xDB, 0x80, 0x7F, 0x70, 0xDC, 0x40, 0xB9, 0xF0, 0xDD, 0x80, 0xBA, 0xF0, +0xDE, 0x80, 0xBB, 0x30, 0xDF, 0xC0, 0xFA, 0x30, 0xE0, 0xC0, 0xFB, 0x70, 0xE1, 0x00, 0xFC, 0x30, +0xE2, 0x00, 0xFD, 0x70, 0xE3, 0x40, 0xFF, 0xB0, 0xE4, 0x00, 0x83, 0x01, 0x8A, 0x51, 0x21, 0x6A, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xE4, 0x00, 0x64, 0x9C, 0xDD, 0x69, +0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, +0x3F, 0x30, 0xEF, 0x45, 0x6F, 0xC8, 0xDD, 0x80, 0x00, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, +0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xE4, 0xB0, 0xE2, 0x00, 0x8F, 0x29, 0x90, 0x69, +0xE2, 0xCB, 0x8E, 0xE9, 0x93, 0xE9, 0x00, 0x00, 0x83, 0x52, 0x03, 0x53, 0x64, 0x9C, 0xF2, 0x29, +0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, +0x6F, 0xC8, 0xEF, 0xF9, 0x10, 0x38, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, 0x00, 0xB0, 0xDE, 0x80, +0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x80, 0xF0, 0x83, 0x96, +0xB5, 0x40, 0x03, 0x30, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x64, 0x08, 0xDE, 0x80, +0x02, 0xF0, 0x15, 0x64, 0x8A, 0x95, 0x04, 0xF0, 0xE3, 0x40, 0x1C, 0x70, 0xE2, 0x00, 0xE2, 0xCB, +0xBF, 0xA9, 0xE3, 0x0B, 0xBF, 0xA9, 0x00, 0x00, 0x91, 0x70, 0x83, 0x96, 0x03, 0x53, 0xB5, 0x40, +0x83, 0x52, 0x64, 0x08, 0xAE, 0xBE, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x83, 0x96, 0xB6, 0x40, +0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x64, 0x08, 0xDE, 0x80, 0x02, 0xF0, 0x15, 0x64, 0x8A, 0x95, +0x83, 0x96, 0xAE, 0x1C, 0xAF, 0x18, 0x09, 0x6A, 0x0C, 0x6A, 0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, +0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0xF3, 0x30, 0xEF, 0x45, 0x6F, 0xC8, +0xDD, 0x80, 0x00, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x8C, 0xA9, 0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0xFE, 0xF9, 0x01, 0x38, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, +0x00, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0xAE, 0x29, 0x83, 0x52, 0x1B, 0x92, 0x08, 0x40, 0x83, 0x52, 0x1B, 0xD6, 0x08, 0x40, 0xEA, 0x40, +0x6A, 0x98, 0x1A, 0xAA, 0x25, 0x70, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0x21, 0x6A, 0x2A, 0x70, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xE6, 0x40, 0xE9, 0x40, 0x3F, 0x30, 0xE9, 0xC5, 0x88, 0x30, 0x83, 0x96, 0xB5, 0x40, +0x83, 0x52, 0x69, 0x48, 0x83, 0x96, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x6A, 0x48, +0xDE, 0x80, 0x02, 0xF0, 0x15, 0x64, 0x8A, 0x95, 0x66, 0x48, 0xE3, 0x40, 0x06, 0x30, 0x03, 0xD0, +0xE3, 0xCC, 0xFF, 0x7E, 0x03, 0x9D, 0x37, 0x2A, 0x63, 0x48, 0xE9, 0x40, 0x03, 0x30, 0xE9, 0xC5, +0x6A, 0x98, 0x68, 0xAA, 0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xEF, 0xC0, 0x69, 0x48, 0xE3, 0x40, 0x03, 0xD0, 0xE3, 0x0D, 0x03, 0xD0, 0xE3, 0x0D, +0x6F, 0xC8, 0xF3, 0xB9, 0x63, 0x44, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, 0x00, 0xB0, 0xDE, 0x80, +0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x6A, 0x98, 0x87, 0xEA, +0x26, 0x70, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0x8E, 0xEA, +0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, +0x69, 0x48, 0xE3, 0x40, 0x06, 0x30, 0x03, 0xD0, 0xE3, 0x0D, 0xFF, 0x7E, 0x03, 0x9D, 0x73, 0x2A, +0x6F, 0xC8, 0x3F, 0xB9, 0x63, 0x44, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, 0x00, 0xB0, 0xDE, 0x80, +0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x5E, 0x2A, 0x2B, 0xB0, +0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xE9, 0x40, 0x07, 0x70, +0xE9, 0xC5, 0x06, 0x30, 0x69, 0x42, 0x03, 0x5C, 0xA7, 0x2A, 0x8A, 0x70, 0x83, 0x96, 0xB5, 0x40, +0x6A, 0xB0, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x6A, 0x48, 0xDE, 0x80, 0x02, 0xF0, +0x15, 0x64, 0x8A, 0x95, 0x8B, 0xB0, 0x83, 0x96, 0xB5, 0x40, 0x04, 0xF0, 0xC0, 0x6A, 0x69, 0x48, +0x05, 0xBA, 0x8A, 0x70, 0x03, 0x9D, 0xB1, 0xEA, 0x83, 0x96, 0xB5, 0x40, 0x20, 0xF0, 0xB6, 0x40, +0xB4, 0xEA, 0x83, 0x96, 0xB5, 0x40, 0xB6, 0x81, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x6A, 0x48, +0xDE, 0x80, 0x02, 0xF0, 0x15, 0x64, 0x8A, 0x95, 0x8B, 0xB0, 0x83, 0x96, 0xB5, 0x40, 0x05, 0x30, +0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x6A, 0x48, 0xDE, 0x80, 0x02, 0xF0, 0x15, 0x64, +0x8A, 0x95, 0x6A, 0x98, 0xE3, 0x2A, 0x27, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xE5, 0x40, 0x28, 0x30, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xE9, 0x40, 0x29, 0x70, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xFA, 0x6A, 0x2C, 0x70, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xE5, 0x40, 0x2D, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xE9, 0x40, 0x2E, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xE7, 0x80, 0x69, 0x48, 0xE3, 0x40, 0x07, 0x70, 0x03, 0xD0, 0xE3, 0xCC, +0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xFF, 0xEA, 0x65, 0xCD, 0x63, 0x44, 0xE8, 0x00, 0x84, 0x30, +0x83, 0x96, 0xB5, 0x40, 0x83, 0x52, 0x68, 0x08, 0x83, 0x96, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, +0xDD, 0x80, 0x6A, 0x48, 0xDE, 0x80, 0x02, 0xF0, 0x15, 0x64, 0x8A, 0x95, 0x67, 0x88, 0xE3, 0x40, +0x07, 0x70, 0x03, 0xD0, 0xE3, 0xCC, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x1A, 0xEB, 0x69, 0xCD, +0x63, 0x44, 0xE8, 0x00, 0x85, 0x70, 0x83, 0x96, 0xB5, 0x40, 0x83, 0x52, 0x68, 0x08, 0x83, 0x96, +0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x6A, 0x48, 0xDE, 0x80, 0x02, 0xF0, 0x15, 0x64, +0x8A, 0x95, 0x03, 0xD0, 0x67, 0x0D, 0xE8, 0x00, 0x86, 0x70, 0x83, 0x96, 0xB5, 0x40, 0x83, 0x52, +0x68, 0x08, 0x83, 0x96, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x6A, 0x48, 0xDE, 0x80, +0x02, 0xF0, 0x15, 0x64, 0x8A, 0x95, 0x6A, 0x48, 0x2D, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x88, +0x03, 0x59, 0x4F, 0x6B, 0x6A, 0x48, 0x5B, 0xBE, 0x84, 0x80, 0x03, 0x30, 0x53, 0x2B, 0x6A, 0x48, +0x5B, 0xBE, 0x84, 0x80, 0x01, 0xF0, 0x80, 0x40, 0x80, 0xF0, 0x83, 0x96, 0xB5, 0x40, 0x83, 0x52, +0x6A, 0x48, 0x5B, 0xBE, 0x84, 0x80, 0x00, 0x48, 0x04, 0x38, 0x83, 0x96, 0xB6, 0x40, 0xB5, 0xF0, +0x83, 0x52, 0xDD, 0x80, 0x6A, 0x48, 0xDE, 0x80, 0x02, 0xF0, 0x15, 0x64, 0x8A, 0x95, 0x03, 0x30, +0xE4, 0x00, 0x7D, 0x30, 0xE3, 0x40, 0xE3, 0x0B, 0x6B, 0x6B, 0xE4, 0xCB, 0x6B, 0x6B, 0x83, 0x52, +0x03, 0x53, 0x6A, 0xDC, 0x87, 0x2B, 0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0x12, 0x6F, 0xC8, 0xDD, 0x80, 0x00, 0xB0, 0xDE, 0x80, +0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x9A, 0x2B, 0x00, 0xB0, +0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xD0, +0x6F, 0xC8, 0xDD, 0x80, 0x00, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x05, 0x30, 0xE3, 0x40, 0xE3, 0x0B, 0x9C, 0x2B, 0x83, 0x52, 0x03, 0x53, +0x6A, 0xDC, 0xFC, 0xAB, 0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0xDF, 0xF9, 0x20, 0x38, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, +0x00, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x15, 0x70, 0xE4, 0x00, 0xC6, 0xB0, 0xE3, 0x40, 0xE3, 0x0B, 0xBC, 0x6B, 0xE4, 0xCB, 0xBC, 0x6B, +0x00, 0x00, 0x80, 0xF0, 0x83, 0x96, 0x03, 0x53, 0xB5, 0x40, 0x83, 0x52, 0x6A, 0x48, 0x5B, 0xBE, +0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x0C, 0x78, 0x83, 0x96, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, +0xDD, 0x80, 0x6A, 0x48, 0xDE, 0x80, 0x02, 0xF0, 0x15, 0x64, 0x8A, 0x95, 0x04, 0xF0, 0xE4, 0x00, +0x1C, 0x70, 0xE3, 0x40, 0xE3, 0x0B, 0xDA, 0x6B, 0xE4, 0xCB, 0xDA, 0x6B, 0x00, 0x00, 0x11, 0x30, +0x83, 0x96, 0x03, 0x53, 0xB5, 0x40, 0x55, 0xB0, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, +0x6A, 0x48, 0xDE, 0x80, 0x02, 0xF0, 0x4E, 0xA4, 0x8A, 0x95, 0x6A, 0x48, 0xAE, 0xBE, 0x84, 0x80, +0x83, 0x96, 0x35, 0x48, 0x83, 0x93, 0x80, 0x40, 0x83, 0x52, 0x6A, 0x48, 0xAE, 0xBE, 0x84, 0x80, +0x00, 0xDC, 0x13, 0xAC, 0x9B, 0xD2, 0x08, 0x40, 0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0xFD, 0xF9, 0x02, 0x38, 0xEF, 0xC0, +0x6F, 0xC8, 0xDD, 0x80, 0x00, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0xB8, 0x2B, 0x9B, 0x16, 0x08, 0x40, 0xE0, 0xC0, 0x5E, 0xD8, 0x33, 0xEC, +0xE1, 0x41, 0x60, 0xC8, 0x61, 0x02, 0x03, 0x18, 0x28, 0x6C, 0x61, 0x08, 0x5D, 0x07, 0xDF, 0xC0, +0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x99, 0x00, 0x18, 0x14, 0x18, 0xD0, 0xE1, 0x8A, 0x19, 0xAC, +0x18, 0x55, 0x18, 0x11, 0x83, 0x52, 0x03, 0x53, 0x11, 0x5C, 0x08, 0x40, 0x05, 0x30, 0xDF, 0xC0, +0xDF, 0x8B, 0x30, 0x6C, 0x2A, 0xAC, 0xE1, 0x41, 0x60, 0xC8, 0x61, 0x02, 0x03, 0x18, 0x43, 0xAC, +0x61, 0x08, 0x5D, 0x07, 0xDF, 0xC0, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x9A, 0x00, 0x98, 0x95, +0x98, 0x51, 0xE1, 0x8A, 0x34, 0xAC, 0x98, 0x96, 0x98, 0x52, 0x83, 0x52, 0x03, 0x53, 0x91, 0x9C, +0x08, 0x40, 0x05, 0x30, 0xDF, 0xC0, 0xDF, 0x8B, 0x4B, 0xEC, 0x45, 0xAC, 0xE0, 0xC0, 0x10, 0xF0, +0x60, 0xC2, 0x03, 0x5C, 0x55, 0xEC, 0x10, 0xF0, 0x56, 0xEC, 0x60, 0xC8, 0xE1, 0x00, 0x5E, 0xD8, +0x85, 0xAC, 0xE2, 0x41, 0x61, 0x08, 0x62, 0x02, 0x03, 0x18, 0x69, 0xEC, 0x62, 0x08, 0x5D, 0x07, +0xDF, 0xC0, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x99, 0x00, 0x18, 0x14, 0x18, 0xD0, 0xE2, 0x8A, +0x5A, 0xEC, 0x18, 0x55, 0x18, 0x11, 0x83, 0x52, 0x03, 0x53, 0x11, 0x5C, 0x75, 0x2C, 0x2A, 0x70, +0xDF, 0xC0, 0xDF, 0x8B, 0x71, 0xEC, 0x00, 0x00, 0x6B, 0x2C, 0xE2, 0x41, 0x61, 0x08, 0x62, 0x02, +0x03, 0x18, 0x08, 0x40, 0x62, 0x08, 0x5D, 0x07, 0xDF, 0xC0, 0x84, 0x80, 0x0F, 0x48, 0x83, 0x93, +0x80, 0x40, 0x98, 0x54, 0x98, 0x10, 0xE2, 0x8A, 0x76, 0x2C, 0xE2, 0x41, 0x61, 0x08, 0x62, 0x02, +0x03, 0x18, 0x95, 0xEC, 0x62, 0x08, 0x5D, 0x07, 0xDF, 0xC0, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, +0x9A, 0x00, 0x98, 0x95, 0x98, 0x51, 0xE2, 0x8A, 0x86, 0xAC, 0x98, 0x96, 0x98, 0x52, 0x83, 0x52, +0x03, 0x53, 0x91, 0x9C, 0xA1, 0xAC, 0x2A, 0x70, 0xDF, 0xC0, 0xDF, 0x8B, 0x9D, 0x2C, 0x00, 0x00, +0x97, 0x2C, 0xE2, 0x41, 0x61, 0x08, 0x62, 0x02, 0x03, 0x18, 0x08, 0x40, 0x62, 0x08, 0x5D, 0x07, +0xDF, 0xC0, 0x84, 0x80, 0x10, 0x88, 0x83, 0x93, 0x80, 0x40, 0x18, 0x56, 0x18, 0x12, 0xE2, 0x8A, +0xA2, 0xAC, 0x55, 0xB0, 0x9B, 0x40, 0xD7, 0xC1, 0x02, 0xF0, 0x57, 0x82, 0x03, 0x18, 0xC9, 0x2D, +0x57, 0x88, 0x01, 0xBE, 0x9B, 0x40, 0x57, 0x88, 0x33, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, +0x57, 0x88, 0x33, 0x7E, 0x84, 0x80, 0x80, 0x88, 0x03, 0x9D, 0xEC, 0x2C, 0x3C, 0xB0, 0xDD, 0x80, +0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x57, 0xD8, 0xDC, 0x2C, +0x57, 0x88, 0x33, 0x7E, 0x84, 0x80, 0x6F, 0xC8, 0xDF, 0xC0, 0x07, 0x70, 0x03, 0xD0, 0xDF, 0x4C, +0xFF, 0x7E, 0x03, 0x9D, 0xD6, 0x2C, 0xE7, 0x6C, 0x57, 0x88, 0x33, 0x7E, 0x84, 0x80, 0x6F, 0xC8, +0xDF, 0xC0, 0x05, 0x30, 0x03, 0xD0, 0xDF, 0x4C, 0xFF, 0x7E, 0x03, 0x9D, 0xE2, 0xEC, 0x5F, 0xC8, +0x01, 0x79, 0x83, 0x93, 0x80, 0x40, 0xC0, 0x6C, 0x3D, 0xF0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x03, 0x59, 0xF9, 0x6C, 0x6F, 0x4B, +0x18, 0xAD, 0x57, 0x88, 0x2D, 0x7E, 0x84, 0x80, 0x6F, 0xC8, 0x83, 0x93, 0x80, 0x40, 0x57, 0x88, +0x2D, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xDD, 0x80, 0x1A, 0x70, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, +0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3E, 0xF0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x03, 0x9D, 0x2B, 0x2D, 0x49, 0xED, +0x57, 0x88, 0x2D, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x57, 0x88, 0x2D, 0x7E, 0x84, 0x80, +0x00, 0x48, 0xDD, 0x80, 0x1A, 0x70, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x0C, 0xAD, 0x10, 0xF0, 0x6F, 0xC2, 0x03, 0x18, 0x49, 0xED, 0x57, 0x88, +0x2B, 0x7E, 0x84, 0x80, 0x6F, 0xC8, 0x83, 0x93, 0x80, 0x40, 0x57, 0x88, 0x2B, 0x7E, 0x84, 0x80, +0x00, 0x48, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x57, 0x88, 0x29, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x80, 0xCA, +0x76, 0x6D, 0x6F, 0xC8, 0xFF, 0x3A, 0x03, 0x9D, 0x5E, 0x6D, 0x57, 0x88, 0x2B, 0x7E, 0x84, 0x80, +0x07, 0x70, 0x83, 0x93, 0x80, 0x40, 0xFF, 0xB0, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, +0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x71, 0x2D, 0x57, 0x88, 0x2B, 0x7E, +0x84, 0x80, 0x07, 0x70, 0x83, 0x93, 0x80, 0x40, 0x57, 0x88, 0x2B, 0x7E, 0x84, 0x80, 0x00, 0x48, +0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x57, 0x88, 0x29, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x3F, 0x30, 0xDD, 0x80, +0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x0A, 0x30, 0x6F, 0xC2, +0x03, 0x18, 0x9C, 0x2D, 0x57, 0x88, 0x25, 0x3E, 0x84, 0x80, 0x6F, 0xC8, 0x83, 0x93, 0x80, 0x40, +0x57, 0x88, 0x25, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, 0x5D, 0x88, +0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x57, 0x88, 0xA8, 0x3E, 0x84, 0x80, +0x83, 0x93, 0x80, 0x81, 0x80, 0xCA, 0xC7, 0x6D, 0x6F, 0xC8, 0xFF, 0x3A, 0x03, 0x9D, 0xB0, 0xED, +0x57, 0x88, 0x25, 0x3E, 0x84, 0x80, 0xFF, 0xB0, 0x83, 0x93, 0x80, 0x81, 0xDD, 0x80, 0x1C, 0x70, +0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xC2, 0xED, +0x57, 0x88, 0x25, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x57, 0x88, 0x25, 0x3E, 0x84, 0x80, +0x00, 0x48, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x57, 0x88, 0xA8, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0xD7, 0x0A, +0xB4, 0xEC, 0x03, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x03, 0xBA, 0x03, 0x9D, 0xCB, 0x6D, 0x3E, 0xF0, +0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xB6, 0x40, 0x3F, 0x30, +0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xB8, 0x00, 0x36, 0x42, +0x03, 0x5C, 0x07, 0xEE, 0x0A, 0x30, 0x36, 0x42, 0x03, 0x18, 0x07, 0xEE, 0x36, 0x48, 0xDD, 0x80, +0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x38, 0x08, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x36, 0x48, 0xB5, 0x40, 0x38, 0x08, 0xB7, 0x80, 0x1D, 0x2E, 0x09, 0x30, +0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x1C, 0x70, 0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x09, 0x30, 0xB5, 0x40, 0xB7, 0xC1, 0x04, 0xF0, 0x9B, 0x40, 0x3C, 0xB0, +0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, +0x04, 0x7A, 0x03, 0x9D, 0x1F, 0x6E, 0x3E, 0xF0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xBA, 0x40, 0x3F, 0x30, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xBC, 0x40, 0x3A, 0x42, 0x03, 0x5C, 0x5A, 0x2E, 0x02, 0xF0, 0x3C, 0x42, +0x03, 0x5C, 0x5A, 0x2E, 0x3A, 0x48, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, +0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3C, 0x48, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, +0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3A, 0x48, 0xB9, 0x40, +0x3C, 0x48, 0x71, 0x2E, 0x23, 0x70, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, +0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x10, 0xF0, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, +0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x23, 0x70, 0xB9, 0x40, +0x10, 0xF0, 0xBB, 0x80, 0x05, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x05, 0xBA, 0x03, 0x9D, 0x74, 0x2E, +0x3E, 0xF0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC6, 0x00, +0x3F, 0x30, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC8, 0xC0, +0x46, 0x02, 0x03, 0x5C, 0xB2, 0x2E, 0x10, 0xF0, 0x46, 0x02, 0x03, 0x18, 0xB2, 0x2E, 0x48, 0xC8, +0x03, 0x59, 0xB2, 0x2E, 0x46, 0x08, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, +0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x48, 0xC8, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, +0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x46, 0x08, 0xC5, 0x00, +0x48, 0xC8, 0xC9, 0x2E, 0x0B, 0x70, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, +0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x07, 0x70, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, +0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x0B, 0x70, 0xC5, 0x00, +0x07, 0x70, 0xC7, 0x40, 0x06, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x06, 0xBA, 0x03, 0x9D, 0xCC, 0x2E, +0x3D, 0xF0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xCC, 0x00, +0x3E, 0xF0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xCA, 0x00, +0x3F, 0x30, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xCE, 0x40, +0x4A, 0x08, 0x4C, 0x02, 0x03, 0x5C, 0x1F, 0xAF, 0x4E, 0x48, 0x4A, 0x02, 0x03, 0x5C, 0x1F, 0xAF, +0x4E, 0x48, 0x03, 0x59, 0x1F, 0xAF, 0x4C, 0x08, 0xDD, 0x80, 0x1A, 0x70, 0xDE, 0x80, 0x5D, 0x88, +0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x4A, 0x08, 0xDD, 0x80, 0x1B, 0xB0, +0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x4E, 0x48, +0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x4C, 0x08, 0xCB, 0x40, 0x4A, 0x08, 0xC9, 0x00, 0x4E, 0x48, 0x42, 0xEF, 0x73, 0xF0, +0xDD, 0x80, 0x1A, 0x70, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x64, 0x70, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x2D, 0xB0, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, 0x5D, 0x88, +0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x73, 0xF0, 0xCB, 0x40, 0x64, 0x70, +0xC9, 0x00, 0x2D, 0xB0, 0xCD, 0x40, 0x07, 0x70, 0x9B, 0x40, 0x3C, 0xB0, 0xDD, 0x80, 0x5D, 0x88, +0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x07, 0xFA, 0x03, 0x9D, +0x45, 0x2F, 0x3E, 0xF0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, +0xBE, 0x80, 0x3F, 0x30, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, +0xC0, 0x80, 0x3E, 0x82, 0x03, 0x5C, 0x83, 0x2F, 0x10, 0xF0, 0x3E, 0x82, 0x03, 0x18, 0x83, 0x2F, +0x40, 0x88, 0x03, 0x59, 0x83, 0x2F, 0x3E, 0x88, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, +0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x40, 0x88, 0xDD, 0x80, 0x1C, 0x70, +0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3E, 0x88, +0xBD, 0x80, 0x40, 0x88, 0x9A, 0x6F, 0x0E, 0x70, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, +0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x04, 0xF0, 0xDD, 0x80, 0x1C, 0x70, +0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x0E, 0x70, +0xBD, 0x80, 0x04, 0xF0, 0xBF, 0xC0, 0x08, 0xF0, 0x9B, 0x40, 0x3C, 0xB0, 0xDD, 0x80, 0x5D, 0x88, +0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x08, 0x7A, 0x03, 0x9D, +0x9D, 0xAF, 0x3E, 0xF0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, +0xC2, 0xC0, 0x3F, 0x30, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, +0xC4, 0xC0, 0x42, 0xC8, 0x44, 0xC2, 0x03, 0x18, 0xD9, 0xAF, 0x02, 0xF0, 0x44, 0xC2, 0x03, 0x5C, +0xD9, 0xAF, 0x42, 0xC8, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x44, 0xC8, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, 0x5D, 0x88, +0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x42, 0xC8, 0xC1, 0xC0, 0x44, 0xC8, +0xF0, 0x6F, 0x84, 0x30, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x34, 0x70, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, 0x5D, 0x88, +0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x84, 0x30, 0xC1, 0xC0, 0x34, 0x70, +0xC3, 0x00, 0x09, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x09, 0xBA, 0x03, 0x59, 0x08, 0x40, 0xF3, 0xEF + }; + \ No newline at end of file diff --git a/libloragw/src/arb_fw.var b/libloragw/src/arb_fw.var new file mode 100644 index 0000000..325df3c --- /dev/null +++ b/libloragw/src/arb_fw.var @@ -0,0 +1,515 @@ +static uint8_t arb_firmware[8192] = { +0x8A, 0x51, 0xE9, 0xAF, 0x00, 0xB0, 0x8A, 0xC0, 0x04, 0x88, 0x84, 0x0A, 0x82, 0x47, 0x00, 0xF4, +0x01, 0x34, 0x02, 0x34, 0x04, 0x34, 0x08, 0x34, 0x10, 0x34, 0x20, 0x34, 0x40, 0x34, 0x80, 0x34, +0x01, 0x34, 0x02, 0x34, 0x04, 0x34, 0x08, 0x34, 0x10, 0x34, 0x20, 0x34, 0x40, 0x34, 0x80, 0x34, +0x00, 0xF4, 0x00, 0xF4, 0x00, 0xF4, 0x00, 0xF4, 0x00, 0xF4, 0x01, 0x34, 0x03, 0x74, 0x07, 0xB4, +0x0F, 0xF4, 0x1F, 0x34, 0x3F, 0x74, 0x7F, 0xB4, 0xFF, 0xF4, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0x64, 0xC0, 0x80, 0x81, 0x84, 0x0A, 0x04, 0xC6, +0x03, 0x59, 0x00, 0xF4, 0x04, 0xC6, 0xF5, 0xAB, 0x95, 0x41, 0x96, 0x41, 0x97, 0x81, 0x98, 0x01, +0x99, 0x41, 0x9A, 0x41, 0x9B, 0x81, 0x9C, 0x41, 0x9E, 0x81, 0xDC, 0x81, 0x10, 0xF0, 0x5C, 0x42, +0x03, 0x18, 0x25, 0xAC, 0x5C, 0x48, 0x22, 0xFE, 0x84, 0x80, 0x80, 0x81, 0x5C, 0x48, 0xB0, 0x3E, +0x84, 0x80, 0x80, 0x81, 0x5C, 0x48, 0xD0, 0x3E, 0x84, 0x80, 0x80, 0x81, 0x5C, 0x48, 0xA0, 0xFE, +0x84, 0x80, 0x80, 0x81, 0x5C, 0x48, 0xC0, 0xFE, 0x84, 0x80, 0x80, 0x81, 0x5C, 0x48, 0xE0, 0x3E, +0x84, 0x80, 0xFF, 0xB0, 0x80, 0x40, 0xDC, 0xCA, 0x06, 0x6C, 0xC6, 0x41, 0xC7, 0x81, 0xC9, 0x41, +0xC4, 0x01, 0xC8, 0x01, 0x6C, 0x50, 0xEC, 0x90, 0xA1, 0x01, 0xDC, 0x81, 0x40, 0xF0, 0x5C, 0x42, +0x03, 0x18, 0x3A, 0xEC, 0x5C, 0x48, 0xA0, 0xFE, 0x84, 0x80, 0xFF, 0xB0, 0x83, 0xD7, 0x80, 0x40, +0xDC, 0xCA, 0x2E, 0xEC, 0xC5, 0x41, 0x08, 0xF0, 0x45, 0x02, 0x03, 0x18, 0x08, 0x40, 0x45, 0x08, +0x3A, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x45, 0x08, 0x32, 0x3E, 0x84, 0x80, 0x80, 0x81, +0xC5, 0x8A, 0x3B, 0x2C, 0x01, 0xF0, 0xA0, 0x80, 0x8A, 0x51, 0xFC, 0x63, 0x8A, 0x51, 0x03, 0x30, +0x9E, 0x40, 0x02, 0xF0, 0x9E, 0x40, 0x01, 0xF0, 0x9E, 0x40, 0x9E, 0x81, 0x9B, 0x81, 0xE1, 0x41, +0xE1, 0x8A, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x2A, 0x70, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xDF, 0xC0, 0x5F, 0xC8, 0xF7, 0xFA, 0x03, 0x9D, 0x70, 0xAC, 0xEB, 0xC1, 0x72, 0xEC, +0xEB, 0xC1, 0xEB, 0x0A, 0x6B, 0x88, 0x03, 0x59, 0xE2, 0xEC, 0x01, 0xF0, 0x9B, 0x40, 0x29, 0x70, +0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, +0xDF, 0xC0, 0x5F, 0x4B, 0x77, 0x6C, 0x28, 0x30, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, +0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xCA, 0x00, 0x0D, 0x70, 0x4A, 0x02, 0x03, 0x18, +0xD6, 0x2C, 0x05, 0x30, 0x4A, 0x02, 0xD5, 0x81, 0x03, 0x5C, 0xD7, 0x6C, 0xD5, 0xCA, 0x4A, 0x08, +0xE1, 0x00, 0x01, 0xF0, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x2A, 0x70, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xD6, 0x40, 0x10, 0xF0, 0x56, 0x42, 0x03, 0x5C, 0xB1, 0xEC, 0x10, 0xF0, +0xD6, 0x40, 0xF0, 0xB0, 0xD6, 0x0E, 0xD6, 0xC5, 0x56, 0x48, 0xE1, 0x00, 0x03, 0x30, 0xE2, 0x00, +0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x2B, 0xB0, 0x83, 0x52, +0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xE0, 0xC0, +0x01, 0xF0, 0xE0, 0x45, 0x60, 0xC8, 0xE1, 0x00, 0x04, 0xF0, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, +0x62, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x9B, 0x81, 0xEB, 0x6C, 0xD5, 0x81, 0xFF, 0xB0, +0xE1, 0x00, 0x01, 0xF0, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0xA1, 0xAC, 0x07, 0x70, 0xD5, 0x81, 0xD5, 0xCA, 0xCA, 0x00, 0x30, 0x30, 0xD6, 0x40, +0x01, 0xF0, 0xE0, 0x01, 0x9B, 0x40, 0x0E, 0x58, 0xEB, 0x6C, 0x25, 0x70, 0x83, 0x52, 0x03, 0x53, +0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD7, 0x80, 0x07, 0x70, +0xD7, 0x05, 0x57, 0x88, 0xC5, 0x00, 0x08, 0xF0, 0x45, 0x02, 0x03, 0x18, 0xEB, 0x6C, 0x45, 0x08, +0x8A, 0x51, 0x07, 0xA5, 0x8A, 0x51, 0x0E, 0x58, 0xEB, 0x6C, 0xC5, 0x8A, 0xFB, 0xAC, 0xEA, 0x40, +0xE1, 0x00, 0x00, 0xB0, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x8E, 0xDC, 0x08, 0x40, 0x18, 0x14, 0x05, 0x30, 0xE8, 0x00, 0xE8, 0xCB, 0x16, 0xED, +0x21, 0x30, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xC6, 0x00, 0x22, 0x30, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC7, 0x40, 0x24, 0x30, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, +0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC9, 0x00, 0x20, 0xF0, 0x83, 0x52, +0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC4, 0xC0, +0x23, 0x70, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xC8, 0xC0, 0x18, 0xD0, 0x55, 0xCB, 0x56, 0x2D, 0x48, 0xC8, 0xE8, 0x00, 0xE9, 0x81, +0xE8, 0x1B, 0xE9, 0xC3, 0x4A, 0x46, 0x69, 0x44, 0x03, 0x59, 0x60, 0xAD, 0x0A, 0x30, 0x49, 0x02, +0x03, 0x5C, 0x8D, 0x2D, 0x44, 0xC8, 0x80, 0x7A, 0x78, 0x7E, 0x03, 0x5C, 0x81, 0xAD, 0x8D, 0x2D, +0x44, 0xC8, 0x80, 0x7A, 0x78, 0x7E, 0x03, 0x5C, 0xC4, 0xDB, 0x56, 0x2D, 0x44, 0xC8, 0x3A, 0x7E, +0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x01, 0xBE, 0xE8, 0x00, 0x44, 0xC8, 0x3A, 0x7E, 0x84, 0x80, +0x68, 0x08, 0x80, 0x40, 0x44, 0xC8, 0x3A, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xE1, 0x00, 0x44, 0xC8, +0x01, 0xBE, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x56, 0x2D, 0xC4, 0xDB, 0x8D, 0x2D, 0x48, 0xC8, 0x80, 0x7A, 0x73, 0xBE, 0x03, 0x18, 0x8D, 0x2D, +0x48, 0xC8, 0x80, 0x7A, 0x7B, 0xFE, 0x03, 0x18, 0x8F, 0x6D, 0x6C, 0xD5, 0x30, 0xAE, 0x6C, 0x91, +0x48, 0xC8, 0x11, 0xFE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xC7, 0xC5, 0x48, 0xC8, +0xE8, 0x00, 0x03, 0xD0, 0xE8, 0xCD, 0x03, 0xD0, 0xE8, 0xCD, 0x03, 0xD0, 0xE8, 0xCD, 0x44, 0xC8, +0x68, 0x87, 0xD8, 0x7E, 0xDE, 0x80, 0xA0, 0xFE, 0x84, 0x80, 0x83, 0xD7, 0x00, 0x48, 0xDC, 0x40, +0x3E, 0xF0, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xC2, 0xC0, 0x3F, 0x30, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC3, 0x00, 0x6C, 0x91, 0xDC, 0x81, 0x08, 0xF0, 0x5C, 0x42, +0x03, 0x18, 0xF5, 0xAD, 0x5C, 0x48, 0xE0, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x5E, 0xC6, +0x03, 0x9D, 0xF1, 0x6D, 0x5C, 0x48, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, +0x42, 0x05, 0x03, 0x59, 0xF1, 0x6D, 0x46, 0x08, 0xE1, 0x00, 0x5C, 0x48, 0xB0, 0x3E, 0x84, 0x80, +0x00, 0x48, 0xE2, 0x00, 0x5C, 0x48, 0x22, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xE3, 0x40, 0x56, 0x48, +0xE4, 0x00, 0x48, 0xC8, 0xE5, 0x40, 0x47, 0x48, 0x52, 0xE7, 0x8A, 0x51, 0xE8, 0x00, 0x68, 0x4C, +0x03, 0x5C, 0xEE, 0xAD, 0x83, 0x52, 0x03, 0x53, 0x6C, 0xD5, 0xF1, 0x6D, 0x83, 0x52, 0x03, 0x53, +0x6C, 0x91, 0x6C, 0xD9, 0xF5, 0xAD, 0xDC, 0xCA, 0xBE, 0xAD, 0x6C, 0xD9, 0x30, 0xAE, 0x08, 0xF0, +0xDC, 0x40, 0x10, 0xF0, 0x5C, 0x42, 0x03, 0x18, 0x30, 0xAE, 0x5C, 0x48, 0xE0, 0x3E, 0x84, 0x80, +0x83, 0x93, 0x00, 0x48, 0x5E, 0xC6, 0x03, 0x9D, 0x2C, 0xEE, 0x5C, 0x48, 0x01, 0xBE, 0x84, 0x80, +0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x43, 0x45, 0x03, 0x59, 0x2C, 0xEE, 0x46, 0x08, 0xE1, 0x00, +0x5C, 0x48, 0xB0, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xE2, 0x00, 0x5C, 0x48, 0x22, 0xFE, 0x84, 0x80, +0x00, 0x48, 0xE3, 0x40, 0x56, 0x48, 0xE4, 0x00, 0x48, 0xC8, 0xE5, 0x40, 0x47, 0x48, 0x52, 0xE7, +0x8A, 0x51, 0xE8, 0x00, 0x68, 0x4C, 0x03, 0x5C, 0x29, 0xEE, 0x83, 0x52, 0x03, 0x53, 0x6C, 0xD5, +0x2C, 0xEE, 0x83, 0x52, 0x03, 0x53, 0x6C, 0x91, 0x6C, 0xD9, 0x30, 0xAE, 0xDC, 0xCA, 0xF9, 0xAD, +0x6C, 0xD9, 0x08, 0x40, 0x48, 0xC8, 0x80, 0x7A, 0x77, 0xFE, 0x03, 0x5C, 0x3B, 0x6E, 0x60, 0xC8, +0x01, 0xBE, 0xDD, 0x80, 0x3D, 0x6E, 0xDD, 0xC1, 0xDD, 0x0A, 0xDB, 0xC1, 0x5D, 0x88, 0x5B, 0x82, +0x03, 0x18, 0x08, 0x40, 0x11, 0x30, 0xDC, 0x40, 0x3C, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, +0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD8, 0x00, 0x3D, 0xF0, 0x83, 0x52, +0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD9, 0x40, +0x48, 0xC8, 0x80, 0x7A, 0x75, 0xBE, 0x03, 0x5C, 0x65, 0x2E, 0x58, 0x08, 0x80, 0x7A, 0x70, 0x3E, +0x03, 0x5C, 0xD8, 0x1B, 0x75, 0x6E, 0x58, 0x08, 0x6C, 0x2E, 0x59, 0x48, 0x80, 0x7A, 0x70, 0x3E, +0x03, 0x5C, 0xD9, 0x5B, 0x6E, 0x6E, 0x59, 0x48, 0xDC, 0x40, 0x75, 0x6E, 0x58, 0x08, 0x80, 0x7A, +0x70, 0x3E, 0x03, 0x18, 0x75, 0x6E, 0xD8, 0x5F, 0x63, 0x2E, 0x10, 0xF0, 0x5C, 0x42, 0x03, 0x18, +0x50, 0xEF, 0x5C, 0x48, 0x22, 0xFE, 0x84, 0x80, 0x46, 0x08, 0x83, 0x93, 0x80, 0x40, 0x5C, 0x48, +0xB0, 0x3E, 0x84, 0x80, 0x47, 0x48, 0x80, 0x40, 0x5C, 0x48, 0xD0, 0x3E, 0x84, 0x80, 0x49, 0x08, +0x80, 0x40, 0x5C, 0x48, 0xA0, 0xFE, 0x84, 0x80, 0x44, 0xC8, 0x80, 0x40, 0x5C, 0x48, 0xC0, 0xFE, +0x84, 0x80, 0x48, 0xC8, 0x80, 0x40, 0x5C, 0x48, 0xE0, 0x3E, 0x84, 0x80, 0x5E, 0x88, 0x80, 0x40, +0x5E, 0x88, 0xA0, 0xFE, 0x84, 0x80, 0x5C, 0x48, 0x83, 0xD7, 0x80, 0x40, 0x48, 0xC8, 0xE1, 0x00, +0x17, 0xB0, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x44, 0xC8, 0xE1, 0x00, 0x18, 0x30, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x46, 0x08, 0xE1, 0x00, 0x19, 0x70, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, +0x62, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x47, 0x48, 0xE1, 0x00, 0x1A, 0x70, 0xE2, 0x00, +0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x5C, 0x48, 0xE1, 0x00, +0x16, 0x70, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x49, 0x08, 0xE1, 0x00, 0x1B, 0xB0, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x5B, 0x88, 0xE1, 0x00, 0x11, 0x30, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, +0x62, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x08, 0xF0, 0x5C, 0x42, 0x5C, 0x48, 0x03, 0x18, +0x12, 0xEF, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xE1, 0x00, 0x12, 0x30, +0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x05, 0x30, +0xE8, 0x00, 0xE8, 0xCB, 0xF9, 0xAE, 0x83, 0x52, 0x12, 0x30, 0x03, 0x53, 0xE1, 0x41, 0xE2, 0x00, +0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x55, 0xCB, 0x50, 0xEF, +0x48, 0xC8, 0xE8, 0x00, 0xE9, 0x81, 0xE8, 0x1B, 0xE9, 0xC3, 0x4A, 0x46, 0x69, 0x44, 0x03, 0x59, +0x30, 0xEF, 0x50, 0xEF, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xE1, 0x00, +0x13, 0x70, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x05, 0x30, 0xE8, 0x00, 0xE8, 0xCB, 0x22, 0xEF, 0x83, 0x52, 0x13, 0x70, 0x03, 0x53, 0xE1, 0x41, +0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x06, 0xEF, +0x44, 0xC8, 0x80, 0x7A, 0x78, 0x7E, 0x03, 0x5C, 0xC4, 0xDB, 0x50, 0xEF, 0x44, 0xC8, 0x32, 0x3E, +0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x01, 0xBE, 0xE8, 0x00, 0x44, 0xC8, 0x32, 0x3E, 0x84, 0x80, +0x68, 0x08, 0x80, 0x40, 0x44, 0xC8, 0x32, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xE1, 0x00, 0x44, 0xC8, +0x09, 0xFE, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0xDB, 0x0A, 0x3E, 0x6E, 0xE7, 0x80, 0x62, 0x02, 0x62, 0x08, 0x03, 0x18, 0x9D, 0xAF, 0x67, 0x82, +0xD0, 0xC0, 0x63, 0x48, 0x61, 0x02, 0x03, 0x5C, 0x61, 0x2F, 0x63, 0x48, 0x61, 0x02, 0xCC, 0x00, +0x68, 0x2F, 0x61, 0x08, 0x63, 0x42, 0xCC, 0x00, 0xFF, 0xB0, 0xD0, 0x87, 0xCC, 0x86, 0xCC, 0x8A, +0x65, 0x48, 0x11, 0xFE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xE6, 0x40, 0x67, 0x88, +0x66, 0x42, 0xD2, 0x00, 0x61, 0x49, 0xCE, 0x40, 0x62, 0x08, 0x52, 0x87, 0xD1, 0x00, 0x4E, 0x89, +0xE6, 0x40, 0x63, 0x48, 0x66, 0x42, 0x03, 0x5C, 0x80, 0xAF, 0x63, 0x48, 0x4E, 0xC7, 0x84, 0xEF, +0xD1, 0x8A, 0x4E, 0x89, 0xCD, 0x40, 0x63, 0x42, 0xCD, 0x40, 0x50, 0xC8, 0x51, 0x02, 0x03, 0x5C, +0xD3, 0xAF, 0x51, 0x08, 0x50, 0xC2, 0x03, 0x5C, 0x91, 0x2F, 0x4D, 0x48, 0x4C, 0x02, 0x03, 0x18, +0xD3, 0xAF, 0xDA, 0x81, 0x5A, 0x48, 0x03, 0x59, 0x99, 0x6F, 0x51, 0x08, 0xCF, 0x80, 0x4D, 0x48, +0xE0, 0x2F, 0x50, 0xC8, 0xCF, 0x80, 0x4C, 0x08, 0xE0, 0x2F, 0x67, 0x82, 0x03, 0x18, 0xD6, 0xAF, +0x67, 0x88, 0x62, 0x02, 0xD0, 0xC0, 0x61, 0x08, 0x63, 0x42, 0x03, 0x5C, 0xAB, 0xAF, 0x61, 0x08, +0x63, 0x42, 0xCC, 0x00, 0xB2, 0x6F, 0x63, 0x48, 0x61, 0x02, 0xCC, 0x00, 0xFF, 0xB0, 0xD0, 0x87, +0xCC, 0x86, 0xCC, 0x8A, 0x65, 0x48, 0x11, 0xFE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, +0xE6, 0x40, 0x62, 0x08, 0x66, 0x42, 0xD2, 0x00, 0x63, 0x89, 0xCE, 0x40, 0x67, 0x88, 0x52, 0x87, +0xD1, 0x00, 0x4E, 0x89, 0xE6, 0x40, 0x61, 0x08, 0x66, 0x42, 0x03, 0x5C, 0xCA, 0x6F, 0x61, 0x08, +0x4E, 0xC7, 0xCE, 0xAF, 0xD1, 0x8A, 0x4E, 0x89, 0xCD, 0x40, 0x61, 0x02, 0xCD, 0x40, 0x50, 0xC8, +0x51, 0x02, 0x03, 0x18, 0x89, 0x2F, 0xDA, 0x81, 0xDA, 0xCA, 0x92, 0x2F, 0xCF, 0xC1, 0x63, 0x48, +0x61, 0x02, 0x03, 0x5C, 0xDE, 0xEF, 0x63, 0x48, 0x61, 0x02, 0xE0, 0x2F, 0x61, 0x08, 0x63, 0x42, +0xCB, 0x40, 0xCF, 0xC8, 0x03, 0x9D, 0x00, 0xF4, 0x64, 0x08, 0x4B, 0x42, 0x03, 0x5C, 0x01, 0x34, +0x00, 0xF4, 0xEC, 0x81, 0x83, 0x93, 0x22, 0x30, 0x84, 0x80, 0x61, 0x70, 0x8A, 0x51, 0xF4, 0x23, +0x8A, 0x51, 0xA0, 0x30, 0x84, 0x80, 0xF0, 0xB0, 0x8A, 0x51, 0xF4, 0x23, 0x8A, 0x51, 0x83, 0xD7, +0xA0, 0x30, 0x84, 0x80, 0xE0, 0x70, 0x8A, 0x51, 0xF4, 0x23, 0x83, 0x01, 0x8A, 0x51, 0x4A, 0xAC, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF + }; + \ No newline at end of file diff --git a/libloragw/src/cal_fw.var b/libloragw/src/cal_fw.var new file mode 100644 index 0000000..341b314 --- /dev/null +++ b/libloragw/src/cal_fw.var @@ -0,0 +1,515 @@ +static uint8_t cal_firmware_sx125x[8192] = { +0x8A, 0x51, 0xF0, 0x6F, 0x00, 0xB0, 0x8A, 0xC0, 0x04, 0x88, 0x84, 0x0A, 0x82, 0x47, 0x00, 0xF4, +0x40, 0x34, 0x2B, 0xF4, 0x1C, 0xB4, 0x13, 0xB4, 0x0D, 0xB4, 0x08, 0x34, 0x06, 0x74, 0x04, 0x34, +0x02, 0x34, 0x10, 0x34, 0x0B, 0xB4, 0x07, 0xB4, 0x05, 0x74, 0x03, 0x74, 0x02, 0x34, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0x64, 0xC0, +0x80, 0x81, 0x84, 0x0A, 0x04, 0xC6, 0x03, 0x59, 0x00, 0xF4, 0x04, 0xC6, 0x30, 0x69, 0xB1, 0x00, +0x04, 0xF0, 0xA2, 0xC0, 0x10, 0xF0, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x2A, 0x08, 0xA2, 0xC0, 0x14, 0x30, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, +0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x2B, 0x48, 0xA2, 0xC0, 0x15, 0x70, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x2C, 0x08, 0xA2, 0xC0, +0x11, 0x30, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x2D, 0x48, 0xA2, 0xC0, 0x12, 0x30, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x29, 0x08, 0xA2, 0xC0, 0x13, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, +0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x31, 0x58, 0xB5, 0x29, 0x2E, 0x48, 0xB0, 0xC0, +0x01, 0xF0, 0xD7, 0xA7, 0x8A, 0x51, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC3, 0x39, 0x30, 0xC4, 0xA2, 0xC0, 0x01, 0xF0, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x01, 0xF0, 0x83, 0x52, +0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD3, 0x67, +0x8A, 0x51, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x01, 0xF0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xA2, 0xC0, 0x01, 0xF0, 0xA2, 0x10, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xB4, 0x41, 0x1B, 0xEA, 0x2E, 0x48, 0xB0, 0xC0, 0x02, 0xF0, +0xD7, 0xA7, 0x8A, 0x51, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xC3, 0x39, 0x30, 0xC4, 0xA2, 0xC0, 0x02, 0xF0, 0xA3, 0x00, 0x22, 0xC8, +0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x02, 0xF0, 0x83, 0x52, 0x03, 0x53, +0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xCF, 0xA7, 0x8A, 0x51, +0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x02, 0xF0, +0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, +0xA2, 0xC0, 0x02, 0xF0, 0xA2, 0x10, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0xB3, 0x29, 0x2F, 0x88, 0xA4, 0xC0, 0x32, 0x70, 0x2D, 0x27, 0x8A, 0x51, +0x32, 0x08, 0xA2, 0xC0, 0x1B, 0xB0, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x33, 0x48, 0xA2, 0xC0, 0x1C, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, +0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x07, 0x70, 0x9B, 0x40, 0x3C, 0xB0, 0x83, 0x52, +0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xB4, 0x00, +0x06, 0xBA, 0x03, 0x59, 0x0E, 0xAA, 0x34, 0x08, 0x07, 0xFA, 0x03, 0x59, 0x2F, 0x2A, 0x06, 0x30, +0x9B, 0x40, 0x3C, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xB4, 0x00, 0x06, 0xBA, 0x03, 0x59, 0xF3, 0x69, 0x21, 0x6A, 0x10, 0xF0, +0xA2, 0x01, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x08, 0x40, 0xB3, 0x40, 0x04, 0xF0, 0xA2, 0xC0, 0x10, 0xF0, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, +0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x2D, 0x48, 0xA2, 0xC0, 0x14, 0x30, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x2E, 0x48, 0xA2, 0xC0, +0x15, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x2C, 0x08, 0xA2, 0xC0, 0x13, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x30, 0xC8, 0x20, 0x38, 0xD5, 0x40, 0xA2, 0xC0, 0x18, 0x30, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x01, 0xF0, 0xB9, 0x81, +0xC2, 0x01, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xFF, 0x3A, 0x01, 0xBE, 0xBA, 0x40, +0x01, 0xF0, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xFF, 0x3A, 0x01, 0xBE, 0xC3, 0x00, +0x01, 0xF0, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xFF, 0x3A, 0x01, 0xBE, 0xBB, 0x80, +0x01, 0xF0, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xC4, 0xC0, 0x01, 0xF0, 0x84, 0x80, +0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xBC, 0x40, 0x01, 0xF0, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, +0x8A, 0x51, 0xFF, 0x3A, 0x01, 0xBE, 0xC5, 0x00, 0x01, 0xF0, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, +0x8A, 0x51, 0xBD, 0x80, 0x01, 0xF0, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xC6, 0x00, +0x0B, 0x70, 0xD7, 0x80, 0x57, 0x88, 0xB4, 0x00, 0x33, 0x98, 0x06, 0xAB, 0x57, 0x88, 0xB1, 0x00, +0x01, 0xF0, 0xE1, 0x27, 0x8A, 0x51, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC3, 0x39, 0x31, 0x04, 0xA2, 0xC0, 0x01, 0xF0, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x01, 0xF0, 0x83, 0x52, +0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD3, 0x67, +0x8A, 0x51, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x01, 0xF0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xA2, 0xC0, 0x01, 0xF0, 0xA2, 0x10, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x39, 0x48, 0xA2, 0xC0, 0x11, 0x30, 0xA3, 0x00, 0x22, 0xC8, +0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x42, 0xC8, 0xA2, 0xC0, 0x12, 0x30, +0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x30, 0xC8, +0xA4, 0xC0, 0x4B, 0xB0, 0x2D, 0x27, 0x8A, 0x51, 0xD6, 0x81, 0x69, 0x2B, 0x57, 0x88, 0xB1, 0x00, +0x02, 0xF0, 0xE1, 0x27, 0x8A, 0x51, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC3, 0x39, 0x31, 0x04, 0xA2, 0xC0, 0x02, 0xF0, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x02, 0xF0, 0x83, 0x52, +0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xCF, 0xA7, +0x8A, 0x51, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x02, 0xF0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xA2, 0xC0, 0x02, 0xF0, 0xA2, 0x10, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xEB, 0x6A, 0x56, 0x48, 0xCA, 0x27, 0x8A, 0x51, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xB1, 0x27, 0x8A, 0x51, +0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x30, 0xC8, +0xA4, 0xC0, 0x51, 0x70, 0x2D, 0x27, 0x8A, 0x51, 0x4B, 0xB0, 0xA1, 0xC0, 0x51, 0x70, 0x79, 0x67, +0x8A, 0x51, 0xCD, 0x40, 0x4D, 0x48, 0x03, 0x59, 0x69, 0x2B, 0x51, 0x08, 0xCB, 0x40, 0x52, 0x08, +0xCC, 0x00, 0x05, 0x30, 0xD6, 0xCA, 0x56, 0x42, 0x03, 0x5C, 0x44, 0xAB, 0x2F, 0x88, 0xD1, 0x00, +0x51, 0x70, 0xD2, 0x41, 0xA1, 0xC0, 0x4B, 0xB0, 0x79, 0x67, 0x8A, 0x51, 0xCD, 0x40, 0x4D, 0x48, +0x03, 0x9D, 0x7F, 0xEB, 0x07, 0x70, 0xD7, 0x03, 0x57, 0x82, 0x03, 0x18, 0xAA, 0xEA, 0x4B, 0x48, +0xB7, 0x80, 0x4C, 0x08, 0xB8, 0x00, 0xD3, 0x81, 0xD4, 0x41, 0xD7, 0xC1, 0x53, 0x48, 0xB9, 0x40, +0x54, 0x08, 0xC2, 0xC0, 0x57, 0x88, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, +0xA1, 0xC0, 0x53, 0x48, 0xDD, 0x66, 0x83, 0x52, 0x03, 0x53, 0xBA, 0x40, 0x57, 0x88, 0x01, 0xBE, +0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xA1, 0xC0, 0x54, 0x08, 0xDD, 0x66, 0x83, 0x52, +0x03, 0x53, 0xC3, 0x00, 0x57, 0x88, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, +0xA1, 0xC0, 0x53, 0x48, 0xDD, 0x66, 0x83, 0x52, 0x03, 0x53, 0xBB, 0x80, 0x57, 0x88, 0x01, 0xBE, +0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xEB, 0xA7, 0x8A, 0x51, 0xDD, 0x66, 0x83, 0x52, +0x03, 0x53, 0xC4, 0xC0, 0x57, 0x88, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, +0xDC, 0x67, 0x8A, 0x51, 0xDD, 0x66, 0x83, 0x52, 0x03, 0x53, 0xBC, 0x40, 0x57, 0x88, 0x01, 0xBE, +0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xA1, 0xC0, 0x54, 0x08, 0xDD, 0x66, 0x83, 0x52, +0x03, 0x53, 0xC5, 0x00, 0x57, 0x88, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, +0xDC, 0x67, 0x8A, 0x51, 0xDD, 0x66, 0x83, 0x52, 0x03, 0x53, 0xBD, 0x80, 0x57, 0x88, 0x01, 0xBE, +0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xEB, 0xA7, 0x8A, 0x51, 0xDD, 0x66, 0x8A, 0x51, +0x83, 0x52, 0x03, 0x53, 0xC6, 0x00, 0x39, 0x48, 0xA2, 0xC0, 0x11, 0x30, 0xA3, 0x00, 0x22, 0xC8, +0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x42, 0xC8, 0xA2, 0xC0, 0x12, 0x30, +0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x30, 0xC8, +0xA4, 0xC0, 0x4F, 0xF0, 0x2D, 0x27, 0x8A, 0x51, 0x05, 0x30, 0xCE, 0x81, 0xA1, 0xC0, 0x57, 0x88, +0x96, 0x27, 0x8A, 0x51, 0xA0, 0xFE, 0x84, 0x80, 0x4F, 0x88, 0xE6, 0x67, 0x8A, 0x51, 0x96, 0x27, +0x8A, 0x51, 0xA0, 0xFE, 0x84, 0x80, 0x50, 0xC8, 0x83, 0xD7, 0x80, 0x40, 0xD6, 0x81, 0x05, 0x30, +0xD6, 0xCA, 0x56, 0x42, 0x03, 0x18, 0x5A, 0xEC, 0x56, 0x48, 0x39, 0x7E, 0x84, 0x80, 0x83, 0x93, +0x00, 0x48, 0xA2, 0xC0, 0x11, 0x30, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0xB1, 0x27, 0x8A, 0x51, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x30, 0xC8, 0xA4, 0xC0, 0x51, 0x70, 0x2D, 0x27, 0x8A, 0x51, +0x51, 0x70, 0xA1, 0xC0, 0x4F, 0xF0, 0x79, 0x67, 0x8A, 0x51, 0xCD, 0x40, 0x4D, 0x48, 0x03, 0x59, +0x44, 0x6C, 0x51, 0x08, 0xBE, 0xA7, 0x8A, 0x51, 0x05, 0x30, 0xA1, 0xC0, 0x57, 0x88, 0x96, 0x27, +0x8A, 0x51, 0xA0, 0xFE, 0x56, 0xC7, 0xB1, 0x00, 0x84, 0x80, 0x51, 0x08, 0xE6, 0x67, 0x8A, 0x51, +0x96, 0x27, 0x8A, 0x51, 0xA0, 0xFE, 0x56, 0xC7, 0xB1, 0x00, 0x84, 0x80, 0x52, 0x08, 0x83, 0xD7, +0x80, 0x40, 0x17, 0xEC, 0x57, 0x88, 0xE0, 0x3E, 0x84, 0x80, 0x4E, 0x48, 0x83, 0x93, 0x80, 0x40, +0x4E, 0x48, 0x39, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xD3, 0x40, 0xC4, 0xE7, 0x8A, 0x51, 0x09, 0x30, +0xD7, 0x0A, 0x57, 0x82, 0x03, 0x5C, 0x86, 0xEB, 0xA1, 0x01, 0xA1, 0x43, 0x53, 0x48, 0xDD, 0x66, +0x8A, 0x51, 0x83, 0x52, 0x03, 0x53, 0xB9, 0x40, 0xA1, 0x01, 0xA1, 0x43, 0x54, 0x08, 0xDD, 0x66, +0x8A, 0x51, 0x83, 0x52, 0x03, 0x53, 0xC2, 0xC0, 0xA1, 0x01, 0xA1, 0x43, 0x53, 0x48, 0xDD, 0x66, +0x8A, 0x51, 0x83, 0x52, 0x03, 0x53, 0xBA, 0x40, 0x54, 0x08, 0xC3, 0x00, 0xA1, 0x01, 0xA1, 0x43, +0x53, 0x48, 0xDD, 0x66, 0x8A, 0x51, 0x83, 0x52, 0x03, 0x53, 0xBB, 0x80, 0xA1, 0x01, 0xA1, 0x4A, +0x54, 0x08, 0xDD, 0x66, 0x8A, 0x51, 0x83, 0x52, 0x03, 0x53, 0xC4, 0xC0, 0x53, 0x48, 0xBC, 0x40, +0xA1, 0x01, 0xA1, 0x43, 0x54, 0x08, 0xDD, 0x66, 0x8A, 0x51, 0x83, 0x52, 0x03, 0x53, 0xC5, 0x00, +0x53, 0x48, 0xBD, 0x80, 0x54, 0x08, 0xC6, 0x00, 0x53, 0x48, 0xBE, 0x80, 0xA1, 0x01, 0xA1, 0x4A, +0x54, 0x08, 0xDD, 0x66, 0x8A, 0x51, 0x83, 0x52, 0x03, 0x53, 0xC7, 0x40, 0xA1, 0x01, 0xA1, 0x4A, +0x53, 0x48, 0xDD, 0x66, 0x8A, 0x51, 0x83, 0x52, 0x03, 0x53, 0xBF, 0xC0, 0xA1, 0x01, 0xA1, 0x43, +0x54, 0x08, 0xDD, 0x66, 0x8A, 0x51, 0x83, 0x52, 0x03, 0x53, 0xC8, 0xC0, 0xA1, 0x01, 0xA1, 0x4A, +0x53, 0x48, 0xDD, 0x66, 0x8A, 0x51, 0x83, 0x52, 0x03, 0x53, 0xC0, 0x80, 0x54, 0x08, 0xC9, 0x00, +0xA1, 0x01, 0xA1, 0x4A, 0x53, 0x48, 0xDD, 0x66, 0x8A, 0x51, 0x83, 0x52, 0x03, 0x53, 0xC1, 0xC0, +0xA1, 0x01, 0xA1, 0x4A, 0x54, 0x08, 0xDD, 0x66, 0x8A, 0x51, 0x83, 0x52, 0x03, 0x53, 0xCA, 0x00, +0xCE, 0x81, 0x39, 0x48, 0xA2, 0xC0, 0x11, 0x30, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x42, 0xC8, 0xA2, 0xC0, 0x12, 0x30, 0xA3, 0x00, 0x22, 0xC8, +0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x30, 0xC8, 0xA4, 0xC0, 0x4F, 0xF0, +0x2D, 0x27, 0x8A, 0x51, 0xD6, 0x81, 0x09, 0x30, 0xD6, 0xCA, 0x56, 0x42, 0x03, 0x18, 0x1D, 0x2D, +0x56, 0x48, 0xCA, 0x27, 0x8A, 0x51, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0xB1, 0x27, 0x8A, 0x51, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x30, 0xC8, 0xA4, 0xC0, 0x51, 0x70, 0x2D, 0x27, 0x8A, 0x51, +0x51, 0x70, 0xA1, 0xC0, 0x4F, 0xF0, 0x79, 0x67, 0x8A, 0x51, 0xCD, 0x40, 0x4D, 0x48, 0x03, 0x59, +0xF3, 0x6C, 0x51, 0x08, 0xBE, 0xA7, 0x8A, 0x51, 0xF3, 0x6C, 0x4E, 0x48, 0x39, 0x7E, 0x84, 0x80, +0x00, 0x48, 0xD3, 0x40, 0xC4, 0xE7, 0x8A, 0x51, 0x53, 0x48, 0xA2, 0xC0, 0x11, 0x30, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x54, 0x08, 0xA2, 0xC0, +0x12, 0x30, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x4F, 0x88, 0xB5, 0x40, 0x50, 0xC8, 0xB6, 0x40, 0x2F, 0x88, 0xA2, 0xC0, 0x19, 0x70, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x34, 0x08, 0xA2, 0xC0, +0x1A, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x37, 0x88, 0xA2, 0xC0, 0x1B, 0xB0, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x38, 0x08, 0xA2, 0xC0, 0x1C, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, +0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x06, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0x83, 0x52, +0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD5, 0x40, +0x9E, 0x40, 0x55, 0x48, 0x06, 0xBA, 0x03, 0x9D, 0x66, 0x2D, 0x35, 0x48, 0xA2, 0xC0, 0x19, 0x70, +0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x36, 0x48, +0xA2, 0xC0, 0x1A, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x53, 0x48, 0xA2, 0xC0, 0x1B, 0xB0, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x54, 0x08, 0xA2, 0xC0, 0x1C, 0x70, 0xA3, 0x00, 0x22, 0xC8, +0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x07, 0x70, 0x9B, 0x40, 0x3C, 0xB0, +0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, +0xD5, 0x40, 0x9E, 0x40, 0x55, 0x48, 0x07, 0xFA, 0x03, 0x9D, 0x9F, 0xAD, 0x83, 0x96, 0x60, 0xC8, +0x83, 0x52, 0xA2, 0xC0, 0x19, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x83, 0x96, 0x61, 0x08, 0x83, 0x52, 0xA2, 0xC0, 0x1A, 0x70, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x83, 0x96, 0x62, 0x08, +0x83, 0x52, 0xA2, 0xC0, 0x1B, 0xB0, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x83, 0x96, 0x63, 0x48, 0x83, 0x52, 0xA2, 0xC0, 0x1C, 0x70, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x08, 0xF0, 0x9B, 0x40, +0x3C, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xD5, 0x40, 0x9E, 0x40, 0x55, 0x48, 0x08, 0x7A, 0x03, 0x9D, 0xE0, 0xED, 0x83, 0x96, +0x64, 0x08, 0x83, 0x52, 0xA2, 0xC0, 0x19, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x83, 0x96, 0x65, 0x48, 0x83, 0x52, 0xA2, 0xC0, 0x1A, 0x70, +0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x83, 0x96, +0x66, 0x48, 0x83, 0x52, 0xA2, 0xC0, 0x1B, 0xB0, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x83, 0x96, 0x67, 0x88, 0x83, 0x52, 0xA2, 0xC0, 0x1C, 0x70, +0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x09, 0x30, +0x9B, 0x40, 0x3C, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xD5, 0x40, 0x9E, 0x40, 0x55, 0x48, 0x09, 0xBA, 0x03, 0x9D, 0x21, 0xAE, +0x83, 0x96, 0x68, 0x08, 0x83, 0x52, 0xA2, 0xC0, 0x19, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, +0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x83, 0x96, 0x69, 0x48, 0x83, 0x52, 0xA2, 0xC0, +0x1A, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x83, 0x96, 0x6A, 0x48, 0x83, 0x52, 0xA2, 0xC0, 0x1B, 0xB0, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, +0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x83, 0x96, 0x6B, 0x88, 0x83, 0x52, 0xA2, 0xC0, +0x1C, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x0A, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD5, 0x40, 0x9E, 0x40, 0x55, 0x48, 0x0A, 0xBA, 0x03, 0x9D, +0x62, 0xEE, 0xD7, 0xC1, 0x03, 0xD0, 0x57, 0x0D, 0xA0, 0xFE, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, +0xA2, 0xC0, 0x19, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x03, 0xD0, 0x57, 0x0D, 0xA0, 0xFE, 0x84, 0x80, 0x83, 0xD7, 0x00, 0x48, 0xA2, 0xC0, +0x1A, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x03, 0xD0, 0x57, 0x0D, 0xA1, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0xA2, 0xC0, 0x1B, 0xB0, +0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x03, 0xD0, +0x57, 0x0D, 0xA1, 0x3E, 0x84, 0x80, 0x83, 0xD7, 0x00, 0x48, 0xA2, 0xC0, 0x1C, 0x70, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x57, 0x88, 0x0C, 0xFE, +0x9B, 0x40, 0x3C, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xA4, 0xE7, 0x8A, 0x51, 0x03, 0x9D, 0xB1, 0x2E, 0x14, 0x30, 0xD7, 0x0A, +0x57, 0x82, 0x03, 0x5C, 0x73, 0x6E, 0x57, 0x88, 0x0C, 0xFE, 0x9B, 0x40, 0x3C, 0xB0, 0x83, 0x52, +0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xA4, 0xE7, +0x8A, 0x51, 0x03, 0x9D, 0xC6, 0x2E, 0x10, 0xF0, 0xA2, 0x01, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, +0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x08, 0x40, 0xAB, 0x40, 0xAB, 0x9F, 0x09, 0xEF, +0xA1, 0x1F, 0x04, 0xAF, 0x2B, 0x48, 0xA2, 0xC0, 0xA3, 0x41, 0xA2, 0xDB, 0xA3, 0x83, 0x80, 0xF0, +0xA4, 0xC0, 0xFF, 0xB0, 0xA5, 0x00, 0x22, 0xC8, 0x24, 0xC2, 0xA6, 0x00, 0x23, 0x08, 0x03, 0x5C, +0x23, 0x4A, 0x25, 0x02, 0xA7, 0x40, 0x21, 0xC8, 0xA8, 0xC0, 0xA9, 0x41, 0xA8, 0xDB, 0xA9, 0x83, +0x29, 0x08, 0x80, 0x7A, 0xAA, 0x00, 0x27, 0x48, 0x80, 0x7A, 0x2A, 0x02, 0x03, 0x9D, 0x02, 0xAF, +0x26, 0x08, 0x28, 0xC2, 0x03, 0x5C, 0x80, 0x34, 0x83, 0x52, 0x03, 0x53, 0x21, 0xC8, 0x2B, 0xC7, +0x08, 0x40, 0x21, 0xC8, 0x80, 0x7A, 0x7F, 0x3E, 0x03, 0x5C, 0x04, 0xAF, 0x21, 0xC8, 0xA2, 0xC0, +0xA3, 0x41, 0xA2, 0xDB, 0xA3, 0x83, 0x2B, 0x48, 0xA4, 0xC0, 0xA5, 0x41, 0xA4, 0xDB, 0xA5, 0x83, +0x7F, 0x70, 0xA6, 0x00, 0x24, 0xC8, 0x26, 0x02, 0xA7, 0x40, 0x25, 0x49, 0x03, 0x18, 0x01, 0xBE, +0xA8, 0xC0, 0x80, 0x7A, 0xA9, 0x00, 0x23, 0x08, 0x80, 0x7A, 0x29, 0x02, 0x03, 0x9D, 0x2A, 0x2F, +0x22, 0xC8, 0x27, 0x42, 0x03, 0x5C, 0x7F, 0xB4, 0x04, 0xAF, 0xA8, 0xC0, 0x24, 0xC8, 0x20, 0x38, +0xA7, 0x40, 0xA2, 0xC0, 0x18, 0x30, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x05, 0x30, 0xA5, 0x00, 0xA5, 0xCB, 0x3C, 0x6F, 0x83, 0x52, 0x03, 0x53, +0x24, 0xC8, 0x30, 0x78, 0xA7, 0x40, 0xA2, 0xC0, 0x18, 0x30, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, +0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x05, 0x30, 0xA5, 0x00, 0xA5, 0xCB, 0x4E, 0x6F, +0x39, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xA6, 0x00, 0x55, 0xB0, 0x9E, 0x40, 0x26, 0x9C, 0x50, 0xEF, 0x28, 0xC8, 0x84, 0x80, +0x3A, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0x83, 0x93, 0x80, 0x40, 0x28, 0x0A, 0x84, 0x80, 0x3B, 0xF0, 0x83, 0x52, 0x03, 0x53, +0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0x83, 0x93, 0x80, 0x40, +0x08, 0x40, 0xA4, 0xC0, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0xA2, 0xC0, 0x21, 0xC8, 0xB8, 0x27, +0x8A, 0x51, 0x03, 0x5C, 0x01, 0x34, 0x21, 0xC8, 0x84, 0x80, 0x00, 0x48, 0xA2, 0xC0, 0x24, 0xC8, +0xB8, 0x27, 0x8A, 0x51, 0x03, 0x5C, 0x00, 0xF4, 0x24, 0x0A, 0x84, 0x80, 0x00, 0x48, 0xA2, 0xC0, +0x21, 0x0A, 0xB8, 0x27, 0x8A, 0x51, 0x03, 0x5C, 0x01, 0x34, 0x00, 0xF4, 0xA3, 0x00, 0xA2, 0x01, +0x21, 0xC8, 0x23, 0x58, 0xA2, 0x87, 0x03, 0xD0, 0xA1, 0x8D, 0x03, 0xD0, 0xA3, 0x8C, 0xA3, 0x48, +0x03, 0x9D, 0x98, 0x2F, 0x22, 0xC8, 0x08, 0x40, 0xD5, 0x40, 0x9E, 0x40, 0x57, 0x88, 0x0C, 0xFE, +0xB1, 0x00, 0x00, 0xB0, 0x03, 0x18, 0x01, 0xF0, 0xB2, 0x00, 0x55, 0x48, 0x31, 0x46, 0x32, 0x04, +0x08, 0x40, 0x56, 0x48, 0x42, 0xFE, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0xA2, 0xC0, 0x12, 0x74, +0x84, 0x80, 0x00, 0x48, 0xA3, 0x00, 0x22, 0xC8, 0x23, 0x02, 0x08, 0x40, 0xCF, 0x80, 0x52, 0x08, +0xD0, 0xC0, 0x56, 0x48, 0xCE, 0x40, 0x08, 0x40, 0x4E, 0x48, 0x42, 0xFE, 0x84, 0x80, 0x00, 0x48, +0xD4, 0x00, 0x08, 0x40, 0x39, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xA2, 0xC0, 0x11, 0x74, 0xFD, 0xF9, +0x02, 0x38, 0xA2, 0xC0, 0x02, 0x34, 0xFD, 0xF9, 0x02, 0x38, 0xA2, 0xC0, 0x01, 0x34, 0x03, 0xD0, +0xB0, 0x8D, 0x03, 0xD0, 0xB0, 0x8D, 0x08, 0x40, 0xFF, 0x3A, 0x01, 0xBE, 0xA1, 0xC0, 0x53, 0x48, +0x08, 0x40, 0x03, 0xD0, 0xB1, 0xCD, 0x03, 0xD0, 0xB1, 0xCD, 0x08, 0x40, 0x80, 0x40, 0x05, 0x30, +0xA1, 0xC0, 0x57, 0x88, 0x08, 0x40, 0xFF, 0x3A, 0x01, 0xBE, 0xA1, 0xC0, 0x54, 0x08, 0x08, 0x40, +0xA0, 0x30, 0x83, 0x93, 0x84, 0x80, 0xF0, 0xB0, 0x8A, 0x51, 0x2F, 0xE1, 0x8A, 0x51, 0xA0, 0x30, +0x83, 0xD7, 0x84, 0x80, 0xE0, 0x70, 0x8A, 0x51, 0x2F, 0xE1, 0x83, 0x01, 0x8A, 0x95, 0xD5, 0x2A, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0x01, 0xF0, 0xA0, 0x80, 0x95, 0x41, +0x96, 0x41, 0x97, 0x81, 0x98, 0x01, 0x99, 0x41, 0x9A, 0x41, 0x9B, 0x81, 0x9C, 0x41, 0x9E, 0x81, +0x9B, 0x81, 0x1C, 0x70, 0xA2, 0x01, 0xA2, 0x4A, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x01, 0xF0, 0x9B, 0x40, 0x3C, 0xB0, 0x83, 0x52, 0x03, 0x53, +0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xE5, 0x40, 0x65, 0x48, +0x03, 0x59, 0xED, 0x6A, 0x9B, 0x81, 0x10, 0xF0, 0xA2, 0x01, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, +0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3C, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, +0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xE5, 0x40, 0x9E, 0x40, 0x65, 0xCB, +0x04, 0x6B, 0x3D, 0xF0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xE2, 0x00, 0xA2, 0xC0, 0x1A, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, +0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3E, 0xF0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, +0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xE3, 0x40, 0xA2, 0xC0, 0x1B, 0xB0, +0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3F, 0x30, +0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, +0xE4, 0x00, 0x03, 0x30, 0xE4, 0x85, 0x64, 0x08, 0xA2, 0xC0, 0x1C, 0x70, 0xA3, 0x00, 0x22, 0xC8, +0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x01, 0xF0, 0x9E, 0x40, 0x9B, 0x40, +0x3C, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xE5, 0x40, 0x9E, 0x40, 0x65, 0x48, 0x02, 0x7A, 0x03, 0x9D, 0x50, 0xAB, 0x3D, 0xF0, +0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, +0xDF, 0xC0, 0xA2, 0xC0, 0x1A, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x3E, 0xF0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xE0, 0xC0, 0xA2, 0xC0, 0x1B, 0xB0, 0xA3, 0x00, 0x22, 0xC8, +0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x02, 0xF0, 0x9E, 0x40, 0x9B, 0x40, +0x3C, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xE5, 0x40, 0x9E, 0x40, 0x65, 0x48, 0x03, 0xBA, 0x03, 0x9D, 0x88, 0xAB, 0x3D, 0xF0, +0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, +0xDC, 0x40, 0xA2, 0xC0, 0x1A, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x3E, 0xF0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xDD, 0x80, 0xA2, 0xC0, 0x1B, 0xB0, 0xA3, 0x00, 0x22, 0xC8, +0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x03, 0x30, 0x9E, 0x40, 0x9B, 0x40, +0x3C, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xE5, 0x40, 0x9E, 0x40, 0x65, 0x48, 0x04, 0x7A, 0x03, 0x9D, 0xC0, 0xAB, 0x9E, 0x81, +0x9B, 0x81, 0x3D, 0xF0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xDE, 0x80, 0xA2, 0xC0, 0x1A, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, +0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3E, 0xF0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, +0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xE1, 0x00, 0xA2, 0xC0, 0x1B, 0xB0, +0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xE2, 0x48, +0x03, 0x9D, 0x02, 0x2C, 0x63, 0x48, 0x56, 0xA4, 0x00, 0xB0, 0x8A, 0x95, 0x5C, 0xA4, 0x8A, 0x95, +0x11, 0x30, 0x3B, 0x2C, 0x62, 0x8B, 0x0C, 0x6C, 0x63, 0x48, 0x56, 0xA4, 0x01, 0xF0, 0x8A, 0x95, +0x5C, 0xA4, 0x8A, 0x95, 0x22, 0x30, 0x3B, 0x2C, 0x62, 0x08, 0x02, 0x7A, 0x03, 0x9D, 0x18, 0x6C, +0x63, 0x48, 0x4C, 0x64, 0x00, 0xB0, 0x8A, 0x51, 0x39, 0xA2, 0x8A, 0x95, 0x33, 0xB0, 0x3B, 0x2C, +0x62, 0x08, 0x03, 0xBA, 0x03, 0x9D, 0x24, 0x6C, 0x63, 0x48, 0x4C, 0x64, 0x01, 0xF0, 0x8A, 0x51, +0x39, 0xA2, 0x8A, 0x95, 0x44, 0x30, 0x3B, 0x2C, 0x62, 0x08, 0x04, 0x7A, 0x03, 0x9D, 0x30, 0x6C, +0x63, 0x48, 0x3E, 0xE4, 0x00, 0xB0, 0x8A, 0x51, 0x37, 0xE1, 0x8A, 0x95, 0x55, 0xB0, 0x3B, 0x2C, +0x62, 0x08, 0x05, 0xBA, 0x03, 0x9D, 0x04, 0x6B, 0x63, 0x48, 0x3E, 0xE4, 0x01, 0xF0, 0x8A, 0x51, +0x37, 0xE1, 0x8A, 0x95, 0x66, 0xB0, 0x9B, 0x40, 0x9E, 0x40, 0x04, 0x6B, 0xA9, 0x00, 0x5F, 0xC8, +0xAA, 0x00, 0x60, 0xC8, 0xAB, 0x40, 0x5C, 0x48, 0xAC, 0x00, 0x5D, 0x88, 0xAD, 0x40, 0x5E, 0x88, +0xAE, 0x40, 0x64, 0x08, 0xAF, 0x80, 0x08, 0x40, 0xAC, 0x00, 0x5F, 0xC8, 0xAD, 0x40, 0x60, 0xC8, +0xAE, 0x40, 0x61, 0x08, 0xAF, 0x80, 0x64, 0x08, 0xB0, 0xC0, 0x08, 0x40, 0xA9, 0x00, 0x61, 0x08, +0xAA, 0x00, 0x64, 0x08, 0xAB, 0x40, 0x08, 0x40, 0xB3, 0x40, 0x04, 0xF0, 0xA2, 0xC0, 0x10, 0xF0, +0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x29, 0x08, +0xA2, 0xC0, 0x13, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x14, 0x30, 0xA2, 0x01, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x15, 0x70, 0xA2, 0x01, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x2A, 0x08, 0xD6, 0x40, 0x0B, 0x70, 0xD7, 0xC1, 0xDB, 0x80, +0x5B, 0x88, 0xB4, 0x00, 0x33, 0x98, 0xE0, 0xAC, 0x5B, 0x88, 0xAC, 0x00, 0x01, 0xF0, 0xFB, 0xE7, +0x8A, 0x95, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xC3, 0x39, 0x2C, 0x04, 0xA2, 0xC0, 0x01, 0xF0, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, +0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x01, 0xF0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, +0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0x8A, 0x51, 0xD3, 0x67, 0x8A, 0x95, +0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x01, 0xF0, +0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, +0xA2, 0xC0, 0x01, 0xF0, 0xA2, 0x10, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x2B, 0x48, 0xA4, 0xC0, 0x53, 0xB0, 0x8A, 0x51, 0x2D, 0x27, 0x8A, 0x95, +0x56, 0xB0, 0xA1, 0xC0, 0x53, 0xB0, 0x8A, 0x51, 0x79, 0x67, 0x8A, 0x95, 0xD0, 0xC0, 0x50, 0xC8, +0x03, 0x9D, 0x1F, 0x6D, 0x07, 0x70, 0xDB, 0x03, 0x5B, 0x82, 0x03, 0x5C, 0x1F, 0x6D, 0x88, 0x6C, +0x5B, 0x88, 0xAC, 0x00, 0x02, 0xF0, 0xFB, 0xE7, 0x8A, 0x95, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, +0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC3, 0x39, 0x2C, 0x04, 0xA2, 0xC0, +0x02, 0xF0, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x02, 0xF0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0x8A, 0x51, 0xCF, 0xA7, 0x8A, 0x95, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x02, 0xF0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, +0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xA2, 0xC0, 0x02, 0xF0, 0xA2, 0x10, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xCA, 0xEC, 0x53, 0x48, +0xB5, 0x40, 0x54, 0x08, 0xB6, 0x40, 0xF3, 0xA7, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x2B, 0x48, 0xA4, 0xC0, 0x56, 0xB0, 0x8A, 0x51, 0x2D, 0x27, +0x8A, 0x95, 0x56, 0x48, 0xB9, 0x40, 0x57, 0x88, 0xBA, 0x40, 0x29, 0x08, 0xFE, 0xFC, 0xAD, 0x40, +0x29, 0x49, 0xAE, 0x40, 0x29, 0x08, 0x01, 0x7C, 0xAF, 0x80, 0x29, 0x08, 0x02, 0x7C, 0xB0, 0xC0, +0x14, 0x30, 0xA2, 0x01, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x15, 0x70, 0xA2, 0x01, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x2D, 0x48, 0xA2, 0xC0, 0x13, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, +0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x2B, 0x48, 0xA4, 0xC0, 0x53, 0xB0, 0x8A, 0x51, +0x2D, 0x27, 0x8A, 0x95, 0xDB, 0xC1, 0xDB, 0x0A, 0x5B, 0x88, 0x2D, 0x7E, 0x84, 0x80, 0x00, 0x48, +0xA2, 0xC0, 0x13, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x2B, 0x48, 0xA4, 0xC0, 0x56, 0xB0, 0x8A, 0x51, 0x2D, 0x27, 0x8A, 0x95, 0x53, 0xB0, +0xA1, 0xC0, 0x56, 0xB0, 0x8A, 0x51, 0x79, 0x67, 0x8A, 0x95, 0xD0, 0xC0, 0x50, 0xC8, 0x03, 0x59, +0x85, 0xED, 0x56, 0x48, 0xD3, 0x40, 0x57, 0x88, 0xD4, 0x00, 0x04, 0xF0, 0xDB, 0x0A, 0x5B, 0x82, +0x03, 0x5C, 0x64, 0xED, 0x53, 0x48, 0xB7, 0x80, 0x54, 0x08, 0xB8, 0x00, 0xF3, 0xA7, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xD8, 0x41, 0xD9, 0x81, +0xDB, 0xC1, 0x58, 0x08, 0xBD, 0x80, 0x59, 0x48, 0xC6, 0x00, 0x5B, 0x88, 0x0A, 0xFE, 0x84, 0x80, +0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x95, 0x58, 0x87, 0xBE, 0x80, 0x5B, 0x88, 0x0A, 0xFE, 0x84, 0x80, +0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x95, 0x59, 0xC7, 0xC7, 0x40, 0x5B, 0x88, 0x0A, 0xFE, 0x84, 0x80, +0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x95, 0x58, 0x87, 0xBF, 0xC0, 0x5B, 0x88, 0x0A, 0xFE, 0x84, 0x80, +0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x95, 0x59, 0x42, 0xC8, 0xC0, 0x5B, 0x88, 0x0A, 0xFE, 0x84, 0x80, +0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x95, 0x58, 0x02, 0xC0, 0x80, 0x5B, 0x88, 0x0A, 0xFE, 0x84, 0x80, +0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x95, 0x59, 0xC7, 0xC9, 0x00, 0x5B, 0x88, 0x0A, 0xFE, 0x84, 0x80, +0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x95, 0x58, 0x02, 0xC1, 0xC0, 0x5B, 0x88, 0x0A, 0xFE, 0x84, 0x80, +0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x95, 0x59, 0x42, 0xCA, 0x00, 0xE1, 0x27, 0x8A, 0x95, 0x97, 0x67, +0x8A, 0x95, 0xDA, 0x67, 0x8A, 0x95, 0x97, 0x67, 0x8A, 0x95, 0xF7, 0xE7, 0xA3, 0x00, 0x22, 0xC8, +0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x46, 0x08, 0xA2, 0xC0, 0x15, 0x70, +0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x2B, 0x48, +0xA4, 0xC0, 0x51, 0x70, 0x8A, 0x51, 0x2D, 0x27, 0x8A, 0x95, 0xCF, 0xC1, 0xDA, 0x81, 0x05, 0x30, +0xDA, 0xCA, 0x5A, 0x42, 0x03, 0x18, 0x32, 0xEE, 0x1F, 0xF0, 0xCB, 0x67, 0x8A, 0x95, 0x97, 0x67, +0x8A, 0x95, 0xAE, 0x67, 0x8A, 0x95, 0x97, 0x67, 0x8A, 0x95, 0xBE, 0xA7, 0x8A, 0x95, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xD3, 0x67, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x2B, 0x48, 0xA4, 0xC0, +0x56, 0xB0, 0x8A, 0x51, 0x2D, 0x27, 0x8A, 0x95, 0x56, 0xB0, 0xA1, 0xC0, 0x51, 0x70, 0x8A, 0x51, +0x79, 0x67, 0x8A, 0x95, 0xD0, 0xC0, 0x50, 0xC8, 0x03, 0x59, 0xFF, 0x2D, 0x56, 0x48, 0xE7, 0xA7, +0x8A, 0x95, 0xFF, 0x2D, 0x4F, 0x88, 0x3D, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xD8, 0x00, 0xED, 0xA7, +0x8A, 0x95, 0x06, 0x30, 0xDB, 0x0A, 0x5B, 0x82, 0x58, 0x08, 0x03, 0x5C, 0x9A, 0x2D, 0xFF, 0x7E, +0xBD, 0x80, 0x59, 0x48, 0xFF, 0x7E, 0xC6, 0x00, 0x58, 0x08, 0xFF, 0x7E, 0xBE, 0x80, 0x59, 0x48, +0xC7, 0x40, 0x58, 0x08, 0xFF, 0x7E, 0xBF, 0xC0, 0x59, 0x48, 0x01, 0xBE, 0xC8, 0xC0, 0x58, 0x08, +0xC0, 0x80, 0x59, 0x48, 0xFF, 0x7E, 0xC9, 0x00, 0x58, 0x08, 0xC1, 0xC0, 0x59, 0x48, 0xCA, 0x00, +0x58, 0x08, 0xC2, 0xC0, 0x59, 0x48, 0x01, 0xBE, 0xCB, 0x40, 0x58, 0x08, 0x01, 0xBE, 0xC3, 0x00, +0x59, 0x48, 0xFF, 0x7E, 0xCC, 0x00, 0x58, 0x08, 0x01, 0xBE, 0xC4, 0xC0, 0x59, 0x48, 0xCD, 0x40, +0x58, 0x08, 0x01, 0xBE, 0xC5, 0x00, 0x59, 0x48, 0x01, 0xBE, 0xCE, 0x40, 0xE1, 0x27, 0x8A, 0x95, +0x97, 0x67, 0x8A, 0x95, 0xDA, 0x67, 0x8A, 0x95, 0x97, 0x67, 0x8A, 0x95, 0xF7, 0xE7, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x46, 0x08, 0xA2, 0xC0, +0x15, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x2B, 0x48, 0xA4, 0xC0, 0x51, 0x70, 0x8A, 0x51, 0x2D, 0x27, 0x8A, 0x95, 0xCF, 0xC1, 0xDA, 0x81, +0x09, 0x30, 0xDA, 0xCA, 0x5A, 0x42, 0x03, 0x18, 0xC3, 0x2E, 0x1F, 0xF0, 0xCB, 0x67, 0x8A, 0x95, +0x97, 0x67, 0x8A, 0x95, 0xAE, 0x67, 0x8A, 0x95, 0x97, 0x67, 0x8A, 0x95, 0xBE, 0xA7, 0x8A, 0x95, +0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xD3, 0x67, +0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x2B, 0x48, +0xA4, 0xC0, 0x56, 0xB0, 0x8A, 0x51, 0x2D, 0x27, 0x8A, 0x95, 0x56, 0xB0, 0xA1, 0xC0, 0x51, 0x70, +0x8A, 0x51, 0x79, 0x67, 0x8A, 0x95, 0xD0, 0xC0, 0x50, 0xC8, 0x03, 0x59, 0x90, 0xAE, 0x56, 0x48, +0xE7, 0xA7, 0x8A, 0x95, 0x90, 0xAE, 0x4F, 0x88, 0x3D, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xD8, 0x00, +0xED, 0xA7, 0x8A, 0x95, 0x51, 0x08, 0xBB, 0x80, 0x52, 0x08, 0xBC, 0x40, 0x58, 0x08, 0xA2, 0xC0, +0x14, 0x30, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x59, 0x48, 0xA2, 0xC0, 0x15, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x2A, 0x08, 0xA2, 0xC0, 0x19, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, +0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x34, 0x08, 0xA2, 0xC0, 0x1A, 0x70, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x35, 0x48, 0xA2, 0xC0, +0x1B, 0xB0, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x36, 0x48, 0xA2, 0xC0, 0x1C, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x06, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0, +0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD5, 0x40, 0x9E, 0x40, 0x55, 0x48, +0x06, 0xBA, 0x03, 0x9D, 0x0C, 0xEF, 0x39, 0x48, 0xA2, 0xC0, 0x19, 0x70, 0xA3, 0x00, 0x22, 0xC8, +0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3A, 0x48, 0xA2, 0xC0, 0x1A, 0x70, +0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x58, 0x08, +0xA2, 0xC0, 0x1B, 0xB0, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x59, 0x48, 0xA2, 0xC0, 0x1C, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x07, 0x70, 0x9B, 0x40, 0x3C, 0xB0, 0x83, 0x52, 0x03, 0x53, +0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD5, 0x40, 0x9E, 0x40, +0x55, 0x48, 0x07, 0xFA, 0x03, 0x9D, 0x45, 0x2F, 0x3B, 0x88, 0xA2, 0xC0, 0x19, 0x70, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3C, 0x48, 0xA2, 0xC0, +0x1A, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x37, 0x88, 0xA2, 0xC0, 0x1B, 0xB0, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x38, 0x08, 0xA2, 0xC0, 0x1C, 0x70, 0xA3, 0x00, 0x22, 0xC8, 0x96, 0x00, +0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x08, 0xF0, 0x9B, 0x40, 0x3C, 0xB0, 0x83, 0x52, +0x03, 0x53, 0xA1, 0xC0, 0x21, 0xC8, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD5, 0x40, +0x9E, 0x40, 0x55, 0x48, 0x08, 0x7A, 0x03, 0x9D, 0x7E, 0xEF, 0x10, 0xF0, 0xA2, 0x01, 0xA3, 0x00, +0x22, 0xC8, 0x96, 0x00, 0x23, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x08, 0x40, 0xA4, 0xC0, +0x21, 0xC8, 0x80, 0x7A, 0xA3, 0x00, 0x24, 0xC8, 0x80, 0x7A, 0xA3, 0x42, 0x03, 0x18, 0xA2, 0x2F, +0x21, 0xC8, 0x08, 0x40, 0x24, 0xC8, 0x80, 0x7A, 0xA3, 0x00, 0x22, 0xC8, 0x80, 0x7A, 0xA3, 0x42, +0x03, 0x18, 0xAC, 0x6F, 0x22, 0xC8, 0x08, 0x40, 0x24, 0xC8, 0x08, 0x40, 0xAC, 0x00, 0x5A, 0x48, +0x3D, 0xBE, 0x84, 0x80, 0x2C, 0x08, 0x83, 0x93, 0x80, 0x40, 0x1F, 0xF0, 0xA1, 0xC0, 0xE0, 0x70, +0xA2, 0xC0, 0x5A, 0x48, 0x46, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x08, 0x40, 0xAC, 0x00, 0x5A, 0x48, +0x46, 0x3E, 0x84, 0x80, 0x2C, 0x08, 0x83, 0x93, 0x80, 0x40, 0x5A, 0x48, 0x3D, 0xBE, 0x84, 0x80, +0x00, 0x48, 0xA2, 0xC0, 0x14, 0x74, 0xA1, 0xC0, 0xE0, 0x70, 0xA2, 0xC0, 0x5A, 0x48, 0x3D, 0xBE, +0x84, 0x80, 0x00, 0x48, 0x08, 0x40, 0x5A, 0x48, 0x46, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, +0xA2, 0xC0, 0x15, 0xB4, 0xBD, 0x80, 0x1F, 0xF0, 0xA1, 0xC0, 0xE0, 0x70, 0xA2, 0xC0, 0x46, 0x08, +0x08, 0x40, 0x1F, 0xF0, 0xA1, 0xC0, 0xE0, 0x70, 0xA2, 0xC0, 0x3D, 0x88, 0x08, 0x40, 0xD1, 0x00, +0x57, 0x88, 0xD2, 0x00, 0x5A, 0x48, 0xCF, 0x80, 0x08, 0x40, 0x4F, 0x88, 0x46, 0x3E, 0x84, 0x80, +0x00, 0x48, 0xD9, 0x40, 0x08, 0x40, 0x29, 0x43, 0xFF, 0x3A, 0xA2, 0xC0, 0x13, 0xB4, 0xC6, 0x00, +0x3D, 0x88, 0xA2, 0xC0, 0x14, 0x74, 0x03, 0xD0, 0xAC, 0xCD, 0x03, 0xD0, 0xAC, 0xCD, 0x08, 0x40 + }; + \ No newline at end of file diff --git a/libloragw/src/loragw_aux.c b/libloragw/src/loragw_aux.c new file mode 100644 index 0000000..76dab0d --- /dev/null +++ b/libloragw/src/loragw_aux.c @@ -0,0 +1,60 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + LoRa concentrator HAL auxiliary functions + +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 /* printf fprintf */ +#include /* clock_nanosleep */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#if DEBUG_AUX == 1 + #define DEBUG_MSG(str) fprintf(stderr, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +/* This implementation is POSIX-pecific and require a fix to be compatible with C99 */ +void wait_ms(unsigned long a) { + struct timespec dly; + struct timespec rem; + + dly.tv_sec = a / 1000; + dly.tv_nsec = ((long)a % 1000) * 1000000; + + DEBUG_PRINTF("NOTE dly: %ld sec %ld ns\n", dly.tv_sec, dly.tv_nsec); + + if((dly.tv_sec > 0) || ((dly.tv_sec == 0) && (dly.tv_nsec > 100000))) { + clock_nanosleep(CLOCK_MONOTONIC, 0, &dly, &rem); + DEBUG_PRINTF("NOTE remain: %ld sec %ld ns\n", rem.tv_sec, rem.tv_nsec); + } + return; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_cal.c b/libloragw/src/loragw_cal.c new file mode 100644 index 0000000..bb94b7d --- /dev/null +++ b/libloragw/src/loragw_cal.c @@ -0,0 +1,1044 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + LoRa concentrator radio calibration functions + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* printf fprintf */ +#include /* malloc free */ +#include /* lseek, close */ +#include /* open */ +#include /* memset */ +#include +#include + +#include +#include + +#include "loragw_reg.h" +#include "loragw_aux.h" +#include "loragw_hal.h" +#include "loragw_sx1302.h" +#include "loragw_sx125x.h" +#include "loragw_cal.h" + +/* -------------------------------------------------------------------------- */ +/* --- DEBUG FLAGS ---------------------------------------------------------- */ + +#define TX_CALIB_DONE_BY_HAL 0 + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_CAL == 1 + #define DEBUG_MSG(str) fprintf(stderr, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) + #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_SPI_ERROR;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return LGW_SPI_ERROR;} +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define CAL_TX_TONE_FREQ_HZ 250000 +#define CAL_ITER 3 /* Number of calibration iterations */ +#define CAL_TX_CORR_DURATION 0 /* 0:1ms, 1:2ms, 2:4ms, 3:8ms */ + +/* -------------------------------------------------------------------------- */ +/* --- INTERNAL SHARED VARIABLES -------------------------------------------- */ + +/* Record Rx IQ mismatch corrections from calibration */ +static int8_t rf_rx_image_amp[LGW_RF_CHAIN_NB] = {0, 0}; +static int8_t rf_rx_image_phi[LGW_RF_CHAIN_NB] = {0, 0}; + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +void cal_rx_result_init(struct lgw_sx125x_cal_rx_result_s *res_rx_min, struct lgw_sx125x_cal_rx_result_s *res_rx_max); +void cal_rx_result_sort(struct lgw_sx125x_cal_rx_result_s *res_rx, struct lgw_sx125x_cal_rx_result_s *res_rx_min, struct lgw_sx125x_cal_rx_result_s *res_rx_max); +bool cal_rx_result_assert(struct lgw_sx125x_cal_rx_result_s *res_rx_min, struct lgw_sx125x_cal_rx_result_s *res_rx_max); +int sx125x_cal_rx_image(uint8_t rf_chain, uint32_t freq_hz, bool use_loopback, uint8_t radio_type, struct lgw_sx125x_cal_rx_result_s * res); + +void cal_tx_result_init(struct lgw_sx125x_cal_tx_result_s *res_tx_min, struct lgw_sx125x_cal_tx_result_s *res_tx_max); +void cal_tx_result_sort(struct lgw_sx125x_cal_tx_result_s *res_tx, struct lgw_sx125x_cal_tx_result_s *res_tx_min, struct lgw_sx125x_cal_tx_result_s *res_tx_max); +bool cal_tx_result_assert(struct lgw_sx125x_cal_tx_result_s *res_tx_min, struct lgw_sx125x_cal_tx_result_s *res_tx_max); +int sx125x_cal_tx_dc_offset(uint8_t rf_chain, uint32_t freq_hz, uint8_t dac_gain, uint8_t mix_gain, uint8_t radio_type, struct lgw_sx125x_cal_tx_result_s * res); + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int sx1302_cal_start(uint8_t version, struct lgw_conf_rxrf_s * rf_chain_cfg, struct lgw_tx_gain_lut_s * txgain_lut) { + int i, j, k; + uint8_t val; + bool cal_status = false; + uint8_t x_max; + int x_max_idx; + uint8_t dac_gain[LGW_RF_CHAIN_NB][TX_GAIN_LUT_SIZE_MAX]; + uint8_t mix_gain[LGW_RF_CHAIN_NB][TX_GAIN_LUT_SIZE_MAX]; + int8_t offset_i[LGW_RF_CHAIN_NB][TX_GAIN_LUT_SIZE_MAX]; + int8_t offset_q[LGW_RF_CHAIN_NB][TX_GAIN_LUT_SIZE_MAX]; + uint8_t nb_gains[LGW_RF_CHAIN_NB]; + bool unique_gains; + struct lgw_sx125x_cal_rx_result_s cal_rx[CAL_ITER], cal_rx_min, cal_rx_max; + struct lgw_sx125x_cal_tx_result_s cal_tx[CAL_ITER], cal_tx_min, cal_tx_max; + + /* Wait for AGC fw to be started, and VERSION available in mailbox */ + sx1302_agc_wait_status(0x01); /* fw has started, VERSION is ready in mailbox */ + + sx1302_agc_mailbox_read(0, &val); + if (val != version) { + printf("ERROR: wrong CAL fw version (%d)\n", val); + return LGW_HAL_ERROR; + } + printf("CAL FW VERSION: %d\n", val); + + /* notify CAL that it can resume */ + sx1302_agc_mailbox_write(3, 0xFF); + + /* Wait for AGC to acknoledge */ + sx1302_agc_wait_status(0x00); + + printf("CAL: started\n"); + + /* Run Rx image calibration */ + for (i = 0; i < LGW_RF_CHAIN_NB; i++) { + if (rf_chain_cfg[i].enable) { + /* Calibration using the other radio for Tx */ + if (rf_chain_cfg[0].type == rf_chain_cfg[1].type) { + cal_rx_result_init(&cal_rx_min, &cal_rx_max); + for (j = 0; j < CAL_ITER; j++) { + sx125x_cal_rx_image(i, rf_chain_cfg[i].freq_hz, false, rf_chain_cfg[i].type, &cal_rx[j]); + cal_rx_result_sort(&cal_rx[j], &cal_rx_min, &cal_rx_max); + } + cal_status = cal_rx_result_assert(&cal_rx_min, &cal_rx_max); + } + + /* If failed or different radios, run calibration using RF loopback (assuming that it is better than no calibration) */ + if ((cal_status == false) || (rf_chain_cfg[0].type != rf_chain_cfg[1].type)) { + cal_rx_result_init(&cal_rx_min, &cal_rx_max); + for (j = 0; j < CAL_ITER; j++) { + sx125x_cal_rx_image(i, rf_chain_cfg[i].freq_hz, true, rf_chain_cfg[i].type, &cal_rx[j]); + cal_rx_result_sort(&cal_rx[j], &cal_rx_min, &cal_rx_max); + } + cal_status = cal_rx_result_assert(&cal_rx_min, &cal_rx_max); + } + + if (cal_status == false) { + DEBUG_MSG("*********************************************\n"); + DEBUG_PRINTF("ERROR: Rx image calibration of radio %d failed\n",i); + DEBUG_MSG("*********************************************\n"); + return LGW_HAL_ERROR; + } + + /* Use the results of the best iteration */ + x_max = 0; + x_max_idx = 0; + for (j=0; j x_max) { + x_max = cal_rx[j].rej; + x_max_idx = j; + } + } + rf_rx_image_amp[i] = cal_rx[x_max_idx].amp; + rf_rx_image_phi[i] = cal_rx[x_max_idx].phi; + + DEBUG_PRINTF("INFO: Rx image calibration of radio %d succeeded. Improved image rejection from %2d to %2d dB (Amp:%3d Phi:%3d)\n", i, cal_rx[x_max_idx].rej_init, cal_rx[x_max_idx].rej, cal_rx[x_max_idx].amp, cal_rx[x_max_idx].phi); + } else { + rf_rx_image_amp[i] = 0; + rf_rx_image_phi[i] = 0; + } + } + + /* Apply calibrated IQ mismatch compensation */ + lgw_reg_w(SX1302_REG_RADIO_FE_IQ_COMP_AMP_COEFF_RADIO_A_AMP_COEFF, (int32_t)rf_rx_image_amp[0]); + lgw_reg_w(SX1302_REG_RADIO_FE_IQ_COMP_PHI_COEFF_RADIO_A_PHI_COEFF, (int32_t)rf_rx_image_phi[0]); + lgw_reg_w(SX1302_REG_RADIO_FE_IQ_COMP_AMP_COEFF_RADIO_B_AMP_COEFF, (int32_t)rf_rx_image_amp[1]); + lgw_reg_w(SX1302_REG_RADIO_FE_IQ_COMP_PHI_COEFF_RADIO_B_PHI_COEFF, (int32_t)rf_rx_image_phi[1]); + + /* Get List of unique combinations of DAC and mixer gains */ + for (k = 0; k < LGW_RF_CHAIN_NB; k++) { + nb_gains[k] = 0; + for (i = 0; i < txgain_lut[k].size; i++) { + unique_gains = true; + for (j = 0; j < nb_gains[k]; j++) { + if ((txgain_lut[k].lut[i].dac_gain == dac_gain[k][j]) && (txgain_lut[k].lut[i].mix_gain == mix_gain[k][j])) { + unique_gains = false; + } + } + if (unique_gains) { + dac_gain[k][nb_gains[k]] = txgain_lut[k].lut[i].dac_gain; + mix_gain[k][nb_gains[k]] = txgain_lut[k].lut[i].mix_gain; + nb_gains[k] += 1; + } + } + } + + /* Run Tx image calibration */ + for (i = 0; i < LGW_RF_CHAIN_NB; i++) { + if (rf_chain_cfg[i].tx_enable) { + for (j = 0; j < nb_gains[i]; j++) { + cal_tx_result_init(&cal_tx_min, &cal_tx_max); + for (k = 0; k < CAL_ITER; k++){ + sx125x_cal_tx_dc_offset(i, rf_chain_cfg[i].freq_hz, dac_gain[i][j], mix_gain[i][j], rf_chain_cfg[i].type, &cal_tx[k]); + cal_tx_result_sort(&cal_tx[k], &cal_tx_min, &cal_tx_max); + } + cal_status = cal_tx_result_assert(&cal_tx_min, &cal_tx_max); + + if (cal_status == false) { + DEBUG_MSG("*********************************************\n"); + DEBUG_PRINTF("ERROR: Tx DC offset calibration of radio %d for DAC gain %d and mixer gain %2d failed\n", i, dac_gain[i][j], mix_gain[i][j]); + DEBUG_MSG("*********************************************\n"); + return LGW_HAL_ERROR; + } + + /* Use the results of the best iteration */ + x_max = 0; + x_max_idx = 0; + for (k = 0; k < CAL_ITER; k++) { + if (cal_tx[k].rej > x_max) { + x_max = cal_tx[k].rej; + x_max_idx = k; + } + } + offset_i[i][j] = cal_tx[x_max_idx].offset_i; + offset_q[i][j] = cal_tx[x_max_idx].offset_q; + + DEBUG_PRINTF("INFO: Tx DC offset calibration of radio %d for DAC gain %d and mixer gain %2d succeeded. Improved DC rejection by %2d dB (I:%4d Q:%4d)\n", i, dac_gain[i][j], mix_gain[i][j], cal_tx[x_max_idx].rej, cal_tx[x_max_idx].offset_i, cal_tx[x_max_idx].offset_q); + } + } + } + + /* Fill DC offsets in Tx LUT */ + for (k = 0; k < LGW_RF_CHAIN_NB; k++) { + for (i = 0; i < txgain_lut[k].size; i++) { + for (j = 0; j < nb_gains[k]; j++) { + if ((txgain_lut[k].lut[i].dac_gain == dac_gain[k][j]) && (txgain_lut[k].lut[i].mix_gain == mix_gain[k][j])) { + break; + } + } + txgain_lut[k].lut[i].offset_i = offset_i[k][j]; + txgain_lut[k].lut[i].offset_q = offset_q[k][j]; + } + } + + printf("-------------------------------------------------------------------\n"); + printf("Radio calibration completed:\n"); + printf(" RadioA: amp:%d phi:%d\n", rf_rx_image_amp[0], rf_rx_image_phi[0]); + printf(" RadioB: amp:%d phi:%d\n", rf_rx_image_amp[1], rf_rx_image_phi[1]); + for (k = 0; k < LGW_RF_CHAIN_NB; k++) { + printf(" TX calibration params for rf_chain %d:\n", k); + for (i = 0; i < txgain_lut[k].size; i++) { + printf(" -- power:%d\tdac:%u\tmix:%u\toffset_i:%d\toffset_q:%d\n", txgain_lut[k].lut[i].rf_power, txgain_lut[k].lut[i].dac_gain, txgain_lut[k].lut[i].mix_gain, txgain_lut[k].lut[i].offset_i, txgain_lut[k].lut[i].offset_q); + } + } + printf("-------------------------------------------------------------------\n"); + + return LGW_HAL_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx125x_cal_rx_image(uint8_t rf_chain, uint32_t freq_hz, bool use_loopback, uint8_t radio_type, struct lgw_sx125x_cal_rx_result_s * res) { + uint8_t rx, tx; + uint32_t rx_freq_hz, tx_freq_hz; + uint32_t rx_freq_int, rx_freq_frac; + uint32_t tx_freq_int, tx_freq_frac; + uint8_t rx_pll_locked, tx_pll_locked; + uint8_t rx_threshold = 8; /* Used by AGC to set decimation gain to increase signal and its image: value is MSB => x * 256 */ + + printf("\n%s: rf_chain:%u, freq_hz:%u, loopback:%d, radio_type:%d\n", __FUNCTION__, rf_chain, freq_hz, use_loopback, radio_type); + + /* Indentify which radio is transmitting the test tone */ + rx = rf_chain; + if (use_loopback == true) { + tx = rf_chain; + } else { + tx = 1-rf_chain; + } + + /* Set PLL frequencies */ + rx_freq_hz = freq_hz; + tx_freq_hz = freq_hz + CAL_TX_TONE_FREQ_HZ; + switch (radio_type) { + case LGW_RADIO_TYPE_SX1255: + rx_freq_int = rx_freq_hz / (SX125x_32MHz_FRAC << 7); /* integer part, gives the MSB */ + rx_freq_frac = ((rx_freq_hz % (SX125x_32MHz_FRAC << 7)) << 9) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ + tx_freq_int = tx_freq_hz / (SX125x_32MHz_FRAC << 7); /* integer part, gives the MSB */ + tx_freq_frac = ((tx_freq_hz % (SX125x_32MHz_FRAC << 7)) << 9) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ + break; + case LGW_RADIO_TYPE_SX1257: + rx_freq_int = rx_freq_hz / (SX125x_32MHz_FRAC << 8); /* integer part, gives the MSB */ + rx_freq_frac = ((rx_freq_hz % (SX125x_32MHz_FRAC << 8)) << 8) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ + tx_freq_int = tx_freq_hz / (SX125x_32MHz_FRAC << 8); /* integer part, gives the MSB */ + tx_freq_frac = ((tx_freq_hz % (SX125x_32MHz_FRAC << 8)) << 8) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ + break; + default: + DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", radio_type); + return LGW_HAL_ERROR; + } + lgw_sx125x_reg_w(SX125x_REG_FRF_RX_MSB, 0xFF & rx_freq_int, rx); + lgw_sx125x_reg_w(SX125x_REG_FRF_RX_MID, 0xFF & (rx_freq_frac >> 8), rx); + lgw_sx125x_reg_w(SX125x_REG_FRF_RX_LSB, 0xFF & rx_freq_frac, rx); + lgw_sx125x_reg_w(SX125x_REG_FRF_TX_MSB, 0xFF & tx_freq_int, tx); + lgw_sx125x_reg_w(SX125x_REG_FRF_TX_MID, 0xFF & (tx_freq_frac >> 8), tx); + lgw_sx125x_reg_w(SX125x_REG_FRF_TX_LSB, 0xFF & tx_freq_frac, tx); + + /* Radio settings for calibration */ + //sx125x_reg_w(SX125x_RX_ANA_GAIN__LNA_ZIN, 1, rx); /* Default: 1 */ + //sx125x_reg_w(SX125x_RX_ANA_GAIN__BB_GAIN, 15, rx); /* Default: 15 */ + //sx125x_reg_w(SX125x_RX_ANA_GAIN__LNA_GAIN, 1, rx); /* Default: 1 */ + lgw_sx125x_reg_w(SX125x_REG_RX_BW__BB_BW, 0, rx); + lgw_sx125x_reg_w(SX125x_REG_RX_BW__ADC_TRIM, 6, rx); + //sx125x_reg_w(SX125x_RX_BW__ADC_BW, 7, rx); /* Default: 7 */ + lgw_sx125x_reg_w(SX125x_REG_RX_PLL_BW__PLL_BW, 0, rx); + lgw_sx125x_reg_w(SX125x_REG_TX_BW__PLL_BW, 0, tx); + //sx125x_reg_w(SX125x_TX_BW__ANA_BW, 0, tx); /* Default: 0 */ + lgw_sx125x_reg_w(SX125x_REG_TX_DAC_BW, 5, tx); + //sx125x_reg_w(SX125x_CLK_SELECT__DAC_CLK_SELECT, 0, tx); /* Use internal clock, in case no Tx connection from SX1302, Default: 0 */ + if (use_loopback == true) { + lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__DAC_GAIN, 3, tx); + lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__MIX_GAIN, 10, tx); //8 + lgw_sx125x_reg_w(SX125x_REG_CLK_SELECT__RF_LOOPBACK_EN, 1, tx); + lgw_sx125x_reg_w(SX125x_REG_MODE, 15, tx); + } else { + lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__DAC_GAIN, 3, tx); + lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__MIX_GAIN, 15, tx); + lgw_sx125x_reg_w(SX125x_REG_MODE, 3, rx); + lgw_sx125x_reg_w(SX125x_REG_MODE, 13, tx); + } + wait_ms(10); + lgw_sx125x_reg_r(SX125x_REG_MODE_STATUS__RX_PLL_LOCKED, &rx_pll_locked, rx); + lgw_sx125x_reg_r(SX125x_REG_MODE_STATUS__TX_PLL_LOCKED, &tx_pll_locked, tx); + if ((rx_pll_locked == 0) || (tx_pll_locked == 0)) { + DEBUG_MSG("ERROR: PLL failed to lock\n"); + return LGW_HAL_ERROR; + } + + /* Trig calibration */ + + /* Select radio to be connected to the Signal Analyzer (warning: RadioA:1, RadioB:0) */ + lgw_reg_w(SX1302_REG_RADIO_FE_SIG_ANA_CFG_RADIO_SEL, (rf_chain == 0) ? 1 : 0); + + /* Set calibration parameters */ + sx1302_agc_mailbox_write(2, rf_chain); /* Set RX test config: radioA:0 radioB:1 */ + sx1302_agc_mailbox_write(1, CAL_TX_TONE_FREQ_HZ * 64e-6); /* Set frequency */ + sx1302_agc_mailbox_write(0, CAL_TX_CORR_DURATION); + + sx1302_agc_mailbox_write(3, 0x00); + sx1302_agc_mailbox_write(3, 0x01); + sx1302_agc_wait_status(0x01); + + sx1302_agc_mailbox_write(3, 0x02); + sx1302_agc_wait_status(0x02); + + sx1302_agc_mailbox_write(3, 0x03); + sx1302_agc_wait_status(0x03); + + sx1302_agc_mailbox_write(2, 0); /* dec_gain (not used) */ + sx1302_agc_mailbox_write(1, rx_threshold); + + sx1302_agc_mailbox_write(3, 0x04); + + /* Get calibration results */ + sx1302_agc_wait_status(0x06); + uint8_t threshold, cal_dec_gain, rx_sig_1, rx_sig_0; + sx1302_agc_mailbox_read(3, &threshold); + sx1302_agc_mailbox_read(2, &cal_dec_gain); + sx1302_agc_mailbox_read(1, &rx_sig_1); + sx1302_agc_mailbox_read(0, &rx_sig_0); + DEBUG_PRINTF("threshold:%u, cal_dec_gain:%u, rx_sig:%u\n", threshold * 256, cal_dec_gain, rx_sig_1 * 256 + rx_sig_0); + sx1302_agc_mailbox_write(3, 0x06); + + sx1302_agc_wait_status(0x07); + uint8_t rx_img_init_0, rx_img_init_1, amp, phi; + sx1302_agc_mailbox_read(3, &rx_img_init_1); + sx1302_agc_mailbox_read(2, &rx_img_init_0); + sx1302_agc_mailbox_read(1, &); + sx1302_agc_mailbox_read(0, &phi); + DEBUG_PRINTF("rx_img_init_0:%u, rx_img_init_1:%u, amp:%d, phi:%d\n", rx_img_init_0, rx_img_init_1, (int8_t)amp, (int8_t)phi); + sx1302_agc_mailbox_write(3, 0x07); + + sx1302_agc_wait_status(0x08); + uint8_t rx_img_0, rx_img_1, rx_noise_raw_0, rx_noise_raw_1; + float rx_img, rx_noise_raw, rx_img_init, rx_sig; + sx1302_agc_mailbox_read(3, &rx_img_1); + sx1302_agc_mailbox_read(2, &rx_img_0); + sx1302_agc_mailbox_read(1, &rx_noise_raw_1); + sx1302_agc_mailbox_read(0, &rx_noise_raw_0); + DEBUG_PRINTF("rx_img_1:%u, rx_img_0:%u, rx_noise_raw_1:%u, rx_noise_raw_0:%u\n", rx_img_1, rx_img_0, rx_noise_raw_1, rx_noise_raw_0); + rx_sig = (float)rx_sig_1 * 256 + (float)rx_sig_0; + rx_noise_raw = (float)rx_noise_raw_1 * 256 + (float)rx_noise_raw_0; + rx_img_init = (float)rx_img_init_1 * 256 + (float)rx_img_init_0; + rx_img = (float)rx_img_1 * 256 + (float)rx_img_0; + DEBUG_PRINTF("rx_img:%u, rx_noise_raw:%u\n", (uint16_t)rx_img, (uint16_t)rx_noise_raw); + sx1302_agc_mailbox_write(3, 0x08); + + res->amp = (int8_t)amp; + res->phi = (int8_t)phi; + res->snr = (uint16_t)(20 * log10(rx_sig/rx_noise_raw)); + res->rej_init = (uint16_t)(20 * log10(rx_sig/rx_img_init)); + res->rej = (uint16_t)(20 * log10(rx_sig/rx_img)); + DEBUG_PRINTF("snr:%u, rej:%u, rej_init:%u\n", res->snr, res->rej, res->rej_init); + + /* Wait for calibration to be completed */ + DEBUG_MSG(" CAL: waiting for RX calibration to complete...\n"); + sx1302_agc_wait_status((rf_chain == 0) ? 0x11 : 0x22); + DEBUG_MSG("CAL: RX Calibration Done\n"); + + printf("%s, RESULT: rf_chain:%u amp:%d phi:%d\n", __FUNCTION__, rf_chain, res->amp, res->phi); + + return LGW_HAL_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx125x_cal_tx_dc_offset(uint8_t rf_chain, uint32_t freq_hz, uint8_t dac_gain, uint8_t mix_gain, uint8_t radio_type, struct lgw_sx125x_cal_tx_result_s * res) { + + uint32_t rx_freq_hz, tx_freq_hz; + uint32_t rx_freq_int, rx_freq_frac; + uint32_t tx_freq_int, tx_freq_frac; + uint8_t rx_pll_locked, tx_pll_locked; + uint16_t reg; + uint8_t tx_threshold = 64; + int i; + + printf("\n%s: rf_chain:%u, freq_hz:%u, dac_gain:%u, mix_gain:%u, radio_type:%d\n", __FUNCTION__, rf_chain, freq_hz, dac_gain, mix_gain, radio_type); + + /* Set PLL frequencies */ + rx_freq_hz = freq_hz - CAL_TX_TONE_FREQ_HZ; + tx_freq_hz = freq_hz; + switch (radio_type) { + case LGW_RADIO_TYPE_SX1255: + rx_freq_int = rx_freq_hz / (SX125x_32MHz_FRAC << 7); /* integer part, gives the MSB */ + rx_freq_frac = ((rx_freq_hz % (SX125x_32MHz_FRAC << 7)) << 9) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ + tx_freq_int = tx_freq_hz / (SX125x_32MHz_FRAC << 7); /* integer part, gives the MSB */ + tx_freq_frac = ((tx_freq_hz % (SX125x_32MHz_FRAC << 7)) << 9) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ + break; + case LGW_RADIO_TYPE_SX1257: + rx_freq_int = rx_freq_hz / (SX125x_32MHz_FRAC << 8); /* integer part, gives the MSB */ + rx_freq_frac = ((rx_freq_hz % (SX125x_32MHz_FRAC << 8)) << 8) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ + tx_freq_int = tx_freq_hz / (SX125x_32MHz_FRAC << 8); /* integer part, gives the MSB */ + tx_freq_frac = ((tx_freq_hz % (SX125x_32MHz_FRAC << 8)) << 8) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ + break; + default: + DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", radio_type); + return LGW_HAL_ERROR; + } + lgw_sx125x_reg_w(SX125x_REG_FRF_RX_MSB, 0xFF & rx_freq_int, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_FRF_RX_MID, 0xFF & (rx_freq_frac >> 8), rf_chain); + lgw_sx125x_reg_w(SX125x_REG_FRF_RX_LSB, 0xFF & rx_freq_frac, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_FRF_TX_MSB, 0xFF & tx_freq_int, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_FRF_TX_MID, 0xFF & (tx_freq_frac >> 8), rf_chain); + lgw_sx125x_reg_w(SX125x_REG_FRF_TX_LSB, 0xFF & tx_freq_frac, rf_chain); + + /* Radio settings for calibration */ + //lgw_sx125x_reg_w(SX125x_RX_ANA_GAIN__LNA_ZIN, 1, rf_chain); /* Default: 1 */ + //lgw_sx125x_reg_w(SX125x_RX_ANA_GAIN__BB_GAIN, 15, rf_chain); /* Default: 15 */ + //lgw_sx125x_reg_w(SX125x_RX_ANA_GAIN__LNA_GAIN, 1, rf_chain); /* Default: 1 */ + lgw_sx125x_reg_w(SX125x_REG_RX_BW__BB_BW, 0, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_RX_BW__ADC_TRIM, 6, rf_chain); + //lgw_sx125x_reg_w(SX125x_RX_BW__ADC_BW, 7, rf_chain); /* Default: 7 */ + lgw_sx125x_reg_w(SX125x_REG_RX_PLL_BW__PLL_BW, 0, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_TX_BW__PLL_BW, 0, rf_chain); + //lgw_sx125x_reg_w(SX125x_TX_BW__ANA_BW, 0, rf_chain); /* Default: 0 */ + lgw_sx125x_reg_w(SX125x_REG_TX_DAC_BW, 5, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_CLK_SELECT__DAC_CLK_SELECT, 1, rf_chain); /* Use external clock from SX1302 */ + lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__DAC_GAIN, dac_gain, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__MIX_GAIN, mix_gain, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_CLK_SELECT__RF_LOOPBACK_EN, 1, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_MODE, 15, rf_chain); + wait_ms(1); + lgw_sx125x_reg_r(SX125x_REG_MODE_STATUS__RX_PLL_LOCKED, &rx_pll_locked, rf_chain); + lgw_sx125x_reg_r(SX125x_REG_MODE_STATUS__TX_PLL_LOCKED, &tx_pll_locked, rf_chain); + if ((rx_pll_locked == 0) || (tx_pll_locked == 0)) { + DEBUG_MSG("ERROR: PLL failed to lock\n"); + return LGW_HAL_ERROR; + } + + /* Trig calibration */ + + /* Select radio to be connected to the Signal Analyzer (warning: RadioA:1, RadioB:0) */ + lgw_reg_w(SX1302_REG_RADIO_FE_SIG_ANA_CFG_RADIO_SEL, (rf_chain == 0) ? 1 : 0); + + reg = REG_SELECT(rf_chain, SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_MODE, + SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_MODE); + lgw_reg_w(reg, 0); + + reg = REG_SELECT(rf_chain, SX1302_REG_TX_TOP_A_TX_TRIG_TX_TRIG_IMMEDIATE, + SX1302_REG_TX_TOP_B_TX_TRIG_TX_TRIG_IMMEDIATE); + lgw_reg_w(reg, 1); + lgw_reg_w(reg, 0); + + reg = REG_SELECT(rf_chain, SX1302_REG_RADIO_FE_CTRL0_RADIO_A_DC_NOTCH_EN, + SX1302_REG_RADIO_FE_CTRL0_RADIO_B_DC_NOTCH_EN); + lgw_reg_w(reg, 1); + +#if TX_CALIB_DONE_BY_HAL /* For debug */ + + lgw_reg_w(SX1302_REG_RADIO_FE_SIG_ANA_CFG_FORCE_HAL_CTRL, 1); + agc_cal_tx_dc_offset(rf_chain, CAL_TX_TONE_FREQ_HZ * 64e-6, rf_rx_image_amp[rf_chain], rf_rx_image_phi[rf_chain], tx_threshold, 0, &(res->offset_i), &(res->offset_q), &(res->rej)); + lgw_reg_w(SX1302_REG_RADIO_FE_SIG_ANA_CFG_FORCE_HAL_CTRL, 0); + +#else + + /* Set calibration parameters */ + sx1302_agc_mailbox_write(2, rf_chain + 2); /* Set TX test config: radioA:2 radioB:3 */ + sx1302_agc_mailbox_write(1, CAL_TX_TONE_FREQ_HZ * 64e-6); /* Set frequency */ + sx1302_agc_mailbox_write(0, 0); /* correlation duration: 0:1ms, 1:2ms, 2:4ms, 3:8ms) */ + + sx1302_agc_mailbox_write(3, 0x00); /* sync */ + sx1302_agc_mailbox_write(3, 0x01); /* sync */ + sx1302_agc_wait_status(0x01); + + sx1302_agc_mailbox_write(2, rf_rx_image_amp[rf_chain]); /* amp */ + sx1302_agc_mailbox_write(1, rf_rx_image_phi[rf_chain]); /* phi */ + + sx1302_agc_mailbox_write(3, 0x02); /* sync */ + sx1302_agc_wait_status(0x02); + + sx1302_agc_mailbox_write(2, 0); /* i offset init */ + sx1302_agc_mailbox_write(1, 0); /* q offset init */ + + sx1302_agc_mailbox_write(3, 0x03); /* sync */ + sx1302_agc_wait_status(0x03); + + sx1302_agc_mailbox_write(2, 0); + sx1302_agc_mailbox_write(1, tx_threshold); + + sx1302_agc_mailbox_write(3, 0x04); /* sync */ + + /* Get calibration results */ + sx1302_agc_wait_status(0x06); + uint8_t threshold, cal_dec_gain, tx_sig_0, tx_sig_1; + sx1302_agc_mailbox_read(3, &threshold); + sx1302_agc_mailbox_read(2, &cal_dec_gain); + sx1302_agc_mailbox_read(1, &tx_sig_1); + sx1302_agc_mailbox_read(0, &tx_sig_0); + DEBUG_PRINTF("threshold:%u, cal_dec_gain:%u, tx_sig:%u\n", threshold * 256, cal_dec_gain, tx_sig_0 * 256 + tx_sig_1); + sx1302_agc_mailbox_write(3, 0x06); /* sync */ + + sx1302_agc_wait_status(0x07); + uint8_t tx_dc_0, tx_dc_1, offset_i, offset_q; + float tx_sig, tx_dc; + sx1302_agc_mailbox_read(3, &tx_dc_1); + sx1302_agc_mailbox_read(2, &tx_dc_0); + sx1302_agc_mailbox_read(1, &offset_i); + sx1302_agc_mailbox_read(0, &offset_q); + tx_sig = (float)tx_sig_1 * 256 + (float)tx_sig_0; + tx_dc = (float)tx_dc_1 * 256 + (float)tx_dc_0; + res->rej = (uint16_t)(20 * log10(tx_sig/tx_dc)); + res->offset_i = (int8_t)offset_i; + res->offset_q = (int8_t)offset_q; + DEBUG_PRINTF("tx_dc:%u, offset_i:%d, offset_q:%d\n", tx_dc_0 * 256 + tx_dc_1, (int8_t)offset_i, (int8_t)offset_q); + sx1302_agc_mailbox_write(3, 0x07); /* sync */ + + /* -----------------------------------------------*/ + /* DEBUG: Get IQ offsets selected for iterations */ + uint8_t index[12]; + + sx1302_agc_wait_status(0x08); + sx1302_agc_mailbox_read(3, &index[0]); + sx1302_agc_mailbox_read(2, &index[1]); + sx1302_agc_mailbox_read(1, &index[2]); + sx1302_agc_mailbox_read(0, &index[3]); + sx1302_agc_mailbox_write(3, 0x08); /* sync */ + + sx1302_agc_wait_status(0x09); + sx1302_agc_mailbox_read(3, &index[4]); + sx1302_agc_mailbox_read(2, &index[5]); + sx1302_agc_mailbox_read(1, &index[6]); + sx1302_agc_mailbox_read(0, &index[7]); + sx1302_agc_mailbox_write(3, 0x09); /* sync */ + + sx1302_agc_wait_status(0x0a); + sx1302_agc_mailbox_read(3, &index[8]); + sx1302_agc_mailbox_read(2, &index[9]); + sx1302_agc_mailbox_read(1, &index[10]); + sx1302_agc_mailbox_read(0, &index[11]); + sx1302_agc_mailbox_write(3, 0x0a); /* sync */ + +#if DEBUG_CAL == 1 + int16_t lut_calib[9] = {64, 43, 28, 19, 13, 8, 6, 4, 2}; + int16_t offset_i_tmp = 0; + int16_t offset_q_tmp = 0; + + printf("IQ sequence:"); + for (i = 0; i < 9; i++) { + if (index[i] == 0) { + offset_i_tmp = offset_i_tmp + 0; + offset_q_tmp = offset_q_tmp + 0; + + }else if(index[i] == 1) { + offset_i_tmp = offset_i_tmp + lut_calib[i]; + offset_q_tmp = offset_q_tmp + lut_calib[i]; + }else if(index[i] == 2) { + offset_i_tmp = offset_i_tmp + lut_calib[i]; + offset_q_tmp = offset_q_tmp - lut_calib[i]; + }else if(index[i] == 3) { + offset_i_tmp = offset_i_tmp - lut_calib[i]; + offset_q_tmp = offset_q_tmp + lut_calib[i]; + }else if(index[i] == 4) { + offset_i_tmp = offset_i_tmp - lut_calib[i]; + offset_q_tmp = offset_q_tmp - lut_calib[i]; + } + printf("i:%d q:%d\n", offset_i_tmp, offset_q_tmp); + } + printf("\n"); + +#endif /* DEBUG_CAL */ + /* -----------------------------------------------*/ + + /* -----------------------------------------------*/ + /* DEBUG: Get TX_SIG returned by siognal analyzer */ + uint8_t msb[40]; + uint8_t lsb[40]; + + for (i = 0; i < 20; i++) { + sx1302_agc_wait_status(0x0c + i); + sx1302_agc_mailbox_read(3, &msb[2*i]); + sx1302_agc_mailbox_read(2, &lsb[2*i]); + sx1302_agc_mailbox_read(1, &msb[2*i+1]); + sx1302_agc_mailbox_read(0, &lsb[2*i+1]); + sx1302_agc_mailbox_write(3, 0x0c + i); /* sync */ + } + sx1302_agc_wait_status(0x0c + 20); + +#if DEBUG_CAL == 1 + printf("TX_SIG values returned by signal analyzer:\n"); + for (i = 0; i < 40; i++) { + if (i%5 == 0) { + printf("\n"); + } + printf("%u ", msb[i] * 256 + lsb[i]); + } + printf("\n"); +#endif /* DEBUG_CAL */ + + sx1302_agc_mailbox_write(3, 0x0c + 20); /* sync */ + /* -----------------------------------------------*/ + + printf("%s: RESULT: offset_i:%d offset_q:%d rej:%u\n", __FUNCTION__, res->offset_i, res->offset_q, res->rej); + + /* Wait for calibration to be completed */ + DEBUG_MSG("waiting for TX calibration to complete...\n"); + sx1302_agc_wait_status((rf_chain == 0) ? 0x33 : 0x44); + +#endif /* TX_CALIB_DONE_BY_HAL */ + + DEBUG_MSG("TX Calibration Done\n"); + + return LGW_HAL_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void cal_rx_result_init(struct lgw_sx125x_cal_rx_result_s *res_rx_min, struct lgw_sx125x_cal_rx_result_s *res_rx_max) { + res_rx_min->amp = 31; + res_rx_min->phi = 31; + res_rx_min->rej = 255; + res_rx_min->rej_init = 255; + res_rx_min->snr = 255; + + res_rx_max->amp = -32; + res_rx_max->phi = -32; + res_rx_max->rej = 0; + res_rx_max->rej_init = 0; + res_rx_max->snr = 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void cal_rx_result_sort(struct lgw_sx125x_cal_rx_result_s *res_rx, struct lgw_sx125x_cal_rx_result_s *res_rx_min, struct lgw_sx125x_cal_rx_result_s *res_rx_max) { + if (res_rx->amp < res_rx_min->amp) + res_rx_min->amp = res_rx->amp; + if (res_rx->phi < res_rx_min->phi) + res_rx_min->phi = res_rx->phi; + if (res_rx->rej < res_rx_min->rej) + res_rx_min->rej = res_rx->rej; + if (res_rx->rej_init < res_rx_min->rej_init) + res_rx_min->rej_init = res_rx->rej_init; + if (res_rx->snr < res_rx_min->snr) + res_rx_min->snr = res_rx->snr; + + if (res_rx->amp > res_rx_max->amp) + res_rx_max->amp = res_rx->amp; + if (res_rx->phi > res_rx_max->phi) + res_rx_max->phi = res_rx->phi; + if (res_rx->rej > res_rx_max->rej) + res_rx_max->rej = res_rx->rej; + if (res_rx->rej_init > res_rx_max->rej_init) + res_rx_max->rej_init = res_rx->rej_init; + if (res_rx->snr > res_rx_max->snr) + res_rx_max->snr = res_rx->snr; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +bool cal_rx_result_assert(struct lgw_sx125x_cal_rx_result_s *res_rx_min, struct lgw_sx125x_cal_rx_result_s *res_rx_max) { + if ( ((res_rx_max->amp - res_rx_min->amp) > 4) + || ((res_rx_max->phi - res_rx_min->phi) > 4) + || (res_rx_min->rej < 50) + || (res_rx_min->snr < 50) ) + return false; + else + return true; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void cal_tx_result_init(struct lgw_sx125x_cal_tx_result_s *res_tx_min, struct lgw_sx125x_cal_tx_result_s *res_tx_max) { + res_tx_min->offset_i = 127; + res_tx_min->offset_q = 127; + res_tx_min->rej = 255; + res_tx_min->sig = 255; + + res_tx_max->offset_i = -128; + res_tx_max->offset_q = -128; + res_tx_max->rej = 0; + res_tx_max->sig = 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void cal_tx_result_sort(struct lgw_sx125x_cal_tx_result_s *res_tx, struct lgw_sx125x_cal_tx_result_s *res_tx_min, struct lgw_sx125x_cal_tx_result_s *res_tx_max) { + if (res_tx->offset_i < res_tx_min->offset_i) + res_tx_min->offset_i = res_tx->offset_i; + if (res_tx->offset_q < res_tx_min->offset_q) + res_tx_min->offset_q = res_tx->offset_q; + if (res_tx->rej < res_tx_min->rej) + res_tx_min->rej = res_tx->rej; + if (res_tx->sig < res_tx_min->sig) + res_tx_min->sig = res_tx->sig; + + if (res_tx->offset_i > res_tx_max->offset_i) + res_tx_max->offset_i = res_tx->offset_i; + if (res_tx->offset_q > res_tx_max->offset_q) + res_tx_max->offset_q = res_tx->offset_q; + if (res_tx->rej > res_tx_max->rej) + res_tx_max->rej = res_tx->rej; + if (res_tx->sig > res_tx_max->sig) + res_tx_max->sig = res_tx->sig; +} + +bool cal_tx_result_assert(struct lgw_sx125x_cal_tx_result_s *res_tx_min, struct lgw_sx125x_cal_tx_result_s *res_tx_max) { + if ( ((res_tx_max->offset_i - res_tx_min->offset_i) > 4) + || ((res_tx_max->offset_q - res_tx_min->offset_q) > 4) + || (res_tx_min->rej < 10) ) + return false; + else + return true; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +#if TX_CALIB_DONE_BY_HAL + +int8_t clip_8b(int8_t val1, int8_t val2) { + int16_t a, b; + + a = (int16_t)val1; + b = (int16_t)val2; + + if ( (a + b) > 127 ) { + return 127; + } else if ( (a+b) < -128 ) { + return -128; + } else { + return (int8_t)(a+b); + } +} + +/* This functions implements what is being done by CAL fw for TX calibration */ +void agc_cal_tx_dc_offset(uint8_t rf_chain, signed char freq, char amp_hal, char phi_hal, char level_reqired, char precision, int8_t * offset_i_res, int8_t * offset_q_res, uint16_t * rej) { + signed char offset_i_set[9]; + signed char offset_q_set[9]; + signed char offset_i; + signed char offset_q; + const signed char span[] = {64, 43, 28, 19, 13, 8, 6, 4, 2}; + char dec_gain; + char i, j; // loop variables + char idx; // max/min variables + uint16_t reg; + int32_t abs_corr_max_i16; + int32_t abs_corr_min_i16; + int32_t abs_corr_i16; + int32_t tx_sig_i16; + int32_t tx_dc_i16; + int DEC_GAIN_MAX = 11; + int DEC_GAIN_MIN = 7; + int32_t val; + int32_t abs_lsb, abs_msb; + + reg = REG_SELECT(rf_chain, SX1302_REG_RADIO_FE_IQ_COMP_AMP_COEFF_RADIO_A_AMP_COEFF, + SX1302_REG_RADIO_FE_IQ_COMP_AMP_COEFF_RADIO_B_AMP_COEFF); + lgw_reg_w(reg, amp_hal); + + reg = REG_SELECT(rf_chain, SX1302_REG_RADIO_FE_IQ_COMP_PHI_COEFF_RADIO_A_PHI_COEFF, + SX1302_REG_RADIO_FE_IQ_COMP_PHI_COEFF_RADIO_B_PHI_COEFF); + lgw_reg_w(reg, phi_hal); + + lgw_reg_w(SX1302_REG_RADIO_FE_SIG_ANA_FREQ_FREQ, freq); + + lgw_reg_w(SX1302_REG_RADIO_FE_SIG_ANA_CFG_DURATION, precision); + lgw_reg_w(SX1302_REG_RADIO_FE_SIG_ANA_CFG_EN, 1); + + // Set dec gain and signal analyser according to potential maximum DC level + offset_i_set[0] = 0; + offset_q_set[0] = 0; + offset_i_set[1] = -span[0]; + offset_q_set[1] = -span[0]; + offset_i_set[2] = -span[0]; + offset_q_set[2] = span[0]; + offset_i_set[3] = span[0]; + offset_q_set[3] = -span[0]; + offset_i_set[4] = span[0]; + offset_q_set[4] = span[0]; + for (i = DEC_GAIN_MAX; i >= DEC_GAIN_MIN; i--) { + dec_gain = i; + /* ------------ */ + reg = REG_SELECT(rf_chain, SX1302_REG_RADIO_FE_CTRL0_RADIO_A_HOST_FILTER_GAIN, + SX1302_REG_RADIO_FE_CTRL0_RADIO_B_HOST_FILTER_GAIN); + lgw_reg_w(reg, dec_gain); + + /* ------------ */ + reg = REG_SELECT(rf_chain, SX1302_REG_TX_TOP_A_TX_RFFE_IF_I_OFFSET_I_OFFSET, + SX1302_REG_TX_TOP_B_TX_RFFE_IF_I_OFFSET_I_OFFSET); + lgw_reg_w(reg, (int8_t)offset_i_set[0]); + + reg = REG_SELECT(rf_chain, SX1302_REG_TX_TOP_A_TX_RFFE_IF_Q_OFFSET_Q_OFFSET, + SX1302_REG_TX_TOP_B_TX_RFFE_IF_Q_OFFSET_Q_OFFSET); + lgw_reg_w(reg, (int8_t)offset_q_set[0]); + + /* ------------ */ + lgw_reg_w(SX1302_REG_RADIO_FE_SIG_ANA_CFG_START, 0); + lgw_reg_w(SX1302_REG_RADIO_FE_SIG_ANA_CFG_START, 1); + + do { + lgw_reg_r(SX1302_REG_RADIO_FE_SIG_ANA_CFG_VALID, &val); + wait_ms(1); + } while (val == 0); + + lgw_reg_r(SX1302_REG_RADIO_FE_SIG_ANA_ABS_LSB_CORR_ABS_OUT, &abs_lsb); + lgw_reg_r(SX1302_REG_RADIO_FE_SIG_ANA_ABS_MSB_CORR_ABS_OUT, &abs_msb); + /* ------------ */ + + abs_corr_max_i16 = abs_msb * 256 + abs_lsb; + + idx = 0; + for (j = 1; j < 5; j++) { + reg = REG_SELECT(rf_chain, SX1302_REG_TX_TOP_A_TX_RFFE_IF_I_OFFSET_I_OFFSET, + SX1302_REG_TX_TOP_B_TX_RFFE_IF_I_OFFSET_I_OFFSET); + lgw_reg_w(reg, (int8_t)offset_i_set[j]); + + reg = REG_SELECT(rf_chain, SX1302_REG_TX_TOP_A_TX_RFFE_IF_Q_OFFSET_Q_OFFSET, + SX1302_REG_TX_TOP_B_TX_RFFE_IF_Q_OFFSET_Q_OFFSET); + lgw_reg_w(reg, (int8_t)offset_q_set[j]); + + lgw_reg_w(SX1302_REG_RADIO_FE_SIG_ANA_CFG_START, 0); + lgw_reg_w(SX1302_REG_RADIO_FE_SIG_ANA_CFG_START, 1); + + do { + lgw_reg_r(SX1302_REG_RADIO_FE_SIG_ANA_CFG_VALID, &val); + wait_ms(1); + } while (val == 0); + + lgw_reg_r(SX1302_REG_RADIO_FE_SIG_ANA_ABS_LSB_CORR_ABS_OUT, &abs_lsb); + lgw_reg_r(SX1302_REG_RADIO_FE_SIG_ANA_ABS_MSB_CORR_ABS_OUT, &abs_msb); + + abs_corr_i16 = abs_msb * 256 + abs_lsb; + + if (abs_corr_i16 > abs_corr_max_i16) { + abs_corr_max_i16 = abs_corr_i16; + idx = j; + } + } + + if (abs_corr_max_i16 > (level_reqired * 256)) { + break; + } + } + + printf("dec_gain:%d\n", dec_gain); + + // store the max results + tx_sig_i16 = abs_corr_max_i16; + printf("tx_sig:%d\n", tx_sig_i16); + + // Calbration algorithm + offset_i = 0; + offset_q = 0; + for (i = 0; i /* C99 types */ +#include /* bool type */ +#include /* printf fprintf */ +#include /* memcmp */ +#include + +#include "loragw_aux.h" +#include "loragw_reg.h" +#include "loragw_hal.h" +#include "loragw_debug.h" + +#include "tinymt32.h" + +/* -------------------------------------------------------------------------- */ +/* --- DEBUG CONSTANTS ------------------------------------------------------ */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS & TYPES -------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +static tinymt32_t tinymt; + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +void dbg_init_random(void) { + tinymt.mat1 = 0x8f7011ee; + tinymt.mat2 = 0xfc78ff1f; + tinymt.tmat = 0x3793fdff; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void dbg_init_gpio(void) { + /* Select GPIO_6 to be controlled by HOST */ + lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_6_SELECTION, 0); + /* Configure it as an OUTPUT */ + lgw_reg_w(SX1302_REG_GPIO_GPIO_DIR_L_DIRECTION, 0xFF); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void dbg_toggle_gpio(void) { + /* Set GPIO_6 to high */ + lgw_reg_w(SX1302_REG_GPIO_GPIO_OUT_L_OUT_VALUE, 64); + /* Set GPIO_6 to low */ + lgw_reg_w(SX1302_REG_GPIO_GPIO_OUT_L_OUT_VALUE, 0); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void dbg_log_buffer_to_file(FILE * file, uint8_t * buffer, uint16_t size) { + int i; + char stat_timestamp[24]; + time_t t; + + t = time(NULL); + strftime(stat_timestamp, sizeof stat_timestamp, "%F %T %Z", gmtime(&t)); + fprintf(file, "---------(%s)------------\n", stat_timestamp); + for (i = 0; i < size; i++) { + fprintf(file, "%02X ", buffer[i]); + } + fprintf(file, "\n"); + + fflush(file); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void dbg_log_payload_diff_to_file(FILE * file, uint8_t * buffer1, uint8_t * buffer2, uint16_t size) { + int i, j; + uint16_t nb_bits_diff = 0; + uint8_t debug_payload_diff[255]; + + fprintf(file, "Diff: "); + /* bit comparison of payloads */ + for (j = 0; j < size; j++) { + debug_payload_diff[j] = buffer1[j] ^ buffer2[j]; + fprintf(file, "%02X ", debug_payload_diff[j]); + } + fprintf(file, "\n"); + + /* count number of bits flipped, and display bit by bit */ + for (j = 0; j < size; j++) { + for (i = 7; i >= 0; i--) { + fprintf(file, "%u", TAKE_N_BITS_FROM(debug_payload_diff[j], i, 1)); + if (TAKE_N_BITS_FROM(debug_payload_diff[j], i, 1) == 1) { + nb_bits_diff += 1; + } + } + fprintf(file, " "); + } + fprintf(file, "\n"); + fprintf(file, "%u bits flipped\n", nb_bits_diff); + + fflush(file); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void dbg_generate_random_payload(uint32_t pkt_cnt, uint8_t * buffer_expected, uint8_t size) { + int k; + + /* construct payload we should get for this packet counter */ + tinymt32_init(&tinymt, (int)pkt_cnt); + buffer_expected[4] = (uint8_t)(pkt_cnt >> 24); + buffer_expected[5] = (uint8_t)(pkt_cnt >> 16); + buffer_expected[6] = (uint8_t)(pkt_cnt >> 8); + buffer_expected[7] = (uint8_t)(pkt_cnt >> 0); + tinymt32_generate_uint32(&tinymt); /* dummy: for sync with random size generation */ + for (k = 8; k < (int)size; k++) { + buffer_expected[k] = (uint8_t)tinymt32_generate_uint32(&tinymt); + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int dbg_check_payload(struct lgw_conf_debug_s * context, FILE * file, uint8_t * payload_received, uint8_t size, uint8_t ref_payload_idx, uint8_t sf) { + int k; + uint32_t debug_payload_cnt; + + /* If the 4 first bytes of received payload match with the expected ones, go on with comparison */ + if (memcmp((void*)payload_received, (void*)(context->ref_payload[ref_payload_idx].payload), 4) == 0) { + /* get counter to initialize random seed */ + debug_payload_cnt = (unsigned int)(payload_received[4] << 24) | (unsigned int)(payload_received[5] << 16) | (unsigned int)(payload_received[6] << 8) | (unsigned int)(payload_received[7] << 0); + + /* check if we missed some packets */ + if (debug_payload_cnt > (context->ref_payload[ref_payload_idx].prev_cnt + 1)) { + printf("ERROR: 0x%08X missed %u pkt before %u (SF%u, size:%u)\n", context->ref_payload[ref_payload_idx].id, debug_payload_cnt - context->ref_payload[ref_payload_idx].prev_cnt - 1, debug_payload_cnt, sf, size); + if (file != NULL) { + fprintf(file, "ERROR: 0x%08X missed %u pkt before %u (SF%u, size:%u)\n", context->ref_payload[ref_payload_idx].id, debug_payload_cnt - context->ref_payload[ref_payload_idx].prev_cnt - 1, debug_payload_cnt, sf, size); + fflush(file); + } + } else if (debug_payload_cnt < context->ref_payload[ref_payload_idx].prev_cnt) { + if (file != NULL) { + fprintf(file, "INFO: 0x%08X got missing pkt %u (SF%u, size:%u) ?\n", context->ref_payload[ref_payload_idx].id, debug_payload_cnt, sf, size); + fflush(file); + } + } else { +#if 0 + if (file != NULL) { + fprintf(file, "0x%08X %u (SF%u, size:%u)\n", context.ref_payload[ref_payload_idx].id, debug_payload_cnt, sf, size); + } +#endif + } + context->ref_payload[ref_payload_idx].prev_cnt = debug_payload_cnt; + + /* generate the random payload which is expected for this packet count */ + dbg_generate_random_payload(debug_payload_cnt, context->ref_payload[ref_payload_idx].payload, size); + + /* compare expected with received */ + if (memcmp((void *)payload_received, (void *)(context->ref_payload[ref_payload_idx].payload), size) != 0) { + if (file != NULL) { + fprintf(file, "RECEIVED:"); + for (k = 0; k < (int)size; k++) { + fprintf(file, "%02X ", payload_received[k]); + } + fprintf(file, "\n"); + fprintf(file, "EXPECTED:"); + for (k = 0; k < (int)size; k++) { + fprintf(file, "%02X ", context->ref_payload[ref_payload_idx].payload[k]); + } + fprintf(file, "\n"); + } + return -1; + } else { + return 1; /* matches */ + } + } + + return 0; /* ignored */ +} diff --git a/libloragw/src/loragw_gps.c b/libloragw/src/loragw_gps.c new file mode 100644 index 0000000..847dafe --- /dev/null +++ b/libloragw/src/loragw_gps.c @@ -0,0 +1,837 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Library of functions to manage a GNSS module (typically GPS) for accurate + timestamping of packets and synchronisation of gateways. + A limited set of module brands/models are supported. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#define _GNU_SOURCE /* needed for qsort_r to be defined */ +#include /* C99 types */ +#include /* bool type */ +#include /* printf fprintf */ +#include /* memcpy */ +#include + +#include /* struct timespec */ +#include /* open */ +#include /* tcflush */ +#include /* modf */ + +#include + +#include "loragw_gps.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_GPS == 1 + #define DEBUG_MSG(args...) fprintf(stderr, args) + #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) + #define DEBUG_ARRAY(a,b,c) for(a=0;a= buff_size) { + DEBUG_MSG("Maximum length reached for nmea_checksum\n"); + return -1; + } + } + + /* Convert checksum value to 2 hexadecimal characters */ + checksum[0] = nibble_to_hexchar(check_num / 16); /* upper nibble */ + checksum[1] = nibble_to_hexchar(check_num % 16); /* lower nibble */ + + return i + 1; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +static char nibble_to_hexchar(uint8_t a) { + if (a < 10) { + return '0' + a; + } else if (a < 16) { + return 'A' + (a-10); + } else { + return '?'; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* +Calculate the checksum of a NMEA frame and compare it to the checksum that is +present at the end of it. +Return true if it matches +*/ +static bool validate_nmea_checksum(const char *serial_buff, int buff_size) { + int checksum_index; + char checksum[2]; /* 2 characters to calculate NMEA checksum */ + + checksum_index = nmea_checksum(serial_buff, buff_size, checksum); + + /* could we calculate a verification checksum ? */ + if (checksum_index < 0) { + DEBUG_MSG("ERROR: IMPOSSIBLE TO PARSE NMEA SENTENCE\n"); + return false; + } + + /* check if there are enough char in the serial buffer to read checksum */ + if (checksum_index >= (buff_size - 2)) { + DEBUG_MSG("ERROR: IMPOSSIBLE TO READ NMEA SENTENCE CHECKSUM\n"); + return false; + } + + /* check the checksum per se */ + if ((serial_buff[checksum_index] == checksum[0]) && (serial_buff[checksum_index+1] == checksum[1])) { + return true; + } else { + DEBUG_MSG("ERROR: NMEA CHECKSUM %c%c DOESN'T MATCH VERIFICATION CHECKSUM %c%c\n", serial_buff[checksum_index], serial_buff[checksum_index+1], checksum[0], checksum[1]); + return false; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* +Return true if the "label" string (can contain wildcard characters) matches +the begining of the "s" string +*/ +static bool match_label(const char *s, char *label, int size, char wildcard) { + int i; + + for (i=0; i < size; i++) { + if (label[i] == wildcard) continue; + if (label[i] != s[i]) return false; + } + return true; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* +Chop a string into smaller strings +Replace every separator in the input character buffer by a null character so +that all s[index] are valid strings. +Populate an array of integer 'idx_ary' representing indexes of token in the +string. +buff_size and max_idx are there to prevent segfaults. +Return the number of token found (number of idx_ary filled). +*/ +int str_chop(char *s, int buff_size, char separator, int *idx_ary, int max_idx) { + int i = 0; /* index in the string */ + int j = 0; /* index in the result array */ + + if ((s == NULL) || (buff_size < 0) || (separator == 0) || (idx_ary == NULL) || (max_idx < 0)) { + /* unsafe to do anything */ + return -1; + } + if ((buff_size == 0) || (max_idx == 0)) { + /* nothing to do */ + return 0; + } + s[buff_size - 1] = 0; /* add string terminator at the end of the buffer, just to be sure */ + idx_ary[j] = 0; + j += 1; + /* loop until string terminator is reached */ + while (s[i] != 0) { + if (s[i] == separator) { + s[i] = 0; /* replace separator by string terminator */ + if (j >= max_idx) { /* no more room in the index array */ + return j; + } + idx_ary[j] = i+1; /* next token start after replaced separator */ + ++j; + } + ++i; + } + return j; +} + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int lgw_gps_enable(char *tty_path, char *gps_family, speed_t target_brate, int *fd_ptr) { + int i; + struct termios ttyopt; /* serial port options */ + int gps_tty_dev; /* file descriptor to the serial port of the GNSS module */ + uint8_t ubx_cmd_timegps[UBX_MSG_NAVTIMEGPS_LEN] = { + 0xB5, 0x62, /* UBX Sync Chars */ + 0x06, 0x01, /* CFG-MSG Class/ID */ + 0x08, 0x00, /* Payload length */ + 0x01, 0x20, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, /* Enable NAV-TIMEGPS output on serial */ + 0x32, 0x94 }; /* Checksum */ + ssize_t num_written; + + /* check input parameters */ + CHECK_NULL(tty_path); + CHECK_NULL(fd_ptr); + + /* open TTY device */ + gps_tty_dev = open(tty_path, O_RDWR | O_NOCTTY); + if (gps_tty_dev <= 0) { + DEBUG_MSG("ERROR: TTY PORT FAIL TO OPEN, CHECK PATH AND ACCESS RIGHTS\n"); + return LGW_GPS_ERROR; + } + *fd_ptr = gps_tty_dev; + + /* manage the different GPS modules families */ + if (gps_family == NULL) { + DEBUG_MSG("WARNING: this version of GPS module may not be supported\n"); + } else if (strncmp(gps_family, "ubx7", 4) != 0) { + /* The current implementation relies on proprietary messages from U-Blox */ + /* GPS modules (UBX, NAV-TIMEGPS...) and has only be tested with a u-blox 7. */ + /* Those messages allow to get NATIVE GPS time (no leap seconds) required */ + /* for class-B handling and GPS synchronization */ + /* see lgw_parse_ubx() function for details */ + DEBUG_MSG("WARNING: this version of GPS module may not be supported\n"); + } + + /* manage the target bitrate */ + if (target_brate != 0) { + DEBUG_MSG("WARNING: target_brate parameter ignored for now\n"); // TODO + } + + /* get actual serial port configuration */ + i = tcgetattr(gps_tty_dev, &ttyopt); + if (i != 0) { + DEBUG_MSG("ERROR: IMPOSSIBLE TO GET TTY PORT CONFIGURATION\n"); + return LGW_GPS_ERROR; + } + + /* Save current serial port configuration for restoring later */ + memcpy(&ttyopt_restore, &ttyopt, sizeof ttyopt); + + /* update baudrates */ + cfsetispeed(&ttyopt, DEFAULT_BAUDRATE); + cfsetospeed(&ttyopt, DEFAULT_BAUDRATE); + + /* update terminal parameters */ + /* The following configuration should allow to: + - Get ASCII NMEA messages + - Get UBX binary messages + - Send UBX binary commands + Note: as binary data have to be read/written, we need to disable + various character processing to avoid loosing data */ + /* Control Modes */ + ttyopt.c_cflag |= CLOCAL; /* local connection, no modem control */ + ttyopt.c_cflag |= CREAD; /* enable receiving characters */ + ttyopt.c_cflag |= CS8; /* 8 bit frames */ + ttyopt.c_cflag &= ~PARENB; /* no parity */ + ttyopt.c_cflag &= ~CSTOPB; /* one stop bit */ + /* Input Modes */ + ttyopt.c_iflag |= IGNPAR; /* ignore bytes with parity errors */ + ttyopt.c_iflag &= ~ICRNL; /* do not map CR to NL on input*/ + ttyopt.c_iflag &= ~IGNCR; /* do not ignore carriage return on input */ + ttyopt.c_iflag &= ~IXON; /* disable Start/Stop output control */ + ttyopt.c_iflag &= ~IXOFF; /* do not send Start/Stop characters */ + /* Output Modes */ + ttyopt.c_oflag = 0; /* disable everything on output as we only write binary */ + /* Local Modes */ + ttyopt.c_lflag &= ~ICANON; /* disable canonical input - cannot use with binary input */ + ttyopt.c_lflag &= ~ISIG; /* disable check for INTR, QUIT, SUSP special characters */ + ttyopt.c_lflag &= ~IEXTEN; /* disable any special control character */ + ttyopt.c_lflag &= ~ECHO; /* do not echo back every character typed */ + ttyopt.c_lflag &= ~ECHOE; /* does not erase the last character in current line */ + ttyopt.c_lflag &= ~ECHOK; /* do not echo NL after KILL character */ + + /* settings for non-canonical mode + read will block for until the lesser of VMIN or requested chars have been received */ + ttyopt.c_cc[VMIN] = LGW_GPS_MIN_MSG_SIZE; + ttyopt.c_cc[VTIME] = 0; + + /* set new serial ports parameters */ + i = tcsetattr(gps_tty_dev, TCSANOW, &ttyopt); + if (i != 0){ + DEBUG_MSG("ERROR: IMPOSSIBLE TO UPDATE TTY PORT CONFIGURATION\n"); + return LGW_GPS_ERROR; + } + tcflush(gps_tty_dev, TCIOFLUSH); + + /* Send UBX CFG NAV-TIMEGPS message to tell GPS module to output native GPS time */ + /* This is a binary message, serial port has to be properly configured to handle this */ + num_written = write (gps_tty_dev, ubx_cmd_timegps, UBX_MSG_NAVTIMEGPS_LEN); + if (num_written != UBX_MSG_NAVTIMEGPS_LEN) { + DEBUG_MSG("ERROR: Failed to write on serial port (written=%d)\n", (int) num_written); + } + + /* get timezone info */ + tzset(); + + /* initialize global variables */ + gps_time_ok = false; + gps_pos_ok = false; + gps_mod = 'N'; + + return LGW_GPS_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_gps_disable(int fd) { + int i; + + /* restore serial ports parameters */ + i = tcsetattr(fd, TCSANOW, &ttyopt_restore); + if (i != 0){ + DEBUG_MSG("ERROR: IMPOSSIBLE TO RESTORE TTY PORT CONFIGURATION - %s\n", strerror(errno)); + return LGW_GPS_ERROR; + } + tcflush(fd, TCIOFLUSH); + + i = close(fd); + if (i != 0) { + DEBUG_PRINTF("ERROR: TTY PORT FAIL TO CLOSE - %s\n", strerror(errno)); + return LGW_GPS_ERROR; + } + + return LGW_GPS_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +enum gps_msg lgw_parse_ubx(const char *serial_buff, size_t buff_size, size_t *msg_size) { + bool valid = 0; /* iTOW, fTOW and week validity */ + unsigned int payload_length; + uint8_t ck_a, ck_b; + uint8_t ck_a_rcv, ck_b_rcv; + unsigned int i; + + *msg_size = 0; /* ensure msg_size alway receives a value */ + + /* check input parameters */ + if (serial_buff == NULL) { + return IGNORED; + } + if (buff_size < 8) { + DEBUG_MSG("ERROR: TOO SHORT TO BE A VALID UBX MESSAGE\n"); + return IGNORED; + } + + /* display received serial data and checksum */ + DEBUG_MSG("Note: parsing UBX frame> "); + for (i=0; i (int)(sizeof(parser_buf) - 1)) { + DEBUG_MSG("Note: input string to big for parsing\n"); + return INVALID; + } + + /* look for some NMEA sentences in particular */ + if (buff_size < 8) { + DEBUG_MSG("ERROR: TOO SHORT TO BE A VALID NMEA SENTENCE\n"); + return UNKNOWN; + } else if (!validate_nmea_checksum(serial_buff, buff_size)) { + DEBUG_MSG("Warning: invalid NMEA sentence (bad checksum)\n"); + return INVALID; + } else if (match_label(serial_buff, "$G?RMC", 6, '?')) { + /* + NMEA sentence format: $xxRMC,time,status,lat,NS,long,EW,spd,cog,date,mv,mvEW,posMode*cs + Valid fix: $GPRMC,083559.34,A,4717.11437,N,00833.91522,E,0.004,77.52,091202,,,A*00 + No fix: $GPRMC,,V,,,,,,,,,,N*00 + */ + memcpy(parser_buf, serial_buff, buff_size); + parser_buf[buff_size] = '\0'; + nb_fields = str_chop(parser_buf, buff_size, ',', str_index, ARRAY_SIZE(str_index)); + if (nb_fields != 13) { + DEBUG_MSG("Warning: invalid RMC sentence (number of fields)\n"); + return IGNORED; + } + /* parse GPS status */ + gps_mod = *(parser_buf + str_index[12]); /* get first character, no need to bother with sscanf */ + if ((gps_mod != 'N') && (gps_mod != 'A') && (gps_mod != 'D')) { + gps_mod = 'N'; + } + /* parse complete time */ + i = sscanf(parser_buf + str_index[1], "%2hd%2hd%2hd%4f", &gps_hou, &gps_min, &gps_sec, &gps_fra); + j = sscanf(parser_buf + str_index[9], "%2hd%2hd%2hd", &gps_day, &gps_mon, &gps_yea); + if ((i == 4) && (j == 3)) { + if ((gps_mod == 'A') || (gps_mod == 'D')) { + gps_time_ok = true; + DEBUG_MSG("Note: Valid RMC sentence, GPS locked, date: 20%02d-%02d-%02dT%02d:%02d:%06.3fZ\n", gps_yea, gps_mon, gps_day, gps_hou, gps_min, gps_fra + (float)gps_sec); + } else { + gps_time_ok = false; + DEBUG_MSG("Note: Valid RMC sentence, no satellite fix, estimated date: 20%02d-%02d-%02dT%02d:%02d:%06.3fZ\n", gps_yea, gps_mon, gps_day, gps_hou, gps_min, gps_fra + (float)gps_sec); + } + } else { + /* could not get a valid hour AND date */ + gps_time_ok = false; + DEBUG_MSG("Note: Valid RMC sentence, mode %c, no date\n", gps_mod); + } + return NMEA_RMC; + } else if (match_label(serial_buff, "$G?GGA", 6, '?')) { + /* + NMEA sentence format: $xxGGA,time,lat,NS,long,EW,quality,numSV,HDOP,alt,M,sep,M,diffAge,diffStation*cs + Valid fix: $GPGGA,092725.00,4717.11399,N,00833.91590,E,1,08,1.01,499.6,M,48.0,M,,*5B + */ + memcpy(parser_buf, serial_buff, buff_size); + parser_buf[buff_size] = '\0'; + nb_fields = str_chop(parser_buf, buff_size, ',', str_index, ARRAY_SIZE(str_index)); + if (nb_fields != 15) { + DEBUG_MSG("Warning: invalid GGA sentence (number of fields)\n"); + return IGNORED; + } + /* parse number of satellites used for fix */ + sscanf(parser_buf + str_index[7], "%hd", &gps_sat); + /* parse 3D coordinates */ + i = sscanf(parser_buf + str_index[2], "%2hd%10lf", &gps_dla, &gps_mla); + gps_ola = *(parser_buf + str_index[3]); + j = sscanf(parser_buf + str_index[4], "%3hd%10lf", &gps_dlo, &gps_mlo); + gps_olo = *(parser_buf + str_index[5]); + k = sscanf(parser_buf + str_index[9], "%hd", &gps_alt); + if ((i == 2) && (j == 2) && (k == 1) && ((gps_ola=='N')||(gps_ola=='S')) && ((gps_olo=='E')||(gps_olo=='W'))) { + gps_pos_ok = true; + DEBUG_MSG("Note: Valid GGA sentence, %d sat, lat %02ddeg %06.3fmin %c, lon %03ddeg%06.3fmin %c, alt %d\n", gps_sat, gps_dla, gps_mla, gps_ola, gps_dlo, gps_mlo, gps_olo, gps_alt); + } else { + /* could not get a valid latitude, longitude AND altitude */ + gps_pos_ok = false; + DEBUG_MSG("Note: Valid GGA sentence, %d sat, no coordinates\n", gps_sat); + } + return NMEA_GGA; + } else { + DEBUG_MSG("Note: ignored NMEA sentence\n"); /* quite verbose */ + return IGNORED; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_gps_get(struct timespec *utc, struct timespec *gps_time, struct coord_s *loc, struct coord_s *err) { + struct tm x; + time_t y; + double intpart, fractpart; + + if (utc != NULL) { + if (!gps_time_ok) { + DEBUG_MSG("ERROR: NO VALID TIME TO RETURN\n"); + return LGW_GPS_ERROR; + } + memset(&x, 0, sizeof(x)); + if (gps_yea < 100) { /* 2-digits year, 20xx */ + x.tm_year = gps_yea + 100; /* 100 years offset to 1900 */ + } else { /* 4-digits year, Gregorian calendar */ + x.tm_year = gps_yea - 1900; + } + x.tm_mon = gps_mon - 1; /* tm_mon is [0,11], gps_mon is [1,12] */ + x.tm_mday = gps_day; + x.tm_hour = gps_hou; + x.tm_min = gps_min; + x.tm_sec = gps_sec; + y = mktime(&x) - timezone; /* need to substract timezone bc mktime assumes time vector is local time */ + if (y == (time_t)(-1)) { + DEBUG_MSG("ERROR: FAILED TO CONVERT BROKEN-DOWN TIME\n"); + return LGW_GPS_ERROR; + } + utc->tv_sec = y; + utc->tv_nsec = (int32_t)(gps_fra * 1e9); + } + if (gps_time != NULL) { + if (!gps_time_ok) { + DEBUG_MSG("ERROR: NO VALID TIME TO RETURN\n"); + return LGW_GPS_ERROR; + } + fractpart = modf(((double)gps_iTOW / 1E3) + ((double)gps_fTOW / 1E9), &intpart); + /* Number of seconds since beginning on current GPS week */ + gps_time->tv_sec = (time_t)intpart; + /* Number of seconds since GPS epoch 06.Jan.1980 */ + gps_time->tv_sec += (time_t)gps_week * 604800; /* day*hours*minutes*secondes: 7*24*60*60; */ + /* Fractional part in nanoseconds */ + gps_time->tv_nsec = (long)(fractpart * 1E9); + } + if (loc != NULL) { + if (!gps_pos_ok) { + DEBUG_MSG("ERROR: NO VALID POSITION TO RETURN\n"); + return LGW_GPS_ERROR; + } + loc->lat = ((double)gps_dla + (gps_mla/60.0)) * ((gps_ola == 'N')?1.0:-1.0); + loc->lon = ((double)gps_dlo + (gps_mlo/60.0)) * ((gps_olo == 'E')?1.0:-1.0); + loc->alt = gps_alt; + } + if (err != NULL) { + DEBUG_MSG("Warning: localization error processing not implemented yet\n"); + err->lat = 0.0; + err->lon = 0.0; + err->alt = 0; + } + + return LGW_GPS_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_gps_sync(struct tref *ref, uint32_t count_us, struct timespec utc, struct timespec gps_time) { + double cnt_diff; /* internal concentrator time difference (in seconds) */ + double utc_diff; /* UTC time difference (in seconds) */ + double slope; /* time slope between new reference and old reference (for sanity check) */ + + bool aber_n0; /* is the update value for synchronization aberrant or not ? */ + static bool aber_min1 = false; /* keep track of whether value at sync N-1 was aberrant or not */ + static bool aber_min2 = false; /* keep track of whether value at sync N-2 was aberrant or not */ + + CHECK_NULL(ref); + + /* calculate the slope */ + + cnt_diff = (double)(count_us - ref->count_us) / (double)(TS_CPS); /* uncorrected by xtal_err */ + utc_diff = (double)(utc.tv_sec - (ref->utc).tv_sec) + (1E-9 * (double)(utc.tv_nsec - (ref->utc).tv_nsec)); + + /* detect aberrant points by measuring if slope limits are exceeded */ + if (utc_diff != 0) { // prevent divide by zero + slope = cnt_diff/utc_diff; + if ((slope > PLUS_10PPM) || (slope < MINUS_10PPM)) { + DEBUG_MSG("Warning: correction range exceeded\n"); + aber_n0 = true; + } else { + aber_n0 = false; + } + } else { + DEBUG_MSG("Warning: aberrant UTC value for synchronization\n"); + aber_n0 = true; + } + + /* watch if the 3 latest sync point were aberrant or not */ + if (aber_n0 == false) { + /* value no aberrant -> sync with smoothed slope */ + ref->systime = time(NULL); + ref->count_us = count_us; + ref->utc.tv_sec = utc.tv_sec; + ref->utc.tv_nsec = utc.tv_nsec; + ref->gps.tv_sec = gps_time.tv_sec; + ref->gps.tv_nsec = gps_time.tv_nsec; + ref->xtal_err = slope; + aber_min2 = aber_min1; + aber_min1 = aber_n0; + return LGW_GPS_SUCCESS; + } else if (aber_n0 && aber_min1 && aber_min2) { + /* 3 successive aberrant values -> sync reset (keep xtal_err) */ + ref->systime = time(NULL); + ref->count_us = count_us; + ref->utc.tv_sec = utc.tv_sec; + ref->utc.tv_nsec = utc.tv_nsec; + ref->gps.tv_sec = gps_time.tv_sec; + ref->gps.tv_nsec = gps_time.tv_nsec; + /* reset xtal_err only if the present value is out of range */ + if ((ref->xtal_err > PLUS_10PPM) || (ref->xtal_err < MINUS_10PPM)) { + ref->xtal_err = 1.0; + } + DEBUG_MSG("Warning: 3 successive aberrant sync attempts, sync reset\n"); + aber_min2 = aber_min1; + aber_min1 = aber_n0; + return LGW_GPS_SUCCESS; + } else { + /* only 1 or 2 successive aberrant values -> ignore and return an error */ + aber_min2 = aber_min1; + aber_min1 = aber_n0; + return LGW_GPS_ERROR; + } + + return LGW_GPS_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_cnt2utc(struct tref ref, uint32_t count_us, struct timespec *utc) { + double delta_sec; + double intpart, fractpart; + long tmp; + + CHECK_NULL(utc); + if ((ref.systime == 0) || (ref.xtal_err > PLUS_10PPM) || (ref.xtal_err < MINUS_10PPM)) { + DEBUG_MSG("ERROR: INVALID REFERENCE FOR CNT -> UTC CONVERSION\n"); + return LGW_GPS_ERROR; + } + + /* calculate delta in seconds between reference count_us and target count_us */ + delta_sec = (double)(count_us - ref.count_us) / (TS_CPS * ref.xtal_err); + + /* now add that delta to reference UTC time */ + fractpart = modf (delta_sec , &intpart); + tmp = ref.utc.tv_nsec + (long)(fractpart * 1E9); + if (tmp < (long)1E9) { /* the nanosecond part doesn't overflow */ + utc->tv_sec = ref.utc.tv_sec + (time_t)intpart; + utc->tv_nsec = tmp; + } else { /* must carry one second */ + utc->tv_sec = ref.utc.tv_sec + (time_t)intpart + 1; + utc->tv_nsec = tmp - (long)1E9; + } + + return LGW_GPS_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_utc2cnt(struct tref ref, struct timespec utc, uint32_t *count_us) { + double delta_sec; + + CHECK_NULL(count_us); + if ((ref.systime == 0) || (ref.xtal_err > PLUS_10PPM) || (ref.xtal_err < MINUS_10PPM)) { + DEBUG_MSG("ERROR: INVALID REFERENCE FOR UTC -> CNT CONVERSION\n"); + return LGW_GPS_ERROR; + } + + /* calculate delta in seconds between reference utc and target utc */ + delta_sec = (double)(utc.tv_sec - ref.utc.tv_sec); + delta_sec += 1E-9 * (double)(utc.tv_nsec - ref.utc.tv_nsec); + + /* now convert that to internal counter tics and add that to reference counter value */ + *count_us = ref.count_us + (uint32_t)(delta_sec * TS_CPS * ref.xtal_err); + + return LGW_GPS_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_cnt2gps(struct tref ref, uint32_t count_us, struct timespec *gps_time) { + double delta_sec; + double intpart, fractpart; + long tmp; + + CHECK_NULL(gps_time); + if ((ref.systime == 0) || (ref.xtal_err > PLUS_10PPM) || (ref.xtal_err < MINUS_10PPM)) { + DEBUG_MSG("ERROR: INVALID REFERENCE FOR CNT -> GPS CONVERSION\n"); + return LGW_GPS_ERROR; + } + + /* calculate delta in milliseconds between reference count_us and target count_us */ + delta_sec = (double)(count_us - ref.count_us) / (TS_CPS * ref.xtal_err); + + /* now add that delta to reference GPS time */ + fractpart = modf (delta_sec , &intpart); + tmp = ref.gps.tv_nsec + (long)(fractpart * 1E9); + if (tmp < (long)1E9) { /* the nanosecond part doesn't overflow */ + gps_time->tv_sec = ref.gps.tv_sec + (time_t)intpart; + gps_time->tv_nsec = tmp; + } else { /* must carry one second */ + gps_time->tv_sec = ref.gps.tv_sec + (time_t)intpart + 1; + gps_time->tv_nsec = tmp - (long)1E9; + } + + return LGW_GPS_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_gps2cnt(struct tref ref, struct timespec gps_time, uint32_t *count_us) { + double delta_sec; + + CHECK_NULL(count_us); + if ((ref.systime == 0) || (ref.xtal_err > PLUS_10PPM) || (ref.xtal_err < MINUS_10PPM)) { + DEBUG_MSG("ERROR: INVALID REFERENCE FOR GPS -> CNT CONVERSION\n"); + return LGW_GPS_ERROR; + } + + /* calculate delta in seconds between reference gps time and target gps time */ + delta_sec = (double)(gps_time.tv_sec - ref.gps.tv_sec); + delta_sec += 1E-9 * (double)(gps_time.tv_nsec - ref.gps.tv_nsec); + + /* now convert that to internal counter tics and add that to reference counter value */ + *count_us = ref.count_us + (uint32_t)(delta_sec * TS_CPS * ref.xtal_err); + + return LGW_GPS_SUCCESS; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_hal.c b/libloragw/src/loragw_hal.c new file mode 100644 index 0000000..50e37e2 --- /dev/null +++ b/libloragw/src/loragw_hal.c @@ -0,0 +1,1048 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (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 ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_i2c.c b/libloragw/src/loragw_i2c.c new file mode 100644 index 0000000..42b3f5a --- /dev/null +++ b/libloragw/src/loragw_i2c.c @@ -0,0 +1,156 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Host specific functions to address the LoRa concentrator I2C peripherals. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* printf fprintf */ +#include /* malloc free */ +#include /* lseek, close */ +#include /* open */ +#include /* memset */ + +#include +#include +#include + +#include "loragw_i2c.h" +#include "loragw_aux.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_I2C == 1 + #define DEBUG_MSG(str) fprintf(stderr, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) + #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_SPI_ERROR;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return LGW_SPI_ERROR;} +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int i2c_linuxdev_open(const char *path, uint8_t device_addr, int *i2c_fd) { + int dev; + + /* Check input variables */ + if (path == NULL) { + DEBUG_MSG("ERROR: null pointer path"); + return LGW_I2C_ERROR; + } + if (i2c_fd == NULL) { + DEBUG_MSG("ERROR: null pointer i2c_fd"); + return LGW_I2C_ERROR; + } + + /* Open I2C device */ + dev = open(path, O_RDWR); + if (dev < 0) { + DEBUG_PRINTF("ERROR: Failed to open I2C %s - %s", path, strerror(errno)); + return LGW_I2C_ERROR; + } + + /* Setting I2C device mode to slave */ + if (ioctl(dev, I2C_SLAVE, device_addr) < 0) { + DEBUG_PRINTF("ERROR: Failed to acquire bus access and/or talk to slave - %s\n", strerror(errno)); + return LGW_I2C_ERROR; + } + + DEBUG_MSG("INFO: I2C port opened successfully"); + *i2c_fd = dev; /* return file descriptor index */ + + return LGW_I2C_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int i2c_linuxdev_read(int i2c_fd, uint8_t device_addr, uint8_t reg_addr, uint8_t *data) { + uint8_t *inbuff, outbuff; + struct i2c_rdwr_ioctl_data packets; + struct i2c_msg messages[2]; + + outbuff = reg_addr; + messages[0].addr = device_addr; + messages[0].flags= 0; + messages[0].len = sizeof(outbuff); + messages[0].buf = &outbuff; + + inbuff = data; + messages[1].addr = device_addr; + messages[1].flags = I2C_M_RD; + messages[1].len = sizeof(*inbuff); + messages[1].buf = inbuff; + + packets.msgs = messages; + packets.nmsgs = 2; + + if (ioctl(i2c_fd, I2C_RDWR, &packets) < 0) { + DEBUG_PRINTF("ERROR: Read from I2C Device failed (%d, 0x%02x, 0x%02x) - %s", i2c_fd, device_addr, reg_addr, strerror(errno)); + return LGW_I2C_ERROR; + } + + return LGW_I2C_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int i2c_linuxdev_write(int i2c_fd, uint8_t device_addr, uint8_t reg_addr, uint8_t data) { + unsigned char buff[2]; + struct i2c_rdwr_ioctl_data packets; + struct i2c_msg messages[1]; + + buff[0] = reg_addr; + buff[1] = data; + + messages[0].addr = device_addr; + messages[0].flags = 0; + messages[0].len = sizeof(buff); + messages[0].buf = buff; + + packets.msgs = messages; + packets.nmsgs = 1; + + if (ioctl(i2c_fd, I2C_RDWR, &packets) < 0) { + DEBUG_PRINTF("ERROR: Write to I2C Device failed (%d, 0x%02x, 0x%02x) - %s", i2c_fd, device_addr, reg_addr, strerror(errno)); + return LGW_I2C_ERROR; + } + + return LGW_I2C_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int i2c_linuxdev_close(int i2c_fd) { + int i; + + i = close(i2c_fd); + if (i == 0) { + DEBUG_MSG("INFO: I2C port closed successfully"); + return LGW_I2C_SUCCESS; + } else { + DEBUG_PRINTF("ERROR: Failed to close I2C - %s", strerror(errno)); + return LGW_I2C_ERROR; + } +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_reg.c b/libloragw/src/loragw_reg.c new file mode 100644 index 0000000..3117f5c --- /dev/null +++ b/libloragw/src/loragw_reg.c @@ -0,0 +1,1501 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle a single LoRa SX1302 concentrator. + Registers are addressed by name. + Multi-bytes registers are handled automatically. + Read-modify-write is handled automatically. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* bool type */ +#include /* printf fprintf */ + +#include "loragw_spi.h" +#include "loragw_reg.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_REG == 1 + #define DEBUG_MSG(str) fprintf(stderr, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) + #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_REG_ERROR;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return LGW_REG_ERROR;} +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define SX1302_REG_EXT_MEM_PAGED_BASE_ADDR 0x0 +#define SX1302_REG_RX_BUFFER_BASE_ADDR 0x4000 +#define SX1302_REG_TX_TOP_A_BASE_ADDR 0x5200 +#define SX1302_REG_TX_TOP_B_BASE_ADDR 0x5400 +#define SX1302_REG_COMMON_BASE_ADDR 0x5600 +#define SX1302_REG_GPIO_BASE_ADDR 0x5640 +#define SX1302_REG_MBIST_BASE_ADDR 0x56c0 +#define SX1302_REG_RADIO_FE_BASE_ADDR 0x5700 +#define SX1302_REG_AGC_MCU_BASE_ADDR 0x5780 +#define SX1302_REG_CLK_CTRL_BASE_ADDR 0x57c0 +#define SX1302_REG_RX_TOP_BASE_ADDR 0x5800 +#define SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR 0x5b00 +#define SX1302_REG_CAPTURE_RAM_BASE_ADDR 0x6000 +#define SX1302_REG_ARB_MCU_BASE_ADDR 0x6080 +#define SX1302_REG_TIMESTAMP_BASE_ADDR 0x6100 +#define SX1302_REG_OTP_BASE_ADDR 0x6180 + +const struct lgw_reg_s loregs[LGW_TOTALREGS+1] = { + {0,SX1302_REG_COMMON_BASE_ADDR+0,0,0,2,0,1,0}, // COMMON_PAGE_PAGE + {0,SX1302_REG_COMMON_BASE_ADDR+1,4,0,1,0,1,0}, // COMMON_CTRL0_CLK32_RIF_CTRL + {0,SX1302_REG_COMMON_BASE_ADDR+1,3,0,1,0,1,1}, // COMMON_CTRL0_HOST_RADIO_CTRL + {0,SX1302_REG_COMMON_BASE_ADDR+1,2,0,1,0,1,0}, // COMMON_CTRL0_RADIO_MISC_EN + {0,SX1302_REG_COMMON_BASE_ADDR+1,1,0,1,0,1,1}, // COMMON_CTRL0_SX1261_MODE_RADIO_B + {0,SX1302_REG_COMMON_BASE_ADDR+1,0,0,1,0,1,1}, // COMMON_CTRL0_SX1261_MODE_RADIO_A + {0,SX1302_REG_COMMON_BASE_ADDR+2,3,0,1,0,1,0}, // COMMON_CTRL1_SWAP_IQ_RADIO_B + {0,SX1302_REG_COMMON_BASE_ADDR+2,2,0,1,0,1,1}, // COMMON_CTRL1_SAMPLING_EDGE_RADIO_B + {0,SX1302_REG_COMMON_BASE_ADDR+2,1,0,1,0,1,0}, // COMMON_CTRL1_SWAP_IQ_RADIO_A + {0,SX1302_REG_COMMON_BASE_ADDR+2,0,0,1,0,1,1}, // COMMON_CTRL1_SAMPLING_EDGE_RADIO_A + {0,SX1302_REG_COMMON_BASE_ADDR+3,0,0,8,0,1,2}, // COMMON_SPI_DIV_RATIO_SPI_HALF_PERIOD + {0,SX1302_REG_COMMON_BASE_ADDR+4,0,0,8,0,1,128}, // COMMON_RADIO_SELECT_RADIO_SELECT + {0,SX1302_REG_COMMON_BASE_ADDR+5,3,0,1,0,1,0}, // COMMON_GEN_GLOBAL_EN + {0,SX1302_REG_COMMON_BASE_ADDR+5,2,0,1,0,1,0}, // COMMON_GEN_FSK_MODEM_ENABLE + {0,SX1302_REG_COMMON_BASE_ADDR+5,1,0,1,0,1,0}, // COMMON_GEN_CONCENTRATOR_MODEM_ENABLE + {0,SX1302_REG_COMMON_BASE_ADDR+5,0,0,1,0,1,0}, // COMMON_GEN_MBWSSF_MODEM_ENABLE + {0,SX1302_REG_COMMON_BASE_ADDR+6,0,0,8,1,1,16}, // COMMON_VERSION_VERSION + {0,SX1302_REG_COMMON_BASE_ADDR+7,0,0,1,1,1,0}, // COMMON_DUMMY_DUMMY + {0,SX1302_REG_AGC_MCU_BASE_ADDR+0,4,0,1,0,1,1}, // AGC_MCU_CTRL_CLK_EN + {0,SX1302_REG_AGC_MCU_BASE_ADDR+0,3,0,1,0,1,0}, // AGC_MCU_CTRL_FORCE_HOST_FE_CTRL + {0,SX1302_REG_AGC_MCU_BASE_ADDR+0,2,0,1,0,1,1}, // AGC_MCU_CTRL_MCU_CLEAR + {0,SX1302_REG_AGC_MCU_BASE_ADDR+0,1,0,1,0,1,0}, // AGC_MCU_CTRL_HOST_PROG + {0,SX1302_REG_AGC_MCU_BASE_ADDR+0,0,0,1,1,1,0}, // AGC_MCU_CTRL_PARITY_ERROR + {0,SX1302_REG_AGC_MCU_BASE_ADDR+1,0,0,8,1,1,0}, // AGC_MCU_MCU_AGC_STATUS_MCU_AGC_STATUS + {0,SX1302_REG_AGC_MCU_BASE_ADDR+2,2,0,2,0,1,0}, // AGC_MCU_PA_GAIN_PA_B_GAIN + {0,SX1302_REG_AGC_MCU_BASE_ADDR+2,0,0,2,0,1,0}, // AGC_MCU_PA_GAIN_PA_A_GAIN + {0,SX1302_REG_AGC_MCU_BASE_ADDR+3,3,0,1,0,1,0}, // AGC_MCU_RF_EN_A_RADIO_RST + {0,SX1302_REG_AGC_MCU_BASE_ADDR+3,2,0,1,0,1,0}, // AGC_MCU_RF_EN_A_RADIO_EN + {0,SX1302_REG_AGC_MCU_BASE_ADDR+3,1,0,1,0,1,0}, // AGC_MCU_RF_EN_A_PA_EN + {0,SX1302_REG_AGC_MCU_BASE_ADDR+3,0,0,1,0,1,0}, // AGC_MCU_RF_EN_A_LNA_EN + {0,SX1302_REG_AGC_MCU_BASE_ADDR+4,3,0,1,0,1,0}, // AGC_MCU_RF_EN_B_RADIO_RST + {0,SX1302_REG_AGC_MCU_BASE_ADDR+4,2,0,1,0,1,0}, // AGC_MCU_RF_EN_B_RADIO_EN + {0,SX1302_REG_AGC_MCU_BASE_ADDR+4,1,0,1,0,1,0}, // AGC_MCU_RF_EN_B_PA_EN + {0,SX1302_REG_AGC_MCU_BASE_ADDR+4,0,0,1,0,1,0}, // AGC_MCU_RF_EN_B_LNA_EN + {0,SX1302_REG_AGC_MCU_BASE_ADDR+5,4,0,4,0,1,0}, // AGC_MCU_LUT_TABLE_A_PA_LUT + {0,SX1302_REG_AGC_MCU_BASE_ADDR+5,0,0,4,0,1,0}, // AGC_MCU_LUT_TABLE_A_LNA_LUT + {0,SX1302_REG_AGC_MCU_BASE_ADDR+6,4,0,4,0,1,0}, // AGC_MCU_LUT_TABLE_B_PA_LUT + {0,SX1302_REG_AGC_MCU_BASE_ADDR+6,0,0,4,0,1,0}, // AGC_MCU_LUT_TABLE_B_LNA_LUT + {0,SX1302_REG_AGC_MCU_BASE_ADDR+7,5,0,1,0,1,0}, // AGC_MCU_UART_CFG_MSBF + {0,SX1302_REG_AGC_MCU_BASE_ADDR+7,4,0,1,0,1,0}, // AGC_MCU_UART_CFG_PAR_EN + {0,SX1302_REG_AGC_MCU_BASE_ADDR+7,3,0,1,0,1,0}, // AGC_MCU_UART_CFG_PAR_MODE + {0,SX1302_REG_AGC_MCU_BASE_ADDR+7,2,0,1,0,1,0}, // AGC_MCU_UART_CFG_START_LEN + {0,SX1302_REG_AGC_MCU_BASE_ADDR+7,1,0,1,0,1,0}, // AGC_MCU_UART_CFG_STOP_LEN + {0,SX1302_REG_AGC_MCU_BASE_ADDR+7,0,0,1,0,1,1}, // AGC_MCU_UART_CFG_WORD_LEN + {0,SX1302_REG_AGC_MCU_BASE_ADDR+8,0,0,8,0,1,0}, // AGC_MCU_UART_CFG2_BIT_RATE + {0,SX1302_REG_AGC_MCU_BASE_ADDR+9,0,0,8,0,1,0}, // AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE3_MCU_MAIL_BOX_WR_DATA + {0,SX1302_REG_AGC_MCU_BASE_ADDR+10,0,0,8,0,1,0}, // AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE2_MCU_MAIL_BOX_WR_DATA + {0,SX1302_REG_AGC_MCU_BASE_ADDR+11,0,0,8,0,1,0}, // AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE1_MCU_MAIL_BOX_WR_DATA + {0,SX1302_REG_AGC_MCU_BASE_ADDR+12,0,0,8,0,1,0}, // AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE0_MCU_MAIL_BOX_WR_DATA + {0,SX1302_REG_AGC_MCU_BASE_ADDR+13,0,0,8,1,1,0}, // AGC_MCU_MCU_MAIL_BOX_RD_DATA_BYTE3_MCU_MAIL_BOX_RD_DATA + {0,SX1302_REG_AGC_MCU_BASE_ADDR+14,0,0,8,1,1,0}, // AGC_MCU_MCU_MAIL_BOX_RD_DATA_BYTE2_MCU_MAIL_BOX_RD_DATA + {0,SX1302_REG_AGC_MCU_BASE_ADDR+15,0,0,8,1,1,0}, // AGC_MCU_MCU_MAIL_BOX_RD_DATA_BYTE1_MCU_MAIL_BOX_RD_DATA + {0,SX1302_REG_AGC_MCU_BASE_ADDR+16,0,0,8,1,1,0}, // AGC_MCU_MCU_MAIL_BOX_RD_DATA_BYTE0_MCU_MAIL_BOX_RD_DATA + {0,SX1302_REG_AGC_MCU_BASE_ADDR+17,0,0,1,1,1,0}, // AGC_MCU_DUMMY_DUMMY3 + {0,SX1302_REG_CLK_CTRL_BASE_ADDR+0,2,0,1,0,1,0}, // CLK_CTRL_CLK_SEL_CLKDIV_EN + {0,SX1302_REG_CLK_CTRL_BASE_ADDR+0,1,0,1,0,1,0}, // CLK_CTRL_CLK_SEL_CLK_RADIO_B_SEL + {0,SX1302_REG_CLK_CTRL_BASE_ADDR+0,0,0,1,0,1,0}, // CLK_CTRL_CLK_SEL_CLK_RADIO_A_SEL + {0,SX1302_REG_CLK_CTRL_BASE_ADDR+1,3,0,1,1,1,0}, // CLK_CTRL_DUMMY_DUMMY + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+0,3,0,1,0,0,0}, // TX_TOP_A_TX_TRIG_TX_FSM_CLR + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+0,2,0,1,0,1,0}, // TX_TOP_A_TX_TRIG_TX_TRIG_GPS + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+0,1,0,1,0,1,0}, // TX_TOP_A_TX_TRIG_TX_TRIG_DELAYED + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+0,0,0,1,0,1,0}, // TX_TOP_A_TX_TRIG_TX_TRIG_IMMEDIATE + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+1,0,0,8,0,1,0}, // TX_TOP_A_TIMER_TRIG_BYTE3_TIMER_DELAYED_TRIG + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+2,0,0,8,0,1,0}, // TX_TOP_A_TIMER_TRIG_BYTE2_TIMER_DELAYED_TRIG + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+3,0,0,8,0,1,0}, // TX_TOP_A_TIMER_TRIG_BYTE1_TIMER_DELAYED_TRIG + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+4,0,0,8,0,1,0}, // TX_TOP_A_TIMER_TRIG_BYTE0_TIMER_DELAYED_TRIG + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+5,0,0,8,0,1,187}, // TX_TOP_A_TX_START_DELAY_MSB_TX_START_DELAY + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+6,0,0,8,0,1,128}, // TX_TOP_A_TX_START_DELAY_LSB_TX_START_DELAY + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+7,0,0,1,0,1,0}, // TX_TOP_A_TX_CTRL_WRITE_BUFFER + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+8,0,0,3,0,1,1}, // TX_TOP_A_TX_RAMP_DURATION_TX_RAMP_DURATION + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+9,0,0,1,0,1,0}, // TX_TOP_A_GEN_CFG_0_MODULATION_TYPE + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+10,1,0,1,0,1,0}, // TX_TOP_A_TEST_0_TX_ACTIVE_CTRL + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+10,0,0,1,0,1,0}, // TX_TOP_A_TEST_0_TX_ACTIVE_SEL + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+11,1,0,1,0,0,0}, // TX_TOP_A_TX_FLAG_TX_TIMEOUT + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+11,0,0,1,0,0,0}, // TX_TOP_A_TX_FLAG_PKT_DONE + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+12,0,0,8,0,1,0}, // TX_TOP_A_AGC_TX_BW_AGC_TX_BW + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+13,0,0,8,0,1,0}, // TX_TOP_A_AGC_TX_PWR_AGC_TX_PWR + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+14,0,0,8,0,1,0}, // TX_TOP_A_TIMEOUT_CNT_BYTE_2_TIMEOUT_CNT + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+15,0,0,8,0,1,0}, // TX_TOP_A_TIMEOUT_CNT_BYTE_1_TIMEOUT_CNT + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+16,0,0,8,0,1,0}, // TX_TOP_A_TIMEOUT_CNT_BYTE_0_TIMEOUT_CNT + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+17,0,0,8,1,1,0}, // TX_TOP_A_TX_FSM_STATUS_TX_STATUS + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+18,3,0,1,1,1,0}, // TX_TOP_A_DUMMY_CONTROL_DUMMY + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+32,5,0,3,0,1,0}, // TX_TOP_A_TX_RFFE_IF_CTRL_PLL_DIV_CTRL + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+32,4,0,1,0,1,1}, // TX_TOP_A_TX_RFFE_IF_CTRL_TX_CLK_EDGE + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+32,3,0,1,0,1,1}, // TX_TOP_A_TX_RFFE_IF_CTRL_TX_MODE + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+32,2,0,1,0,1,0}, // TX_TOP_A_TX_RFFE_IF_CTRL_TX_IF_DST + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+32,0,0,2,0,1,0}, // TX_TOP_A_TX_RFFE_IF_CTRL_TX_IF_SRC + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+33,1,0,1,0,1,0}, // TX_TOP_A_TX_RFFE_IF_CTRL2_SX125X_IQ_INVERT + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+33,0,0,1,0,1,0}, // TX_TOP_A_TX_RFFE_IF_CTRL2_PLL_DIV_CTRL_AGC + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+34,0,0,2,0,1,0}, // TX_TOP_A_TX_RFFE_IF_IQ_GAIN_IQ_GAIN + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+35,0,0,8,0,1,0}, // TX_TOP_A_TX_RFFE_IF_I_OFFSET_I_OFFSET + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+36,0,0,8,0,1,0}, // TX_TOP_A_TX_RFFE_IF_Q_OFFSET_Q_OFFSET + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+37,0,0,8,0,1,108}, // TX_TOP_A_TX_RFFE_IF_FREQ_RF_H_FREQ_RF + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+38,0,0,8,0,1,144}, // TX_TOP_A_TX_RFFE_IF_FREQ_RF_M_FREQ_RF + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+39,0,0,8,0,1,0}, // TX_TOP_A_TX_RFFE_IF_FREQ_RF_L_FREQ_RF + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+40,0,0,4,0,1,0}, // TX_TOP_A_TX_RFFE_IF_FREQ_DEV_H_FREQ_DEV + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+41,0,0,8,0,1,0}, // TX_TOP_A_TX_RFFE_IF_FREQ_DEV_L_FREQ_DEV + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+42,0,0,8,0,1,64}, // TX_TOP_A_TX_RFFE_IF_TEST_MOD_FREQ + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+43,3,0,1,1,1,0}, // TX_TOP_A_DUMMY_MODULATOR_DUMMY + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+64,0,0,8,0,1,15}, // TX_TOP_A_FSK_PKT_LEN_PKT_LENGTH + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+65,5,0,1,0,1,0}, // TX_TOP_A_FSK_CFG_0_TX_CONT + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+65,4,0,1,0,1,0}, // TX_TOP_A_FSK_CFG_0_CRC_IBM + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+65,2,0,2,0,1,0}, // TX_TOP_A_FSK_CFG_0_DCFREE_ENC + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+65,1,0,1,0,1,1}, // TX_TOP_A_FSK_CFG_0_CRC_EN + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+65,0,0,1,0,1,0}, // TX_TOP_A_FSK_CFG_0_PKT_MODE + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+66,0,0,8,0,1,0}, // TX_TOP_A_FSK_PREAMBLE_SIZE_MSB_PREAMBLE_SIZE + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+67,0,0,8,0,1,20}, // TX_TOP_A_FSK_PREAMBLE_SIZE_LSB_PREAMBLE_SIZE + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+68,0,0,8,0,1,26}, // TX_TOP_A_FSK_BIT_RATE_MSB_BIT_RATE + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+69,0,0,8,0,1,11}, // TX_TOP_A_FSK_BIT_RATE_LSB_BIT_RATE + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+70,5,0,3,0,1,3}, // TX_TOP_A_FSK_MOD_FSK_REF_PATTERN_SIZE + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+70,4,0,1,0,1,0}, // TX_TOP_A_FSK_MOD_FSK_PREAMBLE_SEQ + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+70,3,0,1,0,1,1}, // TX_TOP_A_FSK_MOD_FSK_REF_PATTERN_EN + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+70,1,0,2,0,1,0}, // TX_TOP_A_FSK_MOD_FSK_GAUSSIAN_SELECT_BT + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+70,0,0,1,0,1,0}, // TX_TOP_A_FSK_MOD_FSK_GAUSSIAN_EN + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+71,0,0,8,0,1,151}, // TX_TOP_A_FSK_REF_PATTERN_BYTE7_FSK_REF_PATTERN + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+72,0,0,8,0,1,35}, // TX_TOP_A_FSK_REF_PATTERN_BYTE6_FSK_REF_PATTERN + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+73,0,0,8,0,1,82}, // TX_TOP_A_FSK_REF_PATTERN_BYTE5_FSK_REF_PATTERN + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+74,0,0,8,0,1,37}, // TX_TOP_A_FSK_REF_PATTERN_BYTE4_FSK_REF_PATTERN + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+75,0,0,8,0,1,86}, // TX_TOP_A_FSK_REF_PATTERN_BYTE3_FSK_REF_PATTERN + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+76,0,0,8,0,1,83}, // TX_TOP_A_FSK_REF_PATTERN_BYTE2_FSK_REF_PATTERN + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+77,0,0,8,0,1,101}, // TX_TOP_A_FSK_REF_PATTERN_BYTE1_FSK_REF_PATTERN + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+78,0,0,8,0,1,100}, // TX_TOP_A_FSK_REF_PATTERN_BYTE0_FSK_REF_PATTERN + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+79,3,0,1,1,1,0}, // TX_TOP_A_DUMMY_GSFK_DUMMY + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+96,4,0,4,0,1,5}, // TX_TOP_A_TXRX_CFG0_0_MODEM_BW + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+96,0,0,4,0,1,7}, // TX_TOP_A_TXRX_CFG0_0_MODEM_SF + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+97,6,0,2,0,1,2}, // TX_TOP_A_TXRX_CFG0_1_PPM_OFFSET_HDR_CTRL + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+97,4,0,2,0,1,0}, // TX_TOP_A_TXRX_CFG0_1_PPM_OFFSET + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+97,3,0,1,0,1,0}, // TX_TOP_A_TXRX_CFG0_1_POST_PREAMBLE_GAP_LONG + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+97,0,0,3,0,1,2}, // TX_TOP_A_TXRX_CFG0_1_CODING_RATE + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+98,7,0,1,0,1,0}, // TX_TOP_A_TXRX_CFG0_2_FINE_SYNCH_EN + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+98,6,0,1,0,1,0}, // TX_TOP_A_TXRX_CFG0_2_MODEM_EN + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+98,4,0,2,0,1,2}, // TX_TOP_A_TXRX_CFG0_2_CADRXTX + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+98,1,0,1,0,1,0}, // TX_TOP_A_TXRX_CFG0_2_IMPLICIT_HEADER + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+98,0,0,1,0,1,1}, // TX_TOP_A_TXRX_CFG0_2_CRC_EN + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+99,0,0,8,0,1,12}, // TX_TOP_A_TXRX_CFG0_3_PAYLOAD_LENGTH + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+100,7,0,1,0,1,0}, // TX_TOP_A_TXRX_CFG1_0_INT_STEP_ORIDE_EN + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+100,0,0,6,0,1,0}, // TX_TOP_A_TXRX_CFG1_0_INT_STEP_ORIDE + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+101,7,0,1,0,1,0}, // TX_TOP_A_TXRX_CFG1_1_MODEM_START + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+101,6,0,1,0,1,0}, // TX_TOP_A_TXRX_CFG1_1_HEADER_DIFF_MODE + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+101,0,0,6,0,1,0}, // TX_TOP_A_TXRX_CFG1_1_ZERO_PAD + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+102,0,0,8,0,1,8}, // TX_TOP_A_TXRX_CFG1_2_PREAMBLE_SYMB_NB + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+103,0,0,8,0,1,0}, // TX_TOP_A_TXRX_CFG1_3_PREAMBLE_SYMB_NB + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+104,6,0,1,0,1,1}, // TX_TOP_A_TXRX_CFG1_4_AUTO_ACK_INT_DELAY + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+104,5,0,1,0,1,0}, // TX_TOP_A_TXRX_CFG1_4_AUTO_ACK_RX + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+104,4,0,1,0,1,0}, // TX_TOP_A_TXRX_CFG1_4_AUTO_ACK_TX + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+105,4,0,3,0,1,0}, // TX_TOP_A_TX_CFG0_0_CHIRP_LOWPASS + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+105,3,0,1,0,1,0}, // TX_TOP_A_TX_CFG0_0_PPM_OFFSET_SIG + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+105,2,0,1,0,1,1}, // TX_TOP_A_TX_CFG0_0_CONTCHIRP + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+105,1,0,1,0,1,1}, // TX_TOP_A_TX_CFG0_0_CHIRP_INVERT + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+105,0,0,1,0,1,0}, // TX_TOP_A_TX_CFG0_0_CONTINUOUS + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+106,0,0,6,0,1,20}, // TX_TOP_A_TX_CFG0_1_POWER_RANGING + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+107,0,0,8,0,1,0}, // TX_TOP_A_TX_CFG1_0_FRAME_NB + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+108,5,0,2,0,1,0}, // TX_TOP_A_TX_CFG1_1_HOP_CTRL + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+108,0,0,5,0,1,10}, // TX_TOP_A_TX_CFG1_1_IFS + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+109,7,0,1,0,1,1}, // TX_TOP_A_FRAME_SYNCH_0_AUTO_SCALE + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+109,6,0,1,0,1,0}, // TX_TOP_A_FRAME_SYNCH_0_DROP_ON_SYNCH + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+109,5,0,1,0,1,1}, // TX_TOP_A_FRAME_SYNCH_0_GAIN + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+109,0,1,5,0,1,2}, // TX_TOP_A_FRAME_SYNCH_0_PEAK1_POS + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+110,7,0,1,0,1,0}, // TX_TOP_A_FRAME_SYNCH_1_FINETIME_ON_LAST + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+110,5,0,2,0,1,3}, // TX_TOP_A_FRAME_SYNCH_1_TIMEOUT_OPT + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+110,0,1,5,0,1,4}, // TX_TOP_A_FRAME_SYNCH_1_PEAK2_POS + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+111,0,0,4,1,1,0}, // TX_TOP_A_LORA_TX_STATE_STATUS + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+112,2,0,1,0,0,0}, // TX_TOP_A_LORA_TX_FLAG_FRAME_DONE + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+112,1,0,1,0,0,0}, // TX_TOP_A_LORA_TX_FLAG_CONT_DONE + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+112,0,0,1,0,0,0}, // TX_TOP_A_LORA_TX_FLAG_PLD_DONE + {0,SX1302_REG_TX_TOP_A_BASE_ADDR+113,3,0,1,1,1,0}, // TX_TOP_A_DUMMY_LORA_DUMMY + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+0,3,0,1,0,0,0}, // TX_TOP_B_TX_TRIG_TX_FSM_CLR + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+0,2,0,1,0,1,0}, // TX_TOP_B_TX_TRIG_TX_TRIG_GPS + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+0,1,0,1,0,1,0}, // TX_TOP_B_TX_TRIG_TX_TRIG_DELAYED + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+0,0,0,1,0,1,0}, // TX_TOP_B_TX_TRIG_TX_TRIG_IMMEDIATE + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+1,0,0,8,0,1,0}, // TX_TOP_B_TIMER_TRIG_BYTE3_TIMER_DELAYED_TRIG + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+2,0,0,8,0,1,0}, // TX_TOP_B_TIMER_TRIG_BYTE2_TIMER_DELAYED_TRIG + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+3,0,0,8,0,1,0}, // TX_TOP_B_TIMER_TRIG_BYTE1_TIMER_DELAYED_TRIG + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+4,0,0,8,0,1,0}, // TX_TOP_B_TIMER_TRIG_BYTE0_TIMER_DELAYED_TRIG + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+5,0,0,8,0,1,187}, // TX_TOP_B_TX_START_DELAY_MSB_TX_START_DELAY + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+6,0,0,8,0,1,128}, // TX_TOP_B_TX_START_DELAY_LSB_TX_START_DELAY + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+7,0,0,1,0,1,0}, // TX_TOP_B_TX_CTRL_WRITE_BUFFER + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+8,0,0,3,0,1,1}, // TX_TOP_B_TX_RAMP_DURATION_TX_RAMP_DURATION + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+9,0,0,1,0,1,0}, // TX_TOP_B_GEN_CFG_0_MODULATION_TYPE + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+10,1,0,1,0,1,0}, // TX_TOP_B_TEST_0_TX_ACTIVE_CTRL + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+10,0,0,1,0,1,0}, // TX_TOP_B_TEST_0_TX_ACTIVE_SEL + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+11,1,0,1,0,0,0}, // TX_TOP_B_TX_FLAG_TX_TIMEOUT + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+11,0,0,1,0,0,0}, // TX_TOP_B_TX_FLAG_PKT_DONE + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+12,0,0,8,0,1,0}, // TX_TOP_B_AGC_TX_BW_AGC_TX_BW + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+13,0,0,8,0,1,0}, // TX_TOP_B_AGC_TX_PWR_AGC_TX_PWR + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+14,0,0,8,0,1,0}, // TX_TOP_B_TIMEOUT_CNT_BYTE_2_TIMEOUT_CNT + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+15,0,0,8,0,1,0}, // TX_TOP_B_TIMEOUT_CNT_BYTE_1_TIMEOUT_CNT + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+16,0,0,8,0,1,0}, // TX_TOP_B_TIMEOUT_CNT_BYTE_0_TIMEOUT_CNT + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+17,0,0,8,1,1,0}, // TX_TOP_B_TX_FSM_STATUS_TX_STATUS + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+18,3,0,1,1,1,0}, // TX_TOP_B_DUMMY_CONTROL_DUMMY + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+32,5,0,3,0,1,0}, // TX_TOP_B_TX_RFFE_IF_CTRL_PLL_DIV_CTRL + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+32,4,0,1,0,1,1}, // TX_TOP_B_TX_RFFE_IF_CTRL_TX_CLK_EDGE + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+32,3,0,1,0,1,1}, // TX_TOP_B_TX_RFFE_IF_CTRL_TX_MODE + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+32,2,0,1,0,1,0}, // TX_TOP_B_TX_RFFE_IF_CTRL_TX_IF_DST + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+32,0,0,2,0,1,0}, // TX_TOP_B_TX_RFFE_IF_CTRL_TX_IF_SRC + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+33,1,0,1,0,1,0}, // TX_TOP_B_TX_RFFE_IF_CTRL2_SX125X_IQ_INVERT + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+33,0,0,1,0,1,0}, // TX_TOP_B_TX_RFFE_IF_CTRL2_PLL_DIV_CTRL_AGC + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+34,0,0,2,0,1,0}, // TX_TOP_B_TX_RFFE_IF_IQ_GAIN_IQ_GAIN + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+35,0,0,8,0,1,0}, // TX_TOP_B_TX_RFFE_IF_I_OFFSET_I_OFFSET + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+36,0,0,8,0,1,0}, // TX_TOP_B_TX_RFFE_IF_Q_OFFSET_Q_OFFSET + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+37,0,0,8,0,1,108}, // TX_TOP_B_TX_RFFE_IF_FREQ_RF_H_FREQ_RF + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+38,0,0,8,0,1,144}, // TX_TOP_B_TX_RFFE_IF_FREQ_RF_M_FREQ_RF + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+39,0,0,8,0,1,0}, // TX_TOP_B_TX_RFFE_IF_FREQ_RF_L_FREQ_RF + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+40,0,0,4,0,1,0}, // TX_TOP_B_TX_RFFE_IF_FREQ_DEV_H_FREQ_DEV + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+41,0,0,8,0,1,0}, // TX_TOP_B_TX_RFFE_IF_FREQ_DEV_L_FREQ_DEV + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+42,0,0,8,0,1,64}, // TX_TOP_B_TX_RFFE_IF_TEST_MOD_FREQ + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+43,3,0,1,1,1,0}, // TX_TOP_B_DUMMY_MODULATOR_DUMMY + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+64,0,0,8,0,1,15}, // TX_TOP_B_FSK_PKT_LEN_PKT_LENGTH + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+65,5,0,1,0,1,0}, // TX_TOP_B_FSK_CFG_0_TX_CONT + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+65,4,0,1,0,1,0}, // TX_TOP_B_FSK_CFG_0_CRC_IBM + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+65,2,0,2,0,1,0}, // TX_TOP_B_FSK_CFG_0_DCFREE_ENC + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+65,1,0,1,0,1,1}, // TX_TOP_B_FSK_CFG_0_CRC_EN + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+65,0,0,1,0,1,0}, // TX_TOP_B_FSK_CFG_0_PKT_MODE + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+66,0,0,8,0,1,0}, // TX_TOP_B_FSK_PREAMBLE_SIZE_MSB_PREAMBLE_SIZE + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+67,0,0,8,0,1,20}, // TX_TOP_B_FSK_PREAMBLE_SIZE_LSB_PREAMBLE_SIZE + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+68,0,0,8,0,1,26}, // TX_TOP_B_FSK_BIT_RATE_MSB_BIT_RATE + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+69,0,0,8,0,1,11}, // TX_TOP_B_FSK_BIT_RATE_LSB_BIT_RATE + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+70,5,0,3,0,1,3}, // TX_TOP_B_FSK_MOD_FSK_REF_PATTERN_SIZE + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+70,4,0,1,0,1,0}, // TX_TOP_B_FSK_MOD_FSK_PREAMBLE_SEQ + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+70,3,0,1,0,1,1}, // TX_TOP_B_FSK_MOD_FSK_REF_PATTERN_EN + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+70,1,0,2,0,1,0}, // TX_TOP_B_FSK_MOD_FSK_GAUSSIAN_SELECT_BT + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+70,0,0,1,0,1,0}, // TX_TOP_B_FSK_MOD_FSK_GAUSSIAN_EN + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+71,0,0,8,0,1,151}, // TX_TOP_B_FSK_REF_PATTERN_BYTE7_FSK_REF_PATTERN + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+72,0,0,8,0,1,35}, // TX_TOP_B_FSK_REF_PATTERN_BYTE6_FSK_REF_PATTERN + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+73,0,0,8,0,1,82}, // TX_TOP_B_FSK_REF_PATTERN_BYTE5_FSK_REF_PATTERN + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+74,0,0,8,0,1,37}, // TX_TOP_B_FSK_REF_PATTERN_BYTE4_FSK_REF_PATTERN + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+75,0,0,8,0,1,86}, // TX_TOP_B_FSK_REF_PATTERN_BYTE3_FSK_REF_PATTERN + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+76,0,0,8,0,1,83}, // TX_TOP_B_FSK_REF_PATTERN_BYTE2_FSK_REF_PATTERN + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+77,0,0,8,0,1,101}, // TX_TOP_B_FSK_REF_PATTERN_BYTE1_FSK_REF_PATTERN + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+78,0,0,8,0,1,100}, // TX_TOP_B_FSK_REF_PATTERN_BYTE0_FSK_REF_PATTERN + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+79,3,0,1,1,1,0}, // TX_TOP_B_DUMMY_GSFK_DUMMY + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+96,4,0,4,0,1,5}, // TX_TOP_B_TXRX_CFG0_0_MODEM_BW + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+96,0,0,4,0,1,7}, // TX_TOP_B_TXRX_CFG0_0_MODEM_SF + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+97,6,0,2,0,1,2}, // TX_TOP_B_TXRX_CFG0_1_PPM_OFFSET_HDR_CTRL + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+97,4,0,2,0,1,0}, // TX_TOP_B_TXRX_CFG0_1_PPM_OFFSET + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+97,3,0,1,0,1,0}, // TX_TOP_B_TXRX_CFG0_1_POST_PREAMBLE_GAP_LONG + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+97,0,0,3,0,1,2}, // TX_TOP_B_TXRX_CFG0_1_CODING_RATE + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+98,7,0,1,0,1,0}, // TX_TOP_B_TXRX_CFG0_2_FINE_SYNCH_EN + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+98,6,0,1,0,1,0}, // TX_TOP_B_TXRX_CFG0_2_MODEM_EN + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+98,4,0,2,0,1,2}, // TX_TOP_B_TXRX_CFG0_2_CADRXTX + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+98,1,0,1,0,1,0}, // TX_TOP_B_TXRX_CFG0_2_IMPLICIT_HEADER + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+98,0,0,1,0,1,1}, // TX_TOP_B_TXRX_CFG0_2_CRC_EN + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+99,0,0,8,0,1,12}, // TX_TOP_B_TXRX_CFG0_3_PAYLOAD_LENGTH + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+100,7,0,1,0,1,0}, // TX_TOP_B_TXRX_CFG1_0_INT_STEP_ORIDE_EN + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+100,0,0,6,0,1,0}, // TX_TOP_B_TXRX_CFG1_0_INT_STEP_ORIDE + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+101,7,0,1,0,1,0}, // TX_TOP_B_TXRX_CFG1_1_MODEM_START + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+101,6,0,1,0,1,0}, // TX_TOP_B_TXRX_CFG1_1_HEADER_DIFF_MODE + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+101,0,0,6,0,1,0}, // TX_TOP_B_TXRX_CFG1_1_ZERO_PAD + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+102,0,0,8,0,1,8}, // TX_TOP_B_TXRX_CFG1_2_PREAMBLE_SYMB_NB + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+103,0,0,8,0,1,0}, // TX_TOP_B_TXRX_CFG1_3_PREAMBLE_SYMB_NB + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+104,6,0,1,0,1,1}, // TX_TOP_B_TXRX_CFG1_4_AUTO_ACK_INT_DELAY + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+104,5,0,1,0,1,0}, // TX_TOP_B_TXRX_CFG1_4_AUTO_ACK_RX + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+104,4,0,1,0,1,0}, // TX_TOP_B_TXRX_CFG1_4_AUTO_ACK_TX + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+105,4,0,3,0,1,0}, // TX_TOP_B_TX_CFG0_0_CHIRP_LOWPASS + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+105,3,0,1,0,1,0}, // TX_TOP_B_TX_CFG0_0_PPM_OFFSET_SIG + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+105,2,0,1,0,1,1}, // TX_TOP_B_TX_CFG0_0_CONTCHIRP + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+105,1,0,1,0,1,1}, // TX_TOP_B_TX_CFG0_0_CHIRP_INVERT + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+105,0,0,1,0,1,0}, // TX_TOP_B_TX_CFG0_0_CONTINUOUS + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+106,0,0,6,0,1,20}, // TX_TOP_B_TX_CFG0_1_POWER_RANGING + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+107,0,0,8,0,1,0}, // TX_TOP_B_TX_CFG1_0_FRAME_NB + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+108,5,0,2,0,1,0}, // TX_TOP_B_TX_CFG1_1_HOP_CTRL + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+108,0,0,5,0,1,10}, // TX_TOP_B_TX_CFG1_1_IFS + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+109,7,0,1,0,1,1}, // TX_TOP_B_FRAME_SYNCH_0_AUTO_SCALE + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+109,6,0,1,0,1,0}, // TX_TOP_B_FRAME_SYNCH_0_DROP_ON_SYNCH + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+109,5,0,1,0,1,1}, // TX_TOP_B_FRAME_SYNCH_0_GAIN + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+109,0,1,5,0,1,2}, // TX_TOP_B_FRAME_SYNCH_0_PEAK1_POS + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+110,7,0,1,0,1,0}, // TX_TOP_B_FRAME_SYNCH_1_FINETIME_ON_LAST + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+110,5,0,2,0,1,3}, // TX_TOP_B_FRAME_SYNCH_1_TIMEOUT_OPT + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+110,0,1,5,0,1,4}, // TX_TOP_B_FRAME_SYNCH_1_PEAK2_POS + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+111,0,0,4,1,1,0}, // TX_TOP_B_LORA_TX_STATE_STATUS + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+112,2,0,1,0,0,0}, // TX_TOP_B_LORA_TX_FLAG_FRAME_DONE + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+112,1,0,1,0,0,0}, // TX_TOP_B_LORA_TX_FLAG_CONT_DONE + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+112,0,0,1,0,0,0}, // TX_TOP_B_LORA_TX_FLAG_PLD_DONE + {0,SX1302_REG_TX_TOP_B_BASE_ADDR+113,3,0,1,1,1,0}, // TX_TOP_B_DUMMY_LORA_DUMMY + {0,SX1302_REG_GPIO_BASE_ADDR+0,0,0,4,0,1,0}, // GPIO_GPIO_DIR_H_DIRECTION + {0,SX1302_REG_GPIO_BASE_ADDR+1,0,0,8,0,1,0}, // GPIO_GPIO_DIR_L_DIRECTION + {0,SX1302_REG_GPIO_BASE_ADDR+2,0,0,4,0,1,0}, // GPIO_GPIO_OUT_H_OUT_VALUE + {0,SX1302_REG_GPIO_BASE_ADDR+3,0,0,8,0,1,0}, // GPIO_GPIO_OUT_L_OUT_VALUE + {0,SX1302_REG_GPIO_BASE_ADDR+4,0,0,4,1,1,0}, // GPIO_GPIO_IN_H_IN_VALUE + {0,SX1302_REG_GPIO_BASE_ADDR+5,0,0,8,1,1,0}, // GPIO_GPIO_IN_L_IN_VALUE + {0,SX1302_REG_GPIO_BASE_ADDR+6,0,0,4,0,1,0}, // GPIO_GPIO_PD_H_PD_VALUE + {0,SX1302_REG_GPIO_BASE_ADDR+7,0,0,8,0,1,0}, // GPIO_GPIO_PD_L_PD_VALUE + {0,SX1302_REG_GPIO_BASE_ADDR+8,0,0,4,0,1,0}, // GPIO_GPIO_SEL_0_SELECTION + {0,SX1302_REG_GPIO_BASE_ADDR+9,0,0,4,0,1,0}, // GPIO_GPIO_SEL_1_SELECTION + {0,SX1302_REG_GPIO_BASE_ADDR+10,0,0,4,0,1,0}, // GPIO_GPIO_SEL_2_SELECTION + {0,SX1302_REG_GPIO_BASE_ADDR+11,0,0,4,0,1,0}, // GPIO_GPIO_SEL_3_SELECTION + {0,SX1302_REG_GPIO_BASE_ADDR+12,0,0,4,0,1,0}, // GPIO_GPIO_SEL_4_SELECTION + {0,SX1302_REG_GPIO_BASE_ADDR+13,0,0,4,0,1,0}, // GPIO_GPIO_SEL_5_SELECTION + {0,SX1302_REG_GPIO_BASE_ADDR+14,0,0,4,0,1,0}, // GPIO_GPIO_SEL_6_SELECTION + {0,SX1302_REG_GPIO_BASE_ADDR+15,0,0,4,0,1,0}, // GPIO_GPIO_SEL_7_SELECTION + {0,SX1302_REG_GPIO_BASE_ADDR+16,1,0,4,0,1,0}, // GPIO_GPIO_SEL_8_11_GPIO_11_9_SEL + {0,SX1302_REG_GPIO_BASE_ADDR+16,0,0,1,0,1,0}, // GPIO_GPIO_SEL_8_11_GPIO_8_SEL + {0,SX1302_REG_GPIO_BASE_ADDR+17,5,0,1,0,0,0}, // GPIO_HOST_IRQ_TX_TIMEOUT_B + {0,SX1302_REG_GPIO_BASE_ADDR+17,4,0,1,0,0,0}, // GPIO_HOST_IRQ_TX_TIMEOUT_A + {0,SX1302_REG_GPIO_BASE_ADDR+17,3,0,1,0,0,0}, // GPIO_HOST_IRQ_TX_DONE_B + {0,SX1302_REG_GPIO_BASE_ADDR+17,2,0,1,0,0,0}, // GPIO_HOST_IRQ_TX_DONE_A + {0,SX1302_REG_GPIO_BASE_ADDR+17,1,0,1,0,0,0}, // GPIO_HOST_IRQ_TIMESTAMP + {0,SX1302_REG_GPIO_BASE_ADDR+17,0,0,1,0,0,0}, // GPIO_HOST_IRQ_RX_BUFFER_WATERMARK + {0,SX1302_REG_GPIO_BASE_ADDR+18,5,0,1,0,1,0}, // GPIO_HOST_IRQ_EN_TX_TIMEOUT_B + {0,SX1302_REG_GPIO_BASE_ADDR+18,4,0,1,0,1,0}, // GPIO_HOST_IRQ_EN_TX_TIMEOUT_A + {0,SX1302_REG_GPIO_BASE_ADDR+18,3,0,1,0,1,0}, // GPIO_HOST_IRQ_EN_TX_DONE_B + {0,SX1302_REG_GPIO_BASE_ADDR+18,2,0,1,0,1,0}, // GPIO_HOST_IRQ_EN_TX_DONE_A + {0,SX1302_REG_GPIO_BASE_ADDR+18,1,0,1,0,1,0}, // GPIO_HOST_IRQ_EN_TIMESTAMP + {0,SX1302_REG_GPIO_BASE_ADDR+18,0,0,1,0,1,0}, // GPIO_HOST_IRQ_EN_RX_BUFFER_WATERMARK + {0,SX1302_REG_GPIO_BASE_ADDR+19,0,0,1,1,1,0}, // GPIO_DUMMY_DUMMY + {0,SX1302_REG_TIMESTAMP_BASE_ADDR+0,1,0,1,0,1,0}, // TIMESTAMP_GPS_CTRL_GPS_POL + {0,SX1302_REG_TIMESTAMP_BASE_ADDR+0,0,0,1,0,1,0}, // TIMESTAMP_GPS_CTRL_GPS_EN + {0,SX1302_REG_TIMESTAMP_BASE_ADDR+1,0,0,8,1,1,0}, // TIMESTAMP_TIMESTAMP_PPS_MSB2_TIMESTAMP_PPS + {0,SX1302_REG_TIMESTAMP_BASE_ADDR+2,0,0,8,1,1,0}, // TIMESTAMP_TIMESTAMP_PPS_MSB1_TIMESTAMP_PPS + {0,SX1302_REG_TIMESTAMP_BASE_ADDR+3,0,0,8,1,1,0}, // TIMESTAMP_TIMESTAMP_PPS_LSB2_TIMESTAMP_PPS + {0,SX1302_REG_TIMESTAMP_BASE_ADDR+4,0,0,8,1,1,0}, // TIMESTAMP_TIMESTAMP_PPS_LSB1_TIMESTAMP_PPS + {0,SX1302_REG_TIMESTAMP_BASE_ADDR+5,0,0,8,1,1,0}, // TIMESTAMP_TIMESTAMP_MSB2_TIMESTAMP + {0,SX1302_REG_TIMESTAMP_BASE_ADDR+6,0,0,8,1,1,0}, // TIMESTAMP_TIMESTAMP_MSB1_TIMESTAMP + {0,SX1302_REG_TIMESTAMP_BASE_ADDR+7,0,0,8,1,1,0}, // TIMESTAMP_TIMESTAMP_LSB2_TIMESTAMP + {0,SX1302_REG_TIMESTAMP_BASE_ADDR+8,0,0,8,1,1,0}, // TIMESTAMP_TIMESTAMP_LSB1_TIMESTAMP + {0,SX1302_REG_TIMESTAMP_BASE_ADDR+9,0,0,8,0,1,0}, // TIMESTAMP_TIMESTAMP_SET3_TIMESTAMP + {0,SX1302_REG_TIMESTAMP_BASE_ADDR+10,0,0,8,0,1,0}, // TIMESTAMP_TIMESTAMP_SET2_TIMESTAMP + {0,SX1302_REG_TIMESTAMP_BASE_ADDR+11,0,0,8,0,1,0}, // TIMESTAMP_TIMESTAMP_SET1_TIMESTAMP + {0,SX1302_REG_TIMESTAMP_BASE_ADDR+12,0,0,8,0,1,0}, // TIMESTAMP_TIMESTAMP_SET0_TIMESTAMP + {0,SX1302_REG_TIMESTAMP_BASE_ADDR+13,0,0,8,0,1,0}, // TIMESTAMP_TIMESTAMP_IRQ_3_TIMESTAMP + {0,SX1302_REG_TIMESTAMP_BASE_ADDR+14,0,0,8,0,1,0}, // TIMESTAMP_TIMESTAMP_IRQ_2_TIMESTAMP + {0,SX1302_REG_TIMESTAMP_BASE_ADDR+15,0,0,8,0,1,0}, // TIMESTAMP_TIMESTAMP_IRQ_1_TIMESTAMP + {0,SX1302_REG_TIMESTAMP_BASE_ADDR+16,0,0,8,0,1,0}, // TIMESTAMP_TIMESTAMP_IRQ_0_TIMESTAMP + {0,SX1302_REG_TIMESTAMP_BASE_ADDR+17,0,0,1,1,1,0}, // TIMESTAMP_DUMMY_DUMMY + {0,SX1302_REG_RX_TOP_BASE_ADDR+0,0,0,5,0,1,0}, // RX_TOP_FREQ_0_MSB_IF_FREQ_0 + {0,SX1302_REG_RX_TOP_BASE_ADDR+1,0,0,8,0,1,128}, // RX_TOP_FREQ_0_LSB_IF_FREQ_0 + {0,SX1302_REG_RX_TOP_BASE_ADDR+2,0,0,5,0,1,1}, // RX_TOP_FREQ_1_MSB_IF_FREQ_1 + {0,SX1302_REG_RX_TOP_BASE_ADDR+3,0,0,8,0,1,128}, // RX_TOP_FREQ_1_LSB_IF_FREQ_1 + {0,SX1302_REG_RX_TOP_BASE_ADDR+4,0,0,5,0,1,30}, // RX_TOP_FREQ_2_MSB_IF_FREQ_2 + {0,SX1302_REG_RX_TOP_BASE_ADDR+5,0,0,8,0,1,128}, // RX_TOP_FREQ_2_LSB_IF_FREQ_2 + {0,SX1302_REG_RX_TOP_BASE_ADDR+6,0,0,5,0,1,0}, // RX_TOP_FREQ_3_MSB_IF_FREQ_3 + {0,SX1302_REG_RX_TOP_BASE_ADDR+7,0,0,8,0,1,128}, // RX_TOP_FREQ_3_LSB_IF_FREQ_3 + {0,SX1302_REG_RX_TOP_BASE_ADDR+8,0,0,5,0,1,0}, // RX_TOP_FREQ_4_MSB_IF_FREQ_4 + {0,SX1302_REG_RX_TOP_BASE_ADDR+9,0,0,8,0,1,50}, // RX_TOP_FREQ_4_LSB_IF_FREQ_4 + {0,SX1302_REG_RX_TOP_BASE_ADDR+10,0,0,5,0,1,0}, // RX_TOP_FREQ_5_MSB_IF_FREQ_5 + {0,SX1302_REG_RX_TOP_BASE_ADDR+11,0,0,8,0,1,60}, // RX_TOP_FREQ_5_LSB_IF_FREQ_5 + {0,SX1302_REG_RX_TOP_BASE_ADDR+12,0,0,5,0,1,0}, // RX_TOP_FREQ_6_MSB_IF_FREQ_6 + {0,SX1302_REG_RX_TOP_BASE_ADDR+13,0,0,8,0,1,70}, // RX_TOP_FREQ_6_LSB_IF_FREQ_6 + {0,SX1302_REG_RX_TOP_BASE_ADDR+14,0,0,5,0,1,0}, // RX_TOP_FREQ_7_MSB_IF_FREQ_7 + {0,SX1302_REG_RX_TOP_BASE_ADDR+15,0,0,8,0,1,80}, // RX_TOP_FREQ_7_LSB_IF_FREQ_7 + {0,SX1302_REG_RX_TOP_BASE_ADDR+16,0,0,8,0,1,0}, // RX_TOP_RADIO_SELECT_RADIO_SELECT + {0,SX1302_REG_RX_TOP_BASE_ADDR+17,3,0,5,0,1,7}, // RX_TOP_RSSI_CONTROL_RSSI_FILTER_ALPHA + {0,SX1302_REG_RX_TOP_BASE_ADDR+17,0,0,3,0,1,0}, // RX_TOP_RSSI_CONTROL_SELECT_RSSI + {0,SX1302_REG_RX_TOP_BASE_ADDR+18,0,0,8,0,1,0}, // RX_TOP_RSSI_DEF_VALUE_CHAN_RSSI_DEF_VALUE + {0,SX1302_REG_RX_TOP_BASE_ADDR+19,0,0,8,0,1,0}, // RX_TOP_CHANN_DAGC_CFG1_CHAN_DAGC_THRESHOLD_HIGH + {0,SX1302_REG_RX_TOP_BASE_ADDR+20,0,0,8,0,1,0}, // RX_TOP_CHANN_DAGC_CFG2_CHAN_DAGC_THRESHOLD_LOW + {0,SX1302_REG_RX_TOP_BASE_ADDR+21,4,0,4,0,1,15}, // RX_TOP_CHANN_DAGC_CFG3_CHAN_DAGC_MAX_ATTEN + {0,SX1302_REG_RX_TOP_BASE_ADDR+21,0,0,4,0,1,0}, // RX_TOP_CHANN_DAGC_CFG3_CHAN_DAGC_MIN_ATTEN + {0,SX1302_REG_RX_TOP_BASE_ADDR+22,0,0,4,0,1,0}, // RX_TOP_CHANN_DAGC_CFG4_CHAN_DAGC_STEP + {0,SX1302_REG_RX_TOP_BASE_ADDR+23,0,0,2,0,1,0}, // RX_TOP_CHANN_DAGC_CFG5_CHAN_DAGC_MODE + {0,SX1302_REG_RX_TOP_BASE_ADDR+24,0,0,8,1,1,0}, // RX_TOP_RSSI_VALUE_CHAN_RSSI + {0,SX1302_REG_RX_TOP_BASE_ADDR+25,4,0,1,0,1,0}, // RX_TOP_GAIN_CONTROL_CHAN_GAIN_VALID + {0,SX1302_REG_RX_TOP_BASE_ADDR+25,0,0,4,0,1,0}, // RX_TOP_GAIN_CONTROL_CHAN_GAIN + {0,SX1302_REG_RX_TOP_BASE_ADDR+26,0,0,1,0,1,1}, // RX_TOP_CLK_CONTROL_CHAN_CLK_EN + {0,SX1302_REG_RX_TOP_BASE_ADDR+27,0,0,1,1,1,0}, // RX_TOP_DUMMY0_DUMMY0 + {0,SX1302_REG_RX_TOP_BASE_ADDR+32,0,0,8,0,1,255}, // RX_TOP_CORR_CLOCK_ENABLE_CLK_EN + {0,SX1302_REG_RX_TOP_BASE_ADDR+33,0,0,8,0,1,0}, // RX_TOP_CORRELATOR_EN_CORR_EN + {0,SX1302_REG_RX_TOP_BASE_ADDR+34,0,0,8,0,1,255}, // RX_TOP_CORRELATOR_SF_EN_CORR_SF_EN + {0,SX1302_REG_RX_TOP_BASE_ADDR+35,0,0,8,0,1,255}, // RX_TOP_CORRELATOR_ENABLE_ONLY_FIRST_DET_EDGE_ENABLE_ONLY_FIRST_DET_EDGE + {0,SX1302_REG_RX_TOP_BASE_ADDR+36,0,0,8,0,1,255}, // RX_TOP_CORRELATOR_ENABLE_ACC_CLEAR_ENABLE_CORR_ACC_CLEAR + {0,SX1302_REG_RX_TOP_BASE_ADDR+37,6,0,2,0,1,2}, // RX_TOP_SF5_CFG1_ACC_WIN_LEN + {0,SX1302_REG_RX_TOP_BASE_ADDR+37,5,0,1,0,1,1}, // RX_TOP_SF5_CFG1_ACC_PEAK_SUM_EN + {0,SX1302_REG_RX_TOP_BASE_ADDR+37,4,0,1,0,1,1}, // RX_TOP_SF5_CFG1_ACC_PEAK_POS_SEL + {0,SX1302_REG_RX_TOP_BASE_ADDR+37,2,0,2,0,1,2}, // RX_TOP_SF5_CFG1_ACC_COEFF + {0,SX1302_REG_RX_TOP_BASE_ADDR+37,1,0,1,0,1,1}, // RX_TOP_SF5_CFG1_ACC_AUTO_RESCALE + {0,SX1302_REG_RX_TOP_BASE_ADDR+37,0,0,1,0,1,1}, // RX_TOP_SF5_CFG1_ACC_2_SAME_PEAKS + {0,SX1302_REG_RX_TOP_BASE_ADDR+38,7,0,1,0,1,0}, // RX_TOP_SF5_CFG2_ACC_MIN2 + {0,SX1302_REG_RX_TOP_BASE_ADDR+38,0,0,7,0,1,55}, // RX_TOP_SF5_CFG2_ACC_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+39,0,0,8,0,1,11}, // RX_TOP_SF5_CFG3_MIN_SINGLE_PEAK + {0,SX1302_REG_RX_TOP_BASE_ADDR+40,0,0,7,0,1,32}, // RX_TOP_SF5_CFG4_MSP_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+41,0,0,7,0,1,48}, // RX_TOP_SF5_CFG5_MSP2_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+42,3,0,3,0,1,5}, // RX_TOP_SF5_CFG6_MSP_PEAK_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+42,1,0,2,0,1,1}, // RX_TOP_SF5_CFG6_MSP_CNT_MODE + {0,SX1302_REG_RX_TOP_BASE_ADDR+42,0,0,1,0,1,1}, // RX_TOP_SF5_CFG6_MSP_POS_SEL + {0,SX1302_REG_RX_TOP_BASE_ADDR+43,2,0,3,0,1,5}, // RX_TOP_SF5_CFG7_MSP2_PEAK_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+43,0,0,2,0,1,2}, // RX_TOP_SF5_CFG7_NOISE_COEFF + {0,SX1302_REG_RX_TOP_BASE_ADDR+44,6,0,2,0,1,2}, // RX_TOP_SF6_CFG1_ACC_WIN_LEN + {0,SX1302_REG_RX_TOP_BASE_ADDR+44,5,0,1,0,1,1}, // RX_TOP_SF6_CFG1_ACC_PEAK_SUM_EN + {0,SX1302_REG_RX_TOP_BASE_ADDR+44,4,0,1,0,1,1}, // RX_TOP_SF6_CFG1_ACC_PEAK_POS_SEL + {0,SX1302_REG_RX_TOP_BASE_ADDR+44,2,0,2,0,1,2}, // RX_TOP_SF6_CFG1_ACC_COEFF + {0,SX1302_REG_RX_TOP_BASE_ADDR+44,1,0,1,0,1,1}, // RX_TOP_SF6_CFG1_ACC_AUTO_RESCALE + {0,SX1302_REG_RX_TOP_BASE_ADDR+44,0,0,1,0,1,1}, // RX_TOP_SF6_CFG1_ACC_2_SAME_PEAKS + {0,SX1302_REG_RX_TOP_BASE_ADDR+45,7,0,1,0,1,0}, // RX_TOP_SF6_CFG2_ACC_MIN2 + {0,SX1302_REG_RX_TOP_BASE_ADDR+45,0,0,7,0,1,55}, // RX_TOP_SF6_CFG2_ACC_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+46,0,0,8,0,1,11}, // RX_TOP_SF6_CFG3_MIN_SINGLE_PEAK + {0,SX1302_REG_RX_TOP_BASE_ADDR+47,0,0,7,0,1,32}, // RX_TOP_SF6_CFG4_MSP_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+48,0,0,7,0,1,48}, // RX_TOP_SF6_CFG5_MSP2_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+49,3,0,3,0,1,4}, // RX_TOP_SF6_CFG6_MSP_PEAK_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+49,1,0,2,0,1,1}, // RX_TOP_SF6_CFG6_MSP_CNT_MODE + {0,SX1302_REG_RX_TOP_BASE_ADDR+49,0,0,1,0,1,1}, // RX_TOP_SF6_CFG6_MSP_POS_SEL + {0,SX1302_REG_RX_TOP_BASE_ADDR+50,2,0,3,0,1,5}, // RX_TOP_SF6_CFG7_MSP2_PEAK_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+50,0,0,2,0,1,2}, // RX_TOP_SF6_CFG7_NOISE_COEFF + {0,SX1302_REG_RX_TOP_BASE_ADDR+51,6,0,2,0,1,2}, // RX_TOP_SF7_CFG1_ACC_WIN_LEN + {0,SX1302_REG_RX_TOP_BASE_ADDR+51,5,0,1,0,1,1}, // RX_TOP_SF7_CFG1_ACC_PEAK_SUM_EN + {0,SX1302_REG_RX_TOP_BASE_ADDR+51,4,0,1,0,1,1}, // RX_TOP_SF7_CFG1_ACC_PEAK_POS_SEL + {0,SX1302_REG_RX_TOP_BASE_ADDR+51,2,0,2,0,1,2}, // RX_TOP_SF7_CFG1_ACC_COEFF + {0,SX1302_REG_RX_TOP_BASE_ADDR+51,1,0,1,0,1,1}, // RX_TOP_SF7_CFG1_ACC_AUTO_RESCALE + {0,SX1302_REG_RX_TOP_BASE_ADDR+51,0,0,1,0,1,1}, // RX_TOP_SF7_CFG1_ACC_2_SAME_PEAKS + {0,SX1302_REG_RX_TOP_BASE_ADDR+52,7,0,1,0,1,0}, // RX_TOP_SF7_CFG2_ACC_MIN2 + {0,SX1302_REG_RX_TOP_BASE_ADDR+52,0,0,7,0,1,55}, // RX_TOP_SF7_CFG2_ACC_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+53,0,0,8,0,1,11}, // RX_TOP_SF7_CFG3_MIN_SINGLE_PEAK + {0,SX1302_REG_RX_TOP_BASE_ADDR+54,0,0,7,0,1,32}, // RX_TOP_SF7_CFG4_MSP_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+55,0,0,7,0,1,48}, // RX_TOP_SF7_CFG5_MSP2_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+56,3,0,3,0,1,3}, // RX_TOP_SF7_CFG6_MSP_PEAK_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+56,1,0,2,0,1,1}, // RX_TOP_SF7_CFG6_MSP_CNT_MODE + {0,SX1302_REG_RX_TOP_BASE_ADDR+56,0,0,1,0,1,1}, // RX_TOP_SF7_CFG6_MSP_POS_SEL + {0,SX1302_REG_RX_TOP_BASE_ADDR+57,2,0,3,0,1,5}, // RX_TOP_SF7_CFG7_MSP2_PEAK_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+57,0,0,2,0,1,2}, // RX_TOP_SF7_CFG7_NOISE_COEFF + {0,SX1302_REG_RX_TOP_BASE_ADDR+58,6,0,2,0,1,2}, // RX_TOP_SF8_CFG1_ACC_WIN_LEN + {0,SX1302_REG_RX_TOP_BASE_ADDR+58,5,0,1,0,1,1}, // RX_TOP_SF8_CFG1_ACC_PEAK_SUM_EN + {0,SX1302_REG_RX_TOP_BASE_ADDR+58,4,0,1,0,1,1}, // RX_TOP_SF8_CFG1_ACC_PEAK_POS_SEL + {0,SX1302_REG_RX_TOP_BASE_ADDR+58,2,0,2,0,1,2}, // RX_TOP_SF8_CFG1_ACC_COEFF + {0,SX1302_REG_RX_TOP_BASE_ADDR+58,1,0,1,0,1,1}, // RX_TOP_SF8_CFG1_ACC_AUTO_RESCALE + {0,SX1302_REG_RX_TOP_BASE_ADDR+58,0,0,1,0,1,1}, // RX_TOP_SF8_CFG1_ACC_2_SAME_PEAKS + {0,SX1302_REG_RX_TOP_BASE_ADDR+59,7,0,1,0,1,0}, // RX_TOP_SF8_CFG2_ACC_MIN2 + {0,SX1302_REG_RX_TOP_BASE_ADDR+59,0,0,7,0,1,56}, // RX_TOP_SF8_CFG2_ACC_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+60,0,0,8,0,1,11}, // RX_TOP_SF8_CFG3_MIN_SINGLE_PEAK + {0,SX1302_REG_RX_TOP_BASE_ADDR+61,0,0,7,0,1,32}, // RX_TOP_SF8_CFG4_MSP_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+62,0,0,7,0,1,48}, // RX_TOP_SF8_CFG5_MSP2_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+63,3,0,3,0,1,3}, // RX_TOP_SF8_CFG6_MSP_PEAK_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+63,1,0,2,0,1,1}, // RX_TOP_SF8_CFG6_MSP_CNT_MODE + {0,SX1302_REG_RX_TOP_BASE_ADDR+63,0,0,1,0,1,1}, // RX_TOP_SF8_CFG6_MSP_POS_SEL + {0,SX1302_REG_RX_TOP_BASE_ADDR+64,2,0,3,0,1,5}, // RX_TOP_SF8_CFG7_MSP2_PEAK_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+64,0,0,2,0,1,2}, // RX_TOP_SF8_CFG7_NOISE_COEFF + {0,SX1302_REG_RX_TOP_BASE_ADDR+65,6,0,2,0,1,2}, // RX_TOP_SF9_CFG1_ACC_WIN_LEN + {0,SX1302_REG_RX_TOP_BASE_ADDR+65,5,0,1,0,1,1}, // RX_TOP_SF9_CFG1_ACC_PEAK_SUM_EN + {0,SX1302_REG_RX_TOP_BASE_ADDR+65,4,0,1,0,1,1}, // RX_TOP_SF9_CFG1_ACC_PEAK_POS_SEL + {0,SX1302_REG_RX_TOP_BASE_ADDR+65,2,0,2,0,1,2}, // RX_TOP_SF9_CFG1_ACC_COEFF + {0,SX1302_REG_RX_TOP_BASE_ADDR+65,1,0,1,0,1,1}, // RX_TOP_SF9_CFG1_ACC_AUTO_RESCALE + {0,SX1302_REG_RX_TOP_BASE_ADDR+65,0,0,1,0,1,1}, // RX_TOP_SF9_CFG1_ACC_2_SAME_PEAKS + {0,SX1302_REG_RX_TOP_BASE_ADDR+66,7,0,1,0,1,0}, // RX_TOP_SF9_CFG2_ACC_MIN2 + {0,SX1302_REG_RX_TOP_BASE_ADDR+66,0,0,7,0,1,58}, // RX_TOP_SF9_CFG2_ACC_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+67,0,0,8,0,1,11}, // RX_TOP_SF9_CFG3_MIN_SINGLE_PEAK + {0,SX1302_REG_RX_TOP_BASE_ADDR+68,0,0,7,0,1,32}, // RX_TOP_SF9_CFG4_MSP_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+69,0,0,7,0,1,48}, // RX_TOP_SF9_CFG5_MSP2_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+70,3,0,3,0,1,3}, // RX_TOP_SF9_CFG6_MSP_PEAK_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+70,1,0,2,0,1,1}, // RX_TOP_SF9_CFG6_MSP_CNT_MODE + {0,SX1302_REG_RX_TOP_BASE_ADDR+70,0,0,1,0,1,1}, // RX_TOP_SF9_CFG6_MSP_POS_SEL + {0,SX1302_REG_RX_TOP_BASE_ADDR+71,2,0,3,0,1,5}, // RX_TOP_SF9_CFG7_MSP2_PEAK_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+71,0,0,2,0,1,2}, // RX_TOP_SF9_CFG7_NOISE_COEFF + {0,SX1302_REG_RX_TOP_BASE_ADDR+72,6,0,2,0,1,2}, // RX_TOP_SF10_CFG1_ACC_WIN_LEN + {0,SX1302_REG_RX_TOP_BASE_ADDR+72,5,0,1,0,1,1}, // RX_TOP_SF10_CFG1_ACC_PEAK_SUM_EN + {0,SX1302_REG_RX_TOP_BASE_ADDR+72,4,0,1,0,1,1}, // RX_TOP_SF10_CFG1_ACC_PEAK_POS_SEL + {0,SX1302_REG_RX_TOP_BASE_ADDR+72,2,0,2,0,1,2}, // RX_TOP_SF10_CFG1_ACC_COEFF + {0,SX1302_REG_RX_TOP_BASE_ADDR+72,1,0,1,0,1,1}, // RX_TOP_SF10_CFG1_ACC_AUTO_RESCALE + {0,SX1302_REG_RX_TOP_BASE_ADDR+72,0,0,1,0,1,1}, // RX_TOP_SF10_CFG1_ACC_2_SAME_PEAKS + {0,SX1302_REG_RX_TOP_BASE_ADDR+73,7,0,1,0,1,0}, // RX_TOP_SF10_CFG2_ACC_MIN2 + {0,SX1302_REG_RX_TOP_BASE_ADDR+73,0,0,7,0,1,60}, // RX_TOP_SF10_CFG2_ACC_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+74,0,0,8,0,1,11}, // RX_TOP_SF10_CFG3_MIN_SINGLE_PEAK + {0,SX1302_REG_RX_TOP_BASE_ADDR+75,0,0,7,0,1,32}, // RX_TOP_SF10_CFG4_MSP_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+76,0,0,7,0,1,48}, // RX_TOP_SF10_CFG5_MSP2_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+77,3,0,3,0,1,3}, // RX_TOP_SF10_CFG6_MSP_PEAK_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+77,1,0,2,0,1,1}, // RX_TOP_SF10_CFG6_MSP_CNT_MODE + {0,SX1302_REG_RX_TOP_BASE_ADDR+77,0,0,1,0,1,1}, // RX_TOP_SF10_CFG6_MSP_POS_SEL + {0,SX1302_REG_RX_TOP_BASE_ADDR+78,2,0,3,0,1,5}, // RX_TOP_SF10_CFG7_MSP2_PEAK_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+78,0,0,2,0,1,2}, // RX_TOP_SF10_CFG7_NOISE_COEFF + {0,SX1302_REG_RX_TOP_BASE_ADDR+79,6,0,2,0,1,2}, // RX_TOP_SF11_CFG1_ACC_WIN_LEN + {0,SX1302_REG_RX_TOP_BASE_ADDR+79,5,0,1,0,1,1}, // RX_TOP_SF11_CFG1_ACC_PEAK_SUM_EN + {0,SX1302_REG_RX_TOP_BASE_ADDR+79,4,0,1,0,1,1}, // RX_TOP_SF11_CFG1_ACC_PEAK_POS_SEL + {0,SX1302_REG_RX_TOP_BASE_ADDR+79,2,0,2,0,1,2}, // RX_TOP_SF11_CFG1_ACC_COEFF + {0,SX1302_REG_RX_TOP_BASE_ADDR+79,1,0,1,0,1,1}, // RX_TOP_SF11_CFG1_ACC_AUTO_RESCALE + {0,SX1302_REG_RX_TOP_BASE_ADDR+79,0,0,1,0,1,1}, // RX_TOP_SF11_CFG1_ACC_2_SAME_PEAKS + {0,SX1302_REG_RX_TOP_BASE_ADDR+80,7,0,1,0,1,0}, // RX_TOP_SF11_CFG2_ACC_MIN2 + {0,SX1302_REG_RX_TOP_BASE_ADDR+80,0,0,7,0,1,60}, // RX_TOP_SF11_CFG2_ACC_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+81,0,0,8,0,1,11}, // RX_TOP_SF11_CFG3_MIN_SINGLE_PEAK + {0,SX1302_REG_RX_TOP_BASE_ADDR+82,0,0,7,0,1,32}, // RX_TOP_SF11_CFG4_MSP_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+83,0,0,7,0,1,48}, // RX_TOP_SF11_CFG5_MSP2_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+84,3,0,3,0,1,3}, // RX_TOP_SF11_CFG6_MSP_PEAK_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+84,1,0,2,0,1,1}, // RX_TOP_SF11_CFG6_MSP_CNT_MODE + {0,SX1302_REG_RX_TOP_BASE_ADDR+84,0,0,1,0,1,1}, // RX_TOP_SF11_CFG6_MSP_POS_SEL + {0,SX1302_REG_RX_TOP_BASE_ADDR+85,2,0,3,0,1,5}, // RX_TOP_SF11_CFG7_MSP2_PEAK_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+85,0,0,2,0,1,2}, // RX_TOP_SF11_CFG7_NOISE_COEFF + {0,SX1302_REG_RX_TOP_BASE_ADDR+86,6,0,2,0,1,2}, // RX_TOP_SF12_CFG1_ACC_WIN_LEN + {0,SX1302_REG_RX_TOP_BASE_ADDR+86,5,0,1,0,1,1}, // RX_TOP_SF12_CFG1_ACC_PEAK_SUM_EN + {0,SX1302_REG_RX_TOP_BASE_ADDR+86,4,0,1,0,1,1}, // RX_TOP_SF12_CFG1_ACC_PEAK_POS_SEL + {0,SX1302_REG_RX_TOP_BASE_ADDR+86,2,0,2,0,1,2}, // RX_TOP_SF12_CFG1_ACC_COEFF + {0,SX1302_REG_RX_TOP_BASE_ADDR+86,1,0,1,0,1,1}, // RX_TOP_SF12_CFG1_ACC_AUTO_RESCALE + {0,SX1302_REG_RX_TOP_BASE_ADDR+86,0,0,1,0,1,1}, // RX_TOP_SF12_CFG1_ACC_2_SAME_PEAKS + {0,SX1302_REG_RX_TOP_BASE_ADDR+87,7,0,1,0,1,0}, // RX_TOP_SF12_CFG2_ACC_MIN2 + {0,SX1302_REG_RX_TOP_BASE_ADDR+87,0,0,7,0,1,60}, // RX_TOP_SF12_CFG2_ACC_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+88,0,0,8,0,1,11}, // RX_TOP_SF12_CFG3_MIN_SINGLE_PEAK + {0,SX1302_REG_RX_TOP_BASE_ADDR+89,0,0,7,0,1,32}, // RX_TOP_SF12_CFG4_MSP_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+90,0,0,7,0,1,48}, // RX_TOP_SF12_CFG5_MSP2_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+91,3,0,3,0,1,3}, // RX_TOP_SF12_CFG6_MSP_PEAK_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+91,1,0,2,0,1,1}, // RX_TOP_SF12_CFG6_MSP_CNT_MODE + {0,SX1302_REG_RX_TOP_BASE_ADDR+91,0,0,1,0,1,1}, // RX_TOP_SF12_CFG6_MSP_POS_SEL + {0,SX1302_REG_RX_TOP_BASE_ADDR+92,2,0,3,0,1,5}, // RX_TOP_SF12_CFG7_MSP2_PEAK_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+92,0,0,2,0,1,2}, // RX_TOP_SF12_CFG7_NOISE_COEFF + {0,SX1302_REG_RX_TOP_BASE_ADDR+93,0,0,1,1,1,0}, // RX_TOP_DUMMY1_DUMMY1 + {0,SX1302_REG_RX_TOP_BASE_ADDR+96,4,0,3,0,1,0}, // RX_TOP_DC_NOTCH_CFG1_BW_START + {0,SX1302_REG_RX_TOP_BASE_ADDR+96,3,0,1,0,1,1}, // RX_TOP_DC_NOTCH_CFG1_AUTO_BW_RED + {0,SX1302_REG_RX_TOP_BASE_ADDR+96,2,0,1,0,1,0}, // RX_TOP_DC_NOTCH_CFG1_NO_FAST_START + {0,SX1302_REG_RX_TOP_BASE_ADDR+96,1,0,1,0,1,0}, // RX_TOP_DC_NOTCH_CFG1_BYPASS + {0,SX1302_REG_RX_TOP_BASE_ADDR+96,0,0,1,0,1,0}, // RX_TOP_DC_NOTCH_CFG1_ENABLE + {0,SX1302_REG_RX_TOP_BASE_ADDR+97,3,0,3,0,1,1}, // RX_TOP_DC_NOTCH_CFG2_BW_LOCKED + {0,SX1302_REG_RX_TOP_BASE_ADDR+97,0,0,3,0,1,5}, // RX_TOP_DC_NOTCH_CFG2_BW + {0,SX1302_REG_RX_TOP_BASE_ADDR+98,0,0,3,0,1,0}, // RX_TOP_DC_NOTCH_CFG3_BW_RED + {0,SX1302_REG_RX_TOP_BASE_ADDR+99,0,0,8,0,1,0}, // RX_TOP_DC_NOTCH_CFG4_IIR_DCC_TIME + {0,SX1302_REG_RX_TOP_BASE_ADDR+100,0,1,8,0,1,2}, // RX_TOP_RX_DFE_FIR1_0_FIR1_COEFF_0 + {0,SX1302_REG_RX_TOP_BASE_ADDR+101,0,1,8,0,1,3}, // RX_TOP_RX_DFE_FIR1_1_FIR1_COEFF_1 + {0,SX1302_REG_RX_TOP_BASE_ADDR+102,0,1,8,0,1,2}, // RX_TOP_RX_DFE_FIR1_2_FIR1_COEFF_2 + {0,SX1302_REG_RX_TOP_BASE_ADDR+103,0,1,8,0,1,3}, // RX_TOP_RX_DFE_FIR1_3_FIR1_COEFF_3 + {0,SX1302_REG_RX_TOP_BASE_ADDR+104,0,1,8,0,1,5}, // RX_TOP_RX_DFE_FIR1_4_FIR1_COEFF_4 + {0,SX1302_REG_RX_TOP_BASE_ADDR+105,0,1,8,0,1,8}, // RX_TOP_RX_DFE_FIR1_5_FIR1_COEFF_5 + {0,SX1302_REG_RX_TOP_BASE_ADDR+106,0,1,8,0,1,6}, // RX_TOP_RX_DFE_FIR1_6_FIR1_COEFF_6 + {0,SX1302_REG_RX_TOP_BASE_ADDR+107,0,1,8,0,1,4}, // RX_TOP_RX_DFE_FIR1_7_FIR1_COEFF_7 + {0,SX1302_REG_RX_TOP_BASE_ADDR+108,0,1,8,0,1,2}, // RX_TOP_RX_DFE_FIR2_0_FIR2_COEFF_0 + {0,SX1302_REG_RX_TOP_BASE_ADDR+109,0,1,8,0,1,-2}, // RX_TOP_RX_DFE_FIR2_1_FIR2_COEFF_1 + {0,SX1302_REG_RX_TOP_BASE_ADDR+110,0,1,8,0,1,-4}, // RX_TOP_RX_DFE_FIR2_2_FIR2_COEFF_2 + {0,SX1302_REG_RX_TOP_BASE_ADDR+111,0,1,8,0,1,-3}, // RX_TOP_RX_DFE_FIR2_3_FIR2_COEFF_3 + {0,SX1302_REG_RX_TOP_BASE_ADDR+112,0,1,8,0,1,3}, // RX_TOP_RX_DFE_FIR2_4_FIR2_COEFF_4 + {0,SX1302_REG_RX_TOP_BASE_ADDR+113,0,1,8,0,1,11}, // RX_TOP_RX_DFE_FIR2_5_FIR2_COEFF_5 + {0,SX1302_REG_RX_TOP_BASE_ADDR+114,0,1,8,0,1,19}, // RX_TOP_RX_DFE_FIR2_6_FIR2_COEFF_6 + {0,SX1302_REG_RX_TOP_BASE_ADDR+115,0,1,8,0,1,10}, // RX_TOP_RX_DFE_FIR2_7_FIR2_COEFF_7 + {0,SX1302_REG_RX_TOP_BASE_ADDR+116,7,0,1,0,1,0}, // RX_TOP_RX_DFE_AGC0_RADIO_GAIN_RED_SEL + {0,SX1302_REG_RX_TOP_BASE_ADDR+116,0,0,7,0,1,0}, // RX_TOP_RX_DFE_AGC0_RADIO_GAIN_RED_DB + {0,SX1302_REG_RX_TOP_BASE_ADDR+117,4,0,1,0,1,0}, // RX_TOP_RX_DFE_AGC1_DC_COMP_EN + {0,SX1302_REG_RX_TOP_BASE_ADDR+117,3,0,1,0,1,0}, // RX_TOP_RX_DFE_AGC1_FORCE_DEFAULT_FIR + {0,SX1302_REG_RX_TOP_BASE_ADDR+117,2,0,1,0,1,1}, // RX_TOP_RX_DFE_AGC1_RSSI_EARLY_LATCH + {0,SX1302_REG_RX_TOP_BASE_ADDR+117,0,0,2,0,1,3}, // RX_TOP_RX_DFE_AGC1_FREEZE_ON_SYNC + {0,SX1302_REG_RX_TOP_BASE_ADDR+118,6,0,1,0,1,0}, // RX_TOP_RX_DFE_AGC2_DAGC_IN_COMP + {0,SX1302_REG_RX_TOP_BASE_ADDR+118,5,0,1,0,1,1}, // RX_TOP_RX_DFE_AGC2_DAGC_FIR_HYST + {0,SX1302_REG_RX_TOP_BASE_ADDR+118,3,0,2,0,1,1}, // RX_TOP_RX_DFE_AGC2_RSSI_MAX_SAMPLE + {0,SX1302_REG_RX_TOP_BASE_ADDR+118,0,0,3,0,1,2}, // RX_TOP_RX_DFE_AGC2_RSSI_MIN_SAMPLE + {0,SX1302_REG_RX_TOP_BASE_ADDR+119,7,0,1,0,1,0}, // RX_TOP_RX_DFE_GAIN0_DAGC_FIR_FAST + {0,SX1302_REG_RX_TOP_BASE_ADDR+119,6,0,1,0,1,0}, // RX_TOP_RX_DFE_GAIN0_FORCE_GAIN_FIR + {0,SX1302_REG_RX_TOP_BASE_ADDR+119,4,0,2,0,1,3}, // RX_TOP_RX_DFE_GAIN0_GAIN_FIR1 + {0,SX1302_REG_RX_TOP_BASE_ADDR+119,0,0,3,0,1,1}, // RX_TOP_RX_DFE_GAIN0_GAIN_FIR2 + {0,SX1302_REG_RX_TOP_BASE_ADDR+120,6,0,2,0,1,0}, // RX_TOP_DAGC_CFG_TARGET_LVL + {0,SX1302_REG_RX_TOP_BASE_ADDR+120,5,0,1,0,1,0}, // RX_TOP_DAGC_CFG_GAIN_INCR_STEP + {0,SX1302_REG_RX_TOP_BASE_ADDR+120,4,0,1,0,1,0}, // RX_TOP_DAGC_CFG_GAIN_DROP_COMP + {0,SX1302_REG_RX_TOP_BASE_ADDR+120,3,0,1,0,1,1}, // RX_TOP_DAGC_CFG_COMB_FILTER_EN + {0,SX1302_REG_RX_TOP_BASE_ADDR+120,2,0,1,0,1,0}, // RX_TOP_DAGC_CFG_NO_FREEZE_START + {0,SX1302_REG_RX_TOP_BASE_ADDR+120,0,0,2,0,1,3}, // RX_TOP_DAGC_CFG_FREEZE_ON_SYNC + {0,SX1302_REG_RX_TOP_BASE_ADDR+121,0,0,8,0,1,60}, // RX_TOP_DAGC_CNT0_SAMPLE + {0,SX1302_REG_RX_TOP_BASE_ADDR+122,0,0,8,0,1,6}, // RX_TOP_DAGC_CNT1_THR_M6 + {0,SX1302_REG_RX_TOP_BASE_ADDR+123,0,0,8,0,1,25}, // RX_TOP_DAGC_CNT2_THR_M12 + {0,SX1302_REG_RX_TOP_BASE_ADDR+124,0,0,8,0,1,42}, // RX_TOP_DAGC_CNT3_THR_M18 + {0,SX1302_REG_RX_TOP_BASE_ADDR+125,4,0,4,0,1,8}, // RX_TOP_DAGC_CNT4_GAIN + {0,SX1302_REG_RX_TOP_BASE_ADDR+125,0,0,1,0,1,0}, // RX_TOP_DAGC_CNT4_FORCE_GAIN + {0,SX1302_REG_RX_TOP_BASE_ADDR+126,6,0,2,0,1,2}, // RX_TOP_TXRX_CFG1_PPM_OFFSET_HDR_CTRL + {0,SX1302_REG_RX_TOP_BASE_ADDR+126,4,0,2,0,1,0}, // RX_TOP_TXRX_CFG1_PPM_OFFSET + {0,SX1302_REG_RX_TOP_BASE_ADDR+126,3,0,1,0,1,0}, // RX_TOP_TXRX_CFG1_MODEM_EN + {0,SX1302_REG_RX_TOP_BASE_ADDR+126,0,0,3,0,1,2}, // RX_TOP_TXRX_CFG1_CODING_RATE + {0,SX1302_REG_RX_TOP_BASE_ADDR+127,4,0,1,0,0,0}, // RX_TOP_TXRX_CFG2_MODEM_START + {0,SX1302_REG_RX_TOP_BASE_ADDR+127,2,0,2,0,1,1}, // RX_TOP_TXRX_CFG2_CADRXTX + {0,SX1302_REG_RX_TOP_BASE_ADDR+127,1,0,1,0,1,0}, // RX_TOP_TXRX_CFG2_IMPLICIT_HEADER + {0,SX1302_REG_RX_TOP_BASE_ADDR+127,0,0,1,0,1,1}, // RX_TOP_TXRX_CFG2_CRC_EN + {0,SX1302_REG_RX_TOP_BASE_ADDR+128,0,0,8,0,1,12}, // RX_TOP_TXRX_CFG3_PAYLOAD_LENGTH + {0,SX1302_REG_RX_TOP_BASE_ADDR+129,7,0,1,0,1,0}, // RX_TOP_TXRX_CFG4_INT_STEP_ORIDE_EN + {0,SX1302_REG_RX_TOP_BASE_ADDR+129,0,0,6,0,1,0}, // RX_TOP_TXRX_CFG4_INT_STEP_ORIDE + {0,SX1302_REG_RX_TOP_BASE_ADDR+130,6,0,1,0,1,0}, // RX_TOP_TXRX_CFG5_HEADER_DIFF_MODE + {0,SX1302_REG_RX_TOP_BASE_ADDR+130,0,0,6,0,1,0}, // RX_TOP_TXRX_CFG5_ZERO_PAD + {0,SX1302_REG_RX_TOP_BASE_ADDR+131,0,0,8,0,1,8}, // RX_TOP_TXRX_CFG6_PREAMBLE_SYMB_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+132,0,0,8,0,1,0}, // RX_TOP_TXRX_CFG7_PREAMBLE_SYMB_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+133,3,0,1,0,1,1}, // RX_TOP_TXRX_CFG8_AUTO_ACK_INT_DELAY + {0,SX1302_REG_RX_TOP_BASE_ADDR+133,2,0,1,0,1,0}, // RX_TOP_TXRX_CFG8_AUTO_ACK_RX + {0,SX1302_REG_RX_TOP_BASE_ADDR+133,1,0,1,0,1,0}, // RX_TOP_TXRX_CFG8_AUTO_ACK_TX + {0,SX1302_REG_RX_TOP_BASE_ADDR+133,0,0,1,0,1,0}, // RX_TOP_TXRX_CFG8_POST_PREAMBLE_GAP_LONG + {0,SX1302_REG_RX_TOP_BASE_ADDR+134,7,0,1,0,1,0}, // RX_TOP_TXRX_CFG9_FINE_SYNCH_EN_SF12 + {0,SX1302_REG_RX_TOP_BASE_ADDR+134,6,0,1,0,1,0}, // RX_TOP_TXRX_CFG9_FINE_SYNCH_EN_SF11 + {0,SX1302_REG_RX_TOP_BASE_ADDR+134,5,0,1,0,1,0}, // RX_TOP_TXRX_CFG9_FINE_SYNCH_EN_SF10 + {0,SX1302_REG_RX_TOP_BASE_ADDR+134,4,0,1,0,1,0}, // RX_TOP_TXRX_CFG9_FINE_SYNCH_EN_SF9 + {0,SX1302_REG_RX_TOP_BASE_ADDR+134,3,0,1,0,1,0}, // RX_TOP_TXRX_CFG9_FINE_SYNCH_EN_SF8 + {0,SX1302_REG_RX_TOP_BASE_ADDR+134,2,0,1,0,1,0}, // RX_TOP_TXRX_CFG9_FINE_SYNCH_EN_SF7 + {0,SX1302_REG_RX_TOP_BASE_ADDR+134,1,0,1,0,1,1}, // RX_TOP_TXRX_CFG9_FINE_SYNCH_EN_SF6 + {0,SX1302_REG_RX_TOP_BASE_ADDR+134,0,0,1,0,1,1}, // RX_TOP_TXRX_CFG9_FINE_SYNCH_EN_SF5 + {0,SX1302_REG_RX_TOP_BASE_ADDR+135,4,0,2,0,1,3}, // RX_TOP_RX_CFG0_DFT_PEAK_EN + {0,SX1302_REG_RX_TOP_BASE_ADDR+135,2,0,1,0,1,1}, // RX_TOP_RX_CFG0_CHIRP_INVERT + {0,SX1302_REG_RX_TOP_BASE_ADDR+135,1,0,1,0,1,0}, // RX_TOP_RX_CFG0_SWAP_IQ + {0,SX1302_REG_RX_TOP_BASE_ADDR+135,0,0,1,0,1,0}, // RX_TOP_RX_CFG0_CONTINUOUS + {0,SX1302_REG_RX_TOP_BASE_ADDR+136,0,0,8,0,1,0}, // RX_TOP_RX_CFG1_DETECT_TIMEOUT + {0,SX1302_REG_RX_TOP_BASE_ADDR+137,4,0,1,0,1,1}, // RX_TOP_RX_CFG2_CLK_EN_RESYNC_DIN + {0,SX1302_REG_RX_TOP_BASE_ADDR+137,0,0,4,0,1,11}, // RX_TOP_RX_CFG2_LLR_SCALE + {0,SX1302_REG_RX_TOP_BASE_ADDR+138,0,1,5,0,1,2}, // RX_TOP_FRAME_SYNCH0_SF5_PEAK1_POS_SF5 + {0,SX1302_REG_RX_TOP_BASE_ADDR+139,0,1,5,0,1,4}, // RX_TOP_FRAME_SYNCH1_SF5_PEAK2_POS_SF5 + {0,SX1302_REG_RX_TOP_BASE_ADDR+140,0,1,5,0,1,2}, // RX_TOP_FRAME_SYNCH0_SF6_PEAK1_POS_SF6 + {0,SX1302_REG_RX_TOP_BASE_ADDR+141,0,1,5,0,1,4}, // RX_TOP_FRAME_SYNCH1_SF6_PEAK2_POS_SF6 + {0,SX1302_REG_RX_TOP_BASE_ADDR+142,0,1,5,0,1,2}, // RX_TOP_FRAME_SYNCH0_SF7TO12_PEAK1_POS_SF7TO12 + {0,SX1302_REG_RX_TOP_BASE_ADDR+143,0,1,5,0,1,4}, // RX_TOP_FRAME_SYNCH1_SF7TO12_PEAK2_POS_SF7TO12 + {0,SX1302_REG_RX_TOP_BASE_ADDR+144,5,0,1,0,1,0}, // RX_TOP_FRAME_SYNCH2_FINETIME_ON_LAST + {0,SX1302_REG_RX_TOP_BASE_ADDR+144,4,0,1,0,1,1}, // RX_TOP_FRAME_SYNCH2_AUTO_SCALE + {0,SX1302_REG_RX_TOP_BASE_ADDR+144,3,0,1,0,1,0}, // RX_TOP_FRAME_SYNCH2_DROP_ON_SYNCH + {0,SX1302_REG_RX_TOP_BASE_ADDR+144,2,0,1,0,1,1}, // RX_TOP_FRAME_SYNCH2_GAIN + {0,SX1302_REG_RX_TOP_BASE_ADDR+144,0,0,2,0,1,3}, // RX_TOP_FRAME_SYNCH2_TIMEOUT_OPT + {0,SX1302_REG_RX_TOP_BASE_ADDR+145,7,0,1,0,1,1}, // RX_TOP_FINE_TIMING_A_0_GAIN_P_HDR_RED + {0,SX1302_REG_RX_TOP_BASE_ADDR+145,6,0,1,0,1,0}, // RX_TOP_FINE_TIMING_A_0_ROUNDING + {0,SX1302_REG_RX_TOP_BASE_ADDR+145,4,0,2,0,1,0}, // RX_TOP_FINE_TIMING_A_0_POS_LIMIT + {0,SX1302_REG_RX_TOP_BASE_ADDR+145,2,0,2,0,1,0}, // RX_TOP_FINE_TIMING_A_0_SUM_SIZE + {0,SX1302_REG_RX_TOP_BASE_ADDR+145,0,0,2,0,1,3}, // RX_TOP_FINE_TIMING_A_0_MODE + {0,SX1302_REG_RX_TOP_BASE_ADDR+146,6,0,2,0,1,1}, // RX_TOP_FINE_TIMING_A_1_GAIN_P_AUTO + {0,SX1302_REG_RX_TOP_BASE_ADDR+146,3,0,3,0,1,2}, // RX_TOP_FINE_TIMING_A_1_GAIN_P_PAYLOAD + {0,SX1302_REG_RX_TOP_BASE_ADDR+146,0,0,3,0,1,6}, // RX_TOP_FINE_TIMING_A_1_GAIN_P_PREAMB + {0,SX1302_REG_RX_TOP_BASE_ADDR+147,6,0,2,0,1,3}, // RX_TOP_FINE_TIMING_A_2_GAIN_I_AUTO + {0,SX1302_REG_RX_TOP_BASE_ADDR+147,3,0,3,0,1,1}, // RX_TOP_FINE_TIMING_A_2_GAIN_I_PAYLOAD + {0,SX1302_REG_RX_TOP_BASE_ADDR+147,0,0,3,0,1,4}, // RX_TOP_FINE_TIMING_A_2_GAIN_I_PREAMB + {0,SX1302_REG_RX_TOP_BASE_ADDR+148,7,0,1,0,1,0}, // RX_TOP_FINE_TIMING_A_3_FINESYNCH_SUM + {0,SX1302_REG_RX_TOP_BASE_ADDR+148,4,0,3,0,1,5}, // RX_TOP_FINE_TIMING_A_3_FINESYNCH_GAIN + {0,SX1302_REG_RX_TOP_BASE_ADDR+149,6,0,2,0,1,0}, // RX_TOP_FINE_TIMING_A_4_GAIN_I_EN_SF8 + {0,SX1302_REG_RX_TOP_BASE_ADDR+149,4,0,2,0,1,0}, // RX_TOP_FINE_TIMING_A_4_GAIN_I_EN_SF7 + {0,SX1302_REG_RX_TOP_BASE_ADDR+149,2,0,2,0,1,0}, // RX_TOP_FINE_TIMING_A_4_GAIN_I_EN_SF6 + {0,SX1302_REG_RX_TOP_BASE_ADDR+149,0,0,2,0,1,0}, // RX_TOP_FINE_TIMING_A_4_GAIN_I_EN_SF5 + {0,SX1302_REG_RX_TOP_BASE_ADDR+150,6,0,2,0,1,0}, // RX_TOP_FINE_TIMING_A_5_GAIN_I_EN_SF12 + {0,SX1302_REG_RX_TOP_BASE_ADDR+150,4,0,2,0,1,0}, // RX_TOP_FINE_TIMING_A_5_GAIN_I_EN_SF11 + {0,SX1302_REG_RX_TOP_BASE_ADDR+150,2,0,2,0,1,0}, // RX_TOP_FINE_TIMING_A_5_GAIN_I_EN_SF10 + {0,SX1302_REG_RX_TOP_BASE_ADDR+150,0,0,2,0,1,0}, // RX_TOP_FINE_TIMING_A_5_GAIN_I_EN_SF9 + {0,SX1302_REG_RX_TOP_BASE_ADDR+151,4,0,3,0,1,7}, // RX_TOP_FINE_TIMING_A_6_GAIN_P_PREAMB_SF12 + {0,SX1302_REG_RX_TOP_BASE_ADDR+151,0,0,3,0,1,4}, // RX_TOP_FINE_TIMING_A_6_GAIN_P_PREAMB_SF5_6 + {0,SX1302_REG_RX_TOP_BASE_ADDR+152,4,0,3,0,1,0}, // RX_TOP_FINE_TIMING_7_GAIN_I_AUTO_MAX + {0,SX1302_REG_RX_TOP_BASE_ADDR+152,0,0,3,0,1,0}, // RX_TOP_FINE_TIMING_7_GAIN_P_AUTO_MAX + {0,SX1302_REG_RX_TOP_BASE_ADDR+153,7,0,1,0,1,1}, // RX_TOP_FINE_TIMING_B_0_GAIN_P_HDR_RED + {0,SX1302_REG_RX_TOP_BASE_ADDR+153,6,0,1,0,1,0}, // RX_TOP_FINE_TIMING_B_0_ROUNDING + {0,SX1302_REG_RX_TOP_BASE_ADDR+153,4,0,2,0,1,0}, // RX_TOP_FINE_TIMING_B_0_POS_LIMIT + {0,SX1302_REG_RX_TOP_BASE_ADDR+153,2,0,2,0,1,0}, // RX_TOP_FINE_TIMING_B_0_SUM_SIZE + {0,SX1302_REG_RX_TOP_BASE_ADDR+153,0,0,2,0,1,3}, // RX_TOP_FINE_TIMING_B_0_MODE + {0,SX1302_REG_RX_TOP_BASE_ADDR+154,6,0,2,0,1,1}, // RX_TOP_FINE_TIMING_B_1_GAIN_P_AUTO + {0,SX1302_REG_RX_TOP_BASE_ADDR+154,3,0,3,0,1,2}, // RX_TOP_FINE_TIMING_B_1_GAIN_P_PAYLOAD + {0,SX1302_REG_RX_TOP_BASE_ADDR+154,0,0,3,0,1,6}, // RX_TOP_FINE_TIMING_B_1_GAIN_P_PREAMB + {0,SX1302_REG_RX_TOP_BASE_ADDR+155,6,0,2,0,1,3}, // RX_TOP_FINE_TIMING_B_2_GAIN_I_AUTO + {0,SX1302_REG_RX_TOP_BASE_ADDR+155,3,0,3,0,1,1}, // RX_TOP_FINE_TIMING_B_2_GAIN_I_PAYLOAD + {0,SX1302_REG_RX_TOP_BASE_ADDR+155,0,0,3,0,1,4}, // RX_TOP_FINE_TIMING_B_2_GAIN_I_PREAMB + {0,SX1302_REG_RX_TOP_BASE_ADDR+156,7,0,1,0,1,0}, // RX_TOP_FINE_TIMING_B_3_FINESYNCH_SUM + {0,SX1302_REG_RX_TOP_BASE_ADDR+156,4,0,3,0,1,5}, // RX_TOP_FINE_TIMING_B_3_FINESYNCH_GAIN + {0,SX1302_REG_RX_TOP_BASE_ADDR+157,6,0,2,0,1,0}, // RX_TOP_FINE_TIMING_B_4_GAIN_I_EN_SF8 + {0,SX1302_REG_RX_TOP_BASE_ADDR+157,4,0,2,0,1,0}, // RX_TOP_FINE_TIMING_B_4_GAIN_I_EN_SF7 + {0,SX1302_REG_RX_TOP_BASE_ADDR+157,2,0,2,0,1,0}, // RX_TOP_FINE_TIMING_B_4_GAIN_I_EN_SF6 + {0,SX1302_REG_RX_TOP_BASE_ADDR+157,0,0,2,0,1,0}, // RX_TOP_FINE_TIMING_B_4_GAIN_I_EN_SF5 + {0,SX1302_REG_RX_TOP_BASE_ADDR+158,6,0,2,0,1,0}, // RX_TOP_FINE_TIMING_B_5_GAIN_I_EN_SF12 + {0,SX1302_REG_RX_TOP_BASE_ADDR+158,4,0,2,0,1,0}, // RX_TOP_FINE_TIMING_B_5_GAIN_I_EN_SF11 + {0,SX1302_REG_RX_TOP_BASE_ADDR+158,2,0,2,0,1,0}, // RX_TOP_FINE_TIMING_B_5_GAIN_I_EN_SF10 + {0,SX1302_REG_RX_TOP_BASE_ADDR+158,0,0,2,0,1,0}, // RX_TOP_FINE_TIMING_B_5_GAIN_I_EN_SF9 + {0,SX1302_REG_RX_TOP_BASE_ADDR+159,4,0,3,0,1,7}, // RX_TOP_FINE_TIMING_B_6_GAIN_P_PREAMB_SF12 + {0,SX1302_REG_RX_TOP_BASE_ADDR+159,0,0,3,0,1,4}, // RX_TOP_FINE_TIMING_B_6_GAIN_P_PREAMB_SF5_6 + {0,SX1302_REG_RX_TOP_BASE_ADDR+160,0,0,4,0,1,9}, // RX_TOP_FREQ_TO_TIME0_FREQ_TO_TIME_DRIFT_MANT + {0,SX1302_REG_RX_TOP_BASE_ADDR+161,0,0,8,0,1,112}, // RX_TOP_FREQ_TO_TIME1_FREQ_TO_TIME_DRIFT_MANT + {0,SX1302_REG_RX_TOP_BASE_ADDR+162,0,0,3,0,1,3}, // RX_TOP_FREQ_TO_TIME2_FREQ_TO_TIME_DRIFT_EXP + {0,SX1302_REG_RX_TOP_BASE_ADDR+163,5,0,1,0,1,0}, // RX_TOP_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_FREQ_DELTA + {0,SX1302_REG_RX_TOP_BASE_ADDR+163,4,0,1,0,1,0}, // RX_TOP_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_FINE_DELTA + {0,SX1302_REG_RX_TOP_BASE_ADDR+163,3,0,1,0,1,1}, // RX_TOP_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_FREQ_ERROR + {0,SX1302_REG_RX_TOP_BASE_ADDR+163,2,0,1,0,1,0}, // RX_TOP_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_TIME_SYMB + {0,SX1302_REG_RX_TOP_BASE_ADDR+163,1,0,1,0,1,0}, // RX_TOP_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_TIME_OFFSET + {0,SX1302_REG_RX_TOP_BASE_ADDR+163,0,0,1,0,1,1}, // RX_TOP_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_DETECT + {0,SX1302_REG_RX_TOP_BASE_ADDR+164,0,0,8,0,1,33}, // RX_TOP_FREQ_TO_TIME4_FREQ_TO_TIME_INVERT_RNG + {0,SX1302_REG_RX_TOP_BASE_ADDR+165,6,0,2,0,1,3}, // RX_TOP_FREQ_TRACK_A_0_FREQ_TRACK_EN_SF8 + {0,SX1302_REG_RX_TOP_BASE_ADDR+165,4,0,2,0,1,3}, // RX_TOP_FREQ_TRACK_A_0_FREQ_TRACK_EN_SF7 + {0,SX1302_REG_RX_TOP_BASE_ADDR+165,2,0,2,0,1,3}, // RX_TOP_FREQ_TRACK_A_0_FREQ_TRACK_EN_SF6 + {0,SX1302_REG_RX_TOP_BASE_ADDR+165,0,0,2,0,1,3}, // RX_TOP_FREQ_TRACK_A_0_FREQ_TRACK_EN_SF5 + {0,SX1302_REG_RX_TOP_BASE_ADDR+166,6,0,2,0,1,3}, // RX_TOP_FREQ_TRACK_A_1_FREQ_TRACK_EN_SF12 + {0,SX1302_REG_RX_TOP_BASE_ADDR+166,4,0,2,0,1,3}, // RX_TOP_FREQ_TRACK_A_1_FREQ_TRACK_EN_SF11 + {0,SX1302_REG_RX_TOP_BASE_ADDR+166,2,0,2,0,1,3}, // RX_TOP_FREQ_TRACK_A_1_FREQ_TRACK_EN_SF10 + {0,SX1302_REG_RX_TOP_BASE_ADDR+166,0,0,2,0,1,3}, // RX_TOP_FREQ_TRACK_A_1_FREQ_TRACK_EN_SF9 + {0,SX1302_REG_RX_TOP_BASE_ADDR+167,6,0,2,0,1,3}, // RX_TOP_FREQ_TRACK_B_0_FREQ_TRACK_EN_SF8 + {0,SX1302_REG_RX_TOP_BASE_ADDR+167,4,0,2,0,1,3}, // RX_TOP_FREQ_TRACK_B_0_FREQ_TRACK_EN_SF7 + {0,SX1302_REG_RX_TOP_BASE_ADDR+167,2,0,2,0,1,3}, // RX_TOP_FREQ_TRACK_B_0_FREQ_TRACK_EN_SF6 + {0,SX1302_REG_RX_TOP_BASE_ADDR+167,0,0,2,0,1,3}, // RX_TOP_FREQ_TRACK_B_0_FREQ_TRACK_EN_SF5 + {0,SX1302_REG_RX_TOP_BASE_ADDR+168,6,0,2,0,1,3}, // RX_TOP_FREQ_TRACK_B_1_FREQ_TRACK_EN_SF12 + {0,SX1302_REG_RX_TOP_BASE_ADDR+168,4,0,2,0,1,3}, // RX_TOP_FREQ_TRACK_B_1_FREQ_TRACK_EN_SF11 + {0,SX1302_REG_RX_TOP_BASE_ADDR+168,2,0,2,0,1,3}, // RX_TOP_FREQ_TRACK_B_1_FREQ_TRACK_EN_SF10 + {0,SX1302_REG_RX_TOP_BASE_ADDR+168,0,0,2,0,1,3}, // RX_TOP_FREQ_TRACK_B_1_FREQ_TRACK_EN_SF9 + {0,SX1302_REG_RX_TOP_BASE_ADDR+169,7,0,1,0,1,0}, // RX_TOP_FREQ_TRACK2_FREQ_TRACK_FINE + {0,SX1302_REG_RX_TOP_BASE_ADDR+169,4,0,3,0,1,4}, // RX_TOP_FREQ_TRACK2_FREQ_TRACK_HDR_SKIP + {0,SX1302_REG_RX_TOP_BASE_ADDR+170,4,0,3,0,1,5}, // RX_TOP_FREQ_TRACK3_FREQ_SYNCH_GAIN + {0,SX1302_REG_RX_TOP_BASE_ADDR+170,0,0,4,0,1,3}, // RX_TOP_FREQ_TRACK3_FREQ_TRACK_AUTO_THR + {0,SX1302_REG_RX_TOP_BASE_ADDR+171,5,0,2,0,1,1}, // RX_TOP_FREQ_TRACK4_SNR_MIN_WINDOW + {0,SX1302_REG_RX_TOP_BASE_ADDR+171,4,0,1,0,1,0}, // RX_TOP_FREQ_TRACK4_GAIN_AUTO_SNR_MIN + {0,SX1302_REG_RX_TOP_BASE_ADDR+171,0,0,4,0,1,8}, // RX_TOP_FREQ_TRACK4_FREQ_SYNCH_THR + {0,SX1302_REG_RX_TOP_BASE_ADDR+172,0,0,7,0,1,32}, // RX_TOP_DETECT_MSP0_MSP_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+173,0,0,7,0,1,48}, // RX_TOP_DETECT_MSP1_MSP2_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+174,4,0,3,0,1,5}, // RX_TOP_DETECT_MSP2_MSP2_PEAK_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+174,0,0,3,0,1,3}, // RX_TOP_DETECT_MSP2_MSP_PEAK_NB + {0,SX1302_REG_RX_TOP_BASE_ADDR+175,6,0,1,0,1,0}, // RX_TOP_DETECT_MSP3_ACC_MIN2 + {0,SX1302_REG_RX_TOP_BASE_ADDR+175,4,0,2,0,1,2}, // RX_TOP_DETECT_MSP3_ACC_WIN_LEN + {0,SX1302_REG_RX_TOP_BASE_ADDR+175,2,0,1,0,1,1}, // RX_TOP_DETECT_MSP3_MSP_POS_SEL + {0,SX1302_REG_RX_TOP_BASE_ADDR+175,0,0,2,0,1,1}, // RX_TOP_DETECT_MSP3_MSP_CNT_MODE + {0,SX1302_REG_RX_TOP_BASE_ADDR+176,7,0,1,0,1,0}, // RX_TOP_DETECT_ACC1_USE_GAIN_SYMB + {0,SX1302_REG_RX_TOP_BASE_ADDR+176,0,0,7,0,1,64}, // RX_TOP_DETECT_ACC1_ACC_PNR + {0,SX1302_REG_RX_TOP_BASE_ADDR+177,6,0,2,0,1,2}, // RX_TOP_DETECT_ACC2_NOISE_COEFF + {0,SX1302_REG_RX_TOP_BASE_ADDR+177,4,0,2,0,1,2}, // RX_TOP_DETECT_ACC2_ACC_COEFF + {0,SX1302_REG_RX_TOP_BASE_ADDR+177,3,0,1,0,1,1}, // RX_TOP_DETECT_ACC2_ACC_2_SAME_PEAKS + {0,SX1302_REG_RX_TOP_BASE_ADDR+177,2,0,1,0,1,1}, // RX_TOP_DETECT_ACC2_ACC_AUTO_RESCALE + {0,SX1302_REG_RX_TOP_BASE_ADDR+177,1,0,1,0,1,1}, // RX_TOP_DETECT_ACC2_ACC_PEAK_POS_SEL + {0,SX1302_REG_RX_TOP_BASE_ADDR+177,0,0,1,0,1,1}, // RX_TOP_DETECT_ACC2_ACC_PEAK_SUM_EN + {0,SX1302_REG_RX_TOP_BASE_ADDR+178,0,0,8,0,1,11}, // RX_TOP_DETECT_ACC3_MIN_SINGLE_PEAK + {0,SX1302_REG_RX_TOP_BASE_ADDR+179,4,0,1,0,1,0}, // RX_TOP_TIMESTAMP_SEL_SNR_MIN + {0,SX1302_REG_RX_TOP_BASE_ADDR+179,3,0,1,0,1,0}, // RX_TOP_TIMESTAMP_ENABLE + {0,SX1302_REG_RX_TOP_BASE_ADDR+179,0,0,3,0,1,7}, // RX_TOP_TIMESTAMP_NB_SYMB + {0,SX1302_REG_RX_TOP_BASE_ADDR+180,0,0,8,1,1,0}, // RX_TOP_MODEM_BUSY_MSB_RX_MODEM_BUSY + {0,SX1302_REG_RX_TOP_BASE_ADDR+181,0,0,8,1,1,0}, // RX_TOP_MODEM_BUSY_LSB_RX_MODEM_BUSY + {0,SX1302_REG_RX_TOP_BASE_ADDR+182,4,0,4,1,1,0}, // RX_TOP_MODEM_STATE_RX_MODEM_STS_SPARE + {0,SX1302_REG_RX_TOP_BASE_ADDR+182,0,0,4,1,1,0}, // RX_TOP_MODEM_STATE_RX_MODEM_STATE + {0,SX1302_REG_RX_TOP_BASE_ADDR+183,6,0,2,0,1,2}, // RX_TOP_MODEM_SYNC_DELTA_MSB_PEAK_POS_FINE_GAIN_H + {0,SX1302_REG_RX_TOP_BASE_ADDR+183,4,0,2,0,1,3}, // RX_TOP_MODEM_SYNC_DELTA_MSB_PEAK_POS_FINE_GAIN_L + {0,SX1302_REG_RX_TOP_BASE_ADDR+183,3,0,1,0,1,1}, // RX_TOP_MODEM_SYNC_DELTA_MSB_PEAK_POS_FINE_SIGN + {0,SX1302_REG_RX_TOP_BASE_ADDR+183,0,0,3,0,1,0}, // RX_TOP_MODEM_SYNC_DELTA_MSB_MODEM_SYNC_DELTA + {0,SX1302_REG_RX_TOP_BASE_ADDR+184,0,0,8,0,1,127}, // RX_TOP_MODEM_SYNC_DELTA_LSB_MODEM_SYNC_DELTA + {0,SX1302_REG_RX_TOP_BASE_ADDR+185,6,0,2,0,1,0}, // RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF8 + {0,SX1302_REG_RX_TOP_BASE_ADDR+185,4,0,2,0,1,0}, // RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF7 + {0,SX1302_REG_RX_TOP_BASE_ADDR+185,2,0,2,0,1,0}, // RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF6 + {0,SX1302_REG_RX_TOP_BASE_ADDR+185,0,0,2,0,1,0}, // RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF5 + {0,SX1302_REG_RX_TOP_BASE_ADDR+186,6,0,2,0,1,1}, // RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF12 + {0,SX1302_REG_RX_TOP_BASE_ADDR+186,4,0,2,0,1,1}, // RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF11 + {0,SX1302_REG_RX_TOP_BASE_ADDR+186,2,0,2,0,1,0}, // RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF10 + {0,SX1302_REG_RX_TOP_BASE_ADDR+186,0,0,2,0,1,0}, // RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF9 + {0,SX1302_REG_RX_TOP_BASE_ADDR+187,0,0,8,0,1,85}, // RX_TOP_MODEM_CLOCK_GATE_OVERRIDE_3_CLK_OVERRIDE + {0,SX1302_REG_RX_TOP_BASE_ADDR+188,0,0,8,0,1,85}, // RX_TOP_MODEM_CLOCK_GATE_OVERRIDE_2_CLK_OVERRIDE + {0,SX1302_REG_RX_TOP_BASE_ADDR+189,0,0,8,0,1,85}, // RX_TOP_MODEM_CLOCK_GATE_OVERRIDE_1_CLK_OVERRIDE + {0,SX1302_REG_RX_TOP_BASE_ADDR+190,0,0,8,0,1,85}, // RX_TOP_MODEM_CLOCK_GATE_OVERRIDE_0_CLK_OVERRIDE + {0,SX1302_REG_RX_TOP_BASE_ADDR+191,0,0,1,1,1,0}, // RX_TOP_DUMMY2_DUMMY2 + {0,SX1302_REG_RX_TOP_BASE_ADDR+192,4,0,1,0,1,0}, // RX_TOP_RX_BUFFER_DEBUG_MODE + {0,SX1302_REG_RX_TOP_BASE_ADDR+192,3,0,1,0,1,0}, // RX_TOP_RX_BUFFER_DIRECT_RAM_IF + {0,SX1302_REG_RX_TOP_BASE_ADDR+192,2,0,1,0,1,0}, // RX_TOP_RX_BUFFER_LEGACY_TIMESTAMP + {0,SX1302_REG_RX_TOP_BASE_ADDR+192,1,0,1,0,1,0}, // RX_TOP_RX_BUFFER_STORE_HEADER_ERR_META + {0,SX1302_REG_RX_TOP_BASE_ADDR+192,0,0,1,0,1,0}, // RX_TOP_RX_BUFFER_STORE_SYNC_FAIL_META + {0,SX1302_REG_RX_TOP_BASE_ADDR+193,0,0,8,0,1,255}, // RX_TOP_RX_BUFFER_TIMESTAMP_CFG_MAX_TS_METRICS + {0,SX1302_REG_RX_TOP_BASE_ADDR+194,0,0,5,0,1,0}, // RX_TOP_RX_BUFFER_IRQ_CTRL_MSB_RX_BUFFER_IRQ_THRESHOLD + {0,SX1302_REG_RX_TOP_BASE_ADDR+195,0,0,8,0,1,21}, // RX_TOP_RX_BUFFER_IRQ_CTRL_LSB_RX_BUFFER_IRQ_THRESHOLD + {0,SX1302_REG_RX_TOP_BASE_ADDR+196,0,0,4,1,1,0}, // RX_TOP_RX_BUFFER_LAST_ADDR_READ_MSB_LAST_ADDR_READ + {0,SX1302_REG_RX_TOP_BASE_ADDR+197,0,0,8,1,1,0}, // RX_TOP_RX_BUFFER_LAST_ADDR_READ_LSB_LAST_ADDR_READ + {0,SX1302_REG_RX_TOP_BASE_ADDR+198,0,0,4,1,1,0}, // RX_TOP_RX_BUFFER_LAST_ADDR_WRITE_MSB_LAST_ADDR_WRITE + {0,SX1302_REG_RX_TOP_BASE_ADDR+199,0,0,8,1,1,0}, // RX_TOP_RX_BUFFER_LAST_ADDR_WRITE_LSB_LAST_ADDR_WRITE + {0,SX1302_REG_RX_TOP_BASE_ADDR+200,0,0,5,1,1,0}, // RX_TOP_RX_BUFFER_NB_BYTES_MSB_RX_BUFFER_NB_BYTES + {0,SX1302_REG_RX_TOP_BASE_ADDR+201,0,0,8,1,1,0}, // RX_TOP_RX_BUFFER_NB_BYTES_LSB_RX_BUFFER_NB_BYTES + {0,SX1302_REG_RX_TOP_BASE_ADDR+202,0,0,8,1,1,0}, // RX_TOP_MULTI_SF_SYNC_ERR_PKT_CNT_MULTI_SF_SYNC_ERR_PKTS + {0,SX1302_REG_RX_TOP_BASE_ADDR+203,0,0,8,1,1,0}, // RX_TOP_MULTI_SF_PLD_ERR_PKT_CNT_MULTI_SF_PLD_ERR_PKTS + {0,SX1302_REG_RX_TOP_BASE_ADDR+204,0,0,8,1,1,0}, // RX_TOP_MULTI_SF_GOOD_PKT_CNT_MULTI_SF_GOOD_PKTS + {0,SX1302_REG_RX_TOP_BASE_ADDR+205,0,0,8,1,1,0}, // RX_TOP_SERV_MODEM_SYNC_ERR_PKT_CNT_SERV_MODEM_SYNC_ERR_PKTS + {0,SX1302_REG_RX_TOP_BASE_ADDR+206,0,0,8,1,1,0}, // RX_TOP_SERV_MODEM_PLD_ERR_PKT_CNT_SERV_MODEM_PLD_ERR_PKTS + {0,SX1302_REG_RX_TOP_BASE_ADDR+207,0,0,8,1,1,0}, // RX_TOP_SERV_MODEM_GOOD_PKT_CNT_SERV_MODEM_GOOD_PKTS + {0,SX1302_REG_RX_TOP_BASE_ADDR+208,0,0,8,1,1,0}, // RX_TOP_GFSK_MODEM_SYNC_ERR_PKT_CNT_GFSK_MODEM_SYNC_ERR_PKTS + {0,SX1302_REG_RX_TOP_BASE_ADDR+209,0,0,8,1,1,0}, // RX_TOP_GFSK_MODEM_PLD_ERR_PKT_CNT_GFSK_MODEM_PLD_ERR_PKTS + {0,SX1302_REG_RX_TOP_BASE_ADDR+210,0,0,8,1,1,0}, // RX_TOP_GFSK_MODEM_GOOD_PKT_CNT_GFSK_MODEM_GOOD_PKTS + {0,SX1302_REG_RX_TOP_BASE_ADDR+211,0,0,2,1,1,0}, // RX_TOP_BAD_MODEM_ID_WRITE_0_BAD_MODEM_ID_WRITE + {0,SX1302_REG_RX_TOP_BASE_ADDR+212,0,0,8,1,1,0}, // RX_TOP_BAD_MODEM_ID_WRITE_1_BAD_MODEM_ID_WRITE + {0,SX1302_REG_RX_TOP_BASE_ADDR+213,0,0,8,1,1,0}, // RX_TOP_BAD_MODEM_ID_WRITE_2_BAD_MODEM_ID_WRITE + {0,SX1302_REG_RX_TOP_BASE_ADDR+214,0,0,2,1,1,0}, // RX_TOP_BAD_MODEM_ID_READ_0_BAD_MODEM_ID_READ + {0,SX1302_REG_RX_TOP_BASE_ADDR+215,0,0,8,1,1,0}, // RX_TOP_BAD_MODEM_ID_READ_1_BAD_MODEM_ID_READ + {0,SX1302_REG_RX_TOP_BASE_ADDR+216,0,0,8,1,1,0}, // RX_TOP_BAD_MODEM_ID_READ_2_BAD_MODEM_ID_READ + {0,SX1302_REG_RX_TOP_BASE_ADDR+217,0,0,2,0,1,1}, // RX_TOP_CLOCK_GATE_OVERRIDE_0_CLK_OVERRIDE + {0,SX1302_REG_RX_TOP_BASE_ADDR+218,0,0,8,1,1,0}, // RX_TOP_SAMPLE_4_MSPS_LATCHED_125K_SAMPLE_4_MSPS_LATCHED_125K + {0,SX1302_REG_RX_TOP_BASE_ADDR+219,0,0,1,1,1,0}, // RX_TOP_DUMMY3_DUMMY3 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+0,5,0,1,0,1,1}, // ARB_MCU_CTRL_CLK_EN + {0,SX1302_REG_ARB_MCU_BASE_ADDR+0,4,0,1,0,1,0}, // ARB_MCU_CTRL_RADIO_RST + {0,SX1302_REG_ARB_MCU_BASE_ADDR+0,3,0,1,0,1,0}, // ARB_MCU_CTRL_FORCE_HOST_FE_CTRL + {0,SX1302_REG_ARB_MCU_BASE_ADDR+0,2,0,1,0,1,1}, // ARB_MCU_CTRL_MCU_CLEAR + {0,SX1302_REG_ARB_MCU_BASE_ADDR+0,1,0,1,0,1,0}, // ARB_MCU_CTRL_HOST_PROG + {0,SX1302_REG_ARB_MCU_BASE_ADDR+0,0,0,1,1,1,0}, // ARB_MCU_CTRL_PARITY_ERROR + {0,SX1302_REG_ARB_MCU_BASE_ADDR+1,0,0,8,1,1,0}, // ARB_MCU_MCU_ARB_STATUS_MCU_ARB_STATUS + {0,SX1302_REG_ARB_MCU_BASE_ADDR+7,5,0,1,0,1,0}, // ARB_MCU_UART_CFG_MSBF + {0,SX1302_REG_ARB_MCU_BASE_ADDR+7,4,0,1,0,1,0}, // ARB_MCU_UART_CFG_PAR_EN + {0,SX1302_REG_ARB_MCU_BASE_ADDR+7,3,0,1,0,1,0}, // ARB_MCU_UART_CFG_PAR_MODE + {0,SX1302_REG_ARB_MCU_BASE_ADDR+7,2,0,1,0,1,0}, // ARB_MCU_UART_CFG_START_LEN + {0,SX1302_REG_ARB_MCU_BASE_ADDR+7,1,0,1,0,1,0}, // ARB_MCU_UART_CFG_STOP_LEN + {0,SX1302_REG_ARB_MCU_BASE_ADDR+7,0,0,1,0,1,1}, // ARB_MCU_UART_CFG_WORD_LEN + {0,SX1302_REG_ARB_MCU_BASE_ADDR+8,0,0,8,0,1,0}, // ARB_MCU_UART_CFG2_BIT_RATE + {0,SX1302_REG_ARB_MCU_BASE_ADDR+9,0,0,8,0,1,0}, // ARB_MCU_ARB_DEBUG_CFG_0_ARB_DEBUG_CFG_0 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+10,0,0,8,0,1,0}, // ARB_MCU_ARB_DEBUG_CFG_1_ARB_DEBUG_CFG_1 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+11,0,0,8,0,1,0}, // ARB_MCU_ARB_DEBUG_CFG_2_ARB_DEBUG_CFG_2 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+12,0,0,8,0,1,0}, // ARB_MCU_ARB_DEBUG_CFG_3_ARB_DEBUG_CFG_3 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+13,0,0,8,1,1,0}, // ARB_MCU_ARB_DEBUG_STS_0_ARB_DEBUG_STS_0 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+14,0,0,8,1,1,0}, // ARB_MCU_ARB_DEBUG_STS_1_ARB_DEBUG_STS_1 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+15,0,0,8,1,1,0}, // ARB_MCU_ARB_DEBUG_STS_2_ARB_DEBUG_STS_2 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+16,0,0,8,1,1,0}, // ARB_MCU_ARB_DEBUG_STS_3_ARB_DEBUG_STS_3 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+17,0,0,8,1,1,0}, // ARB_MCU_ARB_DEBUG_STS_4_ARB_DEBUG_STS_4 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+18,0,0,8,1,1,0}, // ARB_MCU_ARB_DEBUG_STS_5_ARB_DEBUG_STS_5 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+19,0,0,8,1,1,0}, // ARB_MCU_ARB_DEBUG_STS_6_ARB_DEBUG_STS_6 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+20,0,0,8,1,1,0}, // ARB_MCU_ARB_DEBUG_STS_7_ARB_DEBUG_STS_7 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+21,0,0,8,1,1,0}, // ARB_MCU_ARB_DEBUG_STS_8_ARB_DEBUG_STS_8 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+22,0,0,8,1,1,0}, // ARB_MCU_ARB_DEBUG_STS_9_ARB_DEBUG_STS_9 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+23,0,0,8,1,1,0}, // ARB_MCU_ARB_DEBUG_STS_10_ARB_DEBUG_STS_10 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+24,0,0,8,1,1,0}, // ARB_MCU_ARB_DEBUG_STS_11_ARB_DEBUG_STS_11 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+25,0,0,8,1,1,0}, // ARB_MCU_ARB_DEBUG_STS_12_ARB_DEBUG_STS_12 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+26,0,0,8,1,1,0}, // ARB_MCU_ARB_DEBUG_STS_13_ARB_DEBUG_STS_13 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+27,0,0,8,1,1,0}, // ARB_MCU_ARB_DEBUG_STS_14_ARB_DEBUG_STS_14 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+28,0,0,8,1,1,0}, // ARB_MCU_ARB_DEBUG_STS_15_ARB_DEBUG_STS_15 + {0,SX1302_REG_ARB_MCU_BASE_ADDR+29,4,0,4,0,1,0}, // ARB_MCU_CHANNEL_SYNC_OFFSET_01_CHANNEL_1_OFFSET + {0,SX1302_REG_ARB_MCU_BASE_ADDR+29,0,0,4,0,1,0}, // ARB_MCU_CHANNEL_SYNC_OFFSET_01_CHANNEL_0_OFFSET + {0,SX1302_REG_ARB_MCU_BASE_ADDR+30,4,0,4,0,1,0}, // ARB_MCU_CHANNEL_SYNC_OFFSET_23_CHANNEL_3_OFFSET + {0,SX1302_REG_ARB_MCU_BASE_ADDR+30,0,0,4,0,1,0}, // ARB_MCU_CHANNEL_SYNC_OFFSET_23_CHANNEL_2_OFFSET + {0,SX1302_REG_ARB_MCU_BASE_ADDR+31,4,0,4,0,1,0}, // ARB_MCU_CHANNEL_SYNC_OFFSET_45_CHANNEL_5_OFFSET + {0,SX1302_REG_ARB_MCU_BASE_ADDR+31,0,0,4,0,1,0}, // ARB_MCU_CHANNEL_SYNC_OFFSET_45_CHANNEL_4_OFFSET + {0,SX1302_REG_ARB_MCU_BASE_ADDR+32,4,0,4,0,1,0}, // ARB_MCU_CHANNEL_SYNC_OFFSET_67_CHANNEL_7_OFFSET + {0,SX1302_REG_ARB_MCU_BASE_ADDR+32,0,0,4,0,1,0}, // ARB_MCU_CHANNEL_SYNC_OFFSET_67_CHANNEL_6_OFFSET + {0,SX1302_REG_ARB_MCU_BASE_ADDR+33,0,0,1,1,1,0}, // ARB_MCU_DUMMY_DUMMY3 + {0,SX1302_REG_RADIO_FE_BASE_ADDR+0,1,0,1,0,1,0}, // RADIO_FE_GLBL_CTRL_DECIM_B_CLR + {0,SX1302_REG_RADIO_FE_BASE_ADDR+0,0,0,1,0,1,0}, // RADIO_FE_GLBL_CTRL_DECIM_A_CLR + {0,SX1302_REG_RADIO_FE_BASE_ADDR+1,5,0,1,0,1,0}, // RADIO_FE_CTRL0_RADIO_A_DC_NOTCH_EN + {0,SX1302_REG_RADIO_FE_BASE_ADDR+1,4,0,1,0,1,0}, // RADIO_FE_CTRL0_RADIO_A_FORCE_HOST_FILTER_GAIN + {0,SX1302_REG_RADIO_FE_BASE_ADDR+1,0,0,4,0,1,0}, // RADIO_FE_CTRL0_RADIO_A_HOST_FILTER_GAIN + {0,SX1302_REG_RADIO_FE_BASE_ADDR+2,0,0,8,0,1,0}, // RADIO_FE_RSSI_DB_DEF_RADIO_A_RSSI_DB_DEFAULT_VALUE + {0,SX1302_REG_RADIO_FE_BASE_ADDR+3,0,0,8,0,1,0}, // RADIO_FE_RSSI_DEC_DEF_RADIO_A_RSSI_DEC_DEFAULT_VALUE + {0,SX1302_REG_RADIO_FE_BASE_ADDR+4,0,0,8,1,1,0}, // RADIO_FE_RSSI_DEC_RD_RADIO_A_RSSI_DEC_OUT + {0,SX1302_REG_RADIO_FE_BASE_ADDR+5,0,0,8,1,1,0}, // RADIO_FE_RSSI_BB_RD_RADIO_A_RSSI_BB_OUT + {0,SX1302_REG_RADIO_FE_BASE_ADDR+6,0,0,4,1,1,0}, // RADIO_FE_DEC_FILTER_RD_RADIO_A_DEC_FILTER_GAIN + {0,SX1302_REG_RADIO_FE_BASE_ADDR+7,0,0,5,0,1,0}, // RADIO_FE_RSSI_BB_FILTER_ALPHA_RADIO_A_RSSI_BB_FILTER_ALPHA + {0,SX1302_REG_RADIO_FE_BASE_ADDR+8,0,0,5,0,1,0}, // RADIO_FE_RSSI_DEC_FILTER_ALPHA_RADIO_A_RSSI_DEC_FILTER_ALPHA + {0,SX1302_REG_RADIO_FE_BASE_ADDR+9,0,0,6,0,1,0}, // RADIO_FE_IQ_COMP_AMP_COEFF_RADIO_A_AMP_COEFF + {0,SX1302_REG_RADIO_FE_BASE_ADDR+10,0,0,6,0,1,0}, // RADIO_FE_IQ_COMP_PHI_COEFF_RADIO_A_PHI_COEFF + {0,SX1302_REG_RADIO_FE_BASE_ADDR+11,0,0,6,0,1,0}, // RADIO_FE_RADIO_DIO_TEST_MODE_RADIO_A_DIO_TEST_MODE + {0,SX1302_REG_RADIO_FE_BASE_ADDR+12,0,0,6,0,1,0}, // RADIO_FE_RADIO_DIO_TEST_DIR_RADIO_A_DIO_TEST_DIR + {0,SX1302_REG_RADIO_FE_BASE_ADDR+13,0,0,6,1,1,0}, // RADIO_FE_RADIO_DIO_DIR_RADIO_A_DIO_DIR + {0,SX1302_REG_RADIO_FE_BASE_ADDR+14,5,0,1,0,1,0}, // RADIO_FE_CTRL0_RADIO_B_DC_NOTCH_EN + {0,SX1302_REG_RADIO_FE_BASE_ADDR+14,4,0,1,0,1,0}, // RADIO_FE_CTRL0_RADIO_B_FORCE_HOST_FILTER_GAIN + {0,SX1302_REG_RADIO_FE_BASE_ADDR+14,0,0,4,0,1,0}, // RADIO_FE_CTRL0_RADIO_B_HOST_FILTER_GAIN + {0,SX1302_REG_RADIO_FE_BASE_ADDR+15,0,0,8,0,1,0}, // RADIO_FE_RSSI_DB_DEF_RADIO_B_RSSI_DB_DEFAULT_VALUE + {0,SX1302_REG_RADIO_FE_BASE_ADDR+16,0,0,8,0,1,0}, // RADIO_FE_RSSI_DEC_DEF_RADIO_B_RSSI_DEC_DEFAULT_VALUE + {0,SX1302_REG_RADIO_FE_BASE_ADDR+17,0,0,8,1,1,0}, // RADIO_FE_RSSI_DEC_RD_RADIO_B_RSSI_DEC_OUT + {0,SX1302_REG_RADIO_FE_BASE_ADDR+18,0,0,8,1,1,0}, // RADIO_FE_RSSI_BB_RD_RADIO_B_RSSI_BB_OUT + {0,SX1302_REG_RADIO_FE_BASE_ADDR+19,0,0,4,1,1,0}, // RADIO_FE_DEC_FILTER_RD_RADIO_B_DEC_FILTER_GAIN + {0,SX1302_REG_RADIO_FE_BASE_ADDR+20,0,0,5,0,1,0}, // RADIO_FE_RSSI_BB_FILTER_ALPHA_RADIO_B_RSSI_BB_FILTER_ALPHA + {0,SX1302_REG_RADIO_FE_BASE_ADDR+21,0,0,5,0,1,0}, // RADIO_FE_RSSI_DEC_FILTER_ALPHA_RADIO_B_RSSI_DEC_FILTER_ALPHA + {0,SX1302_REG_RADIO_FE_BASE_ADDR+22,0,0,6,0,1,0}, // RADIO_FE_IQ_COMP_AMP_COEFF_RADIO_B_AMP_COEFF + {0,SX1302_REG_RADIO_FE_BASE_ADDR+23,0,0,6,0,1,0}, // RADIO_FE_IQ_COMP_PHI_COEFF_RADIO_B_PHI_COEFF + {0,SX1302_REG_RADIO_FE_BASE_ADDR+24,0,0,6,0,1,0}, // RADIO_FE_RADIO_DIO_TEST_MODE_RADIO_B_DIO_TEST_MODE + {0,SX1302_REG_RADIO_FE_BASE_ADDR+25,0,0,6,0,1,0}, // RADIO_FE_RADIO_DIO_TEST_DIR_RADIO_B_DIO_TEST_DIR + {0,SX1302_REG_RADIO_FE_BASE_ADDR+26,0,0,6,1,1,0}, // RADIO_FE_RADIO_DIO_DIR_RADIO_B_DIO_DIR + {0,SX1302_REG_RADIO_FE_BASE_ADDR+27,7,0,1,1,1,0}, // RADIO_FE_SIG_ANA_CFG_VALID + {0,SX1302_REG_RADIO_FE_BASE_ADDR+27,6,0,1,1,1,0}, // RADIO_FE_SIG_ANA_CFG_BUSY + {0,SX1302_REG_RADIO_FE_BASE_ADDR+27,4,0,2,0,1,0}, // RADIO_FE_SIG_ANA_CFG_DURATION + {0,SX1302_REG_RADIO_FE_BASE_ADDR+27,3,0,1,0,1,0}, // RADIO_FE_SIG_ANA_CFG_FORCE_HAL_CTRL + {0,SX1302_REG_RADIO_FE_BASE_ADDR+27,2,0,1,0,1,0}, // RADIO_FE_SIG_ANA_CFG_START + {0,SX1302_REG_RADIO_FE_BASE_ADDR+27,1,0,1,0,1,0}, // RADIO_FE_SIG_ANA_CFG_RADIO_SEL + {0,SX1302_REG_RADIO_FE_BASE_ADDR+27,0,0,1,0,1,0}, // RADIO_FE_SIG_ANA_CFG_EN + {0,SX1302_REG_RADIO_FE_BASE_ADDR+28,0,0,8,0,1,0}, // RADIO_FE_SIG_ANA_FREQ_FREQ + {0,SX1302_REG_RADIO_FE_BASE_ADDR+29,0,0,8,1,1,0}, // RADIO_FE_SIG_ANA_ABS_MSB_CORR_ABS_OUT + {0,SX1302_REG_RADIO_FE_BASE_ADDR+30,0,0,8,1,1,0}, // RADIO_FE_SIG_ANA_ABS_LSB_CORR_ABS_OUT + {0,SX1302_REG_RADIO_FE_BASE_ADDR+31,0,0,1,1,1,0}, // RADIO_FE_DUMMY_DUMMY + {0,SX1302_REG_OTP_BASE_ADDR+0,0,0,8,0,1,0}, // OTP_BYTE_ADDR_ADDR + {0,SX1302_REG_OTP_BASE_ADDR+1,0,0,8,1,1,0}, // OTP_RD_DATA_RD_DATA + {0,SX1302_REG_OTP_BASE_ADDR+2,4,0,4,1,1,0}, // OTP_STATUS_CHECKSUM_STATUS + {0,SX1302_REG_OTP_BASE_ADDR+2,0,0,1,1,1,0}, // OTP_STATUS_FSM_READY + {0,SX1302_REG_OTP_BASE_ADDR+3,0,0,2,0,1,0}, // OTP_CFG_ACCESS_MODE + {0,SX1302_REG_OTP_BASE_ADDR+4,0,0,3,0,1,0}, // OTP_BIT_POS_POS + {0,SX1302_REG_OTP_BASE_ADDR+5,4,0,4,0,1,0}, // OTP_PIN_CTRL_0_TM + {0,SX1302_REG_OTP_BASE_ADDR+5,3,0,1,0,1,0}, // OTP_PIN_CTRL_0_STROBE + {0,SX1302_REG_OTP_BASE_ADDR+5,2,0,1,0,1,0}, // OTP_PIN_CTRL_0_PGENB + {0,SX1302_REG_OTP_BASE_ADDR+5,1,0,1,0,1,0}, // OTP_PIN_CTRL_0_LOAD + {0,SX1302_REG_OTP_BASE_ADDR+5,0,0,1,0,1,0}, // OTP_PIN_CTRL_0_CSB + {0,SX1302_REG_OTP_BASE_ADDR+6,2,0,1,0,1,0}, // OTP_PIN_CTRL_1_FSCK + {0,SX1302_REG_OTP_BASE_ADDR+6,1,0,1,0,1,0}, // OTP_PIN_CTRL_1_FSI + {0,SX1302_REG_OTP_BASE_ADDR+6,0,0,1,0,1,0}, // OTP_PIN_CTRL_1_FRST + {0,SX1302_REG_OTP_BASE_ADDR+7,0,0,1,1,1,0}, // OTP_PIN_STATUS_FSO + {0,SX1302_REG_OTP_BASE_ADDR+8,0,0,8,0,1,255}, // OTP_MODEM_EN_0_MODEM_EN + {0,SX1302_REG_OTP_BASE_ADDR+9,0,0,8,0,1,255}, // OTP_MODEM_EN_1_MODEM_EN + {0,SX1302_REG_OTP_BASE_ADDR+10,0,0,8,0,1,255}, // OTP_MODEM_SF_EN_SF_EN + {0,SX1302_REG_OTP_BASE_ADDR+11,0,0,1,0,1,1}, // OTP_TIMESTAMP_EN_TIMESTAMP_EN + {0,SX1302_REG_OTP_BASE_ADDR+12,0,0,1,1,1,0}, // OTP_DUMMY_DUMMY + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+0,0,0,5,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_LORA_SERVICE_FREQ_MSB_IF_FREQ_0 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+1,0,0,8,0,1,128}, // RX_TOP_LORA_SERVICE_FSK_LORA_SERVICE_FREQ_LSB_IF_FREQ_0 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+2,0,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_LORA_SERVICE_RADIO_SEL_RADIO_SELECT + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+3,4,0,3,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG1_BW_START + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+3,3,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG1_AUTO_BW_RED + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+3,2,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG1_NO_FAST_START + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+3,1,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG1_BYPASS + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+3,0,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG1_ENABLE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+4,3,0,3,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG2_BW_LOCKED + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+4,0,0,3,0,1,5}, // RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG2_BW + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+5,0,0,3,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG3_BW_RED + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+6,0,0,8,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG4_IIR_DCC_TIME + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+7,0,1,8,0,1,2}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR1_0_FIR1_COEFF_0 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+8,0,1,8,0,1,3}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR1_1_FIR1_COEFF_1 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+9,0,1,8,0,1,2}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR1_2_FIR1_COEFF_2 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+10,0,1,8,0,1,3}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR1_3_FIR1_COEFF_3 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+11,0,1,8,0,1,5}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR1_4_FIR1_COEFF_4 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+12,0,1,8,0,1,8}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR1_5_FIR1_COEFF_5 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+13,0,1,8,0,1,6}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR1_6_FIR1_COEFF_6 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+14,0,1,8,0,1,4}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR1_7_FIR1_COEFF_7 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+15,0,1,8,0,1,2}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR2_0_FIR2_COEFF_0 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+16,0,1,8,0,1,-2}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR2_1_FIR2_COEFF_1 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+17,0,1,8,0,1,-4}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR2_2_FIR2_COEFF_2 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+18,0,1,8,0,1,-3}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR2_3_FIR2_COEFF_3 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+19,0,1,8,0,1,3}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR2_4_FIR2_COEFF_4 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+20,0,1,8,0,1,11}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR2_5_FIR2_COEFF_5 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+21,0,1,8,0,1,19}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR2_6_FIR2_COEFF_6 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+22,0,1,8,0,1,10}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_FIR2_7_FIR2_COEFF_7 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+23,7,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC0_RADIO_GAIN_RED_SEL + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+23,0,0,7,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC0_RADIO_GAIN_RED_DB + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+24,4,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC1_DC_COMP_EN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+24,3,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC1_FORCE_DEFAULT_FIR + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+24,2,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC1_RSSI_EARLY_LATCH + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+24,0,0,2,0,1,3}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC1_FREEZE_ON_SYNC + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+25,6,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC2_DAGC_IN_COMP + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+25,5,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC2_DAGC_FIR_HYST + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+25,3,0,2,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC2_RSSI_MAX_SAMPLE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+25,0,0,3,0,1,2}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC2_RSSI_MIN_SAMPLE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+26,7,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_GAIN0_DAGC_FIR_FAST + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+26,6,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_GAIN0_FORCE_GAIN_FIR + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+26,4,0,2,0,1,3}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_GAIN0_GAIN_FIR1 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+26,0,0,3,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_RX_DFE_GAIN0_GAIN_FIR2 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+27,6,0,2,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_DAGC_CFG_TARGET_LVL + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+27,5,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_DAGC_CFG_GAIN_INCR_STEP + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+27,4,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_DAGC_CFG_GAIN_DROP_COMP + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+27,3,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_DAGC_CFG_COMB_FILTER_EN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+27,2,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_DAGC_CFG_NO_FREEZE_START + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+27,0,0,2,0,1,3}, // RX_TOP_LORA_SERVICE_FSK_DAGC_CFG_FREEZE_ON_SYNC + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+28,0,0,8,0,1,60}, // RX_TOP_LORA_SERVICE_FSK_DAGC_CNT0_SAMPLE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+29,0,0,8,0,1,6}, // RX_TOP_LORA_SERVICE_FSK_DAGC_CNT1_THR_M6 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+30,0,0,8,0,1,25}, // RX_TOP_LORA_SERVICE_FSK_DAGC_CNT2_THR_M12 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+31,0,0,8,0,1,42}, // RX_TOP_LORA_SERVICE_FSK_DAGC_CNT3_THR_M18 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+32,4,0,4,0,1,8}, // RX_TOP_LORA_SERVICE_FSK_DAGC_CNT4_GAIN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+32,0,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_DAGC_CNT4_FORCE_GAIN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+33,4,0,4,0,1,4}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG0_MODEM_BW + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+33,0,0,4,0,1,7}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG0_MODEM_SF + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+34,6,0,2,0,1,2}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG1_PPM_OFFSET_HDR_CTRL + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+34,4,0,2,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG1_PPM_OFFSET + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+34,3,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG1_MODEM_EN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+34,0,0,3,0,1,2}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG1_CODING_RATE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+35,5,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+35,4,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_MODEM_START + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+35,2,0,2,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_CADRXTX + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+35,1,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_IMPLICIT_HEADER + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+35,0,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_CRC_EN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+36,0,0,8,0,1,12}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG3_PAYLOAD_LENGTH + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+37,7,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG4_INT_STEP_ORIDE_EN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+37,0,0,6,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG4_INT_STEP_ORIDE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+38,6,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG5_HEADER_DIFF_MODE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+38,0,0,6,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG5_ZERO_PAD + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+39,0,0,8,0,1,8}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG6_PREAMBLE_SYMB_NB + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+40,0,0,8,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG7_PREAMBLE_SYMB_NB + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+41,3,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG8_AUTO_ACK_INT_DELAY + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+41,2,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG8_AUTO_ACK_RX + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+41,1,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG8_AUTO_ACK_TX + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+41,0,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_TXRX_CFG8_POST_PREAMBLE_GAP_LONG + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+42,4,0,2,0,1,3}, // RX_TOP_LORA_SERVICE_FSK_RX_CFG0_DFT_PEAK_EN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+42,2,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_RX_CFG0_CHIRP_INVERT + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+42,1,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_RX_CFG0_SWAP_IQ + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+42,0,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_RX_CFG0_CONTINUOUS + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+43,0,0,8,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_RX_CFG1_DETECT_TIMEOUT + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+44,5,0,2,0,1,2}, // RX_TOP_LORA_SERVICE_FSK_RX_CFG2_AUTO_ACK_RANGE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+44,0,0,5,0,1,22}, // RX_TOP_LORA_SERVICE_FSK_RX_CFG2_AUTO_ACK_DELAY + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+45,5,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_RX_CFG3_RESTART_ON_HDR_ERR + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+45,4,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_RX_CFG3_CLK_EN_RESYNC_DIN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+45,0,0,4,0,1,11}, // RX_TOP_LORA_SERVICE_FSK_RX_CFG3_LLR_SCALE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+46,0,1,5,0,1,2}, // RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH0_PEAK1_POS + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+47,0,1,5,0,1,4}, // RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH1_PEAK2_POS + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+48,5,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH2_FINETIME_ON_LAST + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+48,4,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH2_AUTO_SCALE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+48,3,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH2_DROP_ON_SYNCH + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+48,2,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH2_GAIN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+48,0,0,2,0,1,3}, // RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH2_TIMEOUT_OPT + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+49,7,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_FINE_TIMING0_GAIN_P_HDR_RED + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+49,6,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FINE_TIMING0_ROUNDING + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+49,4,0,2,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FINE_TIMING0_POS_LIMIT + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+49,2,0,2,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FINE_TIMING0_SUM_SIZE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+49,0,0,2,0,1,3}, // RX_TOP_LORA_SERVICE_FSK_FINE_TIMING0_MODE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+50,6,0,2,0,1,2}, // RX_TOP_LORA_SERVICE_FSK_FINE_TIMING1_GAIN_P_AUTO + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+50,3,0,3,0,1,2}, // RX_TOP_LORA_SERVICE_FSK_FINE_TIMING1_GAIN_P_PAYLOAD + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+50,0,0,3,0,1,4}, // RX_TOP_LORA_SERVICE_FSK_FINE_TIMING1_GAIN_P_PREAMB + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+51,6,0,2,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_EN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+51,3,0,3,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_PAYLOAD + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+51,0,0,3,0,1,4}, // RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_PREAMB + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+52,7,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FINE_TIMING3_FINESYNCH_SUM + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+52,4,0,3,0,1,5}, // RX_TOP_LORA_SERVICE_FSK_FINE_TIMING3_FINESYNCH_GAIN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+52,0,0,2,0,1,3}, // RX_TOP_LORA_SERVICE_FSK_FINE_TIMING3_GAIN_I_AUTO + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+53,4,0,3,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FINE_TIMING4_GAIN_I_AUTO_MAX + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+53,0,0,3,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FINE_TIMING4_GAIN_P_AUTO_MAX + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+54,0,0,4,0,1,9}, // RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME0_FREQ_TO_TIME_DRIFT_MANT + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+55,0,0,8,0,1,112}, // RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME1_FREQ_TO_TIME_DRIFT_MANT + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+56,0,0,3,0,1,3}, // RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME2_FREQ_TO_TIME_DRIFT_EXP + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+57,5,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_FREQ_DELTA + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+57,4,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_FINE_DELTA + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+57,3,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_FREQ_ERROR + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+57,2,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_TIME_SYMB + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+57,1,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_TIME_OFFSET + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+57,0,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_DETECT + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+58,0,0,8,0,1,33}, // RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME4_FREQ_TO_TIME_INVERT_RNG + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+59,7,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FREQ_TRACK0_FREQ_TRACK_FINE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+59,4,0,3,0,1,4}, // RX_TOP_LORA_SERVICE_FSK_FREQ_TRACK0_FREQ_TRACK_HDR_SKIP + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+59,0,0,2,0,1,3}, // RX_TOP_LORA_SERVICE_FSK_FREQ_TRACK0_FREQ_TRACK_EN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+60,4,0,3,0,1,5}, // RX_TOP_LORA_SERVICE_FSK_FREQ_TRACK1_FREQ_SYNCH_GAIN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+60,0,0,4,0,1,3}, // RX_TOP_LORA_SERVICE_FSK_FREQ_TRACK1_FREQ_TRACK_AUTO_THR + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+61,5,0,2,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_FREQ_TRACK2_SNR_MIN_WINDOW + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+61,4,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FREQ_TRACK2_GAIN_AUTO_SNR_MIN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+61,0,0,4,0,1,8}, // RX_TOP_LORA_SERVICE_FSK_FREQ_TRACK2_FREQ_SYNCH_THR + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+62,0,0,7,0,1,24}, // RX_TOP_LORA_SERVICE_FSK_DETECT_MSP0_MSP_PNR + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+63,0,0,7,0,1,48}, // RX_TOP_LORA_SERVICE_FSK_DETECT_MSP1_MSP2_PNR + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+64,4,0,3,0,1,7}, // RX_TOP_LORA_SERVICE_FSK_DETECT_MSP2_MSP2_PEAK_NB + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+64,0,0,3,0,1,7}, // RX_TOP_LORA_SERVICE_FSK_DETECT_MSP2_MSP_PEAK_NB + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+65,7,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_DETECT_MSP3_ACC_MIN2 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+65,4,0,2,0,1,2}, // RX_TOP_LORA_SERVICE_FSK_DETECT_MSP3_ACC_WIN_LEN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+65,2,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_DETECT_MSP3_MSP_POS_SEL + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+65,0,0,2,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_DETECT_MSP3_MSP_CNT_MODE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+66,7,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_USE_GAIN_SYMB + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+66,0,0,7,0,1,55}, // RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+67,6,0,2,0,1,2}, // RX_TOP_LORA_SERVICE_FSK_DETECT_ACC2_NOISE_COEFF + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+67,4,0,2,0,1,2}, // RX_TOP_LORA_SERVICE_FSK_DETECT_ACC2_ACC_COEFF + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+67,3,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_DETECT_ACC2_ACC_2_SAME_PEAKS + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+67,2,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_DETECT_ACC2_ACC_AUTO_RESCALE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+67,1,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_DETECT_ACC2_ACC_PEAK_POS_SEL + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+67,0,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_DETECT_ACC2_ACC_PEAK_SUM_EN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+68,0,0,8,0,1,11}, // RX_TOP_LORA_SERVICE_FSK_DETECT_ACC3_MIN_SINGLE_PEAK + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+69,4,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_TIMESTAMP_SEL_SNR_MIN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+69,3,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_TIMESTAMP_ENABLE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+69,0,0,3,0,1,7}, // RX_TOP_LORA_SERVICE_FSK_TIMESTAMP_NB_SYMB + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+70,6,0,2,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_CLOCK_GATE_OVERRIDE_FSK_TRANSPOSE_CLK_OVERRIDE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+70,4,0,2,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_CLOCK_GATE_OVERRIDE_FSK_MODEM_CLK_OVERRIDE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+70,2,0,2,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_CLOCK_GATE_OVERRIDE_TRANSPOSE_CLK_OVERRIDE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+70,0,0,2,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_CLOCK_GATE_OVERRIDE_MODEM_CLK_OVERRIDE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+71,0,0,1,1,1,0}, // RX_TOP_LORA_SERVICE_FSK_DUMMY0_DUMMY0 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+80,0,0,5,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_FREQ_MSB_IF_FREQ_0 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+81,0,0,8,0,1,128}, // RX_TOP_LORA_SERVICE_FSK_FSK_FREQ_LSB_IF_FREQ_0 + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+82,4,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_CRC_IBM + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+82,2,0,2,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_DCFREE_ENC + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+82,1,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_CRC_EN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+82,0,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_PKT_MODE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+83,6,0,2,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_CFG_1_ADRS_COMP + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+83,3,0,3,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_CFG_1_PSIZE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+83,0,0,3,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_CFG_1_CH_BW_EXPO + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+84,3,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_MODEM_INVERT_IQ + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+84,2,0,1,0,1,1}, // RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_AUTO_AFC + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+84,1,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_RADIO_SELECT + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+84,0,0,1,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_RX_INVERT + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+85,5,0,3,0,1,4}, // RX_TOP_LORA_SERVICE_FSK_FSK_CFG_4_RSSI_LENGTH + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+85,0,0,5,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_CFG_4_ERROR_OSR_TOL + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+86,0,0,8,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_NODE_ADRS_NODE_ADRS + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+87,0,0,8,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_BROADCAST_BROADCAST + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+88,0,0,8,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_PKT_LENGTH_PKT_LENGTH + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+89,0,0,2,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_TIMEOUT_MSB_TIMEOUT + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+90,0,0,8,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_TIMEOUT_LSB_TIMEOUT + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+91,0,0,8,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_BIT_RATE_MSB_BIT_RATE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+92,0,0,8,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_BIT_RATE_LSB_BIT_RATE + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+93,0,0,8,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE7_FSK_REF_PATTERN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+94,0,0,8,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE6_FSK_REF_PATTERN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+95,0,0,8,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE5_FSK_REF_PATTERN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+96,0,0,8,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE4_FSK_REF_PATTERN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+97,0,0,8,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE3_FSK_REF_PATTERN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+98,0,0,8,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE2_FSK_REF_PATTERN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+99,0,0,8,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE1_FSK_REF_PATTERN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+100,0,0,8,0,1,0}, // RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE0_FSK_REF_PATTERN + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+101,0,0,4,0,1,4}, // RX_TOP_LORA_SERVICE_FSK_FSK_RSSI_FILTER_ALPHA_FSK_RSSI_FILTER_ALPHA + {0,SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BASE_ADDR+102,0,0,1,1,1,0}, // RX_TOP_LORA_SERVICE_FSK_DUMMY1_DUMMY1 + {0,SX1302_REG_CAPTURE_RAM_BASE_ADDR+0,4,0,1,0,1,0}, // CAPTURE_RAM_CAPTURE_CFG_ENABLE + {0,SX1302_REG_CAPTURE_RAM_BASE_ADDR+0,3,0,1,0,1,0}, // CAPTURE_RAM_CAPTURE_CFG_CAPTUREWRAP + {0,SX1302_REG_CAPTURE_RAM_BASE_ADDR+0,2,0,1,0,1,0}, // CAPTURE_RAM_CAPTURE_CFG_CAPTUREFORCETRIGGER + {0,SX1302_REG_CAPTURE_RAM_BASE_ADDR+0,1,0,1,0,1,0}, // CAPTURE_RAM_CAPTURE_CFG_CAPTURESTART + {0,SX1302_REG_CAPTURE_RAM_BASE_ADDR+0,0,0,1,0,1,0}, // CAPTURE_RAM_CAPTURE_CFG_RAMCONFIG + {0,SX1302_REG_CAPTURE_RAM_BASE_ADDR+1,0,0,5,0,1,0}, // CAPTURE_RAM_CAPTURE_SOURCE_A_SOURCEMUX + {0,SX1302_REG_CAPTURE_RAM_BASE_ADDR+2,0,0,5,0,1,0}, // CAPTURE_RAM_CAPTURE_SOURCE_B_SOURCEMUX + {0,SX1302_REG_CAPTURE_RAM_BASE_ADDR+3,0,0,8,0,1,0}, // CAPTURE_RAM_CAPTURE_PERIOD_0_CAPTUREPERIOD + {0,SX1302_REG_CAPTURE_RAM_BASE_ADDR+4,0,0,8,0,1,0}, // CAPTURE_RAM_CAPTURE_PERIOD_1_CAPTUREPERIOD + {0,SX1302_REG_CAPTURE_RAM_BASE_ADDR+5,0,0,1,1,1,0}, // CAPTURE_RAM_STATUS_CAPCOMPLETE + {0,SX1302_REG_CAPTURE_RAM_BASE_ADDR+6,0,0,8,1,1,0}, // CAPTURE_RAM_LAST_RAM_ADDR_0_LASTRAMADDR + {0,SX1302_REG_CAPTURE_RAM_BASE_ADDR+7,0,0,4,1,1,0}, // CAPTURE_RAM_LAST_RAM_ADDR_1_LASTRAMADDR + {0,SX1302_REG_CAPTURE_RAM_BASE_ADDR+8,0,0,2,0,1,2}, // CAPTURE_RAM_CLOCK_GATE_OVERRIDE_CLK_OVERRIDE + {0,SX1302_REG_CAPTURE_RAM_BASE_ADDR+9,0,0,1,1,1,0}, // CAPTURE_RAM_DUMMY0_DUMMY0 + {0,0,0,0,0,0,0,0} +}; + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- INTERNAL SHARED VARIABLES -------------------------------------------- */ + +void *lgw_spi_target = NULL; /*! generic pointer to the SPI device */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ + +int reg_w_align32(void *spi_target, uint8_t spi_mux_target, struct lgw_reg_s r, int32_t reg_value) { + int spi_stat = LGW_REG_SUCCESS; + int i, size_byte; + uint8_t buf[4] = "\x00\x00\x00\x00"; + + if ((r.leng == 8) && (r.offs == 0)) { + /* direct write */ + spi_stat += lgw_spi_w(spi_target, spi_mux_target, r.addr, (uint8_t)reg_value); + } else if ((r.offs + r.leng) <= 8) { + /* single-byte read-modify-write, offs:[0-7], leng:[1-7] */ + spi_stat += lgw_spi_r(spi_target, spi_mux_target, r.addr, &buf[0]); + buf[1] = ((1 << r.leng) - 1) << r.offs; /* bit mask */ + buf[2] = ((uint8_t)reg_value) << r.offs; /* new data offsetted */ + buf[3] = (~buf[1] & buf[0]) | (buf[1] & buf[2]); /* mixing old & new data */ + spi_stat += lgw_spi_w(spi_target, spi_mux_target, r.addr, buf[3]); + } else if ((r.offs == 0) && (r.leng > 0) && (r.leng <= 32)) { + /* multi-byte direct write routine */ + size_byte = (r.leng + 7) / 8; /* add a byte if it's not an exact multiple of 8 */ + for (i=0; i> 8); + } + spi_stat += lgw_spi_wb(spi_target, spi_mux_target, r.addr, buf, size_byte); /* write the register in one burst */ + } else { + /* register spanning multiple memory bytes but with an offset */ + DEBUG_MSG("ERROR: REGISTER SIZE AND OFFSET ARE NOT SUPPORTED\n"); + return LGW_REG_ERROR; + } + + return spi_stat; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int reg_r_align32(void *spi_target, uint8_t spi_mux_target, struct lgw_reg_s r, int32_t *reg_value) { + int spi_stat = LGW_SPI_SUCCESS; + uint8_t bufu[4] = "\x00\x00\x00\x00"; + int8_t *bufs = (int8_t *)bufu; + int i, size_byte; + uint32_t u = 0; + + if ((r.offs + r.leng) <= 8) { + /* read one byte, then shift and mask bits to get reg value with sign extension if needed */ + spi_stat += lgw_spi_r(spi_target, spi_mux_target, r.addr, &bufu[0]); + bufu[1] = bufu[0] << (8 - r.leng - r.offs); /* left-align the data */ + if (r.sign == true) { + bufs[2] = bufs[1] >> (8 - r.leng); /* right align the data with sign extension (ARITHMETIC right shift) */ + *reg_value = (int32_t)bufs[2]; /* signed pointer -> 32b sign extension */ + } else { + bufu[2] = bufu[1] >> (8 - r.leng); /* right align the data, no sign extension */ + *reg_value = (int32_t)bufu[2]; /* unsigned pointer -> no sign extension */ + } + } else if ((r.offs == 0) && (r.leng > 0) && (r.leng <= 32)) { + size_byte = (r.leng + 7) / 8; /* add a byte if it's not an exact multiple of 8 */ + spi_stat += lgw_spi_rb(spi_target, spi_mux_target, r.addr, bufu, size_byte); + u = 0; + for (i=(size_byte-1); i>=0; --i) { + u = (uint32_t)bufu[i] + (u << 8); /* transform a 4-byte array into a 32 bit word */ + } + if (r.sign == true) { + u = u << (32 - r.leng); /* left-align the data */ + *reg_value = (int32_t)u >> (32 - r.leng); /* right-align the data with sign extension (ARITHMETIC right shift) */ + } else { + *reg_value = (int32_t)u; /* unsigned value -> return 'as is' */ + } + } else { + /* register spanning multiple memory bytes but with an offset */ + DEBUG_MSG("ERROR: REGISTER SIZE AND OFFSET ARE NOT SUPPORTED\n"); + return LGW_REG_ERROR; + } + + return spi_stat; +} + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +/* Concentrator connect */ +int lgw_connect(const char * spidev_path) { + int spi_stat = LGW_SPI_SUCCESS; + uint8_t u = 0; + + /* check SPI link status */ + if (spidev_path == NULL) { + DEBUG_MSG("ERROR: SPIDEV PATH IS NOT SET\n"); + return LGW_REG_ERROR; + } + if (lgw_spi_target != NULL) { + DEBUG_MSG("WARNING: concentrator was already connected\n"); + lgw_spi_close(lgw_spi_target); + } + + /* open the SPI link */ + spi_stat = lgw_spi_open(spidev_path, &lgw_spi_target); + if (spi_stat != LGW_SPI_SUCCESS) { + DEBUG_MSG("ERROR CONNECTING CONCENTRATOR\n"); + return LGW_REG_ERROR; + } + + /* check SX1302 version */ + spi_stat = lgw_spi_r(lgw_spi_target, LGW_SPI_MUX_TARGET_SX1302, loregs[SX1302_REG_COMMON_VERSION_VERSION].addr, &u); + if (spi_stat != LGW_SPI_SUCCESS) { + DEBUG_MSG("ERROR READING CHIP VERSION REGISTER\n"); + return LGW_REG_ERROR; + } + if (u != loregs[SX1302_REG_COMMON_VERSION_VERSION].dflt) { + DEBUG_PRINTF("ERROR: NOT EXPECTED CHIP VERSION (v%u)\n", u); + return LGW_REG_ERROR; + } + DEBUG_PRINTF("Note: chip version is 0x%02X (v%u.%u)\n", u, (u >> 4) & 0x0F, u & 0x0F) ; + + DEBUG_MSG("Note: success connecting the concentrator\n"); + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* Concentrator disconnect */ +int lgw_disconnect(void) { + if (lgw_spi_target != NULL) { + lgw_spi_close(lgw_spi_target); + lgw_spi_target = NULL; + DEBUG_MSG("Note: success disconnecting the concentrator\n"); + return LGW_REG_SUCCESS; + } else { + DEBUG_MSG("WARNING: concentrator was already disconnected\n"); + return LGW_REG_ERROR; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* Write to a register addressed by name */ +int lgw_reg_w(uint16_t register_id, int32_t reg_value) { + int spi_stat = LGW_SPI_SUCCESS; + struct lgw_reg_s r; + + /* check input parameters */ + if (register_id >= LGW_TOTALREGS) { + DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n"); + return LGW_REG_ERROR; + } + + /* check if SPI is initialised */ + if (lgw_spi_target == NULL) { + DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); + return LGW_REG_ERROR; + } + + /* get register struct from the struct array */ + r = loregs[register_id]; + + /* reject write to read-only registers */ + if (r.rdon == 1){ + DEBUG_MSG("ERROR: TRYING TO WRITE A READ-ONLY REGISTER\n"); + return LGW_REG_ERROR; + } + + spi_stat += reg_w_align32(lgw_spi_target, LGW_SPI_MUX_TARGET_SX1302, r, reg_value); + + if (spi_stat != LGW_SPI_SUCCESS) { + DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER WRITE\n"); + return LGW_REG_ERROR; + } else { + return LGW_REG_SUCCESS; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* Read to a register addressed by name */ +int lgw_reg_r(uint16_t register_id, int32_t *reg_value) { + int spi_stat = LGW_SPI_SUCCESS; + struct lgw_reg_s r; + + /* check input parameters */ + CHECK_NULL(reg_value); + if (register_id >= LGW_TOTALREGS) { + DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n"); + return LGW_REG_ERROR; + } + + /* check if SPI is initialised */ + if (lgw_spi_target == NULL) { + DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); + return LGW_REG_ERROR; + } + + /* get register struct from the struct array */ + r = loregs[register_id]; + + spi_stat += reg_r_align32(lgw_spi_target, LGW_SPI_MUX_TARGET_SX1302, r, reg_value); + + if (spi_stat != LGW_SPI_SUCCESS) { + DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER WRITE\n"); + return LGW_REG_ERROR; + } else { + return LGW_REG_SUCCESS; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* Point to a register by name and do a burst write */ +int lgw_reg_wb(uint16_t register_id, uint8_t *data, uint16_t size) { + int spi_stat = LGW_SPI_SUCCESS; + struct lgw_reg_s r; + + /* check input parameters */ + CHECK_NULL(data); + if (size == 0) { + DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n"); + return LGW_REG_ERROR; + } + if (register_id >= LGW_TOTALREGS) { + DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n"); + return LGW_REG_ERROR; + } + + /* check if SPI is initialised */ + if (lgw_spi_target == NULL) { + DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); + return LGW_REG_ERROR; + } + + /* get register struct from the struct array */ + r = loregs[register_id]; + + /* reject write to read-only registers */ + if (r.rdon == 1){ + DEBUG_MSG("ERROR: TRYING TO BURST WRITE A READ-ONLY REGISTER\n"); + return LGW_REG_ERROR; + } + + /* do the burst write */ + spi_stat += lgw_spi_wb(lgw_spi_target, LGW_SPI_MUX_TARGET_SX1302, r.addr, data, size); + + if (spi_stat != LGW_SPI_SUCCESS) { + DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER BURST WRITE\n"); + return LGW_REG_ERROR; + } else { + return LGW_REG_SUCCESS; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* Point to a register by name and do a burst read */ +int lgw_reg_rb(uint16_t register_id, uint8_t *data, uint16_t size) { + int spi_stat = LGW_SPI_SUCCESS; + struct lgw_reg_s r; + + /* check input parameters */ + CHECK_NULL(data); + if (size == 0) { + DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n"); + return LGW_REG_ERROR; + } + if (register_id >= LGW_TOTALREGS) { + DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n"); + return LGW_REG_ERROR; + } + + /* check if SPI is initialised */ + if (lgw_spi_target == NULL) { + DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); + return LGW_REG_ERROR; + } + + /* get register struct from the struct array */ + r = loregs[register_id]; + + /* do the burst read */ + spi_stat += lgw_spi_rb(lgw_spi_target, LGW_SPI_MUX_TARGET_SX1302, r.addr, data, size); + + if (spi_stat != LGW_SPI_SUCCESS) { + DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER BURST READ\n"); + return LGW_REG_ERROR; + } else { + return LGW_REG_SUCCESS; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_mem_wb(uint16_t mem_addr, const uint8_t *data, uint16_t size) { + int spi_stat = LGW_SPI_SUCCESS; + int chunk_cnt = 0; + uint16_t addr = mem_addr; + uint16_t sz_todo = size; + uint16_t chunk_size; + const uint16_t CHUNK_SIZE_MAX = 1024; + + /* check input parameters */ + CHECK_NULL(data); + if (size == 0) { + DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n"); + return LGW_REG_ERROR; + } + + /* check if SPI is initialised */ + if (lgw_spi_target == NULL) { + DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); + return LGW_REG_ERROR; + } + + /* write memory by chunks */ + while (sz_todo > 0) { + /* full or partial chunk ? */ + chunk_size = (sz_todo > CHUNK_SIZE_MAX) ? CHUNK_SIZE_MAX : sz_todo; + + /* do the burst write */ + spi_stat += lgw_spi_wb(lgw_spi_target, LGW_SPI_MUX_TARGET_SX1302, addr, &data[chunk_cnt * CHUNK_SIZE_MAX], chunk_size); + + /* prepare for next write */ + addr += chunk_size; + sz_todo -= chunk_size; + chunk_cnt += 1; + } + + if (spi_stat != LGW_SPI_SUCCESS) { + DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER BURST WRITE\n"); + return LGW_REG_ERROR; + } else { + return LGW_REG_SUCCESS; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_mem_rb(uint16_t mem_addr, uint8_t *data, uint16_t size, bool fifo_mode) { + int spi_stat = LGW_SPI_SUCCESS; + int chunk_cnt = 0; + uint16_t addr = mem_addr; + uint16_t sz_todo = size; + uint16_t chunk_size; + const uint16_t CHUNK_SIZE_MAX = 1024; + + /* check input parameters */ + CHECK_NULL(data); + if (size == 0) { + DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n"); + return LGW_REG_ERROR; + } + + /* check if SPI is initialised */ + if (lgw_spi_target == NULL) { + DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); + return LGW_REG_ERROR; + } + + /* read memory by chunks */ + while (sz_todo > 0) { + /* full or partial chunk ? */ + chunk_size = (sz_todo > CHUNK_SIZE_MAX) ? CHUNK_SIZE_MAX : sz_todo; + + /* do the burst read */ + spi_stat += lgw_spi_rb(lgw_spi_target, LGW_SPI_MUX_TARGET_SX1302, addr, &data[chunk_cnt * CHUNK_SIZE_MAX], chunk_size); + + /* do not increment the address when the target memory is in FIFO mode (auto-increment) */ + if (fifo_mode == false) { + addr += chunk_size; + } + + /* prepare for next read */ + sz_todo -= chunk_size; + chunk_cnt += 1; + } + + if (spi_stat != LGW_SPI_SUCCESS) { + DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER BURST READ\n"); + return LGW_REG_ERROR; + } else { + return LGW_REG_SUCCESS; + } +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_spi.c b/libloragw/src/loragw_spi.c new file mode 100644 index 0000000..307c50a --- /dev/null +++ b/libloragw/src/loragw_spi.c @@ -0,0 +1,352 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Host specific functions to address the LoRa concentrator registers through + a SPI interface. + Single-byte read/write and burst read/write. + Could be used with multiple SPI ports in parallel (explicit file descriptor) + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* printf fprintf */ +#include /* malloc free */ +#include /* lseek, close */ +#include /* open */ +#include /* memset */ + +#include +#include + +#include "loragw_spi.h" +#include "loragw_aux.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_SPI == 1 + #define DEBUG_MSG(str) fprintf(stderr, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) + #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_SPI_ERROR;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return LGW_SPI_ERROR;} +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define READ_ACCESS 0x00 +#define WRITE_ACCESS 0x80 + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +/* SPI initialization and configuration */ +int lgw_spi_open(const char * spidev_path, void **spi_target_ptr) { + int *spi_device = NULL; + int dev; + int a=0, b=0; + int i; + + /* check input variables */ + CHECK_NULL(spi_target_ptr); /* cannot be null, must point on a void pointer (*spi_target_ptr can be null) */ + + /* allocate memory for the device descriptor */ + spi_device = malloc(sizeof(int)); + if (spi_device == NULL) { + DEBUG_MSG("ERROR: MALLOC FAIL\n"); + return LGW_SPI_ERROR; + } + + /* open SPI device */ + dev = open(spidev_path, O_RDWR); + if (dev < 0) { + DEBUG_PRINTF("ERROR: failed to open SPI device %s\n", spidev_path); + return LGW_SPI_ERROR; + } + + /* setting SPI mode to 'mode 0' */ + i = SPI_MODE_0; + a = ioctl(dev, SPI_IOC_WR_MODE, &i); + b = ioctl(dev, SPI_IOC_RD_MODE, &i); + if ((a < 0) || (b < 0)) { + DEBUG_MSG("ERROR: SPI PORT FAIL TO SET IN MODE 0\n"); + close(dev); + free(spi_device); + return LGW_SPI_ERROR; + } + + /* setting SPI max clk (in Hz) */ + i = SPI_SPEED; + a = ioctl(dev, SPI_IOC_WR_MAX_SPEED_HZ, &i); + b = ioctl(dev, SPI_IOC_RD_MAX_SPEED_HZ, &i); + if ((a < 0) || (b < 0)) { + DEBUG_MSG("ERROR: SPI PORT FAIL TO SET MAX SPEED\n"); + close(dev); + free(spi_device); + return LGW_SPI_ERROR; + } + + /* setting SPI to MSB first */ + i = 0; + a = ioctl(dev, SPI_IOC_WR_LSB_FIRST, &i); + b = ioctl(dev, SPI_IOC_RD_LSB_FIRST, &i); + if ((a < 0) || (b < 0)) { + DEBUG_MSG("ERROR: SPI PORT FAIL TO SET MSB FIRST\n"); + close(dev); + free(spi_device); + return LGW_SPI_ERROR; + } + + /* setting SPI to 8 bits per word */ + i = 0; + a = ioctl(dev, SPI_IOC_WR_BITS_PER_WORD, &i); + b = ioctl(dev, SPI_IOC_RD_BITS_PER_WORD, &i); + if ((a < 0) || (b < 0)) { + DEBUG_MSG("ERROR: SPI PORT FAIL TO SET 8 BITS-PER-WORD\n"); + close(dev); + return LGW_SPI_ERROR; + } + + *spi_device = dev; + *spi_target_ptr = (void *)spi_device; + DEBUG_MSG("Note: SPI port opened and configured ok\n"); + return LGW_SPI_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* SPI release */ +int lgw_spi_close(void *spi_target) { + int spi_device; + int a; + + /* check input variables */ + CHECK_NULL(spi_target); + + /* close file & deallocate file descriptor */ + spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */ + a = close(spi_device); + free(spi_target); + + /* determine return code */ + if (a < 0) { + DEBUG_MSG("ERROR: SPI PORT FAILED TO CLOSE\n"); + return LGW_SPI_ERROR; + } else { + DEBUG_MSG("Note: SPI port closed\n"); + return LGW_SPI_SUCCESS; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* Simple write */ +int lgw_spi_w(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8_t data) { + int spi_device; + uint8_t out_buf[4]; + uint8_t command_size; + struct spi_ioc_transfer k; + int a; + + /* check input variables */ + CHECK_NULL(spi_target); + + spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */ + + /* prepare frame to be sent */ + out_buf[0] = spi_mux_target; + out_buf[1] = WRITE_ACCESS | ((address >> 8) & 0x7F); + out_buf[2] = ((address >> 0) & 0xFF); + out_buf[3] = data; + command_size = 4; + + /* I/O transaction */ + memset(&k, 0, sizeof(k)); /* clear k */ + k.tx_buf = (unsigned long) out_buf; + k.len = command_size; + k.speed_hz = SPI_SPEED; + k.cs_change = 0; + k.bits_per_word = 8; + a = ioctl(spi_device, SPI_IOC_MESSAGE(1), &k); + + /* determine return code */ + if (a != (int)k.len) { + DEBUG_MSG("ERROR: SPI WRITE FAILURE\n"); + return LGW_SPI_ERROR; + } else { + DEBUG_MSG("Note: SPI write success\n"); + return LGW_SPI_SUCCESS; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* Simple read */ +int lgw_spi_r(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8_t *data) { + int spi_device; + uint8_t out_buf[5]; + uint8_t command_size; + uint8_t in_buf[ARRAY_SIZE(out_buf)]; + struct spi_ioc_transfer k; + int a; + + /* check input variables */ + CHECK_NULL(spi_target); + CHECK_NULL(data); + + spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */ + + /* prepare frame to be sent */ + out_buf[0] = spi_mux_target; + out_buf[1] = READ_ACCESS | ((address >> 8) & 0x7F); + out_buf[2] = ((address >> 0) & 0xFF); + out_buf[3] = 0x00; + out_buf[4] = 0x00; + command_size = 5; + + /* I/O transaction */ + memset(&k, 0, sizeof(k)); /* clear k */ + k.tx_buf = (unsigned long) out_buf; + k.rx_buf = (unsigned long) in_buf; + k.len = command_size; + k.cs_change = 0; + a = ioctl(spi_device, SPI_IOC_MESSAGE(1), &k); + + /* determine return code */ + if (a != (int)k.len) { + DEBUG_MSG("ERROR: SPI READ FAILURE\n"); + return LGW_SPI_ERROR; + } else { + DEBUG_MSG("Note: SPI read success\n"); + *data = in_buf[command_size - 1]; + return LGW_SPI_SUCCESS; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* Burst (multiple-byte) write */ +int lgw_spi_wb(void *spi_target, uint8_t spi_mux_target, uint16_t address, const uint8_t *data, uint16_t size) { + int spi_device; + uint8_t command[3]; + uint8_t command_size; + struct spi_ioc_transfer k[2]; + int size_to_do, chunk_size, offset; + int byte_transfered = 0; + int i; + + /* check input parameters */ + CHECK_NULL(spi_target); + CHECK_NULL(data); + if (size == 0) { + DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n"); + return LGW_SPI_ERROR; + } + + spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */ + + /* prepare command byte */ + command[0] = spi_mux_target; + command[1] = WRITE_ACCESS | ((address >> 8) & 0x7F); + command[2] = ((address >> 0) & 0xFF); + command_size = 3; + size_to_do = size; + + /* I/O transaction */ + memset(&k, 0, sizeof(k)); /* clear k */ + k[0].tx_buf = (unsigned long) &command[0]; + k[0].len = command_size; + k[0].cs_change = 0; + k[1].cs_change = 0; + for (i=0; size_to_do > 0; ++i) { + chunk_size = (size_to_do < LGW_BURST_CHUNK) ? size_to_do : LGW_BURST_CHUNK; + offset = i * LGW_BURST_CHUNK; + k[1].tx_buf = (unsigned long)(data + offset); + k[1].len = chunk_size; + byte_transfered += (ioctl(spi_device, SPI_IOC_MESSAGE(2), &k) - k[0].len ); + DEBUG_PRINTF("BURST WRITE: to trans %d # chunk %d # transferred %d \n", size_to_do, chunk_size, byte_transfered); + size_to_do -= chunk_size; /* subtract the quantity of data already transferred */ + } + + /* determine return code */ + if (byte_transfered != size) { + DEBUG_MSG("ERROR: SPI BURST WRITE FAILURE\n"); + return LGW_SPI_ERROR; + } else { + DEBUG_MSG("Note: SPI burst write success\n"); + return LGW_SPI_SUCCESS; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* Burst (multiple-byte) read */ +int lgw_spi_rb(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8_t *data, uint16_t size) { + int spi_device; + uint8_t command[4]; + uint8_t command_size; + struct spi_ioc_transfer k[2]; + int size_to_do, chunk_size, offset; + int byte_transfered = 0; + int i; + + /* check input parameters */ + CHECK_NULL(spi_target); + CHECK_NULL(data); + if (size == 0) { + DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n"); + return LGW_SPI_ERROR; + } + + spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */ + + /* prepare command byte */ + command[0] = spi_mux_target; + command[1] = READ_ACCESS | ((address >> 8) & 0x7F); + command[2] = ((address >> 0) & 0xFF); + command[3] = 0x00; + command_size = 4; + size_to_do = size; + + /* I/O transaction */ + memset(&k, 0, sizeof(k)); /* clear k */ + k[0].tx_buf = (unsigned long) &command[0]; + k[0].len = command_size; + k[0].cs_change = 0; + k[1].cs_change = 0; + for (i=0; size_to_do > 0; ++i) { + chunk_size = (size_to_do < LGW_BURST_CHUNK) ? size_to_do : LGW_BURST_CHUNK; + offset = i * LGW_BURST_CHUNK; + k[1].rx_buf = (unsigned long)(data + offset); + k[1].len = chunk_size; + byte_transfered += (ioctl(spi_device, SPI_IOC_MESSAGE(2), &k) - k[0].len ); + DEBUG_PRINTF("BURST READ: to trans %d # chunk %d # transferred %d \n", size_to_do, chunk_size, byte_transfered); + size_to_do -= chunk_size; /* subtract the quantity of data already transferred */ + } + + /* determine return code */ + if (byte_transfered != size) { + DEBUG_MSG("ERROR: SPI BURST READ FAILURE\n"); + return LGW_SPI_ERROR; + } else { + DEBUG_MSG("Note: SPI burst read success\n"); + return LGW_SPI_SUCCESS; + } +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_stts751.c b/libloragw/src/loragw_stts751.c new file mode 100644 index 0000000..b2b42ff --- /dev/null +++ b/libloragw/src/loragw_stts751.c @@ -0,0 +1,213 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Basic driver for ST ts751 temperature sensor + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* bool type */ +#include /* printf fprintf */ + +#include "loragw_i2c.h" +#include "loragw_stts751.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_I2C == 1 + #define DEBUG_MSG(str) fprintf(stderr, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) + #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_REG_ERROR;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return LGW_REG_ERROR;} +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define STTS751_REG_TEMP_H 0x00 +#define STTS751_REG_STATUS 0x01 +#define STTS751_STATUS_TRIPT BIT(0) +#define STTS751_STATUS_TRIPL BIT(5) +#define STTS751_STATUS_TRIPH BIT(6) +#define STTS751_REG_TEMP_L 0x02 +#define STTS751_REG_CONF 0x03 +#define STTS751_CONF_RES_MASK 0x0C +#define STTS751_CONF_RES_SHIFT 2 +#define STTS751_CONF_EVENT_DIS BIT(7) +#define STTS751_CONF_STOP BIT(6) +#define STTS751_REG_RATE 0x04 +#define STTS751_REG_HLIM_H 0x05 +#define STTS751_REG_HLIM_L 0x06 +#define STTS751_REG_LLIM_H 0x07 +#define STTS751_REG_LLIM_L 0x08 +#define STTS751_REG_TLIM 0x20 +#define STTS751_REG_HYST 0x21 +#define STTS751_REG_SMBUS_TO 0x22 + +#define STTS751_REG_PROD_ID 0xFD +#define STTS751_REG_MAN_ID 0xFE +#define STTS751_REG_REV_ID 0xFF + +#define STTS751_0_PROD_ID 0x00 +#define STTS751_1_PROD_ID 0x01 +#define ST_MAN_ID 0x53 + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- INTERNAL SHARED VARIABLES -------------------------------------------- */ + +extern int lgw_i2c_target; + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ + +int stts751_configure( int i2c_fd ) +{ + int err; + uint8_t val; + + /* Check Input Params */ + if( i2c_fd <= 0 ) + { + printf( "ERROR: invalid I2C file descriptor\n" ); + return LGW_I2C_ERROR; + } + + DEBUG_MSG("INFO: configuring STTS751 temperature sensor...\n"); + + /* Get product ID */ + err = i2c_linuxdev_read( i2c_fd, I2C_PORT_TEMP_SENSOR, STTS751_REG_PROD_ID, &val ); + if ( err != 0 ) + { + printf( "ERROR: failed to read I2C device 0x%02X (err=%i)\n", I2C_PORT_TEMP_SENSOR, err ); + return LGW_I2C_ERROR; + } + switch( val ) + { + case STTS751_0_PROD_ID: + DEBUG_MSG("INFO: Product ID: STTS751-0\n"); + break; + case STTS751_1_PROD_ID: + DEBUG_MSG("INFO: Product ID: STTS751-1\n"); + break; + default: + printf("ERROR: Product ID: UNKNOWN\n"); + return LGW_I2C_ERROR; + } + + /* Get Manufacturer ID */ + err = i2c_linuxdev_read( i2c_fd, I2C_PORT_TEMP_SENSOR, STTS751_REG_MAN_ID, &val ); + if ( err != 0 ) + { + printf( "ERROR: failed to read I2C device 0x%02X (err=%i)\n", I2C_PORT_TEMP_SENSOR, err ); + return LGW_I2C_ERROR; + } + if ( val != ST_MAN_ID ) + { + printf( "ERROR: Manufacturer ID: UNKNOWN\n" ); + return LGW_I2C_ERROR; + } + else + { + DEBUG_PRINTF("INFO: Manufacturer ID: 0x%02X\n", val); + } + + /* Get revision number */ + err = i2c_linuxdev_read( i2c_fd, I2C_PORT_TEMP_SENSOR, STTS751_REG_REV_ID, &val ); + if ( err != 0 ) + { + printf( "ERROR: failed to read I2C device 0x%02X (err=%i)\n", I2C_PORT_TEMP_SENSOR, err ); + return LGW_I2C_ERROR; + } + DEBUG_PRINTF("INFO: Revision number: 0x%02X\n", val); + + /* Set conversion resolution to 12 bits */ + err = i2c_linuxdev_write( i2c_fd, I2C_PORT_TEMP_SENSOR, STTS751_REG_CONF, 0x8C ); /* TODO: do not hardcode the whole byte */ + if ( err != 0 ) + { + printf( "ERROR: failed to write I2C device 0x%02X (err=%i)\n", I2C_PORT_TEMP_SENSOR, err ); + return LGW_I2C_ERROR; + } + + /* Set conversion rate to 1 / second */ + err = i2c_linuxdev_write( i2c_fd, I2C_PORT_TEMP_SENSOR, STTS751_REG_RATE, 0x04 ); + if ( err != 0 ) + { + printf( "ERROR: failed to write I2C device 0x%02X (err=%i)\n", I2C_PORT_TEMP_SENSOR, err ); + return LGW_I2C_ERROR; + } + + return LGW_I2C_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int stts751_get_temperature( int i2c_fd, float * temperature) +{ + int err; + uint8_t high_byte, low_byte; + int8_t h; + + /* Check Input Params */ + if( i2c_fd <= 0 ) + { + printf( "ERROR: invalid I2C file descriptor\n" ); + return LGW_I2C_ERROR; + } + + /* Read Temperature LSB */ + err = i2c_linuxdev_read( i2c_fd, I2C_PORT_TEMP_SENSOR, STTS751_REG_TEMP_L, &low_byte ); + if ( err != 0 ) + { + printf( "ERROR: failed to read I2C device 0x%02X (err=%i)\n", I2C_PORT_TEMP_SENSOR, err ); + return LGW_I2C_ERROR; + } + + /* Read Temperature MSB */ + err = i2c_linuxdev_read( i2c_fd, I2C_PORT_TEMP_SENSOR, STTS751_REG_TEMP_H, &high_byte ); + if ( err != 0 ) + { + printf( "ERROR: failed to read I2C device 0x%02X (err=%i)\n", I2C_PORT_TEMP_SENSOR, err ); + return LGW_I2C_ERROR; + } + + h = (int8_t)high_byte; + *temperature = ((h << 8) | low_byte) / 256.0; + + DEBUG_PRINTF("Temperature: %f C (h:0x%02X l:0x%02X)\n", *temperature, high_byte, low_byte); + + return LGW_I2C_SUCCESS; +} + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int lgw_stts751_configure(void) { + return stts751_configure(lgw_i2c_target); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_stts751_get_temperature(float * temperature) { + return stts751_get_temperature(lgw_i2c_target, temperature); +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_sx1250.c b/libloragw/src/loragw_sx1250.c new file mode 100644 index 0000000..1e2bedb --- /dev/null +++ b/libloragw/src/loragw_sx1250.c @@ -0,0 +1,285 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1250 radios. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* printf fprintf */ +#include /* malloc free */ +#include /* lseek, close */ +#include /* open */ +#include /* memset */ + +#include +#include + +#include "loragw_spi.h" +#include "loragw_reg.h" +#include "loragw_aux.h" +#include "loragw_sx1250.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_RAD == 1 + #define DEBUG_MSG(str) fprintf(stderr, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) + #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_SPI_ERROR;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return LGW_SPI_ERROR;} +#endif + +#define SX1250_FREQ_TO_REG(f) (uint32_t)((uint64_t)f * (1 << 25) / 32000000U) + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define WAIT_BUSY_SX1250_MS 1 + +/* -------------------------------------------------------------------------- */ +/* --- INTERNAL SHARED VARIABLES -------------------------------------------- */ + +extern void *lgw_spi_target; /*! generic pointer to the SPI device */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int sx1250_write_command(uint8_t rf_chain, sx1250_op_code_t op_code, uint8_t *data, uint16_t size) { + int spi_device; + int cmd_size = 2; /* header + op_code */ + uint8_t out_buf[cmd_size + size]; + uint8_t command_size; + struct spi_ioc_transfer k; + int a, i; + + /* wait BUSY */ + wait_ms(WAIT_BUSY_SX1250_MS); + + /* check input variables */ + CHECK_NULL(lgw_spi_target); + + spi_device = *(int *)lgw_spi_target; /* must check that spi_target is not null beforehand */ + + /* prepare frame to be sent */ + out_buf[0] = (rf_chain == 0) ? LGW_SPI_MUX_TARGET_RADIOA : LGW_SPI_MUX_TARGET_RADIOB; + out_buf[1] = (uint8_t)op_code; + for(i = 0; i < (int)size; i++) { + out_buf[cmd_size + i] = data[i]; + } + command_size = cmd_size + size; + + /* I/O transaction */ + memset(&k, 0, sizeof(k)); /* clear k */ + k.tx_buf = (unsigned long) out_buf; + k.len = command_size; + k.speed_hz = SPI_SPEED; + k.cs_change = 0; + k.bits_per_word = 8; + a = ioctl(spi_device, SPI_IOC_MESSAGE(1), &k); + + /* determine return code */ + if (a != (int)k.len) { + DEBUG_MSG("ERROR: SPI WRITE FAILURE\n"); + return LGW_SPI_ERROR; + } else { + DEBUG_MSG("Note: SPI write success\n"); + return LGW_SPI_SUCCESS; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1250_read_command(uint8_t rf_chain, sx1250_op_code_t op_code, uint8_t *data, uint16_t size) { + int spi_device; + int cmd_size = 2; /* header + op_code + NOP */ + uint8_t out_buf[cmd_size + size]; + uint8_t command_size; + uint8_t in_buf[ARRAY_SIZE(out_buf)]; + struct spi_ioc_transfer k; + int a, i; + + /* wait BUSY */ + wait_ms(WAIT_BUSY_SX1250_MS); + + /* check input variables */ + CHECK_NULL(lgw_spi_target); + CHECK_NULL(data); + + spi_device = *(int *)lgw_spi_target; /* must check that spi_target is not null beforehand */ + + /* prepare frame to be sent */ + out_buf[0] = (rf_chain == 0) ? LGW_SPI_MUX_TARGET_RADIOA : LGW_SPI_MUX_TARGET_RADIOB; + out_buf[1] = (uint8_t)op_code; + for(i = 0; i < (int)size; i++) { + out_buf[cmd_size + i] = data[i]; + } + command_size = cmd_size + size; + + /* I/O transaction */ + memset(&k, 0, sizeof(k)); /* clear k */ + k.tx_buf = (unsigned long) out_buf; + k.rx_buf = (unsigned long) in_buf; + k.len = command_size; + k.cs_change = 0; + a = ioctl(spi_device, SPI_IOC_MESSAGE(1), &k); + + /* determine return code */ + if (a != (int)k.len) { + DEBUG_MSG("ERROR: SPI READ FAILURE\n"); + return LGW_SPI_ERROR; + } else { + DEBUG_MSG("Note: SPI read success\n"); + //*data = in_buf[command_size - 1]; + memcpy(data, in_buf + cmd_size, size); + return LGW_SPI_SUCCESS; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1250_calibrate(uint8_t rf_chain, uint32_t freq_hz) { + uint8_t buff[16]; + + buff[0] = 0x00; + sx1250_read_command(rf_chain, GET_STATUS, buff, 1); + + /* Run calibration */ + if ((freq_hz > 430E6) && (freq_hz < 440E6)) { + buff[0] = 0x6B; + buff[1] = 0x6F; + } else if ((freq_hz > 470E6) && (freq_hz < 510E6)) { + buff[0] = 0x75; + buff[1] = 0x81; + } else if ((freq_hz > 779E6) && (freq_hz < 787E6)) { + buff[0] = 0xC1; + buff[1] = 0xC5; + } else if ((freq_hz > 863E6) && (freq_hz < 870E6)) { + buff[0] = 0xD7; + buff[1] = 0xDB; + } else if ((freq_hz > 902E6) && (freq_hz < 928E6)) { + buff[0] = 0xE1; + buff[1] = 0xE9; + } else { + printf("ERROR: failed to calibrate sx1250 radio, frequency range not supported (%u)\n", freq_hz); + return -1; + } + sx1250_write_command(rf_chain, CALIBRATE_IMAGE, buff, 2); + + /* Wait for calibration to complete */ + wait_ms(10); + + buff[0] = 0x00; + buff[1] = 0x00; + buff[2] = 0x00; + sx1250_read_command(rf_chain, GET_DEVICE_ERRORS, buff, 3); + if (TAKE_N_BITS_FROM(buff[2], 4, 1) != 0) { + printf("ERROR: sx1250 Image Calibration Error\n"); + return -1; + } + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1250_setup(uint8_t rf_chain, uint32_t freq_hz) { + int32_t freq_reg; + uint8_t buff[16]; + + /* Set Radio in Standby mode */ + buff[0] = (uint8_t)STDBY_XOSC; + sx1250_write_command(rf_chain, SET_STANDBY, buff, 1); + wait_ms(10); + + buff[0] = 0x00; + sx1250_read_command(rf_chain, GET_STATUS, buff, 1); + + /* Set Bitrate to maximum (to lower TX to FS switch time) */ + buff[0] = 0x06; + buff[1] = 0xA1; + buff[2] = 0x01; + sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); + buff[0] = 0x06; + buff[1] = 0xA2; + buff[2] = 0x00; + sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); + buff[0] = 0x06; + buff[1] = 0xA3; + buff[2] = 0x00; + sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); + + /* Configure DIO for Rx */ + buff[0] = 0x05; + buff[1] = 0x82; + buff[2] = 0x00; + sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); /* Drive strength to min */ + buff[0] = 0x05; + buff[1] = 0x83; + buff[2] = 0x00; + sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); /* Input enable, all disabled */ + buff[0] = 0x05; + buff[1] = 0x84; + buff[2] = 0x00; + sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); /* No pull up */ + buff[0] = 0x05; + buff[1] = 0x85; + buff[2] = 0x00; + sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); /* No pull down */ + buff[0] = 0x05; + buff[1] = 0x80; + buff[2] = 0x00; + sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); /* Output enable, all enabled */ + + /* Set fix gain (??) */ + buff[0] = 0x08; + buff[1] = 0xB6; + buff[2] = 0x2A; + sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); + + /* Set frequency */ + freq_reg = SX1250_FREQ_TO_REG(freq_hz); + buff[0] = (uint8_t)(freq_reg >> 24); + buff[1] = (uint8_t)(freq_reg >> 16); + buff[2] = (uint8_t)(freq_reg >> 8); + buff[3] = (uint8_t)(freq_reg >> 0); + sx1250_write_command(rf_chain, SET_RF_FREQUENCY, buff, 4); + + /* Set frequency offset to 0 */ + buff[0] = 0x08; + buff[1] = 0x8F; + buff[2] = 0x00; + buff[3] = 0x00; + buff[4] = 0x00; + sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 5); + + /* Set Radio in Rx mode, necessary to give a clock to SX1302 */ + buff[0] = 0xFF; + buff[1] = 0xFF; + buff[2] = 0xFF; + sx1250_write_command(rf_chain, SET_RX, buff, 3); /* Rx Continuous */ + + buff[0] = 0x05; + buff[1] = 0x87; + buff[2] = 0x0B; + sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); /* FPGA_MODE_RX */ + + return 0; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_sx125x.c b/libloragw/src/loragw_sx125x.c new file mode 100644 index 0000000..43deebb --- /dev/null +++ b/libloragw/src/loragw_sx125x.c @@ -0,0 +1,384 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1255/SX1257 radios. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* bool type */ +#include /* printf fprintf */ +#include /* memset */ +#include +#include + +#include "loragw_sx125x.h" +#include "loragw_spi.h" +#include "loragw_aux.h" +#include "loragw_reg.h" +#include "loragw_hal.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_RAD == 1 + #define DEBUG_MSG(str) fprintf(stderr, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) + #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_REG_ERROR;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return LGW_REG_ERROR;} +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE TYPES -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define PLL_LOCK_MAX_ATTEMPTS 5 + +#define READ_ACCESS 0x00 +#define WRITE_ACCESS 0x80 + +static const struct radio_reg_s sx125x_regs[RADIO_TOTALREGS] = { + {0,0,8}, /* MODE */ + {0,3,1}, /* MODE__PA_DRIVER_EN */ + {0,2,1}, /* MODE__TX_EN */ + {0,1,1}, /* MODE__RX_EN */ + {0,0,1}, /* MODE__STANDBY_EN */ + {1,0,8}, /* FRF_RX_MSB */ + {2,0,8}, /* FRF_RX_MID */ + {3,0,8}, /* FRF_RX_LSB */ + {4,0,8}, /* FRF_TX_MSB */ + {5,0,8}, /* FRF_TX_MID */ + {6,0,8}, /* FRF_TX_LSB */ + {7,0,8}, /* VERSION */ + {8,0,8}, /* TX_GAIN */ + {8,4,3}, /* TX_GAIN__DAC_GAIN */ + {8,0,4}, /* TX_GAIN__MIX_GAIN */ + {10,0,8}, /* TX_BW */ + {10,5,2}, /* TX_BW__PLL_BW */ + {10,0,5}, /* TX_BW__ANA_BW */ + {11,0,8}, /* TX_DAC_BW */ + {12,0,8}, /* RX_ANA_GAIN */ + {12,5,3}, /* RX_ANA_GAIN__LNA_GAIN */ + {12,1,4}, /* RX_ANA_GAIN__BB_GAIN */ + {12,0,1}, /* RX_ANA_GAIN__LNA_ZIN */ + {13,0,8}, /* RX_BW */ + {13,5,3}, /* RX_BW__ADC_BW */ + {13,2,3}, /* RX_BW__ADC_TRIM */ + {13,0,2}, /* RX_BW__BB_BW */ + {14,0,8}, /* RX_PLL_BW */ + {14,1,2}, /* RX_PLL_BW__PLL_BW */ + {14,0,1}, /* RX_PLL_BW__ADC_TEMP_EN */ + {15,0,8}, /* DIO_MAPPING */ + {15,6,2}, /* DIO_MAPPING__DIO_0_MAPPING */ + {15,4,2}, /* DIO_MAPPING__DIO_1_MAPPING */ + {15,2,2}, /* DIO_MAPPING__DIO_2_MAPPING */ + {15,0,2}, /* DIO_MAPPING__DIO_3_MAPPING */ + {16,0,8}, /* CLK_SELECT */ + {16,3,1}, /* CLK_SELECT__DIG_LOOPBACK_EN */ + {16,2,1}, /* CLK_SELECT__RF_LOOPBACK_EN */ + {16,1,1}, /* CLK_SELECT__CLK_OUT */ + {16,0,1}, /* CLK_SELECT__DAC_CLK_SELECT */ + {17,0,8}, /* MODE_STATUS */ + {17,2,1}, /* MODE_STATUS__LOW_BAT_EN */ + {17,1,1}, /* MODE_STATUS__RX_PLL_LOCKED */ + {17,0,1}, /* MODE_STATUS__TX_PLL_LOCKED */ + {26,0,8}, /* LOW_BAT_THRESH */ + {38,0,8}, /* SX1257_XOSC_TEST */ + {38,4,3}, /* SX1257_XOSC_TEST__DISABLE */ + {38,0,4}, /* SX1257_XOSC_TEST__GM_STARTUP */ + {40,0,8}, /* SX1255_XOSC_TEST */ + {40,4,3}, /* SX1255_XOSC_TEST__DISABLE */ + {40,0,4} /* SX1255_XOSC_TEST__GM_STARTUP */ +}; + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +extern void *lgw_spi_target; /*! generic pointer to the SPI device */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ + +/* Simple read */ +int sx125x_reg_r(void *spi_target, uint8_t spi_mux_target, uint8_t address, uint8_t *data) { + int spi_device; + uint8_t out_buf[3]; + uint8_t command_size; + uint8_t in_buf[ARRAY_SIZE(out_buf)]; + struct spi_ioc_transfer k; + int a; + + /* check input variables */ + CHECK_NULL(spi_target); + CHECK_NULL(data); + + spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */ + + /* prepare frame to be sent */ + out_buf[0] = spi_mux_target; + out_buf[1] = READ_ACCESS | (address & 0x7F); + out_buf[2] = 0x00; + command_size = 3; + + /* I/O transaction */ + memset(&k, 0, sizeof(k)); /* clear k */ + k.tx_buf = (unsigned long) out_buf; + k.rx_buf = (unsigned long) in_buf; + k.len = command_size; + k.cs_change = 0; + a = ioctl(spi_device, SPI_IOC_MESSAGE(1), &k); + + /* determine return code */ + if (a != (int)k.len) { + DEBUG_MSG("ERROR: SPI READ FAILURE\n"); + return LGW_SPI_ERROR; + } else { + //DEBUG_MSG("Note: SPI read success\n"); + *data = in_buf[command_size - 1]; + return LGW_SPI_SUCCESS; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx125x_reg_w(void *spi_target, uint8_t spi_mux_target, uint8_t address, uint8_t data) { + int spi_device; + uint8_t out_buf[3]; + uint8_t command_size; + struct spi_ioc_transfer k; + int a; + + /* check input variables */ + CHECK_NULL(spi_target); + + spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */ + + /* prepare frame to be sent */ + out_buf[0] = spi_mux_target; + out_buf[1] = WRITE_ACCESS | (address & 0x7F); + out_buf[2] = data; + command_size = 3; + + /* I/O transaction */ + memset(&k, 0, sizeof(k)); /* clear k */ + k.tx_buf = (unsigned long) out_buf; + k.len = command_size; + k.speed_hz = SPI_SPEED; + k.cs_change = 0; + k.bits_per_word = 8; + a = ioctl(spi_device, SPI_IOC_MESSAGE(1), &k); + + /* determine return code */ + if (a != (int)k.len) { + DEBUG_MSG("ERROR: SPI WRITE FAILURE\n"); + return LGW_SPI_ERROR; + } else { + //DEBUG_MSG("Note: SPI write success\n"); + return LGW_SPI_SUCCESS; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_sx125x_reg_w(radio_reg_t idx, uint8_t data, uint8_t rf_chain) { + + int spi_stat; + struct radio_reg_s reg; + uint8_t mask; + uint8_t r; + uint8_t w; + uint8_t val_check; + + /* checking input parameters */ + if (rf_chain >= LGW_RF_CHAIN_NB) { + DEBUG_MSG("ERROR: INVALID RF_CHAIN\n"); + return LGW_REG_ERROR; + } + if (idx >= RADIO_TOTALREGS) { + DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n"); + return LGW_REG_ERROR; + } + + reg = sx125x_regs[idx]; + + if ((reg.leng == 8) && (reg.offs == 0)){ + /* direct write */ + spi_stat = sx125x_reg_w(lgw_spi_target, ((rf_chain == 0) ? LGW_SPI_MUX_TARGET_RADIOA : LGW_SPI_MUX_TARGET_RADIOB), reg.addr, data); + } else { + /* read-modify-write */ + spi_stat = sx125x_reg_r(lgw_spi_target, ((rf_chain == 0) ? LGW_SPI_MUX_TARGET_RADIOA : LGW_SPI_MUX_TARGET_RADIOB), reg.addr, &r); + mask = ((1 << reg.leng) - 1) << reg.offs; + w = (r & ~mask) | ((data << reg.offs) & mask); + spi_stat |= sx125x_reg_w(lgw_spi_target, ((rf_chain == 0) ? LGW_SPI_MUX_TARGET_RADIOA : LGW_SPI_MUX_TARGET_RADIOB), reg.addr, w); + } + + /* Check that we can read what we have written */ + lgw_sx125x_reg_r(idx, &val_check, rf_chain); + if (val_check != data) { + printf("ERROR: sx125x register %d write failed (w:%u r:%u)!!\n", idx, data, val_check); + spi_stat = LGW_SPI_ERROR; + } + + if (spi_stat != LGW_SPI_SUCCESS) { + DEBUG_MSG("ERROR: SPI ERROR DURING RADIO REGISTER WRITE\n"); + return LGW_REG_ERROR; + } else { + return LGW_REG_SUCCESS; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_sx125x_reg_r(radio_reg_t idx, uint8_t *data, uint8_t rf_chain) { + + int spi_stat; + struct radio_reg_s reg; + uint8_t mask; + uint8_t r; + + /* checking input parameters */ + if (rf_chain >= LGW_RF_CHAIN_NB) { + DEBUG_MSG("ERROR: INVALID RF_CHAIN\n"); + return LGW_REG_ERROR; + } + if (idx >= RADIO_TOTALREGS) { + DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n"); + return LGW_REG_ERROR; + } + + reg = sx125x_regs[idx]; + + spi_stat = sx125x_reg_r(lgw_spi_target, ((rf_chain == 0) ? LGW_SPI_MUX_TARGET_RADIOA : LGW_SPI_MUX_TARGET_RADIOB), reg.addr, &r); + mask = ((1 << reg.leng) - 1) << reg.offs; + *data = (r & mask) >> reg.offs; + + if (spi_stat != LGW_SPI_SUCCESS) { + DEBUG_MSG("ERROR: SPI ERROR DURING RADIO REGISTER READ\n"); + return LGW_REG_ERROR; + } else { + return LGW_REG_SUCCESS; + } +} + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int sx125x_setup(uint8_t rf_chain, uint8_t rf_clkout, bool rf_enable, uint8_t rf_radio_type, uint32_t freq_hz) { + uint32_t part_int = 0; + uint32_t part_frac = 0; + int cpt_attempts = 0; + uint8_t val; + + if (rf_chain >= LGW_RF_CHAIN_NB) { + DEBUG_MSG("ERROR: INVALID RF_CHAIN\n"); + return -1; + } + + /* Get version to identify SX1255/57 silicon revision */ + lgw_sx125x_reg_r(SX125x_REG_VERSION, &val, rf_chain); + DEBUG_PRINTF("Note: SX125x #%d version register returned 0x%02x\n", rf_chain, val); + + /* General radio setup */ + if (rf_clkout == rf_chain) { + lgw_sx125x_reg_w(SX125x_REG_CLK_SELECT, SX125x_TX_DAC_CLK_SEL + 2, rf_chain); + DEBUG_PRINTF("Note: SX125x #%d clock output enabled\n", rf_chain); + } else { + lgw_sx125x_reg_w(SX125x_REG_CLK_SELECT, SX125x_TX_DAC_CLK_SEL, rf_chain); + DEBUG_PRINTF("Note: SX125x #%d clock output disabled\n", rf_chain); + } + + switch (rf_radio_type) { + case LGW_RADIO_TYPE_SX1255: + lgw_sx125x_reg_w(SX125x_REG_SX1255_XOSC_TEST__GM_STARTUP, SX125x_XOSC_GM_STARTUP, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_SX1255_XOSC_TEST__DISABLE, SX125x_XOSC_DISABLE, rf_chain); + break; + case LGW_RADIO_TYPE_SX1257: + lgw_sx125x_reg_w(SX125x_REG_SX1257_XOSC_TEST__GM_STARTUP, SX125x_XOSC_GM_STARTUP, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_SX1257_XOSC_TEST__DISABLE, SX125x_XOSC_DISABLE, rf_chain); + break; + default: + DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", rf_radio_type); + break; + } + + if (rf_enable == true) { + /* Tx gain and trim */ + lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__MIX_GAIN, SX125x_TX_MIX_GAIN, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__DAC_GAIN, SX125x_TX_DAC_GAIN, rf_chain); + + lgw_sx125x_reg_w(SX125x_REG_TX_BW__ANA_BW, SX125x_TX_ANA_BW, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_TX_BW__PLL_BW, SX125x_TX_PLL_BW, rf_chain); + + lgw_sx125x_reg_w(SX125x_REG_TX_DAC_BW, SX125x_TX_DAC_BW, rf_chain); + + /* Rx gain and trim */ + lgw_sx125x_reg_w(SX125x_REG_RX_ANA_GAIN__LNA_ZIN, SX125x_LNA_ZIN, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_RX_ANA_GAIN__BB_GAIN, SX125x_RX_BB_GAIN, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_RX_ANA_GAIN__LNA_GAIN, SX125x_RX_LNA_GAIN, rf_chain); + + lgw_sx125x_reg_w(SX125x_REG_RX_BW__BB_BW, SX125x_RX_BB_BW, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_RX_BW__ADC_TRIM, SX125x_RX_ADC_TRIM, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_RX_BW__ADC_BW, SX125x_RX_ADC_BW, rf_chain); + + lgw_sx125x_reg_w(SX125x_REG_RX_PLL_BW__ADC_TEMP_EN, SX125x_ADC_TEMP, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_RX_PLL_BW__PLL_BW, SX125x_RX_PLL_BW, rf_chain); + + /* set RX PLL frequency */ + switch (rf_radio_type) { + case LGW_RADIO_TYPE_SX1255: + part_int = freq_hz / (SX125x_32MHz_FRAC << 7); /* integer part, gives the MSB */ + part_frac = ((freq_hz % (SX125x_32MHz_FRAC << 7)) << 9) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ + break; + case LGW_RADIO_TYPE_SX1257: + part_int = freq_hz / (SX125x_32MHz_FRAC << 8); /* integer part, gives the MSB */ + part_frac = ((freq_hz % (SX125x_32MHz_FRAC << 8)) << 8) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ + break; + default: + DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", rf_radio_type); + break; + } + + lgw_sx125x_reg_w(SX125x_REG_FRF_RX_MSB, 0xFF & part_int, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_FRF_RX_MID, 0xFF & (part_frac >> 8), rf_chain); + lgw_sx125x_reg_w(SX125x_REG_FRF_RX_LSB, 0xFF & part_frac, rf_chain); + + /* start and PLL lock */ + do { + if (cpt_attempts >= PLL_LOCK_MAX_ATTEMPTS) { + DEBUG_MSG("ERROR: FAIL TO LOCK PLL\n"); + return -1; + } + lgw_sx125x_reg_w(SX125x_REG_MODE, 1, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_MODE, 3, rf_chain); + ++cpt_attempts; + DEBUG_PRINTF("Note: SX125x #%d PLL start (attempt %d)\n", rf_chain, cpt_attempts); + wait_ms(1); + lgw_sx125x_reg_r(SX125x_REG_MODE_STATUS, &val, rf_chain); + } while ((val & 0x02) == 0); + } else { + DEBUG_PRINTF("Note: SX125x #%d kept in standby mode\n", rf_chain); + } + + return 0; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_sx1302.c b/libloragw/src/loragw_sx1302.c new file mode 100644 index 0000000..6e047ce --- /dev/null +++ b/libloragw/src/loragw_sx1302.c @@ -0,0 +1,2317 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + SX1302 Hardware Abstraction Layer entry functions. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* printf fprintf */ +#include /* malloc free */ +#include /* lseek, close */ +#include /* open */ +#include /* memset */ +#include /* pow, cell */ +#include +#include + +#include +#include + +#include "loragw_reg.h" +#include "loragw_aux.h" +#include "loragw_hal.h" +#include "loragw_sx1302.h" +#include "loragw_sx1302_timestamp.h" +#include "loragw_sx1302_rx.h" +#include "loragw_sx1250.h" +#include "loragw_agc_params.h" +#include "loragw_cal.h" +#include "loragw_debug.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_SX1302 == 1 + #define DEBUG_MSG(str) fprintf(stderr, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stderr, fmt, args) + #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_REG_ERROR;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return LGW_REG_ERROR;} +#endif + +#define IF_HZ_TO_REG(f) ((f << 5) / 15625) + +#define SX1302_FREQ_TO_REG(f) (uint32_t)((uint64_t)f * (1 << 18) / 32000000U) + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE TYPES -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define AGC_RADIO_A_INIT_DONE 0x80 +#define AGC_RADIO_B_INIT_DONE 0x20 + +#define MCU_AGC 0x01 +#define MCU_ARB 0x02 + +#define AGC_MEM_ADDR 0x0000 +#define ARB_MEM_ADDR 0x2000 + +#define MCU_FW_SIZE 8192 /* size of the firmware IN BYTES (= twice the number of 14b words) */ + +#define FW_VERSION_CAL 1 /* Expected version of calibration firmware */ + +#define RSSI_FSK_POLY_0 86 /* polynomiam coefficients to linearize FSK RSSI */ +#define RSSI_FSK_POLY_1 1 +#define RSSI_FSK_POLY_2 0 + +#define FREQ_OFFSET_LSB_125KHZ 0.11920929f /* 125000 * 32 / 2^6 / 2^19 */ +#define FREQ_OFFSET_LSB_250KHZ 0.238418579f /* 250000 * 32 / 2^6 / 2^19 */ +#define FREQ_OFFSET_LSB_500KHZ 0.476837158f /* 500000 * 32 / 2^6 / 2^19 */ + +/* sx1302 hardware modem capabilities */ +#define LGW_IFMODEM_CONFIG {\ + IF_LORA_MULTI, \ + IF_LORA_MULTI, \ + IF_LORA_MULTI, \ + IF_LORA_MULTI, \ + IF_LORA_MULTI, \ + IF_LORA_MULTI, \ + IF_LORA_MULTI, \ + IF_LORA_MULTI, \ + IF_LORA_STD, \ + IF_FSK_STD } /* configuration of available IF chains and modems on the hardware */ + +/* constant arrays defining hardware capability */ +const uint8_t ifmod_config[LGW_IF_CHAIN_NB] = LGW_IFMODEM_CONFIG; + +#define MIN_LORA_PREAMBLE 6 +#define STD_LORA_PREAMBLE 8 +#define MIN_FSK_PREAMBLE 3 +#define STD_FSK_PREAMBLE 5 + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +/* Radio calibration firmware */ +#include "cal_fw.var" /* text_cal_sx1257_16_Nov_1 */ + +/* Buffer to hold RX data */ +rx_buffer_t rx_buffer; + +/* Internal timestamp counter */ +timestamp_counter_t counter_us; + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ + +/** +@brief TODO +@param TODO +@return TODO +*/ +extern int32_t lgw_sf_getval(int x); + +/** +@brief TODO +@param TODO +@return TODO +*/ +extern int32_t lgw_bw_getval(int x); + +/** +@brief TODO +@param TODO +@return TODO +*/ +void lora_crc16(const char data, int *crc); + +/* -------------------------------------------------------------------------- */ +/* --- INTERNAL SHARED VARIABLES -------------------------------------------- */ + +/* Log file */ +extern FILE * log_file; + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +int calculate_freq_to_time_drift(uint32_t freq_hz, uint8_t bw, uint16_t * mant, uint8_t * exp) { + uint64_t mantissa_u64; + uint8_t exponent = 0; + int32_t bw_hz; + + /* check input variables */ + CHECK_NULL(mant); + CHECK_NULL(exp); + + bw_hz = lgw_bw_getval(bw); + if (bw_hz < 0) { + printf("ERROR: Unsupported bandwidth for frequency to time drift calculation\n"); + return LGW_REG_ERROR; + } + + mantissa_u64 = (uint64_t)bw_hz * (2 << (20-1)) / freq_hz; + while (mantissa_u64 < 2048) { + exponent += 1; + mantissa_u64 <<= 1; + } + + *mant = (uint16_t)mantissa_u64; + *exp = exponent; + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void lora_crc16(const char data, int *crc) { + int next = 0; + next = (((data>>0)&1) ^ ((*crc>>12)&1) ^ ((*crc>> 8)&1) ) ; + next += ((((data>>1)&1) ^ ((*crc>>13)&1) ^ ((*crc>> 9)&1) )<<1 ) ; + next += ((((data>>2)&1) ^ ((*crc>>14)&1) ^ ((*crc>>10)&1) )<<2 ) ; + next += ((((data>>3)&1) ^ ((*crc>>15)&1) ^ ((*crc>>11)&1) )<<3 ) ; + next += ((((data>>4)&1) ^ ((*crc>>12)&1) )<<4 ) ; + next += ((((data>>5)&1) ^ ((*crc>>13)&1) ^ ((*crc>>12)&1) ^ ((*crc>> 8)&1))<<5 ) ; + next += ((((data>>6)&1) ^ ((*crc>>14)&1) ^ ((*crc>>13)&1) ^ ((*crc>> 9)&1))<<6 ) ; + next += ((((data>>7)&1) ^ ((*crc>>15)&1) ^ ((*crc>>14)&1) ^ ((*crc>>10)&1))<<7 ) ; + next += ((((*crc>>0)&1) ^ ((*crc>>15)&1) ^ ((*crc>>11)&1) )<<8 ) ; + next += ((((*crc>>1)&1) ^ ((*crc>>12)&1) )<<9 ) ; + next += ((((*crc>>2)&1) ^ ((*crc>>13)&1) )<<10) ; + next += ((((*crc>>3)&1) ^ ((*crc>>14)&1) )<<11) ; + next += ((((*crc>>4)&1) ^ ((*crc>>15)&1) ^ ((*crc>>12)&1) ^ ((*crc>> 8)&1))<<12) ; + next += ((((*crc>>5)&1) ^ ((*crc>>13)&1) ^ ((*crc>> 9)&1) )<<13) ; + next += ((((*crc>>6)&1) ^ ((*crc>>14)&1) ^ ((*crc>>10)&1) )<<14) ; + next += ((((*crc>>7)&1) ^ ((*crc>>15)&1) ^ ((*crc>>11)&1) )<<15) ; + (*crc) = next; +} + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +void sx1302_init(struct lgw_conf_timestamp_s *conf_ts) { + timestamp_counter_new(&counter_us); + + if (conf_ts != NULL) { + timestamp_counter_mode(conf_ts->enable_precision_ts, conf_ts->max_ts_metrics, conf_ts->nb_symbols); + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_get_eui(uint64_t * eui) { + int i, err; + int32_t val; + + *eui = 0; + for (i = 0; i < 8; i++) { + err = lgw_reg_w(SX1302_REG_OTP_BYTE_ADDR_ADDR, i); + if (err != LGW_REG_SUCCESS) { + return LGW_REG_ERROR; + } + err = lgw_reg_r(SX1302_REG_OTP_RD_DATA_RD_DATA, &val); + if (err != LGW_REG_SUCCESS) { + return LGW_REG_ERROR; + } + + *eui |= (uint64_t)((uint8_t)val) << (56 - (i * 8)); + } + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_update(void) { + int32_t val; + + /* Check MCUs parity errors */ + lgw_reg_r(SX1302_REG_AGC_MCU_CTRL_PARITY_ERROR, &val); + if (val != 0) { + printf("ERROR: Parity error check failed on AGC firmware\n"); + return LGW_REG_ERROR; + } + lgw_reg_r(SX1302_REG_ARB_MCU_CTRL_PARITY_ERROR, &val); + if (val != 0) { + printf("ERROR: Parity error check failed on ARB firmware\n"); + return LGW_REG_ERROR; + } + + /* Update internal timestamp counter wrapping status */ + timestamp_counter_get(&counter_us, false); /* maintain inst counter */ + timestamp_counter_get(&counter_us, true); /* maintain pps counter */ + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_radio_clock_select(uint8_t rf_chain) { + /* Check input parameters */ + if (rf_chain >= LGW_RF_CHAIN_NB) + { + DEBUG_MSG("ERROR: invalid RF chain\n"); + return LGW_REG_ERROR; + } + + /* Switch SX1302 clock from SPI clock to radio clock of the selected RF chain */ + switch (rf_chain) { + case 0: + DEBUG_MSG("Select Radio A clock\n"); + lgw_reg_w(SX1302_REG_CLK_CTRL_CLK_SEL_CLK_RADIO_A_SEL, 0x01); + lgw_reg_w(SX1302_REG_CLK_CTRL_CLK_SEL_CLK_RADIO_B_SEL, 0x00); + break; + case 1: + DEBUG_MSG("Select Radio B clock\n"); + lgw_reg_w(SX1302_REG_CLK_CTRL_CLK_SEL_CLK_RADIO_A_SEL, 0x00); + lgw_reg_w(SX1302_REG_CLK_CTRL_CLK_SEL_CLK_RADIO_B_SEL, 0x01); + break; + default: + return LGW_REG_ERROR; + } + + /* Enable clock dividers */ + lgw_reg_w(SX1302_REG_CLK_CTRL_CLK_SEL_CLKDIV_EN, 0x01); + + /* Set the RIF clock to the 32MHz clock of the radio */ + lgw_reg_w(SX1302_REG_COMMON_CTRL0_CLK32_RIF_CTRL, 0x01); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_radio_reset(uint8_t rf_chain, lgw_radio_type_t type) { + uint16_t reg_radio_en; + uint16_t reg_radio_rst; + + /* Check input parameters */ + if (rf_chain >= LGW_RF_CHAIN_NB) + { + DEBUG_MSG("ERROR: invalid RF chain\n"); + return LGW_REG_ERROR; + } + if ((type != LGW_RADIO_TYPE_SX1255) && (type != LGW_RADIO_TYPE_SX1257) && (type != LGW_RADIO_TYPE_SX1250)) { + DEBUG_MSG("ERROR: invalid radio type\n"); + return LGW_REG_ERROR; + } + + /* Switch to SPI clock before reseting the radio */ + lgw_reg_w(SX1302_REG_COMMON_CTRL0_CLK32_RIF_CTRL, 0x00); + + /* Enable the radio */ + reg_radio_en = REG_SELECT(rf_chain, SX1302_REG_AGC_MCU_RF_EN_A_RADIO_EN, SX1302_REG_AGC_MCU_RF_EN_B_RADIO_EN); + lgw_reg_w(reg_radio_en, 0x01); + + /* Select the proper reset sequence depending on the radio type */ + reg_radio_rst = REG_SELECT(rf_chain, SX1302_REG_AGC_MCU_RF_EN_A_RADIO_RST, SX1302_REG_AGC_MCU_RF_EN_B_RADIO_RST); + lgw_reg_w(reg_radio_rst, 0x01); + wait_ms(500); + lgw_reg_w(reg_radio_rst, 0x00); + wait_ms(10); + switch (type) { + case LGW_RADIO_TYPE_SX1255: + case LGW_RADIO_TYPE_SX1257: + /* Do nothing */ + DEBUG_PRINTF("INFO: reset sx125x (RADIO_%s) done\n", REG_SELECT(rf_chain, "A", "B")); + break; + case LGW_RADIO_TYPE_SX1250: + lgw_reg_w(reg_radio_rst, 0x01); + wait_ms(10); /* wait for auto calibration to complete */ + DEBUG_PRINTF("INFO: reset sx1250 (RADIO_%s) done\n", REG_SELECT(rf_chain, "A", "B")); + break; + default: + return LGW_REG_ERROR; + } + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_radio_set_mode(uint8_t rf_chain, lgw_radio_type_t type) { + uint16_t reg; + + /* Check input parameters */ + if (rf_chain >= LGW_RF_CHAIN_NB) { + DEBUG_MSG("ERROR: invalid RF chain\n"); + return LGW_REG_ERROR; + } + if ((type != LGW_RADIO_TYPE_SX1255) && (type != LGW_RADIO_TYPE_SX1257) && (type != LGW_RADIO_TYPE_SX1250)) { + DEBUG_MSG("ERROR: invalid radio type\n"); + return LGW_REG_ERROR; + } + + /* Set the radio mode */ + reg = REG_SELECT(rf_chain, SX1302_REG_COMMON_CTRL0_SX1261_MODE_RADIO_A, + SX1302_REG_COMMON_CTRL0_SX1261_MODE_RADIO_B); + switch (type) { + case LGW_RADIO_TYPE_SX1250: + DEBUG_PRINTF("Setting rf_chain_%u in sx1250 mode\n", rf_chain); + lgw_reg_w(reg, 0x01); + break; + default: + DEBUG_PRINTF("Setting rf_chain_%u in sx125x mode\n", rf_chain); + lgw_reg_w(reg, 0x00); + break; + } + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_radio_host_ctrl(bool host_ctrl) { + lgw_reg_w(SX1302_REG_COMMON_CTRL0_HOST_RADIO_CTRL, (host_ctrl == false) ? 0x00 : 0x01); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_radio_calibrate(struct lgw_conf_rxrf_s * context_rf_chain, uint8_t clksrc, struct lgw_tx_gain_lut_s * txgain_lut) { + int i; + + /* -- Reset radios */ + 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); + sx1302_radio_set_mode(i, context_rf_chain[i].type); + } + } + /* -- Select the radio which provides the clock to the sx1302 */ + sx1302_radio_clock_select(clksrc); + + /* -- Ensure PA/LNA are disabled */ + lgw_reg_w(SX1302_REG_AGC_MCU_CTRL_FORCE_HOST_FE_CTRL, 1); + lgw_reg_w(SX1302_REG_AGC_MCU_RF_EN_A_PA_EN, 0); + lgw_reg_w(SX1302_REG_AGC_MCU_RF_EN_A_LNA_EN, 0); + /* -- Start calibration */ + if ((context_rf_chain[clksrc].type == LGW_RADIO_TYPE_SX1257) || + (context_rf_chain[clksrc].type == LGW_RADIO_TYPE_SX1255)) { + DEBUG_MSG("Loading CAL fw for sx125x\n"); + if (sx1302_agc_load_firmware(cal_firmware_sx125x) != LGW_HAL_SUCCESS) { + printf("ERROR: Failed to load calibration fw\n"); + return LGW_REG_ERROR; + } + if (sx1302_cal_start(FW_VERSION_CAL, context_rf_chain, txgain_lut) != LGW_HAL_SUCCESS) { + printf("ERROR: radio calibration failed\n"); + sx1302_radio_reset(0, context_rf_chain[0].type); + sx1302_radio_reset(1, context_rf_chain[1].type); + return LGW_REG_ERROR; + } + } else { + DEBUG_MSG("Calibrating sx1250 radios\n"); + for (i = 0; i < LGW_RF_CHAIN_NB; i++) { + if (context_rf_chain[i].enable == true) { + if (sx1250_calibrate(i, context_rf_chain[i].freq_hz)) { + printf("ERROR: radio calibration failed\n"); + return LGW_REG_ERROR; + } + } + } + } + /* -- Release control over FE */ + lgw_reg_w(SX1302_REG_AGC_MCU_CTRL_FORCE_HOST_FE_CTRL, 0); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_pa_lna_lut_configure(void) { + lgw_reg_w(SX1302_REG_AGC_MCU_LUT_TABLE_A_PA_LUT, 0x04); /* Enable PA: RADIO_CTRL[2] is high when PA_EN=1 & LNA_EN=0 */ + lgw_reg_w(SX1302_REG_AGC_MCU_LUT_TABLE_B_PA_LUT, 0x04); /* Enable PA: RADIO_CTRL[8] is high when PA_EN=1 & LNA_EN=0 */ + lgw_reg_w(SX1302_REG_AGC_MCU_LUT_TABLE_A_LNA_LUT, 0x02); /* Enable LNA: RADIO_CTRL[1] is high when PA_EN=0 & LNA_EN=1 */ + lgw_reg_w(SX1302_REG_AGC_MCU_LUT_TABLE_B_LNA_LUT, 0x02); /* Enable LNA: RADIO_CTRL[7] is high when PA_EN=0 & LNA_EN=1 */ + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_radio_fe_configure(void) { + lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_BB_FILTER_ALPHA_RADIO_A_RSSI_BB_FILTER_ALPHA, 0x03); + lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_DEC_FILTER_ALPHA_RADIO_A_RSSI_DEC_FILTER_ALPHA, 0x07); + lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_BB_FILTER_ALPHA_RADIO_B_RSSI_BB_FILTER_ALPHA, 0x03); + lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_DEC_FILTER_ALPHA_RADIO_B_RSSI_DEC_FILTER_ALPHA, 0x07); + + lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_DB_DEF_RADIO_A_RSSI_DB_DEFAULT_VALUE, 23); + lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_DEC_DEF_RADIO_A_RSSI_DEC_DEFAULT_VALUE, 66); + lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_DB_DEF_RADIO_B_RSSI_DB_DEFAULT_VALUE, 23); + lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_DEC_DEF_RADIO_B_RSSI_DEC_DEFAULT_VALUE, 66); + + lgw_reg_w(SX1302_REG_RADIO_FE_CTRL0_RADIO_A_DC_NOTCH_EN, 1); + lgw_reg_w(SX1302_REG_RADIO_FE_CTRL0_RADIO_A_HOST_FILTER_GAIN, 0x0b); + lgw_reg_w(SX1302_REG_RADIO_FE_CTRL0_RADIO_B_DC_NOTCH_EN, 1); + lgw_reg_w(SX1302_REG_RADIO_FE_CTRL0_RADIO_B_HOST_FILTER_GAIN, 0x0b); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint8_t sx1302_get_ifmod_config(uint8_t if_chain) { + return ifmod_config[if_chain]; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_channelizer_configure(struct lgw_conf_rxif_s * if_cfg, bool fix_gain) { + int32_t if_freq; + uint8_t channels_mask = 0x00; + int i; + + /* Check input parameters */ + if (if_cfg == NULL) { + printf("ERROR: Failed to configure LoRa channelizer\n"); + return LGW_REG_ERROR; + } + + /* Select which radio is connected to each multi-SF channel */ + for (i = 0; i < LGW_MULTI_NB; i++) { + channels_mask |= (if_cfg[i].rf_chain << i); + } + DEBUG_PRINTF("LoRa multi-SF radio select: 0x%02X\n", channels_mask); + lgw_reg_w(SX1302_REG_RX_TOP_RADIO_SELECT_RADIO_SELECT, channels_mask); + + /* Select which radio is connected to the LoRa service channel */ + DEBUG_PRINTF("LoRa service radio select: 0x%02X\n", if_cfg[8].rf_chain); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_LORA_SERVICE_RADIO_SEL_RADIO_SELECT, if_cfg[8].rf_chain); + + /* Select which radio is connected to the FSK channel */ + DEBUG_PRINTF("FSK radio select %u\n", if_cfg[9].rf_chain); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_RADIO_SELECT, if_cfg[9].rf_chain); + + /* Configure multi-SF channels IF frequencies */ + if_freq = IF_HZ_TO_REG(if_cfg[0].freq_hz); + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_0_MSB_IF_FREQ_0, (if_freq >> 8) & 0x0000001F); + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_0_LSB_IF_FREQ_0, (if_freq >> 0) & 0x000000FF); + + if_freq = IF_HZ_TO_REG(if_cfg[1].freq_hz); + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_1_MSB_IF_FREQ_1, (if_freq >> 8) & 0x0000001F); + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_1_LSB_IF_FREQ_1, (if_freq >> 0) & 0x000000FF); + + if_freq = IF_HZ_TO_REG(if_cfg[2].freq_hz); + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_2_MSB_IF_FREQ_2, (if_freq >> 8) & 0x0000001F); + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_2_LSB_IF_FREQ_2, (if_freq >> 0) & 0x000000FF); + + if_freq = IF_HZ_TO_REG(if_cfg[3].freq_hz); + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_3_MSB_IF_FREQ_3, (if_freq >> 8) & 0x0000001F); + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_3_LSB_IF_FREQ_3, (if_freq >> 0) & 0x000000FF); + + if_freq = IF_HZ_TO_REG(if_cfg[4].freq_hz); + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_4_MSB_IF_FREQ_4, (if_freq >> 8) & 0x0000001F); + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_4_LSB_IF_FREQ_4, (if_freq >> 0) & 0x000000FF); + + if_freq = IF_HZ_TO_REG(if_cfg[5].freq_hz); + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_5_MSB_IF_FREQ_5, (if_freq >> 8) & 0x0000001F); + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_5_LSB_IF_FREQ_5, (if_freq >> 0) & 0x000000FF); + + if_freq = IF_HZ_TO_REG(if_cfg[6].freq_hz); + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_6_MSB_IF_FREQ_6, (if_freq >> 8) & 0x0000001F); + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_6_LSB_IF_FREQ_6, (if_freq >> 0) & 0x000000FF); + + if_freq = IF_HZ_TO_REG(if_cfg[7].freq_hz); + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_7_MSB_IF_FREQ_7, (if_freq >> 8) & 0x0000001F); + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_7_LSB_IF_FREQ_7, (if_freq >> 0) & 0x000000FF); + + /* Configure LoRa service channel IF frequency */ + if_freq = IF_HZ_TO_REG(if_cfg[8].freq_hz); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_LORA_SERVICE_FREQ_MSB_IF_FREQ_0, (if_freq >> 8) & 0x0000001F); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_LORA_SERVICE_FREQ_LSB_IF_FREQ_0, (if_freq >> 0) & 0x000000FF); + + /* Configure FSK channel IF frequency */ + if_freq = IF_HZ_TO_REG(if_cfg[9].freq_hz); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_FREQ_MSB_IF_FREQ_0, (if_freq >> 8) & 0x0000001F); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_FREQ_LSB_IF_FREQ_0, (if_freq >> 0) & 0x000000FF); + + /* Set the low pass filtering corner frequency for RSSI indicator */ + lgw_reg_w(SX1302_REG_RX_TOP_RSSI_CONTROL_RSSI_FILTER_ALPHA, 0x05); + + /* Set the channelizer RSSI reset value */ + lgw_reg_w(SX1302_REG_RX_TOP_RSSI_DEF_VALUE_CHAN_RSSI_DEF_VALUE, 85); + + /* Force channelizer in fix gain, or let it be controlled by AGC */ + if (fix_gain == true) { + lgw_reg_w(SX1302_REG_RX_TOP_CHANN_DAGC_CFG5_CHAN_DAGC_MODE, 0x00); + lgw_reg_w(SX1302_REG_RX_TOP_GAIN_CONTROL_CHAN_GAIN, 5); + } else { + /* Allow the AGC to control gains */ + lgw_reg_w(SX1302_REG_RX_TOP_CHANN_DAGC_CFG5_CHAN_DAGC_MODE, 0x01); + /* Disable the internal DAGC */ + lgw_reg_w(SX1302_REG_RX_TOP_CHANN_DAGC_CFG1_CHAN_DAGC_THRESHOLD_HIGH, 255 ); + lgw_reg_w(SX1302_REG_RX_TOP_CHANN_DAGC_CFG2_CHAN_DAGC_THRESHOLD_LOW, 0 ); + lgw_reg_w(SX1302_REG_RX_TOP_CHANN_DAGC_CFG3_CHAN_DAGC_MAX_ATTEN, 15 ); + lgw_reg_w(SX1302_REG_RX_TOP_CHANN_DAGC_CFG3_CHAN_DAGC_MIN_ATTEN, 0 ); + } + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_fsk_configure(struct lgw_conf_rxif_s * cfg) { + uint64_t fsk_sync_word_reg; + uint32_t fsk_br_reg; + + DEBUG_PRINTF("FSK: syncword:0x%" PRIx64 ", syncword_size:%u\n", cfg->sync_word, cfg->sync_word_size); + + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_1_PSIZE, cfg->sync_word_size - 1); + fsk_sync_word_reg = cfg->sync_word << (8 * (8 - cfg->sync_word_size)); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE0_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 0)); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE1_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 8)); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE2_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 16)); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE3_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 24)); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE4_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 32)); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE5_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 40)); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE6_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 48)); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE7_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 56)); + + fsk_br_reg = 32000000 / cfg->datarate; + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BIT_RATE_MSB_BIT_RATE, (uint8_t)(fsk_br_reg >> 8)); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BIT_RATE_LSB_BIT_RATE, (uint8_t)(fsk_br_reg >> 0)); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_1_CH_BW_EXPO, 0x03); /* 125KHz */ + + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_RX_INVERT, 0); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_MODEM_INVERT_IQ, 0); + + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_4_RSSI_LENGTH, 4); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_PKT_MODE, 1); /* variable length */ + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_CRC_EN, 1); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_DCFREE_ENC, 2); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_CRC_IBM, 0); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_4_ERROR_OSR_TOL, 10); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_PKT_LENGTH_PKT_LENGTH, 255); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_NODE_ADRS_NODE_ADRS, 0); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_BROADCAST_BROADCAST, 0); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_AUTO_AFC, 1); /* ?? */ + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_TIMEOUT_MSB_TIMEOUT, 0); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_TIMEOUT_LSB_TIMEOUT, 128); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_lora_correlator_configure() { + lgw_reg_w(SX1302_REG_RX_TOP_SF5_CFG2_ACC_PNR, 52); + lgw_reg_w(SX1302_REG_RX_TOP_SF5_CFG4_MSP_PNR, 24); + lgw_reg_w(SX1302_REG_RX_TOP_SF5_CFG6_MSP_PEAK_NB, 7); + lgw_reg_w(SX1302_REG_RX_TOP_SF5_CFG7_MSP2_PEAK_NB, 5); + + lgw_reg_w(SX1302_REG_RX_TOP_SF6_CFG2_ACC_PNR, 52); + lgw_reg_w(SX1302_REG_RX_TOP_SF6_CFG4_MSP_PNR, 24); + lgw_reg_w(SX1302_REG_RX_TOP_SF6_CFG6_MSP_PEAK_NB, 7); + lgw_reg_w(SX1302_REG_RX_TOP_SF6_CFG7_MSP2_PEAK_NB, 5); + + lgw_reg_w(SX1302_REG_RX_TOP_SF7_CFG2_ACC_PNR, 52); + lgw_reg_w(SX1302_REG_RX_TOP_SF7_CFG4_MSP_PNR, 24); + lgw_reg_w(SX1302_REG_RX_TOP_SF7_CFG6_MSP_PEAK_NB, 7); + lgw_reg_w(SX1302_REG_RX_TOP_SF7_CFG7_MSP2_PEAK_NB, 5); + + lgw_reg_w(SX1302_REG_RX_TOP_SF8_CFG2_ACC_PNR, 52); + lgw_reg_w(SX1302_REG_RX_TOP_SF8_CFG4_MSP_PNR, 24); + lgw_reg_w(SX1302_REG_RX_TOP_SF8_CFG6_MSP_PEAK_NB, 7); + lgw_reg_w(SX1302_REG_RX_TOP_SF8_CFG7_MSP2_PEAK_NB, 5); + + lgw_reg_w(SX1302_REG_RX_TOP_SF9_CFG2_ACC_PNR, 52); + lgw_reg_w(SX1302_REG_RX_TOP_SF9_CFG4_MSP_PNR, 24); + lgw_reg_w(SX1302_REG_RX_TOP_SF9_CFG6_MSP_PEAK_NB, 7); + lgw_reg_w(SX1302_REG_RX_TOP_SF9_CFG7_MSP2_PEAK_NB, 5); + + lgw_reg_w(SX1302_REG_RX_TOP_SF10_CFG2_ACC_PNR, 52); + lgw_reg_w(SX1302_REG_RX_TOP_SF10_CFG4_MSP_PNR, 24); + lgw_reg_w(SX1302_REG_RX_TOP_SF10_CFG6_MSP_PEAK_NB, 7); + lgw_reg_w(SX1302_REG_RX_TOP_SF10_CFG7_MSP2_PEAK_NB, 5); + + lgw_reg_w(SX1302_REG_RX_TOP_SF11_CFG2_ACC_PNR, 52); + lgw_reg_w(SX1302_REG_RX_TOP_SF11_CFG4_MSP_PNR, 24); + lgw_reg_w(SX1302_REG_RX_TOP_SF11_CFG6_MSP_PEAK_NB, 7); + lgw_reg_w(SX1302_REG_RX_TOP_SF11_CFG7_MSP2_PEAK_NB, 5); + + lgw_reg_w(SX1302_REG_RX_TOP_SF12_CFG2_ACC_PNR, 52); + lgw_reg_w(SX1302_REG_RX_TOP_SF12_CFG4_MSP_PNR, 24); + lgw_reg_w(SX1302_REG_RX_TOP_SF12_CFG6_MSP_PEAK_NB, 7); + lgw_reg_w(SX1302_REG_RX_TOP_SF12_CFG7_MSP2_PEAK_NB, 5); + + lgw_reg_w(SX1302_REG_RX_TOP_CORR_CLOCK_ENABLE_CLK_EN, 0xFF); + + lgw_reg_w(SX1302_REG_RX_TOP_CORRELATOR_ENABLE_ONLY_FIRST_DET_EDGE_ENABLE_ONLY_FIRST_DET_EDGE, 0xFF); + lgw_reg_w(SX1302_REG_RX_TOP_CORRELATOR_ENABLE_ACC_CLEAR_ENABLE_CORR_ACC_CLEAR, 0xFF); + lgw_reg_w(SX1302_REG_RX_TOP_CORRELATOR_SF_EN_CORR_SF_EN, 0xFF); /* 12 11 10 9 8 7 6 5 */ + lgw_reg_w(SX1302_REG_RX_TOP_CORRELATOR_EN_CORR_EN, 0xFF); /* 1 correlator per channel */ + + /* For debug: get packets with sync_error and header_error in FIFO */ +#if 0 + lgw_reg_w(SX1302_REG_RX_TOP_RX_BUFFER_STORE_SYNC_FAIL_META, 0x01); + lgw_reg_w(SX1302_REG_RX_TOP_RX_BUFFER_STORE_HEADER_ERR_META, 0x01); +#endif + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_lora_service_correlator_configure(struct lgw_conf_rxif_s * cfg) { + + /* Common config for all SF */ + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_MSP2_MSP_PEAK_NB, 7); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_MSP2_MSP2_PEAK_NB, 5); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_USE_GAIN_SYMB, 1); + + switch (cfg->datarate) { + case 5: + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 1); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); + break; + case 6: + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 1); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); + break; + case 7: + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 0); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); + break; + case 8: + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 0); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); + break; + case 9: + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 0); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); + break; + case 10: + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 0); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); + break; + case 11: + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 0); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); + break; + case 12: + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 0); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); + break; + default: + printf("ERROR: Failed to configure LoRa service modem correlators\n"); + return LGW_REG_ERROR; + } + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_lora_modem_configure(uint32_t radio_freq_hz) { + uint16_t mantissa = 0; + uint8_t exponent = 0; + + /* TODO: test if channel is enabled */ + + lgw_reg_w(SX1302_REG_RX_TOP_DC_NOTCH_CFG1_ENABLE, 0x00); + lgw_reg_w(SX1302_REG_RX_TOP_RX_DFE_AGC1_FORCE_DEFAULT_FIR, 0x01); + lgw_reg_w(SX1302_REG_RX_TOP_DAGC_CFG_GAIN_DROP_COMP, 0x01); + lgw_reg_w(SX1302_REG_RX_TOP_DAGC_CFG_TARGET_LVL, 0x01); + + /* Enable full modems */ + DEBUG_MSG("Configuring 8 full-SF modems\n"); + lgw_reg_w(SX1302_REG_OTP_MODEM_EN_0_MODEM_EN, 0xFF); + + /* Enable limited modems */ +#if FPGA_BOARD_16_CH + DEBUG_MSG("Configuring 8 limited-SF modems\n"); + lgw_reg_w(SX1302_REG_OTP_MODEM_EN_1_MODEM_EN, 0xFF); +#else + DEBUG_MSG("Configuring 4 limited-SF modems\n"); + lgw_reg_w(SX1302_REG_OTP_MODEM_EN_1_MODEM_EN, 0x0F); +#endif + + /* Configure coarse sync between correlators and modems */ + lgw_reg_w(SX1302_REG_RX_TOP_MODEM_SYNC_DELTA_MSB_MODEM_SYNC_DELTA, 0); + lgw_reg_w(SX1302_REG_RX_TOP_MODEM_SYNC_DELTA_LSB_MODEM_SYNC_DELTA, 126); + + /* Configure fine sync offset for each channel */ + lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_01_CHANNEL_0_OFFSET, 1); + lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_01_CHANNEL_1_OFFSET, 5); + lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_23_CHANNEL_2_OFFSET, 9); + lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_23_CHANNEL_3_OFFSET, 13); + lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_45_CHANNEL_4_OFFSET, 1); + lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_45_CHANNEL_5_OFFSET, 5); + lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_67_CHANNEL_6_OFFSET, 9); + lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_67_CHANNEL_7_OFFSET, 13); + + /* Configure PPM offset */ + lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF5, 0x00); + lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF6, 0x00); + lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF7, 0x00); + lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF8, 0x00); + lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF9, 0x00); + lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF10, 0x00); + lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF11, 0x01); + lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF12, 0x01); + + /* Improve SF5 and SF6 performances */ + lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_A_1_GAIN_P_AUTO, 3); // Default is 1 + lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_A_1_GAIN_P_PAYLOAD, 3); // Default is 2 + + /* Improve SF11/SF12 performances */ + lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_A_5_GAIN_I_EN_SF11, 1); + lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_A_5_GAIN_I_EN_SF12, 1); + lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_B_5_GAIN_I_EN_SF11, 1); + lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_B_5_GAIN_I_EN_SF12, 1); + + /* Freq2TimeDrift computation */ + if (calculate_freq_to_time_drift(radio_freq_hz, BW_125KHZ, &mantissa, &exponent) != 0) { + printf("ERROR: failed to calculate frequency to time drift for LoRa modem\n"); + return LGW_REG_ERROR; + } + DEBUG_PRINTF("Freq2TimeDrift MultiSF: Mantissa = %d (0x%02X, 0x%02X), Exponent = %d (0x%02X)\n", mantissa, (mantissa >> 8) & 0x00FF, (mantissa) & 0x00FF, exponent, exponent); + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TO_TIME0_FREQ_TO_TIME_DRIFT_MANT, (mantissa >> 8) & 0x00FF); + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TO_TIME1_FREQ_TO_TIME_DRIFT_MANT, (mantissa) & 0x00FF); + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TO_TIME2_FREQ_TO_TIME_DRIFT_EXP, exponent); + + /* Time drift compensation */ + lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_TIME_SYMB, 1); + + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_lora_service_modem_configure(struct lgw_conf_rxif_s * cfg, uint32_t radio_freq_hz) { + uint16_t mantissa = 0; + uint8_t exponent = 0; + + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG1_ENABLE, 0x00); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC1_FORCE_DEFAULT_FIR, 0x01); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DAGC_CFG_GAIN_DROP_COMP, 0x01); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DAGC_CFG_TARGET_LVL, 0x01); + + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING1_GAIN_P_AUTO, 0x03); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_PAYLOAD, 0x03); + + switch (cfg->datarate) { + case DR_LORA_SF5: + case DR_LORA_SF6: + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING1_GAIN_P_PREAMB, 0x04); // Default value + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_EN, 0x00); // Default value + break; + case DR_LORA_SF7: + case DR_LORA_SF8: + case DR_LORA_SF9: + case DR_LORA_SF10: + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING1_GAIN_P_PREAMB, 0x06); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_EN, 0x00); + break; + case DR_LORA_SF11: + case DR_LORA_SF12: + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING1_GAIN_P_PREAMB, 0x07); + switch (cfg->bandwidth) { + case BW_125KHZ: + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_EN, 0x01); + break; + case BW_250KHZ: + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_EN, 0x02); + break; + case BW_500KHZ: + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_EN, 0x03); + break; + default: + printf("ERROR: unsupported bandwidth %u for LoRa Service modem\n", cfg->bandwidth); + break; + } + break; + default: + printf("ERROR: unsupported datarate %u for LoRa Service modem\n", cfg->datarate); + break; + } + + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_IMPLICIT_HEADER, (cfg->implicit_hdr == true) ? 1 : 0); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_CRC_EN, (cfg->implicit_crc_en == true) ? 1 : 0); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG1_CODING_RATE, cfg->implicit_coderate); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG3_PAYLOAD_LENGTH, cfg->implicit_payload_length); + + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG0_MODEM_SF, cfg->datarate); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG0_MODEM_BW, cfg->bandwidth); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG1_PPM_OFFSET, SET_PPM_ON(cfg->bandwidth, cfg->datarate)); + + //SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG6_PREAMBLE_SYMB_NB + //SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG7_PREAMBLE_SYMB_NB + + /* Freq2TimeDrift computation */ + if (calculate_freq_to_time_drift(radio_freq_hz, cfg->bandwidth, &mantissa, &exponent) != 0) { + printf("ERROR: failed to calculate frequency to time drift for LoRa service modem\n"); + return LGW_REG_ERROR; + } + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME0_FREQ_TO_TIME_DRIFT_MANT, (mantissa >> 8) & 0x00FF); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME1_FREQ_TO_TIME_DRIFT_MANT, (mantissa) & 0x00FF); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME2_FREQ_TO_TIME_DRIFT_EXP, exponent); + DEBUG_PRINTF("Freq2TimeDrift SingleSF: Mantissa = %d (0x%02X, 0x%02X), Exponent = %d (0x%02X)\n", mantissa, (mantissa >> 8) & 0x00FF, (mantissa) & 0x00FF, exponent, exponent); + + /* Time drift compensation */ + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_TIME_SYMB, 1); + + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC2_DAGC_IN_COMP, 1); + + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG1_MODEM_EN, 1); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_CADRXTX, 1); + + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_MODEM_START, 1); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_modem_enable(void) { + /* Enable LoRa multi-SF modems */ + lgw_reg_w(SX1302_REG_COMMON_GEN_CONCENTRATOR_MODEM_ENABLE, 0x01); + + /* Enable LoRa service modem */ + lgw_reg_w(SX1302_REG_COMMON_GEN_MBWSSF_MODEM_ENABLE, 0x01); + + /* Enable FSK modem */ + lgw_reg_w(SX1302_REG_COMMON_GEN_FSK_MODEM_ENABLE, 0x01); + + /* Enable RX */ + lgw_reg_w(SX1302_REG_COMMON_GEN_GLOBAL_EN, 0x01); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_lora_syncword(bool public, uint8_t lora_service_sf) { + /* Multi-SF modem configuration */ + DEBUG_MSG("INFO: configuring LoRa (Multi-SF) SF5->SF6 with syncword PRIVATE (0x12)\n"); + lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH0_SF5_PEAK1_POS_SF5, 2); + lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH1_SF5_PEAK2_POS_SF5, 4); + lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH0_SF6_PEAK1_POS_SF6, 2); + lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH1_SF6_PEAK2_POS_SF6, 4); + if (public == true) { + DEBUG_MSG("INFO: configuring LoRa (Multi-SF) SF7->SF12 with syncword PUBLIC (0x34)\n"); + lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH0_SF7TO12_PEAK1_POS_SF7TO12, 6); + lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH1_SF7TO12_PEAK2_POS_SF7TO12, 8); + } else { + DEBUG_MSG("INFO: configuring LoRa (Multi-SF) SF7->SF12 with syncword PRIVATE (0x12)\n"); + lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH0_SF7TO12_PEAK1_POS_SF7TO12, 2); + lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH1_SF7TO12_PEAK2_POS_SF7TO12, 4); + } + + /* LoRa Service modem configuration */ + if ((public == false) || (lora_service_sf == DR_LORA_SF5) || (lora_service_sf == DR_LORA_SF6)) { + DEBUG_PRINTF("INFO: configuring LoRa (Service) SF%u with syncword PRIVATE (0x12)\n", lora_service_sf); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH0_PEAK1_POS, 2); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH1_PEAK2_POS, 4); + } else { + DEBUG_PRINTF("INFO: configuring LoRa (Service) SF%u with syncword PUBLIC (0x34)\n", lora_service_sf); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH0_PEAK1_POS, 6); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH1_PEAK2_POS, 8); + } + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint32_t sx1302_timestamp_counter(bool pps) { + return timestamp_counter_get(&counter_us, pps); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_gps_enable(bool enable) { + if (enable == true) { + lgw_reg_w(SX1302_REG_TIMESTAMP_GPS_CTRL_GPS_EN, 1); + lgw_reg_w(SX1302_REG_TIMESTAMP_GPS_CTRL_GPS_POL, 1); /* invert polarity for PPS */ + } else { + lgw_reg_w(SX1302_REG_TIMESTAMP_GPS_CTRL_GPS_EN, 0); + } + + return LGW_HAL_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_agc_load_firmware(const uint8_t *firmware) { + int32_t val; + uint8_t fw_check[MCU_FW_SIZE]; + int32_t gpio_sel = MCU_AGC; + + /* Configure GPIO to let AGC MCU access board LEDs */ + lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_0_SELECTION, gpio_sel); + lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_1_SELECTION, gpio_sel); + lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_2_SELECTION, gpio_sel); + lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_3_SELECTION, gpio_sel); + lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_4_SELECTION, gpio_sel); + lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_5_SELECTION, gpio_sel); + lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_6_SELECTION, gpio_sel); + lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_7_SELECTION, gpio_sel); + lgw_reg_w(SX1302_REG_GPIO_GPIO_DIR_L_DIRECTION, 0xFF); /* GPIO output direction */ + + /* Take control over AGC MCU */ + lgw_reg_w(SX1302_REG_AGC_MCU_CTRL_MCU_CLEAR, 0x01); + lgw_reg_w(SX1302_REG_AGC_MCU_CTRL_HOST_PROG, 0x01); + lgw_reg_w(SX1302_REG_COMMON_PAGE_PAGE, 0x00); + + /* Write AGC fw in AGC MEM */ + lgw_mem_wb(AGC_MEM_ADDR, firmware, MCU_FW_SIZE); + + /* Read back and check */ + lgw_mem_rb(AGC_MEM_ADDR, fw_check, MCU_FW_SIZE, false); + if (memcmp(firmware, fw_check, sizeof fw_check) != 0) { + printf ("ERROR: Failed to load fw\n"); + return -1; + } + +#if BYPASS_FW_INIT + printf("Disable AGC init protocol\n"); + sx1302_agc_mailbox_write(2, 0xF7); /* To be done before fw starts */ +#endif + + /* Release control over AGC MCU */ + lgw_reg_w(SX1302_REG_AGC_MCU_CTRL_HOST_PROG, 0x00); + lgw_reg_w(SX1302_REG_AGC_MCU_CTRL_MCU_CLEAR, 0x00); + + lgw_reg_r(SX1302_REG_AGC_MCU_CTRL_PARITY_ERROR, &val); + DEBUG_PRINTF("AGC fw loaded (parity error:0x%02X)\n", val); + + return LGW_HAL_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_agc_status(uint8_t* status) { + int32_t val; + + if (lgw_reg_r(SX1302_REG_AGC_MCU_MCU_AGC_STATUS_MCU_AGC_STATUS, &val) != LGW_REG_SUCCESS) { + printf("ERROR: Failed to get AGC status\n"); + return LGW_HAL_ERROR; + } + + *status = (uint8_t)val; + + return LGW_HAL_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_agc_wait_status(uint8_t status) { + uint8_t val; + + do { + if (sx1302_agc_status(&val) != LGW_HAL_SUCCESS) { + return LGW_HAL_ERROR; + } + } while (val != status); + + return LGW_HAL_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_agc_mailbox_read(uint8_t mailbox, uint8_t* value) { + uint16_t reg; + int32_t val; + + /* Check parameters */ + if (mailbox > 3) { + printf("ERROR: invalid AGC mailbox ID\n"); + return LGW_HAL_ERROR; + } + + reg = SX1302_REG_AGC_MCU_MCU_MAIL_BOX_RD_DATA_BYTE0_MCU_MAIL_BOX_RD_DATA - mailbox; + if (lgw_reg_r(reg, &val) != LGW_REG_SUCCESS) { + printf("ERROR: failed to read AGC mailbox\n"); + return LGW_HAL_ERROR; + } + + *value = (uint8_t)val; + + return LGW_HAL_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_agc_mailbox_write(uint8_t mailbox, uint8_t value) { + uint16_t reg; + + /* Check parameters */ + if (mailbox > 3) { + printf("ERROR: invalid AGC mailbox ID\n"); + return LGW_HAL_ERROR; + } + + reg = SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE0_MCU_MAIL_BOX_WR_DATA - mailbox; + if (lgw_reg_w(reg, (int32_t)value) != LGW_REG_SUCCESS) { + printf("ERROR: failed to write AGC mailbox\n"); + return LGW_HAL_ERROR; + } + + return LGW_HAL_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_agc_start(uint8_t version, lgw_radio_type_t radio_type, uint8_t ana_gain, uint8_t dec_gain, uint8_t fdd_mode) { + uint8_t val; + struct agc_gain_params_s agc_params; + + /* Check parameters */ + if ((radio_type != LGW_RADIO_TYPE_SX1255) && (radio_type != LGW_RADIO_TYPE_SX1257) && (radio_type != LGW_RADIO_TYPE_SX1250)) { + DEBUG_MSG("ERROR: invalid radio type\n"); + return LGW_REG_ERROR; + } + + /* Wait for AGC fw to be started, and VERSION available in mailbox */ + sx1302_agc_wait_status(0x01); /* fw has started, VERSION is ready in mailbox */ + + sx1302_agc_mailbox_read(0, &val); + if (val != version) { + printf("ERROR: wrong AGC fw version (%d)\n", val); + return LGW_HAL_ERROR; + } + DEBUG_PRINTF("AGC FW VERSION: %d\n", val); + +#if BYPASS_FW_INIT + printf("Bypass AGC init protocol\n"); + return 0; +#endif + + /* Configure Radio A gains */ + sx1302_agc_mailbox_write(0, ana_gain); /* 0:auto agc*/ + sx1302_agc_mailbox_write(1, dec_gain); + if (radio_type != LGW_RADIO_TYPE_SX1250) { + printf("AGC: setting fdd_mode to %u\n", fdd_mode); + sx1302_agc_mailbox_write(2, fdd_mode); + } + + /* notify AGC that gains has been set to mailbox for Radio A */ + sx1302_agc_mailbox_write(3, AGC_RADIO_A_INIT_DONE); + + /* Wait for AGC to acknoledge it has received gain settings for Radio A */ + sx1302_agc_wait_status(0x02); + + /* Check ana_gain setting */ + sx1302_agc_mailbox_read(0, &val); + if (val != ana_gain) { + printf("ERROR: Analog gain of Radio A has not been set properly\n"); + return LGW_HAL_ERROR; + } + + /* Check dec_gain setting */ + sx1302_agc_mailbox_read(1, &val); + if (val != dec_gain) { + printf("ERROR: Decimator gain of Radio A has not been set properly\n"); + return LGW_HAL_ERROR; + } + + /* Check FDD mode setting */ + sx1302_agc_mailbox_read(2, &val); + if (val != fdd_mode) { + printf("ERROR: FDD mode of Radio A has not been set properly\n"); + return LGW_HAL_ERROR; + } + + DEBUG_MSG("AGC: Radio A config done\n"); + + /* Configure Radio B gains */ + sx1302_agc_mailbox_write(0, ana_gain); /* 0:auto agc*/ + sx1302_agc_mailbox_write(1, dec_gain); + if (radio_type != LGW_RADIO_TYPE_SX1250) { + sx1302_agc_mailbox_write(2, fdd_mode); + } + + /* notify AGC that gains has been set to mailbox for Radio B */ + sx1302_agc_mailbox_write(3, AGC_RADIO_B_INIT_DONE); + + /* Wait for AGC to acknoledge it has received gain settings for Radio B */ + sx1302_agc_wait_status(0x03); + + /* Check ana_gain setting */ + sx1302_agc_mailbox_read(0, &val); + if (val != ana_gain) { + printf("ERROR: Analog gain of Radio B has not been set properly\n"); + return LGW_HAL_ERROR; + } + + /* Check dec_gain setting */ + sx1302_agc_mailbox_read(1, &val); + if (val != dec_gain) { + printf("ERROR: Decimator gain of Radio B has not been set properly\n"); + return LGW_HAL_ERROR; + } + + /* Check FDD mode setting */ + sx1302_agc_mailbox_read(2, &val); + if (val != fdd_mode) { + printf("ERROR: FDD mode of Radio B has not been set properly\n"); + return LGW_HAL_ERROR; + } + + DEBUG_MSG("AGC: Radio B config done\n"); + + /* Configure AGC gains */ + agc_params = (radio_type == LGW_RADIO_TYPE_SX1250) ? agc_params_sx1250 : agc_params_sx125x; + + /* Configure analog gain min/max */ + sx1302_agc_mailbox_write(0, agc_params.ana_min); + sx1302_agc_mailbox_write(1, agc_params.ana_max); + + /* notify AGC that params have been set to mailbox */ + sx1302_agc_mailbox_write(3, 0x03); + + /* Wait for AGC to acknoledge it has received params */ + sx1302_agc_wait_status(0x04); + + /* Check params */ + sx1302_agc_mailbox_read(0, &val); + if (val != agc_params.ana_min) { + printf("ERROR: wrong ana_min (w:%u r:%u)\n", agc_params.ana_min, val); + return LGW_HAL_ERROR; + } + sx1302_agc_mailbox_read(1, &val); + if (val != agc_params.ana_max) { + printf("ERROR: ana_max (w:%u r:%u)\n", agc_params.ana_max, val); + return LGW_HAL_ERROR; + } + + DEBUG_MSG("AGC: config of analog gain min/max done\n"); + + /* Configure analog thresholds */ + sx1302_agc_mailbox_write(0, agc_params.ana_thresh_l); + sx1302_agc_mailbox_write(1, agc_params.ana_thresh_h); + + /* notify AGC that params have been set to mailbox */ + sx1302_agc_mailbox_write(3, 0x04); + + /* Wait for AGC to acknoledge it has received params */ + sx1302_agc_wait_status(0x05); + + /* Check params */ + sx1302_agc_mailbox_read(0, &val); + if (val != agc_params.ana_thresh_l) { + printf("ERROR: wrong ana_thresh_l (w:%u r:%u)\n", agc_params.ana_thresh_l, val); + return LGW_HAL_ERROR; + } + sx1302_agc_mailbox_read(1, &val); + if (val != agc_params.ana_thresh_h) { + printf("ERROR: wrong ana_thresh_h (w:%u r:%u)\n", agc_params.ana_thresh_h, val); + return LGW_HAL_ERROR; + } + + DEBUG_MSG("AGC: config of analog threshold done\n"); + + /* Configure decimator attenuation min/max */ + sx1302_agc_mailbox_write(0, agc_params.dec_attn_min); + sx1302_agc_mailbox_write(1, agc_params.dec_attn_max); + + /* notify AGC that params have been set to mailbox */ + sx1302_agc_mailbox_write(3, 0x05); + + /* Wait for AGC to acknoledge it has received params */ + sx1302_agc_wait_status(0x06); + + /* Check params */ + sx1302_agc_mailbox_read(0, &val); + if (val != agc_params.dec_attn_min) { + printf("ERROR: wrong dec_attn_min (w:%u r:%u)\n", agc_params.dec_attn_min, val); + return LGW_HAL_ERROR; + } + sx1302_agc_mailbox_read(1, &val); + if (val != agc_params.dec_attn_max) { + printf("ERROR: wrong dec_attn_max (w:%u r:%u)\n", agc_params.dec_attn_max, val); + return LGW_HAL_ERROR; + } + + DEBUG_MSG("AGC: config of decimator atten min/max done\n"); + + /* Configure decimator attenuation thresholds */ + sx1302_agc_mailbox_write(0, agc_params.dec_thresh_l); + sx1302_agc_mailbox_write(1, agc_params.dec_thresh_h1); + sx1302_agc_mailbox_write(2, agc_params.dec_thresh_h2); + + /* notify AGC that params have been set to mailbox */ + sx1302_agc_mailbox_write(3, 0x06); + + /* Wait for AGC to acknoledge it has received params */ + sx1302_agc_wait_status(0x07); + + /* Check params */ + sx1302_agc_mailbox_read(0, &val); + if (val != agc_params.dec_thresh_l) { + printf("ERROR: wrong dec_thresh_l (w:%u r:%u)\n", agc_params.dec_thresh_l, val); + return LGW_HAL_ERROR; + } + sx1302_agc_mailbox_read(1, &val); + if (val != agc_params.dec_thresh_h1) { + printf("ERROR: wrong dec_thresh_h1 (w:%u r:%u)\n", agc_params.dec_thresh_h1, val); + return LGW_HAL_ERROR; + } + sx1302_agc_mailbox_read(2, &val); + if (val != agc_params.dec_thresh_h2) { + printf("ERROR: wrong dec_thresh_h2 (w:%u r:%u)\n", agc_params.dec_thresh_h2, val); + return LGW_HAL_ERROR; + } + + DEBUG_MSG("AGC: config of decimator threshold done\n"); + + /* Configure channel attenuation min/max */ + sx1302_agc_mailbox_write(0, agc_params.chan_attn_min); + sx1302_agc_mailbox_write(1, agc_params.chan_attn_max); + + /* notify AGC that params have been set to mailbox */ + sx1302_agc_mailbox_write(3, 0x07); + + /* Wait for AGC to acknoledge it has received params */ + sx1302_agc_wait_status(0x08); + + /* Check params */ + sx1302_agc_mailbox_read(0, &val); + if (val != agc_params.chan_attn_min) { + printf("ERROR: wrong chan_attn_min (w:%u r:%u)\n", agc_params.chan_attn_min, val); + return LGW_HAL_ERROR; + } + sx1302_agc_mailbox_read(1, &val); + if (val != agc_params.chan_attn_max) { + printf("ERROR: wrong chan_attn_max (w:%u r:%u)\n", agc_params.chan_attn_max, val); + return LGW_HAL_ERROR; + } + + DEBUG_MSG("AGC: config of channel atten min/max done\n"); + + /* Configure channel attenuation threshold */ + sx1302_agc_mailbox_write(0, agc_params.chan_thresh_l); + sx1302_agc_mailbox_write(1, agc_params.chan_thresh_h); + + /* notify AGC that params have been set to mailbox */ + sx1302_agc_mailbox_write(3, 0x08); + + /* Wait for AGC to acknoledge it has received params */ + sx1302_agc_wait_status(0x09); + + /* Check params */ + sx1302_agc_mailbox_read(0, &val); + if (val != agc_params.chan_thresh_l) { + printf("ERROR: wrong chan_thresh_l (w:%u r:%u)\n", agc_params.chan_thresh_l, val); + return LGW_HAL_ERROR; + } + sx1302_agc_mailbox_read(1, &val); + if (val != agc_params.chan_thresh_h) { + printf("ERROR: wrong chan_thresh_h (w:%u r:%u)\n", agc_params.chan_thresh_h, val); + return LGW_HAL_ERROR; + } + + DEBUG_MSG("AGC: config of channel atten threshold done\n"); + + if (radio_type == LGW_RADIO_TYPE_SX1250) { + /* Configure sx1250 SetPAConfig */ + sx1302_agc_mailbox_write(0, agc_params.deviceSel); + sx1302_agc_mailbox_write(1, agc_params.hpMax); + sx1302_agc_mailbox_write(2, agc_params.paDutyCycle); + + /* notify AGC that params have been set to mailbox */ + sx1302_agc_mailbox_write(3, 0x09); + + /* Wait for AGC to acknoledge it has received params */ + sx1302_agc_wait_status(0x0A); + + /* Check params */ + sx1302_agc_mailbox_read(0, &val); + if (val != agc_params.deviceSel) { + printf("ERROR: wrong deviceSel (w:%u r:%u)\n", agc_params.deviceSel, val); + return LGW_HAL_ERROR; + } + sx1302_agc_mailbox_read(1, &val); + if (val != agc_params.hpMax) { + printf("ERROR: wrong hpMax (w:%u r:%u)\n", agc_params.hpMax, val); + return LGW_HAL_ERROR; + } + sx1302_agc_mailbox_read(2, &val); + if (val != agc_params.paDutyCycle) { + printf("ERROR: wrong paDutyCycle (w:%u r:%u)\n", agc_params.paDutyCycle, val); + return LGW_HAL_ERROR; + } + + DEBUG_MSG("AGC: config of sx1250 PA optimal settings done\n"); + + /* notify AGC that it can resume */ + sx1302_agc_mailbox_write(3, 0x0A); + } else { + /* notify AGC that it can resume */ + sx1302_agc_mailbox_write(3, 0x09); + } + + DEBUG_MSG("AGC: started\n"); + + return LGW_HAL_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_arb_load_firmware(const uint8_t *firmware) { + uint8_t fw_check[MCU_FW_SIZE]; + int32_t gpio_sel = MCU_ARB; + int32_t val; + + lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_0_SELECTION, gpio_sel); + lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_1_SELECTION, gpio_sel); + lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_2_SELECTION, gpio_sel); + lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_3_SELECTION, gpio_sel); + lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_4_SELECTION, gpio_sel); + lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_5_SELECTION, gpio_sel); + lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_6_SELECTION, gpio_sel); + lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_7_SELECTION, gpio_sel); + lgw_reg_w(SX1302_REG_GPIO_GPIO_DIR_L_DIRECTION, 0xFF); /* GPIO output direction */ + + /* Take control over ARB MCU */ + lgw_reg_w(SX1302_REG_ARB_MCU_CTRL_MCU_CLEAR, 0x01); + lgw_reg_w(SX1302_REG_ARB_MCU_CTRL_HOST_PROG, 0x01); + lgw_reg_w(SX1302_REG_COMMON_PAGE_PAGE, 0x00); + + /* Write ARB fw in ARB MEM */ + lgw_mem_wb(ARB_MEM_ADDR, &firmware[0], MCU_FW_SIZE); + + /* Read back and check */ + lgw_mem_rb(ARB_MEM_ADDR, fw_check, MCU_FW_SIZE, false); + if (memcmp(firmware, fw_check, sizeof fw_check) != 0) { + printf ("ERROR: Failed to load fw\n"); + return -1; + } + +#if BYPASS_FW_INIT + printf("Disable ARB init protocol\n"); + sx1302_arb_debug_write(2, 0xF7); /* To be done before fw starts */ +#endif + + /* Release control over ARB MCU */ + lgw_reg_w(SX1302_REG_ARB_MCU_CTRL_HOST_PROG, 0x00); + lgw_reg_w(SX1302_REG_ARB_MCU_CTRL_MCU_CLEAR, 0x00); + + lgw_reg_r(SX1302_REG_ARB_MCU_CTRL_PARITY_ERROR, &val); + DEBUG_PRINTF("ARB fw loaded (parity error:0x%02X)\n", val); + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_arb_status(uint8_t* status) { + int32_t val; + + if (lgw_reg_r(SX1302_REG_ARB_MCU_MCU_ARB_STATUS_MCU_ARB_STATUS, &val) != LGW_REG_SUCCESS) { + printf("ERROR: Failed to get AGC status\n"); + return LGW_HAL_ERROR; + } + + *status = (uint8_t)val; + + return LGW_HAL_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_arb_wait_status(uint8_t status) { + uint8_t val; + + do { + if (sx1302_arb_status(&val) != LGW_HAL_SUCCESS) { + return LGW_HAL_ERROR; + } + } while (val != status); + + return LGW_HAL_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_arb_debug_read(uint8_t reg_id, uint8_t* value) { + uint16_t reg; + int32_t val; + + /* Check parameters */ + if (reg_id > 15) { + printf("ERROR: invalid ARB debug register ID\n"); + return LGW_HAL_ERROR; + } + + reg = SX1302_REG_ARB_MCU_ARB_DEBUG_STS_0_ARB_DEBUG_STS_0 + reg_id; + if (lgw_reg_r(reg, &val) != LGW_REG_SUCCESS) { + printf("ERROR: failed to read ARB debug register\n"); + return LGW_HAL_ERROR; + } + + *value = (uint8_t)val; + + return LGW_HAL_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_arb_debug_write(uint8_t reg_id, uint8_t value) { + uint16_t reg; + + /* Check parameters */ + if (reg_id > 3) { + printf("ERROR: invalid ARB debug register ID\n"); + return LGW_HAL_ERROR; + } + + reg = SX1302_REG_ARB_MCU_ARB_DEBUG_CFG_0_ARB_DEBUG_CFG_0 + reg_id; + if (lgw_reg_w(reg, (int32_t)value) != LGW_REG_SUCCESS) { + printf("ERROR: failed to write ARB debug register ID\n"); + return LGW_HAL_ERROR; + } + + return LGW_HAL_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void sx1302_arb_set_debug_stats(bool enable, uint8_t sf) { + if (enable == true) { + DEBUG_PRINTF("ARB: Debug stats enabled for SF%u\n", sf); + lgw_reg_w(SX1302_REG_ARB_MCU_ARB_DEBUG_CFG_0_ARB_DEBUG_CFG_0, sf); + } else { + DEBUG_MSG("ARB: Debug stats disabled\n"); + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint8_t sx1302_arb_get_debug_stats_detect(uint8_t channel) { + int32_t dbg_val; + + if (channel >= 8) { + printf("ERROR: wrong configuration, channel num must be < 8"); + return 0; + } + lgw_reg_r(SX1302_REG_ARB_MCU_ARB_DEBUG_STS_0_ARB_DEBUG_STS_0 + channel, &dbg_val); + + return (uint8_t)dbg_val; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint8_t sx1302_arb_get_debug_stats_alloc(uint8_t channel) { + int32_t dbg_val; + + if (channel >= 8) { + printf("ERROR: wrong configuration, channel num must be < 8"); + return 0; + } + lgw_reg_r(SX1302_REG_ARB_MCU_ARB_DEBUG_STS_8_ARB_DEBUG_STS_8 + channel, &dbg_val); + + return (uint8_t)dbg_val; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void sx1302_arb_print_debug_stats(void) { + int i; + uint8_t nb_detect; + uint8_t nb_alloc; + int nb_detect_total = 0; + int nb_alloc_total = 0; + + /* Get number of detects for all channels */ + nb_detect_total = 0; + DEBUG_MSG("ARB: nb_detect: ["); + for (i = 0; i < 8; i++) { + nb_detect = sx1302_arb_get_debug_stats_detect(i); + DEBUG_PRINTF("%u ", nb_detect); + nb_detect_total += nb_detect; + } + DEBUG_MSG("]\n"); + + /* Get number of modem allocation for all channels */ + nb_alloc_total = 0; + DEBUG_MSG("ARB: nb_alloc: ["); + for (i = 0; i < 8; i++) { + nb_alloc = sx1302_arb_get_debug_stats_alloc(i); + DEBUG_PRINTF("%u ", nb_alloc); + nb_alloc_total += nb_alloc; + } + DEBUG_MSG("]\n"); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_arb_start(uint8_t version) { + uint8_t val; + + /* Wait for ARB fw to be started, and VERSION available in debug registers */ + sx1302_arb_wait_status(0x01); + + /* Get firmware VERSION */ + sx1302_arb_debug_read(0, &val); + if (val != version) { + printf("ERROR: wrong ARB fw version (%d)\n", val); + return LGW_HAL_ERROR; + } + DEBUG_PRINTF("ARB FW VERSION: %d\n", val); + +#if BYPASS_FW_INIT + printf("Bypass ARB init protocol\n"); + return 0; +#endif + + /* Enable/disable ARB detect/modem alloc stats for the specified SF */ + sx1302_arb_set_debug_stats(true, DR_LORA_SF7); + + /* 0:Disable 1:Enable double demod for different timing set (best_timestamp / best_demodulation) - Only available for SF9 -> SF12 */ + sx1302_arb_debug_write(3, 0); + + /* Set double detect packet filtering threshold [0..3] */ + sx1302_arb_debug_write(2, 3); + + /* Notify ARB that it can resume */ + sx1302_arb_debug_write(1, 1); + + /* Wait for ARB to acknoledge */ + sx1302_arb_wait_status(0x00); + + DEBUG_MSG("ARB: started\n"); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_fetch(uint16_t * nb_bytes) { + int err; + + /* Initialize RX buffer */ + err = rx_buffer_new(&rx_buffer); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: Failed to initialize RX buffer\n"); + return LGW_REG_ERROR; + } + + /* Fetch RX buffer if any data available */ + err = rx_buffer_fetch(&rx_buffer); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: Failed to fetch RX buffer\n"); + return LGW_REG_ERROR; + } + + /* Return the number of bytes fetched */ + *nb_bytes = rx_buffer.buffer_size; + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_parse(lgw_context_t * context, struct lgw_pkt_rx_s * p) { + int i, err; + int ifmod; /* type of if_chain/modem a packet was received by */ + uint16_t payload_crc16_calc; + uint8_t cr; + uint32_t timestamp_correction; /* correction to account for processing delay */ + rx_packet_t pkt; + + /* Check input params */ + CHECK_NULL(context); + CHECK_NULL(p); + + /* FOR DEBUG: Print statistics of number of detects and modem allocations from ARB for configured SF (see sx1302_arb_start()) */ + sx1302_arb_print_debug_stats(); + + /* get packet from RX buffer */ + err = rx_buffer_pop(&rx_buffer, &pkt); + if (err != LGW_REG_SUCCESS) { + return LGW_REG_ERROR; + } + + /* copy payload to result struct */ + memcpy((void *)p->payload, (void *)(&(pkt.payload)), pkt.rxbytenb_modem); + p->size = pkt.rxbytenb_modem; + + /* process metadata */ + p->modem_id = pkt.modem_id; + p->if_chain = pkt.rx_channel_in; + if (p->if_chain >= LGW_IF_CHAIN_NB) { + DEBUG_PRINTF("WARNING: %u NOT A VALID IF_CHAIN NUMBER, ABORTING\n", p->if_chain); + return LGW_REG_ERROR; + } + ifmod = ifmod_config[p->if_chain]; + DEBUG_PRINTF("[%d 0x%02X]\n", p->if_chain, ifmod); + + p->rf_chain = (uint8_t)context->if_chain_cfg[p->if_chain].rf_chain; + + /* Get the frequency for the channel configuration */ + p->freq_hz = (uint32_t)((int32_t)context->rf_chain_cfg[p->rf_chain].freq_hz + context->if_chain_cfg[p->if_chain].freq_hz); + + /* Get signal strength : offset and temperature compensation will be applied later */ + p->rssic = (float)(pkt.rssi_chan_avg); + p->rssis = (float)(pkt.rssi_signal_avg); + + /* Get modulation metadata */ + if ((ifmod == IF_LORA_MULTI) || (ifmod == IF_LORA_STD)) { + DEBUG_PRINTF("Note: LoRa packet (modem %u chan %u)\n", p->modem_id, p->if_chain); + p->modulation = MOD_LORA; + + /* Get CRC status */ + if (pkt.crc_en || (context->lora_service_cfg.implicit_crc_en == true)) { + /* CRC enabled */ + if (pkt.payload_crc_error) { + p->status = STAT_CRC_BAD; + } else { + p->status = STAT_CRC_OK; + + /* Sanity check of the payload CRC */ + if (p->size > 0) { + payload_crc16_calc = sx1302_lora_payload_crc(p->payload, p->size); + if (payload_crc16_calc != pkt.rx_crc16_value) { + printf("ERROR: Payload CRC16 check failed (got:0x%04X calc:0x%04X)\n", pkt.rx_crc16_value, payload_crc16_calc); + if (log_file != NULL) { + fprintf(log_file, "ERROR: Payload CRC16 check failed (got:0x%04X calc:0x%04X)\n", pkt.rx_crc16_value, payload_crc16_calc); + dbg_log_buffer_to_file(log_file, rx_buffer.buffer, rx_buffer.buffer_size); + } + return LGW_REG_ERROR; + } else { + DEBUG_PRINTF("Payload CRC check OK (0x%04X)\n", pkt.rx_crc16_value); + } + } + } + } else { + /* CRC disabled */ + p->status = STAT_NO_CRC; + } + +#if 1 + /* FOR DEBUG: Check data integrity for known devices (debug context) */ + if (p->status == STAT_CRC_OK || p->status == STAT_NO_CRC) { + /* We compare the received payload with predefined ones to ensure that the payload content is what we expect. + 4 bytes: ID to identify the payload + 4 bytes: packet counter used to initialize the seed for pseudo-random generation + x bytes: pseudo-random payload + */ + int res; + for (i = 0; i < context->debug_cfg.nb_ref_payload; i++) { + res = dbg_check_payload(&(context->debug_cfg), log_file, p->payload, p->size, i, pkt.rx_rate_sf); + if (res == -1) { + printf("ERROR: 0x%08X payload error\n", context->debug_cfg.ref_payload[i].id); + if (log_file != NULL) { + fprintf(log_file, "ERROR: 0x%08X payload error\n", context->debug_cfg.ref_payload[i].id); + dbg_log_buffer_to_file(log_file, rx_buffer.buffer, rx_buffer.buffer_size); + dbg_log_payload_diff_to_file(log_file, p->payload, context->debug_cfg.ref_payload[i].payload, p->size); + } + return LGW_REG_ERROR; + } else if (res == 1) { + DEBUG_PRINTF("0x%08X payload matches\n", context->debug_cfg.ref_payload[i].id); + } else { + /* Do nothing */ + } + } + } +#endif + + /* Get SNR - converted from 0.25dB step to dB */ + p->snr = (float)(pkt.snr_average) / 4; + + /* Get bandwidth */ + if (ifmod == IF_LORA_MULTI) { + p->bandwidth = BW_125KHZ; /* fixed in hardware */ + } else { + p->bandwidth = context->lora_service_cfg.bandwidth; /* get the parameter from the config variable */ + } + + /* Get datarate */ + switch (pkt.rx_rate_sf) { + case 5: p->datarate = DR_LORA_SF5; break; + case 6: p->datarate = DR_LORA_SF6; break; + case 7: p->datarate = DR_LORA_SF7; break; + case 8: p->datarate = DR_LORA_SF8; break; + case 9: p->datarate = DR_LORA_SF9; break; + case 10: p->datarate = DR_LORA_SF10; break; + case 11: p->datarate = DR_LORA_SF11; break; + case 12: p->datarate = DR_LORA_SF12; break; + default: p->datarate = DR_UNDEFINED; + } + + /* Get coding rate */ + if ((ifmod == IF_LORA_MULTI) || (context->lora_service_cfg.implicit_hdr == false)) { + cr = pkt.coding_rate; + } else { + cr = context->lora_service_cfg.implicit_coderate; + } + switch (cr) { + case 1: p->coderate = CR_LORA_4_5; break; + case 2: p->coderate = CR_LORA_4_6; break; + case 3: p->coderate = CR_LORA_4_7; break; + case 4: p->coderate = CR_LORA_4_8; break; + default: p->coderate = CR_UNDEFINED; + } + + /* Get frequency offset in Hz depending on bandwidth */ + switch (p->bandwidth) { + case BW_125KHZ: + p->freq_offset = (int32_t)((float)(pkt.frequency_offset_error) * FREQ_OFFSET_LSB_125KHZ ); + break; + case BW_250KHZ: + p->freq_offset = (int32_t)((float)(pkt.frequency_offset_error) * FREQ_OFFSET_LSB_250KHZ ); + break; + case BW_500KHZ: + p->freq_offset = (int32_t)((float)(pkt.frequency_offset_error) * FREQ_OFFSET_LSB_500KHZ ); + break; + default: + p->freq_offset = 0; + printf("Invalid frequency offset\n"); + break; + } + + /* Get timestamp correction to be applied */ + timestamp_correction = timestamp_counter_correction(ifmod, p->bandwidth, p->datarate, p->coderate, pkt.crc_en, pkt.rxbytenb_modem); + } else if (ifmod == IF_FSK_STD) { + DEBUG_PRINTF("Note: FSK packet (modem %u chan %u)\n", pkt.modem_id, p->if_chain); + p->modulation = MOD_FSK; + + /* Get CRC status */ + if (pkt.crc_en) { + /* CRC enabled */ + if (pkt.payload_crc_error) { + printf("FSK: CRC ERR\n"); + p->status = STAT_CRC_BAD; + } else { + printf("FSK: CRC OK\n"); + p->status = STAT_CRC_OK; + } + } else { + /* CRC disabled */ + p->status = STAT_NO_CRC; + } + + /* Get modulation params */ + p->bandwidth = context->fsk_cfg.bandwidth; + p->datarate = context->fsk_cfg.datarate; + + /* Compute timestamp correction to be applied */ + timestamp_correction = ((uint32_t)680000 / context->fsk_cfg.datarate) - 20; + + /* RSSI correction */ + p->rssic = RSSI_FSK_POLY_0 + RSSI_FSK_POLY_1 * p->rssic + RSSI_FSK_POLY_2 * pow(p->rssic, 2); + + /* Undefined for FSK */ + p->coderate = CR_UNDEFINED; + p->snr = -128.0; + p->rssis = -128.0; + } else { + DEBUG_MSG("ERROR: UNEXPECTED PACKET ORIGIN\n"); + p->status = STAT_UNDEFINED; + p->modulation = MOD_UNDEFINED; + p->rssic = -128.0; + p->rssis = -128.0; + p->snr = -128.0; + p->snr_min = -128.0; + p->snr_max = -128.0; + p->bandwidth = BW_UNDEFINED; + p->datarate = DR_UNDEFINED; + p->coderate = CR_UNDEFINED; + timestamp_correction = 0; + } + + /* Scale packet timestamp to 1 MHz (microseconds) */ + p->count_us = pkt.timestamp_cnt / 32; + /* Expand 27-bits counter to 32-bits counter, based on current wrapping status */ + p->count_us = timestamp_counter_expand(&counter_us, false, p->count_us); + /* Packet timestamp corrected */ + p->count_us = p->count_us - timestamp_correction; + + /* Packet CRC status */ + p->crc = pkt.rx_crc16_value; + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint16_t sx1302_lora_payload_crc(const uint8_t * data, uint8_t size) { + int i; + int crc = 0; + + for (i = 0; i < size; i++) { + lora_crc16(data[i], &crc); + } + + //printf("CRC16: 0x%02X 0x%02X (%X)\n", (uint8_t)(crc >> 8), (uint8_t)crc, crc); + return (uint16_t)crc; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_tx_set_start_delay(uint8_t rf_chain, lgw_radio_type_t radio_type, uint8_t modulation, uint8_t bandwidth) { + uint16_t tx_start_delay = TX_START_DELAY_DEFAULT * 32; + uint16_t radio_bw_delay = 0; + uint16_t filter_delay = 0; + uint16_t modem_delay = 0; + int32_t bw_hz = lgw_bw_getval(bandwidth); + int32_t val; + uint8_t chirp_low_pass = 0; + + /* Adjust with radio type and bandwidth */ + switch (radio_type) { + case LGW_RADIO_TYPE_SX1250: + if (bandwidth == BW_125KHZ) { + radio_bw_delay = 19; + } else if (bandwidth == BW_250KHZ) { + radio_bw_delay = 24; + } else if (bandwidth == BW_500KHZ) { + radio_bw_delay = 21; + } else { + DEBUG_MSG("ERROR: bandwidth not supported\n"); + return LGW_REG_ERROR; + } + break; + case LGW_RADIO_TYPE_SX1255: + case LGW_RADIO_TYPE_SX1257: + radio_bw_delay = 3*32 + 4; + if (bandwidth == BW_125KHZ) { + radio_bw_delay += 0; + } else if (bandwidth == BW_250KHZ) { + radio_bw_delay += 6; + } else if (bandwidth == BW_500KHZ) { + radio_bw_delay += 0; + } else { + DEBUG_MSG("ERROR: bandwidth not supported\n"); + return LGW_REG_ERROR; + } + break; + default: + DEBUG_MSG("ERROR: radio type not supported\n"); + return LGW_REG_ERROR; + } + + /* Adjust with modulation */ + if (modulation == MOD_LORA) { + lgw_reg_r(SX1302_REG_TX_TOP_TX_CFG0_0_CHIRP_LOWPASS(0), &val); + chirp_low_pass = (uint8_t)val; + filter_delay = ((1 << chirp_low_pass) - 1) * 1e6 / bw_hz; + modem_delay = 8 * (32e6 / (32 * bw_hz)); /* if bw=125k then modem freq=4MHz */ + } else { + /* TODO */ + filter_delay = 0; + modem_delay = 0; + } + + /* Compute total delay */ + tx_start_delay -= (radio_bw_delay + filter_delay + modem_delay); + + DEBUG_PRINTF("INFO: tx_start_delay=%u (%u, radio_bw_delay=%u, filter_delay=%u, modem_delay=%u)\n", (uint16_t)tx_start_delay, TX_START_DELAY_DEFAULT*32, radio_bw_delay, filter_delay, modem_delay); + + /* Configure the SX1302 with the calculated delay */ + lgw_reg_w(SX1302_REG_TX_TOP_TX_START_DELAY_MSB_TX_START_DELAY(rf_chain), (uint8_t)(tx_start_delay >> 8)); + lgw_reg_w(SX1302_REG_TX_TOP_TX_START_DELAY_LSB_TX_START_DELAY(rf_chain), (uint8_t)(tx_start_delay >> 0)); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +float sx1302_rssi_get_temperature_offset(struct lgw_rssi_tcomp_s * context, float temperature) { + /* Chekc params */ + CHECK_NULL(context); + + DEBUG_MSG ("INFO: RSSI temperature compensation:\n"); + DEBUG_PRINTF(" coeff_a: %.3f\n", context->coeff_a); + DEBUG_PRINTF(" coeff_b: %.3f\n", context->coeff_b); + DEBUG_PRINTF(" coeff_c: %.3f\n", context->coeff_c); + DEBUG_PRINTF(" coeff_d: %.3f\n", context->coeff_d); + DEBUG_PRINTF(" coeff_e: %.3f\n", context->coeff_e); + + /* Compute the offset to be applied to RSSI for given temperature */ + return ((context->coeff_a * pow(temperature, 4)) + + (context->coeff_b * pow(temperature, 3)) + + (context->coeff_c * pow(temperature, 2)) + + (context->coeff_d * temperature) + context->coeff_e) / pow(2, 16); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint8_t sx1302_tx_status(uint8_t rf_chain) { + int err; + int32_t read_value; + + err = lgw_reg_r(SX1302_REG_TX_TOP_TX_FSM_STATUS_TX_STATUS(rf_chain), &read_value); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: Failed to read TX STATUS"); + return TX_STATUS_UNKNOWN; + } + + if (read_value == 0x80) { + return TX_FREE; + } else if ((read_value == 0x30) || (read_value == 0x50) || (read_value == 0x60) || (read_value == 0x70)) { + return TX_EMITTING; + } else if ((read_value == 0x91) || (read_value == 0x92)) { + return TX_SCHEDULED; + } else { + printf("ERROR: UNKNOWN TX STATUS 0x%02X\n", read_value); + return TX_STATUS_UNKNOWN; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint8_t sx1302_rx_status(uint8_t rf_chain) { + if (rf_chain) {}; /* dummy for compilation */ + /* Not implemented */ + return RX_STATUS_UNKNOWN; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_tx_abort(uint8_t rf_chain) { + lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_IMMEDIATE(rf_chain), 0x00); + lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_DELAYED(rf_chain), 0x00); + lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_GPS(rf_chain), 0x00); + + do { + wait_ms(1); + } while (sx1302_tx_status(rf_chain) != TX_FREE); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_tx_configure(lgw_radio_type_t radio_type) { + /* Select the TX destination interface */ + switch (radio_type) { + case LGW_RADIO_TYPE_SX1250: + /* Let AGC control PLL DIV (sx1250 only) */ + lgw_reg_w(SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL2_PLL_DIV_CTRL_AGC, 1); + lgw_reg_w(SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL2_PLL_DIV_CTRL_AGC, 1); + + /* SX126x Tx RFFE */ + lgw_reg_w(SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_IF_DST, 0x01); + lgw_reg_w(SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_IF_DST, 0x01); + break; + case LGW_RADIO_TYPE_SX1257: + /* SX1255/57 Tx RFFE */ + lgw_reg_w(SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_IF_DST, 0x00); + lgw_reg_w(SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_IF_DST, 0x00); + break; + default: + DEBUG_MSG("ERROR: radio type not supported\n"); + return LGW_REG_ERROR; + } + + /* Configure the TX mode of operation */ + lgw_reg_w(SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_MODE, 0x01); /* Modulation */ + lgw_reg_w(SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_MODE, 0x01); /* Modulation */ + + /* Configure the output data clock edge */ + lgw_reg_w(SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_CLK_EDGE, 0x00); /* Data on rising edge */ + lgw_reg_w(SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_CLK_EDGE, 0x00); /* Data on rising edge */ + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_send(lgw_radio_type_t radio_type, struct lgw_tx_gain_lut_s * tx_lut, bool lwan_public, struct lgw_conf_rxif_s * context_fsk, struct lgw_pkt_tx_s * pkt_data) { + uint32_t freq_reg, fdev_reg; + uint32_t freq_dev; + uint32_t fsk_br_reg; + uint64_t fsk_sync_word_reg; + uint16_t mem_addr; + uint32_t count_us; + uint8_t power; + uint8_t pow_index; + uint8_t mod_bw; + uint8_t pa_en; + + /* CHeck input parameters */ + CHECK_NULL(tx_lut); + CHECK_NULL(pkt_data); + + /* Select the proper modem */ + switch (pkt_data->modulation) { + case MOD_CW: + lgw_reg_w(SX1302_REG_TX_TOP_GEN_CFG_0_MODULATION_TYPE(pkt_data->rf_chain), 0x00); + lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_CTRL_TX_IF_SRC(pkt_data->rf_chain), 0x00); + break; + case MOD_LORA: + lgw_reg_w(SX1302_REG_TX_TOP_GEN_CFG_0_MODULATION_TYPE(pkt_data->rf_chain), 0x00); + lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_CTRL_TX_IF_SRC(pkt_data->rf_chain), 0x01); + break; + case MOD_FSK: + lgw_reg_w(SX1302_REG_TX_TOP_GEN_CFG_0_MODULATION_TYPE(pkt_data->rf_chain), 0x01); + lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_CTRL_TX_IF_SRC(pkt_data->rf_chain), 0x02); + break; + default: + DEBUG_MSG("ERROR: modulation type not supported\n"); + return LGW_REG_ERROR; + } + + /* Find the proper index in the TX gain LUT according to requested rf_power */ + for (pow_index = tx_lut->size-1; pow_index > 0; pow_index--) { + if (tx_lut->lut[pow_index].rf_power <= pkt_data->rf_power) { + break; + } + } + DEBUG_PRINTF("INFO: selecting TX Gain LUT index %u\n", pow_index); + + /* loading calibrated Tx DC offsets */ + lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_I_OFFSET_I_OFFSET(pkt_data->rf_chain), tx_lut->lut[pow_index].offset_i); + lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_Q_OFFSET_Q_OFFSET(pkt_data->rf_chain), tx_lut->lut[pow_index].offset_q); + + DEBUG_PRINTF("INFO: Applying IQ offset (i:%d, q:%d)\n", tx_lut->lut[pow_index].offset_i, tx_lut->lut[pow_index].offset_q); + + /* Set the power parameters to be used for TX */ + switch (radio_type) { + case LGW_RADIO_TYPE_SX1250: + pa_en = (tx_lut->lut[pow_index].pa_gain > 0) ? 1 : 0; /* only 1 bit used to control the external PA */ + power = (pa_en << 6) | tx_lut->lut[pow_index].pwr_idx; + break; + case LGW_RADIO_TYPE_SX1257: + power = (tx_lut->lut[pow_index].pa_gain << 6) | (tx_lut->lut[pow_index].dac_gain << 4) | tx_lut->lut[pow_index].mix_gain; + break; + default: + DEBUG_MSG("ERROR: radio type not supported\n"); + return LGW_HAL_ERROR; + } + lgw_reg_w(SX1302_REG_TX_TOP_AGC_TX_PWR_AGC_TX_PWR(pkt_data->rf_chain), power); + + /* Set digital gain */ + lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_IQ_GAIN_IQ_GAIN(pkt_data->rf_chain), tx_lut->lut[pow_index].dig_gain); + + /* Set Tx frequency */ + freq_reg = SX1302_FREQ_TO_REG(pkt_data->freq_hz); /* TODO: AGC fw to be updated for sx1255 */ + lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_RF_H_FREQ_RF(pkt_data->rf_chain), (freq_reg >> 16) & 0xFF); + lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_RF_M_FREQ_RF(pkt_data->rf_chain), (freq_reg >> 8) & 0xFF); + lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_RF_L_FREQ_RF(pkt_data->rf_chain), (freq_reg >> 0) & 0xFF); + + /* Set AGC bandwidth and modulation type*/ + switch (pkt_data->modulation) { + case MOD_LORA: + mod_bw = pkt_data->bandwidth; + break; + case MOD_CW: + /* Intended fall-through */ + case MOD_FSK: + mod_bw = (0x01 << 7) | pkt_data->bandwidth; + break; + default: + printf("ERROR: Modulation not supported\n"); + return LGW_REG_ERROR; + } + lgw_reg_w(SX1302_REG_TX_TOP_AGC_TX_BW_AGC_TX_BW(pkt_data->rf_chain), mod_bw); + + /* Configure modem */ + switch (pkt_data->modulation) { + case MOD_CW: + /* Set frequency deviation */ + freq_dev = ceil(fabs((float)pkt_data->freq_offset/10))*10e3; + printf("CW: f_dev %d Hz\n", (int)(freq_dev)); + fdev_reg = SX1302_FREQ_TO_REG(freq_dev); + lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_H_FREQ_DEV(pkt_data->rf_chain), (fdev_reg >> 8) & 0xFF); + lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_L_FREQ_DEV(pkt_data->rf_chain), (fdev_reg >> 0) & 0xFF); + + /* Send frequency deviation to AGC fw for radio config */ + fdev_reg = SX1250_FREQ_TO_REG(freq_dev); + lgw_reg_w(SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE2_MCU_MAIL_BOX_WR_DATA, (fdev_reg >> 16) & 0xFF); /* Needed by AGC to configure the sx1250 */ + lgw_reg_w(SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE1_MCU_MAIL_BOX_WR_DATA, (fdev_reg >> 8) & 0xFF); /* Needed by AGC to configure the sx1250 */ + lgw_reg_w(SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE0_MCU_MAIL_BOX_WR_DATA, (fdev_reg >> 0) & 0xFF); /* Needed by AGC to configure the sx1250 */ + + /* Set the frequency offset (ratio of the frequency deviation)*/ + printf("CW: IF test mod freq %d\n", (int)(((float)pkt_data->freq_offset*1e3*64/(float)freq_dev))); + lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_TEST_MOD_FREQ(pkt_data->rf_chain), (int)(((float)pkt_data->freq_offset*1e3*64/(float)freq_dev))); + break; + case MOD_LORA: + /* Set bandwidth */ + freq_dev = lgw_bw_getval(pkt_data->bandwidth) / 2; + fdev_reg = SX1302_FREQ_TO_REG(freq_dev); + lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_H_FREQ_DEV(pkt_data->rf_chain), (fdev_reg >> 8) & 0xFF); + lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_L_FREQ_DEV(pkt_data->rf_chain), (fdev_reg >> 0) & 0xFF); + lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_0_MODEM_BW(pkt_data->rf_chain), pkt_data->bandwidth); + + /* Preamble length */ + if (pkt_data->preamble == 0) { /* if not explicit, use recommended LoRa preamble size */ + pkt_data->preamble = STD_LORA_PREAMBLE; + } else if (pkt_data->preamble < MIN_LORA_PREAMBLE) { /* enforce minimum preamble size */ + pkt_data->preamble = MIN_LORA_PREAMBLE; + DEBUG_MSG("Note: preamble length adjusted to respect minimum LoRa preamble size\n"); + } + lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG1_3_PREAMBLE_SYMB_NB(pkt_data->rf_chain), (pkt_data->preamble >> 8) & 0xFF); /* MSB */ + lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG1_2_PREAMBLE_SYMB_NB(pkt_data->rf_chain), (pkt_data->preamble >> 0) & 0xFF); /* LSB */ + + /* LoRa datarate */ + lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_0_MODEM_SF(pkt_data->rf_chain), pkt_data->datarate); + if (pkt_data->datarate < 10) { + lgw_reg_w(SX1302_REG_TX_TOP_TX_CFG0_0_CHIRP_LOWPASS(pkt_data->rf_chain), 6); /* less filtering for low SF : TBC */ + } else { + lgw_reg_w(SX1302_REG_TX_TOP_TX_CFG0_0_CHIRP_LOWPASS(pkt_data->rf_chain), 7); + } + + /* Coding Rate */ + lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_1_CODING_RATE(pkt_data->rf_chain), pkt_data->coderate); + + /* Start LoRa modem */ + lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_2_MODEM_EN(pkt_data->rf_chain), 1); + lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_2_CADRXTX(pkt_data->rf_chain), 2); + lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG1_1_MODEM_START(pkt_data->rf_chain), 1); + lgw_reg_w(SX1302_REG_TX_TOP_TX_CFG0_0_CONTINUOUS(pkt_data->rf_chain), 0); + + /* Modulation options */ + lgw_reg_w(SX1302_REG_TX_TOP_TX_CFG0_0_CHIRP_INVERT(pkt_data->rf_chain), (pkt_data->invert_pol) ? 1 : 0); + lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_2_IMPLICIT_HEADER(pkt_data->rf_chain), (pkt_data->no_header) ? 1 : 0); + lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_2_CRC_EN(pkt_data->rf_chain), (pkt_data->no_crc) ? 0 : 1); + + /* Syncword */ + if ((lwan_public == false) || (pkt_data->datarate == DR_LORA_SF5) || (pkt_data->datarate == DR_LORA_SF6)) { + DEBUG_MSG("Setting LoRa syncword 0x12\n"); + lgw_reg_w(SX1302_REG_TX_TOP_FRAME_SYNCH_0_PEAK1_POS(pkt_data->rf_chain), 2); + lgw_reg_w(SX1302_REG_TX_TOP_FRAME_SYNCH_1_PEAK2_POS(pkt_data->rf_chain), 4); + } else { + DEBUG_MSG("Setting LoRa syncword 0x34\n"); + lgw_reg_w(SX1302_REG_TX_TOP_FRAME_SYNCH_0_PEAK1_POS(pkt_data->rf_chain), 6); + lgw_reg_w(SX1302_REG_TX_TOP_FRAME_SYNCH_1_PEAK2_POS(pkt_data->rf_chain), 8); + } + + /* Set Fine Sync for SF5/SF6 */ + if ((pkt_data->datarate == DR_LORA_SF5) || (pkt_data->datarate == DR_LORA_SF6)) { + DEBUG_MSG("Enable Fine Sync\n"); + lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_2_FINE_SYNCH_EN(pkt_data->rf_chain), 1); + } else { + DEBUG_MSG("Disable Fine Sync\n"); + lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_2_FINE_SYNCH_EN(pkt_data->rf_chain), 0); + } + + /* Set Payload length */ + lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_3_PAYLOAD_LENGTH(pkt_data->rf_chain), pkt_data->size); + + /* Set PPM offset (low datarate optimization) */ + lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_1_PPM_OFFSET_HDR_CTRL(pkt_data->rf_chain), 0); + if (SET_PPM_ON(pkt_data->bandwidth, pkt_data->datarate)) { + DEBUG_MSG("Low datarate optimization ENABLED\n"); + lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_1_PPM_OFFSET(pkt_data->rf_chain), 1); + } else { + DEBUG_MSG("Low datarate optimization DISABLED\n"); + lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_1_PPM_OFFSET(pkt_data->rf_chain), 0); + } + break; + case MOD_FSK: + CHECK_NULL(context_fsk); + + /* Set frequency deviation */ + freq_dev = pkt_data->f_dev * 1e3; + fdev_reg = SX1302_FREQ_TO_REG(freq_dev); + lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_H_FREQ_DEV(pkt_data->rf_chain), (fdev_reg >> 8) & 0xFF); + lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_L_FREQ_DEV(pkt_data->rf_chain), (fdev_reg >> 0) & 0xFF); + + /* Send frequency deviation to AGC fw for radio config */ + fdev_reg = SX1250_FREQ_TO_REG(freq_dev); + lgw_reg_w(SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE2_MCU_MAIL_BOX_WR_DATA, (fdev_reg >> 16) & 0xFF); /* Needed by AGC to configure the sx1250 */ + lgw_reg_w(SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE1_MCU_MAIL_BOX_WR_DATA, (fdev_reg >> 8) & 0xFF); /* Needed by AGC to configure the sx1250 */ + lgw_reg_w(SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE0_MCU_MAIL_BOX_WR_DATA, (fdev_reg >> 0) & 0xFF); /* Needed by AGC to configure the sx1250 */ + + /* Modulation parameters */ + lgw_reg_w(SX1302_REG_TX_TOP_FSK_CFG_0_PKT_MODE(pkt_data->rf_chain), 1); /* Variable length */ + lgw_reg_w(SX1302_REG_TX_TOP_FSK_CFG_0_CRC_EN(pkt_data->rf_chain), (pkt_data->no_crc) ? 0 : 1); + lgw_reg_w(SX1302_REG_TX_TOP_FSK_CFG_0_CRC_IBM(pkt_data->rf_chain), 0); /* CCITT CRC */ + lgw_reg_w(SX1302_REG_TX_TOP_FSK_CFG_0_DCFREE_ENC(pkt_data->rf_chain), 2); /* Whitening Encoding */ + lgw_reg_w(SX1302_REG_TX_TOP_FSK_MOD_FSK_GAUSSIAN_EN(pkt_data->rf_chain), 1); + lgw_reg_w(SX1302_REG_TX_TOP_FSK_MOD_FSK_GAUSSIAN_SELECT_BT(pkt_data->rf_chain), 2); + lgw_reg_w(SX1302_REG_TX_TOP_FSK_MOD_FSK_REF_PATTERN_EN(pkt_data->rf_chain), 1); + lgw_reg_w(SX1302_REG_TX_TOP_FSK_MOD_FSK_REF_PATTERN_SIZE(pkt_data->rf_chain), context_fsk->sync_word_size - 1); + + /* Syncword */ + fsk_sync_word_reg = context_fsk->sync_word << (8 * (8 - context_fsk->sync_word_size)); + lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE0_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 0)); + lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE1_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 8)); + lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE2_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 16)); + lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE3_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 24)); + lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE4_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 32)); + lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE5_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 40)); + lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE6_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 48)); + lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE7_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 56)); + lgw_reg_w(SX1302_REG_TX_TOP_FSK_MOD_FSK_PREAMBLE_SEQ(pkt_data->rf_chain), 0); + + /* Set datarate */ + fsk_br_reg = 32000000 / pkt_data->datarate; + lgw_reg_w(SX1302_REG_TX_TOP_FSK_BIT_RATE_MSB_BIT_RATE(pkt_data->rf_chain), fsk_br_reg >> 8); + lgw_reg_w(SX1302_REG_TX_TOP_FSK_BIT_RATE_LSB_BIT_RATE(pkt_data->rf_chain), fsk_br_reg >> 0); + + /* Preamble length */ + if (pkt_data->preamble == 0) { /* if not explicit, use LoRaWAN preamble size */ + pkt_data->preamble = STD_FSK_PREAMBLE; + } else if (pkt_data->preamble < MIN_FSK_PREAMBLE) { /* enforce minimum preamble size */ + pkt_data->preamble = MIN_FSK_PREAMBLE; + DEBUG_MSG("Note: preamble length adjusted to respect minimum FSK preamble size\n"); + } + lgw_reg_w(SX1302_REG_TX_TOP_FSK_PREAMBLE_SIZE_MSB_PREAMBLE_SIZE(pkt_data->rf_chain), (pkt_data->preamble >> 8) & 0xFF); /* MSB */ + lgw_reg_w(SX1302_REG_TX_TOP_FSK_PREAMBLE_SIZE_LSB_PREAMBLE_SIZE(pkt_data->rf_chain), (pkt_data->preamble >> 0) & 0xFF); /* LSB */ + + /* Set Payload length */ + lgw_reg_w(SX1302_REG_TX_TOP_FSK_PKT_LEN_PKT_LENGTH(pkt_data->rf_chain), pkt_data->size); + break; + default: + printf("ERROR: Modulation not supported\n"); + return LGW_REG_ERROR; + } + + /* Set TX start delay */ + sx1302_tx_set_start_delay(pkt_data->rf_chain, radio_type, pkt_data->modulation, pkt_data->bandwidth); + + /* Write payload in transmit buffer */ + lgw_reg_w(SX1302_REG_TX_TOP_TX_CTRL_WRITE_BUFFER(pkt_data->rf_chain), 0x01); + mem_addr = REG_SELECT(pkt_data->rf_chain, 0x5300, 0x5500); + if (pkt_data->modulation == MOD_FSK) { + lgw_mem_wb(mem_addr, (uint8_t *)(&(pkt_data->size)), 1); /* insert payload size in the packet for FSK variable mode (1 byte) */ + lgw_mem_wb(mem_addr+1, &(pkt_data->payload[0]), pkt_data->size); + } else { + lgw_mem_wb(mem_addr, &(pkt_data->payload[0]), pkt_data->size); + } + lgw_reg_w(SX1302_REG_TX_TOP_TX_CTRL_WRITE_BUFFER(pkt_data->rf_chain), 0x00); + + /* Trigger transmit */ + DEBUG_PRINTF("Start Tx: Freq:%u %s%u size:%u preamb:%u\n", pkt_data->freq_hz, (pkt_data->modulation == MOD_LORA) ? "SF" : "DR:", pkt_data->datarate, pkt_data->size, pkt_data->preamble); + switch (pkt_data->tx_mode) { + case IMMEDIATE: + lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_IMMEDIATE(pkt_data->rf_chain), 0x00); /* reset state machine */ + lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_IMMEDIATE(pkt_data->rf_chain), 0x01); + break; + case TIMESTAMPED: + count_us = pkt_data->count_us * 32; + DEBUG_PRINTF("--> programming trig delay at %u (%u)\n", pkt_data->count_us, count_us); + + lgw_reg_w(SX1302_REG_TX_TOP_TIMER_TRIG_BYTE0_TIMER_DELAYED_TRIG(pkt_data->rf_chain), (uint8_t)((count_us >> 0) & 0x000000FF)); + lgw_reg_w(SX1302_REG_TX_TOP_TIMER_TRIG_BYTE1_TIMER_DELAYED_TRIG(pkt_data->rf_chain), (uint8_t)((count_us >> 8) & 0x000000FF)); + lgw_reg_w(SX1302_REG_TX_TOP_TIMER_TRIG_BYTE2_TIMER_DELAYED_TRIG(pkt_data->rf_chain), (uint8_t)((count_us >> 16) & 0x000000FF)); + lgw_reg_w(SX1302_REG_TX_TOP_TIMER_TRIG_BYTE3_TIMER_DELAYED_TRIG(pkt_data->rf_chain), (uint8_t)((count_us >> 24) & 0x000000FF)); + + lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_DELAYED(pkt_data->rf_chain), 0x00); /* reset state machine */ + lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_DELAYED(pkt_data->rf_chain), 0x01); + break; + case ON_GPS: + lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_GPS(pkt_data->rf_chain), 0x00); /* reset state machine */ + lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_GPS(pkt_data->rf_chain), 0x01); + break; + default: + printf("ERROR: TX mode not supported\n"); + return LGW_REG_ERROR; + } + + return LGW_REG_SUCCESS; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_sx1302_rx.c b/libloragw/src/loragw_sx1302_rx.c new file mode 100644 index 0000000..684da42 --- /dev/null +++ b/libloragw/src/loragw_sx1302_rx.c @@ -0,0 +1,370 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + SX1302 RX buffer Hardware Abstraction Layer + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* printf fprintf */ +#include /* memset */ +#include /* assert */ + +#include "loragw_aux.h" +#include "loragw_reg.h" +#include "loragw_sx1302_rx.h" +#include "loragw_sx1302_timestamp.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_SX1302 == 1 + #define DEBUG_MSG(str) fprintf(stderr, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stderr, fmt, args) + #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_REG_ERROR;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return LGW_REG_ERROR;} +#endif + +#define SX1302_PKT_PAYLOAD_LENGTH(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 2], 0, 8) +#define SX1302_PKT_CHANNEL(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 3], 0, 8) +#define SX1302_PKT_CRC_EN(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 4], 0, 1) +#define SX1302_PKT_CODING_RATE(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 4], 1, 3) +#define SX1302_PKT_DATARATE(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 4], 4, 4) +#define SX1302_PKT_MODEM_ID(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 5], 0, 8) +#define SX1302_PKT_FREQ_OFFSET_7_0(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 6], 0, 8) +#define SX1302_PKT_FREQ_OFFSET_15_8(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 7], 0, 8) +#define SX1302_PKT_FREQ_OFFSET_19_16(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 8], 0, 4) +#define SX1302_PKT_CRC_ERROR(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 9], 0, 1) +#define SX1302_PKT_SYNC_ERROR(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 9], 2, 1) +#define SX1302_PKT_HEADER_ERROR(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 9], 3, 1) +#define SX1302_PKT_TIMING_SET(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 9], 4, 1) +#define SX1302_PKT_SNR_AVG(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 10], 0, 8) +#define SX1302_PKT_RSSI_CHAN(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 11], 0, 8) +#define SX1302_PKT_RSSI_SIG(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 12], 0, 8) +#define SX1302_PKT_RSSI_CHAN_MAX_NEG_DELTA(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 13], 0, 4) +#define SX1302_PKT_RSSI_CHAN_MAX_POS_DELTA(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 13], 4, 4) +#define SX1302_PKT_RSSI_SIG_MAX_NEG_DELTA(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 14], 0, 4) +#define SX1302_PKT_RSSI_SIG_MAX_POS_DELTA(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 14], 4, 4) +#define SX1302_PKT_TIMESTAMP_7_0(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 15], 0, 8) +#define SX1302_PKT_TIMESTAMP_15_8(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 16], 0, 8) +#define SX1302_PKT_TIMESTAMP_23_16(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 17], 0, 8) +#define SX1302_PKT_TIMESTAMP_31_24(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 18], 0, 8) +#define SX1302_PKT_CRC_PAYLOAD_7_0(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 19], 0, 8) +#define SX1302_PKT_CRC_PAYLOAD_15_8(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 20], 0, 8) +#define SX1302_PKT_NUM_TS_METRICS(buffer, start_index) TAKE_N_BITS_FROM(buffer[start_index + 21], 0, 8) + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE TYPES -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +/* RX buffer packet structure */ +#define SX1302_PKT_SYNCWORD_BYTE_0 0xA5 +#define SX1302_PKT_SYNCWORD_BYTE_1 0xC0 +#define SX1302_PKT_HEAD_METADATA 9 +#define SX1302_PKT_TAIL_METADATA 14 + +/* modem IDs */ +#if FPGA_BOARD_16_CH +#define SX1302_LORA_MODEM_ID_MAX 15 +#define SX1302_LORA_STD_MODEM_ID 16 +#define SX1302_FSK_MODEM_ID 17 +#else +#define SX1302_LORA_MODEM_ID_MAX 11 +#define SX1302_LORA_STD_MODEM_ID 12 +#define SX1302_FSK_MODEM_ID 13 +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- INTERNAL SHARED VARIABLES -------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int rx_buffer_new(rx_buffer_t * self) { + /* Check input params */ + CHECK_NULL(self); + + /* Initialize members */ + memset(self->buffer, 0, sizeof self->buffer); + self->buffer_size = 0; + self->buffer_index = 0; + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int rx_buffer_del(rx_buffer_t * self) { + /* Check input params */ + CHECK_NULL(self); + + /* Reset index & size */ + self->buffer_size = 0; + self->buffer_index = 0; + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int rx_buffer_fetch(rx_buffer_t * self) { + int i, res; + uint8_t buff[2]; + + /* Check input params */ + CHECK_NULL(self); + + /* Check if there is data in the FIFO */ + lgw_reg_rb(SX1302_REG_RX_TOP_RX_BUFFER_NB_BYTES_MSB_RX_BUFFER_NB_BYTES, buff, sizeof buff); + self->buffer_size = (uint16_t)((buff[0] << 8) & 0xFF00); + self->buffer_size |= (uint16_t)((buff[1] << 0) & 0x00FF); + + /* Fetch bytes from fifo if any */ + if (self->buffer_size > 0) { + DEBUG_MSG ("-----------------\n"); + DEBUG_PRINTF("%s: nb_bytes to be fetched: %u (%u %u)\n", __FUNCTION__, self->buffer_size, buff[1], buff[0]); + + memset(self->buffer, 0, sizeof self->buffer); + res = lgw_mem_rb(0x4000, self->buffer, self->buffer_size, true); + if (res != LGW_REG_SUCCESS) { + printf("ERROR: Failed to read RX buffer, SPI error\n"); + return LGW_REG_ERROR; + } + + /* print debug info : TODO to be removed */ + DEBUG_MSG("RX_BUFFER: "); + for (i = 0; i < self->buffer_size; i++) { + DEBUG_PRINTF("%02X ", self->buffer[i]); + } + DEBUG_MSG("\n"); + + } + + /* Initialize the current buffer index to iterate on */ + self->buffer_index = 0; + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int rx_buffer_pop(rx_buffer_t * self, rx_packet_t * pkt) { + int i; + uint8_t checksum_rcv, checksum_calc = 0; + uint16_t checksum_idx; + uint16_t pkt_num_bytes; + + /* Check input params */ + CHECK_NULL(self); + CHECK_NULL(pkt); + + /* Is there any data to be parsed ? */ + if (self->buffer_index >= self->buffer_size) { + DEBUG_MSG("INFO: No more data to be parsed\n"); + return LGW_REG_ERROR; + } + + /* Get pkt sync words */ + if ((self->buffer[self->buffer_index] != SX1302_PKT_SYNCWORD_BYTE_0) || (self->buffer[self->buffer_index + 1] != SX1302_PKT_SYNCWORD_BYTE_1)) { + printf("INFO: searching syncword...\n"); + self->buffer_index += 1; + return LGW_REG_ERROR; + /* TODO: while loop until syncword found ?? */ + } + DEBUG_PRINTF("INFO: pkt syncword found at index %u\n", self->buffer_index); + + /* Get payload length */ + pkt->rxbytenb_modem = SX1302_PKT_PAYLOAD_LENGTH(self->buffer, self->buffer_index); + + /* Get fine timestamp metrics */ + pkt->num_ts_metrics_stored = SX1302_PKT_NUM_TS_METRICS(self->buffer, self->buffer_index + pkt->rxbytenb_modem); + + /* Calculate the total number of bytes in the packet */ + pkt_num_bytes = SX1302_PKT_HEAD_METADATA + pkt->rxbytenb_modem + SX1302_PKT_TAIL_METADATA + (2 * pkt->num_ts_metrics_stored); + + /* Check if we have a complete packet in the rx buffer fetched */ + if((self->buffer_index + pkt_num_bytes) > self->buffer_size) { + printf("WARNING: aborting truncated message (size=%u)\n", self->buffer_size); + return LGW_REG_ERROR; + } + + /* Get the checksum as received in the RX buffer */ + checksum_idx = pkt_num_bytes - 1; + checksum_rcv = self->buffer[self->buffer_index + pkt_num_bytes - 1]; + + /* Calculate the checksum from the actual payload bytes received */ + for (i = 0; i < (int)checksum_idx; i++) { + checksum_calc += self->buffer[self->buffer_index + i]; + } + + /* Check if the checksum is correct */ + if (checksum_rcv != checksum_calc) { + printf("WARNING: checksum failed (got:0x%02X calc:0x%02X)\n", checksum_rcv, checksum_calc); + return LGW_REG_ERROR; + } else { + DEBUG_PRINTF("Packet checksum OK (0x%02X)\n", checksum_rcv); + } + + /* Parse packet metadata */ + pkt->modem_id = SX1302_PKT_MODEM_ID(self->buffer, self->buffer_index); + pkt->rx_channel_in = SX1302_PKT_CHANNEL(self->buffer, self->buffer_index); + pkt->crc_en = SX1302_PKT_CRC_EN(self->buffer, self->buffer_index); + pkt->payload_crc_error = SX1302_PKT_CRC_ERROR(self->buffer, self->buffer_index + pkt->rxbytenb_modem); + pkt->sync_error = SX1302_PKT_SYNC_ERROR(self->buffer, self->buffer_index + pkt->rxbytenb_modem); + pkt->header_error = SX1302_PKT_HEADER_ERROR(self->buffer, self->buffer_index + pkt->rxbytenb_modem); + pkt->timing_set = SX1302_PKT_TIMING_SET(self->buffer, self->buffer_index + pkt->rxbytenb_modem); + pkt->coding_rate = SX1302_PKT_CODING_RATE(self->buffer, self->buffer_index); + pkt->rx_rate_sf = SX1302_PKT_DATARATE(self->buffer, self->buffer_index); + pkt->rssi_chan_avg = SX1302_PKT_RSSI_CHAN(self->buffer, self->buffer_index + pkt->rxbytenb_modem); + pkt->rssi_signal_avg = SX1302_PKT_RSSI_SIG(self->buffer, self->buffer_index + pkt->rxbytenb_modem); + pkt->rx_crc16_value = (uint16_t)((SX1302_PKT_CRC_PAYLOAD_7_0(self->buffer, self->buffer_index + pkt->rxbytenb_modem) << 0) & 0x00FF); + pkt->rx_crc16_value |= (uint16_t)((SX1302_PKT_CRC_PAYLOAD_15_8(self->buffer, self->buffer_index + pkt->rxbytenb_modem) << 8) & 0xFF00); + pkt->snr_average = (int8_t)SX1302_PKT_SNR_AVG(self->buffer, self->buffer_index + pkt->rxbytenb_modem); + + pkt->frequency_offset_error = (int32_t)((SX1302_PKT_FREQ_OFFSET_19_16(self->buffer, self->buffer_index) << 16) | (SX1302_PKT_FREQ_OFFSET_15_8(self->buffer, self->buffer_index) << 8) | (SX1302_PKT_FREQ_OFFSET_7_0(self->buffer, self->buffer_index) << 0)); + if (pkt->frequency_offset_error >= (1<<19)) { /* Handle signed value on 20bits */ + pkt->frequency_offset_error = (pkt->frequency_offset_error - (1<<20)); + } + + /* Packet timestamp (32MHz ) */ + pkt->timestamp_cnt = (uint32_t)((SX1302_PKT_TIMESTAMP_7_0(self->buffer, self->buffer_index + pkt->rxbytenb_modem) << 0) & 0x000000FF); + pkt->timestamp_cnt |= (uint32_t)((SX1302_PKT_TIMESTAMP_15_8(self->buffer, self->buffer_index + pkt->rxbytenb_modem) << 8) & 0x0000FF00); + pkt->timestamp_cnt |= (uint32_t)((SX1302_PKT_TIMESTAMP_23_16(self->buffer, self->buffer_index + pkt->rxbytenb_modem) << 16) & 0x00FF0000); + pkt->timestamp_cnt |= (uint32_t)((SX1302_PKT_TIMESTAMP_31_24(self->buffer, self->buffer_index + pkt->rxbytenb_modem) << 24) & 0xFF000000); + +#if 0 + /* Scale packet timestamp to 1 MHz (microseconds) */ + pkt->timestamp_cnt /= 32; + /* Expand 27-bits counter to 32-bits counter, based on current wrapping status */ + pkt->timestamp_cnt = timestamp_counter_expand(&counter_us, false, pkt->timestamp_cnt); +#endif + + DEBUG_MSG ("-----------------\n"); + DEBUG_PRINTF(" modem: %u\n", pkt->modem_id); + DEBUG_PRINTF(" chan: %u\n", pkt->rx_channel_in); + DEBUG_PRINTF(" size: %u\n", pkt->rxbytenb_modem); + DEBUG_PRINTF(" crc_en: %u\n", pkt->crc_en); + DEBUG_PRINTF(" crc_err: %u\n", pkt->payload_crc_error); + DEBUG_PRINTF(" sync_err: %u\n", pkt->sync_error); + DEBUG_PRINTF(" hdr_err: %u\n", pkt->header_error); + DEBUG_PRINTF(" timing_set: %u\n", pkt->timing_set); + DEBUG_PRINTF(" codr: %u\n", pkt->coding_rate); + DEBUG_PRINTF(" datr: %u\n", pkt->rx_rate_sf); + DEBUG_PRINTF(" num_ts: %u\n", pkt->num_ts_metrics_stored); + DEBUG_MSG ("-----------------\n"); + + /* Sanity checks: check the range of few metadata */ + if (pkt->modem_id > SX1302_FSK_MODEM_ID) { + printf("ERROR: modem_id is out of range - %u\n", pkt->modem_id); + return LGW_REG_ERROR; + } else { + if (pkt->modem_id <= SX1302_LORA_STD_MODEM_ID) { /* LoRa modems */ + if (pkt->rx_channel_in > 9) { + printf("ERROR: channel is out of range - %u\n", pkt->rx_channel_in); + return LGW_REG_ERROR; + } + if ((pkt->rx_rate_sf < 5) || (pkt->rx_rate_sf > 12)) { + printf("ERROR: SF is out of range - %u\n", pkt->rx_rate_sf); + return LGW_REG_ERROR; + } + } else { /* FSK modem */ + /* TODO: not checked */ + } + } + + /* Parse & copy payload in packet struct */ + memcpy((void *)pkt->payload, (void *)(&(self->buffer[self->buffer_index + SX1302_PKT_HEAD_METADATA])), pkt->rxbytenb_modem); + + /* move buffer index toward next message */ + self->buffer_index += (SX1302_PKT_HEAD_METADATA + pkt->rxbytenb_modem + SX1302_PKT_TAIL_METADATA + (2 * pkt->num_ts_metrics_stored)); + + return LGW_REG_SUCCESS; +} + +/* -------------------------------------------------------------------------- */ +/* --- DEBUG FUNCTIONS DEFINITION ------------------------------------------- */ + +uint16_t rx_buffer_read_ptr_addr(void) { + int32_t val; + uint16_t addr; + + lgw_reg_r(SX1302_REG_RX_TOP_RX_BUFFER_LAST_ADDR_READ_MSB_LAST_ADDR_READ, &val); /* mandatory to read MSB first */ + addr = (uint16_t)(val << 8); + lgw_reg_r(SX1302_REG_RX_TOP_RX_BUFFER_LAST_ADDR_READ_LSB_LAST_ADDR_READ, &val); + addr |= (uint16_t)val; + + return addr; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint16_t rx_buffer_write_ptr_addr(void) { + int32_t val; + uint16_t addr; + + lgw_reg_r(SX1302_REG_RX_TOP_RX_BUFFER_LAST_ADDR_WRITE_MSB_LAST_ADDR_WRITE, &val); /* mandatory to read MSB first */ + addr = (uint16_t)(val << 8); + lgw_reg_r(SX1302_REG_RX_TOP_RX_BUFFER_LAST_ADDR_WRITE_LSB_LAST_ADDR_WRITE, &val); + addr |= (uint16_t)val; + + return addr; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void rx_buffer_dump(FILE * file, uint16_t start_addr, uint16_t end_addr) { + int i; + uint8_t rx_buffer_debug[4096]; + + printf("Dumping %u bytes, from 0x%X to 0x%X\n", end_addr - start_addr + 1, start_addr, end_addr); + + memset(rx_buffer_debug, 0, sizeof rx_buffer_debug); + + lgw_reg_w(SX1302_REG_RX_TOP_RX_BUFFER_DIRECT_RAM_IF, 1); + lgw_mem_rb(0x4000 + start_addr, rx_buffer_debug, end_addr - start_addr + 1, false); + lgw_reg_w(SX1302_REG_RX_TOP_RX_BUFFER_DIRECT_RAM_IF, 0); + + for (i = 0; i < (end_addr - start_addr + 1); i++) { + if (file == NULL) { + printf("%02X ", rx_buffer_debug[i]); + } else { + fprintf(file, "%02X ", rx_buffer_debug[i]); + } + } + if (file == NULL) { + printf("\n"); + } else { + fprintf(file, "\n"); + } + + /* Switching to direct-access memory could lead to corruption, so to be done only for debugging */ + assert(0); +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_sx1302_timestamp.c b/libloragw/src/loragw_sx1302_timestamp.c new file mode 100644 index 0000000..a51e92a --- /dev/null +++ b/libloragw/src/loragw_sx1302_timestamp.c @@ -0,0 +1,295 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + SX1302 timestamp counter Hardware Abstraction Layer + Handles the conversion of a 32-bits 32MHz counter into a 32-bits 1 MHz counter. + This modules MUST be called regularly by the application to maintain counter + wrapping handling for conversion in 1MHz counter. + Provides function to compute the correction to be applied to the received + timestamp for demodulation processing time. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* printf fprintf */ + +#include "loragw_sx1302_timestamp.h" +#include "loragw_reg.h" +#include "loragw_hal.h" +#include "loragw_sx1302.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_SX1302 == 1 + #define DEBUG_MSG(str) fprintf(stderr, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stderr, fmt, args) + #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_REG_ERROR;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return LGW_REG_ERROR;} +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE TYPES -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- INTERNAL SHARED VARIABLES -------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +void timestamp_counter_new(timestamp_counter_t * self) { + self->counter_us_raw_27bits_inst_prev = 0; + self->counter_us_raw_27bits_pps_prev = 0; + self->counter_us_raw_27bits_inst_wrap = 0; + self->counter_us_raw_27bits_pps_wrap = 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void timestamp_counter_delete(timestamp_counter_t * self) { + self->counter_us_raw_27bits_inst_prev = 0; + self->counter_us_raw_27bits_pps_prev = 0; + self->counter_us_raw_27bits_inst_wrap = 0; + self->counter_us_raw_27bits_pps_wrap = 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void timestamp_counter_update(timestamp_counter_t * self, bool pps, uint32_t cnt) { + uint32_t counter_us_raw_27bits_prev; + uint8_t counter_us_raw_27bits_wrap; + + /* Get the previous counter value and wrap status */ + if (pps == true) { + counter_us_raw_27bits_prev = self->counter_us_raw_27bits_pps_prev; + counter_us_raw_27bits_wrap = self->counter_us_raw_27bits_pps_wrap; + } else { + counter_us_raw_27bits_prev = self->counter_us_raw_27bits_inst_prev; + counter_us_raw_27bits_wrap = self->counter_us_raw_27bits_inst_wrap; + } + + /* Check if counter has wrapped, and update wrap status if necessary */ + if (cnt < counter_us_raw_27bits_prev) { + counter_us_raw_27bits_wrap += 1; + counter_us_raw_27bits_wrap = counter_us_raw_27bits_wrap % 32; + } + + /* Store counter value and wrap status for next time */ + if (pps == true) { + self->counter_us_raw_27bits_pps_prev = cnt; + self->counter_us_raw_27bits_pps_wrap = counter_us_raw_27bits_wrap; + } else { + self->counter_us_raw_27bits_inst_prev = cnt; + self->counter_us_raw_27bits_inst_wrap = counter_us_raw_27bits_wrap; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint32_t timestamp_counter_get(timestamp_counter_t * self, bool pps) { + int x; + uint8_t buff[4]; + uint32_t counter_us_raw_27bits_now; + + /* Get the 32MHz timestamp counter - 4 bytes */ + /* step of 31.25 ns */ + x = lgw_reg_rb((pps == true) ? SX1302_REG_TIMESTAMP_TIMESTAMP_PPS_MSB2_TIMESTAMP_PPS : SX1302_REG_TIMESTAMP_TIMESTAMP_MSB2_TIMESTAMP, &buff[0], 4); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: Failed to get timestamp counter value\n"); + return 0; + } + + counter_us_raw_27bits_now = (uint32_t)((buff[0] << 24) & 0xFF000000); + counter_us_raw_27bits_now |= (uint32_t)((buff[1] << 16) & 0x00FF0000); + counter_us_raw_27bits_now |= (uint32_t)((buff[2] << 8) & 0x0000FF00); + counter_us_raw_27bits_now |= (uint32_t)((buff[3] << 0) & 0x000000FF); + counter_us_raw_27bits_now /= 32; /* scale to 1MHz */ + + /* Update counter wrapping status */ + timestamp_counter_update(self, pps, counter_us_raw_27bits_now); + + /* Convert 27-bits counter to 32-bits counter */ + return timestamp_counter_expand(self, pps, counter_us_raw_27bits_now); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint32_t timestamp_counter_expand(timestamp_counter_t * self, bool pps, uint32_t cnt_us) { + uint32_t counter_us_32bits; + + if (pps == true) { + counter_us_32bits = (self->counter_us_raw_27bits_pps_wrap << 27) | cnt_us; + } else { + counter_us_32bits = (self->counter_us_raw_27bits_inst_wrap << 27) | cnt_us; + } + +#if 0 + /* DEBUG: to be enabled when running test_loragw_counter test application + This generates a CSV log, and can be plotted with gnuplot: + > set datafile separator comma + > plot for [col=1:2:1] 'log_count.txt' using col with lines + */ + printf("%u,%u,%u\n", cnt_us, counter_us_32bits, (pps == true) ? self->counter_us_raw_27bits_pps_wrap : self->counter_us_raw_27bits_inst_wrap); +#endif + + return counter_us_32bits; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int timestamp_counter_mode(bool enable_precision_ts, uint8_t max_ts_metrics, uint8_t nb_symbols) { + if (enable_precision_ts == false) { + DEBUG_MSG("INFO: using legacy timestamp\n"); + /* Latch end-of-packet timestamp (sx1301 compatibility) */ + lgw_reg_w(SX1302_REG_RX_TOP_RX_BUFFER_LEGACY_TIMESTAMP, 0x01); + } else { + DEBUG_PRINTF("INFO: using precision timestamp (max_ts_metrics:%u nb_symbols:%u)\n", max_ts_metrics, nb_symbols); + /* Latch end-of-preamble timestamp */ + lgw_reg_w(SX1302_REG_RX_TOP_RX_BUFFER_LEGACY_TIMESTAMP, 0x00); + lgw_reg_w(SX1302_REG_RX_TOP_RX_BUFFER_TIMESTAMP_CFG_MAX_TS_METRICS, max_ts_metrics); + + /* LoRa multi-SF modems */ + lgw_reg_w(SX1302_REG_RX_TOP_TIMESTAMP_ENABLE, 0x01); + lgw_reg_w(SX1302_REG_RX_TOP_TIMESTAMP_NB_SYMB, nb_symbols); + + /* LoRa service modem */ + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TIMESTAMP_ENABLE, 0x01); + lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TIMESTAMP_NB_SYMB, nb_symbols); + } + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint32_t timestamp_counter_correction(int ifmod, uint8_t bandwidth, uint8_t datarate, uint8_t coderate, uint32_t crc_en, uint16_t payload_length) { + int32_t val; + uint32_t sf = (uint32_t)datarate, cr = (uint32_t)coderate, bw_pow, ppm; + uint32_t clk_period; + uint32_t nb_nibble, nb_nibble_in_hdr, nb_nibble_in_last_block; + uint32_t dft_peak_en, nb_iter; + uint32_t demap_delay, decode_delay, fft_delay_state3, fft_delay, delay_x; + uint32_t timestamp_correction; + + /* determine if 'PPM mode' is on */ + if (SET_PPM_ON(bandwidth, datarate)) { + ppm = 1; + } else { + ppm = 0; + } + + /* timestamp correction code, base delay */ + switch (bandwidth) + { + case BW_125KHZ: + bw_pow = 1; + delay_x = 16000000 / bw_pow + 2031250; + break; + case BW_250KHZ: + bw_pow = 2; + delay_x = 16000000 / bw_pow + 2031250; + break; + case BW_500KHZ: + bw_pow = 4; + delay_x = 16000000 / bw_pow + 2031250; + break; + default: + DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", bandwidth); + delay_x = 0; + bw_pow = 0; + break; + } + clk_period = 250000; + + nb_nibble = (payload_length + 2 * crc_en) * 2 + 5; + + if ((sf == 5) || (sf == 6)) { + nb_nibble_in_hdr = sf; + } else { + nb_nibble_in_hdr = sf - 2; + } + + nb_nibble_in_last_block = nb_nibble - nb_nibble_in_hdr - (sf - 2 * ppm) * ((nb_nibble - nb_nibble_in_hdr) / (sf - 2 * ppm)); + if (nb_nibble_in_last_block == 0) { + nb_nibble_in_last_block = sf - 2 * ppm; + } + + nb_iter = ((sf + 1) >> 1); + + /* timestamp correction code, variable delay */ + if (ifmod == IF_LORA_STD) { + lgw_reg_r(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_CFG0_DFT_PEAK_EN, &val); + } else { + lgw_reg_r(SX1302_REG_RX_TOP_RX_CFG0_DFT_PEAK_EN, &val); + } + if (val != 0) { + /* TODO: should we differentiate the mode (FULL/TRACK) ? */ + dft_peak_en = 1; + } else { + dft_peak_en = 0; + } + + + if ((sf >= 5) && (sf <= 12) && (bw_pow > 0)) { + if ((2 * (payload_length + 2 * crc_en) - (sf - 7)) <= 0) { /* payload fits entirely in first 8 symbols (header) */ + if (sf > 6) { + nb_nibble_in_last_block = sf - 2; + } else { + nb_nibble_in_last_block = sf; // can't be acheived + } + dft_peak_en = 0; + cr = 4; /* header coding rate is 4 */ + demap_delay = clk_period + (1 << sf) * clk_period * 3 / 4 + 3 * clk_period + (sf - 2) * clk_period; + } else { + demap_delay = clk_period + (1 << sf) * clk_period * (1 - ppm / 4) + 3 * clk_period + (sf - 2 * ppm) * clk_period; + } + + fft_delay_state3 = clk_period * (((1 << sf) - 6) + 2 * ((1 << sf) * (nb_iter - 1) + 6)) + 4 * clk_period; + + if (dft_peak_en) { + fft_delay = (5 - 2 * ppm) * ((1 << sf) * clk_period + 7 * clk_period) + 2 * clk_period; + } else { + fft_delay = (1 << sf) * 2 * clk_period + 3 * clk_period; + } + + decode_delay = 5 * clk_period + (9 * clk_period + clk_period * cr) * nb_nibble_in_last_block + 3 * clk_period; + timestamp_correction = (uint32_t)(delay_x + fft_delay_state3 + fft_delay + demap_delay + decode_delay + 0.5e6) / 1e6; + //printf("INFO: timestamp_correction = %u us (delay_x %u, fft_delay_state3=%u, fft_delay=%u, demap_delay=%u, decode_delay = %u)\n", timestamp_correction, delay_x, fft_delay_state3, fft_delay, demap_delay, decode_delay); + } + else + { + timestamp_correction = 0; + DEBUG_MSG("WARNING: invalid packet, no timestamp correction\n"); + } + + return timestamp_correction; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/tst/test_loragw_cal.c b/libloragw/tst/test_loragw_cal.c new file mode 100644 index 0000000..6a69904 --- /dev/null +++ b/libloragw/tst/test_loragw_cal.c @@ -0,0 +1,698 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Minimum test program for HAL calibration + +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 +#include +#include +#include +#include +#include +#include /* sigaction */ +#include /* getopt_long */ +#include + +#include "loragw_hal.h" +#include "loragw_reg.h" +#include "loragw_sx1302.h" +#include "loragw_sx125x.h" +#include "loragw_aux.h" +#include "loragw_cal.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define RAND_RANGE(min, max) (rand() % (max + 1 - min) + min) + +#define DEBUG_MSG(str) fprintf(stderr, str) +#define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" + +#define DEFAULT_CLK_SRC 0 +#define DEFAULT_FREQ_HZ 868500000U + +#define DEFAULT_DAC_GAIN 3 +#define DEFAULT_MIX_GAIN 15 + +#define CAL_TX_TONE_FREQ_HZ 250000 +#define CAL_DEC_GAIN 8 +#define CAL_SIG_ANA_DURATION 0 /* correlation duration: 0:1, 1:2, 2:4, 3:8 ms) */ + +#define TEST_FREQ_SCAN 0 +#define TEST_OFFSET_IQ 1 +#define TEST_AMP_PHI 2 + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE TYPES -------------------------------------------------------- */ +struct cal_tx_log { + int32_t mean; + int32_t i_offset; + int32_t q_offset; +}; + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +FILE * fp; + +static uint32_t rf_rx_freq[LGW_RF_CHAIN_NB] = {865500000, 865500000}; +static lgw_radio_type_t rf_radio_type[LGW_RF_CHAIN_NB] = {LGW_RADIO_TYPE_SX1257, LGW_RADIO_TYPE_SX1257}; +static struct lgw_tx_gain_lut_s txlut; /* TX gain table */ + +/* Signal handling variables */ +static int exit_sig = 0; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */ +static int quit_sig = 0; /* 1 -> application terminates without shutting down the hardware */ + +#include "../src/cal_fw.var" /* text_cal_sx1257_16_Nov_1 */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ + +/* describe command line options */ +void usage(void) { + //printf("Library version information: %s\n", lgw_version_info()); + printf("Available options:\n"); + printf(" -h print this help\n"); + printf(" -d use Linux SPI device driver\n"); + printf(" => default path: " LINUXDEV_PATH_DEFAULT "\n"); + printf(" -k Concentrator clock source (Radio A or Radio B) [0..1]\n"); + printf(" -c RF chain to be used for TX (Radio A or Radio B) [0..1]\n"); + printf(" -r Radio type (1255, 1257, 1250)\n"); + printf(" -f Radio TX frequency in MHz\n"); + printf( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" ); + printf(" --pa PA gain [0..3]\n"); + printf(" --dig sx1302 digital gain [0..3]\n"); + printf(" --dac sx1257 DAC gain [0..3]\n"); + printf(" --mix sx1257 MIX gain [0..15]\n"); +} + +/* handle signals */ +static void sig_handler(int sigio) +{ + if (sigio == SIGQUIT) { + quit_sig = 1; + } + else if((sigio == SIGINT) || (sigio == SIGTERM)) { + exit_sig = 1; + } +} + +int setup_tx_dc_offset(uint8_t rf_chain, uint32_t freq_hz, uint8_t dac_gain, uint8_t mix_gain, uint8_t radio_type) { + uint32_t rx_freq_hz, tx_freq_hz; + uint32_t rx_freq_int, rx_freq_frac; + uint32_t tx_freq_int, tx_freq_frac; + uint8_t rx_pll_locked, tx_pll_locked; + + /* Set PLL frequencies */ + rx_freq_hz = freq_hz - CAL_TX_TONE_FREQ_HZ; + tx_freq_hz = freq_hz; + switch (radio_type) { + case LGW_RADIO_TYPE_SX1255: + rx_freq_int = rx_freq_hz / (SX125x_32MHz_FRAC << 7); /* integer part, gives the MSB */ + rx_freq_frac = ((rx_freq_hz % (SX125x_32MHz_FRAC << 7)) << 9) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ + tx_freq_int = tx_freq_hz / (SX125x_32MHz_FRAC << 7); /* integer part, gives the MSB */ + tx_freq_frac = ((tx_freq_hz % (SX125x_32MHz_FRAC << 7)) << 9) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ + break; + case LGW_RADIO_TYPE_SX1257: + rx_freq_int = rx_freq_hz / (SX125x_32MHz_FRAC << 8); /* integer part, gives the MSB */ + rx_freq_frac = ((rx_freq_hz % (SX125x_32MHz_FRAC << 8)) << 8) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ + tx_freq_int = tx_freq_hz / (SX125x_32MHz_FRAC << 8); /* integer part, gives the MSB */ + tx_freq_frac = ((tx_freq_hz % (SX125x_32MHz_FRAC << 8)) << 8) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ + break; + default: + DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", radio_type); + return LGW_HAL_ERROR; + } + lgw_sx125x_reg_w(SX125x_REG_FRF_RX_MSB, 0xFF & rx_freq_int, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_FRF_RX_MID, 0xFF & (rx_freq_frac >> 8), rf_chain); + lgw_sx125x_reg_w(SX125x_REG_FRF_RX_LSB, 0xFF & rx_freq_frac, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_FRF_TX_MSB, 0xFF & tx_freq_int, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_FRF_TX_MID, 0xFF & (tx_freq_frac >> 8), rf_chain); + lgw_sx125x_reg_w(SX125x_REG_FRF_TX_LSB, 0xFF & tx_freq_frac, rf_chain); + + /* Radio settings for calibration */ + //lgw_sx125x_reg_w(SX125x_RX_ANA_GAIN__LNA_ZIN, 1, rf_chain); /* Default: 1 */ + //lgw_sx125x_reg_w(SX125x_RX_ANA_GAIN__BB_GAIN, 15, rf_chain); /* Default: 15 */ + //lgw_sx125x_reg_w(SX125x_RX_ANA_GAIN__LNA_GAIN, 1, rf_chain); /* Default: 1 */ + lgw_sx125x_reg_w(SX125x_REG_RX_BW__BB_BW, 0, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_RX_BW__ADC_TRIM, 6, rf_chain); + //lgw_sx125x_reg_w(SX125x_RX_BW__ADC_BW, 7, rf_chain); /* Default: 7 */ + lgw_sx125x_reg_w(SX125x_REG_RX_PLL_BW__PLL_BW, 0, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_TX_BW__PLL_BW, 0, rf_chain); + //lgw_sx125x_reg_w(SX125x_TX_BW__ANA_BW, 0, rf_chain); /* Default: 0 */ + lgw_sx125x_reg_w(SX125x_REG_TX_DAC_BW, 5, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_CLK_SELECT__DAC_CLK_SELECT, 1, rf_chain); /* Use external clock from SX1302 */ + lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__DAC_GAIN, dac_gain, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__MIX_GAIN, mix_gain, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_CLK_SELECT__RF_LOOPBACK_EN, 1, rf_chain); + lgw_sx125x_reg_w(SX125x_REG_MODE, 15, rf_chain); + wait_ms(1); + lgw_sx125x_reg_r(SX125x_REG_MODE_STATUS__RX_PLL_LOCKED, &rx_pll_locked, rf_chain); + lgw_sx125x_reg_r(SX125x_REG_MODE_STATUS__TX_PLL_LOCKED, &tx_pll_locked, rf_chain); + if ((rx_pll_locked == 0) || (tx_pll_locked == 0)) { + DEBUG_MSG("ERROR: PLL failed to lock\n"); + return LGW_HAL_ERROR; + } + + return 0; +} + +int cal_tx_dc_offset(uint8_t test_id, uint8_t rf_chain, uint32_t freq_hz, uint8_t dac_gain, uint8_t mix_gain, uint8_t radio_type, int32_t f_offset, int32_t i_offset, int32_t q_offset, bool full_log, bool use_agc, uint8_t amp, uint8_t phi) { + int i; + uint16_t reg; + int32_t val_min, val_max; + int32_t acc; + int32_t val_mean; + float val_std; + float acc2 = 0 ; + int loop_len = 3; + float res_sig[loop_len]; + struct timeval start, stop; + + //DEBUG_MSG("\n"); + //DEBUG_PRINTF("rf_chain:%u, freq_hz:%u, dac_gain:%u, mix_gain:%u, radio_type:%d\n", rf_chain, freq_hz, dac_gain, mix_gain, radio_type); + + if (setup_tx_dc_offset(rf_chain, freq_hz, dac_gain, mix_gain, radio_type) != LGW_HAL_SUCCESS) { + return LGW_HAL_ERROR; + } + + /* Trig calibration */ + + /* Select radio to be connected to the Signal Analyzer (warning: RadioA:1, RadioB:0) */ + lgw_reg_w(SX1302_REG_RADIO_FE_SIG_ANA_CFG_RADIO_SEL, (rf_chain == 0) ? 1 : 0); + + reg = REG_SELECT(rf_chain, SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_MODE, + SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_MODE); + lgw_reg_w(reg, 0); + + reg = REG_SELECT(rf_chain, SX1302_REG_TX_TOP_A_TX_TRIG_TX_TRIG_IMMEDIATE, + SX1302_REG_TX_TOP_B_TX_TRIG_TX_TRIG_IMMEDIATE); + lgw_reg_w(reg, 1); + lgw_reg_w(reg, 0); + + reg = REG_SELECT(rf_chain, SX1302_REG_RADIO_FE_CTRL0_RADIO_A_DC_NOTCH_EN, + SX1302_REG_RADIO_FE_CTRL0_RADIO_B_DC_NOTCH_EN); + lgw_reg_w(reg, 1); + + /* Measuring */ + if (use_agc == true) { + uint8_t val_sig, val_sig2; + + /* Set calibration parameters */ + sx1302_agc_mailbox_write(2, rf_chain + 4); /* Sig ana test radio A/B */ + sx1302_agc_mailbox_write(1, f_offset/*(CAL_TX_TONE_FREQ_HZ + f_offset) * 64e-6*/); /* Set frequency */ + sx1302_agc_mailbox_write(0, CAL_SIG_ANA_DURATION); + + /* */ + sx1302_agc_mailbox_write(3, 0x00); + sx1302_agc_mailbox_write(3, 0x01); + sx1302_agc_wait_status(0x01); + + sx1302_agc_mailbox_write(2, amp); /* amp */ + sx1302_agc_mailbox_write(1, phi); /* phi */ + + sx1302_agc_mailbox_write(3, 0x02); + sx1302_agc_wait_status(0x02); + + sx1302_agc_mailbox_write(2, i_offset); /* i offset init */ + sx1302_agc_mailbox_write(1, q_offset); /* q offset init */ + + sx1302_agc_mailbox_write(3, 0x03); + sx1302_agc_wait_status(0x03); + + sx1302_agc_mailbox_write(2, CAL_DEC_GAIN); /* dec_gain */ + sx1302_agc_mailbox_write(2, 0); /* threshold (not used) */ + + sx1302_agc_mailbox_write(3, 0x04); + + reg = REG_SELECT(rf_chain, SX1302_REG_TX_TOP_A_TX_TRIG_TX_TRIG_IMMEDIATE, + SX1302_REG_TX_TOP_B_TX_TRIG_TX_TRIG_IMMEDIATE); + lgw_reg_w(reg, 0); + + gettimeofday (&start, NULL); + for (i = 0; i < loop_len; i++) { + sx1302_agc_wait_status(0x06); + sx1302_agc_mailbox_write(3, 0x06); + + sx1302_agc_wait_status(0x07); + sx1302_agc_mailbox_read(0, &val_sig); + sx1302_agc_mailbox_read(1, &val_sig2); + res_sig[i] = val_sig2 * 256 + val_sig; + + if (i == (loop_len - 1)) { + sx1302_agc_mailbox_write(3, 0x07); /* unlock */ + } else { + sx1302_agc_mailbox_write(3, 0x00); /* unlock */ + } + } + gettimeofday (&stop, NULL); + //printf("processing time: %ld us\n", ((stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec) - start.tv_usec); + } else { + int32_t val; + int32_t abs_lsb, abs_msb; + float abs_iq; + + reg = REG_SELECT(rf_chain, SX1302_REG_TX_TOP_A_TX_RFFE_IF_Q_OFFSET_Q_OFFSET, + SX1302_REG_TX_TOP_B_TX_RFFE_IF_Q_OFFSET_Q_OFFSET); + lgw_reg_w(reg, (int8_t)q_offset); + + reg = REG_SELECT(rf_chain, SX1302_REG_TX_TOP_A_TX_RFFE_IF_I_OFFSET_I_OFFSET, + SX1302_REG_TX_TOP_B_TX_RFFE_IF_I_OFFSET_I_OFFSET); + lgw_reg_w(reg, (int8_t)i_offset); + + reg = REG_SELECT(rf_chain, SX1302_REG_RADIO_FE_CTRL0_RADIO_A_DC_NOTCH_EN, + SX1302_REG_RADIO_FE_CTRL0_RADIO_B_DC_NOTCH_EN); + lgw_reg_w(reg, 1); + + reg = REG_SELECT(rf_chain, SX1302_REG_RADIO_FE_CTRL0_RADIO_A_FORCE_HOST_FILTER_GAIN, + SX1302_REG_RADIO_FE_CTRL0_RADIO_B_FORCE_HOST_FILTER_GAIN); + lgw_reg_w(reg, 0x01); + + reg = REG_SELECT(rf_chain, SX1302_REG_RADIO_FE_CTRL0_RADIO_A_HOST_FILTER_GAIN, + SX1302_REG_RADIO_FE_CTRL0_RADIO_B_HOST_FILTER_GAIN); + lgw_reg_w(reg, CAL_DEC_GAIN); + + lgw_reg_w(SX1302_REG_RADIO_FE_SIG_ANA_CFG_FORCE_HAL_CTRL, 1); + + lgw_reg_w(SX1302_REG_RADIO_FE_SIG_ANA_FREQ_FREQ, f_offset); + + lgw_reg_w(SX1302_REG_RADIO_FE_SIG_ANA_CFG_DURATION, CAL_SIG_ANA_DURATION); + lgw_reg_w(SX1302_REG_RADIO_FE_SIG_ANA_CFG_EN, 1); + + gettimeofday (&start, NULL); + for (i = 0; i < loop_len; i++) { + lgw_reg_w(SX1302_REG_RADIO_FE_SIG_ANA_CFG_START, 0); + lgw_reg_w(SX1302_REG_RADIO_FE_SIG_ANA_CFG_START, 1); + + do { + lgw_reg_r(SX1302_REG_RADIO_FE_SIG_ANA_CFG_VALID, &val); + wait_ms(1); + } while (val == 0); + + lgw_reg_r(SX1302_REG_RADIO_FE_SIG_ANA_ABS_LSB_CORR_ABS_OUT, &abs_lsb); + lgw_reg_r(SX1302_REG_RADIO_FE_SIG_ANA_ABS_MSB_CORR_ABS_OUT, &abs_msb); + abs_iq = (abs_msb << 8) | abs_lsb; + + res_sig[i] = abs_iq; + } + gettimeofday (&stop, NULL); + //printf("processing time: %ld us\n", ((stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec) - start.tv_usec); + + lgw_reg_w(SX1302_REG_RADIO_FE_SIG_ANA_CFG_FORCE_HAL_CTRL, 0); + } + + if (full_log == true) { + printf("i_offset:%d q_offset:%d f_offset:%d dac_gain:%d mix_gain:%d dec_gain:%d amp:%u phi:%u => ", i_offset, q_offset, f_offset, dac_gain, mix_gain, CAL_DEC_GAIN, amp, phi); + } else { + switch (test_id) { + case TEST_FREQ_SCAN: + fprintf(fp, "%u ", f_offset); + break; + case TEST_OFFSET_IQ: + fprintf(fp, "%d %d ", i_offset, q_offset); + break; + case TEST_AMP_PHI: + fprintf(fp, "%d %d ", amp, phi); + break; + default: + printf("ERROR: wrong test ID (%u)\n", test_id); + break; + } + } + + /* Analyze result */ + val_min = res_sig[0]; + val_max = res_sig[0]; + acc = 0; + for (i = 0; i < loop_len; i++) { + if (res_sig[i] > val_max) { + val_max = res_sig[i]; + } + if (res_sig[i] < val_min) { + val_min = res_sig[i]; + } + acc += res_sig[i]; + } + val_mean = acc / loop_len; + + for (i = 0; i < loop_len; i++) { + acc2 += pow((res_sig[i]-val_mean),2); + } + val_std = sqrt(acc2/loop_len); + + if (full_log == true) { + printf(" min:%u max:%u mean:%u std:%f\n", val_min, val_max, val_mean, val_std); + } else { + switch (test_id) { + case TEST_OFFSET_IQ: + case TEST_AMP_PHI: + fprintf(fp, "%u %u %u %f\n", val_min, val_max, val_mean, val_std); + break; + case TEST_FREQ_SCAN: + fprintf(fp, "%u\n", val_mean); + break; + default: + break; + } + } + + return LGW_HAL_SUCCESS; +} + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int test_freq_scan(uint8_t rf_chain, bool full_log, bool use_agc) { + int f; + + printf("-------------------------------------\n"); + for (f = 0; f < 256; f++) + { + cal_tx_dc_offset(TEST_FREQ_SCAN, rf_chain, rf_rx_freq[rf_chain], txlut.lut[0].dac_gain, txlut.lut[0].mix_gain, rf_radio_type[rf_chain], f, 0, 0, full_log, use_agc, 0, 0); + + if ((quit_sig == 1) || (exit_sig == 1)) { + break; + } + } + + return 0; +} + +int test_iq_offset(uint8_t rf_chain, uint8_t f_offset, bool full_log, bool use_agc) { + int i, q; + + printf("-------------------------------------\n"); + for (i = -128; i < 127; i+=8) + { + for (q = -128; q < 127; q+=8) + { + cal_tx_dc_offset(TEST_OFFSET_IQ, rf_chain, rf_rx_freq[rf_chain], txlut.lut[0].dac_gain, txlut.lut[0].mix_gain, rf_radio_type[rf_chain], f_offset, i, q, full_log, use_agc, 0, 0); + if ((quit_sig == 1) || (exit_sig == 1)) { + return 0; + } + } + } + + return 0; +} + +int test_amp_phi(uint8_t rf_chain, uint8_t f_offset, bool full_log, bool use_agc) { + int amp, phi; + + printf("-------------------------------------\n"); + for (amp = 0; amp < 64; amp++) + { + for (phi = 0; phi < 64; phi++) + { + cal_tx_dc_offset(TEST_AMP_PHI, rf_chain, rf_rx_freq[rf_chain], txlut.lut[0].dac_gain, txlut.lut[0].mix_gain, rf_radio_type[rf_chain], f_offset, 0, 0, full_log, use_agc, amp, phi); + if ((quit_sig == 1) || (exit_sig == 1)) { + return 0; + } + } + } + + return 0; +} + +int test_capture_ram(uint8_t rf_chain) { + uint16_t reg; + + setup_tx_dc_offset(rf_chain, rf_rx_freq[rf_chain], txlut.lut[0].dac_gain, txlut.lut[0].mix_gain, rf_radio_type[rf_chain]); + + reg = REG_SELECT(rf_chain, SX1302_REG_RADIO_FE_CTRL0_RADIO_A_DC_NOTCH_EN, + SX1302_REG_RADIO_FE_CTRL0_RADIO_B_DC_NOTCH_EN); + lgw_reg_w(reg, 1); + + printf("Waiting...\n"); + while ((quit_sig != 1) && (exit_sig != 1)) { + wait_ms(1000); + } + + return 0; +} + +int main(int argc, char **argv) +{ + int i, x; + uint32_t ft = DEFAULT_FREQ_HZ; + double arg_d = 0.0; + unsigned int arg_u; + uint8_t clocksource = 0; + uint8_t rf_chain = 0; + lgw_radio_type_t radio_type = LGW_RADIO_TYPE_NONE; + + struct lgw_conf_board_s boardconf; + struct lgw_conf_rxrf_s rfconf; + + static struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ + + /* SPI interfaces */ + const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; + const char * spidev_path = spidev_path_default; + + /* Initialize TX gain LUT */ + txlut.size = 1; + memset(txlut.lut, 0, sizeof txlut.lut); + txlut.lut[0].dac_gain = DEFAULT_DAC_GAIN; + txlut.lut[0].mix_gain = DEFAULT_MIX_GAIN; + + /* Parameter parsing */ + int option_index = 0; + static struct option long_options[] = { + {"dac", 1, 0, 0}, + {"mix", 1, 0, 0}, + {0, 0, 0, 0} + }; + + /* parse command line options */ + while ((i = getopt_long (argc, argv, "hf:k:r:c:d:", long_options, &option_index)) != -1) { + switch (i) { + case 'h': + usage(); + return -1; + break; + + case 'd': + if (optarg != NULL) { + spidev_path = optarg; + } + break; + + case 'r': /* Radio type */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || ((arg_u != 1255) && (arg_u != 1257) && (arg_u != 1250))) { + printf("ERROR: argument parsing of -r argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + switch (arg_u) { + case 1255: + radio_type = LGW_RADIO_TYPE_SX1255; + break; + case 1257: + radio_type = LGW_RADIO_TYPE_SX1257; + break; + default: /* 1250 */ + radio_type = LGW_RADIO_TYPE_SX1250; + break; + } + } + break; + + case 'k': /* Clock Source */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 1)) { + printf("ERROR: argument parsing of -k argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + clocksource = (uint8_t)arg_u; + } + break; + + case 'c': /* RF chain */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 1)) { + printf("ERROR: argument parsing of -c argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + rf_chain = (uint8_t)arg_u; + } + break; + + case 'f': /* Radio TX frequency in MHz */ + i = sscanf(optarg, "%lf", &arg_d); + if (i != 1) { + printf("ERROR: argument parsing of -f argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + ft = (uint32_t)((arg_d*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ + } + break; + + case 0: + if (strcmp(long_options[option_index].name, "dac") == 0) { + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 3)) { + printf("ERROR: argument parsing of --dac argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + txlut.size = 1; + txlut.lut[0].dac_gain = (uint8_t)arg_u; + } + } else if (strcmp(long_options[option_index].name, "mix") == 0) { + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 15)) { + printf("ERROR: argument parsing of --mix argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + txlut.size = 1; + txlut.lut[0].mix_gain = (uint8_t)arg_u; + } + } else { + printf("ERROR: argument parsing options. Use -h to print help\n"); + return EXIT_FAILURE; + } + break; + + default: + printf("ERROR: argument parsing\n"); + usage(); + return -1; + } + } + + /* Configure signal handling */ + sigemptyset( &sigact.sa_mask ); + sigact.sa_flags = 0; + sigact.sa_handler = sig_handler; + sigaction( SIGQUIT, &sigact, NULL ); + sigaction( SIGINT, &sigact, NULL ); + sigaction( SIGTERM, &sigact, NULL ); + + /* Board reset */ + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + + /* Configure the gateway */ + memset(&boardconf, 0, sizeof boardconf); + boardconf.lorawan_public = true; + boardconf.clksrc = clocksource; + boardconf.full_duplex = false; + strncpy(boardconf.spidev_path, spidev_path, sizeof boardconf.spidev_path); + if (lgw_board_setconf(&boardconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure board\n"); + return EXIT_FAILURE; + } + + memset(&rfconf, 0, sizeof rfconf); + rfconf.enable = ((rf_chain == 0) ? true : false); + rfconf.freq_hz = ft; + rfconf.type = radio_type; + rfconf.tx_enable = true; + if (lgw_rxrf_setconf(0, &rfconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure rxrf 0\n"); + return EXIT_FAILURE; + } + + memset(&rfconf, 0, sizeof rfconf); + rfconf.enable = ((rf_chain == 1) ? true : false); + rfconf.freq_hz = ft; + rfconf.type = radio_type; + rfconf.tx_enable = true; + if (lgw_rxrf_setconf(1, &rfconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure rxrf 1\n"); + return EXIT_FAILURE; + } + + if (txlut.size > 0) { + if (lgw_txgain_setconf(rf_chain, &txlut) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure txgain lut\n"); + return EXIT_FAILURE; + } + } + + /* open log file for writing */ + fp = fopen("log.txt", "w+"); + + /* connect the gateway */ + x = lgw_connect(spidev_path); + if (x != 0) { + printf("ERROR: failed to connect the gateway\n"); + return EXIT_FAILURE; + } + + sx1302_radio_reset(rf_chain, LGW_RADIO_TYPE_SX1257); + sx1302_radio_clock_select(clocksource); + sx1302_radio_set_mode(rf_chain, LGW_RADIO_TYPE_SX1257); + + printf("Loading CAL fw for sx125x\n"); + if (sx1302_agc_load_firmware(cal_firmware_sx125x) != LGW_HAL_SUCCESS) { + return LGW_HAL_ERROR; + } + + printf("waiting for capture ram\n"); + wait_ms(1000); + + /* testing */ + printf("testing: rf_chain:%u, dac_gain: %u, mix_gain:%u, dec_gain:%u, sig_ana_duration:%u\n", rf_chain, txlut.lut[0].dac_gain, txlut.lut[0].mix_gain, CAL_DEC_GAIN, CAL_SIG_ANA_DURATION); + + test_freq_scan(rf_chain, false, false); /* rf_chain, full_log, use_agc */ + /* gnuplot> plot 'log.txt' with lines */ + + //test_iq_offset(rf_chain, 16, false, false); /* rf_chain, f_offset, full_log, use_agc */ + + //test_amp_phi(rf_chain, 240, true, true); /* rf_chain, f_offset, full_log, use_agc */ + + //test_capture_ram(rf_chain); + + sx1302_radio_reset(0, LGW_RADIO_TYPE_SX1257); + sx1302_radio_reset(1, LGW_RADIO_TYPE_SX1257); + + /* disconnect the gateway */ + x = lgw_disconnect(); + if (x != 0) { + printf("ERROR: failed to disconnect the gateway\n"); + return EXIT_FAILURE; + } + + /* Close log file */ + fclose(fp); + + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + + printf("=========== Test End ===========\n"); + + return 0; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/tst/test_loragw_capture_ram.c b/libloragw/tst/test_loragw_capture_ram.c new file mode 100644 index 0000000..a8acba7 --- /dev/null +++ b/libloragw/tst/test_loragw_capture_ram.c @@ -0,0 +1,369 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Minimum test program to test the capture RAM block + +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 +#include /* printf */ +#include +#include /* sigaction */ +#include /* getopt_long */ + +#include "loragw_hal.h" +#include "loragw_reg.h" +#include "loragw_aux.h" +#include "loragw_sx1250.h" +#include "loragw_sx125x.h" +#include "loragw_sx1302.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define DEBUG_MSG(str) fprintf(stderr, str) + +#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" + +#define FULL_INIT 0 +#define CAPTURE_RAM_SIZE 0x4000 + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +/* Signal handling variables */ +static int exit_sig = 0; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */ +static int quit_sig = 0; /* 1 -> application terminates without shutting down the hardware */ + +uint32_t sampling_frequency[] = {4e6, 4e6, 4e6, 4e6, 4e6, 4e6, 4e6, 0, 0, 1e6, 125e3, 125e3, 125e3, 125e3, 125e3, 125e3, 125e3, 125e3, 8e6, 125e3, 125e3, 125e3, 0, 32e6, 32e6, 0, 32e6, 32e6, 0, 32e6, 32e6, 32e6}; + +#if FULL_INIT +#include "src/text_agc_sx1250_27_Nov_1.var" +#include "src/text_agc_sx1257_19_Nov_1.var" +#include "src/text_arb_sx1302_13_Nov_3.var" + +#define FW_VERSION_CAL 0 /* Expected version of calibration firmware */ /* TODO */ +#define FW_VERSION_AGC 1 /* Expected version of AGC firmware */ +#define FW_VERSION_ARB 1 /* Expected version of arbiter firmware */ + +static bool rf_enable[LGW_RF_CHAIN_NB]; +static uint32_t rf_rx_freq[LGW_RF_CHAIN_NB]; /* absolute, in Hz */ +static lgw_radio_type_t rf_radio_type[LGW_RF_CHAIN_NB]; +static uint8_t rf_clkout = 0; +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ + +/* describe command line options */ +void usage(void) +{ + printf("Available options:\n"); + printf(" -h print this help\n"); + printf(" -d use Linux SPI device driver\n"); + printf(" => default path: " LINUXDEV_PATH_DEFAULT "\n"); + printf(" -s Capture source [0..31]\n"); +} + +/* handle signals */ +static void sig_handler(int sigio) +{ + if (sigio == SIGQUIT) { + quit_sig = 1; + } + else if((sigio == SIGINT) || (sigio == SIGTERM)) { + exit_sig = 1; + } +} + +/* Main program */ +int main(int argc, char **argv) +{ + int i; + int32_t val = 0; + int reg_stat; + unsigned int arg_u; + uint8_t capture_source = 0; + uint16_t period_value = 0; + int16_t real = 0, imag = 0; +#if FULL_INIT + uint32_t val1, val2; +#endif + uint8_t capture_ram_buffer[CAPTURE_RAM_SIZE]; + + static struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ + + /* SPI interfaces */ + const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; + const char * spidev_path = spidev_path_default; + + /* Parameter parsing */ + int option_index = 0; + static struct option long_options[] = { + {0, 0, 0, 0} + }; + + /* parse command line options */ + while ((i = getopt_long (argc, argv, "h:s:d:", long_options, &option_index)) != -1) { + switch (i) { + case 'h': + usage(); + return -1; + break; + + case 'd': + if (optarg != NULL) { + spidev_path = optarg; + } + break; + + case 's': /* Capture Source */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 31)) { + printf("ERROR: argument parsing of -s argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + capture_source = arg_u; + } + break; + + default: + printf("ERROR: argument parsing\n"); + usage(); + return -1; + } + } + + /* Configure signal handling */ + sigemptyset( &sigact.sa_mask ); + sigact.sa_flags = 0; + sigact.sa_handler = sig_handler; + sigaction( SIGQUIT, &sigact, NULL ); + sigaction( SIGINT, &sigact, NULL ); + sigaction( SIGTERM, &sigact, NULL ); + +#if FULL_INIT + /* Board reset */ + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } +#endif + + /* Initialize memory for capture */ + for (i = 0; i < CAPTURE_RAM_SIZE; i++) { + capture_ram_buffer[i] = i%256; + } + + reg_stat = lgw_connect(spidev_path); + if (reg_stat == LGW_REG_ERROR) { + DEBUG_MSG("ERROR: FAIL TO CONNECT BOARD\n"); + return LGW_HAL_ERROR; + } + + /* Manual init */ +#if FULL_INIT + rf_radio_type[0] = LGW_RADIO_TYPE_SX1250; + rf_radio_type[1] = LGW_RADIO_TYPE_SX1257; + rf_enable[0] = false; + rf_enable[1] = true; + rf_clkout = 1; + rf_rx_freq[1] = 863700000; + + /* setup radios */ + for (i=0; i < 2; i++) + { + if (rf_enable[i] == true) { + sx1302_radio_reset(i, rf_radio_type[i]); + switch (radio_type) { + case LGW_RADIO_TYPE_SX1250: + sx1250_setup(i, rf_rx_freq[i]); + break; + case LGW_RADIO_TYPE_SX1255: + case LGW_RADIO_TYPE_SX1257: + sx125x_setup(i, rf_clkout, true, rf_radio_type[i], rf_rx_freq[i]); + break; + default: + DEBUG_MSG("ERROR: RADIO TYPE NOT SUPPORTED\n"); + return LGW_HAL_ERROR; + } + sx1302_radio_set_mode(i, radio_type); + } + } + + /* Select the radio which provides the clock to the sx1302 */ + sx1302_radio_clock_select(rf_clkout); + + /* Check that the SX1302 timestamp counter is running */ + lgw_get_instcnt(&val1); + lgw_get_instcnt(&val2); + if (val1 == val2) { + printf("ERROR: SX1302 timestamp counter is not running (val:%u)\n", (uint32_t)val1); + return -1; + } + + /* Configure Radio FE */ + sx1302_radio_fe_configure(); + + /* give radio control to AGC MCU */ + lgw_reg_w(SX1302_REG_COMMON_CTRL0_HOST_RADIO_CTRL, 0x00); + + /* Load firmware */ + switch (rf_radio_type[rf_clkout]) { + case LGW_RADIO_TYPE_SX1250: + printf("Loading AGC fw for sx1250\n"); + if (sx1302_agc_load_firmware(agc_firmware_sx1250) != LGW_HAL_SUCCESS) { + return LGW_HAL_ERROR; + } + if (sx1302_agc_start(FW_VERSION_AGC, SX1302_RADIO_TYPE_SX1250, SX1302_AGC_RADIO_GAIN_AUTO, SX1302_AGC_RADIO_GAIN_AUTO, 0) != LGW_HAL_SUCCESS) { + return LGW_HAL_ERROR; + } + break; + case LGW_RADIO_TYPE_SX1257: + printf("Loading AGC fw for sx125x\n"); + if (sx1302_agc_load_firmware(agc_firmware_sx125x) != LGW_HAL_SUCCESS) { + return LGW_HAL_ERROR; + } + if (sx1302_agc_start(FW_VERSION_AGC, SX1302_RADIO_TYPE_SX125X, SX1302_AGC_RADIO_GAIN_AUTO, SX1302_AGC_RADIO_GAIN_AUTO, 0) != LGW_HAL_SUCCESS) { + // if (sx1302_agc_start(FW_VERSION_AGC, SX1302_RADIO_TYPE_SX125X, 1, 7, 0) != LGW_HAL_SUCCESS) { + return LGW_HAL_ERROR; + } + break; + default: + break; + } + printf("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; + } +#endif + + // lgw_reg_w(SX1302_REG_CAPTURE_RAM_CLOCK_GATE_OVERRIDE_CLK_OVERRIDE, 3); + + /* Configure the Capture Ram block */ + lgw_reg_w(SX1302_REG_CAPTURE_RAM_CAPTURE_CFG_ENABLE, 1); /* Enable Capture RAM */ + lgw_reg_w(SX1302_REG_CAPTURE_RAM_CAPTURE_CFG_CAPTUREWRAP, 0); /* Capture once, and stop when memory is full */ + lgw_reg_w(SX1302_REG_CAPTURE_RAM_CAPTURE_CFG_RAMCONFIG, 0); /* RAM configuration, 0: 4kx32, 1: 2kx64 */ + fprintf(stdout, "Capture source: %d\n", capture_source); + lgw_reg_w(SX1302_REG_CAPTURE_RAM_CAPTURE_SOURCE_A_SOURCEMUX, capture_source); + + printf("Sampling frequency: %d\n", sampling_frequency[capture_source]); + if (sampling_frequency[capture_source] != 0) + { + period_value = (32e6/sampling_frequency[capture_source]) - 1; + } + else + { + fprintf(stderr ,"ERROR: Sampling frequency is null\n"); + return -1; + } + + // fprintf(stdout, "period_value=%04X\n", period_value); + lgw_reg_w(SX1302_REG_CAPTURE_RAM_CAPTURE_PERIOD_0_CAPTUREPERIOD, period_value & 0xFF); // LSB + lgw_reg_w(SX1302_REG_CAPTURE_RAM_CAPTURE_PERIOD_1_CAPTUREPERIOD, (period_value>>8) & 0xFF); // MSB + + /* Read back registers */ + // lgw_reg_r(SX1302_REG_CAPTURE_RAM_CAPTURE_PERIOD_0_CAPTUREPERIOD, &val); + // fprintf(stdout, "SX1302_REG_CAPTURE_RAM_CAPTURE_PERIOD_0_CAPTUREPERIOD value: %d\n", val); + // lgw_reg_r(SX1302_REG_CAPTURE_RAM_CAPTURE_PERIOD_1_CAPTUREPERIOD, &val); + // fprintf(stdout, "SX1302_REG_CAPTURE_RAM_CAPTURE_PERIOD_1_CAPTUREPERIOD value: %d\n", val); + + /* Launch capture */ + lgw_reg_w(SX1302_REG_CAPTURE_RAM_CAPTURE_CFG_CAPTURESTART, 1); + // lgw_reg_w(SX1302_REG_CAPTURE_RAM_CAPTURE_CFG_CAPTUREFORCETRIGGER, 1); + + /* Poll Status.CapComplete */ + do{ + lgw_reg_r(SX1302_REG_CAPTURE_RAM_STATUS_CAPCOMPLETE, &val); + + wait_ms(10); + if ((quit_sig == 1) || (exit_sig == 1)) { + break; + } + } while (val != 1); + lgw_reg_w(SX1302_REG_CAPTURE_RAM_CAPTURE_CFG_CAPTURESTART, 0); + + + // lgw_reg_r(SX1302_REG_CAPTURE_RAM_LAST_RAM_ADDR_0_LASTRAMADDR, &val); + // fprintf(stdout, "SX1302_REG_CAPTURE_RAM_LAST_RAM_ADDR_0_LASTRAMADDR value: %02x\n", val); + // lgw_reg_r(SX1302_REG_CAPTURE_RAM_LAST_RAM_ADDR_1_LASTRAMADDR, &val); + // fprintf(stdout, "SX1302_REG_CAPTURE_RAM_LAST_RAM_ADDR_1_LASTRAMADDR value: %02x\n", val); + + lgw_reg_w(SX1302_REG_COMMON_PAGE_PAGE, 1); + lgw_mem_rb(0, capture_ram_buffer, CAPTURE_RAM_SIZE, false); + lgw_reg_w(SX1302_REG_COMMON_PAGE_PAGE, 0); + + printf("Data:\n"); + for (i = 0; i < CAPTURE_RAM_SIZE; i += 4) + { + if (((capture_source >= 2) && (capture_source <= 3)) || (capture_source == 9)) + { + real = (int16_t)((((uint16_t)(capture_ram_buffer[i+3]) << 8) & 0xFF00) + ((uint16_t)capture_ram_buffer[i+2] & 0x00FF)); + imag = (int16_t)((((uint16_t)(capture_ram_buffer[i+1]) << 8) & 0xFF00) + ((uint16_t)capture_ram_buffer[i+0] & 0x00FF)); + real >>= 4; // 12 bits I + imag >>= 4; // 12 bits Q + } + else if ((capture_source >= 4) && (capture_source <= 6)) + { + real = (int16_t)((((uint16_t)(capture_ram_buffer[i+3]) << 8) & 0xFF00) + ((uint16_t)capture_ram_buffer[i+2] & 0x00FF)); // 16 bits I + imag = (int16_t)((((uint16_t)(capture_ram_buffer[i+1]) << 8) & 0xFF00) + ((uint16_t)capture_ram_buffer[i+0] & 0x00FF)); // 16 bits Q + } + else if ((capture_source >= 10) && (capture_source <= 17)) + { + real = (int8_t)(capture_ram_buffer[i+3]); // 8 bits I + imag = (int8_t)(capture_ram_buffer[i+1]); // 8 bits Q + } + else + { + real = 0; + imag = 0; + } + + if (((capture_source >= 2) && (capture_source <= 6)) || ((capture_source >= 9) && (capture_source <= 17))) + { + fprintf(stdout, "%d", real); + if (imag >= 0) + { + fprintf(stdout, "+"); + } + fprintf(stdout, "%di\n", imag); + } + else + { + printf("%02X ", capture_ram_buffer[i]); + } + } + printf("End of Data\n"); + +#if FULL_INIT + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } +#endif + + return 0; +} diff --git a/libloragw/tst/test_loragw_counter.c b/libloragw/tst/test_loragw_counter.c new file mode 100644 index 0000000..fd83e55 --- /dev/null +++ b/libloragw/tst/test_loragw_counter.c @@ -0,0 +1,257 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Minimum test program for HAL timestamp counter handling + +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 +#include +#include +#include +#include +#include +#include + +#include "loragw_hal.h" +#include "loragw_reg.h" +#include "loragw_aux.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#define RAND_RANGE(min, max) (rand() % (max + 1 - min) + min) + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define DEFAULT_FREQ_HZ 868500000U + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +static int exit_sig = 0; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */ +static int quit_sig = 0; /* 1 -> application terminates without shutting down the hardware */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ + +static void sig_handler(int sigio) { + if (sigio == SIGQUIT) { + quit_sig = 1;; + } else if ((sigio == SIGINT) || (sigio == SIGTERM)) { + exit_sig = 1; + } +} + +void usage(void) { + //printf("Library version information: %s\n", lgw_version_info()); + printf( "Available options:\n"); + printf( " -h print this help\n"); + printf( " -k Concentrator clock source (Radio A or Radio B) [0..1]\n"); + printf( " -r Radio type (1255, 1257, 1250)\n"); + printf( " -p Test PPS trig counter when set\n" ); +} + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char **argv) +{ + /* SPI interfaces */ + const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; + const char * spidev_path = spidev_path_default; + + struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ + + int i, x; + uint32_t fa = DEFAULT_FREQ_HZ; + uint32_t fb = DEFAULT_FREQ_HZ; + unsigned int arg_u; + uint8_t clocksource = 0; + lgw_radio_type_t radio_type = LGW_RADIO_TYPE_NONE; + + struct lgw_conf_board_s boardconf; + struct lgw_conf_rxrf_s rfconf; + struct lgw_conf_rxif_s ifconf; + + uint32_t counter; + bool trig_cnt = false; + + const int32_t channel_if[9] = { + -400000, + -200000, + 0, + -400000, + -200000, + 0, + 200000, + 400000, + -200000 /* lora service */ + }; + + const uint8_t channel_rfchain[9] = { 1, 1, 1, 0, 0, 0, 0, 0, 1 }; + + /* parse command line options */ + while ((i = getopt (argc, argv, "hk:r:p")) != -1) { + switch (i) { + case 'h': + usage(); + return -1; + break; + case 'r': /* Radio type */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || ((arg_u != 1255) && (arg_u != 1257) && (arg_u != 1250))) { + printf("ERROR: argument parsing of -r argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + switch (arg_u) { + case 1255: + radio_type = LGW_RADIO_TYPE_SX1255; + break; + case 1257: + radio_type = LGW_RADIO_TYPE_SX1257; + break; + default: /* 1250 */ + radio_type = LGW_RADIO_TYPE_SX1250; + break; + } + } + break; + case 'k': /* Clock Source */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 1)) { + printf("ERROR: argument parsing of -k argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + clocksource = (uint8_t)arg_u; + } + break; + case 'p': + trig_cnt = true; + break; + default: + printf("ERROR: argument parsing\n"); + usage(); + return -1; + } + } + + /* configure signal handling */ + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigact.sa_handler = sig_handler; + sigaction(SIGQUIT, &sigact, NULL); + sigaction(SIGINT, &sigact, NULL); + sigaction(SIGTERM, &sigact, NULL); + + printf("===== sx1302 counter test =====\n"); + + /* Board reset */ + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + + /* Configure the gateway */ + memset( &boardconf, 0, sizeof boardconf); + boardconf.lorawan_public = true; + boardconf.clksrc = clocksource; + boardconf.full_duplex = false; + strncpy(boardconf.spidev_path, spidev_path, sizeof boardconf.spidev_path); + if (lgw_board_setconf(&boardconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure board\n"); + return EXIT_FAILURE; + } + + /* set configuration for RF chains */ + memset( &rfconf, 0, sizeof rfconf); + rfconf.enable = true; + rfconf.freq_hz = fa; + rfconf.type = radio_type; + rfconf.tx_enable = false; + if (lgw_rxrf_setconf(0, &rfconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure rxrf 0\n"); + return EXIT_FAILURE; + } + + memset( &rfconf, 0, sizeof rfconf); + rfconf.enable = true; + rfconf.freq_hz = fb; + rfconf.type = radio_type; + rfconf.tx_enable = false; + if (lgw_rxrf_setconf(1, &rfconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure rxrf 1\n"); + return EXIT_FAILURE; + } + + /* set configuration for LoRa multi-SF channels (bandwidth cannot be set) */ + memset(&ifconf, 0, sizeof(ifconf)); + for (i = 0; i < 9; i++) { + ifconf.enable = true; + ifconf.rf_chain = channel_rfchain[i]; + ifconf.freq_hz = channel_if[i]; + ifconf.datarate = DR_LORA_SF7; + if (lgw_rxif_setconf(i, &ifconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure rxif %d\n", i); + return EXIT_FAILURE; + } + } + + /* connect, configure and start the LoRa concentrator */ + x = lgw_start(); + if (x != 0) { + printf("ERROR: failed to start the gateway\n"); + return EXIT_FAILURE; + } + + /* Loop until user quits */ + while( (quit_sig != 1) && (exit_sig != 1) ) { + if (trig_cnt == false) { + lgw_get_instcnt(&counter); + } else { + lgw_get_trigcnt(&counter); + } + wait_ms(10); + } + + /* Stop the gateway */ + x = lgw_stop(); + if (x != 0) { + printf("ERROR: failed to stop the gateway\n"); + return EXIT_FAILURE; + } + + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + + printf("=========== Test End ===========\n"); + + return 0; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/tst/test_loragw_gps.c b/libloragw/tst/test_loragw_gps.c new file mode 100644 index 0000000..d3f4fe5 --- /dev/null +++ b/libloragw/tst/test_loragw_gps.c @@ -0,0 +1,415 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Minimum test program for the loragw_gps module + +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 */ +#include /* memset */ +#include /* sigaction */ +#include /* exit */ +#include /* read */ + +#include "loragw_hal.h" +#include "loragw_gps.h" +#include "loragw_aux.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define MATCH(a,b) ( ((int32_t)(a-b)<=1) && ((int32_t)(a-b)>=-1) ) /* tolerate 1µs */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +static int exit_sig = 0; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */ +static int quit_sig = 0; /* 1 -> application terminates without shutting down the hardware */ + +struct tref ppm_ref; + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ + +static void sig_handler(int sigio); +static void gps_process_sync(void); +static void gps_process_coords(void); + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +void usage(void) { + //printf("Library version information: %s\n", lgw_version_info()); + printf( "Available options:\n"); + printf( " -h print this help\n"); + printf( " -k Concentrator clock source (Radio A or Radio B) [0..1]\n"); + printf( " -r Radio type (1255, 1257, 1250)\n"); +} + +static void sig_handler(int sigio) { + if (sigio == SIGQUIT) { + quit_sig = 1;; + } else if ((sigio == SIGINT) || (sigio == SIGTERM)) { + exit_sig = 1; + } +} + +static void gps_process_sync(void) { + /* variables for PPM pulse GPS synchronization */ + uint32_t ppm_tstamp; + struct timespec ppm_gps; + struct timespec ppm_utc; + + /* variables for timestamp <-> GPS time conversions */ + uint32_t x, z; + struct timespec y; + + /* get GPS time for synchronization */ + int i = lgw_gps_get(&ppm_utc, &ppm_gps, NULL, NULL); + if (i != LGW_GPS_SUCCESS) { + printf(" No valid reference GPS time available, synchronization impossible.\n"); + return; + } + + /* get timestamp for synchronization */ + i = lgw_get_trigcnt(&ppm_tstamp); + if (i != LGW_HAL_SUCCESS) { + printf(" Failed to read timestamp, synchronization impossible.\n"); + return; + } + + /* try to update synchronize time reference with the new GPS & timestamp */ + i = lgw_gps_sync(&ppm_ref, ppm_tstamp, ppm_utc, ppm_gps); + if (i != LGW_GPS_SUCCESS) { + printf(" Synchronization error.\n"); + return; + } + + /* display result */ + printf(" * Synchronization successful *\n"); + printf(" UTC reference time: %lld.%09ld\n", (long long)ppm_ref.utc.tv_sec, ppm_ref.utc.tv_nsec); + printf(" GPS reference time: %lld.%09ld\n", (long long)ppm_ref.gps.tv_sec, ppm_ref.gps.tv_nsec); + printf(" Internal counter reference value: %u\n", ppm_ref.count_us); + printf(" Clock error: %.9f\n", ppm_ref.xtal_err); + + x = ppm_tstamp + 500000; + + /* CNT -> GPS -> CNT */ + printf("\n"); + printf(" * Test of timestamp counter <-> GPS value conversion *\n"); + printf(" Test value: %u\n", x); + lgw_cnt2gps(ppm_ref, x, &y); + printf(" Conversion to GPS: %lld.%09ld\n", (long long)y.tv_sec, y.tv_nsec); + lgw_gps2cnt(ppm_ref, y, &z); + printf(" Converted back: %u ==> %dµs\n", z, (int32_t)(z-x)); + /* Display test result */ + if (MATCH(x,z)) { + printf(" ** PASS **: (SX1302 -> GPS -> SX1302) conversion MATCH\n"); + } else { + printf(" ** FAILED **: (SX1302 -> GPS -> SX1302) conversion MISMATCH\n"); + } + + /* CNT -> UTC -> CNT */ + printf("\n"); + printf(" * Test of timestamp counter <-> UTC value conversion *\n"); + printf(" Test value: %u\n", x); + lgw_cnt2utc(ppm_ref, x, &y); + printf(" Conversion to UTC: %lld.%09ld\n", (long long)y.tv_sec, y.tv_nsec); + lgw_utc2cnt(ppm_ref, y, &z); + printf(" Converted back: %u ==> %dµs\n", z, (int32_t)(z-x)); + /* Display test result */ + if (MATCH(x,z)) { + printf(" ** PASS **: (SX1302 -> UTC -> SX1302) conversion MATCH\n"); + } else { + printf(" ** FAILED **: (SX1302 -> UTC -> SX1302) conversion MISMATCH\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 */ + if (i == LGW_GPS_SUCCESS) { + printf("\n"); + printf("# GPS coordinates: latitude %.5f, longitude %.5f, altitude %i m\n", coord.lat, coord.lon, coord.alt); + printf("# GPS err: latitude %.5f, longitude %.5f, altitude %i m\n", gpserr.lat, gpserr.lon, gpserr.alt); + } +} + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char **argv) +{ + /* SPI interfaces */ + const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; + const char * spidev_path = spidev_path_default; + + struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ + + int i; + unsigned int arg_u; + + /* concentrator variables */ + uint8_t clocksource = 0; + lgw_radio_type_t radio_type = LGW_RADIO_TYPE_NONE; + struct lgw_conf_board_s boardconf; + struct lgw_conf_rxrf_s rfconf; + + /* serial variables */ + char serial_buff[128]; /* buffer to receive GPS data */ + size_t wr_idx = 0; /* pointer to end of chars in buffer */ + int gps_tty_dev; /* file descriptor to the serial port of the GNSS module */ + + /* NMEA/UBX variables */ + enum gps_msg latest_msg; /* keep track of latest NMEA/UBX message parsed */ + + /* parse command line options */ + while ((i = getopt (argc, argv, "hk:r:")) != -1) { + switch (i) { + case 'h': + usage(); + return -1; + break; + case 'r': /* Radio type */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || ((arg_u != 1255) && (arg_u != 1257) && (arg_u != 1250))) { + printf("ERROR: argument parsing of -r argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + switch (arg_u) { + case 1255: + radio_type = LGW_RADIO_TYPE_SX1255; + break; + case 1257: + radio_type = LGW_RADIO_TYPE_SX1257; + break; + default: /* 1250 */ + radio_type = LGW_RADIO_TYPE_SX1250; + break; + } + } + break; + case 'k': /* Clock Source */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 1)) { + printf("ERROR: argument parsing of -k argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + clocksource = (uint8_t)arg_u; + } + break; + default: + printf("ERROR: argument parsing\n"); + usage(); + exit(EXIT_FAILURE); + } + } + + /* Check arguments */ + if (radio_type == LGW_RADIO_TYPE_NONE) { + printf("ERROR: radio type must be specified\n"); + usage(); + exit(EXIT_FAILURE); + } + + /* configure signal handling */ + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigact.sa_handler = sig_handler; + sigaction(SIGQUIT, &sigact, NULL); + sigaction(SIGINT, &sigact, NULL); + sigaction(SIGTERM, &sigact, NULL); + + /* Intro message and library information */ + printf("Beginning of test for loragw_gps.c\n"); + printf("*** Library version information ***\n%s\n***\n", lgw_version_info()); + + /* Board reset */ + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + + /* Open and configure GPS */ + i = lgw_gps_enable("/dev/ttyS0", "ubx7", 0, &gps_tty_dev); + if (i != LGW_GPS_SUCCESS) { + printf("ERROR: Failed to enable GPS\n"); + exit(EXIT_FAILURE); + } + + /* start concentrator (default conf for IoT SK) */ + /* board config */ + memset(&boardconf, 0, sizeof(boardconf)); + boardconf.lorawan_public = true; + boardconf.clksrc = clocksource; + boardconf.full_duplex = false; + strncpy(boardconf.spidev_path, spidev_path, sizeof boardconf.spidev_path); + if (lgw_board_setconf(&boardconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure board\n"); + return EXIT_FAILURE; + } + + /* set configuration for RF chains */ + memset( &rfconf, 0, sizeof rfconf); + rfconf.enable = true; + rfconf.freq_hz = 868000000; + rfconf.rssi_offset = 0.0; + rfconf.type = radio_type; + rfconf.tx_enable = false; + if (lgw_rxrf_setconf(0, &rfconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure rxrf 0\n"); + return EXIT_FAILURE; + } + + memset( &rfconf, 0, sizeof rfconf); + rfconf.enable = true; + rfconf.freq_hz = 868000000; + rfconf.rssi_offset = 0.0; + rfconf.type = radio_type; + rfconf.tx_enable = false; + if (lgw_rxrf_setconf(1, &rfconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure rxrf 1\n"); + return EXIT_FAILURE; + } + + /* start */ + if (lgw_start() != LGW_HAL_SUCCESS) { + printf("ERROR: IMPOSSIBLE TO START THE GATEWAY\n"); + exit(EXIT_FAILURE); + } + + /* initialize some variables before loop */ + memset(serial_buff, 0, sizeof serial_buff); + memset(&ppm_ref, 0, sizeof ppm_ref); + + /* loop until user action */ + while ((quit_sig != 1) && (exit_sig != 1)) { + size_t rd_idx = 0; + size_t frame_end_idx = 0; + + /* blocking non-canonical read on serial port */ + ssize_t nb_char = read(gps_tty_dev, serial_buff + wr_idx, LGW_GPS_MIN_MSG_SIZE); + if (nb_char <= 0) { + printf("WARNING: [gps] read() returned value %d\n", nb_char); + continue; + } + wr_idx += (size_t)nb_char; + + /******************************************* + * Scan buffer for UBX/NMEA sync chars and * + * attempt to decode frame if one is found * + *******************************************/ + while (rd_idx < wr_idx) { + size_t frame_size = 0; + + /* Scan buffer for UBX sync char */ + if (serial_buff[rd_idx] == LGW_GPS_UBX_SYNC_CHAR) { + + /*********************** + * Found UBX sync char * + ***********************/ + latest_msg = lgw_parse_ubx(&serial_buff[rd_idx], (wr_idx - rd_idx), &frame_size); + + if (frame_size > 0) { + if (latest_msg == INCOMPLETE) { + /* UBX header found but frame appears to be missing bytes */ + frame_size = 0; + } else if (latest_msg == INVALID) { + /* message header received but message appears to be corrupted */ + printf("WARNING: [gps] could not get a valid message from GPS (no time)\n"); + frame_size = 0; + } else if (latest_msg == UBX_NAV_TIMEGPS) { + printf("\n~~ UBX NAV-TIMEGPS sentence, triggering synchronization attempt ~~\n"); + gps_process_sync(); + } + } + } else if(serial_buff[rd_idx] == LGW_GPS_NMEA_SYNC_CHAR) { + /************************ + * Found NMEA sync char * + ************************/ + /* scan for NMEA end marker (LF = 0x0a) */ + char* nmea_end_ptr = memchr(&serial_buff[rd_idx],(int)0x0a, (wr_idx - rd_idx)); + + if (nmea_end_ptr) { + /* found end marker */ + frame_size = nmea_end_ptr - &serial_buff[rd_idx] + 1; + latest_msg = lgw_parse_nmea(&serial_buff[rd_idx], frame_size); + + if(latest_msg == INVALID || latest_msg == UNKNOWN) { + /* checksum failed */ + frame_size = 0; + } else if (latest_msg == NMEA_RMC) { /* Get location from RMC frames */ + gps_process_coords(); + } + } + } + + if (frame_size > 0) { + /* At this point message is a checksum verified frame + we're processed or ignored. Remove frame from buffer */ + rd_idx += frame_size; + frame_end_idx = rd_idx; + } else { + rd_idx++; + } + } /* ...for(rd_idx = 0... */ + + if (frame_end_idx) { + /* Frames have been processed. Remove bytes to end of last processed frame */ + memcpy(serial_buff,&serial_buff[frame_end_idx],wr_idx - frame_end_idx); + wr_idx -= frame_end_idx; + } /* ...for(rd_idx = 0... */ + + /* Prevent buffer overflow */ + if ((sizeof(serial_buff) - wr_idx) < LGW_GPS_MIN_MSG_SIZE) { + memcpy(serial_buff,&serial_buff[LGW_GPS_MIN_MSG_SIZE],wr_idx - LGW_GPS_MIN_MSG_SIZE); + wr_idx -= LGW_GPS_MIN_MSG_SIZE; + } + } + + /* clean up before leaving */ + if (exit_sig == 1) { + lgw_gps_disable(gps_tty_dev); + lgw_stop(); + } + + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + + printf("\nEnd of test for loragw_gps.c\n"); + exit(EXIT_SUCCESS); +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/tst/test_loragw_hal_rx.c b/libloragw/tst/test_loragw_hal_rx.c new file mode 100644 index 0000000..79d3ea2 --- /dev/null +++ b/libloragw/tst/test_loragw_hal_rx.c @@ -0,0 +1,320 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Minimum test program for HAL RX capability + +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 +#include +#include +#include +#include +#include +#include + +#include "loragw_hal.h" +#include "loragw_reg.h" +#include "loragw_aux.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#define RAND_RANGE(min, max) (rand() % (max + 1 - min) + min) + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define DEFAULT_FREQ_HZ 868500000U + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +static int exit_sig = 0; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */ +static int quit_sig = 0; /* 1 -> application terminates without shutting down the hardware */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ + +static void sig_handler(int sigio) { + if (sigio == SIGQUIT) { + quit_sig = 1; + } else if ((sigio == SIGINT) || (sigio == SIGTERM)) { + exit_sig = 1; + } +} + +void usage(void) { + //printf("Library version information: %s\n", lgw_version_info()); + printf( "Available options:\n"); + printf( " -h print this help\n"); + printf( " -k Concentrator clock source (Radio A or Radio B) [0..1]\n"); + printf( " -r Radio type (1255, 1257, 1250)\n"); + printf( " -a Radio A RX frequency in MHz\n"); + printf( " -b Radio B RX frequency in MHz\n"); + printf( " -n number of packet received with CRC OK for each HAL start/stop loop\n"); +} + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char **argv) +{ + /* SPI interfaces */ + const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; + const char * spidev_path = spidev_path_default; + + struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ + + int i, j, x; + uint32_t fa = DEFAULT_FREQ_HZ; + uint32_t fb = DEFAULT_FREQ_HZ; + double arg_d = 0.0; + unsigned int arg_u; + uint8_t clocksource = 0; + lgw_radio_type_t radio_type = LGW_RADIO_TYPE_NONE; + + struct lgw_conf_board_s boardconf; + struct lgw_conf_rxrf_s rfconf; + struct lgw_conf_rxif_s ifconf; + struct lgw_pkt_rx_s rxpkt[16]; + + unsigned long nb_pkt_crc_ok = 0, nb_loop = 1, cnt_loop; + int nb_pkt; + + const int32_t channel_if[9] = { + -400000, + -200000, + 0, + -400000, + -200000, + 0, + 200000, + 400000, + -200000 /* lora service */ + }; + + const uint8_t channel_rfchain[9] = { 1, 1, 1, 0, 0, 0, 0, 0, 1 }; + + /* parse command line options */ + while ((i = getopt (argc, argv, "ha:b:k:r:n:")) != -1) { + switch (i) { + case 'h': + usage(); + return -1; + break; + case 'r': /* Radio type */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || ((arg_u != 1255) && (arg_u != 1257) && (arg_u != 1250))) { + printf("ERROR: argument parsing of -r argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + switch (arg_u) { + case 1255: + radio_type = LGW_RADIO_TYPE_SX1255; + break; + case 1257: + radio_type = LGW_RADIO_TYPE_SX1257; + break; + default: /* 1250 */ + radio_type = LGW_RADIO_TYPE_SX1250; + break; + } + } + break; + case 'k': /* Clock Source */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 1)) { + printf("ERROR: argument parsing of -k argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + clocksource = (uint8_t)arg_u; + } + break; + case 'a': /* Radio A RX frequency in MHz */ + i = sscanf(optarg, "%lf", &arg_d); + if (i != 1) { + printf("ERROR: argument parsing of -f argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + fa = (uint32_t)((arg_d*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ + } + break; + case 'b': /* Radio B RX frequency in MHz */ + i = sscanf(optarg, "%lf", &arg_d); + if (i != 1) { + printf("ERROR: argument parsing of -f argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + fb = (uint32_t)((arg_d*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ + } + break; + case 'n': /* NUmber of packets to be received before exiting */ + i = sscanf(optarg, "%u", &arg_u); + if (i != 1) { + printf("ERROR: argument parsing of -n argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + nb_loop = arg_u; + } + break; + default: + printf("ERROR: argument parsing\n"); + usage(); + return -1; + } + } + + /* configure signal handling */ + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigact.sa_handler = sig_handler; + sigaction(SIGQUIT, &sigact, NULL); + sigaction(SIGINT, &sigact, NULL); + sigaction(SIGTERM, &sigact, NULL); + + printf("===== sx1302 HAL RX test =====\n"); + + /* Configure the gateway */ + memset( &boardconf, 0, sizeof boardconf); + boardconf.lorawan_public = true; + boardconf.clksrc = clocksource; + boardconf.full_duplex = false; + strncpy(boardconf.spidev_path, spidev_path, sizeof boardconf.spidev_path); + if (lgw_board_setconf(&boardconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure board\n"); + return EXIT_FAILURE; + } + + /* set configuration for RF chains */ + memset( &rfconf, 0, sizeof rfconf); + rfconf.enable = true; + rfconf.freq_hz = fa; + rfconf.type = radio_type; + rfconf.tx_enable = false; + if (lgw_rxrf_setconf(0, &rfconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure rxrf 0\n"); + return EXIT_FAILURE; + } + + memset( &rfconf, 0, sizeof rfconf); + rfconf.enable = true; + rfconf.freq_hz = fb; + rfconf.type = radio_type; + rfconf.tx_enable = false; + if (lgw_rxrf_setconf(1, &rfconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure rxrf 1\n"); + return EXIT_FAILURE; + } + + /* set configuration for LoRa multi-SF channels (bandwidth cannot be set) */ + memset(&ifconf, 0, sizeof(ifconf)); + for (i = 0; i < 9; i++) { + ifconf.enable = true; + ifconf.rf_chain = channel_rfchain[i]; + ifconf.freq_hz = channel_if[i]; + ifconf.datarate = DR_LORA_SF7; + if (lgw_rxif_setconf(i, &ifconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure rxif %d\n", i); + return EXIT_FAILURE; + } + } + + /* Loop until user quits */ + cnt_loop = 0; + while( (quit_sig != 1) && (exit_sig != 1) ) + { + cnt_loop += 1; + + /* Board reset */ + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + + /* connect, configure and start the LoRa concentrator */ + x = lgw_start(); + if (x != 0) { + printf("ERROR: failed to start the gateway\n"); + return EXIT_FAILURE; + } + + /* Loop until we have enough packets with CRC OK */ + printf("Waiting for packets...\n"); + nb_pkt_crc_ok = 0; + while ((nb_pkt_crc_ok < nb_loop) && (quit_sig != 1) && (exit_sig != 1)) { + /* fetch N packets */ + nb_pkt = lgw_receive(ARRAY_SIZE(rxpkt), rxpkt); + + if (nb_pkt == 0) { + wait_ms(10); + } else { + printf("Received %d packets\n", nb_pkt); + for (i = 0; i < nb_pkt; i++) { + if (rxpkt[i].status == STAT_CRC_OK) { + nb_pkt_crc_ok += 1; + } + printf("\n----- %s packet -----\n", (rxpkt[i].modulation == MOD_LORA) ? "LoRa" : "FSK"); + printf(" count_us: %u\n", rxpkt[i].count_us); + printf(" size: %u\n", rxpkt[i].size); + printf(" chan: %u\n", rxpkt[i].if_chain); + printf(" status: 0x%02X\n", rxpkt[i].status); + printf(" datr: %u\n", rxpkt[i].datarate); + printf(" codr: %u\n", rxpkt[i].coderate); + printf(" rf_chain %u\n", rxpkt[i].rf_chain); + printf(" freq_hz %u\n", rxpkt[i].freq_hz); + printf(" snr_avg: %.1f\n", rxpkt[i].snr); + printf(" rssi_chan:%.1f\n", rxpkt[i].rssic); + printf(" rssi_sig :%.1f\n", rxpkt[i].rssis); + printf(" crc: 0x%04X\n", rxpkt[i].crc); + for (j = 0; j < rxpkt[i].size; j++) { + printf("%02X ", rxpkt[i].payload[j]); + } + printf("\n"); + } + } + } + + printf( "\nNb valid packets received: %lu CRC OK (%lu)\n", nb_pkt_crc_ok, cnt_loop ); + + /* Stop the gateway */ + x = lgw_stop(); + if (x != 0) { + printf("ERROR: failed to stop the gateway\n"); + return EXIT_FAILURE; + } + + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + } + + printf("=========== Test End ===========\n"); + + return 0; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/tst/test_loragw_hal_tx.c b/libloragw/tst/test_loragw_hal_tx.c new file mode 100644 index 0000000..21027ef --- /dev/null +++ b/libloragw/tst/test_loragw_hal_tx.c @@ -0,0 +1,583 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Minimum test program for HAL TX capability + +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 +#include +#include +#include +#include +#include +#include /* sigaction */ +#include /* getopt_long */ + +#include "loragw_hal.h" +#include "loragw_reg.h" +#include "loragw_aux.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define RAND_RANGE(min, max) (rand() % (max + 1 - min) + min) + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" + +#define DEFAULT_CLK_SRC 0 +#define DEFAULT_FREQ_HZ 868500000U + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +/* Signal handling variables */ +static int exit_sig = 0; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */ +static int quit_sig = 0; /* 1 -> application terminates without shutting down the hardware */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ + +/* describe command line options */ +void usage(void) { + //printf("Library version information: %s\n", lgw_version_info()); + printf("Available options:\n"); + printf(" -h print this help\n"); + printf(" -k Concentrator clock source (Radio A or Radio B) [0..1]\n"); + printf(" -c RF chain to be used for TX (Radio A or Radio B) [0..1]\n"); + printf(" -r Radio type (1255, 1257, 1250)\n"); + printf(" -f Radio TX frequency in MHz\n"); + printf(" -m modulation type ['CW', 'LORA', 'FSK']\n"); + printf(" -o CW frequency offset from Radio TX frequency in kHz [-65..65]\n"); + printf(" -s LoRa datarate 0:random, [5..12]\n"); + printf(" -b LoRa bandwidth in khz 0:random, [125, 250, 500]\n"); + printf(" -l FSK/LoRa preamble length, [6..65535]\n"); + printf(" -d FSK frequency deviation in kHz [1:250]\n"); + printf(" -q FSK bitrate in kbps [0.5:250]\n"); + printf(" -n Number of packets to be sent\n"); + printf(" -z size of packets to be sent 0:random, [9..255]\n"); + printf(" -t TX mode timestamped with delay in ms. If delay is 0, TX mode GPS trigger\n"); + printf(" -p RF power in dBm\n"); + printf(" -i Send LoRa packet using inverted modulation polarity\n"); + printf( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" ); + printf(" --pa PA gain SX125x:[0..3], SX1250:[0,1]\n"); + printf(" --dig sx1302 digital gain for sx125x [0..3]\n"); + printf(" --dac sx125x DAC gain [0..3]\n"); + printf(" --mix sx125x MIX gain [5..15]\n"); + printf(" --pwid sx1250 power index [0..22]\n"); + printf( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" ); + printf(" --nhdr Send LoRa packet with implicit header\n"); + printf( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" ); + printf(" --loop Number of loops for HAL start/stop (HAL unitary test)\n"); +} + +/* handle signals */ +static void sig_handler(int sigio) +{ + if (sigio == SIGQUIT) { + quit_sig = 1; + } + else if((sigio == SIGINT) || (sigio == SIGTERM)) { + exit_sig = 1; + } +} + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char **argv) +{ + int i, x; + uint32_t ft = DEFAULT_FREQ_HZ; + int8_t rf_power = 0; + uint8_t sf = 0; + uint16_t bw_khz = 0; + uint32_t nb_pkt = 1; + unsigned int nb_loop = 1, cnt_loop; + uint8_t size = 0; + char mod[64] = "LORA"; + float br_kbps = 50; + uint8_t fdev_khz = 25; + int8_t freq_offset = 0; + double arg_d = 0.0; + unsigned int arg_u; + int arg_i; + char arg_s[64]; + float xf = 0.0; + uint8_t clocksource = 0; + uint8_t rf_chain = 0; + lgw_radio_type_t radio_type = LGW_RADIO_TYPE_NONE; + uint16_t preamble = 8; + bool invert_pol = false; + bool no_header = false; + + struct lgw_conf_board_s boardconf; + struct lgw_conf_rxrf_s rfconf; + struct lgw_pkt_tx_s pkt; + struct lgw_tx_gain_lut_s txlut; /* TX gain table */ + uint8_t tx_status; + uint32_t count_us; + uint32_t trig_delay_us = 1000000; + bool trig_delay = false; + + /* SPI interfaces */ + const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; + const char * spidev_path = spidev_path_default; + + static struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ + + /* Initialize TX gain LUT */ + txlut.size = 0; + memset(txlut.lut, 0, sizeof txlut.lut); + + /* Parameter parsing */ + int option_index = 0; + static struct option long_options[] = { + {"pa", required_argument, 0, 0}, + {"dac", required_argument, 0, 0}, + {"dig", required_argument, 0, 0}, + {"mix", required_argument, 0, 0}, + {"pwid", required_argument, 0, 0}, + {"loop", required_argument, 0, 0}, + {"nhdr", no_argument, 0, 0}, + {0, 0, 0, 0} + }; + + /* parse command line options */ + while ((i = getopt_long (argc, argv, "hif:s:b:n:z:p:k:r:c:l:t:m:o:q:d:", long_options, &option_index)) != -1) { + switch (i) { + case 'h': + usage(); + return -1; + break; + case 'i': /* Send packet using inverted modulation polarity */ + invert_pol = true; + break; + case 'r': /* Radio type */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || ((arg_u != 1255) && (arg_u != 1257) && (arg_u != 1250))) { + printf("ERROR: argument parsing of -r argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + switch (arg_u) { + case 1255: + radio_type = LGW_RADIO_TYPE_SX1255; + break; + case 1257: + radio_type = LGW_RADIO_TYPE_SX1257; + break; + default: /* 1250 */ + radio_type = LGW_RADIO_TYPE_SX1250; + break; + } + } + break; + case 'l': /* LoRa/FSK preamble length */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 65535)) { + printf("ERROR: argument parsing of -l argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + preamble = (uint16_t)arg_u; + } + break; + case 'm': /* Modulation type */ + i = sscanf(optarg, "%s", arg_s); + if ((i != 1) || ((strcmp(arg_s, "CW") != 0) && (strcmp(arg_s, "LORA") != 0) && (strcmp(arg_s, "FSK")))) { + printf("ERROR: invalid modulation type\n"); + return EXIT_FAILURE; + } else { + sprintf(mod, "%s", arg_s); + } + break; + case 'o': /* CW frequency offset from Radio TX frequency */ + i = sscanf(optarg, "%d", &arg_i); + if ((arg_i < -65) || (arg_i > 65)) { + printf("ERROR: invalid frequency offset\n"); + return EXIT_FAILURE; + } else { + freq_offset = (int32_t)arg_i; + } + break; + case 'd': /* FSK frequency deviation */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u < 1) || (arg_u > 250)) { + printf("ERROR: invalid FSK frequency deviation\n"); + return EXIT_FAILURE; + } else { + fdev_khz = (uint8_t)arg_u; + } + break; + case 'q': /* FSK bitrate */ + i = sscanf(optarg, "%f", &xf); + if ((i != 1) || (xf < 0.5) || (xf > 250)) { + printf("ERROR: invalid FSK bitrate\n"); + return EXIT_FAILURE; + } else { + br_kbps = xf; + } + break; + case 't': /* Trigger delay in ms */ + i = sscanf(optarg, "%u", &arg_u); + if (i != 1) { + printf("ERROR: argument parsing of -t argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + trig_delay = true; + trig_delay_us = (uint32_t)(arg_u * 1E3); + } + break; + case 'k': /* Clock Source */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 1)) { + printf("ERROR: argument parsing of -k argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + clocksource = (uint8_t)arg_u; + } + break; + case 'c': /* RF chain */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 1)) { + printf("ERROR: argument parsing of -c argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + rf_chain = (uint8_t)arg_u; + } + break; + case 'f': /* Radio TX frequency in MHz */ + i = sscanf(optarg, "%lf", &arg_d); + if (i != 1) { + printf("ERROR: argument parsing of -f argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + ft = (uint32_t)((arg_d*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ + } + break; + case 's': /* LoRa datarate */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u < 5) || (arg_u > 12)) { + printf("ERROR: argument parsing of -s argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + sf = (uint8_t)arg_u; + } + break; + case 'b': /* LoRa bandwidth in khz */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || ((arg_u != 125) && (arg_u != 250) && (arg_u != 500))) { + printf("ERROR: argument parsing of -b argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + bw_khz = (uint16_t)arg_u; + } + break; + case 'n': /* Number of packets to be sent */ + i = sscanf(optarg, "%u", &arg_u); + if (i != 1) { + printf("ERROR: argument parsing of -n argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + nb_pkt = (uint32_t)arg_u; + } + break; + case 'p': /* RF power */ + i = sscanf(optarg, "%d", &arg_i); + if (i != 1) { + printf("ERROR: argument parsing of -p argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + rf_power = (int8_t)arg_i; + txlut.size = 1; + txlut.lut[0].rf_power = rf_power; + } + break; + case 'z': /* packet size */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u < 9) || (arg_u > 255)) { + printf("ERROR: argument parsing of -z argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + size = (uint8_t)arg_u; + } + break; + case 0: + if (strcmp(long_options[option_index].name, "pa") == 0) { + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 3)) { + printf("ERROR: argument parsing of --pa argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + txlut.size = 1; + txlut.lut[0].pa_gain = (uint8_t)arg_u; + } + } else if (strcmp(long_options[option_index].name, "dac") == 0) { + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 3)) { + printf("ERROR: argument parsing of --dac argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + txlut.size = 1; + txlut.lut[0].dac_gain = (uint8_t)arg_u; + } + } else if (strcmp(long_options[option_index].name, "mix") == 0) { + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 15)) { + printf("ERROR: argument parsing of --mix argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + txlut.size = 1; + txlut.lut[0].mix_gain = (uint8_t)arg_u; + } + } else if (strcmp(long_options[option_index].name, "dig") == 0) { + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 3)) { + printf("ERROR: argument parsing of --dig argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + txlut.size = 1; + txlut.lut[0].dig_gain = (uint8_t)arg_u; + } + } else if (strcmp(long_options[option_index].name, "pwid") == 0) { + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 22)) { + printf("ERROR: argument parsing of --pwid argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + txlut.size = 1; + txlut.lut[0].mix_gain = 5; /* TODO: rework this, should not be needed for sx1250 */ + txlut.lut[0].pwr_idx = (uint8_t)arg_u; + } + } else if (strcmp(long_options[option_index].name, "loop") == 0) { + printf("%p\n", optarg); + i = sscanf(optarg, "%u", &arg_u); + if (i != 1) { + printf("ERROR: argument parsing of --loop argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + nb_loop = arg_u; + } + } else if (strcmp(long_options[option_index].name, "nhdr") == 0) { + no_header = true; + } else { + printf("ERROR: argument parsing options. Use -h to print help\n"); + return EXIT_FAILURE; + } + break; + default: + printf("ERROR: argument parsing\n"); + usage(); + return -1; + } + } + + /* Summary of packet parameters */ + if (strcmp(mod, "CW") == 0) { + printf("Sending %i CW on %u Hz (Freq. offset %d kHz) at %i dBm\n", nb_pkt, ft, freq_offset, rf_power); + } + else if (strcmp(mod, "FSK") == 0) { + printf("Sending %i FSK packets on %u Hz (FDev %u kHz, Bitrate %.2f, %i bytes payload, %i symbols preamble) at %i dBm\n", nb_pkt, ft, fdev_khz, br_kbps, size, preamble, rf_power); + } else { + printf("Sending %i LoRa packets on %u Hz (BW %i kHz, SF %i, CR %i, %i bytes payload, %i symbols preamble, %s header, %s polarity) at %i dBm\n", nb_pkt, ft, bw_khz, sf, 1, size, preamble, (no_header == false) ? "explicit" : "implicit", (invert_pol == false) ? "non-inverted" : "inverted", rf_power); + } + + /* Configure signal handling */ + sigemptyset( &sigact.sa_mask ); + sigact.sa_flags = 0; + sigact.sa_handler = sig_handler; + sigaction( SIGQUIT, &sigact, NULL ); + sigaction( SIGINT, &sigact, NULL ); + sigaction( SIGTERM, &sigact, NULL ); + + /* Configure the gateway */ + memset( &boardconf, 0, sizeof boardconf); + boardconf.lorawan_public = true; + boardconf.clksrc = clocksource; + boardconf.full_duplex = false; + strncpy(boardconf.spidev_path, spidev_path, sizeof boardconf.spidev_path); + if (lgw_board_setconf(&boardconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure board\n"); + return EXIT_FAILURE; + } + + memset( &rfconf, 0, sizeof rfconf); + rfconf.enable = true; /* rf chain 0 needs to be enabled for calibration to work on sx1257 */ + rfconf.freq_hz = 868500000; /* dummy */ + rfconf.type = radio_type; + rfconf.tx_enable = true; + if (lgw_rxrf_setconf(0, &rfconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure rxrf 0\n"); + return EXIT_FAILURE; + } + + memset( &rfconf, 0, sizeof rfconf); + rfconf.enable = (((rf_chain == 1) || (clocksource == 1)) ? true : false); + rfconf.freq_hz = 868500000; /* dummy */ + rfconf.type = radio_type; + rfconf.tx_enable = false; + if (lgw_rxrf_setconf(1, &rfconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure rxrf 1\n"); + return EXIT_FAILURE; + } + + if (txlut.size > 0) { + if (lgw_txgain_setconf(rf_chain, &txlut) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure txgain lut\n"); + return EXIT_FAILURE; + } + } + + for (cnt_loop = 0; cnt_loop < nb_loop; cnt_loop++) { + /* Board reset */ + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + + /* connect, configure and start the LoRa concentrator */ + x = lgw_start(); + if (x != 0) { + printf("ERROR: failed to start the gateway\n"); + return EXIT_FAILURE; + } + + /* Send packets */ + memset(&pkt, 0, sizeof pkt); + pkt.rf_chain = rf_chain; + pkt.freq_hz = ft; + pkt.rf_power = rf_power; + if (trig_delay == false) { + pkt.tx_mode = IMMEDIATE; + } else { + if (trig_delay_us == 0) { + pkt.tx_mode = ON_GPS; + } else { + pkt.tx_mode = TIMESTAMPED; + } + } + if ( strcmp( mod, "CW" ) == 0 ) { + pkt.modulation = MOD_CW; + pkt.freq_offset = freq_offset; + pkt.f_dev = fdev_khz; + } + else if( strcmp( mod, "FSK" ) == 0 ) { + pkt.modulation = MOD_FSK; + pkt.no_crc = false; + pkt.datarate = br_kbps * 1e3; + pkt.f_dev = fdev_khz; + } else { + pkt.modulation = MOD_LORA; + pkt.coderate = CR_LORA_4_5; + pkt.no_crc = true; + } + pkt.invert_pol = invert_pol; + pkt.preamble = preamble; + pkt.no_header = no_header; + pkt.payload[0] = 0x40; /* Confirmed Data Up */ + pkt.payload[1] = 0xAB; + pkt.payload[2] = 0xAB; + pkt.payload[3] = 0xAB; + pkt.payload[4] = 0xAB; + pkt.payload[5] = 0x00; /* FCTrl */ + pkt.payload[6] = 0; /* FCnt */ + pkt.payload[7] = 0; /* FCnt */ + pkt.payload[8] = 0x02; /* FPort */ + for (i = 9; i < 255; i++) { + pkt.payload[i] = i; + } + + for (i = 0; i < (int)nb_pkt; i++) { + if (trig_delay == true) { + if (trig_delay_us > 0) { + lgw_get_instcnt(&count_us); + printf("count_us:%u\n", count_us); + pkt.count_us = count_us + trig_delay_us; + printf("programming TX for %u\n", pkt.count_us); + } else { + printf("programming TX for next PPS (GPS)\n"); + } + } + + if( strcmp( mod, "LORA" ) == 0 ) { + pkt.datarate = (sf == 0) ? (uint8_t)RAND_RANGE(5, 12) : sf; + } + + switch (bw_khz) { + case 125: + pkt.bandwidth = BW_125KHZ; + break; + case 250: + pkt.bandwidth = BW_250KHZ; + break; + case 500: + pkt.bandwidth = BW_500KHZ; + break; + default: + pkt.bandwidth = (uint8_t)RAND_RANGE(BW_125KHZ, BW_500KHZ); + break; + } + + pkt.size = (size == 0) ? (uint8_t)RAND_RANGE(9, 255) : size; + + pkt.payload[6] = (uint8_t)(i >> 0); /* FCnt */ + pkt.payload[7] = (uint8_t)(i >> 8); /* FCnt */ + x = lgw_send(&pkt); + if (x != 0) { + printf("ERROR: failed to send packet\n"); + return EXIT_FAILURE; + } + /* wait for packet to finish sending */ + do { + wait_ms(5); + lgw_status(pkt.rf_chain, TX_STATUS, &tx_status); /* get TX status */ + } while ((tx_status != TX_FREE) && (quit_sig != 1) && (exit_sig != 1)); + + if ((quit_sig == 1) || (exit_sig == 1)) { + break; + } + printf("TX done\n"); + } + + printf( "\nNb packets sent: %u (%u)\n", i, cnt_loop + 1 ); + + /* Stop the gateway */ + x = lgw_stop(); + if (x != 0) { + printf("ERROR: failed to stop the gateway\n"); + return EXIT_FAILURE; + } + + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + } + + printf("=========== Test End ===========\n"); + + return 0; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/tst/test_loragw_i2c.c b/libloragw/tst/test_loragw_i2c.c new file mode 100644 index 0000000..e6e54af --- /dev/null +++ b/libloragw/tst/test_loragw_i2c.c @@ -0,0 +1,245 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Minimum test program for the loragw_i2c module + +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 +#include +#include +#include +#include /* sigaction */ +#include /* getopt, access */ +#include + +#include "loragw_i2c.h" +#include "loragw_aux.h" +#include "loragw_hal.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define I2C_PORT_STTS751 0x39 + +#define STTS751_REG_TEMP_H 0x00 +#define STTS751_REG_TEMP_L 0x02 +#define STTS751_REG_CONF 0x03 +#define STTS751_REG_RATE 0x04 +#define STTS751_REG_PROD_ID 0xFD +#define STTS751_REG_MAN_ID 0xFE +#define STTS751_REG_REV_ID 0xFF + +#define STTS751_0_PROD_ID 0x00 +#define STTS751_1_PROD_ID 0x01 +#define ST_MAN_ID 0x53 + +/* -------------------------------------------------------------------------- */ +/* --- GLOBAL VARIABLES ----------------------------------------------------- */ + +/* Signal handling variables */ +static int exit_sig = 0; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */ +static int quit_sig = 0; /* 1 -> application terminates without shutting down the hardware */ + +static int i2c_dev = -1; + +/* -------------------------------------------------------------------------- */ +/* --- SUBFUNCTIONS DECLARATION --------------------------------------------- */ + +static void sig_handler(int sigio); +static void usage(void); + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char ** argv) +{ + int i, err; + static struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ + uint8_t val; + uint8_t high_byte, low_byte; + int8_t h; + float temperature; + + /* Parse command line options */ + while ((i = getopt(argc, argv, "hd:")) != -1) { + switch (i) { + case 'h': + usage(); + return EXIT_SUCCESS; + break; + + case 'd': + if (optarg != NULL) { + /* TODO */ + } + break; + + default: + printf("ERROR: argument parsing options, use -h option for help\n"); + usage(); + return EXIT_FAILURE; + } + } + + /* Configure signal handling */ + sigemptyset( &sigact.sa_mask ); + sigact.sa_flags = 0; + sigact.sa_handler = sig_handler; + sigaction( SIGQUIT, &sigact, NULL ); + sigaction( SIGINT, &sigact, NULL ); + sigaction( SIGTERM, &sigact, NULL ); + + printf( "+++ Start of I2C test program +++\n" ); + + /* Open I2C port expander */ + err = i2c_linuxdev_open( I2C_DEVICE, I2C_PORT_STTS751, &i2c_dev ); + if ( (err != 0) || (i2c_dev <= 0) ) + { + printf( "ERROR: failed to open I2C device %s (err=%i)\n", I2C_DEVICE, err ); + return EXIT_FAILURE; + } + + /* Get temperature sensor product ID */ + err = i2c_linuxdev_read( i2c_dev, I2C_PORT_STTS751, STTS751_REG_PROD_ID, &val ); + if ( err != 0 ) + { + printf( "ERROR: failed to read I2C device %s (err=%i)\n", I2C_DEVICE, err ); + return EXIT_FAILURE;; + } + switch( val ) + { + case STTS751_0_PROD_ID: + printf("INFO: Product ID: STTS751-0\n"); + break; + case STTS751_1_PROD_ID: + printf("INFO: Product ID: STTS751-1\n"); + break; + default: + printf("ERROR: Product ID: UNKNOWN\n"); + return EXIT_FAILURE;; + } + + /* Get temperature sensor Manufacturer ID */ + err = i2c_linuxdev_read( i2c_dev, I2C_PORT_STTS751, STTS751_REG_MAN_ID, &val ); + if ( err != 0 ) + { + printf( "ERROR: failed to read I2C device %s (err=%i)\n", I2C_DEVICE, err ); + return EXIT_FAILURE;; + } + if ( val != ST_MAN_ID ) + { + printf( "ERROR: Manufacturer ID: UNKNOWN\n" ); + return EXIT_FAILURE;; + } + else + { + printf("INFO: Manufacturer ID: 0x%02X\n", val); + } + + /* Get temperature sensor revision number */ + err = i2c_linuxdev_read( i2c_dev, I2C_PORT_STTS751, STTS751_REG_REV_ID, &val ); + if ( err != 0 ) + { + printf( "ERROR: failed to read I2C device %s (err=%i)\n", I2C_DEVICE, err ); + return EXIT_FAILURE;; + } + printf("INFO: Revision number: 0x%02X\n", val); + + /* Set conversion resolution to 12 bits */ + err = i2c_linuxdev_write( i2c_dev, I2C_PORT_STTS751, STTS751_REG_CONF, 0x8C ); /* TODO: do not hardcode the whole byte */ + if ( err != 0 ) + { + printf( "ERROR: failed to write I2C device 0x%02X (err=%i)\n", I2C_PORT_STTS751, err ); + return EXIT_FAILURE; + } + + /* Set conversion rate to 1 / second */ + err = i2c_linuxdev_write( i2c_dev, I2C_PORT_STTS751, STTS751_REG_RATE, 0x04 ); + if ( err != 0 ) + { + printf( "ERROR: failed to write I2C device 0x%02X (err=%i)\n", I2C_PORT_STTS751, err ); + return EXIT_FAILURE; + } + + while ((quit_sig != 1) && (exit_sig != 1)) { + /* Read Temperature LSB */ + err = i2c_linuxdev_read( i2c_dev, I2C_PORT_STTS751, STTS751_REG_TEMP_L, &low_byte ); + if ( err != 0 ) + { + printf( "ERROR: failed to read I2C device 0x%02X (err=%i)\n", I2C_PORT_STTS751, err ); + return EXIT_FAILURE; + } + + /* Read Temperature MSB */ + err = i2c_linuxdev_read( i2c_dev, I2C_PORT_STTS751, STTS751_REG_TEMP_H, &high_byte ); + if ( err != 0 ) + { + printf( "ERROR: failed to read I2C device 0x%02X (err=%i)\n", I2C_PORT_STTS751, err ); + return EXIT_FAILURE; + } + + h = (int8_t)high_byte; + temperature = ((h << 8) | low_byte) / 256.0; + + printf( "Temperature: %f C (h:0x%02X l:0x%02X)\n", temperature, high_byte, low_byte ); + wait_ms( 100 ); + } + + /* Terminate */ + printf( "+++ End of I2C test program +++\n" ); + + err = i2c_linuxdev_close( i2c_dev ); + if ( err != 0 ) + { + printf( "ERROR: failed to close I2C device (err=%i)\n", err ); + return EXIT_FAILURE; + } + + return 0; +} + +/* -------------------------------------------------------------------------- */ +/* --- SUBFUNCTIONS DEFINITION ---------------------------------------------- */ + +static void sig_handler(int sigio) { + if (sigio == SIGQUIT) { + quit_sig = 1; + } else if((sigio == SIGINT) || (sigio == SIGTERM)) { + exit_sig = 1; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +static void usage(void) { + printf("~~~ Library version string~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); + printf(" %s\n", lgw_version_info()); + printf("~~~ Available options ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); + printf(" -h print this help\n"); + printf(" -d use Linux I2C device driver\n"); + printf(" => default path: " I2C_DEVICE "\n"); +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/tst/test_loragw_reg.c b/libloragw/tst/test_loragw_reg.c new file mode 100644 index 0000000..b5e32b9 --- /dev/null +++ b/libloragw/tst/test_loragw_reg.c @@ -0,0 +1,204 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Minimum test program for the loragw_reg module + +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 +#include +#include +#include +#include /* getopt, access */ +#include + +#include "loragw_reg.h" +#include "loragw_aux.h" +#include "loragw_hal.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +extern const struct lgw_reg_s loregs[LGW_TOTALREGS+1]; + +/* -------------------------------------------------------------------------- */ +/* --- SUBFUNCTIONS DECLARATION --------------------------------------------- */ + +static void usage(void); + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char ** argv) +{ + int x, i; + int32_t val; + bool error_found = false; + uint8_t rand_values[LGW_TOTALREGS]; + bool reg_ignored[LGW_TOTALREGS]; /* store register to be ignored */ + uint8_t reg_val; + uint8_t reg_max; + + /* SPI interfaces */ + const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; + const char * spidev_path = spidev_path_default; + + /* Parse command line options */ + while ((i = getopt(argc, argv, "hd:")) != -1) { + switch (i) { + case 'h': + usage(); + return EXIT_SUCCESS; + break; + + case 'd': + if (optarg != NULL) { + spidev_path = optarg; + } + break; + + default: + printf("ERROR: argument parsing options, use -h option for help\n"); + usage(); + return EXIT_FAILURE; + } + } + + /* Board reset */ + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + + x = lgw_connect(spidev_path); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: failed to connect\n"); + return -1; + } + + /* The following registers cannot be tested this way */ + memset(reg_ignored, 0, sizeof reg_ignored); + reg_ignored[SX1302_REG_COMMON_CTRL0_CLK32_RIF_CTRL] = true; /* all test fails if we set this one to 1 */ + + /* Test 1: read all registers and check default value for non-read-only registers */ + printf("## TEST#1: read all registers and check default value for non-read-only registers\n"); + error_found = false; + for (i = 0; i < LGW_TOTALREGS; i++) { + if (loregs[i].rdon == 0) { + x = lgw_reg_r(i, &val); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: failed to read register at index %d\n", i); + return -1; + } + if (val != loregs[i].dflt) { + printf("ERROR: default value for register at index %d is %d, should be %d\n", i, val, loregs[i].dflt); + error_found = true; + } + } + } + printf("------------------\n"); + printf(" TEST#1 %s\n", (error_found == false) ? "PASSED" : "FAILED"); + printf("------------------\n\n"); + + /* Test 2: read/write test on all non-read-only, non-pulse, non-w0clr, non-w1clr registers */ + printf("## TEST#2: read/write test on all non-read-only, non-pulse, non-w0clr, non-w1clr registers\n"); + /* Write all registers with a random value */ + error_found = false; + for (i = 0; i < LGW_TOTALREGS; i++) { + if ((loregs[i].rdon == 0) && (reg_ignored[i] == false)) { + /* Peek a random value different form the default reg value */ + reg_max = pow(2, loregs[i].leng) - 1; + if (loregs[i].leng == 1) { + reg_val = !loregs[i].dflt; + } else { + /* ensure random value is not the default one */ + do { + if (loregs[i].sign == 1) { + reg_val = rand() % (reg_max / 2); + } else { + reg_val = rand() % reg_max; + } + } while (reg_val == loregs[i].dflt); + } + /* Write selected value */ + x = lgw_reg_w(i, reg_val); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: failed to read register at index %d\n", i); + return -1; + } + /* store value for later check */ + rand_values[i] = reg_val; + } + } + /* Read all registers and check if we got proper random value back */ + for (i = 0; i < LGW_TOTALREGS; i++) { + if ((loregs[i].rdon == 0) && (loregs[i].chck == 1) && (reg_ignored[i] == false)) { + x = lgw_reg_r(i, &val); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: failed to read register at index %d\n", i); + return -1; + } + /* check value */ + if (val != rand_values[i]) { + printf("ERROR: value read from register at index %d differs from the written value (w:%u r:%d)\n", i, rand_values[i], val); + error_found = true; + } else { + //printf("INFO: MATCH reg %d (%u, %u)\n", i, rand_values[i], (uint8_t)val); + } + } + } + printf("------------------\n"); + printf(" TEST#2 %s\n", (error_found == false) ? "PASSED" : "FAILED"); + printf("------------------\n\n"); + + x = lgw_disconnect(); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: failed to disconnect\n"); + return -1; + } + + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + + return 0; +} + +/* -------------------------------------------------------------------------- */ +/* --- SUBFUNCTIONS DEFINITION ---------------------------------------------- */ + +static void usage(void) { + printf("~~~ Library version string~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); + printf(" %s\n", lgw_version_info()); + printf("~~~ Available options ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); + printf(" -h print this help\n"); + printf(" -d use Linux SPI device driver\n"); + printf(" => default path: " LINUXDEV_PATH_DEFAULT "\n"); +} + + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/tst/test_loragw_spi.c b/libloragw/tst/test_loragw_spi.c new file mode 100644 index 0000000..0111896 --- /dev/null +++ b/libloragw/tst/test_loragw_spi.c @@ -0,0 +1,208 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Minimum test program for the loragw_spi module + +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 +#include +#include +#include +#include /* sigaction */ +#include /* getopt, access */ +#include + +#include "loragw_spi.h" +#include "loragw_aux.h" +#include "loragw_hal.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define BUFF_SIZE 1024 + +#define SX1302_AGC_MCU_MEM 0x0000 +#define SX1302_REG_COMMON 0x5600 +#define SX1302_REG_AGC_MCU 0x5780 + +#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" + +/* -------------------------------------------------------------------------- */ +/* --- GLOBAL VARIABLES ----------------------------------------------------- */ + +/* Signal handling variables */ +static int exit_sig = 0; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */ +static int quit_sig = 0; /* 1 -> application terminates without shutting down the hardware */ + +/* -------------------------------------------------------------------------- */ +/* --- SUBFUNCTIONS DECLARATION --------------------------------------------- */ + +static void sig_handler(int sigio); +static void usage(void); + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char ** argv) +{ + static struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ + + uint8_t data = 0; + uint8_t test_buff[BUFF_SIZE]; + uint8_t read_buff[BUFF_SIZE]; + int cycle_number = 0; + int i; + uint16_t size; + + /* SPI interfaces */ + const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; + const char * spidev_path = spidev_path_default; + void *spi_target = NULL; + + /* Parse command line options */ + while ((i = getopt(argc, argv, "hd:")) != -1) { + switch (i) { + case 'h': + usage(); + return EXIT_SUCCESS; + break; + + case 'd': + if (optarg != NULL) { + spidev_path = optarg; + } + break; + + default: + printf("ERROR: argument parsing options, use -h option for help\n"); + usage(); + return EXIT_FAILURE; + } + } + + /* Configure signal handling */ + sigemptyset( &sigact.sa_mask ); + sigact.sa_flags = 0; + sigact.sa_handler = sig_handler; + sigaction( SIGQUIT, &sigact, NULL ); + sigaction( SIGINT, &sigact, NULL ); + sigaction( SIGTERM, &sigact, NULL ); + + /* Board reset */ + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + + printf("Beginning of test for loragw_spi.c\n"); + i = lgw_spi_open(spidev_path, &spi_target); + if (i != 0) { + printf("ERROR: failed to open SPI device %s\n", spidev_path); + return -1; + } + + /* normal R/W test */ + /* TODO */ + + /* burst R/W test, small bursts << LGW_BURST_CHUNK */ + /* TODO */ + + /* burst R/W test, large bursts >> LGW_BURST_CHUNK */ + /* TODO */ + + lgw_spi_r(spi_target, LGW_SPI_MUX_TARGET_SX1302, SX1302_REG_COMMON + 6, &data); + printf("SX1302 version: 0x%02X\n", data); + + lgw_spi_r(spi_target, LGW_SPI_MUX_TARGET_SX1302, SX1302_REG_AGC_MCU + 0, &data); + lgw_spi_w(spi_target, LGW_SPI_MUX_TARGET_SX1302, SX1302_REG_AGC_MCU + 0, 0x06); /* mcu_clear, host_prog */ + + srand(time(NULL)); + + /* databuffer R/W stress test */ + while ((quit_sig != 1) && (exit_sig != 1)) { + size = rand() % BUFF_SIZE; + for (i = 0; i < size; ++i) { + test_buff[i] = rand() & 0xFF; + } + printf("Cycle %i > ", cycle_number); + lgw_spi_wb(spi_target, LGW_SPI_MUX_TARGET_SX1302, SX1302_AGC_MCU_MEM, test_buff, size); + lgw_spi_rb(spi_target, LGW_SPI_MUX_TARGET_SX1302, SX1302_AGC_MCU_MEM, read_buff, size); + for (i=0; ((i use Linux SPI device driver\n"); + printf(" => default path: " LINUXDEV_PATH_DEFAULT "\n"); +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/tst/test_loragw_spi_sx1250.c b/libloragw/tst/test_loragw_spi_sx1250.c new file mode 100644 index 0000000..ca91162 --- /dev/null +++ b/libloragw/tst/test_loragw_spi_sx1250.c @@ -0,0 +1,216 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Minimum test program for the sx1250 module + +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 +#include +#include +#include +#include /* sigaction */ +#include /* getopt, access */ + +#include "loragw_spi.h" +#include "loragw_aux.h" +#include "loragw_reg.h" +#include "loragw_hal.h" +#include "loragw_sx1250.h" +#include "loragw_sx1302.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define BUFF_SIZE 16 + +#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" + +/* -------------------------------------------------------------------------- */ +/* --- GLOBAL VARIABLES ----------------------------------------------------- */ + +/* Signal handling variables */ +static int exit_sig = 0; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */ +static int quit_sig = 0; /* 1 -> application terminates without shutting down the hardware */ + +/* -------------------------------------------------------------------------- */ +/* --- SUBFUNCTIONS DECLARATION --------------------------------------------- */ + +static void sig_handler(int sigio); +static void usage(void); + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char ** argv) +{ + static struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ + + uint8_t test_buff[BUFF_SIZE]; + uint8_t read_buff[BUFF_SIZE]; + uint32_t test_val, read_val; + int cycle_number = 0; + int i, x; + + /* SPI interfaces */ + const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; + const char * spidev_path = spidev_path_default; + + /* Parse command line options */ + while ((i = getopt(argc, argv, "hd:")) != -1) { + switch (i) { + case 'h': + usage(); + return EXIT_SUCCESS; + break; + + case 'd': + if (optarg != NULL) { + spidev_path = optarg; + } + break; + + default: + printf("ERROR: argument parsing options, use -h option for help\n"); + usage(); + return EXIT_FAILURE; + } + } + + /* Configure signal handling */ + sigemptyset( &sigact.sa_mask ); + sigact.sa_flags = 0; + sigact.sa_handler = sig_handler; + sigaction( SIGQUIT, &sigact, NULL ); + sigaction( SIGINT, &sigact, NULL ); + sigaction( SIGTERM, &sigact, NULL ); + + /* Board reset */ + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + + x = lgw_connect(spidev_path); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: Failed to connect to the concentrator using SPI %s\n", spidev_path); + return EXIT_FAILURE; + } + + /* Reset radios */ + for (i = 0; i < LGW_RF_CHAIN_NB; i++) { + sx1302_radio_reset(i, LGW_RADIO_TYPE_SX1250); + sx1302_radio_set_mode(i, LGW_RADIO_TYPE_SX1250); + } + + /* Select the radio which provides the clock to the sx1302 */ + sx1302_radio_clock_select(0); + + /* Ensure we can control the radio */ + lgw_reg_w(SX1302_REG_COMMON_CTRL0_HOST_RADIO_CTRL, 0x01); + + /* Ensure PA/LNA are disabled */ + lgw_reg_w(SX1302_REG_AGC_MCU_CTRL_FORCE_HOST_FE_CTRL, 1); + lgw_reg_w(SX1302_REG_AGC_MCU_RF_EN_A_PA_EN, 0); + lgw_reg_w(SX1302_REG_AGC_MCU_RF_EN_A_LNA_EN, 0); + + /* Set Radio in Standby mode */ + test_buff[0] = (uint8_t)STDBY_XOSC; + sx1250_write_command(0, SET_STANDBY, test_buff, 1); + sx1250_write_command(1, SET_STANDBY, test_buff, 1); + wait_ms(10); + + test_buff[0] = 0x00; + sx1250_read_command(0, GET_STATUS, test_buff, 1); + printf("Radio0: get_status: 0x%02X\n", test_buff[0]); + sx1250_read_command(1, GET_STATUS, test_buff, 1); + printf("Radio1: get_status: 0x%02X\n", test_buff[0]); + + /* databuffer R/W stress test */ + while ((quit_sig != 1) && (exit_sig != 1)) { + test_buff[0] = rand() & 0x7F; + test_buff[1] = rand() & 0xFF; + test_buff[2] = rand() & 0xFF; + test_buff[3] = rand() & 0xFF; + test_val = (test_buff[0] << 24) | (test_buff[1] << 16) | (test_buff[2] << 8) | (test_buff[3] << 0); + sx1250_write_command(0, SET_RF_FREQUENCY, test_buff, 4); + + read_buff[0] = 0x08; + read_buff[1] = 0x8B; + read_buff[2] = 0x00; + read_buff[3] = 0x00; + read_buff[4] = 0x00; + read_buff[5] = 0x00; + read_buff[6] = 0x00; + sx1250_read_command(0, READ_REGISTER, read_buff, 7); + read_val = (read_buff[3] << 24) | (read_buff[4] << 16) | (read_buff[5] << 8) | (read_buff[6] << 0); + + printf("Cycle %i > ", cycle_number); + if (read_val != test_val) { + printf("error during the buffer comparison\n"); + printf("Written value: %08X\n", test_val); + printf("Read value: %08X\n", read_val); + return EXIT_FAILURE; + } else { + printf("did a %i-byte R/W on a register with no error\n", 4); + ++cycle_number; + } + + } + + lgw_disconnect(); + printf("End of test for loragw_spi_sx1250.c\n"); + + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + + return 0; +} + +/* -------------------------------------------------------------------------- */ +/* --- SUBFUNCTIONS DEFINITION ---------------------------------------------- */ + +static void sig_handler(int sigio) { + if (sigio == SIGQUIT) { + quit_sig = 1; + } else if((sigio == SIGINT) || (sigio == SIGTERM)) { + exit_sig = 1; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +static void usage(void) { + printf("~~~ Library version string~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); + printf(" %s\n", lgw_version_info()); + printf("~~~ Available options ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); + printf(" -h print this help\n"); + printf(" -d use Linux SPI device driver\n"); + printf(" => default path: " LINUXDEV_PATH_DEFAULT "\n"); +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libtools/Makefile b/libtools/Makefile new file mode 100644 index 0000000..cf84988 --- /dev/null +++ b/libtools/Makefile @@ -0,0 +1,48 @@ +### get external defined data + +### constant symbols + +ARCH ?= +CROSS_COMPILE ?= +CC := $(CROSS_COMPILE)gcc +AR := $(CROSS_COMPILE)ar + +CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I. + +OBJDIR = obj +INCLUDES = $(wildcard inc/*.h) + +### linking options + +### general build targets + +all: libtinymt32.a libparson.a libbase64.a + +clean: + rm -f libtinymt32.a + rm -f libparson.a + rm -f libbase64.a + rm -f $(OBJDIR)/*.o + +### library module target + +$(OBJDIR): + mkdir -p $(OBJDIR) + +$(OBJDIR)/%.o: src/%.c $(INCLUDES) | $(OBJDIR) + $(CC) -c $(CFLAGS) $< -o $@ + +### static library + +libtinymt32.a: $(OBJDIR)/tinymt32.o + $(AR) rcs $@ $^ + +libparson.a: $(OBJDIR)/parson.o + $(AR) rcs $@ $^ + +libbase64.a: $(OBJDIR)/base64.o + $(AR) rcs $@ $^ + +### test programs + +### EOF diff --git a/libtools/inc/base64.h b/libtools/inc/base64.h new file mode 100644 index 0000000..2f217dd --- /dev/null +++ b/libtools/inc/base64.h @@ -0,0 +1,61 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Base64 encoding & decoding library + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _BASE64_H +#define _BASE64_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +/** +@brief Encode binary data in Base64 string (no padding) +@param in pointer to a table of binary data +@param size number of bytes to be encoded to base64 +@param out pointer to a string where the function will output encoded data +@param max_len max length of the out string (including null char) +@return >=0 length of the resulting string (w/o null char), -1 for error +*/ +int bin_to_b64_nopad(const uint8_t * in, int size, char * out, int max_len); + +/** +@brief Decode Base64 string to binary data (no padding) +@param in string containing only base64 valid characters +@param size number of characters to be decoded from base64 (w/o null char) +@param out pointer to a data buffer where the function will output decoded data +@param out_max_len usable size of the output data buffer +@return >=0 number of bytes written to the data buffer, -1 for error +*/ +int b64_to_bin_nopad(const char * in, int size, uint8_t * out, int max_len); + +/* === derivative functions === */ + +/** +@brief Encode binary data in Base64 string (with added padding) +*/ +int bin_to_b64(const uint8_t * in, int size, char * out, int max_len); + +/** +@brief Decode Base64 string to binary data (remove padding if necessary) +*/ +int b64_to_bin(const char * in, int size, uint8_t * out, int max_len); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libtools/inc/parson.h b/libtools/inc/parson.h new file mode 100644 index 0000000..2669a18 --- /dev/null +++ b/libtools/inc/parson.h @@ -0,0 +1,222 @@ +/* + Parson ( http://kgabis.github.com/parson/ ) + Copyright (c) 2012 - 2016 Krzysztof Gabis + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef parson_parson_h +#define parson_parson_h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include /* size_t */ + +/* Types and enums */ +typedef struct json_object_t JSON_Object; +typedef struct json_array_t JSON_Array; +typedef struct json_value_t JSON_Value; + +enum json_value_type { + JSONError = -1, + JSONNull = 1, + JSONString = 2, + JSONNumber = 3, + JSONObject = 4, + JSONArray = 5, + JSONBoolean = 6 +}; +typedef int JSON_Value_Type; + +enum json_result_t { + JSONSuccess = 0, + JSONFailure = -1 +}; +typedef int JSON_Status; + +typedef void * (*JSON_Malloc_Function)(size_t); +typedef void (*JSON_Free_Function)(void *); + +/* Call only once, before calling any other function from parson API. If not called, malloc and free + from stdlib will be used for all allocations */ +void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun); + +/* Parses first JSON value in a file, returns NULL in case of error */ +JSON_Value * json_parse_file(const char *filename); + +/* Parses first JSON value in a file and ignores comments (/ * * / and //), + returns NULL in case of error */ +JSON_Value * json_parse_file_with_comments(const char *filename); + +/* Parses first JSON value in a string, returns NULL in case of error */ +JSON_Value * json_parse_string(const char *string); + +/* Parses first JSON value in a string and ignores comments (/ * * / and //), + returns NULL in case of error */ +JSON_Value * json_parse_string_with_comments(const char *string); + +/* Serialization */ +size_t json_serialization_size(const JSON_Value *value); /* returns 0 on fail */ +JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes); +JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename); +char * json_serialize_to_string(const JSON_Value *value); + +/* Pretty serialization */ +size_t json_serialization_size_pretty(const JSON_Value *value); /* returns 0 on fail */ +JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes); +JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename); +char * json_serialize_to_string_pretty(const JSON_Value *value); + +void json_free_serialized_string(char *string); /* frees string from json_serialize_to_string and json_serialize_to_string_pretty */ + +/* Comparing */ +int json_value_equals(const JSON_Value *a, const JSON_Value *b); + +/* Validation + This is *NOT* JSON Schema. It validates json by checking if object have identically + named fields with matching types. + For example schema {"name":"", "age":0} will validate + {"name":"Joe", "age":25} and {"name":"Joe", "age":25, "gender":"m"}, + but not {"name":"Joe"} or {"name":"Joe", "age":"Cucumber"}. + In case of arrays, only first value in schema is checked against all values in tested array. + Empty objects ({}) validate all objects, empty arrays ([]) validate all arrays, + null validates values of every type. + */ +JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value); + +/* + * JSON Object + */ +JSON_Value * json_object_get_value (const JSON_Object *object, const char *name); +const char * json_object_get_string (const JSON_Object *object, const char *name); +JSON_Object * json_object_get_object (const JSON_Object *object, const char *name); +JSON_Array * json_object_get_array (const JSON_Object *object, const char *name); +double json_object_get_number (const JSON_Object *object, const char *name); /* returns 0 on fail */ +int json_object_get_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */ + +/* dotget functions enable addressing values with dot notation in nested objects, + just like in structs or c++/java/c# objects (e.g. objectA.objectB.value). + Because valid names in JSON can contain dots, some values may be inaccessible + this way. */ +JSON_Value * json_object_dotget_value (const JSON_Object *object, const char *name); +const char * json_object_dotget_string (const JSON_Object *object, const char *name); +JSON_Object * json_object_dotget_object (const JSON_Object *object, const char *name); +JSON_Array * json_object_dotget_array (const JSON_Object *object, const char *name); +double json_object_dotget_number (const JSON_Object *object, const char *name); /* returns 0 on fail */ +int json_object_dotget_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */ + +/* Functions to get available names */ +size_t json_object_get_count(const JSON_Object *object); +const char * json_object_get_name (const JSON_Object *object, size_t index); + +/* Creates new name-value pair or frees and replaces old value with a new one. + * json_object_set_value does not copy passed value so it shouldn't be freed afterwards. */ +JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value); +JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string); +JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number); +JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean); +JSON_Status json_object_set_null(JSON_Object *object, const char *name); + +/* Works like dotget functions, but creates whole hierarchy if necessary. + * json_object_dotset_value does not copy passed value so it shouldn't be freed afterwards. */ +JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value); +JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string); +JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number); +JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean); +JSON_Status json_object_dotset_null(JSON_Object *object, const char *name); + +/* Frees and removes name-value pair */ +JSON_Status json_object_remove(JSON_Object *object, const char *name); + +/* Works like dotget function, but removes name-value pair only on exact match. */ +JSON_Status json_object_dotremove(JSON_Object *object, const char *key); + +/* Removes all name-value pairs in object */ +JSON_Status json_object_clear(JSON_Object *object); + +/* + *JSON Array + */ +JSON_Value * json_array_get_value (const JSON_Array *array, size_t index); +const char * json_array_get_string (const JSON_Array *array, size_t index); +JSON_Object * json_array_get_object (const JSON_Array *array, size_t index); +JSON_Array * json_array_get_array (const JSON_Array *array, size_t index); +double json_array_get_number (const JSON_Array *array, size_t index); /* returns 0 on fail */ +int json_array_get_boolean(const JSON_Array *array, size_t index); /* returns -1 on fail */ +size_t json_array_get_count (const JSON_Array *array); + +/* Frees and removes value at given index, does nothing and returns JSONFailure if index doesn't exist. + * Order of values in array may change during execution. */ +JSON_Status json_array_remove(JSON_Array *array, size_t i); + +/* Frees and removes from array value at given index and replaces it with given one. + * Does nothing and returns JSONFailure if index doesn't exist. + * json_array_replace_value does not copy passed value so it shouldn't be freed afterwards. */ +JSON_Status json_array_replace_value(JSON_Array *array, size_t i, JSON_Value *value); +JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string); +JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number); +JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean); +JSON_Status json_array_replace_null(JSON_Array *array, size_t i); + +/* Frees and removes all values from array */ +JSON_Status json_array_clear(JSON_Array *array); + +/* Appends new value at the end of array. + * json_array_append_value does not copy passed value so it shouldn't be freed afterwards. */ +JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value); +JSON_Status json_array_append_string(JSON_Array *array, const char *string); +JSON_Status json_array_append_number(JSON_Array *array, double number); +JSON_Status json_array_append_boolean(JSON_Array *array, int boolean); +JSON_Status json_array_append_null(JSON_Array *array); + +/* + *JSON Value + */ +JSON_Value * json_value_init_object (void); +JSON_Value * json_value_init_array (void); +JSON_Value * json_value_init_string (const char *string); /* copies passed string */ +JSON_Value * json_value_init_number (double number); +JSON_Value * json_value_init_boolean(int boolean); +JSON_Value * json_value_init_null (void); +JSON_Value * json_value_deep_copy (const JSON_Value *value); +void json_value_free (JSON_Value *value); + +JSON_Value_Type json_value_get_type (const JSON_Value *value); +JSON_Object * json_value_get_object (const JSON_Value *value); +JSON_Array * json_value_get_array (const JSON_Value *value); +const char * json_value_get_string (const JSON_Value *value); +double json_value_get_number (const JSON_Value *value); +int json_value_get_boolean(const JSON_Value *value); + +/* Same as above, but shorter */ +JSON_Value_Type json_type (const JSON_Value *value); +JSON_Object * json_object (const JSON_Value *value); +JSON_Array * json_array (const JSON_Value *value); +const char * json_string (const JSON_Value *value); +double json_number (const JSON_Value *value); +int json_boolean(const JSON_Value *value); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libtools/inc/tinymt32.h b/libtools/inc/tinymt32.h new file mode 100644 index 0000000..437840f --- /dev/null +++ b/libtools/inc/tinymt32.h @@ -0,0 +1,247 @@ +#ifndef TINYMT32_H +#define TINYMT32_H +/** + * @file tinymt32.h + * + * @brief Tiny Mersenne Twister only 127 bit internal state + * + * @author Mutsuo Saito (Hiroshima University) + * @author Makoto Matsumoto (University of Tokyo) + * + * Copyright (C) 2011 Mutsuo Saito, Makoto Matsumoto, + * Hiroshima University and The University of Tokyo. + * All rights reserved. + * + * The 3-clause BSD License is applied to this software, see + * LICENSE.txt + */ + +#include +#include + +#define TINYMT32_MEXP 127 +#define TINYMT32_SH0 1 +#define TINYMT32_SH1 10 +#define TINYMT32_SH8 8 +#define TINYMT32_MASK UINT32_C(0x7fffffff) +#define TINYMT32_MUL (1.0f / 16777216.0f) + +#if defined(__cplusplus) +extern "C" { +#endif + +/** + * tinymt32 internal state vector and parameters + */ +struct TINYMT32_T { + uint32_t status[4]; + uint32_t mat1; + uint32_t mat2; + uint32_t tmat; +}; + +typedef struct TINYMT32_T tinymt32_t; + +void tinymt32_init(tinymt32_t * random, uint32_t seed); +void tinymt32_init_by_array(tinymt32_t * random, uint32_t init_key[], + int key_length); + +#if defined(__GNUC__) +/** + * This function always returns 127 + * @param random not used + * @return always 127 + */ +inline static int tinymt32_get_mexp( + tinymt32_t * random __attribute__((unused))) { + return TINYMT32_MEXP; +} +#else +inline static int tinymt32_get_mexp(tinymt32_t * random) { + return TINYMT32_MEXP; +} +#endif + +/** + * This function changes internal state of tinymt32. + * Users should not call this function directly. + * @param random tinymt internal status + */ +inline static void tinymt32_next_state(tinymt32_t * random) { + uint32_t x; + uint32_t y; + + y = random->status[3]; + x = (random->status[0] & TINYMT32_MASK) + ^ random->status[1] + ^ random->status[2]; + x ^= (x << TINYMT32_SH0); + y ^= (y >> TINYMT32_SH0) ^ x; + random->status[0] = random->status[1]; + random->status[1] = random->status[2]; + random->status[2] = x ^ (y << TINYMT32_SH1); + random->status[3] = y; + random->status[1] ^= -((int32_t)(y & 1)) & random->mat1; + random->status[2] ^= -((int32_t)(y & 1)) & random->mat2; +} + +/** + * This function outputs 32-bit unsigned integer from internal state. + * Users should not call this function directly. + * @param random tinymt internal status + * @return 32-bit unsigned pseudorandom number + */ +inline static uint32_t tinymt32_temper(tinymt32_t * random) { + uint32_t t0, t1; + t0 = random->status[3]; +#if defined(LINEARITY_CHECK) + t1 = random->status[0] + ^ (random->status[2] >> TINYMT32_SH8); +#else + t1 = random->status[0] + + (random->status[2] >> TINYMT32_SH8); +#endif + t0 ^= t1; + t0 ^= -((int32_t)(t1 & 1)) & random->tmat; + return t0; +} + +/** + * This function outputs floating point number from internal state. + * Users should not call this function directly. + * @param random tinymt internal status + * @return floating point number r (1.0 <= r < 2.0) + */ +inline static float tinymt32_temper_conv(tinymt32_t * random) { + uint32_t t0, t1; + union { + uint32_t u; + float f; + } conv; + + t0 = random->status[3]; +#if defined(LINEARITY_CHECK) + t1 = random->status[0] + ^ (random->status[2] >> TINYMT32_SH8); +#else + t1 = random->status[0] + + (random->status[2] >> TINYMT32_SH8); +#endif + t0 ^= t1; + conv.u = ((t0 ^ (-((int32_t)(t1 & 1)) & random->tmat)) >> 9) + | UINT32_C(0x3f800000); + return conv.f; +} + +/** + * This function outputs floating point number from internal state. + * Users should not call this function directly. + * @param random tinymt internal status + * @return floating point number r (1.0 < r < 2.0) + */ +inline static float tinymt32_temper_conv_open(tinymt32_t * random) { + uint32_t t0, t1; + union { + uint32_t u; + float f; + } conv; + + t0 = random->status[3]; +#if defined(LINEARITY_CHECK) + t1 = random->status[0] + ^ (random->status[2] >> TINYMT32_SH8); +#else + t1 = random->status[0] + + (random->status[2] >> TINYMT32_SH8); +#endif + t0 ^= t1; + conv.u = ((t0 ^ (-((int32_t)(t1 & 1)) & random->tmat)) >> 9) + | UINT32_C(0x3f800001); + return conv.f; +} + +/** + * This function outputs 32-bit unsigned integer from internal state. + * @param random tinymt internal status + * @return 32-bit unsigned integer r (0 <= r < 2^32) + */ +inline static uint32_t tinymt32_generate_uint32(tinymt32_t * random) { + tinymt32_next_state(random); + return tinymt32_temper(random); +} + +/** + * This function outputs floating point number from internal state. + * This function is implemented using multiplying by (1 / 2^24). + * floating point multiplication is faster than using union trick in + * my Intel CPU. + * @param random tinymt internal status + * @return floating point number r (0.0 <= r < 1.0) + */ +inline static float tinymt32_generate_float(tinymt32_t * random) { + tinymt32_next_state(random); + return (tinymt32_temper(random) >> 8) * TINYMT32_MUL; +} + +/** + * This function outputs floating point number from internal state. + * This function is implemented using union trick. + * @param random tinymt internal status + * @return floating point number r (1.0 <= r < 2.0) + */ +inline static float tinymt32_generate_float12(tinymt32_t * random) { + tinymt32_next_state(random); + return tinymt32_temper_conv(random); +} + +/** + * This function outputs floating point number from internal state. + * This function is implemented using union trick. + * @param random tinymt internal status + * @return floating point number r (0.0 <= r < 1.0) + */ +inline static float tinymt32_generate_float01(tinymt32_t * random) { + tinymt32_next_state(random); + return tinymt32_temper_conv(random) - 1.0f; +} + +/** + * This function outputs floating point number from internal state. + * This function may return 1.0 and never returns 0.0. + * @param random tinymt internal status + * @return floating point number r (0.0 < r <= 1.0) + */ +inline static float tinymt32_generate_floatOC(tinymt32_t * random) { + tinymt32_next_state(random); + return 1.0f - tinymt32_generate_float(random); +} + +/** + * This function outputs floating point number from internal state. + * This function returns neither 0.0 nor 1.0. + * @param random tinymt internal status + * @return floating point number r (0.0 < r < 1.0) + */ +inline static float tinymt32_generate_floatOO(tinymt32_t * random) { + tinymt32_next_state(random); + return tinymt32_temper_conv_open(random) - 1.0f; +} + +/** + * This function outputs double precision floating point number from + * internal state. The returned value has 32-bit precision. + * In other words, this function makes one double precision floating point + * number from one 32-bit unsigned integer. + * @param random tinymt internal status + * @return floating point number r (0.0 <= r < 1.0) + */ +inline static double tinymt32_generate_32double(tinymt32_t * random) { + tinymt32_next_state(random); + return tinymt32_temper(random) * (1.0 / 4294967296.0); +} + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/libtools/src/base64.c b/libtools/src/base64.c new file mode 100644 index 0000000..d7e4bbd --- /dev/null +++ b/libtools/src/base64.c @@ -0,0 +1,307 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Base64 encoding & decoding library + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include +#include +#include + +#include "base64.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#define CRIT(a) fprintf(stderr, "\nCRITICAL file:%s line:%u msg:%s\n", __FILE__, __LINE__,a);exit(EXIT_FAILURE) + +//#define DEBUG(args...) fprintf(stderr,"debug: " args) /* diagnostic message that is destined to the user */ +#define DEBUG(args...) + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MODULE-WIDE VARIABLES ---------------------------------------- */ + +static char code_62 = '+'; /* RFC 1421 standard character for code 62 */ +static char code_63 = '/'; /* RFC 1421 standard character for code 63 */ +static char code_pad = '='; /* RFC 1421 padding character if padding */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ + +/** +@brief Convert a code in the range 0-63 to an ASCII character +*/ +char code_to_char(uint8_t x); + +/** +@brief Convert an ASCII character to a code in the range 0-63 +*/ +uint8_t char_to_code(char x); + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +char code_to_char(uint8_t x) { + if (x <= 25) { + return 'A' + x; + } else if ((x >= 26) && (x <= 51)) { + return 'a' + (x-26); + } else if ((x >= 52) && (x <= 61)) { + return '0' + (x-52); + } else if (x == 62) { + return code_62; + } else if (x == 63) { + return code_63; + } else { + DEBUG("ERROR: %i IS OUT OF RANGE 0-63 FOR BASE64 ENCODING\n", x); + exit(EXIT_FAILURE); + } //TODO: improve error management +} + +uint8_t char_to_code(char x) { + if ((x >= 'A') && (x <= 'Z')) { + return (uint8_t)x - (uint8_t)'A'; + } else if ((x >= 'a') && (x <= 'z')) { + return (uint8_t)x - (uint8_t)'a' + 26; + } else if ((x >= '0') && (x <= '9')) { + return (uint8_t)x - (uint8_t)'0' + 52; + } else if (x == code_62) { + return 62; + } else if (x == code_63) { + return 63; + } else { + DEBUG("ERROR: %c (0x%x) IS INVALID CHARACTER FOR BASE64 DECODING\n", x, x); + exit(EXIT_FAILURE); + } //TODO: improve error management +} + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int bin_to_b64_nopad(const uint8_t * in, int size, char * out, int max_len) { + int i; + int result_len; /* size of the result */ + int full_blocks; /* number of 3 unsigned chars / 4 characters blocks */ + int last_bytes; /* number of unsigned chars <3 in the last block */ + int last_chars; /* number of characters <4 in the last block */ + uint32_t b; + + /* check input values */ + if ((out == NULL) || (in == NULL)) { + DEBUG("ERROR: NULL POINTER AS OUTPUT IN BIN_TO_B64\n"); + return -1; + } + if (size == 0) { + *out = 0; /* null string */ + return 0; + } + + /* calculate the number of base64 'blocks' */ + full_blocks = size / 3; + last_bytes = size % 3; + switch (last_bytes) { + case 0: /* no byte left to encode */ + last_chars = 0; + break; + case 1: /* 1 byte left to encode -> +2 chars */ + last_chars = 2; + break; + case 2: /* 2 bytes left to encode -> +3 chars */ + last_chars = 3; + break; + default: + CRIT("switch default that should not be possible"); + } + + /* check if output buffer is big enough */ + result_len = (4*full_blocks) + last_chars; + if (max_len < (result_len + 1)) { /* 1 char added for string terminator */ + DEBUG("ERROR: OUTPUT BUFFER TOO SMALL IN BIN_TO_B64\n"); + return -1; + } + + /* process all the full blocks */ + for (i=0; i < full_blocks; ++i) { + b = (0xFF & in[3*i] ) << 16; + b |= (0xFF & in[3*i + 1]) << 8; + b |= 0xFF & in[3*i + 2]; + out[4*i + 0] = code_to_char((b >> 18) & 0x3F); + out[4*i + 1] = code_to_char((b >> 12) & 0x3F); + out[4*i + 2] = code_to_char((b >> 6 ) & 0x3F); + out[4*i + 3] = code_to_char( b & 0x3F); + } + + /* process the last 'partial' block and terminate string */ + i = full_blocks; + if (last_chars == 0) { + out[4*i] = 0; /* null character to terminate string */ + } else if (last_chars == 2) { + b = (0xFF & in[3*i] ) << 16; + out[4*i + 0] = code_to_char((b >> 18) & 0x3F); + out[4*i + 1] = code_to_char((b >> 12) & 0x3F); + out[4*i + 2] = 0; /* null character to terminate string */ + } else if (last_chars == 3) { + b = (0xFF & in[3*i] ) << 16; + b |= (0xFF & in[3*i + 1]) << 8; + out[4*i + 0] = code_to_char((b >> 18) & 0x3F); + out[4*i + 1] = code_to_char((b >> 12) & 0x3F); + out[4*i + 2] = code_to_char((b >> 6 ) & 0x3F); + out[4*i + 3] = 0; /* null character to terminate string */ + } + + return result_len; +} + +int b64_to_bin_nopad(const char * in, int size, uint8_t * out, int max_len) { + int i; + int result_len; /* size of the result */ + int full_blocks; /* number of 3 unsigned chars / 4 characters blocks */ + int last_chars; /* number of characters <4 in the last block */ + int last_bytes; /* number of unsigned chars <3 in the last block */ + uint32_t b; + ; + + /* check input values */ + if ((out == NULL) || (in == NULL)) { + DEBUG("ERROR: NULL POINTER AS OUTPUT OR INPUT IN B64_TO_BIN\n"); + return -1; + } + if (size == 0) { + return 0; + } + + /* calculate the number of base64 'blocks' */ + full_blocks = size / 4; + last_chars = size % 4; + switch (last_chars) { + case 0: /* no char left to decode */ + last_bytes = 0; + break; + case 1: /* only 1 char left is an error */ + DEBUG("ERROR: ONLY ONE CHAR LEFT IN B64_TO_BIN\n"); + return -1; + case 2: /* 2 chars left to decode -> +1 byte */ + last_bytes = 1; + break; + case 3: /* 3 chars left to decode -> +2 bytes */ + last_bytes = 2; + break; + default: + CRIT("switch default that should not be possible"); + } + + /* check if output buffer is big enough */ + result_len = (3*full_blocks) + last_bytes; + if (max_len < result_len) { + DEBUG("ERROR: OUTPUT BUFFER TOO SMALL IN B64_TO_BIN\n"); + return -1; + } + + /* process all the full blocks */ + for (i=0; i < full_blocks; ++i) { + b = (0x3F & char_to_code(in[4*i] )) << 18; + b |= (0x3F & char_to_code(in[4*i + 1])) << 12; + b |= (0x3F & char_to_code(in[4*i + 2])) << 6; + b |= 0x3F & char_to_code(in[4*i + 3]); + out[3*i + 0] = (b >> 16) & 0xFF; + out[3*i + 1] = (b >> 8 ) & 0xFF; + out[3*i + 2] = b & 0xFF; + } + + /* process the last 'partial' block */ + i = full_blocks; + if (last_bytes == 1) { + b = (0x3F & char_to_code(in[4*i] )) << 18; + b |= (0x3F & char_to_code(in[4*i + 1])) << 12; + out[3*i + 0] = (b >> 16) & 0xFF; + if (((b >> 12) & 0x0F) != 0) { + DEBUG("WARNING: last character contains unusable bits\n"); + } + } else if (last_bytes == 2) { + b = (0x3F & char_to_code(in[4*i] )) << 18; + b |= (0x3F & char_to_code(in[4*i + 1])) << 12; + b |= (0x3F & char_to_code(in[4*i + 2])) << 6; + out[3*i + 0] = (b >> 16) & 0xFF; + out[3*i + 1] = (b >> 8 ) & 0xFF; + if (((b >> 6) & 0x03) != 0) { + DEBUG("WARNING: last character contains unusable bits\n"); + } + } + + return result_len; +} + +int bin_to_b64(const uint8_t * in, int size, char * out, int max_len) { + int ret; + + ret = bin_to_b64_nopad(in, size, out, max_len); + + if (ret == -1) { + return -1; + } + switch (ret%4) { + case 0: /* nothing to do */ + return ret; + case 1: + DEBUG("ERROR: INVALID UNPADDED BASE64 STRING\n"); + return -1; + case 2: /* 2 chars in last block, must add 2 padding char */ + if (max_len >= (ret + 2 + 1)) { + out[ret] = code_pad; + out[ret+1] = code_pad; + out[ret+2] = 0; + return ret+2; + } else { + DEBUG("ERROR: not enough room to add padding in bin_to_b64\n"); + return -1; + } + case 3: /* 3 chars in last block, must add 1 padding char */ + if (max_len >= (ret + 1 + 1)) { + out[ret] = code_pad; + out[ret+1] = 0; + return ret+1; + } else { + DEBUG("ERROR: not enough room to add padding in bin_to_b64\n"); + return -1; + } + default: + CRIT("switch default that should not be possible"); + } +} + +int b64_to_bin(const char * in, int size, uint8_t * out, int max_len) { + if (in == NULL) { + DEBUG("ERROR: NULL POINTER AS OUTPUT OR INPUT IN B64_TO_BIN\n"); + return -1; + } + if ((size%4 == 0) && (size >= 4)) { /* potentially padded Base64 */ + if (in[size-2] == code_pad) { /* 2 padding char to ignore */ + return b64_to_bin_nopad(in, size-2, out, max_len); + } else if (in[size-1] == code_pad) { /* 1 padding char to ignore */ + return b64_to_bin_nopad(in, size-1, out, max_len); + } else { /* no padding to ignore */ + return b64_to_bin_nopad(in, size, out, max_len); + } + } else { /* treat as unpadded Base64 */ + return b64_to_bin_nopad(in, size, out, max_len); + } +} + + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libtools/src/parson.c b/libtools/src/parson.c new file mode 100644 index 0000000..16bb158 --- /dev/null +++ b/libtools/src/parson.c @@ -0,0 +1,1765 @@ +/* + Parson ( http://kgabis.github.com/parson/ ) + Copyright (c) 2012 - 2016 Krzysztof Gabis + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "parson.h" + +#include +#include +#include +#include +#include + +#define STARTING_CAPACITY 15 +#define ARRAY_MAX_CAPACITY 122880 /* 15*(2^13) */ +#define OBJECT_MAX_CAPACITY 960 /* 15*(2^6) */ +#define MAX_NESTING 19 +#define DOUBLE_SERIALIZATION_FORMAT "%f" + +#define SIZEOF_TOKEN(a) (sizeof(a) - 1) +#define SKIP_CHAR(str) ((*str)++) +#define SKIP_WHITESPACES(str) while (isspace(**str)) { SKIP_CHAR(str); } +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +#undef malloc +#undef free + +static JSON_Malloc_Function parson_malloc = malloc; +static JSON_Free_Function parson_free = free; + +#define IS_CONT(b) (((unsigned char)(b) & 0xC0) == 0x80) /* is utf-8 continuation byte */ + +/* Type definitions */ +typedef union json_value_value { + char *string; + double number; + JSON_Object *object; + JSON_Array *array; + int boolean; + int null; +} JSON_Value_Value; + +struct json_value_t { + JSON_Value_Type type; + JSON_Value_Value value; +}; + +struct json_object_t { + char **names; + JSON_Value **values; + size_t count; + size_t capacity; +}; + +struct json_array_t { + JSON_Value **items; + size_t count; + size_t capacity; +}; + +/* Various */ +static char * read_file(const char *filename); +static void remove_comments(char *string, const char *start_token, const char *end_token); +static char * parson_strndup(const char *string, size_t n); +static char * parson_strdup(const char *string); +static int is_utf16_hex(const unsigned char *string); +static int num_bytes_in_utf8_sequence(unsigned char c); +static int verify_utf8_sequence(const unsigned char *string, int *len); +static int is_valid_utf8(const char *string, size_t string_len); +static int is_decimal(const char *string, size_t length); + +/* JSON Object */ +static JSON_Object * json_object_init(void); +static JSON_Status json_object_add(JSON_Object *object, const char *name, JSON_Value *value); +static JSON_Status json_object_resize(JSON_Object *object, size_t new_capacity); +static JSON_Value * json_object_nget_value(const JSON_Object *object, const char *name, size_t n); +static void json_object_free(JSON_Object *object); + +/* JSON Array */ +static JSON_Array * json_array_init(void); +static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value); +static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity); +static void json_array_free(JSON_Array *array); + +/* JSON Value */ +static JSON_Value * json_value_init_string_no_copy(char *string); + +/* Parser */ +static void skip_quotes(const char **string); +static int parse_utf_16(const char **unprocessed, char **processed); +static char * process_string(const char *input, size_t len); +static char * get_quoted_string(const char **string); +static JSON_Value * parse_object_value(const char **string, size_t nesting); +static JSON_Value * parse_array_value(const char **string, size_t nesting); +static JSON_Value * parse_string_value(const char **string); +static JSON_Value * parse_boolean_value(const char **string); +static JSON_Value * parse_number_value(const char **string); +static JSON_Value * parse_null_value(const char **string); +static JSON_Value * parse_value(const char **string, size_t nesting); + +/* Serialization */ +static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, int is_pretty, char *num_buf); +static int json_serialize_string(const char *string, char *buf); +static int append_indent(char *buf, int level); +static int append_string(char *buf, const char *string); + +/* Various */ +static char * parson_strndup(const char *string, size_t n) { + char *output_string = (char*)parson_malloc(n + 1); + if (!output_string) + return NULL; + output_string[n] = '\0'; + strncpy(output_string, string, n); + return output_string; +} + +static char * parson_strdup(const char *string) { + return parson_strndup(string, strlen(string)); +} + +static int is_utf16_hex(const unsigned char *s) { + return isxdigit(s[0]) && isxdigit(s[1]) && isxdigit(s[2]) && isxdigit(s[3]); +} + +static int num_bytes_in_utf8_sequence(unsigned char c) { + if (c == 0xC0 || c == 0xC1 || c > 0xF4 || IS_CONT(c)) { + return 0; + } else if ((c & 0x80) == 0) { /* 0xxxxxxx */ + return 1; + } else if ((c & 0xE0) == 0xC0) { /* 110xxxxx */ + return 2; + } else if ((c & 0xF0) == 0xE0) { /* 1110xxxx */ + return 3; + } else if ((c & 0xF8) == 0xF0) { /* 11110xxx */ + return 4; + } + return 0; /* won't happen */ +} + +static int verify_utf8_sequence(const unsigned char *string, int *len) { + unsigned int cp = 0; + *len = num_bytes_in_utf8_sequence(string[0]); + + if (*len == 1) { + cp = string[0]; + } else if (*len == 2 && IS_CONT(string[1])) { + cp = string[0] & 0x1F; + cp = (cp << 6) | (string[1] & 0x3F); + } else if (*len == 3 && IS_CONT(string[1]) && IS_CONT(string[2])) { + cp = ((unsigned char)string[0]) & 0xF; + cp = (cp << 6) | (string[1] & 0x3F); + cp = (cp << 6) | (string[2] & 0x3F); + } else if (*len == 4 && IS_CONT(string[1]) && IS_CONT(string[2]) && IS_CONT(string[3])) { + cp = string[0] & 0x7; + cp = (cp << 6) | (string[1] & 0x3F); + cp = (cp << 6) | (string[2] & 0x3F); + cp = (cp << 6) | (string[3] & 0x3F); + } else { + return 0; + } + + /* overlong encodings */ + if ((cp < 0x80 && *len > 1) || + (cp < 0x800 && *len > 2) || + (cp < 0x10000 && *len > 3)) { + return 0; + } + + /* invalid unicode */ + if (cp > 0x10FFFF) { + return 0; + } + + /* surrogate halves */ + if (cp >= 0xD800 && cp <= 0xDFFF) { + return 0; + } + + return 1; +} + +static int is_valid_utf8(const char *string, size_t string_len) { + int len = 0; + const char *string_end = string + string_len; + while (string < string_end) { + if (!verify_utf8_sequence((const unsigned char*)string, &len)) { + return 0; + } + string += len; + } + return 1; +} + +static int is_decimal(const char *string, size_t length) { + if (length > 1 && string[0] == '0' && string[1] != '.') + return 0; + if (length > 2 && !strncmp(string, "-0", 2) && string[2] != '.') + return 0; + while (length--) + if (strchr("xX", string[length])) + return 0; + return 1; +} + +static char * read_file(const char * filename) { + FILE *fp = fopen(filename, "r"); + size_t file_size; + long pos; + char *file_contents; + if (!fp) + return NULL; + fseek(fp, 0L, SEEK_END); + pos = ftell(fp); + if (pos < 0) { + fclose(fp); + return NULL; + } + file_size = pos; + rewind(fp); + file_contents = (char*)parson_malloc(sizeof(char) * (file_size + 1)); + if (!file_contents) { + fclose(fp); + return NULL; + } + if (fread(file_contents, file_size, 1, fp) < 1) { + if (ferror(fp)) { + fclose(fp); + parson_free(file_contents); + return NULL; + } + } + fclose(fp); + file_contents[file_size] = '\0'; + return file_contents; +} + +static void remove_comments(char *string, const char *start_token, const char *end_token) { + int in_string = 0, escaped = 0; + size_t i; + char *ptr = NULL, current_char; + size_t start_token_len = strlen(start_token); + size_t end_token_len = strlen(end_token); + if (start_token_len == 0 || end_token_len == 0) + return; + while ((current_char = *string) != '\0') { + if (current_char == '\\' && !escaped) { + escaped = 1; + string++; + continue; + } else if (current_char == '\"' && !escaped) { + in_string = !in_string; + } else if (!in_string && strncmp(string, start_token, start_token_len) == 0) { + for(i = 0; i < start_token_len; i++) + string[i] = ' '; + string = string + start_token_len; + ptr = strstr(string, end_token); + if (!ptr) + return; + for (i = 0; i < (ptr - string) + end_token_len; i++) + string[i] = ' '; + string = ptr + end_token_len - 1; + } + escaped = 0; + string++; + } +} + +/* JSON Object */ +static JSON_Object * json_object_init(void) { + JSON_Object *new_obj = (JSON_Object*)parson_malloc(sizeof(JSON_Object)); + if (!new_obj) + return NULL; + new_obj->names = (char**)NULL; + new_obj->values = (JSON_Value**)NULL; + new_obj->capacity = 0; + new_obj->count = 0; + return new_obj; +} + +static JSON_Status json_object_add(JSON_Object *object, const char *name, JSON_Value *value) { + size_t index = 0; + if (object == NULL || name == NULL || value == NULL) { + return JSONFailure; + } + if (object->count >= object->capacity) { + size_t new_capacity = MAX(object->capacity * 2, STARTING_CAPACITY); + if (new_capacity > OBJECT_MAX_CAPACITY) + return JSONFailure; + if (json_object_resize(object, new_capacity) == JSONFailure) + return JSONFailure; + } + if (json_object_get_value(object, name) != NULL) + return JSONFailure; + index = object->count; + object->names[index] = parson_strdup(name); + if (object->names[index] == NULL) + return JSONFailure; + object->values[index] = value; + object->count++; + return JSONSuccess; +} + +static JSON_Status json_object_resize(JSON_Object *object, size_t new_capacity) { + char **temp_names = NULL; + JSON_Value **temp_values = NULL; + + if ((object->names == NULL && object->values != NULL) || + (object->names != NULL && object->values == NULL) || + new_capacity == 0) { + return JSONFailure; /* Shouldn't happen */ + } + + temp_names = (char**)parson_malloc(new_capacity * sizeof(char*)); + if (temp_names == NULL) + return JSONFailure; + + temp_values = (JSON_Value**)parson_malloc(new_capacity * sizeof(JSON_Value*)); + if (temp_values == NULL) { + parson_free(temp_names); + return JSONFailure; + } + + if (object->names != NULL && object->values != NULL && object->count > 0) { + memcpy(temp_names, object->names, object->count * sizeof(char*)); + memcpy(temp_values, object->values, object->count * sizeof(JSON_Value*)); + } + parson_free(object->names); + parson_free(object->values); + object->names = temp_names; + object->values = temp_values; + object->capacity = new_capacity; + return JSONSuccess; +} + +static JSON_Value * json_object_nget_value(const JSON_Object *object, const char *name, size_t n) { + size_t i, name_length; + for (i = 0; i < json_object_get_count(object); i++) { + name_length = strlen(object->names[i]); + if (name_length != n) + continue; + if (strncmp(object->names[i], name, n) == 0) + return object->values[i]; + } + return NULL; +} + +static void json_object_free(JSON_Object *object) { + while(object->count--) { + parson_free(object->names[object->count]); + json_value_free(object->values[object->count]); + } + parson_free(object->names); + parson_free(object->values); + parson_free(object); +} + +/* JSON Array */ +static JSON_Array * json_array_init(void) { + JSON_Array *new_array = (JSON_Array*)parson_malloc(sizeof(JSON_Array)); + if (!new_array) + return NULL; + new_array->items = (JSON_Value**)NULL; + new_array->capacity = 0; + new_array->count = 0; + return new_array; +} + +static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value) { + if (array->count >= array->capacity) { + size_t new_capacity = MAX(array->capacity * 2, STARTING_CAPACITY); + if (new_capacity > ARRAY_MAX_CAPACITY) + return JSONFailure; + if (json_array_resize(array, new_capacity) == JSONFailure) + return JSONFailure; + } + array->items[array->count] = value; + array->count++; + return JSONSuccess; +} + +static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity) { + JSON_Value **new_items = NULL; + if (new_capacity == 0) { + return JSONFailure; + } + new_items = (JSON_Value**)parson_malloc(new_capacity * sizeof(JSON_Value*)); + if (new_items == NULL) { + return JSONFailure; + } + if (array->items != NULL && array->count > 0) { + memcpy(new_items, array->items, array->count * sizeof(JSON_Value*)); + } + parson_free(array->items); + array->items = new_items; + array->capacity = new_capacity; + return JSONSuccess; +} + +static void json_array_free(JSON_Array *array) { + while (array->count--) + json_value_free(array->items[array->count]); + parson_free(array->items); + parson_free(array); +} + +/* JSON Value */ +static JSON_Value * json_value_init_string_no_copy(char *string) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) + return NULL; + new_value->type = JSONString; + new_value->value.string = string; + return new_value; +} + +/* Parser */ +static void skip_quotes(const char **string) { + SKIP_CHAR(string); + while (**string != '\"') { + if (**string == '\0') + return; + if (**string == '\\') { + SKIP_CHAR(string); + if (**string == '\0') + return; + } + SKIP_CHAR(string); + } + SKIP_CHAR(string); +} + +static int parse_utf_16(const char **unprocessed, char **processed) { + unsigned int cp, lead, trail; + char *processed_ptr = *processed; + const char *unprocessed_ptr = *unprocessed; + unprocessed_ptr++; /* skips u */ + if (!is_utf16_hex((const unsigned char*)unprocessed_ptr) || sscanf(unprocessed_ptr, "%4x", &cp) == EOF) + return JSONFailure; + if (cp < 0x80) { + *processed_ptr = cp; /* 0xxxxxxx */ + } else if (cp < 0x800) { + *processed_ptr++ = ((cp >> 6) & 0x1F) | 0xC0; /* 110xxxxx */ + *processed_ptr = ((cp ) & 0x3F) | 0x80; /* 10xxxxxx */ + } else if (cp < 0xD800 || cp > 0xDFFF) { + *processed_ptr++ = ((cp >> 12) & 0x0F) | 0xE0; /* 1110xxxx */ + *processed_ptr++ = ((cp >> 6) & 0x3F) | 0x80; /* 10xxxxxx */ + *processed_ptr = ((cp ) & 0x3F) | 0x80; /* 10xxxxxx */ + } else if (cp >= 0xD800 && cp <= 0xDBFF) { /* lead surrogate (0xD800..0xDBFF) */ + lead = cp; + unprocessed_ptr += 4; /* should always be within the buffer, otherwise previous sscanf would fail */ + if (*unprocessed_ptr++ != '\\' || *unprocessed_ptr++ != 'u' || /* starts with \u? */ + !is_utf16_hex((const unsigned char*)unprocessed_ptr) || + sscanf(unprocessed_ptr, "%4x", &trail) == EOF || + trail < 0xDC00 || trail > 0xDFFF) { /* valid trail surrogate? (0xDC00..0xDFFF) */ + return JSONFailure; + } + cp = ((((lead-0xD800)&0x3FF)<<10)|((trail-0xDC00)&0x3FF))+0x010000; + *processed_ptr++ = (((cp >> 18) & 0x07) | 0xF0); /* 11110xxx */ + *processed_ptr++ = (((cp >> 12) & 0x3F) | 0x80); /* 10xxxxxx */ + *processed_ptr++ = (((cp >> 6) & 0x3F) | 0x80); /* 10xxxxxx */ + *processed_ptr = (((cp ) & 0x3F) | 0x80); /* 10xxxxxx */ + } else { /* trail surrogate before lead surrogate */ + return JSONFailure; + } + unprocessed_ptr += 3; + *processed = processed_ptr; + *unprocessed = unprocessed_ptr; + return JSONSuccess; +} + + +/* Copies and processes passed string up to supplied length. +Example: "\u006Corem ipsum" -> lorem ipsum */ +static char* process_string(const char *input, size_t len) { + const char *input_ptr = input; + size_t initial_size = (len + 1) * sizeof(char); + size_t final_size = 0; + char *output = (char*)parson_malloc(initial_size); + char *output_ptr = output; + char *resized_output = NULL; + while ((*input_ptr != '\0') && (size_t)(input_ptr - input) < len) { + if (*input_ptr == '\\') { + input_ptr++; + switch (*input_ptr) { + case '\"': *output_ptr = '\"'; break; + case '\\': *output_ptr = '\\'; break; + case '/': *output_ptr = '/'; break; + case 'b': *output_ptr = '\b'; break; + case 'f': *output_ptr = '\f'; break; + case 'n': *output_ptr = '\n'; break; + case 'r': *output_ptr = '\r'; break; + case 't': *output_ptr = '\t'; break; + case 'u': + if (parse_utf_16(&input_ptr, &output_ptr) == JSONFailure) + goto error; + break; + default: + goto error; + } + } else if ((unsigned char)*input_ptr < 0x20) { + goto error; /* 0x00-0x19 are invalid characters for json string (http://www.ietf.org/rfc/rfc4627.txt) */ + } else { + *output_ptr = *input_ptr; + } + output_ptr++; + input_ptr++; + } + *output_ptr = '\0'; + /* resize to new length */ + final_size = (size_t)(output_ptr-output) + 1; + resized_output = (char*)parson_malloc(final_size); + if (resized_output == NULL) + goto error; + memcpy(resized_output, output, final_size); + parson_free(output); + return resized_output; +error: + parson_free(output); + return NULL; +} + +/* Return processed contents of a string between quotes and + skips passed argument to a matching quote. */ +static char * get_quoted_string(const char **string) { + const char *string_start = *string; + size_t string_len = 0; + skip_quotes(string); + if (**string == '\0') + return NULL; + string_len = *string - string_start - 2; /* length without quotes */ + return process_string(string_start + 1, string_len); +} + +static JSON_Value * parse_value(const char **string, size_t nesting) { + if (nesting > MAX_NESTING) + return NULL; + SKIP_WHITESPACES(string); + switch (**string) { + case '{': + return parse_object_value(string, nesting + 1); + case '[': + return parse_array_value(string, nesting + 1); + case '\"': + return parse_string_value(string); + case 'f': case 't': + return parse_boolean_value(string); + case '-': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + return parse_number_value(string); + case 'n': + return parse_null_value(string); + default: + return NULL; + } +} + +static JSON_Value * parse_object_value(const char **string, size_t nesting) { + JSON_Value *output_value = json_value_init_object(), *new_value = NULL; + JSON_Object *output_object = json_value_get_object(output_value); + char *new_key = NULL; + if (output_value == NULL) + return NULL; + SKIP_CHAR(string); + SKIP_WHITESPACES(string); + if (**string == '}') { /* empty object */ + SKIP_CHAR(string); + return output_value; + } + while (**string != '\0') { + new_key = get_quoted_string(string); + SKIP_WHITESPACES(string); + if (new_key == NULL || **string != ':') { + json_value_free(output_value); + return NULL; + } + SKIP_CHAR(string); + new_value = parse_value(string, nesting); + if (new_value == NULL) { + parson_free(new_key); + json_value_free(output_value); + return NULL; + } + if(json_object_add(output_object, new_key, new_value) == JSONFailure) { + parson_free(new_key); + parson_free(new_value); + json_value_free(output_value); + return NULL; + } + parson_free(new_key); + SKIP_WHITESPACES(string); + if (**string != ',') + break; + SKIP_CHAR(string); + SKIP_WHITESPACES(string); + } + SKIP_WHITESPACES(string); + if (**string != '}' || /* Trim object after parsing is over */ + json_object_resize(output_object, json_object_get_count(output_object)) == JSONFailure) { + json_value_free(output_value); + return NULL; + } + SKIP_CHAR(string); + return output_value; +} + +static JSON_Value * parse_array_value(const char **string, size_t nesting) { + JSON_Value *output_value = json_value_init_array(), *new_array_value = NULL; + JSON_Array *output_array = json_value_get_array(output_value); + if (!output_value) + return NULL; + SKIP_CHAR(string); + SKIP_WHITESPACES(string); + if (**string == ']') { /* empty array */ + SKIP_CHAR(string); + return output_value; + } + while (**string != '\0') { + new_array_value = parse_value(string, nesting); + if (!new_array_value) { + json_value_free(output_value); + return NULL; + } + if(json_array_add(output_array, new_array_value) == JSONFailure) { + parson_free(new_array_value); + json_value_free(output_value); + return NULL; + } + SKIP_WHITESPACES(string); + if (**string != ',') + break; + SKIP_CHAR(string); + SKIP_WHITESPACES(string); + } + SKIP_WHITESPACES(string); + if (**string != ']' || /* Trim array after parsing is over */ + json_array_resize(output_array, json_array_get_count(output_array)) == JSONFailure) { + json_value_free(output_value); + return NULL; + } + SKIP_CHAR(string); + return output_value; +} + +static JSON_Value * parse_string_value(const char **string) { + JSON_Value *value = NULL; + char *new_string = get_quoted_string(string); + if (new_string == NULL) + return NULL; + value = json_value_init_string_no_copy(new_string); + if (value == NULL) { + parson_free(new_string); + return NULL; + } + return value; +} + +static JSON_Value * parse_boolean_value(const char **string) { + size_t true_token_size = SIZEOF_TOKEN("true"); + size_t false_token_size = SIZEOF_TOKEN("false"); + if (strncmp("true", *string, true_token_size) == 0) { + *string += true_token_size; + return json_value_init_boolean(1); + } else if (strncmp("false", *string, false_token_size) == 0) { + *string += false_token_size; + return json_value_init_boolean(0); + } + return NULL; +} + +static JSON_Value * parse_number_value(const char **string) { + char *end; + double number = strtod(*string, &end); + JSON_Value *output_value; + if (is_decimal(*string, end - *string)) { + *string = end; + output_value = json_value_init_number(number); + } else { + output_value = NULL; + } + return output_value; +} + +static JSON_Value * parse_null_value(const char **string) { + size_t token_size = SIZEOF_TOKEN("null"); + if (strncmp("null", *string, token_size) == 0) { + *string += token_size; + return json_value_init_null(); + } + return NULL; +} + +/* Serialization */ +#define APPEND_STRING(str) do { written = append_string(buf, (str)); \ + if (written < 0) { return -1; } \ + if (buf != NULL) { buf += written; } \ + written_total += written; } while(0) + +#define APPEND_INDENT(level) do { written = append_indent(buf, (level)); \ + if (written < 0) { return -1; } \ + if (buf != NULL) { buf += written; } \ + written_total += written; } while(0) + +static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, int is_pretty, char *num_buf) +{ + const char *key = NULL, *string = NULL; + JSON_Value *temp_value = NULL; + JSON_Array *array = NULL; + JSON_Object *object = NULL; + size_t i = 0, count = 0; + double num = 0.0; + int written = -1, written_total = 0; + + switch (json_value_get_type(value)) { + case JSONArray: + array = json_value_get_array(value); + count = json_array_get_count(array); + APPEND_STRING("["); + if (count > 0 && is_pretty) + APPEND_STRING("\n"); + for (i = 0; i < count; i++) { + if (is_pretty) + APPEND_INDENT(level+1); + temp_value = json_array_get_value(array, i); + written = json_serialize_to_buffer_r(temp_value, buf, level+1, is_pretty, num_buf); + if (written < 0) + return -1; + if (buf != NULL) + buf += written; + written_total += written; + if (i < (count - 1)) + APPEND_STRING(","); + if (is_pretty) + APPEND_STRING("\n"); + } + if (count > 0 && is_pretty) + APPEND_INDENT(level); + APPEND_STRING("]"); + return written_total; + case JSONObject: + object = json_value_get_object(value); + count = json_object_get_count(object); + APPEND_STRING("{"); + if (count > 0 && is_pretty) + APPEND_STRING("\n"); + for (i = 0; i < count; i++) { + key = json_object_get_name(object, i); + if (is_pretty) + APPEND_INDENT(level+1); + written = json_serialize_string(key, buf); + if (written < 0) + return -1; + if (buf != NULL) + buf += written; + written_total += written; + APPEND_STRING(":"); + if (is_pretty) + APPEND_STRING(" "); + temp_value = json_object_get_value(object, key); + written = json_serialize_to_buffer_r(temp_value, buf, level+1, is_pretty, num_buf); + if (written < 0) + return -1; + if (buf != NULL) + buf += written; + written_total += written; + if (i < (count - 1)) + APPEND_STRING(","); + if (is_pretty) + APPEND_STRING("\n"); + } + if (count > 0 && is_pretty) + APPEND_INDENT(level); + APPEND_STRING("}"); + return written_total; + case JSONString: + string = json_value_get_string(value); + written = json_serialize_string(string, buf); + if (written < 0) + return -1; + if (buf != NULL) + buf += written; + written_total += written; + return written_total; + case JSONBoolean: + if (json_value_get_boolean(value)) + APPEND_STRING("true"); + else + APPEND_STRING("false"); + return written_total; + case JSONNumber: + num = json_value_get_number(value); + if (buf != NULL) + num_buf = buf; + if (num == ((double)(int)num)) /* check if num is integer */ + written = sprintf(num_buf, "%d", (int)num); + else + written = sprintf(num_buf, DOUBLE_SERIALIZATION_FORMAT, num); + if (written < 0) + return -1; + if (buf != NULL) + buf += written; + written_total += written; + return written_total; + case JSONNull: + APPEND_STRING("null"); + return written_total; + case JSONError: + return -1; + default: + return -1; + } +} + +static int json_serialize_string(const char *string, char *buf) { + size_t i = 0, len = strlen(string); + char c = '\0'; + int written = -1, written_total = 0; + APPEND_STRING("\""); + for (i = 0; i < len; i++) { + c = string[i]; + switch (c) { + case '\"': APPEND_STRING("\\\""); break; + case '\\': APPEND_STRING("\\\\"); break; + case '/': APPEND_STRING("\\/"); break; /* to make json embeddable in xml\/html */ + case '\b': APPEND_STRING("\\b"); break; + case '\f': APPEND_STRING("\\f"); break; + case '\n': APPEND_STRING("\\n"); break; + case '\r': APPEND_STRING("\\r"); break; + case '\t': APPEND_STRING("\\t"); break; + default: + if (buf != NULL) { + buf[0] = c; + buf += 1; + } + written_total += 1; + break; + } + } + APPEND_STRING("\""); + return written_total; +} + +static int append_indent(char *buf, int level) { + int i; + int written = -1, written_total = 0; + for (i = 0; i < level; i++) { + APPEND_STRING(" "); + } + return written_total; +} + +static int append_string(char *buf, const char *string) { + if (buf == NULL) { + return (int)strlen(string); + } + return sprintf(buf, "%s", string); +} + +#undef APPEND_STRING +#undef APPEND_INDENT + +/* Parser API */ +JSON_Value * json_parse_file(const char *filename) { + char *file_contents = read_file(filename); + JSON_Value *output_value = NULL; + if (file_contents == NULL) + return NULL; + output_value = json_parse_string(file_contents); + parson_free(file_contents); + return output_value; +} + +JSON_Value * json_parse_file_with_comments(const char *filename) { + char *file_contents = read_file(filename); + JSON_Value *output_value = NULL; + if (file_contents == NULL) + return NULL; + output_value = json_parse_string_with_comments(file_contents); + parson_free(file_contents); + return output_value; +} + +JSON_Value * json_parse_string(const char *string) { + if (string == NULL) + return NULL; + SKIP_WHITESPACES(&string); + if (*string != '{' && *string != '[') + return NULL; + return parse_value((const char**)&string, 0); +} + +JSON_Value * json_parse_string_with_comments(const char *string) { + JSON_Value *result = NULL; + char *string_mutable_copy = NULL, *string_mutable_copy_ptr = NULL; + string_mutable_copy = parson_strdup(string); + if (string_mutable_copy == NULL) + return NULL; + remove_comments(string_mutable_copy, "/*", "*/"); + remove_comments(string_mutable_copy, "//", "\n"); + string_mutable_copy_ptr = string_mutable_copy; + SKIP_WHITESPACES(&string_mutable_copy_ptr); + if (*string_mutable_copy_ptr != '{' && *string_mutable_copy_ptr != '[') { + parson_free(string_mutable_copy); + return NULL; + } + result = parse_value((const char**)&string_mutable_copy_ptr, 0); + parson_free(string_mutable_copy); + return result; +} + + +/* JSON Object API */ + +JSON_Value * json_object_get_value(const JSON_Object *object, const char *name) { + if (object == NULL || name == NULL) + return NULL; + return json_object_nget_value(object, name, strlen(name)); +} + +const char * json_object_get_string(const JSON_Object *object, const char *name) { + return json_value_get_string(json_object_get_value(object, name)); +} + +double json_object_get_number(const JSON_Object *object, const char *name) { + return json_value_get_number(json_object_get_value(object, name)); +} + +JSON_Object * json_object_get_object(const JSON_Object *object, const char *name) { + return json_value_get_object(json_object_get_value(object, name)); +} + +JSON_Array * json_object_get_array(const JSON_Object *object, const char *name) { + return json_value_get_array(json_object_get_value(object, name)); +} + +int json_object_get_boolean(const JSON_Object *object, const char *name) { + return json_value_get_boolean(json_object_get_value(object, name)); +} + +JSON_Value * json_object_dotget_value(const JSON_Object *object, const char *name) { + const char *dot_position = strchr(name, '.'); + if (!dot_position) + return json_object_get_value(object, name); + object = json_value_get_object(json_object_nget_value(object, name, dot_position - name)); + return json_object_dotget_value(object, dot_position + 1); +} + +const char * json_object_dotget_string(const JSON_Object *object, const char *name) { + return json_value_get_string(json_object_dotget_value(object, name)); +} + +double json_object_dotget_number(const JSON_Object *object, const char *name) { + return json_value_get_number(json_object_dotget_value(object, name)); +} + +JSON_Object * json_object_dotget_object(const JSON_Object *object, const char *name) { + return json_value_get_object(json_object_dotget_value(object, name)); +} + +JSON_Array * json_object_dotget_array(const JSON_Object *object, const char *name) { + return json_value_get_array(json_object_dotget_value(object, name)); +} + +int json_object_dotget_boolean(const JSON_Object *object, const char *name) { + return json_value_get_boolean(json_object_dotget_value(object, name)); +} + +size_t json_object_get_count(const JSON_Object *object) { + return object ? object->count : 0; +} + +const char * json_object_get_name(const JSON_Object *object, size_t index) { + if (index >= json_object_get_count(object)) + return NULL; + return object->names[index]; +} + +/* JSON Array API */ +JSON_Value * json_array_get_value(const JSON_Array *array, size_t index) { + if (index >= json_array_get_count(array)) + return NULL; + return array->items[index]; +} + +const char * json_array_get_string(const JSON_Array *array, size_t index) { + return json_value_get_string(json_array_get_value(array, index)); +} + +double json_array_get_number(const JSON_Array *array, size_t index) { + return json_value_get_number(json_array_get_value(array, index)); +} + +JSON_Object * json_array_get_object(const JSON_Array *array, size_t index) { + return json_value_get_object(json_array_get_value(array, index)); +} + +JSON_Array * json_array_get_array(const JSON_Array *array, size_t index) { + return json_value_get_array(json_array_get_value(array, index)); +} + +int json_array_get_boolean(const JSON_Array *array, size_t index) { + return json_value_get_boolean(json_array_get_value(array, index)); +} + +size_t json_array_get_count(const JSON_Array *array) { + return array ? array->count : 0; +} + +/* JSON Value API */ +JSON_Value_Type json_value_get_type(const JSON_Value *value) { + return value ? value->type : JSONError; +} + +JSON_Object * json_value_get_object(const JSON_Value *value) { + return json_value_get_type(value) == JSONObject ? value->value.object : NULL; +} + +JSON_Array * json_value_get_array(const JSON_Value *value) { + return json_value_get_type(value) == JSONArray ? value->value.array : NULL; +} + +const char * json_value_get_string(const JSON_Value *value) { + return json_value_get_type(value) == JSONString ? value->value.string : NULL; +} + +double json_value_get_number(const JSON_Value *value) { + return json_value_get_type(value) == JSONNumber ? value->value.number : 0; +} + +int json_value_get_boolean(const JSON_Value *value) { + return json_value_get_type(value) == JSONBoolean ? value->value.boolean : -1; +} + +void json_value_free(JSON_Value *value) { + switch (json_value_get_type(value)) { + case JSONObject: + json_object_free(value->value.object); + break; + case JSONString: + if (value->value.string) { parson_free(value->value.string); } + break; + case JSONArray: + json_array_free(value->value.array); + break; + default: + break; + } + parson_free(value); +} + +JSON_Value * json_value_init_object(void) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) + return NULL; + new_value->type = JSONObject; + new_value->value.object = json_object_init(); + if (!new_value->value.object) { + parson_free(new_value); + return NULL; + } + return new_value; +} + +JSON_Value * json_value_init_array(void) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) + return NULL; + new_value->type = JSONArray; + new_value->value.array = json_array_init(); + if (!new_value->value.array) { + parson_free(new_value); + return NULL; + } + return new_value; +} + +JSON_Value * json_value_init_string(const char *string) { + char *copy = NULL; + JSON_Value *value; + size_t string_len = 0; + if (string == NULL) + return NULL; + string_len = strlen(string); + if (!is_valid_utf8(string, string_len)) + return NULL; + copy = parson_strndup(string, string_len); + if (copy == NULL) + return NULL; + value = json_value_init_string_no_copy(copy); + if (value == NULL) + parson_free(copy); + return value; +} + +JSON_Value * json_value_init_number(double number) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) + return NULL; + new_value->type = JSONNumber; + new_value->value.number = number; + return new_value; +} + +JSON_Value * json_value_init_boolean(int boolean) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) + return NULL; + new_value->type = JSONBoolean; + new_value->value.boolean = boolean ? 1 : 0; + return new_value; +} + +JSON_Value * json_value_init_null(void) { + JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); + if (!new_value) + return NULL; + new_value->type = JSONNull; + return new_value; +} + +JSON_Value * json_value_deep_copy(const JSON_Value *value) { + size_t i = 0; + JSON_Value *return_value = NULL, *temp_value_copy = NULL, *temp_value = NULL; + const char *temp_string = NULL, *temp_key = NULL; + char *temp_string_copy = NULL; + JSON_Array *temp_array = NULL, *temp_array_copy = NULL; + JSON_Object *temp_object = NULL, *temp_object_copy = NULL; + + switch (json_value_get_type(value)) { + case JSONArray: + temp_array = json_value_get_array(value); + return_value = json_value_init_array(); + if (return_value == NULL) + return NULL; + temp_array_copy = json_value_get_array(return_value); + for (i = 0; i < json_array_get_count(temp_array); i++) { + temp_value = json_array_get_value(temp_array, i); + temp_value_copy = json_value_deep_copy(temp_value); + if (temp_value_copy == NULL) { + json_value_free(return_value); + return NULL; + } + if (json_array_add(temp_array_copy, temp_value_copy) == JSONFailure) { + json_value_free(return_value); + json_value_free(temp_value_copy); + return NULL; + } + } + return return_value; + case JSONObject: + temp_object = json_value_get_object(value); + return_value = json_value_init_object(); + if (return_value == NULL) + return NULL; + temp_object_copy = json_value_get_object(return_value); + for (i = 0; i < json_object_get_count(temp_object); i++) { + temp_key = json_object_get_name(temp_object, i); + temp_value = json_object_get_value(temp_object, temp_key); + temp_value_copy = json_value_deep_copy(temp_value); + if (temp_value_copy == NULL) { + json_value_free(return_value); + return NULL; + } + if (json_object_add(temp_object_copy, temp_key, temp_value_copy) == JSONFailure) { + json_value_free(return_value); + json_value_free(temp_value_copy); + return NULL; + } + } + return return_value; + case JSONBoolean: + return json_value_init_boolean(json_value_get_boolean(value)); + case JSONNumber: + return json_value_init_number(json_value_get_number(value)); + case JSONString: + temp_string = json_value_get_string(value); + temp_string_copy = parson_strdup(temp_string); + if (temp_string_copy == NULL) + return NULL; + return_value = json_value_init_string_no_copy(temp_string_copy); + if (return_value == NULL) + parson_free(temp_string_copy); + return return_value; + case JSONNull: + return json_value_init_null(); + case JSONError: + return NULL; + default: + return NULL; + } +} + +size_t json_serialization_size(const JSON_Value *value) { + char num_buf[1100]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */ + int res = json_serialize_to_buffer_r(value, NULL, 0, 0, num_buf); + return res < 0 ? 0 : (size_t)(res + 1); +} + +JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) { + int written = -1; + size_t needed_size_in_bytes = json_serialization_size(value); + if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes) { + return JSONFailure; + } + written = json_serialize_to_buffer_r(value, buf, 0, 0, NULL); + if (written < 0) + return JSONFailure; + return JSONSuccess; +} + +JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename) { + JSON_Status return_code = JSONSuccess; + FILE *fp = NULL; + char *serialized_string = json_serialize_to_string(value); + if (serialized_string == NULL) { + return JSONFailure; + } + fp = fopen (filename, "w"); + if (fp != NULL) { + if (fputs (serialized_string, fp) == EOF) { + return_code = JSONFailure; + } + if (fclose (fp) == EOF) { + return_code = JSONFailure; + } + } + json_free_serialized_string(serialized_string); + return return_code; +} + +char * json_serialize_to_string(const JSON_Value *value) { + JSON_Status serialization_result = JSONFailure; + size_t buf_size_bytes = json_serialization_size(value); + char *buf = NULL; + if (buf_size_bytes == 0) { + return NULL; + } + buf = (char*)parson_malloc(buf_size_bytes); + if (buf == NULL) + return NULL; + serialization_result = json_serialize_to_buffer(value, buf, buf_size_bytes); + if (serialization_result == JSONFailure) { + json_free_serialized_string(buf); + return NULL; + } + return buf; +} + +size_t json_serialization_size_pretty(const JSON_Value *value) { + char num_buf[1100]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */ + int res = json_serialize_to_buffer_r(value, NULL, 0, 1, num_buf); + return res < 0 ? 0 : (size_t)(res + 1); +} + +JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) { + int written = -1; + size_t needed_size_in_bytes = json_serialization_size_pretty(value); + if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes) + return JSONFailure; + written = json_serialize_to_buffer_r(value, buf, 0, 1, NULL); + if (written < 0) + return JSONFailure; + return JSONSuccess; +} + +JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename) { + JSON_Status return_code = JSONSuccess; + FILE *fp = NULL; + char *serialized_string = json_serialize_to_string_pretty(value); + if (serialized_string == NULL) { + return JSONFailure; + } + fp = fopen (filename, "w"); + if (fp != NULL) { + if (fputs (serialized_string, fp) == EOF) { + return_code = JSONFailure; + } + if (fclose (fp) == EOF) { + return_code = JSONFailure; + } + } + json_free_serialized_string(serialized_string); + return return_code; +} + +char * json_serialize_to_string_pretty(const JSON_Value *value) { + JSON_Status serialization_result = JSONFailure; + size_t buf_size_bytes = json_serialization_size_pretty(value); + char *buf = NULL; + if (buf_size_bytes == 0) { + return NULL; + } + buf = (char*)parson_malloc(buf_size_bytes); + if (buf == NULL) + return NULL; + serialization_result = json_serialize_to_buffer_pretty(value, buf, buf_size_bytes); + if (serialization_result == JSONFailure) { + json_free_serialized_string(buf); + return NULL; + } + return buf; +} + +void json_free_serialized_string(char *string) { + parson_free(string); +} + +JSON_Status json_array_remove(JSON_Array *array, size_t ix) { + JSON_Value *temp_value = NULL; + size_t last_element_ix = 0; + if (array == NULL || ix >= json_array_get_count(array)) { + return JSONFailure; + } + last_element_ix = json_array_get_count(array) - 1; + json_value_free(json_array_get_value(array, ix)); + if (ix != last_element_ix) { /* Replace value with one from the end of array */ + temp_value = json_array_get_value(array, last_element_ix); + if (temp_value == NULL) { + return JSONFailure; + } + array->items[ix] = temp_value; + } + array->count -= 1; + return JSONSuccess; +} + +JSON_Status json_array_replace_value(JSON_Array *array, size_t ix, JSON_Value *value) { + if (array == NULL || value == NULL || ix >= json_array_get_count(array)) { + return JSONFailure; + } + json_value_free(json_array_get_value(array, ix)); + array->items[ix] = value; + return JSONSuccess; +} + +JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string) { + JSON_Value *value = json_value_init_string(string); + if (value == NULL) + return JSONFailure; + if (json_array_replace_value(array, i, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number) { + JSON_Value *value = json_value_init_number(number); + if (value == NULL) + return JSONFailure; + if (json_array_replace_value(array, i, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean) { + JSON_Value *value = json_value_init_boolean(boolean); + if (value == NULL) + return JSONFailure; + if (json_array_replace_value(array, i, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_replace_null(JSON_Array *array, size_t i) { + JSON_Value *value = json_value_init_null(); + if (value == NULL) + return JSONFailure; + if (json_array_replace_value(array, i, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_clear(JSON_Array *array) { + size_t i = 0; + if (array == NULL) + return JSONFailure; + for (i = 0; i < json_array_get_count(array); i++) { + json_value_free(json_array_get_value(array, i)); + } + array->count = 0; + return JSONSuccess; +} + +JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value) { + if (array == NULL || value == NULL) + return JSONFailure; + return json_array_add(array, value); +} + +JSON_Status json_array_append_string(JSON_Array *array, const char *string) { + JSON_Value *value = json_value_init_string(string); + if (value == NULL) + return JSONFailure; + if (json_array_append_value(array, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_append_number(JSON_Array *array, double number) { + JSON_Value *value = json_value_init_number(number); + if (value == NULL) + return JSONFailure; + if (json_array_append_value(array, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_append_boolean(JSON_Array *array, int boolean) { + JSON_Value *value = json_value_init_boolean(boolean); + if (value == NULL) + return JSONFailure; + if (json_array_append_value(array, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_append_null(JSON_Array *array) { + JSON_Value *value = json_value_init_null(); + if (value == NULL) + return JSONFailure; + if (json_array_append_value(array, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value) { + size_t i = 0; + JSON_Value *old_value; + if (object == NULL || name == NULL || value == NULL) + return JSONFailure; + old_value = json_object_get_value(object, name); + if (old_value != NULL) { /* free and overwrite old value */ + json_value_free(old_value); + for (i = 0; i < json_object_get_count(object); i++) { + if (strcmp(object->names[i], name) == 0) { + object->values[i] = value; + return JSONSuccess; + } + } + } + /* add new key value pair */ + return json_object_add(object, name, value); +} + +JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string) { + return json_object_set_value(object, name, json_value_init_string(string)); +} + +JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number) { + return json_object_set_value(object, name, json_value_init_number(number)); +} + +JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean) { + return json_object_set_value(object, name, json_value_init_boolean(boolean)); +} + +JSON_Status json_object_set_null(JSON_Object *object, const char *name) { + return json_object_set_value(object, name, json_value_init_null()); +} + +JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value) { + const char *dot_pos = NULL; + char *current_name = NULL; + JSON_Object *temp_obj = NULL; + JSON_Value *new_value = NULL; + if (value == NULL || name == NULL || value == NULL) + return JSONFailure; + dot_pos = strchr(name, '.'); + if (dot_pos == NULL) { + return json_object_set_value(object, name, value); + } else { + current_name = parson_strndup(name, dot_pos - name); + temp_obj = json_object_get_object(object, current_name); + if (temp_obj == NULL) { + new_value = json_value_init_object(); + if (new_value == NULL) { + parson_free(current_name); + return JSONFailure; + } + if (json_object_add(object, current_name, new_value) == JSONFailure) { + json_value_free(new_value); + parson_free(current_name); + return JSONFailure; + } + temp_obj = json_object_get_object(object, current_name); + } + parson_free(current_name); + return json_object_dotset_value(temp_obj, dot_pos + 1, value); + } +} + +JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string) { + JSON_Value *value = json_value_init_string(string); + if (value == NULL) + return JSONFailure; + if (json_object_dotset_value(object, name, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number) { + JSON_Value *value = json_value_init_number(number); + if (value == NULL) + return JSONFailure; + if (json_object_dotset_value(object, name, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean) { + JSON_Value *value = json_value_init_boolean(boolean); + if (value == NULL) + return JSONFailure; + if (json_object_dotset_value(object, name, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_dotset_null(JSON_Object *object, const char *name) { + JSON_Value *value = json_value_init_null(); + if (value == NULL) + return JSONFailure; + if (json_object_dotset_value(object, name, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_remove(JSON_Object *object, const char *name) { + size_t i = 0, last_item_index = 0; + if (object == NULL || json_object_get_value(object, name) == NULL) + return JSONFailure; + last_item_index = json_object_get_count(object) - 1; + for (i = 0; i < json_object_get_count(object); i++) { + if (strcmp(object->names[i], name) == 0) { + parson_free(object->names[i]); + json_value_free(object->values[i]); + if (i != last_item_index) { /* Replace key value pair with one from the end */ + object->names[i] = object->names[last_item_index]; + object->values[i] = object->values[last_item_index]; + } + object->count -= 1; + return JSONSuccess; + } + } + return JSONFailure; /* No execution path should end here */ +} + +JSON_Status json_object_dotremove(JSON_Object *object, const char *name) { + const char *dot_pos = strchr(name, '.'); + char *current_name = NULL; + JSON_Object *temp_obj = NULL; + if (dot_pos == NULL) { + return json_object_remove(object, name); + } else { + current_name = parson_strndup(name, dot_pos - name); + temp_obj = json_object_get_object(object, current_name); + if (temp_obj == NULL) { + parson_free(current_name); + return JSONFailure; + } + parson_free(current_name); + return json_object_dotremove(temp_obj, dot_pos + 1); + } +} + +JSON_Status json_object_clear(JSON_Object *object) { + size_t i = 0; + if (object == NULL) { + return JSONFailure; + } + for (i = 0; i < json_object_get_count(object); i++) { + parson_free(object->names[i]); + json_value_free(object->values[i]); + } + object->count = 0; + return JSONSuccess; +} + +JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value) { + JSON_Value *temp_schema_value = NULL, *temp_value = NULL; + JSON_Array *schema_array = NULL, *value_array = NULL; + JSON_Object *schema_object = NULL, *value_object = NULL; + JSON_Value_Type schema_type = JSONError, value_type = JSONError; + const char *key = NULL; + size_t i = 0, count = 0; + if (schema == NULL || value == NULL) + return JSONFailure; + schema_type = json_value_get_type(schema); + value_type = json_value_get_type(value); + if (schema_type != value_type && schema_type != JSONNull) /* null represents all values */ + return JSONFailure; + switch (schema_type) { + case JSONArray: + schema_array = json_value_get_array(schema); + value_array = json_value_get_array(value); + count = json_array_get_count(schema_array); + if (count == 0) + return JSONSuccess; /* Empty array allows all types */ + /* Get first value from array, rest is ignored */ + temp_schema_value = json_array_get_value(schema_array, 0); + for (i = 0; i < json_array_get_count(value_array); i++) { + temp_value = json_array_get_value(value_array, i); + if (json_validate(temp_schema_value, temp_value) == 0) { + return JSONFailure; + } + } + return JSONSuccess; + case JSONObject: + schema_object = json_value_get_object(schema); + value_object = json_value_get_object(value); + count = json_object_get_count(schema_object); + if (count == 0) + return JSONSuccess; /* Empty object allows all objects */ + else if (json_object_get_count(value_object) < count) + return JSONFailure; /* Tested object mustn't have less name-value pairs than schema */ + for (i = 0; i < count; i++) { + key = json_object_get_name(schema_object, i); + temp_schema_value = json_object_get_value(schema_object, key); + temp_value = json_object_get_value(value_object, key); + if (temp_value == NULL) + return JSONFailure; + if (json_validate(temp_schema_value, temp_value) == JSONFailure) + return JSONFailure; + } + return JSONSuccess; + case JSONString: case JSONNumber: case JSONBoolean: case JSONNull: + return JSONSuccess; /* equality already tested before switch */ + case JSONError: default: + return JSONFailure; + } +} + +JSON_Status json_value_equals(const JSON_Value *a, const JSON_Value *b) { + JSON_Object *a_object = NULL, *b_object = NULL; + JSON_Array *a_array = NULL, *b_array = NULL; + const char *a_string = NULL, *b_string = NULL; + const char *key = NULL; + size_t a_count = 0, b_count = 0, i = 0; + JSON_Value_Type a_type, b_type; + a_type = json_value_get_type(a); + b_type = json_value_get_type(b); + if (a_type != b_type) { + return 0; + } + switch (a_type) { + case JSONArray: + a_array = json_value_get_array(a); + b_array = json_value_get_array(b); + a_count = json_array_get_count(a_array); + b_count = json_array_get_count(b_array); + if (a_count != b_count) { + return 0; + } + for (i = 0; i < a_count; i++) { + if (!json_value_equals(json_array_get_value(a_array, i), + json_array_get_value(b_array, i))) { + return 0; + } + } + return 1; + case JSONObject: + a_object = json_value_get_object(a); + b_object = json_value_get_object(b); + a_count = json_object_get_count(a_object); + b_count = json_object_get_count(b_object); + if (a_count != b_count) { + return 0; + } + for (i = 0; i < a_count; i++) { + key = json_object_get_name(a_object, i); + if (!json_value_equals(json_object_get_value(a_object, key), + json_object_get_value(b_object, key))) { + return 0; + } + } + return 1; + case JSONString: + a_string = json_value_get_string(a); + b_string = json_value_get_string(b); + return strcmp(a_string, b_string) == 0; + case JSONBoolean: + return json_value_get_boolean(a) == json_value_get_boolean(b); + case JSONNumber: + return fabs(json_value_get_number(a) - json_value_get_number(b)) < 0.000001; /* EPSILON */ + case JSONError: + return 1; + case JSONNull: + return 1; + default: + return 1; + } +} + +JSON_Value_Type json_type(const JSON_Value *value) { + return json_value_get_type(value); +} + +JSON_Object * json_object (const JSON_Value *value) { + return json_value_get_object(value); +} + +JSON_Array * json_array (const JSON_Value *value) { + return json_value_get_array(value); +} + +const char * json_string (const JSON_Value *value) { + return json_value_get_string(value); +} + +double json_number (const JSON_Value *value) { + return json_value_get_number(value); +} + +int json_boolean(const JSON_Value *value) { + return json_value_get_boolean(value); +} + +void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun) { + parson_malloc = malloc_fun; + parson_free = free_fun; +} diff --git a/libtools/src/tinymt32.c b/libtools/src/tinymt32.c new file mode 100644 index 0000000..42db89b --- /dev/null +++ b/libtools/src/tinymt32.c @@ -0,0 +1,145 @@ +/** + * @file tinymt32.c + * + * @brief Tiny Mersenne Twister only 127 bit internal state + * + * @author Mutsuo Saito (Hiroshima University) + * @author Makoto Matsumoto (The University of Tokyo) + * + * Copyright (C) 2011 Mutsuo Saito, Makoto Matsumoto, + * Hiroshima University and The University of Tokyo. + * All rights reserved. + * + * The 3-clause BSD License is applied to this software, see + * LICENSE.txt + */ +#include "tinymt32.h" +#define MIN_LOOP 8 +#define PRE_LOOP 8 + +/** + * This function represents a function used in the initialization + * by init_by_array + * @param x 32-bit integer + * @return 32-bit integer + */ +static uint32_t ini_func1(uint32_t x) { + return (x ^ (x >> 27)) * UINT32_C(1664525); +} + +/** + * This function represents a function used in the initialization + * by init_by_array + * @param x 32-bit integer + * @return 32-bit integer + */ +static uint32_t ini_func2(uint32_t x) { + return (x ^ (x >> 27)) * UINT32_C(1566083941); +} + +/** + * This function certificate the period of 2^127-1. + * @param random tinymt state vector. + */ +static void period_certification(tinymt32_t * random) { + if ((random->status[0] & TINYMT32_MASK) == 0 && + random->status[1] == 0 && + random->status[2] == 0 && + random->status[3] == 0) { + random->status[0] = 'T'; + random->status[1] = 'I'; + random->status[2] = 'N'; + random->status[3] = 'Y'; + } +} + +/** + * This function initializes the internal state array with a 32-bit + * unsigned integer seed. + * @param random tinymt state vector. + * @param seed a 32-bit unsigned integer used as a seed. + */ +void tinymt32_init(tinymt32_t * random, uint32_t seed) { + random->status[0] = seed; + random->status[1] = random->mat1; + random->status[2] = random->mat2; + random->status[3] = random->tmat; + for (int i = 1; i < MIN_LOOP; i++) { + random->status[i & 3] ^= i + UINT32_C(1812433253) + * (random->status[(i - 1) & 3] + ^ (random->status[(i - 1) & 3] >> 30)); + } + period_certification(random); + for (int i = 0; i < PRE_LOOP; i++) { + tinymt32_next_state(random); + } +} + +/** + * This function initializes the internal state array, + * with an array of 32-bit unsigned integers used as seeds + * @param random tinymt state vector. + * @param init_key the array of 32-bit integers, used as a seed. + * @param key_length the length of init_key. + */ +void tinymt32_init_by_array(tinymt32_t * random, uint32_t init_key[], + int key_length) { + const int lag = 1; + const int mid = 1; + const int size = 4; + int i, j; + int count; + uint32_t r; + uint32_t * st = &random->status[0]; + + st[0] = 0; + st[1] = random->mat1; + st[2] = random->mat2; + st[3] = random->tmat; + if (key_length + 1 > MIN_LOOP) { + count = key_length + 1; + } else { + count = MIN_LOOP; + } + r = ini_func1(st[0] ^ st[mid % size] + ^ st[(size - 1) % size]); + st[mid % size] += r; + r += key_length; + st[(mid + lag) % size] += r; + st[0] = r; + count--; + for (i = 1, j = 0; (j < count) && (j < key_length); j++) { + r = ini_func1(st[i % size] + ^ st[(i + mid) % size] + ^ st[(i + size - 1) % size]); + st[(i + mid) % size] += r; + r += init_key[j] + i; + st[(i + mid + lag) % size] += r; + st[i % size] = r; + i = (i + 1) % size; + } + for (; j < count; j++) { + r = ini_func1(st[i % size] + ^ st[(i + mid) % size] + ^ st[(i + size - 1) % size]); + st[(i + mid) % size] += r; + r += i; + st[(i + mid + lag) % size] += r; + st[i % size] = r; + i = (i + 1) % size; + } + for (j = 0; j < size; j++) { + r = ini_func2(st[i % size] + + st[(i + mid) % size] + + st[(i + size - 1) % size]); + st[(i + mid) % size] ^= r; + r -= i; + st[(i + mid + lag) % size] ^= r; + st[i % size] = r; + i = (i + 1) % size; + } + period_certification(random); + for (i = 0; i < PRE_LOOP; i++) { + tinymt32_next_state(random); + } +} diff --git a/packet_forwarder/Makefile b/packet_forwarder/Makefile new file mode 100644 index 0000000..7075b42 --- /dev/null +++ b/packet_forwarder/Makefile @@ -0,0 +1,93 @@ +### get external defined data + +include ../target.cfg + +### Application-specific constants + +APP_NAME := lora_pkt_fwd + +### Environment constants + +LGW_PATH ?= ../libloragw +LIB_PATH ?= ../libtools +ARCH ?= +CROSS_COMPILE ?= + +OBJDIR = obj +INCLUDES = $(wildcard inc/*.h) + +### External constant definitions +# must get library build option to know if mpsse must be linked or not + +include $(LGW_PATH)/library.cfg +RELEASE_VERSION := `cat ../VERSION` + +### Constant symbols + +CC := $(CROSS_COMPILE)gcc +AR := $(CROSS_COMPILE)ar + +CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I. -I../libtools/inc +VFLAG := -D VERSION_STRING="\"$(RELEASE_VERSION)\"" + +### Constants for Lora concentrator HAL library +# List the library sub-modules that are used by the application + +LGW_INC = +ifneq ($(wildcard $(LGW_PATH)/inc/config.h),) + # only for HAL version 1.3 and beyond + LGW_INC += $(LGW_PATH)/inc/config.h +endif +LGW_INC += $(LGW_PATH)/inc/loragw_hal.h + +### Linking options + +LIBS := -lloragw -ltinymt32 -lparson -lbase64 -lrt -lpthread -lm + +### General build targets + +all: $(APP_NAME) + +clean: + rm -f $(OBJDIR)/*.o + rm -f $(APP_NAME) + +ifneq ($(strip $(TARGET_IP)),) + ifneq ($(strip $(TARGET_DIR)),) + ifneq ($(strip $(TARGET_USR)),) +install: + @echo "---- Copying packet_forwarder files to $(TARGET_IP):$(TARGET_DIR)" + @ssh $(TARGET_USR)@$(TARGET_IP) "mkdir -p $(TARGET_DIR)" + @scp lora_pkt_fwd $(TARGET_USR)@$(TARGET_IP):$(TARGET_DIR) +install_conf: + @echo "---- Copying packet_forwarder conf files to $(TARGET_IP):$(TARGET_DIR)" + @ssh $(TARGET_USR)@$(TARGET_IP) "mkdir -p $(TARGET_DIR)" + @scp global_conf.json.sx1250 $(TARGET_USR)@$(TARGET_IP):$(TARGET_DIR) + @scp global_conf.json.sx1257 $(TARGET_USR)@$(TARGET_IP):$(TARGET_DIR) + else + @echo "ERROR: TARGET_USR is not configured in target.cfg" + endif + else + @echo "ERROR: TARGET_DIR is not configured in target.cfg" + endif +else + @echo "ERROR: TARGET_IP is not configured in target.cfg" +endif + +### Sub-modules compilation + +$(OBJDIR): + mkdir -p $(OBJDIR) + +$(OBJDIR)/%.o: src/%.c $(INCLUDES) | $(OBJDIR) + $(CC) -c $(CFLAGS) -I$(LGW_PATH)/inc $< -o $@ + +### Main program compilation and assembly + +$(OBJDIR)/$(APP_NAME).o: src/$(APP_NAME).c $(LGW_INC) $(INCLUDES) | $(OBJDIR) + $(CC) -c $(CFLAGS) $(VFLAG) -I$(LGW_PATH)/inc $< -o $@ + +$(APP_NAME): $(OBJDIR)/$(APP_NAME).o $(LGW_PATH)/libloragw.a $(OBJDIR)/jitqueue.o + $(CC) -L$(LGW_PATH) -L$(LIB_PATH) $< $(OBJDIR)/jitqueue.o -o $@ $(LIBS) + +### EOF diff --git a/packet_forwarder/PROTOCOL.md b/packet_forwarder/PROTOCOL.md new file mode 100644 index 0000000..59f6c30 --- /dev/null +++ b/packet_forwarder/PROTOCOL.md @@ -0,0 +1,477 @@ + ______ _ + / _____) _ | | + ( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | + (______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Basic communication protocol between LoRa gateway and Network Server +==================================================================== + + +## 1. Introduction + +The protocol between the gateway and the server is purposefully very basic and +for demonstration purpose only, or for use on private and reliable networks. + +There is no authentication of the gateway or the server, and the acknowledges +are only used for network quality assessment, not to correct UDP datagrams +losses (no retries). + + +## 2. System schematic and definitions + + ((( Y ))) + | + | + + - -|- - - - - - - - - - - - - + xxxxxxxxxxxx +--------+ + | +--+-----------+ +------+ | xx x x xxx | | + | | | | | | xx Internet xx | | + | | Concentrator |<--->| Host |<-------xx or xx-------->| | + | | | SPI | | | xx Intranet xx | Server | + | +--------------+ +------+ | xxxx x xxxx | | + | ^ ^ | xxxxxxxx | | + | | PPS +-------+ NMEA | | | | + | +-----| GPS |-------+ | +--------+ + | | (opt) | | + | +-------+ | + | | + | Gateway | + +- - - - - - - - - - - - - - - -+ + +__Concentrator__: radio RX/TX board, based on Semtech multichannel modems +(SX130x), transceivers (SX135x) and/or low-power stand-alone modems (SX127x). + +__Host__: embedded computer on which the packet forwarder is run. Drives the +concentrator through a SPI link. + +__GPS__: GNSS (GPS, Galileo, GLONASS, etc) receiver with a "1 Pulse Per Second" +output and a serial link to the host to send NMEA frames containing time and +geographical coordinates data. Optional. + +__Gateway__: a device composed of at least one radio concentrator, a host, some +network connection to the internet or a private network (Ethernet, 3G, Wifi, +microwave link), and optionally a GPS receiver for synchronization. + +__Server__: an abstract computer that will process the RF packets received and +forwarded by the gateway, and issue RF packets in response that the gateway +will have to emit. + +It is assumed that the gateway can be behind a NAT or a firewall stopping any +incoming connection. +It is assumed that the server has an static IP address (or an address solvable +through a DNS service) and is able to receive incoming connections on a +specific port. + + +## 3. Upstream protocol + +### 3.1. Sequence diagram ### + + +---------+ +---------+ + | Gateway | | Server | + +---------+ +---------+ + | -----------------------------------\ | + |-| When 1-N RF packets are received | | + | ------------------------------------ | + | | + | PUSH_DATA (token X, GW MAC, JSON payload) | + |------------------------------------------------------------->| + | | + | PUSH_ACK (token X) | + |<-------------------------------------------------------------| + | ------------------------------\ | + | | process packets *after* ack |-| + | ------------------------------- | + | | + +### 3.2. PUSH_DATA packet ### + +That packet type is used by the gateway mainly to forward the RF packets +received, and associated metadata, to the server. + + Bytes | Function +:------:|--------------------------------------------------------------------- + 0 | protocol version = 2 + 1-2 | random token + 3 | PUSH_DATA identifier 0x00 + 4-11 | Gateway unique identifier (MAC address) + 12-end | JSON object, starting with {, ending with }, see section 4 + +### 3.3. PUSH_ACK packet ### + +That packet type is used by the server to acknowledge immediately all the +PUSH_DATA packets received. + + Bytes | Function +:------:|--------------------------------------------------------------------- + 0 | protocol version = 2 + 1-2 | same token as the PUSH_DATA packet to acknowledge + 3 | PUSH_ACK identifier 0x01 + + +## 4. Upstream JSON data structure + +The root object can contain an array named "rxpk": + +``` json +{ + "rxpk":[ {...}, ...] +} +``` + +That array contains at least one JSON object, each object contain a RF packet +and associated metadata with the following fields: + + Name | Type | Function +:----:|:------:|-------------------------------------------------------------- + time | string | UTC time of pkt RX, us precision, ISO 8601 'compact' format + tmms | number | GPS time of pkt RX, number of milliseconds since 06.Jan.1980 + tmst | number | Internal timestamp of "RX finished" event (32b unsigned) + freq | number | RX central frequency in MHz (unsigned float, Hz precision) + chan | number | Concentrator "IF" channel used for RX (unsigned integer) + rfch | number | Concentrator "RF chain" used for RX (unsigned integer) + mid | number | Concentrator modem ID on which pkt has been received + stat | number | CRC status: 1 = OK, -1 = fail, 0 = no CRC + modu | string | Modulation identifier "LORA" or "FSK" + datr | string | LoRa datarate identifier (eg. SF12BW500) + datr | number | FSK datarate (unsigned, in bits per second) + codr | string | LoRa ECC coding rate identifier + rssi | number | RSSI of the channel in dBm (signed integer, 1 dB precision) + rssis| number | RSSI of the signal in dBm (signed integer, 1 dB precision) + lsnr | number | Lora SNR ratio in dB (signed float, 0.1 dB precision) + foff | number | LoRa frequency offset in Hz (signed interger) + size | number | RF packet payload size in bytes (unsigned integer) + data | string | Base64 encoded RF packet payload, padded + +Example (white-spaces, indentation and newlines added for readability): + +``` json +{"rxpk":[ + { + "time":"2013-03-31T16:21:17.528002Z", + "tmst":3512348611, + "chan":2, + "rfch":0, + "freq":866.349812, + "stat":1, + "modu":"LORA", + "datr":"SF7BW125", + "codr":"4/6", + "rssi":-35, + "lsnr":5.1, + "size":32, + "data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84" + },{ + "time":"2013-03-31T16:21:17.530974Z", + "tmst":3512348514, + "chan":9, + "rfch":1, + "freq":869.1, + "stat":1, + "modu":"FSK", + "datr":50000, + "rssi":-75, + "size":16, + "data":"VEVTVF9QQUNLRVRfMTIzNA==" + },{ + "time":"2013-03-31T16:21:17.532038Z", + "tmst":3316387610, + "chan":0, + "rfch":0, + "freq":863.00981, + "stat":1, + "modu":"LORA", + "datr":"SF10BW125", + "codr":"4/7", + "rssi":-38, + "lsnr":5.5, + "size":32, + "data":"ysgRl452xNLep9S1NTIg2lomKDxUgn3DJ7DE+b00Ass" + } +]} +``` + +The root object can also contain an object named "stat" : + +``` json +{ + "rxpk":[ {...}, ...], + "stat":{...} +} +``` + +It is possible for a packet to contain no "rxpk" array but a "stat" object. + +``` json +{ + "stat":{...} +} +``` + +That object contains the status of the gateway, with the following fields: + + Name | Type | Function +:----:|:------:|-------------------------------------------------------------- + time | string | UTC 'system' time of the gateway, ISO 8601 'expanded' format + lati | number | GPS latitude of the gateway in degree (float, N is +) + long | number | GPS latitude of the gateway in degree (float, E is +) + alti | number | GPS altitude of the gateway in meter RX (integer) + rxnb | number | Number of radio packets received (unsigned integer) + rxok | number | Number of radio packets received with a valid PHY CRC + rxfw | number | Number of radio packets forwarded (unsigned integer) + ackr | number | Percentage of upstream datagrams that were acknowledged + dwnb | number | Number of downlink datagrams received (unsigned integer) + txnb | number | Number of packets emitted (unsigned integer) + +Example (white-spaces, indentation and newlines added for readability): + +``` json +{"stat":{ + "time":"2014-01-12 08:59:28 GMT", + "lati":46.24000, + "long":3.25230, + "alti":145, + "rxnb":2, + "rxok":2, + "rxfw":2, + "ackr":100.0, + "dwnb":2, + "txnb":2 +}} +``` + + +## 5. Downstream protocol + +### 5.1. Sequence diagram ### + + +---------+ +---------+ + | Gateway | | Server | + +---------+ +---------+ + | -----------------------------------\ | + |-| Every N seconds (keepalive time) | | + | ------------------------------------ | + | | + | PULL_DATA (token Y, MAC@) | + |------------------------------------------------------------->| + | | + | PULL_ACK (token Y) | + |<-------------------------------------------------------------| + | | + + +---------+ +---------+ + | Gateway | | Server | + +---------+ +---------+ + | ------------------------------------------------------\ | + | | Anytime after first PULL_DATA for each packet to TX |-| + | ------------------------------------------------------- | + | | + | PULL_RESP (token Z, JSON payload) | + |<-------------------------------------------------------------| + | | + | TX_ACK (token Z, JSON payload) | + |------------------------------------------------------------->| + +### 5.2. PULL_DATA packet ### + +That packet type is used by the gateway to poll data from the server. + +This data exchange is initialized by the gateway because it might be +impossible for the server to send packets to the gateway if the gateway is +behind a NAT. + +When the gateway initialize the exchange, the network route towards the +server will open and will allow for packets to flow both directions. +The gateway must periodically send PULL_DATA packets to be sure the network +route stays open for the server to be used at any time. + + Bytes | Function +:------:|--------------------------------------------------------------------- + 0 | protocol version = 2 + 1-2 | random token + 3 | PULL_DATA identifier 0x02 + 4-11 | Gateway unique identifier (MAC address) + +### 5.3. PULL_ACK packet ### + +That packet type is used by the server to confirm that the network route is +open and that the server can send PULL_RESP packets at any time. + + Bytes | Function +:------:|--------------------------------------------------------------------- + 0 | protocol version = 2 + 1-2 | same token as the PULL_DATA packet to acknowledge + 3 | PULL_ACK identifier 0x04 + +### 5.4. PULL_RESP packet ### + +That packet type is used by the server to send RF packets and associated +metadata that will have to be emitted by the gateway. + + Bytes | Function +:------:|--------------------------------------------------------------------- + 0 | protocol version = 2 + 1-2 | random token + 3 | PULL_RESP identifier 0x03 + 4-end | JSON object, starting with {, ending with }, see section 6 + +### 5.5. TX_ACK packet ### + +That packet type is used by the gateway to send a feedback to the server +to inform if a downlink request has been accepted or rejected by the gateway. +The datagram may optionnaly contain a JSON string to give more details on +acknoledge. If no JSON is present (empty string), this means than no error +occured. + + Bytes | Function +:------:|--------------------------------------------------------------------- + 0 | protocol version = 2 + 1-2 | same token as the PULL_RESP packet to acknowledge + 3 | TX_ACK identifier 0x05 + 4-11 | Gateway unique identifier (MAC address) + 12-end | [optional] JSON object, starting with {, ending with }, see section 6 + + +## 6. Downstream JSON data structure + +The root object of PULL_RESP packet must contain an object named "txpk": + +``` json +{ + "txpk": {...} +} +``` + +That object contain a RF packet to be emitted and associated metadata with the +following fields: + + Name | Type | Function +:----:|:------:|-------------------------------------------------------------- + imme | bool | Send packet immediately (will ignore tmst & tmms) + tmst | number | Send packet on a certain timestamp value (will ignore tmms) + tmms | number | Send packet at a certain GPS time (GPS synchronization required) + freq | number | TX central frequency in MHz (unsigned float, Hz precision) + rfch | number | Concentrator "RF chain" used for TX (unsigned integer) + powe | number | TX output power in dBm (unsigned integer, dBm precision) + modu | string | Modulation identifier "LORA" or "FSK" + datr | string | LoRa datarate identifier (eg. SF12BW500) + datr | number | FSK datarate (unsigned, in bits per second) + codr | string | LoRa ECC coding rate identifier + fdev | number | FSK frequency deviation (unsigned integer, in Hz) + ipol | bool | Lora modulation polarization inversion + prea | number | RF preamble size (unsigned integer) + size | number | RF packet payload size in bytes (unsigned integer) + data | string | Base64 encoded RF packet payload, padding optional + ncrc | bool | If true, disable the CRC of the physical layer (optional) + +Most fields are optional. +If a field is omitted, default parameters will be used. + +Examples (white-spaces, indentation and newlines added for readability): + +``` json +{"txpk":{ + "imme":true, + "freq":864.123456, + "rfch":0, + "powe":14, + "modu":"LORA", + "datr":"SF11BW125", + "codr":"4/6", + "ipol":false, + "size":32, + "data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v" +}} +``` + +``` json +{"txpk":{ + "imme":true, + "freq":861.3, + "rfch":0, + "powe":12, + "modu":"FSK", + "datr":50000, + "fdev":3000, + "size":32, + "data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v" +}} +``` + +In case of error or warning, the root object of TX_ACK packet must contain an +object named "txpk_ack": + +``` json +{ + "txpk_ack": {...} +} +``` + +That object contain status information concerning the associated PULL_RESP packet. + + Name | Type | Function +:----:|:------:|----------------------------------------------------------------------------------------- +error | string | Indicates the type of failure that occurred for downlink request (optional) +warn | string | Indicates that downlink request has been accepted with limitation (optional) +value | string | When a warning is raised, it gives indications about the limitation (optional) +value | number | When a warning is raised, it gives indications about the limitation (optional) + +The possible values of the "error" field are: + + Value | Definition +:-----------------:|--------------------------------------------------------------------- + TOO_LATE | Rejected because it was already too late to program this packet for downlink + TOO_EARLY | Rejected because downlink packet timestamp is too much in advance + COLLISION_PACKET | Rejected because there was already a packet programmed in requested timeframe + COLLISION_BEACON | Rejected because there was already a beacon planned in requested timeframe + TX_FREQ | Rejected because requested frequency is not supported by TX RF chain + GPS_UNLOCKED | Rejected because GPS is unlocked, so GPS timestamp cannot be used + +The possible values of the "warn" field are: + + Value | Definition +:-----------------:|--------------------------------------------------------------------- + TX_POWER | The requested power is not supported by the gateway, the power actually used is given in the value field + +Examples (white-spaces, indentation and newlines added for readability): + +``` json +{"txpk_ack":{ + "error":"COLLISION_PACKET" +}} +``` + +``` json +{"txpk_ack":{ + "warn":"TX_POWER", + "value":20 +}} +``` + +## 7. Revisions + +### v1.5 ### +* Moved TX_POWER from "error" category to "warn" category in "txpk_ack" object + +### v1.4 ### +* Added "tmms" field for GPS time as a monotonic number of milliseconds +ellapsed since January 6th, 1980 (GPS Epoch). No leap second. + +### v1.3 ### + +* Added downlink feedback from gateway to server (PULL_RESP -> TX_ACK) + +### v1.2 ### + +* Added value of FSK bitrate for upstream. +* Added parameters for FSK bitrate and frequency deviation for downstream. + +### v1.1 ### + +* Added syntax for status report JSON object on upstream. + +### v1.0 ### + +* Initial version. diff --git a/packet_forwarder/global_conf.json.sx1250 b/packet_forwarder/global_conf.json.sx1250 new file mode 100644 index 0000000..87dea44 --- /dev/null +++ b/packet_forwarder/global_conf.json.sx1250 @@ -0,0 +1,98 @@ +{ + "SX130x_conf": { + "spidev_path": "/dev/spidev0.0", + "lorawan_public": true, + "clksrc": 0, + "antenna_gain": 0, /* antenna gain, in dBi */ + "full_duplex": false, + "precision_timestamp": { + "enable": false, + "max_ts_metrics": 255, + "nb_symbols": 1 + }, + "radio_0": { + "enable": true, + "type": "SX1250", + "freq": 867500000, + "rssi_offset": -215.4, + "rssi_tcomp": {"coeff_a": 0, "coeff_b": 0, "coeff_c": 20.41, "coeff_d": 2162.56, "coeff_e": 0}, + "tx_enable": true, + "tx_freq_min": 863000000, + "tx_freq_max": 870000000, + "tx_gain_lut":[ + {"rf_power": 12, "pa_gain": 0, "pwr_idx": 16}, + {"rf_power": 13, "pa_gain": 0, "pwr_idx": 17}, + {"rf_power": 14, "pa_gain": 0, "pwr_idx": 18}, + {"rf_power": 15, "pa_gain": 0, "pwr_idx": 20}, + {"rf_power": 16, "pa_gain": 1, "pwr_idx": 0}, + {"rf_power": 17, "pa_gain": 1, "pwr_idx": 1}, + {"rf_power": 18, "pa_gain": 1, "pwr_idx": 2}, + {"rf_power": 19, "pa_gain": 1, "pwr_idx": 3}, + {"rf_power": 20, "pa_gain": 1, "pwr_idx": 4}, + {"rf_power": 21, "pa_gain": 1, "pwr_idx": 5}, + {"rf_power": 22, "pa_gain": 1, "pwr_idx": 6}, + {"rf_power": 23, "pa_gain": 1, "pwr_idx": 7}, + {"rf_power": 24, "pa_gain": 1, "pwr_idx": 8}, + {"rf_power": 25, "pa_gain": 1, "pwr_idx": 9}, + {"rf_power": 26, "pa_gain": 1, "pwr_idx": 11}, + {"rf_power": 27, "pa_gain": 1, "pwr_idx": 13} + ] + }, + "radio_1": { + "enable": true, + "type": "SX1250", + "freq": 868500000, + "rssi_offset": -215.4, + "rssi_tcomp": {"coeff_a": 0, "coeff_b": 0, "coeff_c": 20.41, "coeff_d": 2162.56, "coeff_e": 0}, + "tx_enable": false + }, + "chan_multiSF_0": {"enable": true, "radio": 1, "if": -400000}, + "chan_multiSF_1": {"enable": true, "radio": 1, "if": -200000}, + "chan_multiSF_2": {"enable": true, "radio": 1, "if": 0}, + "chan_multiSF_3": {"enable": true, "radio": 0, "if": -400000}, + "chan_multiSF_4": {"enable": true, "radio": 0, "if": -200000}, + "chan_multiSF_5": {"enable": true, "radio": 0, "if": 0}, + "chan_multiSF_6": {"enable": true, "radio": 0, "if": 200000}, + "chan_multiSF_7": {"enable": true, "radio": 0, "if": 400000}, + "chan_Lora_std": {"enable": true, "radio": 1, "if": -200000, "bandwidth": 250000, "spread_factor": 7, + "implicit_hdr": false, "implicit_payload_length": 17, "implicit_crc_en": false, "implicit_coderate": 1}, + "chan_FSK": {"enable": true, "radio": 1, "if": 300000, "bandwidth": 125000, "datarate": 50000} + }, + + "gateway_conf": { + "gateway_ID": "AA555A0000000000", + /* change with default server address/ports */ + "server_address": "localhost", + "serv_port_up": 1730, + "serv_port_down": 1730, + /* adjust the following parameters for your network */ + "keepalive_interval": 10, + "stat_interval": 30, + "push_timeout_ms": 100, + /* forward only valid packets */ + "forward_crc_valid": true, + "forward_crc_error": false, + "forward_crc_disabled": false, + /* GPS configuration */ + "gps_tty_path": "/dev/ttyS0", + /* GPS reference coordinates */ + "ref_latitude": 0.0, + "ref_longitude": 0.0, + "ref_altitude": 0, + /* Beaconing parameters */ + "beacon_period": 128, + "beacon_freq_hz": 869525000, + "beacon_datarate": 9, + "beacon_bw_hz": 125000, + "beacon_power": 14, + "beacon_infodesc": 0 + }, + + "debug_conf": { + "ref_payload":[ + {"id": "0xCAFE1234"}, + {"id": "0xCAFE2345"} + ], + "log_file": "loragw_hal.log" + } +} diff --git a/packet_forwarder/global_conf.json.sx1257 b/packet_forwarder/global_conf.json.sx1257 new file mode 100644 index 0000000..985d1ef --- /dev/null +++ b/packet_forwarder/global_conf.json.sx1257 @@ -0,0 +1,98 @@ +{ + "SX130x_conf": { + "spidev_path": "/dev/spidev0.0", + "lorawan_public": true, + "clksrc": 0, + "antenna_gain": 0, /* antenna gain, in dBi */ + "full_duplex": false, + "precision_timestamp": { + "enable": false, + "max_ts_metrics": 255, + "nb_symbols": 1 + }, + "radio_0": { + "enable": true, + "type": "SX1257", + "freq": 867500000, + "rssi_offset": -196.0, + "rssi_tcomp": {"coeff_a": -0.006, "coeff_b": 0.789, "coeff_c": -14.992, "coeff_d": 1988.572, "coeff_e": 105236.996}, + "tx_enable": true, + "tx_freq_min": 863000000, + "tx_freq_max": 870000000, + "tx_gain_lut":[ + {"rf_power": -6, "pa_gain": 0, "dac_gain": 3, "mix_gain": 8, "dig_gain": 0}, + {"rf_power": -3, "pa_gain": 0, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0}, + {"rf_power": 0, "pa_gain": 0, "dac_gain": 3, "mix_gain": 12, "dig_gain": 0}, + {"rf_power": 3, "pa_gain": 1, "dac_gain": 3, "mix_gain": 8, "dig_gain": 0}, + {"rf_power": 6, "pa_gain": 1, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0}, + {"rf_power": 10, "pa_gain": 1, "dac_gain": 3, "mix_gain": 12, "dig_gain": 0}, + {"rf_power": 11, "pa_gain": 1, "dac_gain": 3, "mix_gain": 13, "dig_gain": 0}, + {"rf_power": 12, "pa_gain": 2, "dac_gain": 3, "mix_gain": 9, "dig_gain": 0}, + {"rf_power": 13, "pa_gain": 1, "dac_gain": 3, "mix_gain": 15, "dig_gain": 0}, + {"rf_power": 14, "pa_gain": 2, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0}, + {"rf_power": 16, "pa_gain": 2, "dac_gain": 3, "mix_gain": 11, "dig_gain": 0}, + {"rf_power": 20, "pa_gain": 3, "dac_gain": 3, "mix_gain": 9, "dig_gain": 0}, + {"rf_power": 23, "pa_gain": 3, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0}, + {"rf_power": 25, "pa_gain": 3, "dac_gain": 3, "mix_gain": 11, "dig_gain": 0}, + {"rf_power": 26, "pa_gain": 3, "dac_gain": 3, "mix_gain": 12, "dig_gain": 0}, + {"rf_power": 27, "pa_gain": 3, "dac_gain": 3, "mix_gain": 14, "dig_gain": 0} + ] + }, + "radio_1": { + "enable": true, + "type": "SX1257", + "freq": 868500000, + "rssi_offset": -196.0, + "rssi_tcomp": {"coeff_a": -0.006, "coeff_b": 0.789, "coeff_c": -14.992, "coeff_d": 1988.572, "coeff_e": 105236.996}, + "tx_enable": false + }, + "chan_multiSF_0": {"enable": true, "radio": 1, "if": -400000}, + "chan_multiSF_1": {"enable": true, "radio": 1, "if": -200000}, + "chan_multiSF_2": {"enable": true, "radio": 1, "if": 0}, + "chan_multiSF_3": {"enable": true, "radio": 0, "if": -400000}, + "chan_multiSF_4": {"enable": true, "radio": 0, "if": -200000}, + "chan_multiSF_5": {"enable": true, "radio": 0, "if": 0}, + "chan_multiSF_6": {"enable": true, "radio": 0, "if": 200000}, + "chan_multiSF_7": {"enable": true, "radio": 0, "if": 400000}, + "chan_Lora_std": {"enable": true, "radio": 1, "if": -200000, "bandwidth": 250000, "spread_factor": 7, + "implicit_hdr": false, "implicit_payload_length": 17, "implicit_crc_en": false, "implicit_coderate": 1}, + "chan_FSK": {"enable": true, "radio": 1, "if": 300000, "bandwidth": 125000, "datarate": 50000} + }, + + "gateway_conf": { + "gateway_ID": "AA555A0000000000", + /* change with default server address/ports */ + "server_address": "localhost", + "serv_port_up": 1730, + "serv_port_down": 1730, + /* adjust the following parameters for your network */ + "keepalive_interval": 10, + "stat_interval": 30, + "push_timeout_ms": 100, + /* forward only valid packets */ + "forward_crc_valid": true, + "forward_crc_error": false, + "forward_crc_disabled": false, + /* GPS configuration */ + "gps_tty_path": "/dev/ttyS0", + /* GPS reference coordinates */ + "ref_latitude": 0.0, + "ref_longitude": 0.0, + "ref_altitude": 0, + /* Beaconing parameters */ + "beacon_period": 128, + "beacon_freq_hz": 869525000, + "beacon_datarate": 9, + "beacon_bw_hz": 125000, + "beacon_power": 14, + "beacon_infodesc": 0 + }, + + "debug_conf": { + "ref_payload":[ + {"id": "0xCAFE1234"}, + {"id": "0xCAFE2345"} + ], + "log_file": "loragw_hal.log" + } +} diff --git a/packet_forwarder/global_conf.json.us915.full_duplex b/packet_forwarder/global_conf.json.us915.full_duplex new file mode 100644 index 0000000..451e1b5 --- /dev/null +++ b/packet_forwarder/global_conf.json.us915.full_duplex @@ -0,0 +1,93 @@ +{ + "SX130x_conf": { + "spidev_path": "/dev/spidev0.0", + "lorawan_public": true, + "clksrc": 1, + "antenna_gain": 0, /* antenna gain, in dBi */ + "full_duplex": true, + "precision_timestamp": { + "enable": false, + "max_ts_metrics": 255, + "nb_symbols": 1 + }, + "radio_0": { + "enable": true, + "type": "SX1257", + "freq": 913800000, + "rssi_offset": -196.0, + "tx_enable": true, + "tx_freq_min": 100000000, + "tx_freq_max": 950000000, + "tx_gain_lut":[ + {"rf_power": -6, "pa_gain": 0, "dac_gain": 3, "mix_gain": 8, "dig_gain": 0}, + {"rf_power": -3, "pa_gain": 0, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0}, + {"rf_power": 0, "pa_gain": 0, "dac_gain": 3, "mix_gain": 12, "dig_gain": 0}, + {"rf_power": 3, "pa_gain": 1, "dac_gain": 3, "mix_gain": 8, "dig_gain": 0}, + {"rf_power": 6, "pa_gain": 1, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0}, + {"rf_power": 10, "pa_gain": 1, "dac_gain": 3, "mix_gain": 12, "dig_gain": 0}, + {"rf_power": 11, "pa_gain": 1, "dac_gain": 3, "mix_gain": 13, "dig_gain": 0}, + {"rf_power": 12, "pa_gain": 2, "dac_gain": 3, "mix_gain": 9, "dig_gain": 0}, + {"rf_power": 13, "pa_gain": 1, "dac_gain": 3, "mix_gain": 15, "dig_gain": 0}, + {"rf_power": 14, "pa_gain": 2, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0}, + {"rf_power": 16, "pa_gain": 2, "dac_gain": 3, "mix_gain": 11, "dig_gain": 0}, + {"rf_power": 20, "pa_gain": 3, "dac_gain": 3, "mix_gain": 9, "dig_gain": 0}, + {"rf_power": 23, "pa_gain": 3, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0}, + {"rf_power": 25, "pa_gain": 3, "dac_gain": 3, "mix_gain": 11, "dig_gain": 0}, + {"rf_power": 26, "pa_gain": 3, "dac_gain": 3, "mix_gain": 12, "dig_gain": 0}, + {"rf_power": 27, "pa_gain": 3, "dac_gain": 3, "mix_gain": 14, "dig_gain": 0} + ] + }, + "radio_1": { + "enable": true, + "type": "SX1257", + "freq": 914600000, + "rssi_offset": -196.0, + "tx_enable": true, + "tx_freq_min": 100000000, + "tx_freq_max": 950000000, + "tx_gain_lut":[ + {"rf_power": -6, "pa_gain": 0, "dac_gain": 3, "mix_gain": 8, "dig_gain": 0}, + {"rf_power": -3, "pa_gain": 0, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0}, + {"rf_power": 0, "pa_gain": 0, "dac_gain": 3, "mix_gain": 12, "dig_gain": 0}, + {"rf_power": 3, "pa_gain": 1, "dac_gain": 3, "mix_gain": 8, "dig_gain": 0}, + {"rf_power": 6, "pa_gain": 1, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0}, + {"rf_power": 10, "pa_gain": 1, "dac_gain": 3, "mix_gain": 12, "dig_gain": 0}, + {"rf_power": 11, "pa_gain": 1, "dac_gain": 3, "mix_gain": 13, "dig_gain": 0}, + {"rf_power": 12, "pa_gain": 2, "dac_gain": 3, "mix_gain": 9, "dig_gain": 0}, + {"rf_power": 13, "pa_gain": 1, "dac_gain": 3, "mix_gain": 15, "dig_gain": 0}, + {"rf_power": 14, "pa_gain": 2, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0}, + {"rf_power": 16, "pa_gain": 2, "dac_gain": 3, "mix_gain": 11, "dig_gain": 0}, + {"rf_power": 20, "pa_gain": 3, "dac_gain": 3, "mix_gain": 9, "dig_gain": 0}, + {"rf_power": 23, "pa_gain": 3, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0}, + {"rf_power": 25, "pa_gain": 3, "dac_gain": 3, "mix_gain": 11, "dig_gain": 0}, + {"rf_power": 26, "pa_gain": 3, "dac_gain": 3, "mix_gain": 12, "dig_gain": 0}, + {"rf_power": 27, "pa_gain": 3, "dac_gain": 3, "mix_gain": 14, "dig_gain": 0} + ] + }, + "chan_multiSF_0": {"enable": true, "radio": 0, "if": -300000}, + "chan_multiSF_1": {"enable": true, "radio": 0, "if": -100000}, + "chan_multiSF_2": {"enable": true, "radio": 0, "if": 100000}, + "chan_multiSF_3": {"enable": true, "radio": 0, "if": 300000}, + "chan_multiSF_4": {"enable": true, "radio": 1, "if": -300000}, + "chan_multiSF_5": {"enable": true, "radio": 1, "if": -100000}, + "chan_multiSF_6": {"enable": true, "radio": 1, "if": 100000}, + "chan_multiSF_7": {"enable": true, "radio": 1, "if": 300000} + }, + + "gateway_conf": { + "gateway_ID": "AA555A0000000000", + /* change with default server address/ports */ + "server_address": "localhost", + "serv_port_up": 1750, + "serv_port_down": 1750, + /* adjust the following parameters for your network */ + "keepalive_interval": 10, + "stat_interval": 30, + "push_timeout_ms": 100, + /* forward only valid packets */ + "forward_crc_valid": true, + "forward_crc_error": false, + "forward_crc_disabled": false + } +} + diff --git a/packet_forwarder/inc/jitqueue.h b/packet_forwarder/inc/jitqueue.h new file mode 100644 index 0000000..07168be --- /dev/null +++ b/packet_forwarder/inc/jitqueue.h @@ -0,0 +1,154 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + LoRa concentrator : Just In Time TX scheduling queue + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORA_PKTFWD_JIT_H +#define _LORA_PKTFWD_JIT_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* bool type */ +#include /* timeval */ + +#include "loragw_hal.h" + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +#define JIT_QUEUE_MAX 32 /* Maximum number of packets to be stored in JiT queue */ +#define JIT_NUM_BEACON_IN_QUEUE 3 /* Number of beacons to be loaded in JiT queue at any time */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +enum jit_pkt_type_e { + JIT_PKT_TYPE_DOWNLINK_CLASS_A, + JIT_PKT_TYPE_DOWNLINK_CLASS_B, + JIT_PKT_TYPE_DOWNLINK_CLASS_C, + JIT_PKT_TYPE_BEACON +}; + +enum jit_error_e { + JIT_ERROR_OK, /* Packet ok to be sent */ + JIT_ERROR_TOO_LATE, /* Too late to send this packet */ + JIT_ERROR_TOO_EARLY, /* Too early to queue this packet */ + JIT_ERROR_FULL, /* Downlink queue is full */ + JIT_ERROR_EMPTY, /* Downlink queue is empty */ + JIT_ERROR_COLLISION_PACKET, /* A packet is already enqueued for this timeframe */ + JIT_ERROR_COLLISION_BEACON, /* A beacon is planned for this timeframe */ + JIT_ERROR_TX_FREQ, /* The required frequency for downlink is not supported */ + JIT_ERROR_TX_POWER, /* The required power for downlink is not supported */ + JIT_ERROR_GPS_UNLOCKED, /* GPS timestamp could not be used as GPS is unlocked */ + JIT_ERROR_INVALID /* Packet is invalid */ +}; + +struct jit_node_s { + /* API fields */ + struct lgw_pkt_tx_s pkt; /* TX packet */ + enum jit_pkt_type_e pkt_type; /* Packet type: Downlink, Beacon... */ + + /* Internal fields */ + uint32_t pre_delay; /* Amount of time before packet timestamp to be reserved */ + uint32_t post_delay; /* Amount of time after packet timestamp to be reserved (time on air) */ +}; + +struct jit_queue_s { + uint8_t num_pkt; /* Total number of packets in the queue (downlinks, beacons...) */ + uint8_t num_beacon; /* Number of beacons in the queue */ + struct jit_node_s nodes[JIT_QUEUE_MAX]; /* Nodes/packets array in the queue */ +}; + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +/** +@brief Check if a JiT queue is full. + +@param queue[in] Just in Time queue to be checked. +@return true if queue is full, false otherwise. +*/ +bool jit_queue_is_full(struct jit_queue_s *queue); + +/** +@brief Check if a JiT queue is empty. + +@param queue[in] Just in Time queue to be checked. +@return true if queue is empty, false otherwise. +*/ +bool jit_queue_is_empty(struct jit_queue_s *queue); + +/** +@brief Initialize a Just in Time queue. + +@param queue[in] Just in Time queue to be initialized. Memory should have been allocated already. + +This function is used to reset every elements in the allocated queue. +*/ +void jit_queue_init(struct jit_queue_s *queue); + +/** +@brief Add a packet in a Just-in-Time queue + +@param queue[in/out] Just in Time queue in which the packet should be inserted +@param time_us[in] Current concentrator time +@param packet[in] Packet to be queued in JiT queue +@param pkt_type[in] Type of packet to be queued: Downlink, Beacon +@return success if the function was able to queue the packet + +This function is typically used when a packet is received from server for downlink. +It will check if packet can be queued, with several criterias. Once the packet is queued, it has to be +sent over the air. So all checks should happen before the packet being actually in the queue. +*/ +enum jit_error_e jit_enqueue(struct jit_queue_s *queue, uint32_t time_us, struct lgw_pkt_tx_s *packet, enum jit_pkt_type_e pkt_type); + +/** +@brief Dequeue a packet from a Just-in-Time queue + +@param queue[in/out] Just in Time queue from which the packet should be removed +@param index[in] in the queue where to get the packet to be removed +@param packet[out] that was at index +@param pkt_type[out] Type of packet dequeued: Downlink, Beacon +@return success if the function was able to dequeue the packet + +This function is typically used when a packet is about to be placed on concentrator buffer for TX. +The index is generally got using the jit_peek function. +*/ +enum jit_error_e jit_dequeue(struct jit_queue_s *queue, int index, struct lgw_pkt_tx_s *packet, enum jit_pkt_type_e *pkt_type); + +/** +@brief Check if there is a packet soon to be sent from the JiT queue. + +@param queue[in] Just in Time queue to parse for peeking a packet +@param time_us[in] Current concentrator time +@param pkt_idx[out] Packet index which is soon to be dequeued. +@return success if the function was able to parse the queue. pkt_idx is set to -1 if no packet found. + +This function is typically used to check in JiT queue if there is a packet soon to be sent. +It search the packet with the highest priority in queue, and check if its timestamp is near +enough the current concentrator time. +*/ +enum jit_error_e jit_peek(struct jit_queue_s *queue, uint32_t time_us, int *pkt_idx); + +/** +@brief Debug function to print the queue's content on console + +@param queue[in] Just in Time queue to be displayed +@param show_all[in] Indicates if empty nodes have to be displayed or not +*/ +void jit_print_queue(struct jit_queue_s *queue, bool show_all, int debug_level); + +#endif +/* --- EOF ------------------------------------------------------------------ */ diff --git a/packet_forwarder/inc/trace.h b/packet_forwarder/inc/trace.h new file mode 100644 index 0000000..c2099bf --- /dev/null +++ b/packet_forwarder/inc/trace.h @@ -0,0 +1,39 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + LoRa concentrator : Packet Forwarder trace helpers + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORA_PKTFWD_TRACE_H +#define _LORA_PKTFWD_TRACE_H + +#define DEBUG_PKT_FWD 0 +#define DEBUG_JIT 0 +#define DEBUG_JIT_ERROR 1 +#define DEBUG_TIMERSYNC 0 +#define DEBUG_BEACON 0 +#define DEBUG_LOG 1 + +#define MSG(args...) printf(args) /* message that is destined to the user */ +#define MSG_DEBUG(FLAG, fmt, ...) \ + do { \ + if (FLAG) \ + fprintf(stdout, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); \ + } while (0) +#define MSG_PRINTF(FLAG, fmt, ...) \ + do { \ + if (FLAG) \ + fprintf(stdout, fmt, ##__VA_ARGS__); \ + } while (0) + +#endif +/* --- EOF ------------------------------------------------------------------ */ diff --git a/packet_forwarder/readme.md b/packet_forwarder/readme.md new file mode 100644 index 0000000..e6de9ee --- /dev/null +++ b/packet_forwarder/readme.md @@ -0,0 +1,253 @@ + / _____) _ | | + ( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | + (______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +SX1302 packet forwarder +======================= + +## 1. Introduction + +The packet forwarder is a program running on the host of a Lora gateway that +forwards RF packets receive by the concentrator to a server through a IP/UDP +link, and emits RF packets that are sent by the server. It can also emit a +network-wide GPS-synchronous beacon signal used for coordinating all nodes of +the network. + +To learn more about the network protocol between the gateway and the server, +please read the PROTOCOL.TXT document. + +## 2. System schematic and definitions + + ((( Y ))) + | + | + +- -|- - - - - - - - - - - - -+ xxxxxxxxxxxx +--------+ + |+--+-----------+ +------+| xx x x xxx | | + || | | || xx Internet xx | | + || Concentrator |<----+ Host |<------xx or xx-------->| | + || | SPI | || xx Intranet xx | Server | + |+--------------+ +------+| xxxx x xxxx | | + | ^ ^ | xxxxxxxx | | + | | PPS +-----+ NMEA | | | | + | +------| GPS |-------+ | +--------+ + | +-----+ | + | | + | Gateway | + +- - - - - - - - - - - - - - -+ + +Concentrator: radio RX/TX board, based on Semtech multichannel modems (SX130x), +transceivers (SX135x) and/or low-power stand-alone modems (SX127x). + +Host: embedded computer on which the packet forwarder is run. Drives the +concentrator through a SPI link. + +Gateway: a device composed of at least one radio concentrator, a host, some +network connection to the internet or a private network (Ethernet, 3G, Wifi, +microwave link), and optionally a GPS receiver for synchronization. + +Server: an abstract computer that will process the RF packets received and +forwarded by the gateway, and issue RF packets in response that the gateway +will have to emit. + + +## 3. Dependencies + +This program uses the Parson library (http://kgabis.github.com/parson/) by +Krzysztof Gabis for JSON parsing. +Many thanks to him for that very practical and well written library. + +This program is statically linked with the libloragw Lora concentrator library. +It was tested with v1.3.0 of the library but should work with any later +version provided the API is v1 or a later backward-compatible API. +Data structures of the received packets are accessed by name (ie. not at a +binary level) so new functionalities can be added to the API without affecting +that program at all. + +This program follows the v1.3 version of the gateway-to-server protocol. + +The last dependency is the hardware concentrator (based on FPGA or SX130x +chips) that must be matched with the proper version of the HAL. + +## 4. Usage + +* Pick the global_conf.json file that fit with your platform, region and feature +need. +* Update the JSON configuration (global and local) files, as explained below. +* Run: + ./lora_pkt_fwd -c global_conf.json + +To stop the application, press Ctrl+C. +Unless it is manually stopped or encounter a critical error, the program will +run forever. + +The way the program takes configuration files into account is the following: + * if a specific configuration file is given as program argument, use the + one specified. + * if there is a global_conf.json parse it. + +The global configuration file should be exactly the same throughout your +network, contain all global parameters (parameters for "sensor" radio +channels) and preferably default "safe" values for parameters that are +specific for each gateway (eg. specify a default MAC address). + +In each configuration file, the program looks for a JSON object named +"SX130x_conf" that should contain the parameters for the Lora concentrator +board (RF channels definition, modem parameters, etc) and another JSON object +called "gateway_conf" that should contain the gateway parameters (gateway MAC +address, IP address of the server, keep-alive time, etc). + +To learn more about the JSON configuration format, read the provided JSON +files and the libloragw API documentation. + +Every X seconds (parameter settable in the configuration files) the program +display statistics on the RF packets received and sent, and the network +datagrams received and sent. +The program also send some statistics to the server in JSON format. + +## 5. "Just-In-Time" downlink scheduling + +The LoRa concentrator can have only one TX packet programmed for departure at a +time. The actual departure of a downlink packet will be done based on its +timestamp, when the concentrator internal counter reaches timestamp’s value. +The departure of a beacon will be done based on a GPS PPS event. +It may happen that, due to network variable latency, the gateway receives one +or many downlink packets from the server while a TX is already programmed in the +concentrator. The packet forwarder has to store and order incoming downlink +packets (in a queue), so that they can all be programmed in the concentrator at +the proper time and sent over the air. +Possible failures that may occur and that have to be reported to the server are: +- It is too early or too late to send a given packet +- A packet collides with another packet already queued +- A packet collides with a beacon +- TX RF parameters (frequency, power) are not supported by gateway +- Gateway’s GPS is unlocked, so cannot process Class B downlink +It is called "Just-in-Time" (JiT) scheduling, because the packet forwarder will +program a downlink or a beacon packet in the concentrator just before it has to +be sent over the air. +Another benefit of JiT is to optimize the gateway downlink capacity by avoiding +to keep the concentrator TX buffer busy for too long. + +In order to achieve "Just-in-Time" scheduling, the following software elements +have been added: +- A JiT queue, with associated enqueue/peek/dequeue functions and packet +acceptance criterias. It is where downlink packets are stored, waiting to be +sent. +- A JiT thread, which regularly checks if there is a packet in the JiT queue +ready to be programmed in the concentrator, based on current concentrator +internal time. + +### 5.1. Concentrator vs GPS time synchronization + +There are 2 cases for which we need to convert a GPS time to concentrator +counter: + - Class B downlink: when the “time” field of JSON “txpk” is filled instead + of the “tmst” field, we need to be able to determine if the packet can be + queued in JiT queue or not, based on its corresponding concentrator + counter value. + Note: even if a Class-B downlink is given with a GPS timestamp, the + concentrator TX mode is configured as “TIMESTAMP”, and not “ON_GPS”. So + at the end, it is the counter value which will be used for transmission. + - Beacons: beacons transmission time is based on GPS clock, and the + concentrator TX mode is configured as “ON_GPS” for accurate beacon + transmission on GPS PPS event. In this case, the concentrator does not + need the packet counter to be set. But, as the JiT thread decides if a + packet has to be peeked or not, based on its concentrator counter, we need + to have the beacon packet counter set (see next chapter for more details + on JiT scheduling). +We also need to convert a SX1302 counter value to GPS UTC time when we receive +an uplink, in order to fill the “time” field of JSON “rxpk” structure. + +### 5.2. TX scheduling + +The JiT queue implemented is a static array of nodes, where each node contains: + - the downlink packet, with its type (beacon, downlink class A, B or C) + - a “pre delay” which depends on packet type (BEACON_GUARD, TX_START_DELAY…) + - a “post delay” which depends on packet type (“time on air” of this packet + computed based on its size, datarate and coderate, or BEACON_RESERVED) + +Several functions are implemented to manipulate this queue or get info from it: + - init: initialize array with default values + - is full / is empty: gives queue status + - enqueue: checks if the given packet can be queued or not, based on several + criteria’s + - peek: checks if the queue contains a packet that must be passed + immediately to the concentrator for transmission and returns corresponding + index if any. + - dequeue: actually removes from the queue the packet at index given by peek + function + +The queue is always kept sorted on ascending timestamp order. + +The JiT thread will regularly check in the JiT queue if there is a packet to be +sent soon. If a packet is matching, it is dequeued and programmed in the +concentrator TX buffer. + +### 5.3. Fine tuning parameters + +There are few parameters of the JiT queue which could be tweaked to adapt to +different system constraints. + + - inc/jitqueue.h: + JIT_QUEUE_MAX: The maximum number of nodes in the queue. + - src/jitqueue.c: + TX_JIT_DELAY: The number of milliseconds a packet is programmed in the + concentrator TX buffer before its actual departure time. + TX_MARGIN_DELAY: Packet collision check margin + +6. License +----------- + +Copyright (C) 2019, SEMTECH S.A. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of the Semtech corporation nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL SEMTECH S.A. BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +6. License for Parson library +------------------------------ + +Parson ( http://kgabis.github.com/parson/ ) +Copyright (C) 2012 Krzysztof Gabis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*EOF* diff --git a/packet_forwarder/src/jitqueue.c b/packet_forwarder/src/jitqueue.c new file mode 100644 index 0000000..e3889f7 --- /dev/null +++ b/packet_forwarder/src/jitqueue.c @@ -0,0 +1,458 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + LoRa concentrator : Just In Time TX scheduling queue + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#define _GNU_SOURCE /* needed for qsort_r to be defined */ +#include /* qsort_r */ +#include /* printf, fprintf, snprintf, fopen, fputs */ +#include /* memset, memcpy */ +#include +#include +#include + +#include "trace.h" +#include "jitqueue.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS & TYPES -------------------------------------------- */ +#define TX_START_DELAY 1500 /* microseconds */ + /* TODO: get this value from HAL? */ +#define TX_MARGIN_DELAY 1000 /* Packet overlap margin in microseconds */ + /* TODO: How much margin should we take? */ +#define TX_JIT_DELAY 30000 /* Pre-delay to program packet for TX in microseconds */ +#define TX_MAX_ADVANCE_DELAY ((JIT_NUM_BEACON_IN_QUEUE + 1) * 128 * 1E6) /* Maximum advance delay accepted for a TX packet, compared to current time */ + +#define BEACON_GUARD 3000000 /* Interval where no ping slot can be placed, + to ensure beacon can be sent */ +#define BEACON_RESERVED 2120000 /* Time on air of the beacon, with some margin */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */ +static pthread_mutex_t mx_jit_queue = PTHREAD_MUTEX_INITIALIZER; /* control access to JIT queue */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ----------------------------------------- */ + +bool jit_queue_is_full(struct jit_queue_s *queue) { + bool result; + + pthread_mutex_lock(&mx_jit_queue); + + result = (queue->num_pkt == JIT_QUEUE_MAX)?true:false; + + pthread_mutex_unlock(&mx_jit_queue); + + return result; +} + +bool jit_queue_is_empty(struct jit_queue_s *queue) { + bool result; + + pthread_mutex_lock(&mx_jit_queue); + + result = (queue->num_pkt == 0)?true:false; + + pthread_mutex_unlock(&mx_jit_queue); + + return result; +} + +void jit_queue_init(struct jit_queue_s *queue) { + int i; + + pthread_mutex_lock(&mx_jit_queue); + + memset(queue, 0, sizeof(*queue)); + for (i=0; inodes[i].pre_delay = 0; + queue->nodes[i].post_delay = 0; + } + + pthread_mutex_unlock(&mx_jit_queue); +} + +int compare(const void *a, const void *b, void *arg) +{ + struct jit_node_s *p = (struct jit_node_s *)a; + struct jit_node_s *q = (struct jit_node_s *)b; + int *counter = (int *)arg; + int p_count, q_count; + + p_count = p->pkt.count_us; + q_count = q->pkt.count_us; + + if (p_count > q_count) + *counter = *counter + 1; + + return p_count - q_count; +} + +void jit_sort_queue(struct jit_queue_s *queue) { + int counter = 0; + + if (queue->num_pkt == 0) { + return; + } + + MSG_DEBUG(DEBUG_JIT, "sorting queue in ascending order packet timestamp - queue size:%u\n", queue->num_pkt); + qsort_r(queue->nodes, queue->num_pkt, sizeof(queue->nodes[0]), compare, &counter); + MSG_DEBUG(DEBUG_JIT, "sorting queue done - swapped:%d\n", counter); +} + +bool jit_collision_test(uint32_t p1_count_us, uint32_t p1_pre_delay, uint32_t p1_post_delay, uint32_t p2_count_us, uint32_t p2_pre_delay, uint32_t p2_post_delay) { + if (((p1_count_us - p2_count_us) <= (p1_pre_delay + p2_post_delay + TX_MARGIN_DELAY)) || + ((p2_count_us - p1_count_us) <= (p2_pre_delay + p1_post_delay + TX_MARGIN_DELAY))) { + return true; + } else { + return false; + } +} + +enum jit_error_e jit_enqueue(struct jit_queue_s *queue, uint32_t time_us, struct lgw_pkt_tx_s *packet, enum jit_pkt_type_e pkt_type) { + int i = 0; + uint32_t packet_post_delay = 0; + uint32_t packet_pre_delay = 0; + uint32_t target_pre_delay = 0; + enum jit_error_e err_collision; + uint32_t asap_count_us; + + MSG_DEBUG(DEBUG_JIT, "Current concentrator time is %u, pkt_type=%d\n", time_us, pkt_type); + + if (packet == NULL) { + MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: invalid parameter\n"); + return JIT_ERROR_INVALID; + } + + if (jit_queue_is_full(queue)) { + MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: cannot enqueue packet, JIT queue is full\n"); + return JIT_ERROR_FULL; + } + + /* Compute packet pre/post delays depending on packet's type */ + switch (pkt_type) { + case JIT_PKT_TYPE_DOWNLINK_CLASS_A: + case JIT_PKT_TYPE_DOWNLINK_CLASS_B: + case JIT_PKT_TYPE_DOWNLINK_CLASS_C: + packet_pre_delay = TX_START_DELAY + TX_JIT_DELAY; + packet_post_delay = lgw_time_on_air(packet) * 1000UL; /* in us */ + break; + case JIT_PKT_TYPE_BEACON: + /* As defined in LoRaWAN spec */ + packet_pre_delay = TX_START_DELAY + BEACON_GUARD + TX_JIT_DELAY; + packet_post_delay = BEACON_RESERVED; + break; + default: + break; + } + + pthread_mutex_lock(&mx_jit_queue); + + /* An immediate downlink becomes a timestamped downlink "ASAP" */ + /* Set the packet count_us to the first available slot */ + if (pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_C) { + /* change tx_mode to timestamped */ + packet->tx_mode = TIMESTAMPED; + + /* Search for the ASAP timestamp to be given to the packet */ + asap_count_us = time_us + 1E6; /* TODO: Take 1 second margin, to be refined */ + if (queue->num_pkt == 0) { + /* If the jit queue is empty, we can insert this packet */ + MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink, first in JiT queue (count_us=%u)\n", asap_count_us); + } else { + /* Else we can try to insert it: + - ASAP meaning NOW + MARGIN + - at the last index of the queue + - between 2 downlinks in the queue + */ + + /* First, try if the ASAP time collides with an already enqueued downlink */ + for (i=0; inum_pkt; i++) { + if (jit_collision_test(asap_count_us, packet_pre_delay, packet_post_delay, queue->nodes[i].pkt.count_us, queue->nodes[i].pre_delay, queue->nodes[i].post_delay) == true) { + MSG_DEBUG(DEBUG_JIT, "DEBUG: cannot insert IMMEDIATE downlink at count_us=%u, collides with %u (index=%d)\n", asap_count_us, queue->nodes[i].pkt.count_us, i); + break; + } + } + if (i == queue->num_pkt) { + /* No collision with ASAP time, we can insert it */ + MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink ASAP at %u (no collision)\n", asap_count_us); + } else { + /* Search for the best slot then */ + for (i=0; inum_pkt; i++) { + asap_count_us = queue->nodes[i].pkt.count_us + queue->nodes[i].post_delay + packet_pre_delay + TX_JIT_DELAY + TX_MARGIN_DELAY; + if (i == (queue->num_pkt - 1)) { + /* Last packet index, we can insert after this one */ + MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink, last in JiT queue (count_us=%u)\n", asap_count_us); + } else { + /* Check if packet can be inserted between this index and the next one */ + MSG_DEBUG(DEBUG_JIT, "DEBUG: try to insert IMMEDIATE downlink (count_us=%u) between index %d and index %d?\n", asap_count_us, i, i+1); + if (jit_collision_test(asap_count_us, packet_pre_delay, packet_post_delay, queue->nodes[i+1].pkt.count_us, queue->nodes[i+1].pre_delay, queue->nodes[i+1].post_delay) == true) { + MSG_DEBUG(DEBUG_JIT, "DEBUG: failed to insert IMMEDIATE downlink (count_us=%u), continue...\n", asap_count_us); + continue; + } else { + MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink (count_us=%u)\n", asap_count_us); + break; + } + } + } + } + } + /* Set packet with ASAP timestamp */ + packet->count_us = asap_count_us; + } + + /* Check criteria_1: is it already too late to send this packet ? + * The packet should arrive at least at (tmst - TX_START_DELAY) to be programmed into concentrator + * Note: - Also add some margin, to be checked how much is needed, if needed + * - Valid for both Downlinks and Beacon packets + * + * Warning: unsigned arithmetic (handle roll-over) + * t_packet < t_current + TX_START_DELAY + MARGIN + */ + if ((packet->count_us - time_us) <= (TX_START_DELAY + TX_MARGIN_DELAY + TX_JIT_DELAY)) { + MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: Packet REJECTED, already too late to send it (current=%u, packet=%u, type=%d)\n", time_us, packet->count_us, pkt_type); + pthread_mutex_unlock(&mx_jit_queue); + return JIT_ERROR_TOO_LATE; + } + + /* Check criteria_2: Does packet timestamp seem plausible compared to current time + * We do not expect the server to program a downlink too early compared to current time + * Class A: downlink has to be sent in a 1s or 2s time window after RX + * Class B: downlink has to occur in a 128s time window + * Class C: no check needed, departure time has been calculated previously + * So let's define a safe delay above which we can say that the packet is out of bound: TX_MAX_ADVANCE_DELAY + * Note: - Valid for Downlinks only, not for Beacon packets + * + * Warning: unsigned arithmetic (handle roll-over) + t_packet > t_current + TX_MAX_ADVANCE_DELAY + */ + if ((pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_A) || (pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_B)) { + if ((packet->count_us - time_us) > TX_MAX_ADVANCE_DELAY) { + MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: Packet REJECTED, timestamp seems wrong, too much in advance (current=%u, packet=%u, type=%d)\n", time_us, packet->count_us, pkt_type); + pthread_mutex_unlock(&mx_jit_queue); + return JIT_ERROR_TOO_EARLY; + } + } + + /* Check criteria_3: does this new packet overlap with a packet already enqueued ? + * Note: - need to take into account packet's pre_delay and post_delay of each packet + * - Valid for both Downlinks and beacon packets + * - Beacon guard can be ignored if we try to queue a Class A downlink + */ + for (i=0; inum_pkt; i++) { + /* We ignore Beacon Guard for Class A/C downlinks */ + if (((pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_A) || (pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_C)) && (queue->nodes[i].pkt_type == JIT_PKT_TYPE_BEACON)) { + target_pre_delay = TX_START_DELAY; + } else { + target_pre_delay = queue->nodes[i].pre_delay; + } + + /* Check if there is a collision + * Warning: unsigned arithmetic (handle roll-over) + * t_packet_new - pre_delay_packet_new < t_packet_prev + post_delay_packet_prev (OVERLAP on post delay) + * t_packet_new + post_delay_packet_new > t_packet_prev - pre_delay_packet_prev (OVERLAP on pre delay) + */ + if (jit_collision_test(packet->count_us, packet_pre_delay, packet_post_delay, queue->nodes[i].pkt.count_us, target_pre_delay, queue->nodes[i].post_delay) == true) { + switch (queue->nodes[i].pkt_type) { + case JIT_PKT_TYPE_DOWNLINK_CLASS_A: + case JIT_PKT_TYPE_DOWNLINK_CLASS_B: + case JIT_PKT_TYPE_DOWNLINK_CLASS_C: + MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: Packet (type=%d) REJECTED, collision with packet already programmed at %u (%u)\n", pkt_type, queue->nodes[i].pkt.count_us, packet->count_us); + err_collision = JIT_ERROR_COLLISION_PACKET; + break; + case JIT_PKT_TYPE_BEACON: + if (pkt_type != JIT_PKT_TYPE_BEACON) { + /* do not overload logs for beacon/beacon collision, as it is expected to happen with beacon pre-scheduling algorith used */ + MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: Packet (type=%d) REJECTED, collision with beacon already programmed at %u (%u)\n", pkt_type, queue->nodes[i].pkt.count_us, packet->count_us); + } + err_collision = JIT_ERROR_COLLISION_BEACON; + break; + default: + MSG("ERROR: Unknown packet type, should not occur, BUG?\n"); + assert(0); + break; + } + pthread_mutex_unlock(&mx_jit_queue); + return err_collision; + } + } + + /* Finally enqueue it */ + /* Insert packet at the end of the queue */ + memcpy(&(queue->nodes[queue->num_pkt].pkt), packet, sizeof(struct lgw_pkt_tx_s)); + queue->nodes[queue->num_pkt].pre_delay = packet_pre_delay; + queue->nodes[queue->num_pkt].post_delay = packet_post_delay; + queue->nodes[queue->num_pkt].pkt_type = pkt_type; + if (pkt_type == JIT_PKT_TYPE_BEACON) { + queue->num_beacon++; + } + queue->num_pkt++; + /* Sort the queue in ascending order of packet timestamp */ + jit_sort_queue(queue); + + /* Done */ + pthread_mutex_unlock(&mx_jit_queue); + + jit_print_queue(queue, false, DEBUG_JIT); + + MSG_DEBUG(DEBUG_JIT, "enqueued packet with count_us=%u (size=%u bytes, toa=%u us, type=%u)\n", packet->count_us, packet->size, packet_post_delay, pkt_type); + + return JIT_ERROR_OK; +} + +enum jit_error_e jit_dequeue(struct jit_queue_s *queue, int index, struct lgw_pkt_tx_s *packet, enum jit_pkt_type_e *pkt_type) { + if (packet == NULL) { + MSG("ERROR: invalid parameter\n"); + return JIT_ERROR_INVALID; + } + + if ((index < 0) || (index >= JIT_QUEUE_MAX)) { + MSG("ERROR: invalid parameter\n"); + return JIT_ERROR_INVALID; + } + + if (jit_queue_is_empty(queue)) { + MSG("ERROR: cannot dequeue packet, JIT queue is empty\n"); + return JIT_ERROR_EMPTY; + } + + pthread_mutex_lock(&mx_jit_queue); + + /* Dequeue requested packet */ + memcpy(packet, &(queue->nodes[index].pkt), sizeof(struct lgw_pkt_tx_s)); + queue->num_pkt--; + *pkt_type = queue->nodes[index].pkt_type; + if (*pkt_type == JIT_PKT_TYPE_BEACON) { + queue->num_beacon--; + MSG_DEBUG(DEBUG_BEACON, "--- Beacon dequeued ---\n"); + } + + /* Replace dequeued packet with last packet of the queue */ + memcpy(&(queue->nodes[index]), &(queue->nodes[queue->num_pkt]), sizeof(struct jit_node_s)); + memset(&(queue->nodes[queue->num_pkt]), 0, sizeof(struct jit_node_s)); + + /* Sort queue in ascending order of packet timestamp */ + jit_sort_queue(queue); + + /* Done */ + pthread_mutex_unlock(&mx_jit_queue); + + jit_print_queue(queue, false, DEBUG_JIT); + + MSG_DEBUG(DEBUG_JIT, "dequeued packet with count_us=%u from index %d\n", packet->count_us, index); + + return JIT_ERROR_OK; +} + +enum jit_error_e jit_peek(struct jit_queue_s *queue, uint32_t time_us, int *pkt_idx) { + /* Return index of node containing a packet inline with given time */ + int i = 0; + int idx_highest_priority = -1; + if (pkt_idx == NULL) { + MSG("ERROR: invalid parameter\n"); + return JIT_ERROR_INVALID; + } + + if (jit_queue_is_empty(queue)) { + return JIT_ERROR_EMPTY; + } + + pthread_mutex_lock(&mx_jit_queue); + + /* Search for highest priority packet to be sent */ + for (i=0; inum_pkt; i++) { + /* First check if that packet is outdated: + * If a packet seems too much in advance, and was not rejected at enqueue time, + * it means that we missed it for peeking, we need to drop it + * + * Warning: unsigned arithmetic + * t_packet > t_current + TX_MAX_ADVANCE_DELAY + */ + if ((queue->nodes[i].pkt.count_us - time_us) >= TX_MAX_ADVANCE_DELAY) { + /* We drop the packet to avoid lock-up */ + queue->num_pkt--; + if (queue->nodes[i].pkt_type == JIT_PKT_TYPE_BEACON) { + queue->num_beacon--; + MSG("WARNING: --- Beacon dropped (current_time=%u, packet_time=%u) ---\n", time_us, queue->nodes[i].pkt.count_us); + } else { + MSG("WARNING: --- Packet dropped (current_time=%u, packet_time=%u) ---\n", time_us, queue->nodes[i].pkt.count_us); + } + + /* Replace dropped packet with last packet of the queue */ + memcpy(&(queue->nodes[i]), &(queue->nodes[queue->num_pkt]), sizeof(struct jit_node_s)); + memset(&(queue->nodes[queue->num_pkt]), 0, sizeof(struct jit_node_s)); + + /* Sort queue in ascending order of packet timestamp */ + jit_sort_queue(queue); + + /* restart loop after purge to find packet to be sent */ + i = 0; + continue; + } + + /* Then look for highest priority packet to be sent: + * Warning: unsigned arithmetic (handle roll-over) + * t_packet < t_highest + */ + if ((idx_highest_priority == -1) || (((queue->nodes[i].pkt.count_us - time_us) < (queue->nodes[idx_highest_priority].pkt.count_us - time_us)))) { + idx_highest_priority = i; + } + } + + /* Peek criteria 1: look for a packet to be sent in next TX_JIT_DELAY ms timeframe + * Warning: unsigned arithmetic (handle roll-over) + * t_packet < t_current + TX_JIT_DELAY + */ + if ((queue->nodes[idx_highest_priority].pkt.count_us - time_us) < TX_JIT_DELAY) { + *pkt_idx = idx_highest_priority; + MSG_DEBUG(DEBUG_JIT, "peek packet with count_us=%u at index %d\n", + queue->nodes[idx_highest_priority].pkt.count_us, idx_highest_priority); + } else { + *pkt_idx = -1; + } + + pthread_mutex_unlock(&mx_jit_queue); + + return JIT_ERROR_OK; +} + +void jit_print_queue(struct jit_queue_s *queue, bool show_all, int debug_level) { + int i = 0; + int loop_end; + + if (jit_queue_is_empty(queue)) { + MSG_DEBUG(debug_level, "INFO: [jit] queue is empty\n"); + } else { + pthread_mutex_lock(&mx_jit_queue); + + MSG_DEBUG(debug_level, "INFO: [jit] queue contains %d packets:\n", queue->num_pkt); + MSG_DEBUG(debug_level, "INFO: [jit] queue contains %d beacons:\n", queue->num_beacon); + loop_end = (show_all == true) ? JIT_QUEUE_MAX : queue->num_pkt; + for (i=0; inodes[i].pkt.count_us, + queue->nodes[i].pkt_type); + } + + pthread_mutex_unlock(&mx_jit_queue); + } +} diff --git a/packet_forwarder/src/lora_pkt_fwd.c b/packet_forwarder/src/lora_pkt_fwd.c new file mode 100644 index 0000000..70526ed --- /dev/null +++ b/packet_forwarder/src/lora_pkt_fwd.c @@ -0,0 +1,3226 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +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 +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- 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 /* socket specific definitions */ +#include /* INET constants and stuff */ +#include /* IP address conversion stuff */ +#include /* gai_strerror */ + +#include + +#include "trace.h" +#include "jitqueue.h" +#include "parson.h" +#include "base64.h" +#include "loragw_hal.h" +#include "loragw_aux.h" +#include "loragw_reg.h" +#include "loragw_gps.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 JSON_CONF_DEFAULT "global_conf.json" + +#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 PROTOCOL_JSON_RXPK_FRAME_FORMAT 1 + +#define XERR_INIT_AVG 128 /* nb of measurements the XTAL correction is averaged on as initial value */ +#define XERR_FILT_COEF 256 /* coefficient for low-pass XTAL error tracking */ + +#define PKT_PUSH_DATA 0 +#define PKT_PUSH_ACK 1 +#define PKT_PULL_DATA 2 +#define PKT_PULL_RESP 3 +#define PKT_PULL_ACK 4 +#define PKT_TX_ACK 5 + +#define NB_PKT_MAX 255 /* max number of packets per fetch/send cycle */ + +#define MIN_LORA_PREAMB 6 /* minimum Lora preamble length for this application */ +#define STD_LORA_PREAMB 8 +#define MIN_FSK_PREAMB 3 /* minimum FSK preamble length for this application */ +#define STD_FSK_PREAMB 5 + +#define STATUS_SIZE 200 +#define TX_BUFF_SIZE ((540 * NB_PKT_MAX) + 30 + STATUS_SIZE) +#define ACK_BUFF_SIZE 64 + +#define UNIX_GPS_EPOCH_OFFSET 315964800 /* Number of seconds ellapsed between 01.Jan.1970 00:00:00 + and 06.Jan.1980 00:00:00 */ + +#define DEFAULT_BEACON_FREQ_HZ 869525000 +#define DEFAULT_BEACON_FREQ_NB 1 +#define DEFAULT_BEACON_FREQ_STEP 0 +#define DEFAULT_BEACON_DATARATE 9 +#define DEFAULT_BEACON_BW_HZ 125000 +#define DEFAULT_BEACON_POWER 14 +#define DEFAULT_BEACON_INFODESC 0 + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */ + +/* signal handling variables */ +volatile bool exit_sig = false; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */ +volatile bool quit_sig = false; /* 1 -> application terminates without shutting down the hardware */ + +/* packets filtering configuration variables */ +static bool fwd_valid_pkt = true; /* packets with PAYLOAD CRC OK are forwarded */ +static bool fwd_error_pkt = false; /* packets with PAYLOAD CRC ERROR are NOT forwarded */ +static bool fwd_nocrc_pkt = false; /* packets with NO PAYLOAD CRC are NOT forwarded */ + +/* network configuration variables */ +static uint64_t lgwm = 0; /* Lora gateway MAC address */ +static char serv_addr[64] = STR(DEFAULT_SERVER); /* address of the server (host name or IPv4/IPv6) */ +static char serv_port_up[8] = STR(DEFAULT_PORT_UP); /* server port for upstream traffic */ +static char serv_port_down[8] = STR(DEFAULT_PORT_DW); /* server port for downstream traffic */ +static int keepalive_time = DEFAULT_KEEPALIVE; /* send a PULL_DATA request every X seconds, negative = disabled */ + +/* statistics collection configuration variables */ +static unsigned stat_interval = DEFAULT_STAT; /* time interval (in sec) at which statistics are collected and displayed */ + +/* gateway <-> MAC protocol variables */ +static uint32_t net_mac_h; /* Most Significant Nibble, network order */ +static uint32_t net_mac_l; /* Least Significant Nibble, network order */ + +/* network sockets */ +static int sock_up; /* socket for upstream traffic */ +static int sock_down; /* socket for downstream traffic */ + +/* network protocol variables */ +static struct timeval push_timeout_half = {0, (PUSH_TIMEOUT_MS * 500)}; /* cut in half, critical for throughput */ +static struct timeval pull_timeout = {0, (PULL_TIMEOUT_MS * 1000)}; /* non critical for throughput */ + +/* hardware access control and correction */ +pthread_mutex_t mx_concent = PTHREAD_MUTEX_INITIALIZER; /* control access to the concentrator */ +static pthread_mutex_t mx_xcorr = PTHREAD_MUTEX_INITIALIZER; /* control access to the XTAL correction */ +static bool xtal_correct_ok = false; /* set true when XTAL correction is stable enough */ +static double xtal_correct = 1.0; + +/* GPS configuration and synchronization */ +static char gps_tty_path[64] = "\0"; /* path of the TTY port GPS is connected on */ +static int gps_tty_fd = -1; /* file descriptor of the GPS TTY port */ +static bool gps_enabled = false; /* is GPS enabled on that gateway ? */ + +/* GPS time reference */ +static pthread_mutex_t mx_timeref = PTHREAD_MUTEX_INITIALIZER; /* control access to GPS time reference */ +static bool gps_ref_valid; /* is GPS reference acceptable (ie. not too old) */ +static struct tref time_reference_gps; /* time reference used for GPS <-> timestamp conversion */ + +/* Reference coordinates, for broadcasting (beacon) */ +static struct coord_s reference_coord; + +/* Enable faking the GPS coordinates of the gateway */ +static bool gps_fake_enable; /* enable the feature */ + +/* measurements to establish statistics */ +static pthread_mutex_t mx_meas_up = PTHREAD_MUTEX_INITIALIZER; /* control access to the upstream measurements */ +static uint32_t meas_nb_rx_rcv = 0; /* count packets received */ +static uint32_t meas_nb_rx_ok = 0; /* count packets received with PAYLOAD CRC OK */ +static uint32_t meas_nb_rx_bad = 0; /* count packets received with PAYLOAD CRC ERROR */ +static uint32_t meas_nb_rx_nocrc = 0; /* count packets received with NO PAYLOAD CRC */ +static uint32_t meas_up_pkt_fwd = 0; /* number of radio packet forwarded to the server */ +static uint32_t meas_up_network_byte = 0; /* sum of UDP bytes sent for upstream traffic */ +static uint32_t meas_up_payload_byte = 0; /* sum of radio payload bytes sent for upstream traffic */ +static uint32_t meas_up_dgram_sent = 0; /* number of datagrams sent for upstream traffic */ +static uint32_t meas_up_ack_rcv = 0; /* number of datagrams acknowledged for upstream traffic */ + +static pthread_mutex_t mx_meas_dw = PTHREAD_MUTEX_INITIALIZER; /* control access to the downstream measurements */ +static uint32_t meas_dw_pull_sent = 0; /* number of PULL requests sent for downstream traffic */ +static uint32_t meas_dw_ack_rcv = 0; /* number of PULL requests acknowledged for downstream traffic */ +static uint32_t meas_dw_dgram_rcv = 0; /* count PULL response packets received for downstream traffic */ +static uint32_t meas_dw_network_byte = 0; /* sum of UDP bytes sent for upstream traffic */ +static uint32_t meas_dw_payload_byte = 0; /* sum of radio payload bytes sent for upstream traffic */ +static uint32_t meas_nb_tx_ok = 0; /* count packets emitted successfully */ +static uint32_t meas_nb_tx_fail = 0; /* count packets were TX failed for other reasons */ +static uint32_t meas_nb_tx_requested = 0; /* count TX request from server (downlinks) */ +static uint32_t meas_nb_tx_rejected_collision_packet = 0; /* count packets were TX request were rejected due to collision with another packet already programmed */ +static uint32_t meas_nb_tx_rejected_collision_beacon = 0; /* count packets were TX request were rejected due to collision with a beacon already programmed */ +static uint32_t meas_nb_tx_rejected_too_late = 0; /* count packets were TX request were rejected because it is too late to program it */ +static uint32_t meas_nb_tx_rejected_too_early = 0; /* count packets were TX request were rejected because timestamp is too much in advance */ +static uint32_t meas_nb_beacon_queued = 0; /* count beacon inserted in jit queue */ +static uint32_t meas_nb_beacon_sent = 0; /* count beacon actually sent to concentrator */ +static uint32_t meas_nb_beacon_rejected = 0; /* count beacon rejected for queuing */ + +static pthread_mutex_t mx_meas_gps = PTHREAD_MUTEX_INITIALIZER; /* control access to the GPS statistics */ +static bool gps_coord_valid; /* could we get valid GPS coordinates ? */ +static struct coord_s meas_gps_coord; /* GPS position of the gateway */ +static struct coord_s meas_gps_err; /* GPS position of the gateway */ + +static pthread_mutex_t mx_stat_rep = PTHREAD_MUTEX_INITIALIZER; /* control access to the status report */ +static bool report_ready = false; /* true when there is a new report to send to the server */ +static char status_report[STATUS_SIZE]; /* status report as a JSON object */ + +/* beacon parameters */ +static uint32_t beacon_period = 0; /* set beaconing period, must be a sub-multiple of 86400, the nb of sec in a day */ +static uint32_t beacon_freq_hz = DEFAULT_BEACON_FREQ_HZ; /* set beacon TX frequency, in Hz */ +static uint8_t beacon_freq_nb = DEFAULT_BEACON_FREQ_NB; /* set number of beaconing channels beacon */ +static uint32_t beacon_freq_step = DEFAULT_BEACON_FREQ_STEP; /* set frequency step between beacon channels, in Hz */ +static uint8_t beacon_datarate = DEFAULT_BEACON_DATARATE; /* set beacon datarate (SF) */ +static uint32_t beacon_bw_hz = DEFAULT_BEACON_BW_HZ; /* set beacon bandwidth, in Hz */ +static int8_t beacon_power = DEFAULT_BEACON_POWER; /* set beacon TX power, in dBm */ +static uint8_t beacon_infodesc = DEFAULT_BEACON_INFODESC; /* set beacon information descriptor */ + +/* auto-quit function */ +static uint32_t autoquit_threshold = 0; /* enable auto-quit after a number of non-acknowledged PULL_DATA (0 = disabled)*/ + +/* Just In Time TX scheduling */ +static struct jit_queue_s jit_queue[LGW_RF_CHAIN_NB]; + +/* Gateway specificities */ +static int8_t antenna_gain = 0; + +/* TX capabilities */ +static struct lgw_tx_gain_lut_s txlut[LGW_RF_CHAIN_NB]; /* TX gain table */ +static uint32_t tx_freq_min[LGW_RF_CHAIN_NB]; /* lowest frequency supported by TX chain */ +static uint32_t tx_freq_max[LGW_RF_CHAIN_NB]; /* highest frequency supported by TX chain */ + +static uint32_t nb_pkt_log[LGW_IF_CHAIN_NB][8]; /* [CH][SF] */ +static uint32_t nb_pkt_received_lora = 0; +static uint32_t nb_pkt_received_fsk = 0; + +static struct lgw_conf_debug_s debugconf; +static uint32_t nb_pkt_received_ref[16]; + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ + +static void usage(void); + +static void sig_handler(int sigio); + +static int parse_SX130x_configuration(const char * conf_file); + +static int parse_gateway_configuration(const char * conf_file); + +static int parse_debug_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); + +static int get_tx_gain_lut_index(uint8_t rf_chain, int8_t rf_power, uint8_t * lut_index); + +/* threads */ +void thread_up(void); +void thread_down(void); +void thread_jit(void); +void thread_gps(void); +void thread_valid(void); + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +static void usage( void ) +{ + printf("~~~ Library version string~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); + printf(" %s\n", lgw_version_info()); + printf("~~~ Available options ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); + printf(" -h print this help\n"); + printf(" -c use config file other than 'global_conf.json'\n"); + printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); +} + +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_SX130x_configuration(const char * conf_file) { + int i, j; + char param_name[32]; /* used to generate variable parameter names */ + const char *str; /* used to store string value from JSON object */ + const char conf_obj_name[] = "SX130x_conf"; + JSON_Value *root_val = NULL; + JSON_Value *val = NULL; + JSON_Object *conf_obj = NULL; + JSON_Object *conf_txgain_obj; + JSON_Object *conf_ts_obj; + JSON_Array *conf_txlut_array; + + struct lgw_conf_board_s boardconf; + struct lgw_conf_rxrf_s rfconf; + struct lgw_conf_rxif_s ifconf; + struct lgw_conf_timestamp_s tsconf; + uint32_t sf, bw, fdev; + bool sx1250_tx_lut; + + /* try to parse JSON */ + root_val = json_parse_file_with_comments(conf_file); + if (root_val == NULL) { + MSG("ERROR: %s is not a valid JSON file\n", conf_file); + exit(EXIT_FAILURE); + } + + /* point to the gateway configuration object */ + conf_obj = json_object_get_object(json_value_get_object(root_val), conf_obj_name); + if (conf_obj == NULL) { + MSG("INFO: %s does not contain a JSON object named %s\n", conf_file, conf_obj_name); + return -1; + } else { + MSG("INFO: %s does contain a JSON object named %s, parsing SX1302 parameters\n", conf_file, conf_obj_name); + } + + /* set board configuration */ + memset(&boardconf, 0, sizeof boardconf); /* initialize configuration structure */ + str = json_object_get_string(conf_obj, "spidev_path"); + if (str != NULL) { + strncpy(boardconf.spidev_path, str, sizeof boardconf.spidev_path); + } else { + MSG("ERROR: spidev path must be configured in %s\n", conf_file); + return -1; + } + + val = json_object_get_value(conf_obj, "lorawan_public"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONBoolean) { + boardconf.lorawan_public = (bool)json_value_get_boolean(val); + } else { + MSG("WARNING: Data type for lorawan_public seems wrong, please check\n"); + boardconf.lorawan_public = false; + } + val = json_object_get_value(conf_obj, "clksrc"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + boardconf.clksrc = (uint8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for clksrc seems wrong, please check\n"); + boardconf.clksrc = 0; + } + val = json_object_get_value(conf_obj, "full_duplex"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONBoolean) { + boardconf.full_duplex = (bool)json_value_get_boolean(val); + } else { + MSG("WARNING: Data type for full_duplex seems wrong, please check\n"); + boardconf.full_duplex = false; + } + MSG("INFO: spidev_path %s, lorawan_public %d, clksrc %d, full_duplex %d\n", boardconf.spidev_path, boardconf.lorawan_public, boardconf.clksrc, boardconf.full_duplex); + /* all parameters parsed, submitting configuration to the HAL */ + if (lgw_board_setconf(&boardconf) != LGW_HAL_SUCCESS) { + MSG("ERROR: Failed to configure board\n"); + return -1; + } + + /* set antenna gain configuration */ + val = json_object_get_value(conf_obj, "antenna_gain"); /* fetch value (if possible) */ + if (val != NULL) { + if (json_value_get_type(val) == JSONNumber) { + antenna_gain = (int8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for antenna_gain seems wrong, please check\n"); + antenna_gain = 0; + } + } + MSG("INFO: antenna_gain %d dBi\n", antenna_gain); + + /* set timestamp configuration */ + conf_ts_obj = json_object_get_object(conf_obj, "precision_timestamp"); + if (conf_ts_obj == NULL) { + MSG("INFO: %s does not contain a JSON object for precision timestamp\n", conf_file); + } else { + val = json_object_get_value(conf_ts_obj, "enable"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONBoolean) { + tsconf.enable_precision_ts = (bool)json_value_get_boolean(val); + } else { + MSG("WARNING: Data type for precision_timestamp.enable seems wrong, please check\n"); + tsconf.enable_precision_ts = false; + } + if (tsconf.enable_precision_ts == true) { + val = json_object_get_value(conf_ts_obj, "max_ts_metrics"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + tsconf.max_ts_metrics = (uint8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for precision_timestamp.max_ts_metrics seems wrong, please check\n"); + tsconf.max_ts_metrics = 0xFF; + } + val = json_object_get_value(conf_ts_obj, "nb_symbols"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + tsconf.nb_symbols = (uint8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for precision_timestamp.nb_symbols seems wrong, please check\n"); + tsconf.nb_symbols = 1; + } + MSG("INFO: Configuring precision timestamp: max_ts_metrics:%u, nb_symbols:%u\n", tsconf.max_ts_metrics, tsconf.nb_symbols); + + /* all parameters parsed, submitting configuration to the HAL */ + if (lgw_timestamp_setconf(&tsconf) != LGW_HAL_SUCCESS) { + MSG("ERROR: Failed to configure precision timestamp\n"); + return -1; + } + } else { + MSG("INFO: Configuring legacy timestamp\n"); + } + } + + /* set configuration for RF chains */ + for (i = 0; i < LGW_RF_CHAIN_NB; ++i) { + memset(&rfconf, 0, sizeof rfconf); /* initialize configuration structure */ + snprintf(param_name, sizeof param_name, "radio_%i", i); /* compose parameter path inside JSON structure */ + val = json_object_get_value(conf_obj, param_name); /* fetch value (if possible) */ + if (json_value_get_type(val) != JSONObject) { + MSG("INFO: no configuration for radio %i\n", i); + continue; + } + /* there is an object to configure that radio, let's parse it */ + snprintf(param_name, sizeof param_name, "radio_%i.enable", i); + val = json_object_dotget_value(conf_obj, param_name); + if (json_value_get_type(val) == JSONBoolean) { + rfconf.enable = (bool)json_value_get_boolean(val); + } else { + rfconf.enable = false; + } + if (rfconf.enable == false) { /* radio disabled, nothing else to parse */ + MSG("INFO: radio %i disabled\n", i); + } else { /* radio enabled, will parse the other parameters */ + snprintf(param_name, sizeof param_name, "radio_%i.freq", i); + rfconf.freq_hz = (uint32_t)json_object_dotget_number(conf_obj, param_name); + snprintf(param_name, sizeof param_name, "radio_%i.rssi_offset", i); + rfconf.rssi_offset = (float)json_object_dotget_number(conf_obj, param_name); + snprintf(param_name, sizeof param_name, "radio_%i.rssi_tcomp.coeff_a", i); + rfconf.rssi_tcomp.coeff_a = (float)json_object_dotget_number(conf_obj, param_name); + snprintf(param_name, sizeof param_name, "radio_%i.rssi_tcomp.coeff_b", i); + rfconf.rssi_tcomp.coeff_b = (float)json_object_dotget_number(conf_obj, param_name); + snprintf(param_name, sizeof param_name, "radio_%i.rssi_tcomp.coeff_c", i); + rfconf.rssi_tcomp.coeff_c = (float)json_object_dotget_number(conf_obj, param_name); + snprintf(param_name, sizeof param_name, "radio_%i.rssi_tcomp.coeff_d", i); + rfconf.rssi_tcomp.coeff_d = (float)json_object_dotget_number(conf_obj, param_name); + snprintf(param_name, sizeof param_name, "radio_%i.rssi_tcomp.coeff_e", i); + rfconf.rssi_tcomp.coeff_e = (float)json_object_dotget_number(conf_obj, param_name); + snprintf(param_name, sizeof param_name, "radio_%i.type", i); + str = json_object_dotget_string(conf_obj, param_name); + if (!strncmp(str, "SX1255", 6)) { + rfconf.type = LGW_RADIO_TYPE_SX1255; + } else if (!strncmp(str, "SX1257", 6)) { + rfconf.type = LGW_RADIO_TYPE_SX1257; + } else if (!strncmp(str, "SX1250", 6)) { + rfconf.type = LGW_RADIO_TYPE_SX1250; + } else { + MSG("WARNING: invalid radio type: %s (should be SX1255 or SX1257 or SX1250)\n", str); + } + snprintf(param_name, sizeof param_name, "radio_%i.tx_enable", i); + val = json_object_dotget_value(conf_obj, param_name); + if (json_value_get_type(val) == JSONBoolean) { + rfconf.tx_enable = (bool)json_value_get_boolean(val); + if (rfconf.tx_enable == true) { + /* tx is enabled on this rf chain, we need its frequency range */ + snprintf(param_name, sizeof param_name, "radio_%i.tx_freq_min", i); + tx_freq_min[i] = (uint32_t)json_object_dotget_number(conf_obj, param_name); + snprintf(param_name, sizeof param_name, "radio_%i.tx_freq_max", i); + tx_freq_max[i] = (uint32_t)json_object_dotget_number(conf_obj, param_name); + if ((tx_freq_min[i] == 0) || (tx_freq_max[i] == 0)) { + MSG("WARNING: no frequency range specified for TX rf chain %d\n", i); + } + + /* set configuration for tx gains */ + memset(&txlut[i], 0, sizeof txlut[i]); /* initialize configuration structure */ + snprintf(param_name, sizeof param_name, "radio_%i.tx_gain_lut", i); + conf_txlut_array = json_object_dotget_array(conf_obj, param_name); + if (conf_txlut_array != NULL) { + txlut[i].size = json_array_get_count(conf_txlut_array); + /* Detect if we have a sx125x or sx1250 configuration */ + conf_txgain_obj = json_array_get_object(conf_txlut_array, 0); + val = json_object_dotget_value(conf_txgain_obj, "pwr_idx"); + if (val != NULL) { + printf("INFO: Configuring Tx Gain LUT for rf_chain %u with %u indexes for sx1250\n", i, txlut[i].size); + sx1250_tx_lut = true; + } else { + printf("INFO: Configuring Tx Gain LUT for rf_chain %u with %u indexes for sx125x\n", i, txlut[i].size); + sx1250_tx_lut = false; + } + /* Parse the table */ + for (j = 0; j < (int)txlut[i].size; j++) { + /* Sanity check */ + if (j >= TX_GAIN_LUT_SIZE_MAX) { + printf("ERROR: TX Gain LUT [%u] index %d not supported, skip it\n", i, j); + break; + } + /* Get TX gain object from LUT */ + conf_txgain_obj = json_array_get_object(conf_txlut_array, j); + /* rf power */ + val = json_object_dotget_value(conf_txgain_obj, "rf_power"); + if (json_value_get_type(val) == JSONNumber) { + txlut[i].lut[j].rf_power = (int8_t)json_value_get_number(val); + } else { + printf("WARNING: Data type for %s[%d] seems wrong, please check\n", "rf_power", j); + txlut[i].lut[j].rf_power = 0; + } + /* PA gain */ + val = json_object_dotget_value(conf_txgain_obj, "pa_gain"); + if (json_value_get_type(val) == JSONNumber) { + txlut[i].lut[j].pa_gain = (uint8_t)json_value_get_number(val); + } else { + printf("WARNING: Data type for %s[%d] seems wrong, please check\n", "pa_gain", j); + txlut[i].lut[j].pa_gain = 0; + } + if (sx1250_tx_lut == false) { + /* DIG gain */ + val = json_object_dotget_value(conf_txgain_obj, "dig_gain"); + if (json_value_get_type(val) == JSONNumber) { + txlut[i].lut[j].dig_gain = (uint8_t)json_value_get_number(val); + } else { + printf("WARNING: Data type for %s[%d] seems wrong, please check\n", "dig_gain", j); + txlut[i].lut[j].dig_gain = 0; + } + /* DAC gain */ + val = json_object_dotget_value(conf_txgain_obj, "dac_gain"); + if (json_value_get_type(val) == JSONNumber) { + txlut[i].lut[j].dac_gain = (uint8_t)json_value_get_number(val); + } else { + printf("WARNING: Data type for %s[%d] seems wrong, please check\n", "dac_gain", j); + txlut[i].lut[j].dac_gain = 3; /* This is the only dac_gain supported for now */ + } + /* MIX gain */ + val = json_object_dotget_value(conf_txgain_obj, "mix_gain"); + if (json_value_get_type(val) == JSONNumber) { + txlut[i].lut[j].mix_gain = (uint8_t)json_value_get_number(val); + } else { + printf("WARNING: Data type for %s[%d] seems wrong, please check\n", "mix_gain", j); + txlut[i].lut[j].mix_gain = 0; + } + } else { + /* TODO: rework this, should not be needed for sx1250 */ + txlut[i].lut[j].mix_gain = 5; + + /* power index */ + val = json_object_dotget_value(conf_txgain_obj, "pwr_idx"); + if (json_value_get_type(val) == JSONNumber) { + txlut[i].lut[j].pwr_idx = (uint8_t)json_value_get_number(val); + } else { + printf("WARNING: Data type for %s[%d] seems wrong, please check\n", "pwr_idx", j); + txlut[i].lut[j].pwr_idx = 0; + } + } + } + /* all parameters parsed, submitting configuration to the HAL */ + if (txlut[i].size > 0) { + if (lgw_txgain_setconf(i, &txlut[i]) != LGW_HAL_SUCCESS) { + MSG("ERROR: Failed to configure concentrator TX Gain LUT for rf_chain %u\n", i); + return -1; + } + } else { + MSG("WARNING: No TX gain LUT defined for rf_chain %u\n", i); + } + } else { + MSG("WARNING: No TX gain LUT defined for rf_chain %u\n", i); + } + } + } else { + rfconf.tx_enable = false; + } + MSG("INFO: radio %i enabled (type %s), center frequency %u, RSSI offset %f, tx enabled %d\n", i, str, rfconf.freq_hz, rfconf.rssi_offset, rfconf.tx_enable); + } + /* all parameters parsed, submitting configuration to the HAL */ + if (lgw_rxrf_setconf(i, &rfconf) != LGW_HAL_SUCCESS) { + MSG("ERROR: invalid configuration for radio %i\n", i); + return -1; + } + } + + /* set configuration for Lora multi-SF channels (bandwidth cannot be set) */ + for (i = 0; i < LGW_MULTI_NB; ++i) { + memset(&ifconf, 0, sizeof ifconf); /* initialize configuration structure */ + snprintf(param_name, sizeof param_name, "chan_multiSF_%i", i); /* compose parameter path inside JSON structure */ + val = json_object_get_value(conf_obj, param_name); /* fetch value (if possible) */ + if (json_value_get_type(val) != JSONObject) { + MSG("INFO: no configuration for Lora multi-SF channel %i\n", i); + continue; + } + /* there is an object to configure that Lora multi-SF channel, let's parse it */ + snprintf(param_name, sizeof param_name, "chan_multiSF_%i.enable", i); + val = json_object_dotget_value(conf_obj, param_name); + if (json_value_get_type(val) == JSONBoolean) { + ifconf.enable = (bool)json_value_get_boolean(val); + } else { + ifconf.enable = false; + } + if (ifconf.enable == false) { /* Lora multi-SF channel disabled, nothing else to parse */ + MSG("INFO: Lora multi-SF channel %i disabled\n", i); + } else { /* Lora multi-SF channel enabled, will parse the other parameters */ + snprintf(param_name, sizeof param_name, "chan_multiSF_%i.radio", i); + ifconf.rf_chain = (uint32_t)json_object_dotget_number(conf_obj, param_name); + snprintf(param_name, sizeof param_name, "chan_multiSF_%i.if", i); + ifconf.freq_hz = (int32_t)json_object_dotget_number(conf_obj, param_name); + // TODO: handle individual SF enabling and disabling (spread_factor) + MSG("INFO: Lora multi-SF channel %i> radio %i, IF %i Hz, 125 kHz bw, SF 5 to 12\n", i, ifconf.rf_chain, ifconf.freq_hz); + } + /* all parameters parsed, submitting configuration to the HAL */ + if (lgw_rxif_setconf(i, &ifconf) != LGW_HAL_SUCCESS) { + MSG("ERROR: invalid configuration for Lora multi-SF channel %i\n", i); + return -1; + } + } + + /* set configuration for Lora standard channel */ + memset(&ifconf, 0, sizeof ifconf); /* initialize configuration structure */ + val = json_object_get_value(conf_obj, "chan_Lora_std"); /* fetch value (if possible) */ + if (json_value_get_type(val) != JSONObject) { + MSG("INFO: no configuration for Lora standard channel\n"); + } else { + val = json_object_dotget_value(conf_obj, "chan_Lora_std.enable"); + if (json_value_get_type(val) == JSONBoolean) { + ifconf.enable = (bool)json_value_get_boolean(val); + } else { + ifconf.enable = false; + } + if (ifconf.enable == false) { + MSG("INFO: Lora standard channel %i disabled\n", i); + } else { + ifconf.rf_chain = (uint32_t)json_object_dotget_number(conf_obj, "chan_Lora_std.radio"); + ifconf.freq_hz = (int32_t)json_object_dotget_number(conf_obj, "chan_Lora_std.if"); + bw = (uint32_t)json_object_dotget_number(conf_obj, "chan_Lora_std.bandwidth"); + switch(bw) { + case 500000: ifconf.bandwidth = BW_500KHZ; break; + case 250000: ifconf.bandwidth = BW_250KHZ; break; + case 125000: ifconf.bandwidth = BW_125KHZ; break; + default: ifconf.bandwidth = BW_UNDEFINED; + } + sf = (uint32_t)json_object_dotget_number(conf_obj, "chan_Lora_std.spread_factor"); + switch(sf) { + case 5: ifconf.datarate = DR_LORA_SF5; break; + case 6: ifconf.datarate = DR_LORA_SF6; break; + case 7: ifconf.datarate = DR_LORA_SF7; break; + case 8: ifconf.datarate = DR_LORA_SF8; break; + case 9: ifconf.datarate = DR_LORA_SF9; break; + case 10: ifconf.datarate = DR_LORA_SF10; break; + case 11: ifconf.datarate = DR_LORA_SF11; break; + case 12: ifconf.datarate = DR_LORA_SF12; break; + default: ifconf.datarate = DR_UNDEFINED; + } + val = json_object_dotget_value(conf_obj, "chan_Lora_std.implicit_hdr"); + if (json_value_get_type(val) == JSONBoolean) { + ifconf.implicit_hdr = (bool)json_value_get_boolean(val); + } else { + ifconf.implicit_hdr = false; + } + if (ifconf.implicit_hdr == true) { + val = json_object_dotget_value(conf_obj, "chan_Lora_std.implicit_payload_length"); + if (json_value_get_type(val) == JSONNumber) { + ifconf.implicit_payload_length = (uint8_t)json_value_get_number(val); + } else { + MSG("ERROR: payload length setting is mandatory for implicit header mode\n"); + return -1; + } + val = json_object_dotget_value(conf_obj, "chan_Lora_std.implicit_crc_en"); + if (json_value_get_type(val) == JSONBoolean) { + ifconf.implicit_crc_en = (bool)json_value_get_boolean(val); + } else { + MSG("ERROR: CRC enable setting is mandatory for implicit header mode\n"); + return -1; + } + val = json_object_dotget_value(conf_obj, "chan_Lora_std.implicit_coderate"); + if (json_value_get_type(val) == JSONNumber) { + ifconf.implicit_coderate = (uint8_t)json_value_get_number(val); + } else { + MSG("ERROR: coding rate setting is mandatory for implicit header mode\n"); + return -1; + } + } + + MSG("INFO: Lora std channel> radio %i, IF %i Hz, %u Hz bw, SF %u, %s\n", ifconf.rf_chain, ifconf.freq_hz, bw, sf, (ifconf.implicit_hdr == true) ? "Implicit header" : "Explicit header"); + } + if (lgw_rxif_setconf(8, &ifconf) != LGW_HAL_SUCCESS) { + MSG("ERROR: invalid configuration for Lora standard channel\n"); + return -1; + } + } + + /* set configuration for FSK channel */ + memset(&ifconf, 0, sizeof ifconf); /* initialize configuration structure */ + val = json_object_get_value(conf_obj, "chan_FSK"); /* fetch value (if possible) */ + if (json_value_get_type(val) != JSONObject) { + MSG("INFO: no configuration for FSK channel\n"); + } else { + val = json_object_dotget_value(conf_obj, "chan_FSK.enable"); + if (json_value_get_type(val) == JSONBoolean) { + ifconf.enable = (bool)json_value_get_boolean(val); + } else { + ifconf.enable = false; + } + if (ifconf.enable == false) { + MSG("INFO: FSK channel %i disabled\n", i); + } else { + ifconf.rf_chain = (uint32_t)json_object_dotget_number(conf_obj, "chan_FSK.radio"); + ifconf.freq_hz = (int32_t)json_object_dotget_number(conf_obj, "chan_FSK.if"); + bw = (uint32_t)json_object_dotget_number(conf_obj, "chan_FSK.bandwidth"); + fdev = (uint32_t)json_object_dotget_number(conf_obj, "chan_FSK.freq_deviation"); + ifconf.datarate = (uint32_t)json_object_dotget_number(conf_obj, "chan_FSK.datarate"); + + /* if chan_FSK.bandwidth is set, it has priority over chan_FSK.freq_deviation */ + if ((bw == 0) && (fdev != 0)) { + bw = 2 * fdev + ifconf.datarate; + } + if (bw == 0) ifconf.bandwidth = BW_UNDEFINED; +#if 0 /* TODO */ + else if (bw <= 7800) ifconf.bandwidth = BW_7K8HZ; + else if (bw <= 15600) ifconf.bandwidth = BW_15K6HZ; + else if (bw <= 31200) ifconf.bandwidth = BW_31K2HZ; + else if (bw <= 62500) ifconf.bandwidth = BW_62K5HZ; +#endif + else if (bw <= 125000) ifconf.bandwidth = BW_125KHZ; + else if (bw <= 250000) ifconf.bandwidth = BW_250KHZ; + else if (bw <= 500000) ifconf.bandwidth = BW_500KHZ; + else ifconf.bandwidth = BW_UNDEFINED; + + MSG("INFO: FSK channel> radio %i, IF %i Hz, %u Hz bw, %u bps datarate\n", ifconf.rf_chain, ifconf.freq_hz, bw, ifconf.datarate); + } + if (lgw_rxif_setconf(9, &ifconf) != LGW_HAL_SUCCESS) { + MSG("ERROR: invalid configuration for FSK channel\n"); + return -1; + } + } + json_value_free(root_val); + + return 0; +} + +static int parse_gateway_configuration(const char * conf_file) { + const char conf_obj_name[] = "gateway_conf"; + JSON_Value *root_val; + JSON_Object *conf_obj = NULL; + JSON_Value *val = NULL; /* needed to detect the absence of some fields */ + const char *str; /* pointer to sub-strings in the JSON data */ + unsigned long long ull = 0; + + /* try to parse JSON */ + root_val = json_parse_file_with_comments(conf_file); + if (root_val == NULL) { + MSG("ERROR: %s is not a valid JSON file\n", conf_file); + exit(EXIT_FAILURE); + } + + /* point to the gateway configuration object */ + conf_obj = json_object_get_object(json_value_get_object(root_val), conf_obj_name); + if (conf_obj == NULL) { + MSG("INFO: %s does not contain a JSON object named %s\n", conf_file, conf_obj_name); + return -1; + } else { + MSG("INFO: %s does contain a JSON object named %s, parsing gateway parameters\n", conf_file, conf_obj_name); + } + + /* gateway unique identifier (aka MAC address) (optional) */ + str = json_object_get_string(conf_obj, "gateway_ID"); + if (str != NULL) { + sscanf(str, "%llx", &ull); + lgwm = ull; + MSG("INFO: gateway MAC address is configured to %016llX\n", ull); + } + + /* server hostname or IP address (optional) */ + str = json_object_get_string(conf_obj, "server_address"); + if (str != NULL) { + strncpy(serv_addr, str, sizeof serv_addr); + MSG("INFO: server hostname or IP address is configured to \"%s\"\n", serv_addr); + } + + /* get up and down ports (optional) */ + val = json_object_get_value(conf_obj, "serv_port_up"); + if (val != NULL) { + snprintf(serv_port_up, sizeof serv_port_up, "%u", (uint16_t)json_value_get_number(val)); + MSG("INFO: upstream port is configured to \"%s\"\n", serv_port_up); + } + val = json_object_get_value(conf_obj, "serv_port_down"); + if (val != NULL) { + snprintf(serv_port_down, sizeof serv_port_down, "%u", (uint16_t)json_value_get_number(val)); + MSG("INFO: downstream port is configured to \"%s\"\n", serv_port_down); + } + + /* get keep-alive interval (in seconds) for downstream (optional) */ + val = json_object_get_value(conf_obj, "keepalive_interval"); + if (val != NULL) { + keepalive_time = (int)json_value_get_number(val); + MSG("INFO: downstream keep-alive interval is configured to %u seconds\n", keepalive_time); + } + + /* get interval (in seconds) for statistics display (optional) */ + val = json_object_get_value(conf_obj, "stat_interval"); + if (val != NULL) { + stat_interval = (unsigned)json_value_get_number(val); + MSG("INFO: statistics display interval is configured to %u seconds\n", stat_interval); + } + + /* get time-out value (in ms) for upstream datagrams (optional) */ + val = json_object_get_value(conf_obj, "push_timeout_ms"); + if (val != NULL) { + push_timeout_half.tv_usec = 500 * (long int)json_value_get_number(val); + MSG("INFO: upstream PUSH_DATA time-out is configured to %u ms\n", (unsigned)(push_timeout_half.tv_usec / 500)); + } + + /* packet filtering parameters */ + val = json_object_get_value(conf_obj, "forward_crc_valid"); + if (json_value_get_type(val) == JSONBoolean) { + fwd_valid_pkt = (bool)json_value_get_boolean(val); + } + MSG("INFO: packets received with a valid CRC will%s be forwarded\n", (fwd_valid_pkt ? "" : " NOT")); + val = json_object_get_value(conf_obj, "forward_crc_error"); + if (json_value_get_type(val) == JSONBoolean) { + fwd_error_pkt = (bool)json_value_get_boolean(val); + } + MSG("INFO: packets received with a CRC error will%s be forwarded\n", (fwd_error_pkt ? "" : " NOT")); + val = json_object_get_value(conf_obj, "forward_crc_disabled"); + if (json_value_get_type(val) == JSONBoolean) { + fwd_nocrc_pkt = (bool)json_value_get_boolean(val); + } + MSG("INFO: packets received with no CRC will%s be forwarded\n", (fwd_nocrc_pkt ? "" : " NOT")); + + /* GPS module TTY path (optional) */ + str = json_object_get_string(conf_obj, "gps_tty_path"); + if (str != NULL) { + strncpy(gps_tty_path, str, sizeof gps_tty_path); + MSG("INFO: GPS serial port path is configured to \"%s\"\n", gps_tty_path); + } + + /* get reference coordinates */ + val = json_object_get_value(conf_obj, "ref_latitude"); + if (val != NULL) { + reference_coord.lat = (double)json_value_get_number(val); + MSG("INFO: Reference latitude is configured to %f deg\n", reference_coord.lat); + } + val = json_object_get_value(conf_obj, "ref_longitude"); + if (val != NULL) { + reference_coord.lon = (double)json_value_get_number(val); + MSG("INFO: Reference longitude is configured to %f deg\n", reference_coord.lon); + } + val = json_object_get_value(conf_obj, "ref_altitude"); + if (val != NULL) { + reference_coord.alt = (short)json_value_get_number(val); + MSG("INFO: Reference altitude is configured to %i meters\n", reference_coord.alt); + } + + /* Gateway GPS coordinates hardcoding (aka. faking) option */ + val = json_object_get_value(conf_obj, "fake_gps"); + if (json_value_get_type(val) == JSONBoolean) { + gps_fake_enable = (bool)json_value_get_boolean(val); + if (gps_fake_enable == true) { + MSG("INFO: fake GPS is enabled\n"); + } else { + MSG("INFO: fake GPS is disabled\n"); + } + } + + /* Beacon signal period (optional) */ + val = json_object_get_value(conf_obj, "beacon_period"); + if (val != NULL) { + beacon_period = (uint32_t)json_value_get_number(val); + if ((beacon_period > 0) && (beacon_period < 6)) { + MSG("ERROR: invalid configuration for Beacon period, must be >= 6s\n"); + return -1; + } else { + MSG("INFO: Beaconing period is configured to %u seconds\n", beacon_period); + } + } + + /* Beacon TX frequency (optional) */ + val = json_object_get_value(conf_obj, "beacon_freq_hz"); + if (val != NULL) { + beacon_freq_hz = (uint32_t)json_value_get_number(val); + MSG("INFO: Beaconing signal will be emitted at %u Hz\n", beacon_freq_hz); + } + + /* Number of beacon channels (optional) */ + val = json_object_get_value(conf_obj, "beacon_freq_nb"); + if (val != NULL) { + beacon_freq_nb = (uint8_t)json_value_get_number(val); + MSG("INFO: Beaconing channel number is set to %u\n", beacon_freq_nb); + } + + /* Frequency step between beacon channels (optional) */ + val = json_object_get_value(conf_obj, "beacon_freq_step"); + if (val != NULL) { + beacon_freq_step = (uint32_t)json_value_get_number(val); + MSG("INFO: Beaconing channel frequency step is set to %uHz\n", beacon_freq_step); + } + + /* Beacon datarate (optional) */ + val = json_object_get_value(conf_obj, "beacon_datarate"); + if (val != NULL) { + beacon_datarate = (uint8_t)json_value_get_number(val); + MSG("INFO: Beaconing datarate is set to SF%d\n", beacon_datarate); + } + + /* Beacon modulation bandwidth (optional) */ + val = json_object_get_value(conf_obj, "beacon_bw_hz"); + if (val != NULL) { + beacon_bw_hz = (uint32_t)json_value_get_number(val); + MSG("INFO: Beaconing modulation bandwidth is set to %dHz\n", beacon_bw_hz); + } + + /* Beacon TX power (optional) */ + val = json_object_get_value(conf_obj, "beacon_power"); + if (val != NULL) { + beacon_power = (int8_t)json_value_get_number(val); + MSG("INFO: Beaconing TX power is set to %ddBm\n", beacon_power); + } + + /* Beacon information descriptor (optional) */ + val = json_object_get_value(conf_obj, "beacon_infodesc"); + if (val != NULL) { + beacon_infodesc = (uint8_t)json_value_get_number(val); + MSG("INFO: Beaconing information descriptor is set to %u\n", beacon_infodesc); + } + + /* Auto-quit threshold (optional) */ + val = json_object_get_value(conf_obj, "autoquit_threshold"); + if (val != NULL) { + autoquit_threshold = (uint32_t)json_value_get_number(val); + MSG("INFO: Auto-quit after %u non-acknowledged PULL_DATA\n", autoquit_threshold); + } + + /* free JSON parsing data structure */ + json_value_free(root_val); + return 0; +} + +static int parse_debug_configuration(const char * conf_file) { + int i; + const char conf_obj_name[] = "debug_conf"; + JSON_Value *root_val; + JSON_Object *conf_obj = NULL; + JSON_Array *conf_array = NULL; + JSON_Object *conf_obj_array = NULL; + const char *str; /* pointer to sub-strings in the JSON data */ + + /* Initialize structure */ + memset(&debugconf, 0, sizeof debugconf); + + /* 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); + json_value_free(root_val); + return -1; + } else { + MSG("INFO: %s does contain a JSON object named %s, parsing debug parameters\n", conf_file, conf_obj_name); + } + + /* Get reference payload configuration */ + conf_array = json_object_get_array (conf_obj, "ref_payload"); + if (conf_array != NULL) { + debugconf.nb_ref_payload = json_array_get_count(conf_array); + MSG("INFO: got %u debug reference payload\n", debugconf.nb_ref_payload); + + for (i = 0; i < (int)debugconf.nb_ref_payload; i++) { + conf_obj_array = json_array_get_object(conf_array, i); + /* id */ + str = json_object_get_string(conf_obj_array, "id"); + if (str != NULL) { + sscanf(str, "0x%08X", &(debugconf.ref_payload[i].id)); + MSG("INFO: reference payload ID %d is 0x%08X\n", i, debugconf.ref_payload[i].id); + } + + /* global count */ + nb_pkt_received_ref[i] = 0; + } + } + + /* Get log file configuration */ + str = json_object_get_string(conf_obj, "log_file"); + if (str != NULL) { + strncpy(debugconf.log_file_name, str, strlen(str)); + MSG("INFO: setting debug log file name to %s\n", debugconf.log_file_name); + } + + /* Commit configuration */ + if (lgw_debug_setconf(&debugconf) != LGW_HAL_SUCCESS) { + MSG("ERROR: Failed to configure debug\n"); + json_value_free(root_val); + return -1; + } + + /* 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) { + buff_index += j; + } else { + MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + break; + default: + /* Do nothing */ + break; + } + /* end of JSON structure */ + memcpy((void *)(buff_ack + buff_index), (void *)"}}", 2); + buff_index += 2; + } + + buff_ack[buff_index] = 0; /* add string terminator, for safety */ + + /* send datagram to server */ + return send(sock_down, (void *)buff_ack, buff_index, 0); +} + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char ** argv) +{ + struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ + int i; /* loop variable and temporary variable for return value */ + int x; + int l, m; + + /* configuration file related */ + const char defaut_conf_fname[] = JSON_CONF_DEFAULT; + const char * conf_fname = defaut_conf_fname; /* pointer to a string we won't touch */ + + /* threads */ + pthread_t thrid_up; + pthread_t thrid_down; + pthread_t thrid_gps; + pthread_t thrid_valid; + pthread_t thrid_jit; + + /* network socket creation */ + struct addrinfo hints; + struct addrinfo *result; /* store result of getaddrinfo */ + struct addrinfo *q; /* pointer to move into *result data */ + 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}; + + /* SX1302 data variables */ + uint32_t trig_tstamp; + uint32_t inst_tstamp; + uint64_t eui; + + /* 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; + + /* Parse command line options */ + while( (i = getopt( argc, argv, "hc:" )) != -1 ) + { + switch( i ) + { + case 'h': + usage( ); + return EXIT_SUCCESS; + break; + + case 'c': + conf_fname = optarg; + break; + + default: + printf( "ERROR: argument parsing options, use -h option for help\n" ); + usage( ); + return EXIT_FAILURE; + } + } + + /* display version informations */ + MSG("*** Packet Forwarder ***\nVersion: " VERSION_STRING "\n"); + MSG("*** SX1302 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(conf_fname, R_OK) == 0) { /* if there is a global conf, parse it */ + MSG("INFO: found configuration file %s, parsing it\n", conf_fname); + x = parse_SX130x_configuration(conf_fname); + if (x != 0) { + exit(EXIT_FAILURE); + } + x = parse_gateway_configuration(conf_fname); + if (x != 0) { + exit(EXIT_FAILURE); + } + x = parse_debug_configuration(conf_fname); + if (x != 0) { + MSG("INFO: no debug configuration\n"); + } + } else { + MSG("ERROR: [main] failed to find any configuration file named %s\n", conf_fname); + exit(EXIT_FAILURE); + } + + /* Start GPS a.s.a.p., to allow it to lock */ + if (gps_tty_path[0] != '\0') { /* do not try to open GPS device if no path set */ + i = lgw_gps_enable(gps_tty_path, "ubx7", 0, &gps_tty_fd); /* HAL only supports u-blox 7 for now */ + if (i != LGW_GPS_SUCCESS) { + printf("WARNING: [main] impossible to open %s for GPS sync (check permissions)\n", gps_tty_path); + gps_enabled = false; + gps_ref_valid = false; + } else { + printf("INFO: [main] TTY port %s open for GPS synchronization\n", gps_tty_path); + 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); + + /* Board reset */ + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + + for (l = 0; l < LGW_IF_CHAIN_NB; l++) { + for (m = 0; m < 8; m++) { + nb_pkt_log[l][m] = 0; + } + } + + /* starting the concentrator */ + i = lgw_start(); + if (i == LGW_HAL_SUCCESS) { + MSG("INFO: [main] concentrator started, packet can now be received\n"); + } else { + MSG("ERROR: [main] failed to start the concentrator\n"); + exit(EXIT_FAILURE); + } + + /* get the concentrator EUI */ + i = lgw_get_eui(&eui); + if (i != LGW_HAL_SUCCESS) { + printf("ERROR: failed to get concentrator EUI\n"); + } else { + printf("INFO: concentrator EUI: 0x%016llX\n", eui); + } + + /* 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); + } + + /* spawn thread to manage GPS */ + if (gps_enabled == true) { + i = pthread_create( &thrid_gps, NULL, (void * (*)(void *))thread_gps, NULL); + if (i != 0) { + MSG("ERROR: [main] impossible to create GPS thread\n"); + exit(EXIT_FAILURE); + } + i = pthread_create( &thrid_valid, NULL, (void * (*)(void *))thread_valid, NULL); + if (i != 0) { + MSG("ERROR: [main] impossible to create validation thread\n"); + exit(EXIT_FAILURE); + } + } + + /* configure signal handling */ + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigact.sa_handler = sig_handler; + sigaction(SIGQUIT, &sigact, NULL); /* Ctrl-\ */ + sigaction(SIGINT, &sigact, NULL); /* Ctrl-C */ + sigaction(SIGTERM, &sigact, NULL); /* default "kill" command */ + + /* main loop task : statistics collection */ + while (!exit_sig && !quit_sig) { + /* wait for next reporting interval */ + wait_ms(1000 * stat_interval); + + /* get timestamp for statistics */ + t = time(NULL); + strftime(stat_timestamp, sizeof stat_timestamp, "%F %T %Z", gmtime(&t)); + + /* access upstream statistics, copy and reset them */ + pthread_mutex_lock(&mx_meas_up); + cp_nb_rx_rcv = meas_nb_rx_rcv; + cp_nb_rx_ok = meas_nb_rx_ok; + cp_nb_rx_bad = meas_nb_rx_bad; + cp_nb_rx_nocrc = meas_nb_rx_nocrc; + cp_up_pkt_fwd = meas_up_pkt_fwd; + cp_up_network_byte = meas_up_network_byte; + cp_up_payload_byte = meas_up_payload_byte; + cp_up_dgram_sent = meas_up_dgram_sent; + cp_up_ack_rcv = meas_up_ack_rcv; + meas_nb_rx_rcv = 0; + meas_nb_rx_ok = 0; + meas_nb_rx_bad = 0; + meas_nb_rx_nocrc = 0; + meas_up_pkt_fwd = 0; + meas_up_network_byte = 0; + meas_up_payload_byte = 0; + meas_up_dgram_sent = 0; + meas_up_ack_rcv = 0; + pthread_mutex_unlock(&mx_meas_up); + if (cp_nb_rx_rcv > 0) { + rx_ok_ratio = (float)cp_nb_rx_ok / (float)cp_nb_rx_rcv; + rx_bad_ratio = (float)cp_nb_rx_bad / (float)cp_nb_rx_rcv; + rx_nocrc_ratio = (float)cp_nb_rx_nocrc / (float)cp_nb_rx_rcv; + } else { + rx_ok_ratio = 0.0; + rx_bad_ratio = 0.0; + rx_nocrc_ratio = 0.0; + } + if (cp_up_dgram_sent > 0) { + up_ack_ratio = (float)cp_up_ack_rcv / (float)cp_up_dgram_sent; + } else { + up_ack_ratio = 0.0; + } + + /* access downstream statistics, copy and reset them */ + pthread_mutex_lock(&mx_meas_dw); + cp_dw_pull_sent = meas_dw_pull_sent; + cp_dw_ack_rcv = meas_dw_ack_rcv; + cp_dw_dgram_rcv = meas_dw_dgram_rcv; + cp_dw_network_byte = meas_dw_network_byte; + cp_dw_payload_byte = meas_dw_payload_byte; + cp_nb_tx_ok = meas_nb_tx_ok; + cp_nb_tx_fail = meas_nb_tx_fail; + cp_nb_tx_requested += meas_nb_tx_requested; + cp_nb_tx_rejected_collision_packet += meas_nb_tx_rejected_collision_packet; + cp_nb_tx_rejected_collision_beacon += meas_nb_tx_rejected_collision_beacon; + cp_nb_tx_rejected_too_late += meas_nb_tx_rejected_too_late; + cp_nb_tx_rejected_too_early += meas_nb_tx_rejected_too_early; + cp_nb_beacon_queued += meas_nb_beacon_queued; + cp_nb_beacon_sent += meas_nb_beacon_sent; + cp_nb_beacon_rejected += meas_nb_beacon_rejected; + meas_dw_pull_sent = 0; + meas_dw_ack_rcv = 0; + meas_dw_dgram_rcv = 0; + meas_dw_network_byte = 0; + meas_dw_payload_byte = 0; + meas_nb_tx_ok = 0; + meas_nb_tx_fail = 0; + meas_nb_tx_requested = 0; + meas_nb_tx_rejected_collision_packet = 0; + meas_nb_tx_rejected_collision_beacon = 0; + meas_nb_tx_rejected_too_late = 0; + meas_nb_tx_rejected_too_early = 0; + meas_nb_beacon_queued = 0; + meas_nb_beacon_sent = 0; + meas_nb_beacon_rejected = 0; + pthread_mutex_unlock(&mx_meas_dw); + if (cp_dw_pull_sent > 0) { + dw_ack_ratio = (float)cp_dw_ack_rcv / (float)cp_dw_pull_sent; + } else { + dw_ack_ratio = 0.0; + } + + /* access GPS statistics, copy them */ + if (gps_enabled == true) { + pthread_mutex_lock(&mx_meas_gps); + coord_ok = gps_coord_valid; + cp_gps_coord = meas_gps_coord; + pthread_mutex_unlock(&mx_meas_gps); + } + + /* overwrite with reference coordinates if function is enabled */ + if (gps_fake_enable == true) { + cp_gps_coord = reference_coord; + } + + /* display a report */ + printf("\n##### %s #####\n", stat_timestamp); + printf("### [UPSTREAM] ###\n"); + printf("# RF packets received by concentrator: %u\n", cp_nb_rx_rcv); + printf("# CRC_OK: %.2f%%, CRC_FAIL: %.2f%%, NO_CRC: %.2f%%\n", 100.0 * rx_ok_ratio, 100.0 * rx_bad_ratio, 100.0 * rx_nocrc_ratio); + printf("# RF packets forwarded: %u (%u bytes)\n", cp_up_pkt_fwd, cp_up_payload_byte); + printf("# PUSH_DATA datagrams sent: %u (%u bytes)\n", cp_up_dgram_sent, cp_up_network_byte); + printf("# PUSH_DATA acknowledged: %.2f%%\n", 100.0 * up_ack_ratio); + printf("### [DOWNSTREAM] ###\n"); + printf("# PULL_DATA sent: %u (%.2f%% acknowledged)\n", cp_dw_pull_sent, 100.0 * dw_ack_ratio); + printf("# PULL_RESP(onse) datagrams received: %u (%u bytes)\n", cp_dw_dgram_rcv, cp_dw_network_byte); + printf("# RF packets sent to concentrator: %u (%u bytes)\n", (cp_nb_tx_ok+cp_nb_tx_fail), cp_dw_payload_byte); + printf("# TX errors: %u\n", cp_nb_tx_fail); + if (cp_nb_tx_requested != 0 ) { + printf("# TX rejected (collision packet): %.2f%% (req:%u, rej:%u)\n", 100.0 * cp_nb_tx_rejected_collision_packet / cp_nb_tx_requested, cp_nb_tx_requested, cp_nb_tx_rejected_collision_packet); + printf("# TX rejected (collision beacon): %.2f%% (req:%u, rej:%u)\n", 100.0 * cp_nb_tx_rejected_collision_beacon / cp_nb_tx_requested, cp_nb_tx_requested, cp_nb_tx_rejected_collision_beacon); + printf("# TX rejected (too late): %.2f%% (req:%u, rej:%u)\n", 100.0 * cp_nb_tx_rejected_too_late / cp_nb_tx_requested, cp_nb_tx_requested, cp_nb_tx_rejected_too_late); + printf("# TX rejected (too early): %.2f%% (req:%u, rej:%u)\n", 100.0 * cp_nb_tx_rejected_too_early / cp_nb_tx_requested, cp_nb_tx_requested, cp_nb_tx_rejected_too_early); + } + printf("### SX1302 Status ###\n"); + pthread_mutex_lock(&mx_concent); + i = lgw_get_instcnt(&inst_tstamp); + i |= lgw_get_trigcnt(&trig_tstamp); + pthread_mutex_unlock(&mx_concent); + if (i != LGW_HAL_SUCCESS) { + printf("# SX1302 counter unknown\n"); + } else { + printf("# SX1302 counter (INST): %u\n", inst_tstamp); + printf("# SX1302 counter (PPS): %u\n", trig_tstamp); + } + 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 */ + jit_print_queue (&jit_queue[0], false, DEBUG_LOG); + printf("#--------\n"); + jit_print_queue (&jit_queue[1], 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 */ + if (gps_enabled == true) { + pthread_cancel(thrid_gps); /* don't wait for GPS thread */ + pthread_cancel(thrid_valid); /* don't wait for validation thread */ + + i = lgw_gps_disable(gps_tty_fd); + if (i == LGW_HAL_SUCCESS) { + MSG("INFO: GPS closed successfully\n"); + } else { + MSG("WARNING: failed to close GPS successfully\n"); + } + } + + /* if an exit signal was received, try to quit properly */ + if (exit_sig) { + /* shut down network sockets */ + shutdown(sock_up, SHUT_RDWR); + shutdown(sock_down, SHUT_RDWR); + /* stop the hardware */ + i = lgw_stop(); + if (i == LGW_HAL_SUCCESS) { + MSG("INFO: concentrator stopped successfully\n"); + } else { + MSG("WARNING: failed to stop concentrator successfully\n"); + } + } + + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + + MSG("INFO: Exiting packet forwarder program\n"); + exit(EXIT_SUCCESS); +} + +/* -------------------------------------------------------------------------- */ +/* --- THREAD 1: RECEIVING PACKETS AND FORWARDING THEM ---------------------- */ + +void thread_up(void) { + int i, j, k; /* loop variables */ + unsigned pkt_in_dgram; /* nb on Lora packet in the current datagram */ + char stat_timestamp[24]; + time_t t; + + /* allocate memory for packet fetching and processing */ + struct lgw_pkt_rx_s rxpkt[NB_PKT_MAX]; /* array containing inbound packets + metadata */ + struct lgw_pkt_rx_s *p; /* pointer on a RX packet */ + int nb_pkt; + + /* local copy of GPS time reference */ + bool ref_ok = false; /* determine if GPS time reference must be used or not */ + struct tref local_ref; /* time reference used for UTC <-> timestamp conversion */ + + /* data buffers */ + uint8_t buff_up[TX_BUFF_SIZE]; /* buffer to compose the upstream packet */ + int buff_index; + uint8_t buff_ack[32]; /* buffer to receive acknowledges */ + + /* protocol variables */ + uint8_t token_h; /* random token for acknowledgement matching */ + uint8_t token_l; /* random token for acknowledgement matching */ + + /* ping measurement variables */ + struct timespec send_time; + struct timespec recv_time; + + /* GPS synchronization variables */ + struct timespec pkt_utc_time; + struct tm * x; /* broken-up UTC time */ + struct timespec pkt_gps_time; + uint64_t pkt_gps_time_ms; + + /* report management variable */ + bool send_report = false; + + /* mote info variables */ + uint32_t mote_addr = 0; + uint16_t mote_fcnt = 0; + + /* set upstream socket RX timeout */ + i = setsockopt(sock_up, SOL_SOCKET, SO_RCVTIMEO, (void *)&push_timeout_half, sizeof push_timeout_half); + if (i != 0) { + MSG("ERROR: [up] setsockopt returned %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + /* pre-fill the data buffer with fixed fields */ + buff_up[0] = PROTOCOL_VERSION; + buff_up[3] = PKT_PUSH_DATA; + *(uint32_t *)(buff_up + 4) = net_mac_h; + *(uint32_t *)(buff_up + 8) = net_mac_l; + + while (!exit_sig && !quit_sig) { + + /* fetch packets */ + pthread_mutex_lock(&mx_concent); + nb_pkt = lgw_receive(NB_PKT_MAX, rxpkt); + pthread_mutex_unlock(&mx_concent); + if (nb_pkt == LGW_HAL_ERROR) { + MSG("ERROR: [up] failed packet fetch, exiting\n"); + exit(EXIT_FAILURE); + } + + /* check if there are status report to send */ + send_report = report_ready; /* copy the variable so it doesn't change mid-function */ + /* no mutex, we're only reading */ + + /* wait a short time if no packets, nor status report */ + if ((nb_pkt == 0) && (send_report == false)) { + wait_ms(FETCH_SLEEP_MS); + continue; + } + + /* get a copy of GPS time reference (avoid 1 mutex per packet) */ + if ((nb_pkt > 0) && (gps_enabled == true)) { + pthread_mutex_lock(&mx_timeref); + ref_ok = gps_ref_valid; + local_ref = time_reference_gps; + pthread_mutex_unlock(&mx_timeref); + } else { + ref_ok = false; + } + + /* get timestamp for statistics */ + t = time(NULL); + strftime(stat_timestamp, sizeof stat_timestamp, "%F %T %Z", gmtime(&t)); + MSG_DEBUG(DEBUG_PKT_FWD, "\nCurrent time: %s \n", stat_timestamp); + + /* start composing datagram with the header */ + token_h = (uint8_t)rand(); /* random token */ + token_l = (uint8_t)rand(); /* random token */ + buff_up[1] = token_h; + buff_up[2] = token_l; + buff_index = 12; /* 12-byte header */ + + /* start of JSON structure */ + memcpy((void *)(buff_up + buff_index), (void *)"{\"rxpk\":[", 9); + buff_index += 9; + + /* serialize Lora packets metadata and payload */ + pkt_in_dgram = 0; + for (i = 0; i < nb_pkt; ++i) { + p = &rxpkt[i]; + + /* Get mote information from current packet (addr, fcnt) */ + /* FHDR - DevAddr */ + if (p->size >= 8) { + 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; + } else { + mote_addr = 0; + mote_fcnt = 0; + } + + /* basic packet filtering */ + pthread_mutex_lock(&mx_meas_up); + meas_nb_rx_rcv += 1; + switch(p->status) { + case STAT_CRC_OK: + meas_nb_rx_ok += 1; + if (!fwd_valid_pkt) { + pthread_mutex_unlock(&mx_meas_up); + continue; /* skip that packet */ + } + break; + case STAT_CRC_BAD: + meas_nb_rx_bad += 1; + if (!fwd_error_pkt) { + pthread_mutex_unlock(&mx_meas_up); + continue; /* skip that packet */ + } + break; + case STAT_NO_CRC: + meas_nb_rx_nocrc += 1; + if (!fwd_nocrc_pkt) { + pthread_mutex_unlock(&mx_meas_up); + continue; /* skip that packet */ + } + break; + default: + MSG("WARNING: [up] received packet with unknown status %u (size %u, modulation %u, BW %u, DR %u, RSSI %.1f)\n", p->status, p->size, p->modulation, p->bandwidth, p->datarate, p->rssic); + pthread_mutex_unlock(&mx_meas_up); + continue; /* skip that packet */ + // exit(EXIT_FAILURE); + } + meas_up_pkt_fwd += 1; + meas_up_payload_byte += p->size; + pthread_mutex_unlock(&mx_meas_up); + printf( "\nINFO: Received pkt from mote: %08X (fcnt=%u)\n", mote_addr, mote_fcnt ); + + /* 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; + } + + /* JSON rxpk frame format version, 8 useful chars */ + j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, "\"jver\":%d", PROTOCOL_JSON_RXPK_FRAME_FORMAT ); + if (j > 0) { + buff_index += j; + } else { + MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + + /* 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,\"mid\":%2u", p->if_chain, p->rf_chain, ((double)p->freq_hz / 1e6), p->modem_id); + if (j > 0) { + buff_index += j; + } else { + MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + + /* Packet status, 9-10 useful chars */ + switch (p->status) { + case STAT_CRC_OK: + memcpy((void *)(buff_up + buff_index), (void *)",\"stat\":1", 9); + buff_index += 9; + break; + case STAT_CRC_BAD: + memcpy((void *)(buff_up + buff_index), (void *)",\"stat\":-1", 10); + buff_index += 10; + break; + case STAT_NO_CRC: + memcpy((void *)(buff_up + buff_index), (void *)",\"stat\":0", 9); + buff_index += 9; + break; + default: + MSG("ERROR: [up] received packet with unknown status 0x%02X\n", p->status); + memcpy((void *)(buff_up + buff_index), (void *)",\"stat\":?", 9); + buff_index += 9; + exit(EXIT_FAILURE); + } + + /* Packet modulation, 13-14 useful chars */ + if (p->modulation == MOD_LORA) { + memcpy((void *)(buff_up + buff_index), (void *)",\"modu\":\"LORA\"", 14); + buff_index += 14; + + /* Lora datarate & bandwidth, 16-19 useful chars */ + switch (p->datarate) { + case DR_LORA_SF5: + memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF5", 12); + buff_index += 12; + break; + case DR_LORA_SF6: + memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF6", 12); + buff_index += 12; + break; + case DR_LORA_SF7: + memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF7", 12); + buff_index += 12; + break; + case DR_LORA_SF8: + memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF8", 12); + buff_index += 12; + break; + case DR_LORA_SF9: + memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF9", 12); + buff_index += 12; + break; + case DR_LORA_SF10: + memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF10", 13); + buff_index += 13; + break; + case DR_LORA_SF11: + memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF11", 13); + buff_index += 13; + break; + case DR_LORA_SF12: + memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF12", 13); + buff_index += 13; + break; + default: + MSG("ERROR: [up] lora packet with unknown datarate 0x%02X\n", p->datarate); + memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF?", 12); + buff_index += 12; + exit(EXIT_FAILURE); + } + switch (p->bandwidth) { + case BW_125KHZ: + memcpy((void *)(buff_up + buff_index), (void *)"BW125\"", 6); + buff_index += 6; + break; + case BW_250KHZ: + memcpy((void *)(buff_up + buff_index), (void *)"BW250\"", 6); + buff_index += 6; + break; + case BW_500KHZ: + memcpy((void *)(buff_up + buff_index), (void *)"BW500\"", 6); + buff_index += 6; + break; + default: + MSG("ERROR: [up] lora packet with unknown bandwidth 0x%02X\n", p->bandwidth); + memcpy((void *)(buff_up + buff_index), (void *)"BW?\"", 4); + buff_index += 4; + exit(EXIT_FAILURE); + } + + /* Packet ECC coding rate, 11-13 useful chars */ + switch (p->coderate) { + case CR_LORA_4_5: + memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"4/5\"", 13); + buff_index += 13; + break; + case CR_LORA_4_6: + memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"4/6\"", 13); + buff_index += 13; + break; + case CR_LORA_4_7: + memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"4/7\"", 13); + buff_index += 13; + break; + case CR_LORA_4_8: + memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"4/8\"", 13); + buff_index += 13; + break; + case 0: /* treat the CR0 case (mostly false sync) */ + memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"OFF\"", 13); + buff_index += 13; + break; + default: + MSG("ERROR: [up] lora packet with unknown coderate 0x%02X\n", p->coderate); + memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"?\"", 11); + buff_index += 11; + exit(EXIT_FAILURE); + } + + /* Signal RSSI, payload size */ + j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"rssis\":%.0f", roundf(p->rssis)); + if (j > 0) { + buff_index += j; + } else { + MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + + /* Lora SNR */ + j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"lsnr\":%.1f", p->snr); + if (j > 0) { + buff_index += j; + } else { + MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + + /* Lora frequency offset */ + j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"foff\":%d", p->freq_offset); + if (j > 0) { + buff_index += j; + } else { + MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + } else if (p->modulation == MOD_FSK) { + memcpy((void *)(buff_up + buff_index), (void *)",\"modu\":\"FSK\"", 13); + buff_index += 13; + + /* FSK datarate, 11-14 useful chars */ + j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"datr\":%u", p->datarate); + if (j > 0) { + buff_index += j; + } else { + MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + } else { + MSG("ERROR: [up] received packet with unknown modulation 0x%02X\n", p->modulation); + exit(EXIT_FAILURE); + } + + /* Channel RSSI, payload size, 18-23 useful chars */ + j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"rssi\":%.0f,\"size\":%u", roundf(p->rssic), p->size); + if (j > 0) { + buff_index += j; + } else { + MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + + /* Packet base64-encoded payload, 14-350 useful chars */ + memcpy((void *)(buff_up + buff_index), (void *)",\"data\":\"", 9); + buff_index += 9; + j = bin_to_b64(p->payload, p->size, (char *)(buff_up + buff_index), 341); /* 255 bytes = 340 chars in b64 + null char */ + if (j>=0) { + buff_index += j; + } else { + MSG("ERROR: [up] bin_to_b64 failed line %u\n", (__LINE__ - 5)); + exit(EXIT_FAILURE); + } + buff_up[buff_index] = '"'; + ++buff_index; + + /* End of packet serialization */ + buff_up[buff_index] = '}'; + ++buff_index; + ++pkt_in_dgram; + + if (p->modulation == MOD_LORA) { + /* Log nb of packets per channel, per SF */ + nb_pkt_log[p->if_chain][p->datarate - 5] += 1; + nb_pkt_received_lora += 1; + + /* Log nb of packets for ref_payload (DEBUG) */ + for (k = 0; k < debugconf.nb_ref_payload; k++) { + if ((p->payload[0] == (uint8_t)(debugconf.ref_payload[k].id >> 24)) && + (p->payload[1] == (uint8_t)(debugconf.ref_payload[k].id >> 16)) && + (p->payload[2] == (uint8_t)(debugconf.ref_payload[k].id >> 8)) && + (p->payload[3] == (uint8_t)(debugconf.ref_payload[k].id >> 0))) { + nb_pkt_received_ref[k] += 1; + } + } + } else if (p->modulation == MOD_FSK) { + nb_pkt_log[p->if_chain][0] += 1; + nb_pkt_received_fsk += 1; + } + } + + + /* DEBUG: print the number of packets received per channel and per SF */ + { + int l, m; + MSG_PRINTF(DEBUG_PKT_FWD, "\n"); + for (l = 0; l < (LGW_IF_CHAIN_NB - 1); l++) { + MSG_PRINTF(DEBUG_PKT_FWD, "CH%d: ", l); + for (m = 0; m < 8; m++) { + MSG_PRINTF(DEBUG_PKT_FWD, "\t%d", nb_pkt_log[l][m]); + } + MSG_PRINTF(DEBUG_PKT_FWD, "\n"); + } + MSG_PRINTF(DEBUG_PKT_FWD, "FSK: \t%d", nb_pkt_log[9][0]); + MSG_PRINTF(DEBUG_PKT_FWD, "\n"); + MSG_PRINTF(DEBUG_PKT_FWD, "Total number of LoRa packet received: %u\n", nb_pkt_received_lora); + MSG_PRINTF(DEBUG_PKT_FWD, "Total number of FSK packet received: %u\n", nb_pkt_received_fsk); + for (l = 0; l < debugconf.nb_ref_payload; l++) { + MSG_PRINTF(DEBUG_PKT_FWD, "Total number of LoRa packet received from 0x%08X: %u\n", debugconf.ref_payload[l].id, nb_pkt_received_ref[l]); + } + } + + /* restart fetch sequence without sending empty JSON if all packets have been filtered out */ + if (pkt_in_dgram == 0) { + if (send_report == true) { + /* need to clean up the beginning of the payload */ + buff_index -= 8; /* removes "rxpk":[ */ + } else { + /* all packet have been filtered out and no report, restart loop */ + continue; + } + } else { + /* end of packet array */ + buff_up[buff_index] = ']'; + ++buff_index; + /* add separator if needed */ + if (send_report == true) { + buff_up[buff_index] = ','; + ++buff_index; + } + } + + /* add status report if a new one is available */ + if (send_report == true) { + pthread_mutex_lock(&mx_stat_rep); + report_ready = false; + j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, "%s", status_report); + pthread_mutex_unlock(&mx_stat_rep); + if (j > 0) { + buff_index += j; + } else { + MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 5)); + exit(EXIT_FAILURE); + } + } + + /* end of JSON datagram payload */ + buff_up[buff_index] = '}'; + ++buff_index; + buff_up[buff_index] = 0; /* add string terminator, for safety */ + + printf("\nJSON up: %s\n", (char *)(buff_up + 12)); /* DEBUG: display JSON payload */ + + /* send datagram to server */ + send(sock_up, (void *)buff_up, buff_index, 0); + clock_gettime(CLOCK_MONOTONIC, &send_time); + pthread_mutex_lock(&mx_meas_up); + meas_up_dgram_sent += 1; + meas_up_network_byte += buff_index; + + /* wait for acknowledge (in 2 times, to catch extra packets) */ + for (i=0; i<2; ++i) { + j = recv(sock_up, (void *)buff_ack, sizeof buff_ack, 0); + clock_gettime(CLOCK_MONOTONIC, &recv_time); + if (j == -1) { + if (errno == EAGAIN) { /* timeout */ + continue; + } else { /* server connection error */ + break; + } + } else if ((j < 4) || (buff_ack[0] != PROTOCOL_VERSION) || (buff_ack[3] != PKT_PUSH_ACK)) { + //MSG("WARNING: [up] ignored invalid non-ACL packet\n"); + continue; + } else if ((buff_ack[1] != token_h) || (buff_ack[2] != token_l)) { + //MSG("WARNING: [up] ignored out-of sync ACK packet\n"); + continue; + } else { + MSG("INFO: [up] PUSH_ACK received in %i ms\n", (int)(1000 * difftimespec(recv_time, send_time))); + meas_up_ack_rcv += 1; + break; + } + } + pthread_mutex_unlock(&mx_meas_up); + } + MSG("\nINFO: End of upstream thread\n"); +} + +/* -------------------------------------------------------------------------- */ +/* --- THREAD 2: POLLING SERVER AND ENQUEUING PACKETS IN JIT QUEUE ---------- */ + +static int get_tx_gain_lut_index(uint8_t rf_chain, int8_t rf_power, uint8_t * lut_index) { + uint8_t pow_index; + int current_best_index = -1; + uint8_t current_best_match = 0xFF; + int diff; + + /* Check input parameters */ + if (lut_index == NULL) { + MSG("ERROR: %s - wrong parameter\n", __FUNCTION__); + return -1; + } + + /* Search requested power in TX gain LUT */ + for (pow_index = 0; pow_index < txlut[rf_chain].size; pow_index++) { + diff = rf_power - txlut[rf_chain].lut[pow_index].rf_power; + if (diff < 0) { + /* The selected power must be lower or equal to requested one */ + continue; + } else { + /* Record the index corresponding to the closest rf_power available in LUT */ + if ((current_best_index == -1) || (diff < current_best_match)) { + current_best_match = diff; + current_best_index = pow_index; + } + } + } + + /* Return corresponding index */ + if (current_best_index > -1) { + *lut_index = (uint8_t)current_best_index; + } else { + *lut_index = 0; + MSG("ERROR: %s - failed to find tx gain lut index\n", __FUNCTION__); + return -1; + } + + return 0; +} + +void thread_down(void) { + int i; /* loop variables */ + + /* configuration and metadata for an outbound packet */ + struct lgw_pkt_tx_s txpkt; + bool sent_immediate = false; /* option to sent the packet immediately */ + + /* local timekeeping variables */ + struct timespec send_time; /* time of the pull request */ + struct timespec recv_time; /* time of return from recv socket call */ + + /* data buffers */ + uint8_t buff_down[1000]; /* buffer to receive downstream packets */ + uint8_t buff_req[12]; /* buffer to compose pull requests */ + int msg_len; + + /* protocol variables */ + uint8_t token_h; /* random token for acknowledgement matching */ + uint8_t token_l; /* random token for acknowledgement matching */ + bool req_ack = false; /* keep track of whether PULL_DATA was acknowledged or not */ + + /* JSON parsing variables */ + JSON_Value *root_val = NULL; + JSON_Object *txpk_obj = NULL; + JSON_Value *val = NULL; /* needed to detect the absence of some fields */ + const char *str; /* pointer to sub-strings in the JSON data */ + short x0, x1; + uint64_t x2; + double x3, x4; + + /* variables to send on GPS timestamp */ + struct tref local_ref; /* time reference used for GPS <-> timestamp conversion */ + struct timespec gps_tx; /* GPS time that needs to be converted to timestamp */ + + /* beacon variables */ + struct lgw_pkt_tx_s beacon_pkt; + uint8_t beacon_chan; + uint8_t beacon_loop; + size_t beacon_RFU1_size = 0; + size_t beacon_RFU2_size = 0; + uint8_t beacon_pyld_idx = 0; + time_t diff_beacon_time; + struct timespec next_beacon_gps_time; /* gps time of next beacon packet */ + struct timespec last_beacon_gps_time; /* gps time of last enqueued beacon packet */ + int retry; + + /* beacon data fields, byte 0 is Least Significant Byte */ + int32_t field_latitude; /* 3 bytes, derived from reference latitude */ + int32_t field_longitude; /* 3 bytes, derived from reference longitude */ + uint16_t field_crc1, field_crc2; + + /* auto-quit variable */ + uint32_t autoquit_cnt = 0; /* count the number of PULL_DATA sent since the latest PULL_ACK */ + + /* Just In Time downlink */ + uint32_t current_concentrator_time; + enum jit_error_e jit_result = JIT_ERROR_OK; + enum jit_pkt_type_e downlink_type; + enum jit_error_e warning_result = JIT_ERROR_OK; + int32_t warning_value = 0; + uint8_t tx_lut_idx = 0; + + /* set downstream socket RX timeout */ + i = setsockopt(sock_down, SOL_SOCKET, SO_RCVTIMEO, (void *)&pull_timeout, sizeof pull_timeout); + if (i != 0) { + MSG("ERROR: [down] setsockopt returned %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + /* 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[0]); + jit_queue_init(&jit_queue[1]); + + while (!exit_sig && !quit_sig) { + + /* auto-quit if the threshold is crossed */ + if ((autoquit_threshold > 0) && (autoquit_cnt >= autoquit_threshold)) { + exit_sig = true; + MSG("INFO: [down] the last %u PULL_DATA were not ACKed, exiting application\n", autoquit_threshold); + break; + } + + /* generate random token for request */ + token_h = (uint8_t)rand(); /* random token */ + token_l = (uint8_t)rand(); /* random token */ + buff_req[1] = token_h; + buff_req[2] = token_l; + + /* send PULL request and record time */ + send(sock_down, (void *)buff_req, sizeof buff_req, 0); + clock_gettime(CLOCK_MONOTONIC, &send_time); + pthread_mutex_lock(&mx_meas_dw); + meas_dw_pull_sent += 1; + pthread_mutex_unlock(&mx_meas_dw); + req_ack = false; + autoquit_cnt++; + + /* listen to packets and process them until a new PULL request must be sent */ + recv_time = send_time; + while ((int)difftimespec(recv_time, send_time) < keepalive_time) { + + /* try to receive a datagram */ + msg_len = recv(sock_down, (void *)buff_down, (sizeof buff_down)-1, 0); + clock_gettime(CLOCK_MONOTONIC, &recv_time); + + /* Pre-allocate beacon slots in JiT queue, to check downlink collisions */ + beacon_loop = JIT_NUM_BEACON_IN_QUEUE - jit_queue[0].num_beacon; + retry = 0; + while (beacon_loop && (beacon_period != 0)) { + pthread_mutex_lock(&mx_timeref); + /* Wait for GPS to be ready before inserting beacons in JiT queue */ + if ((gps_ref_valid == true) && (xtal_correct_ok == true)) { + + /* compute GPS time for next beacon to come */ + /* LoRaWAN: T = k*beacon_period + TBeaconDelay */ + /* with TBeaconDelay = [1.5ms +/- 1µs]*/ + if (last_beacon_gps_time.tv_sec == 0) { + /* if no beacon has been queued, get next slot from current GPS time */ + diff_beacon_time = time_reference_gps.gps.tv_sec % ((time_t)beacon_period); + next_beacon_gps_time.tv_sec = time_reference_gps.gps.tv_sec + + ((time_t)beacon_period - diff_beacon_time); + } else { + /* if there is already a beacon, take it as reference */ + next_beacon_gps_time.tv_sec = last_beacon_gps_time.tv_sec + beacon_period; + } + /* now we can add a beacon_period to the reference to get next beacon GPS time */ + next_beacon_gps_time.tv_sec += (retry * beacon_period); + next_beacon_gps_time.tv_nsec = 0; + +#if DEBUG_BEACON + { + time_t time_unix; + + time_unix = time_reference_gps.gps.tv_sec + UNIX_GPS_EPOCH_OFFSET; + MSG_DEBUG(DEBUG_BEACON, "GPS-now : %s", ctime(&time_unix)); + time_unix = last_beacon_gps_time.tv_sec + UNIX_GPS_EPOCH_OFFSET; + MSG_DEBUG(DEBUG_BEACON, "GPS-last: %s", ctime(&time_unix)); + time_unix = next_beacon_gps_time.tv_sec + UNIX_GPS_EPOCH_OFFSET; + MSG_DEBUG(DEBUG_BEACON, "GPS-next: %s", ctime(&time_unix)); + } +#endif + + /* convert GPS time to concentrator time, and set packet counter for JiT trigger */ + lgw_gps2cnt(time_reference_gps, next_beacon_gps_time, &(beacon_pkt.count_us)); + pthread_mutex_unlock(&mx_timeref); + + /* apply frequency correction to beacon TX frequency */ + if (beacon_freq_nb > 1) { + beacon_chan = (next_beacon_gps_time.tv_sec / beacon_period) % beacon_freq_nb; /* floor rounding */ + } else { + beacon_chan = 0; + } + /* Compute beacon frequency */ + beacon_pkt.freq_hz = beacon_freq_hz + (beacon_chan * beacon_freq_step); + + /* load time in beacon payload */ + beacon_pyld_idx = beacon_RFU1_size; + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & next_beacon_gps_time.tv_sec; + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (next_beacon_gps_time.tv_sec >> 8); + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (next_beacon_gps_time.tv_sec >> 16); + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (next_beacon_gps_time.tv_sec >> 24); + + /* calculate CRC */ + field_crc1 = crc16(beacon_pkt.payload, 4 + beacon_RFU1_size); /* CRC for the network common part */ + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & field_crc1; + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (field_crc1 >> 8); + + /* Insert beacon packet in JiT queue */ + pthread_mutex_lock(&mx_concent); + lgw_get_instcnt(¤t_concentrator_time); + pthread_mutex_unlock(&mx_concent); + jit_result = jit_enqueue(&jit_queue[0], current_concentrator_time, &beacon_pkt, JIT_PKT_TYPE_BEACON); + if (jit_result == JIT_ERROR_OK) { + /* update stats */ + pthread_mutex_lock(&mx_meas_dw); + meas_nb_beacon_queued += 1; + pthread_mutex_unlock(&mx_meas_dw); + + /* One more beacon in the queue */ + beacon_loop--; + retry = 0; + last_beacon_gps_time.tv_sec = next_beacon_gps_time.tv_sec; /* keep this beacon time as reference for next one to be programmed */ + + /* display beacon payload */ + MSG("INFO: Beacon queued (count_us=%u, freq_hz=%u, size=%u):\n", beacon_pkt.count_us, beacon_pkt.freq_hz, beacon_pkt.size); + printf( " => " ); + for (i = 0; i < beacon_pkt.size; ++i) { + MSG("%02X ", beacon_pkt.payload[i]); + } + MSG("\n"); + } else { + MSG_DEBUG(DEBUG_BEACON, "--> beacon queuing failed with %d\n", jit_result); + /* update stats */ + pthread_mutex_lock(&mx_meas_dw); + if (jit_result != JIT_ERROR_COLLISION_BEACON) { + meas_nb_beacon_rejected += 1; + } + pthread_mutex_unlock(&mx_meas_dw); + /* In case previous enqueue failed, we retry one period later until it succeeds */ + /* Note: In case the GPS has been unlocked for a while, there can be lots of retries */ + /* to be done from last beacon time to a new valid one */ + retry++; + MSG_DEBUG(DEBUG_BEACON, "--> beacon queuing retry=%d\n", retry); + } + } else { + pthread_mutex_unlock(&mx_timeref); + break; + } + } + + /* if no network message was received, got back to listening sock_down socket */ + if (msg_len == -1) { + //MSG("WARNING: [down] recv returned %s\n", strerror(errno)); /* too verbose */ + continue; + } + + /* if the datagram does not respect protocol, just ignore it */ + if ((msg_len < 4) || (buff_down[0] != PROTOCOL_VERSION) || ((buff_down[3] != PKT_PULL_RESP) && (buff_down[3] != PKT_PULL_ACK))) { + MSG("WARNING: [down] ignoring invalid packet len=%d, protocol_version=%d, id=%d\n", + msg_len, buff_down[0], buff_down[3]); + continue; + } + + /* if the datagram is an ACK, check token */ + if (buff_down[3] == PKT_PULL_ACK) { + if ((buff_down[1] == token_h) && (buff_down[2] == token_l)) { + if (req_ack) { + MSG("INFO: [down] duplicate ACK received :)\n"); + } else { /* if that packet was not already acknowledged */ + req_ack = true; + autoquit_cnt = 0; + pthread_mutex_lock(&mx_meas_dw); + meas_dw_ack_rcv += 1; + pthread_mutex_unlock(&mx_meas_dw); + MSG("INFO: [down] PULL_ACK received in %i ms\n", (int)(1000 * difftimespec(recv_time, send_time))); + } + } else { /* out-of-sync token */ + MSG("INFO: [down] received out-of-sync ACK\n"); + } + continue; + } + + /* the datagram is a PULL_RESP */ + buff_down[msg_len] = 0; /* add string terminator, just to be safe */ + MSG("INFO: [down] PULL_RESP received - token[%d:%d] :)\n", buff_down[1], buff_down[2]); /* very verbose */ + printf("\nJSON down: %s\n", (char *)(buff_down + 4)); /* DEBUG: display JSON payload */ + + /* initialize TX struct and try to parse JSON */ + memset(&txpkt, 0, sizeof txpkt); + root_val = json_parse_string_with_comments((const char *)(buff_down + 4)); /* JSON offset */ + if (root_val == NULL) { + MSG("WARNING: [down] invalid JSON, TX aborted\n"); + continue; + } + + /* look for JSON sub-object 'txpk' */ + txpk_obj = json_object_get_object(json_value_get_object(root_val), "txpk"); + if (txpk_obj == NULL) { + MSG("WARNING: [down] no \"txpk\" object in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + + /* Parse "immediate" tag, or target timestamp, or UTC time to be converted by GPS (mandatory) */ + i = json_object_get_boolean(txpk_obj,"imme"); /* can be 1 if true, 0 if false, or -1 if not a JSON boolean */ + if (i == 1) { + /* TX procedure: send immediately */ + sent_immediate = true; + downlink_type = JIT_PKT_TYPE_DOWNLINK_CLASS_C; + MSG("INFO: [down] a packet will be sent in \"immediate\" mode\n"); + } else { + sent_immediate = false; + val = json_object_get_value(txpk_obj,"tmst"); + if (val != NULL) { + /* TX procedure: send on timestamp value */ + txpkt.count_us = (uint32_t)json_value_get_number(val); + + /* Concentrator timestamp is given, we consider it is a Class A downlink */ + downlink_type = JIT_PKT_TYPE_DOWNLINK_CLASS_A; + } else { + /* TX procedure: send on GPS time (converted to timestamp value) */ + val = json_object_get_value(txpk_obj, "tmms"); + if (val == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.tmst\" or \"txpk.tmms\" objects in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + if (gps_enabled == true) { + pthread_mutex_lock(&mx_timeref); + if (gps_ref_valid == true) { + local_ref = time_reference_gps; + pthread_mutex_unlock(&mx_timeref); + } else { + pthread_mutex_unlock(&mx_timeref); + MSG("WARNING: [down] no valid GPS time reference yet, impossible to send packet on specific GPS time, TX aborted\n"); + json_value_free(root_val); + + /* send acknoledge datagram to server */ + send_tx_ack(buff_down[1], buff_down[2], JIT_ERROR_GPS_UNLOCKED, 0); + 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, 0); + continue; + } + + /* Get GPS time from JSON */ + x2 = (uint64_t)json_value_get_number(val); + + /* Convert GPS time from milliseconds to timespec */ + x3 = modf((double)x2/1E3, &x4); + gps_tx.tv_sec = (time_t)x4; /* get seconds from integer part */ + gps_tx.tv_nsec = (long)(x3 * 1E9); /* get nanoseconds from fractional part */ + + /* transform GPS time to timestamp */ + i = lgw_gps2cnt(local_ref, gps_tx, &(txpkt.count_us)); + if (i != LGW_GPS_SUCCESS) { + MSG("WARNING: [down] could not convert GPS time to timestamp, TX aborted\n"); + json_value_free(root_val); + continue; + } else { + MSG("INFO: [down] a packet will be sent on timestamp value %u (calculated from GPS time)\n", txpkt.count_us); + } + + /* GPS timestamp is given, we consider it is a Class B downlink */ + downlink_type = JIT_PKT_TYPE_DOWNLINK_CLASS_B; + } + } + + /* Parse "No CRC" flag (optional field) */ + val = json_object_get_value(txpk_obj,"ncrc"); + if (val != NULL) { + txpkt.no_crc = (bool)json_value_get_boolean(val); + } + + /* parse target frequency (mandatory) */ + val = json_object_get_value(txpk_obj,"freq"); + if (val == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.freq\" object in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + txpkt.freq_hz = (uint32_t)((double)(1.0e6) * json_value_get_number(val)); + + /* parse RF chain used for TX (mandatory) */ + val = json_object_get_value(txpk_obj,"rfch"); + if (val == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.rfch\" object in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + txpkt.rf_chain = (uint8_t)json_value_get_number(val); + + /* parse TX power (optional field) */ + val = json_object_get_value(txpk_obj,"powe"); + if (val != NULL) { + txpkt.rf_power = (int8_t)json_value_get_number(val) - antenna_gain; + } + + /* Parse modulation (mandatory) */ + str = json_object_get_string(txpk_obj, "modu"); + if (str == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.modu\" object in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + if (strcmp(str, "LORA") == 0) { + /* Lora modulation */ + txpkt.modulation = MOD_LORA; + + /* Parse Lora spreading-factor and modulation bandwidth (mandatory) */ + str = json_object_get_string(txpk_obj, "datr"); + if (str == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.datr\" object in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + i = sscanf(str, "SF%2hdBW%3hd", &x0, &x1); + if (i != 2) { + MSG("WARNING: [down] format error in \"txpk.datr\", TX aborted\n"); + json_value_free(root_val); + continue; + } + switch (x0) { + case 5: txpkt.datarate = DR_LORA_SF5; break; + case 6: txpkt.datarate = DR_LORA_SF6; break; + case 7: txpkt.datarate = DR_LORA_SF7; break; + case 8: txpkt.datarate = DR_LORA_SF8; break; + case 9: txpkt.datarate = DR_LORA_SF9; break; + case 10: txpkt.datarate = DR_LORA_SF10; break; + case 11: txpkt.datarate = DR_LORA_SF11; break; + case 12: txpkt.datarate = DR_LORA_SF12; break; + default: + MSG("WARNING: [down] format error in \"txpk.datr\", invalid SF, TX aborted\n"); + json_value_free(root_val); + continue; + } + switch (x1) { + case 125: txpkt.bandwidth = BW_125KHZ; break; + case 250: txpkt.bandwidth = BW_250KHZ; break; + case 500: txpkt.bandwidth = BW_500KHZ; break; + default: + MSG("WARNING: [down] format error in \"txpk.datr\", invalid BW, TX aborted\n"); + json_value_free(root_val); + continue; + } + + /* Parse ECC coding rate (optional field) */ + str = json_object_get_string(txpk_obj, "codr"); + if (str == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.codr\" object in json, TX aborted\n"); + json_value_free(root_val); + continue; + } + if (strcmp(str, "4/5") == 0) txpkt.coderate = CR_LORA_4_5; + else if (strcmp(str, "4/6") == 0) txpkt.coderate = CR_LORA_4_6; + else if (strcmp(str, "2/3") == 0) txpkt.coderate = CR_LORA_4_6; + else if (strcmp(str, "4/7") == 0) txpkt.coderate = CR_LORA_4_7; + else if (strcmp(str, "4/8") == 0) txpkt.coderate = CR_LORA_4_8; + else if (strcmp(str, "1/2") == 0) txpkt.coderate = CR_LORA_4_8; + else { + MSG("WARNING: [down] format error in \"txpk.codr\", TX aborted\n"); + json_value_free(root_val); + continue; + } + + /* Parse signal polarity switch (optional field) */ + val = json_object_get_value(txpk_obj,"ipol"); + if (val != NULL) { + txpkt.invert_pol = (bool)json_value_get_boolean(val); + } + + /* parse Lora preamble length (optional field, optimum min value enforced) */ + val = json_object_get_value(txpk_obj,"prea"); + if (val != NULL) { + i = (int)json_value_get_number(val); + if (i >= MIN_LORA_PREAMB) { + txpkt.preamble = (uint16_t)i; + } else { + txpkt.preamble = (uint16_t)MIN_LORA_PREAMB; + } + } else { + txpkt.preamble = (uint16_t)STD_LORA_PREAMB; + } + + } else if (strcmp(str, "FSK") == 0) { + /* FSK modulation */ + txpkt.modulation = MOD_FSK; + + /* parse FSK bitrate (mandatory) */ + val = json_object_get_value(txpk_obj,"datr"); + if (val == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.datr\" object in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + txpkt.datarate = (uint32_t)(json_value_get_number(val)); + + /* parse frequency deviation (mandatory) */ + val = json_object_get_value(txpk_obj,"fdev"); + if (val == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.fdev\" object in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + txpkt.f_dev = (uint8_t)(json_value_get_number(val) / 1000.0); /* JSON value in Hz, txpkt.f_dev in kHz */ + + /* parse FSK preamble length (optional field, optimum min value enforced) */ + val = json_object_get_value(txpk_obj,"prea"); + if (val != NULL) { + i = (int)json_value_get_number(val); + if (i >= MIN_FSK_PREAMB) { + txpkt.preamble = (uint16_t)i; + } else { + txpkt.preamble = (uint16_t)MIN_FSK_PREAMB; + } + } else { + txpkt.preamble = (uint16_t)STD_FSK_PREAMB; + } + + } else { + MSG("WARNING: [down] invalid modulation in \"txpk.modu\", TX aborted\n"); + json_value_free(root_val); + continue; + } + + /* Parse payload length (mandatory) */ + val = json_object_get_value(txpk_obj,"size"); + if (val == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.size\" object in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + txpkt.size = (uint16_t)json_value_get_number(val); + + /* Parse payload data (mandatory) */ + str = json_object_get_string(txpk_obj, "data"); + if (str == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.data\" object in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + i = b64_to_bin(str, strlen(str), txpkt.payload, sizeof txpkt.payload); + if (i != txpkt.size) { + MSG("WARNING: [down] mismatch between .size and .data size once converter to binary\n"); + } + + /* free the JSON parse tree from memory */ + json_value_free(root_val); + + /* select TX mode */ + if (sent_immediate) { + txpkt.tx_mode = IMMEDIATE; + } else { + txpkt.tx_mode = TIMESTAMPED; + } + + /* record measurement data */ + pthread_mutex_lock(&mx_meas_dw); + meas_dw_dgram_rcv += 1; /* count only datagrams with no JSON errors */ + meas_dw_network_byte += msg_len; /* meas_dw_network_byte */ + meas_dw_payload_byte += txpkt.size; + pthread_mutex_unlock(&mx_meas_dw); + + /* reset error/warning results */ + jit_result = warning_result = JIT_ERROR_OK; + warning_value = 0; + + /* check TX frequency before trying to queue packet */ + if ((txpkt.freq_hz < tx_freq_min[txpkt.rf_chain]) || (txpkt.freq_hz > tx_freq_max[txpkt.rf_chain])) { + jit_result = JIT_ERROR_TX_FREQ; + MSG("ERROR: Packet REJECTED, unsupported frequency - %u (min:%u,max:%u)\n", txpkt.freq_hz, tx_freq_min[txpkt.rf_chain], tx_freq_max[txpkt.rf_chain]); + } + + /* check TX power before trying to queue packet, send a warning if not supported */ + if (jit_result == JIT_ERROR_OK) { + i = get_tx_gain_lut_index(txpkt.rf_chain, txpkt.rf_power, &tx_lut_idx); + if ((i < 0) || (txlut[txpkt.rf_chain].lut[tx_lut_idx].rf_power != txpkt.rf_power)) { + /* this RF power is not supported, throw a warning, and use the closest lower power supported */ + warning_result = JIT_ERROR_TX_POWER; + warning_value = (int32_t)txlut[txpkt.rf_chain].lut[tx_lut_idx].rf_power; + printf("WARNING: Requested TX power is not supported (%ddBm), actual power used: %ddBm\n", txpkt.rf_power, warning_value); + txpkt.rf_power = txlut[txpkt.rf_chain].lut[tx_lut_idx].rf_power; + } + } + + /* insert packet to be sent into JIT queue */ + if (jit_result == JIT_ERROR_OK) { + pthread_mutex_lock(&mx_concent); + lgw_get_instcnt(¤t_concentrator_time); + pthread_mutex_unlock(&mx_concent); + jit_result = jit_enqueue(&jit_queue[txpkt.rf_chain], current_concentrator_time, &txpkt, downlink_type); + if (jit_result != JIT_ERROR_OK) { + printf("ERROR: Packet REJECTED (jit error=%d)\n", jit_result); + } else { + /* In case of a warning having been raised before, we notify it */ + jit_result = warning_result; + } + pthread_mutex_lock(&mx_meas_dw); + meas_nb_tx_requested += 1; + pthread_mutex_unlock(&mx_meas_dw); + } + + /* Send acknoledge datagram to server */ + send_tx_ack(buff_down[1], buff_down[2], jit_result, warning_value); + } + } + MSG("\nINFO: End of downstream thread\n"); +} + +void print_tx_status(uint8_t tx_status) { + switch (tx_status) { + case TX_OFF: + MSG("INFO: [jit] lgw_status returned TX_OFF\n"); + break; + case TX_FREE: + MSG("INFO: [jit] lgw_status returned TX_FREE\n"); + break; + case TX_EMITTING: + MSG("INFO: [jit] lgw_status returned TX_EMITTING\n"); + break; + case TX_SCHEDULED: + MSG("INFO: [jit] lgw_status returned TX_SCHEDULED\n"); + break; + default: + MSG("INFO: [jit] lgw_status returned UNKNOWN (%d)\n", tx_status); + break; + } +} + + +/* -------------------------------------------------------------------------- */ +/* --- THREAD 3: CHECKING PACKETS TO BE SENT FROM JIT QUEUE AND SEND THEM --- */ + +void thread_jit(void) { + int result = LGW_HAL_SUCCESS; + struct lgw_pkt_tx_s pkt; + int pkt_index = -1; + uint32_t current_concentrator_time; + enum jit_error_e jit_result; + enum jit_pkt_type_e pkt_type; + uint8_t tx_status; + int i; + + while (!exit_sig && !quit_sig) { + wait_ms(10); + + for (i = 0; i < LGW_RF_CHAIN_NB; i++) { + /* transfer data and metadata to the concentrator, and schedule TX */ + pthread_mutex_lock(&mx_concent); + lgw_get_instcnt(¤t_concentrator_time); + pthread_mutex_unlock(&mx_concent); + jit_result = jit_peek(&jit_queue[i], current_concentrator_time, &pkt_index); + if (jit_result == JIT_ERROR_OK) { + if (pkt_index > -1) { + jit_result = jit_dequeue(&jit_queue[i], pkt_index, &pkt, &pkt_type); + if (jit_result == JIT_ERROR_OK) { + /* update beacon stats */ + if (pkt_type == JIT_PKT_TYPE_BEACON) { + /* Compensate breacon frequency with xtal error */ + pthread_mutex_lock(&mx_xcorr); + pkt.freq_hz = (uint32_t)(xtal_correct * (double)pkt.freq_hz); + MSG_DEBUG(DEBUG_BEACON, "beacon_pkt.freq_hz=%u (xtal_correct=%.15lf)\n", pkt.freq_hz, xtal_correct); + pthread_mutex_unlock(&mx_xcorr); + + /* Update statistics */ + pthread_mutex_lock(&mx_meas_dw); + meas_nb_beacon_sent += 1; + pthread_mutex_unlock(&mx_meas_dw); + MSG("INFO: Beacon dequeued (count_us=%u)\n", pkt.count_us); + } + + /* check if concentrator is free for sending new packet */ + pthread_mutex_lock(&mx_concent); /* may have to wait for a fetch to finish */ + result = lgw_status(pkt.rf_chain, TX_STATUS, &tx_status); + pthread_mutex_unlock(&mx_concent); /* free concentrator ASAP */ + if (result == LGW_HAL_ERROR) { + MSG("WARNING: [jit%d] lgw_status failed\n", i); + } else { + if (tx_status == TX_EMITTING) { + MSG("ERROR: concentrator is currently emitting on rf_chain %d\n", i); + print_tx_status(tx_status); + continue; + } else if (tx_status == TX_SCHEDULED) { + MSG("WARNING: a downlink was already scheduled on rf_chain %d, overwritting it...\n", i); + print_tx_status(tx_status); + } else { + /* Nothing to do */ + } + } + + /* send packet to concentrator */ + pthread_mutex_lock(&mx_concent); /* may have to wait for a fetch to finish */ + result = lgw_send(&pkt); + pthread_mutex_unlock(&mx_concent); /* free concentrator ASAP */ + if (result == LGW_HAL_ERROR) { + pthread_mutex_lock(&mx_meas_dw); + meas_nb_tx_fail += 1; + pthread_mutex_unlock(&mx_meas_dw); + MSG("WARNING: [jit] lgw_send failed on rf_chain %d\n", i); + continue; + } else { + pthread_mutex_lock(&mx_meas_dw); + meas_nb_tx_ok += 1; + pthread_mutex_unlock(&mx_meas_dw); + MSG_DEBUG(DEBUG_PKT_FWD, "lgw_send done on rf_chain %d: count_us=%u\n", i, pkt.count_us); + } + } else { + MSG("ERROR: jit_dequeue failed on rf_chain %d with %d\n", i, jit_result); + } + } + } else if (jit_result == JIT_ERROR_EMPTY) { + /* Do nothing, it can happen */ + } else { + MSG("ERROR: jit_peek failed on rf_chain %d with %d\n", i, jit_result); + } + } + } +} + +/* -------------------------------------------------------------------------- */ +/* --- THREAD 4: PARSE GPS MESSAGE AND KEEP GATEWAY IN SYNC ----------------- */ + +static void gps_process_sync(void) { + struct timespec gps_time; + struct timespec utc; + uint32_t trig_tstamp; /* concentrator timestamp associated with PPM pulse */ + int i = lgw_gps_get(&utc, &gps_time, NULL, NULL); + + /* get GPS time for synchronization */ + if (i != LGW_GPS_SUCCESS) { + MSG("WARNING: [gps] could not get GPS time from GPS\n"); + return; + } + + /* get timestamp captured on PPM pulse */ + pthread_mutex_lock(&mx_concent); + i = lgw_get_trigcnt(&trig_tstamp); + pthread_mutex_unlock(&mx_concent); + if (i != LGW_HAL_SUCCESS) { + MSG("WARNING: [gps] failed to read concentrator timestamp\n"); + return; + } + + /* try to update time reference with the new GPS time & timestamp */ + pthread_mutex_lock(&mx_timeref); + i = lgw_gps_sync(&time_reference_gps, trig_tstamp, utc, gps_time); + pthread_mutex_unlock(&mx_timeref); + if (i != LGW_GPS_SUCCESS) { + MSG("WARNING: [gps] GPS out of sync, keeping previous time reference\n"); + } +} + +static void gps_process_coords(void) { + /* position variable */ + struct coord_s coord; + struct coord_s gpserr; + int i = lgw_gps_get(NULL, NULL, &coord, &gpserr); + + /* update gateway coordinates */ + pthread_mutex_lock(&mx_meas_gps); + if (i == LGW_GPS_SUCCESS) { + gps_coord_valid = true; + meas_gps_coord = coord; + meas_gps_err = gpserr; + // TODO: report other GPS statistics (typ. signal quality & integrity) + } else { + gps_coord_valid = false; + } + pthread_mutex_unlock(&mx_meas_gps); +} + +void thread_gps(void) { + /* serial variables */ + char serial_buff[128]; /* buffer to receive GPS data */ + size_t wr_idx = 0; /* pointer to end of chars in buffer */ + + /* variables for PPM pulse GPS synchronization */ + enum gps_msg latest_msg; /* keep track of latest NMEA message parsed */ + + /* initialize some variables before loop */ + memset(serial_buff, 0, sizeof serial_buff); + + while (!exit_sig && !quit_sig) { + size_t rd_idx = 0; + size_t frame_end_idx = 0; + + /* blocking non-canonical read on serial port */ + ssize_t nb_char = read(gps_tty_fd, serial_buff + wr_idx, LGW_GPS_MIN_MSG_SIZE); + if (nb_char <= 0) { + MSG("WARNING: [gps] read() returned value %d\n", nb_char); + continue; + } + wr_idx += (size_t)nb_char; + + /******************************************* + * Scan buffer for UBX/NMEA sync chars and * + * attempt to decode frame if one is found * + *******************************************/ + while(rd_idx < wr_idx) { + size_t frame_size = 0; + + /* Scan buffer for UBX sync char */ + if(serial_buff[rd_idx] == (char)LGW_GPS_UBX_SYNC_CHAR) { + + /*********************** + * Found UBX sync char * + ***********************/ + latest_msg = lgw_parse_ubx(&serial_buff[rd_idx], (wr_idx - rd_idx), &frame_size); + + if (frame_size > 0) { + if (latest_msg == INCOMPLETE) { + /* UBX header found but frame appears to be missing bytes */ + frame_size = 0; + } else if (latest_msg == INVALID) { + /* message header received but message appears to be corrupted */ + MSG("WARNING: [gps] could not get a valid message from GPS (no time)\n"); + frame_size = 0; + } else if (latest_msg == UBX_NAV_TIMEGPS) { + gps_process_sync(); + } + } + } else if(serial_buff[rd_idx] == LGW_GPS_NMEA_SYNC_CHAR) { + /************************ + * Found NMEA sync char * + ************************/ + /* scan for NMEA end marker (LF = 0x0a) */ + char* nmea_end_ptr = memchr(&serial_buff[rd_idx],(int)0x0a, (wr_idx - rd_idx)); + + if(nmea_end_ptr) { + /* found end marker */ + frame_size = nmea_end_ptr - &serial_buff[rd_idx] + 1; + latest_msg = lgw_parse_nmea(&serial_buff[rd_idx], frame_size); + + if(latest_msg == INVALID || latest_msg == UNKNOWN) { + /* checksum failed */ + frame_size = 0; + } else if (latest_msg == NMEA_RMC) { /* Get location from RMC frames */ + gps_process_coords(); + } + } + } + + if(frame_size > 0) { + /* At this point message is a checksum verified frame + we're processed or ignored. Remove frame from buffer */ + rd_idx += frame_size; + frame_end_idx = rd_idx; + } else { + rd_idx++; + } + } /* ...for(rd_idx = 0... */ + + if(frame_end_idx) { + /* Frames have been processed. Remove bytes to end of last processed frame */ + memcpy(serial_buff, &serial_buff[frame_end_idx], wr_idx - frame_end_idx); + wr_idx -= frame_end_idx; + } /* ...for(rd_idx = 0... */ + + /* Prevent buffer overflow */ + if((sizeof(serial_buff) - wr_idx) < LGW_GPS_MIN_MSG_SIZE) { + memcpy(serial_buff, &serial_buff[LGW_GPS_MIN_MSG_SIZE], wr_idx - LGW_GPS_MIN_MSG_SIZE); + wr_idx -= LGW_GPS_MIN_MSG_SIZE; + } + } + MSG("\nINFO: End of GPS thread\n"); +} + +/* -------------------------------------------------------------------------- */ +/* --- THREAD 5: CHECK TIME REFERENCE AND CALCULATE XTAL CORRECTION --------- */ + +void thread_valid(void) { + + /* GPS reference validation variables */ + long gps_ref_age = 0; + bool ref_valid_local = false; + double xtal_err_cpy; + + /* variables for XTAL correction averaging */ + unsigned init_cpt = 0; + double init_acc = 0.0; + double x; + + /* correction debug */ + // FILE * log_file = NULL; + // time_t now_time; + // char log_name[64]; + + /* initialization */ + // time(&now_time); + // strftime(log_name,sizeof log_name,"xtal_err_%Y%m%dT%H%M%SZ.csv",localtime(&now_time)); + // log_file = fopen(log_name, "w"); + // setbuf(log_file, NULL); + // fprintf(log_file,"\"xtal_correct\",\"XERR_INIT_AVG %u XERR_FILT_COEF %u\"\n", XERR_INIT_AVG, XERR_FILT_COEF); // DEBUG + + /* main loop task */ + while (!exit_sig && !quit_sig) { + wait_ms(1000); + + /* calculate when the time reference was last updated */ + pthread_mutex_lock(&mx_timeref); + gps_ref_age = (long)difftime(time(NULL), time_reference_gps.systime); + if ((gps_ref_age >= 0) && (gps_ref_age <= GPS_REF_MAX_AGE)) { + /* time ref is ok, validate and */ + gps_ref_valid = true; + ref_valid_local = true; + xtal_err_cpy = time_reference_gps.xtal_err; + //printf("XTAL err: %.15lf (1/XTAL_err:%.15lf)\n", xtal_err_cpy, 1/xtal_err_cpy); // DEBUG + } else { + /* time ref is too old, invalidate */ + gps_ref_valid = false; + ref_valid_local = false; + } + pthread_mutex_unlock(&mx_timeref); + + /* manage XTAL correction */ + if (ref_valid_local == false) { + /* couldn't sync, or sync too old -> invalidate XTAL correction */ + pthread_mutex_lock(&mx_xcorr); + xtal_correct_ok = false; + xtal_correct = 1.0; + pthread_mutex_unlock(&mx_xcorr); + init_cpt = 0; + init_acc = 0.0; + } else { + if (init_cpt < XERR_INIT_AVG) { + /* initial accumulation */ + init_acc += xtal_err_cpy; + ++init_cpt; + } else if (init_cpt == XERR_INIT_AVG) { + /* initial average calculation */ + pthread_mutex_lock(&mx_xcorr); + xtal_correct = (double)(XERR_INIT_AVG) / init_acc; + //printf("XERR_INIT_AVG=%d, init_acc=%.15lf\n", XERR_INIT_AVG, init_acc); + xtal_correct_ok = true; + pthread_mutex_unlock(&mx_xcorr); + ++init_cpt; + // fprintf(log_file,"%.18lf,\"average\"\n", xtal_correct); // DEBUG + } else { + /* tracking with low-pass filter */ + x = 1 / xtal_err_cpy; + pthread_mutex_lock(&mx_xcorr); + xtal_correct = xtal_correct - xtal_correct/XERR_FILT_COEF + x/XERR_FILT_COEF; + pthread_mutex_unlock(&mx_xcorr); + // fprintf(log_file,"%.18lf,\"track\"\n", xtal_correct); // DEBUG + } + } + // printf("Time ref: %s, XTAL correct: %s (%.15lf)\n", ref_valid_local?"valid":"invalid", xtal_correct_ok?"valid":"invalid", xtal_correct); // DEBUG + } + MSG("\nINFO: End of validation thread\n"); +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/readme.md b/readme.md index e69de29..2fea9f5 100644 --- a/readme.md +++ b/readme.md @@ -0,0 +1,194 @@ + / _____) _ | | + ( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | + (______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +SX1302 LoRa Gateway project +=========================== + +## 1. Core library: libloragw + +This directory contains the sources of the library to build a gateway based on +a Semtech LoRa SX1302 concentrator chip (a.k.a. concentrator). +Once compiled all the code is contained in the libloragw.a file that will be +statically linked (ie. integrated in the final executable). + +The library also comes with few basic tests programs that are used to test the +different sub-modules of the library. + +Please refer to the readme.md file located in the libloragw directory for +more details. + +## 2. Helper programs + +Those programs are included in the project to provide examples on how to use +the HAL library, and to help the system builder test different parts of it. + +### 2.1. packet_frowarder ### + +The packet forwarder is a program running on the host of a Lora gateway that +forwards RF packets receive by the concentrator to a server through a IP/UDP +link, and emits RF packets that are sent by the server. + + ((( Y ))) + | + | + +- -|- - - - - - - - - - - - -+ xxxxxxxxxxxx +--------+ + |+--+-----------+ +------+| xx x x xxx | | + || | | || xx Internet xx | | + || Concentrator |<----+ Host |<------xx or xx-------->| | + || | SPI | || xx Intranet xx | Server | + |+--------------+ +------+| xxxx x xxxx | | + | ^ ^ | xxxxxxxx | | + | | PPS +-----+ NMEA | | | | + | +------| GPS |-------+ | +--------+ + | +-----+ | + | | + | Gateway | + +- - - - - - - - - - - - - - -+ + +Uplink: radio packets received by the gateway, with metadata added by the +gateway, forwarded to the server. Might also include gateway status. + +Downlink: packets generated by the server, with additional metadata, to be +transmitted by the gateway on the radio channel. Might also include +configuration data for the gateway. + +Please refer to the readme.md file located in the packet_forwarder directory +for more details. + +### 2.2. util_net_downlink ### + +The downlink packet sender is a simple helper program listening on a single +UDP port, responding to PUSH_DATA and PULL_DATA datagrams with proper ACK, and +sending downlink JSON packets to the socket, with given frame parameters, at +regular time interval. +It is a network packet sender. + +It can also be used as a UDP packet logger to store received uplinks in a +local CSV file. + +Please refer to the readme.md file located in the util_net_downlink directory +for more details. + +## 3. Helper scripts + +### 3.1. tools/reset_lgw.sh + +This script is used to perform the basic initialization of the SX1302 through +the GPIOs defined by the CoreCell reference design. +It gets the SX1302 out of reset and set the Power Enable pin. +This script is called by every program provided here which accesses the SX1302. +It MUST be located in the same directory as the executable of the program. + +## 4. Compile, install and run instructions + +All the libraries and test programs can be compiled and installed from the +root directory of this project. + +### 4.1. Clean and compile everything + +`make clean all` + +### 4.2. Install executables and associated files in one directory + +First edit the target.cfg file located in the root directory of the project +in order to configure where the executables have to be installed. + +`TARGET_IP` : sets the IP address of the host of the gateway. In case the +project is compiled on the gateway host itself (Raspberry Pi...), this can +be left set to `localhost`. + +`TARGET_DIR` : sets the directory on the gateway host file system in which +the executables must be copied. Note that the directory MUST exist when +invoking the install command. + +`TARGET_USR` : sets the linux user to be used to perform the SSH/SCP command +for copying the executables. + +In order to avoid entering the user password when installing the files, the +following steps have to be followed. + +Lets say you want to copy between two hosts host_src and host_dest (they can +be the same). host_src is the host where you would run the scp command, +irrespective of the direction of the file copy! + +* On host_src, run this command as the user that runs scp
+`ssh-keygen -t rsa` + +This will prompt for a passphrase. Just press the enter key. It'll then +generate an identification (private key) and a public key. Do not ever share +the private key with anyone! ssh-keygen shows where it saved the public key. +This is by default ~/.ssh/id_rsa.pub +* Transfer the id_rsa.pub file to host_dest
+`ssh-copy-id -i ~/.ssh/id_rsa.pub user@host_dest` + +You should be able to log on host_dest without being asked for a password. + +Now that everything is set, the following command can be invoked:
+`make install` + +In order to also install the packet forwarder JSON configuration files:
+`make install_conf` + +### 4.3. Cross-compile from a PC + +* Add the path to the binaries of the compiler corresponding to the target +platform to the `PATH` environment variable. +* set the `ARCH` environment variable to `arm`. +* set the `CROSS_COMPILE` environment variable to the prefix corresponding to +the compiler for the target platform. + +An example for a Raspberry Pi target: + +* `export PATH=[path]/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin` +* `export ARCH=arm` +* `export CROSS_COMPILE=arm-linux-gnueabihf-` + +Then, from the same console where the previous environment variables have been +set, do: + +`make clean all` + +## 5. Third party libraries + +This project relies on several third-party open source libraries, that can be +found in the `libtools` directory. +* parson: a JSON parser (http://kgabis.github.com/parson/) +* tinymt32: a pseudo-random generator (only used for debug/test) + +## 6. Changelog + +### v1.0.0 ### + +* HAL: Initial official release for SX1302 CoreCell Reference Design. + +### v0.0.1 ### + +* HAL: Initial private release for TAP program + +## 7. Legal notice + +The information presented in this project documentation does not form part of +any quotation or contract, is believed to be accurate and reliable and may be +changed without notice. No liability will be accepted by the publisher for any +consequence of its use. Publication thereof does not convey nor imply any +license under patent or other industrial or intellectual property rights. +Semtech assumes no responsibility or liability whatsoever for any failure or +unexpected operation resulting from misuse, neglect improper installation, +repair or improper handling or unusual physical or electrical stress +including, but not limited to, exposure to parameters beyond the specified +maximum ratings or operation outside the specified range. + +SEMTECH PRODUCTS ARE NOT DESIGNED, INTENDED, AUTHORIZED OR WARRANTED TO BE +SUITABLE FOR USE IN LIFE-SUPPORT APPLICATIONS, DEVICES OR SYSTEMS OR OTHER +CRITICAL APPLICATIONS. INCLUSION OF SEMTECH PRODUCTS IN SUCH APPLICATIONS IS +UNDERSTOOD TO BE UNDERTAKEN SOLELY AT THE CUSTOMER'S OWN RISK. Should a +customer purchase or use Semtech products for any such unauthorized +application, the customer shall indemnify and hold Semtech and its officers, +employees, subsidiaries, affiliates, and distributors harmless against all +claims, costs damages and attorney fees which could arise. + +*EOF* diff --git a/target.cfg b/target.cfg new file mode 100644 index 0000000..c55e132 --- /dev/null +++ b/target.cfg @@ -0,0 +1,10 @@ +# That file will be included in the Makefiles to configure where to install files on the target + +# The IP address of the gateway host on which the files need to be installed +TARGET_IP=localhost + +# The directory on the gateway file system to which the files need to be installed +TARGET_DIR=/home/pi/sx1302_hal/bin + +# The user to be used by ssh/scp to copy the files on the gateway host +TARGET_USR=pi diff --git a/tools/node-red-registers.json b/tools/node-red-registers.json new file mode 100644 index 0000000..bd3e7b7 --- /dev/null +++ b/tools/node-red-registers.json @@ -0,0 +1,930 @@ +[ + { + "id": "bf64d63f.630868", + "type": "tab", + "label": "SX1302 register C header", + "disabled": false, + "info": "" + }, + { + "id": "d60daf5.ed6835", + "type": "file in", + "z": "bf64d63f.630868", + "name": "", + "filename": "/home/mcoracin/SHARE/sx1302_reg/common.json", + "format": "utf8", + "chunk": false, + "sendError": true, + "x": 390, + "y": 160, + "wires": [ + [ + "ab571926.a8a0a8" + ] + ] + }, + { + "id": "ab571926.a8a0a8", + "type": "json", + "z": "bf64d63f.630868", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 660, + "y": 160, + "wires": [ + [ + "2efaa7ec.1e3158" + ] + ] + }, + { + "id": "3e749686.017e32", + "type": "inject", + "z": "bf64d63f.630868", + "name": "common", + "topic": "common", + "payload": "true", + "payloadType": "bool", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 110, + "y": 160, + "wires": [ + [ + "d60daf5.ed6835" + ] + ] + }, + { + "id": "a8c02e96.c069", + "type": "inject", + "z": "bf64d63f.630868", + "name": "Init", + "topic": "", + "payload": "true", + "payloadType": "bool", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 100, + "y": 40, + "wires": [ + [ + "13cca583.8beb3a" + ] + ] + }, + { + "id": "13cca583.8beb3a", + "type": "function", + "z": "bf64d63f.630868", + "name": "Init global variables", + "func": "var reg_c = 'const struct lgw_reg_s loregs[LGW_TOTALREGS+1] = {' + '\\n';\n\nvar reg_desc;\nreg_desc = '\\n/* -------------------------------------------------------------------------- */\\n';\nreg_desc += '/* --- REGISTER DESCRIPTIONS ------------------------------------------------\\n';\n\nflow.set('tx_top_macro', '');\nflow.set('reg_h', '');\nflow.set('reg_c', reg_c);\nflow.set('reg_desc', reg_desc);\nflow.set('reg_count', 0);\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "x": 290, + "y": 40, + "wires": [ + [ + "d040198e.3a7208" + ] + ] + }, + { + "id": "d040198e.3a7208", + "type": "debug", + "z": "bf64d63f.630868", + "name": "", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "x": 500, + "y": 40, + "wires": [] + }, + { + "id": "12128a9a.ea5e35", + "type": "file in", + "z": "bf64d63f.630868", + "name": "", + "filename": "/home/mcoracin/SHARE/sx1302_reg/agc_mcu.json", + "format": "utf8", + "chunk": false, + "sendError": true, + "x": 390, + "y": 220, + "wires": [ + [ + "d6c34db.5471fb" + ] + ] + }, + { + "id": "d6c34db.5471fb", + "type": "json", + "z": "bf64d63f.630868", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 660, + "y": 220, + "wires": [ + [ + "2efaa7ec.1e3158" + ] + ] + }, + { + "id": "c7ba4afe.f6229", + "type": "inject", + "z": "bf64d63f.630868", + "name": "agc_mcu", + "topic": "agc_mcu", + "payload": "true", + "payloadType": "bool", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 110, + "y": 220, + "wires": [ + [ + "12128a9a.ea5e35" + ] + ] + }, + { + "id": "917cf46c.ceaa48", + "type": "inject", + "z": "bf64d63f.630868", + "name": "Create files", + "topic": "", + "payload": "true", + "payloadType": "bool", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 110, + "y": 940, + "wires": [ + [ + "67447ab8.c38edc" + ] + ] + }, + { + "id": "67447ab8.c38edc", + "type": "function", + "z": "bf64d63f.630868", + "name": "Write header file", + "func": "var reg_count = flow.get('reg_count') || 0;\n\nvar reg_h = flow.get('reg_h') || '';\nreg_h += '\\n#define LGW_TOTALREGS ' + reg_count + '\\n';\n\nvar reg_c = flow.get('reg_c') || '';\nreg_c += ' {0,0,0,0,0,0,0,0}\\n'\nreg_c += '};';\n\nvar reg_desc = flow.get('reg_desc') || '';\nreg_desc += \"\\n*/\";\n\nvar tx_top_macro = flow.get('tx_top_macro') || '';\n\n// write all to file\nvar file = reg_h + '\\n' + tx_top_macro + '\\n' + reg_c + '\\n' + reg_desc;\nmsg.payload = file;\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "x": 290, + "y": 940, + "wires": [ + [ + "b1b0d974.6ae61" + ] + ] + }, + { + "id": "b1b0d974.6ae61", + "type": "file", + "z": "bf64d63f.630868", + "name": "", + "filename": "/home/mcoracin/SHARE/sx1302_reg/sx1302_reg.h", + "appendNewline": true, + "createDir": false, + "overwriteFile": "true", + "x": 620, + "y": 940, + "wires": [] + }, + { + "id": "aae4ac70.472438", + "type": "debug", + "z": "bf64d63f.630868", + "name": "", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "x": 1160, + "y": 280, + "wires": [] + }, + { + "id": "1450f6f8.1be259", + "type": "file in", + "z": "bf64d63f.630868", + "name": "", + "filename": "/home/mcoracin/SHARE/sx1302_reg/rif_top_mux.json", + "format": "utf8", + "chunk": false, + "sendError": true, + "x": 420, + "y": 100, + "wires": [ + [ + "5c712585.37aeec" + ] + ] + }, + { + "id": "5c712585.37aeec", + "type": "json", + "z": "bf64d63f.630868", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 700, + "y": 100, + "wires": [ + [ + "8bbc1d63.391468" + ] + ] + }, + { + "id": "a48ebe9c.34b028", + "type": "inject", + "z": "bf64d63f.630868", + "name": "rif_top_mux", + "topic": "", + "payload": "true", + "payloadType": "bool", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 120, + "y": 100, + "wires": [ + [ + "1450f6f8.1be259" + ] + ] + }, + { + "id": "8bbc1d63.391468", + "type": "function", + "z": "bf64d63f.630868", + "name": "JSON Parse Base Addresses", + "func": "var reg_h = flow.get('reg_h') || '';\n\nvar prefix = 'SX1302_REG_';\nvar postfix = '_BASE_ADDR';\nvar line = '';\n\nfor(var item in msg.payload.map)\n{\n var base_addr = msg.payload.map[item].addr;\n line += '#define ' + prefix + item.toUpperCase() + postfix + ' 0x' + base_addr.toString(16) + '\\n'\n}\n//console.log(line);\n\nreg_h += line + '\\n';\n\nflow.set('reg_h', reg_h);\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "x": 920, + "y": 100, + "wires": [ + [ + "9494d0f2.75b628" + ] + ] + }, + { + "id": "9494d0f2.75b628", + "type": "debug", + "z": "bf64d63f.630868", + "name": "", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "x": 1160, + "y": 100, + "wires": [] + }, + { + "id": "2d9b9bf2.6b3bb4", + "type": "file in", + "z": "bf64d63f.630868", + "name": "", + "filename": "/home/mcoracin/SHARE/sx1302_reg/clk_ctrl.json", + "format": "utf8", + "chunk": false, + "sendError": true, + "x": 380, + "y": 280, + "wires": [ + [ + "7efa17e6.f8b68" + ] + ] + }, + { + "id": "7efa17e6.f8b68", + "type": "json", + "z": "bf64d63f.630868", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 660, + "y": 280, + "wires": [ + [ + "2efaa7ec.1e3158" + ] + ] + }, + { + "id": "5883ebc9.2fede4", + "type": "inject", + "z": "bf64d63f.630868", + "name": "clk_ctrl", + "topic": "clk_ctrl", + "payload": "true", + "payloadType": "bool", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 100, + "y": 280, + "wires": [ + [ + "2d9b9bf2.6b3bb4" + ] + ] + }, + { + "id": "54a46dd.945aa14", + "type": "file in", + "z": "bf64d63f.630868", + "name": "", + "filename": "/home/mcoracin/SHARE/sx1302_reg/tx_top.json", + "format": "utf8", + "chunk": false, + "sendError": true, + "x": 380, + "y": 340, + "wires": [ + [ + "5ae327f2.618788" + ] + ] + }, + { + "id": "5ae327f2.618788", + "type": "json", + "z": "bf64d63f.630868", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 660, + "y": 340, + "wires": [ + [ + "2efaa7ec.1e3158" + ] + ] + }, + { + "id": "ca4d9728.057d68", + "type": "inject", + "z": "bf64d63f.630868", + "name": "tx_top_a", + "topic": "tx_top_a", + "payload": "true", + "payloadType": "bool", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 110, + "y": 340, + "wires": [ + [ + "54a46dd.945aa14" + ] + ] + }, + { + "id": "50b73755.13ee", + "type": "file in", + "z": "bf64d63f.630868", + "name": "", + "filename": "/home/mcoracin/SHARE/sx1302_reg/tx_top.json", + "format": "utf8", + "chunk": false, + "sendError": true, + "x": 380, + "y": 400, + "wires": [ + [ + "ce78d3ce.e6bf18" + ] + ] + }, + { + "id": "ce78d3ce.e6bf18", + "type": "json", + "z": "bf64d63f.630868", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 660, + "y": 400, + "wires": [ + [ + "2efaa7ec.1e3158" + ] + ] + }, + { + "id": "4791c4e5.60b12c", + "type": "inject", + "z": "bf64d63f.630868", + "name": "tx_top_b", + "topic": "tx_top_b", + "payload": "true", + "payloadType": "bool", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 110, + "y": 400, + "wires": [ + [ + "50b73755.13ee" + ] + ] + }, + { + "id": "2efaa7ec.1e3158", + "type": "function", + "z": "bf64d63f.630868", + "name": "JSON to C header", + "func": "var reg_h = flow.get('reg_h') || '';\nvar reg_c = flow.get('reg_c') || '';\nvar reg_desc = flow.get('reg_desc') || '';\nvar reg_count = flow.get('reg_count') || 0;\nvar tx_top_macro = flow.get('tx_top_macro') || '';\n\nvar rif = msg.topic;\nvar prefix = 'SX1302_REG_';\nvar base_addr = prefix + rif.toUpperCase() + '_BASE_ADDR';\n\n// register definition\nvar page = '';\nvar addr = '';\nvar offs = '';\nvar sign = '';\nvar leng = '';\nvar rdon = '';\nvar dflt = '';\nvar chck = '';\nvar reg_comment = '';\nvar desc = '';\nvar flags;\n\nfor(var register in msg.payload)\n{\n //console.log(register);\n for(var field in msg.payload[register].fields)\n {\n //console.log(' ' + field);\n page = '0';\n addr = base_addr + '+' + msg.payload[register].addr;\n offs = msg.payload[register].fields[field].pos;\n if(msg.payload[register].fields[field].hasOwnProperty('signed'))\n {\n console.log(msg.payload[register].fields[field].signed);\n if(msg.payload[register].fields[field].signed === true)\n {\n sign = '1';\n }\n else\n {\n sign = '0';\n }\n }\n else\n {\n sign = '0';\n }\n leng = msg.payload[register].fields[field].width;\n rdon = (msg.payload[register].fields[field].readonly === false) ? '0' : '1';\n dflt = msg.payload[register].fields[field].value;\n flags = msg.payload[register].fields[field].flags;\n desc = msg.payload[register].fields[field].desc;\n //desc = desc.replace(/(?:\\r\\n|\\r|\\n)/g, ' / ');\n if (flags.length > 0)\n {\n for (var i_flag of flags) {\n console.log(i_flag);\n }\n //if(['pulse','w0clr','w1clr','interrupt'].includes(flags))\n if (flags.includes('pulse') || flags.includes('w0clr') || flags.includes('w1clr') || flags.includes('interrupt'))\n {\n console.log('non-checkable register');\n check = 0;\n }\n else\n {\n check = 1;\n }\n }\n else\n {\n check = 1;\n }\n reg_comment = (rif + '_' + register + '_' + field).toUpperCase();\n\n reg_h += '#define ' + prefix + reg_comment + ' ' + reg_count + '\\n';\n reg_c += ' '; // indent\n reg_c += '{' + page + ',' + addr + ',' + offs + ',' + sign + ',' + leng + ',' + rdon + ',' + check + ',' + dflt + '}, // ' + reg_comment + '\\n';\n reg_desc += '\\n' + reg_comment + ':\\n' + desc + '\\n';\n \n if (msg.topic === 'tx_top_a')\n {\n tx_top_macro += '#define ' + prefix + 'TX_TOP' + '_' + register.toUpperCase() + '_' + field.toUpperCase() + '(rf_chain) ((rf_chain == 0) ? \\\\\\n ' + prefix + 'TX_TOP_A' + '_' + register.toUpperCase() + '_' + field.toUpperCase() + ' : \\\\\\n ' + prefix + 'TX_TOP_B' + '_' + register.toUpperCase() + '_' + field.toUpperCase() + ')\\n';\n }\n \n reg_count += 1;\n }\n}\n\n//console.log(reg_h);\n//console.log(reg_c);\n\nflow.set('reg_count', reg_count);\nflow.set('reg_h', reg_h);\nflow.set('reg_c', reg_c);\nflow.set('reg_desc', reg_desc);\nflow.set('tx_top_macro', tx_top_macro);\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "x": 940, + "y": 280, + "wires": [ + [ + "aae4ac70.472438" + ] + ] + }, + { + "id": "ab7535a2.47b2e8", + "type": "file in", + "z": "bf64d63f.630868", + "name": "", + "filename": "/home/mcoracin/SHARE/sx1302_reg/gpio.json", + "format": "utf8", + "chunk": false, + "sendError": true, + "x": 370, + "y": 460, + "wires": [ + [ + "6017e95e.37841" + ] + ] + }, + { + "id": "6017e95e.37841", + "type": "json", + "z": "bf64d63f.630868", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 660, + "y": 460, + "wires": [ + [ + "2efaa7ec.1e3158" + ] + ] + }, + { + "id": "626f34eb.dac1f4", + "type": "inject", + "z": "bf64d63f.630868", + "name": "gpio", + "topic": "gpio", + "payload": "true", + "payloadType": "bool", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 100, + "y": 460, + "wires": [ + [ + "ab7535a2.47b2e8" + ] + ] + }, + { + "id": "f6871b6f.167d", + "type": "file in", + "z": "bf64d63f.630868", + "name": "", + "filename": "/home/mcoracin/SHARE/sx1302_reg/timestamp.json", + "format": "utf8", + "chunk": false, + "sendError": true, + "x": 390, + "y": 520, + "wires": [ + [ + "d1c6c905.bbf6a" + ] + ] + }, + { + "id": "d1c6c905.bbf6a", + "type": "json", + "z": "bf64d63f.630868", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 660, + "y": 520, + "wires": [ + [ + "2efaa7ec.1e3158" + ] + ] + }, + { + "id": "128c968f.34cc09", + "type": "inject", + "z": "bf64d63f.630868", + "name": "timestamp", + "topic": "timestamp", + "payload": "true", + "payloadType": "bool", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 110, + "y": 520, + "wires": [ + [ + "f6871b6f.167d" + ] + ] + }, + { + "id": "38f6ab4d.eba4b4", + "type": "file in", + "z": "bf64d63f.630868", + "name": "", + "filename": "/home/mcoracin/SHARE/sx1302_reg/rx_top.json", + "format": "utf8", + "chunk": false, + "sendError": true, + "x": 380, + "y": 580, + "wires": [ + [ + "f464e634.1500a8" + ] + ] + }, + { + "id": "f464e634.1500a8", + "type": "json", + "z": "bf64d63f.630868", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 660, + "y": 580, + "wires": [ + [ + "2efaa7ec.1e3158" + ] + ] + }, + { + "id": "4dd5087.c34c178", + "type": "inject", + "z": "bf64d63f.630868", + "name": "rx_top", + "topic": "rx_top", + "payload": "true", + "payloadType": "bool", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 100, + "y": 580, + "wires": [ + [ + "38f6ab4d.eba4b4" + ] + ] + }, + { + "id": "28e533a1.3ef5ac", + "type": "file in", + "z": "bf64d63f.630868", + "name": "", + "filename": "/home/mcoracin/SHARE/sx1302_reg/arb_mcu.json", + "format": "utf8", + "chunk": false, + "sendError": true, + "x": 390, + "y": 640, + "wires": [ + [ + "3c2109da.3df84e" + ] + ] + }, + { + "id": "3c2109da.3df84e", + "type": "json", + "z": "bf64d63f.630868", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 660, + "y": 640, + "wires": [ + [ + "2efaa7ec.1e3158" + ] + ] + }, + { + "id": "c3a46357.bf67c", + "type": "inject", + "z": "bf64d63f.630868", + "name": "arb_mcu", + "topic": "arb_mcu", + "payload": "true", + "payloadType": "bool", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 110, + "y": 640, + "wires": [ + [ + "28e533a1.3ef5ac" + ] + ] + }, + { + "id": "b3ed4e4f.fbd358", + "type": "file in", + "z": "bf64d63f.630868", + "name": "", + "filename": "/home/mcoracin/SHARE/sx1302_reg/radio_fe.json", + "format": "utf8", + "chunk": false, + "sendError": true, + "x": 390, + "y": 700, + "wires": [ + [ + "37f7e33f.14dc6c" + ] + ] + }, + { + "id": "37f7e33f.14dc6c", + "type": "json", + "z": "bf64d63f.630868", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 660, + "y": 700, + "wires": [ + [ + "2efaa7ec.1e3158" + ] + ] + }, + { + "id": "e9006ee1.8d154", + "type": "inject", + "z": "bf64d63f.630868", + "name": "radio_fe", + "topic": "radio_fe", + "payload": "true", + "payloadType": "bool", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 110, + "y": 700, + "wires": [ + [ + "b3ed4e4f.fbd358" + ] + ] + }, + { + "id": "7c16420a.7b8634", + "type": "file in", + "z": "bf64d63f.630868", + "name": "", + "filename": "/home/mcoracin/SHARE/sx1302_reg/otp.json", + "format": "utf8", + "chunk": false, + "sendError": true, + "x": 360, + "y": 760, + "wires": [ + [ + "4e1e58e8.d72998" + ] + ] + }, + { + "id": "4e1e58e8.d72998", + "type": "json", + "z": "bf64d63f.630868", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 650, + "y": 760, + "wires": [ + [ + "2efaa7ec.1e3158" + ] + ] + }, + { + "id": "cdd63a9c.be8f4", + "type": "inject", + "z": "bf64d63f.630868", + "name": "otp", + "topic": "otp", + "payload": "true", + "payloadType": "bool", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 100, + "y": 760, + "wires": [ + [ + "7c16420a.7b8634" + ] + ] + }, + { + "id": "9ac5ddf6.e47048", + "type": "file in", + "z": "bf64d63f.630868", + "name": "", + "filename": "/home/mcoracin/SHARE/sx1302_reg/rx_top_lora_service_fsk.json", + "format": "utf8", + "chunk": false, + "sendError": true, + "x": 500, + "y": 820, + "wires": [ + [ + "5a5536dc.db687" + ] + ] + }, + { + "id": "5a5536dc.db687", + "type": "json", + "z": "bf64d63f.630868", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 800, + "y": 820, + "wires": [ + [ + "2efaa7ec.1e3158" + ] + ] + }, + { + "id": "c5ffdcfa.19ea28", + "type": "inject", + "z": "bf64d63f.630868", + "name": "rx_top_lora_service_fsk", + "topic": "rx_top_lora_service_fsk", + "payload": "true", + "payloadType": "bool", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 150, + "y": 820, + "wires": [ + [ + "9ac5ddf6.e47048" + ] + ] + }, + { + "id": "b5897d4d.0628c", + "type": "file in", + "z": "bf64d63f.630868", + "name": "", + "filename": "/home/mcoracin/SHARE/sx1302_reg/capture_ram.json", + "format": "utf8", + "chunk": false, + "sendError": true, + "x": 410, + "y": 880, + "wires": [ + [ + "753bfa61.c8068c" + ] + ] + }, + { + "id": "753bfa61.c8068c", + "type": "json", + "z": "bf64d63f.630868", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 680, + "y": 880, + "wires": [ + [ + "2efaa7ec.1e3158" + ] + ] + }, + { + "id": "6ca97d0.d189b84", + "type": "inject", + "z": "bf64d63f.630868", + "name": "capture_ram", + "topic": "capture_ram", + "payload": "true", + "payloadType": "bool", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 120, + "y": 880, + "wires": [ + [ + "b5897d4d.0628c" + ] + ] + }, + { + "id": "56ed912a.91d118", + "type": "trigger", + "z": "bf64d63f.630868", + "op1": "1", + "op2": "0", + "op1type": "val", + "op2type": "val", + "duration": "250", + "extend": "false", + "units": "ms", + "reset": "", + "bytopic": "all", + "name": "", + "x": 1020, + "y": 500, + "wires": [ + [] + ] + } +] \ No newline at end of file diff --git a/tools/payload_tools/Makefile b/tools/payload_tools/Makefile new file mode 100644 index 0000000..ed5e5fe --- /dev/null +++ b/tools/payload_tools/Makefile @@ -0,0 +1,61 @@ +### get external defined data + +include ../../target.cfg + +### constant symbols + +ARCH ?= +CROSS_COMPILE ?= +CC := $(CROSS_COMPILE)gcc +AR := $(CROSS_COMPILE)ar + +CFLAGS := -O2 -Wall -Wextra -std=c99 -I. -I../../libtools/inc + +### linking options + +LIBS := -ltinymt32 + +### general build targets + +all: payload_crc payload_diff payload_gen + +clean: + rm -f payload_crc payload_diff payload_gen + rm -f *.o + +install: +ifneq ($(strip $(TARGET_IP)),) + ifneq ($(strip $(TARGET_DIR)),) + ifneq ($(strip $(TARGET_USR)),) + @echo "---- Copying payload tools files to $(TARGET_IP):$(TARGET_DIR)" + @ssh $(TARGET_USR)@$(TARGET_IP) "mkdir -p $(TARGET_DIR)" + @scp payload_crc $(TARGET_USR)@$(TARGET_IP):$(TARGET_DIR) + @scp payload_diff $(TARGET_USR)@$(TARGET_IP):$(TARGET_DIR) + @scp payload_gen $(TARGET_USR)@$(TARGET_IP):$(TARGET_DIR) + else + @echo "ERROR: TARGET_USR is not configured in target.cfg" + endif + else + @echo "ERROR: TARGET_DIR is not configured in target.cfg" + endif +else + @echo "ERROR: TARGET_IP is not configured in target.cfg" +endif + +### rules + +%.o : %.c + $(CC) -c $(CFLAGS) $< -o $@ + +### test programs + +payload_crc: payload_crc.o + $(CC) $(CFLAGS) -o $@ $^ + +payload_diff: payload_diff.o + $(CC) $(CFLAGS) -o $@ $^ + +payload_gen: payload_gen.o + $(CC) $(CFLAGS) -L../../libtools -o $@ $^ $(LIBS) + +### EOF diff --git a/tools/payload_tools/payload_crc.c b/tools/payload_tools/payload_crc.c new file mode 100644 index 0000000..a8e2e28 --- /dev/null +++ b/tools/payload_tools/payload_crc.c @@ -0,0 +1,110 @@ +#include +#include +#include +#include + +/* -------------------------------------------------------------------------- */ +/* --- SUBFUNCTIONS DECLARATION --------------------------------------------- */ + +static void usage(void); +uint16_t sx1302_lora_payload_crc(const uint8_t * data, uint8_t size); +void remove_spaces(char *str); + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char ** argv) +{ + int j; + uint8_t payload[255]; + uint8_t payload_size; + uint16_t crc; + char hexstr[1024]; + + if (argc < 2) { + usage(); + return -1; + } + + /* Get payload hex string from command line */ + memcpy(hexstr, argv[1], strlen(argv[1])); + hexstr[strlen(argv[1])] = '\0'; + printf("Input hex string: %s\n", hexstr); + + /* Remove spaces from the string if any */ + remove_spaces(hexstr); + hexstr[strlen(hexstr)] = '\0'; + printf("Removing spaces: %s\n", hexstr); + + /* Convert hex string to byte array */ + payload_size = strlen(hexstr) / 2; + for (j = 0; j < payload_size; j++) { + sscanf(hexstr + 2*j, "%02hhx", &payload[j]); + } + + /* Compute CRC */ + crc = sx1302_lora_payload_crc(payload, payload_size); + printf("Payload CRC_16: %04X\n", crc); + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void usage(void) { + printf("Missing payload hex string\n"); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void remove_spaces(char *str) +{ + /* To keep track of non-space character count */ + int count = 0; + + /* Traverse the given string. If current character + is not space, then place it at index 'count++' */ + for (int i = 0; str[i]; i++) { + if (str[i] != ' ') { + str[count++] = str[i]; /* here count is incremented */ + } + } + str[count] = '\0'; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void lora_crc16(const char data, int *crc) { + int next = 0; + next = (((data>>0)&1) ^ ((*crc>>12)&1) ^ ((*crc>> 8)&1) ) ; + next += ((((data>>1)&1) ^ ((*crc>>13)&1) ^ ((*crc>> 9)&1) )<<1 ) ; + next += ((((data>>2)&1) ^ ((*crc>>14)&1) ^ ((*crc>>10)&1) )<<2 ) ; + next += ((((data>>3)&1) ^ ((*crc>>15)&1) ^ ((*crc>>11)&1) )<<3 ) ; + next += ((((data>>4)&1) ^ ((*crc>>12)&1) )<<4 ) ; + next += ((((data>>5)&1) ^ ((*crc>>13)&1) ^ ((*crc>>12)&1) ^ ((*crc>> 8)&1))<<5 ) ; + next += ((((data>>6)&1) ^ ((*crc>>14)&1) ^ ((*crc>>13)&1) ^ ((*crc>> 9)&1))<<6 ) ; + next += ((((data>>7)&1) ^ ((*crc>>15)&1) ^ ((*crc>>14)&1) ^ ((*crc>>10)&1))<<7 ) ; + next += ((((*crc>>0)&1) ^ ((*crc>>15)&1) ^ ((*crc>>11)&1) )<<8 ) ; + next += ((((*crc>>1)&1) ^ ((*crc>>12)&1) )<<9 ) ; + next += ((((*crc>>2)&1) ^ ((*crc>>13)&1) )<<10) ; + next += ((((*crc>>3)&1) ^ ((*crc>>14)&1) )<<11) ; + next += ((((*crc>>4)&1) ^ ((*crc>>15)&1) ^ ((*crc>>12)&1) ^ ((*crc>> 8)&1))<<12) ; + next += ((((*crc>>5)&1) ^ ((*crc>>13)&1) ^ ((*crc>> 9)&1) )<<13) ; + next += ((((*crc>>6)&1) ^ ((*crc>>14)&1) ^ ((*crc>>10)&1) )<<14) ; + next += ((((*crc>>7)&1) ^ ((*crc>>15)&1) ^ ((*crc>>11)&1) )<<15) ; + (*crc) = next; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint16_t sx1302_lora_payload_crc(const uint8_t * data, uint8_t size) { + int i; + int crc = 0; + + for (i = 0; i < size; i++) { + lora_crc16(data[i], &crc); + } + + //printf("CRC16: 0x%02X 0x%02X (%X)\n", (uint8_t)(crc >> 8), (uint8_t)crc, crc); + return (uint16_t)crc; +} diff --git a/tools/payload_tools/payload_diff.c b/tools/payload_tools/payload_diff.c new file mode 100644 index 0000000..35a4efb --- /dev/null +++ b/tools/payload_tools/payload_diff.c @@ -0,0 +1,116 @@ +#include +#include +#include +#include + +/* -------------------------------------------------------------------------- */ +/* --- MACROS --------------------------------------------------------------- */ + +#define TAKE_N_BITS_FROM(b, p, n) (((b) >> (p)) & ((1 << (n)) - 1)) + +/* -------------------------------------------------------------------------- */ +/* --- SUBFUNCTIONS DECLARATION --------------------------------------------- */ + +static void usage(void); +void remove_spaces(char *str); + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char ** argv) +{ + int i, j; + uint8_t payload_a[255]; + uint8_t payload_b[255]; + uint8_t payload_diff[255]; + uint8_t payload_size; + char hexstr[1024]; + uint16_t nb_bits_diff = 0; + + if (argc < 3) { + usage(); + return -1; + } + + if (strlen(argv[1]) != strlen(argv[2])) { + printf("ERROR: payloads A & B must have same size\n"); + return -1; + } + + /* Get payload A hex string from command line */ + memcpy(hexstr, argv[1], strlen(argv[1])); + hexstr[strlen(argv[1])] = '\0'; + printf("Input hex string: %s\n", hexstr); + + /* Remove spaces from the string if any */ + remove_spaces(hexstr); + hexstr[strlen(hexstr)] = '\0'; + printf("Removing spaces: %s\n", hexstr); + + /* Convert hex string to byte array */ + payload_size = strlen(hexstr) / 2; + for (j = 0; j < payload_size; j++) { + sscanf(hexstr + 2*j, "%02hhx", &payload_a[j]); + } + + /* Get payload B hex string from command line */ + memcpy(hexstr, argv[2], strlen(argv[2])); + hexstr[strlen(argv[2])] = '\0'; + printf("Input hex string: %s\n", hexstr); + + /* Remove spaces from the string if any */ + remove_spaces(hexstr); + hexstr[strlen(hexstr)] = '\0'; + printf("Removing spaces: %s\n", hexstr); + + /* Convert hex string to byte array */ + for (j = 0; j < payload_size; j++) { + sscanf(hexstr + 2*j, "%02hhx", &payload_b[j]); + } + + /* Count how many bits differs */ + printf("Diff: "); + for (j = 0; j < payload_size; j++) { + payload_diff[j] = payload_a[j] ^ payload_b[j]; + printf("%02X ", payload_diff[j]); + } + printf("\n"); + + for (j = 0; j < payload_size; j++) { + for (i = 7; i >= 0; i--) { + printf("%u", TAKE_N_BITS_FROM(payload_diff[j], i, 1)); + if (TAKE_N_BITS_FROM(payload_diff[j], i, 1) == 1) { + nb_bits_diff += 1; + } + } + printf(" "); + } + printf("\n"); + printf("%u bits flipped\n", nb_bits_diff); + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void usage(void) { + printf("Missing payload hex strings for a & b\n"); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void remove_spaces(char *str) +{ + /* To keep track of non-space character count */ + int count = 0; + + /* Traverse the given string. If current character + is not space, then place it at index 'count++' */ + for (int i = 0; str[i]; i++) { + if (str[i] != ' ') { + str[count++] = str[i]; /* here count is incremented */ + } + } + str[count] = '\0'; +} + diff --git a/tools/payload_tools/payload_gen.c b/tools/payload_tools/payload_gen.c new file mode 100644 index 0000000..5002040 --- /dev/null +++ b/tools/payload_tools/payload_gen.c @@ -0,0 +1,124 @@ +#include +#include +#include +#include + +#include "tinymt32.h" + +/* -------------------------------------------------------------------------- */ +/* --- SUBFUNCTIONS DECLARATION --------------------------------------------- */ + +static void usage(void); +void remove_spaces(char *str); + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char ** argv) +{ + int j; + uint8_t dev_id[4]; + uint8_t payload[255]; + uint8_t payload_size; + unsigned int packet_cnt; + tinymt32_t tinymt; + char hexstr[32]; + + if (argc < 4) { + usage(); + return -1; + } + + /* Get dev_id hex string from command line */ + memcpy(hexstr, argv[1], strlen(argv[1])); + hexstr[strlen(argv[1])] = '\0'; + + /* Remove spaces from the string if any */ + remove_spaces(hexstr); + hexstr[strlen(hexstr)] = '\0'; + printf("Dev_id: %s\n", hexstr); + + /* Convert hex string to byte array */ + payload_size = strlen(hexstr) / 2; + for (j = 0; j < 4; j++) { + sscanf(hexstr + 2*j, "%02hhx", &dev_id[j]); + } + + /* Get packet count from which generate the random payload */ + packet_cnt = atoi(argv[2]); + + /* Get packet payload size */ + payload_size = (uint8_t)atoi(argv[3]); + + /* Initialize the pseudo-random generator */ + tinymt.mat1 = 0x8f7011ee; + tinymt.mat2 = 0xfc78ff1f; + tinymt.tmat = 0x3793fdff; + tinymt32_init(&tinymt, packet_cnt); + + /* Construct packet */ + payload[0] = dev_id[0]; + payload[1] = dev_id[1]; + payload[2] = dev_id[2]; + payload[3] = dev_id[3]; + payload[4] = (uint8_t)(packet_cnt >> 24); + payload[5] = (uint8_t)(packet_cnt >> 16); + payload[6] = (uint8_t)(packet_cnt >> 8); + payload[7] = (uint8_t)(packet_cnt >> 0); + for (j = 8; j < payload_size; j++) { + payload[j] = (uint8_t)tinymt32_generate_uint32(&tinymt); + } + for (j = 0; j < payload_size; j++) { + printf("%02X ", payload[j]); + } + printf("\n"); + +#if 0 + for (packet_cnt = 0; packet_cnt < 10; packet_cnt++) { + tinymt32_init(&tinymt, (int)packet_cnt); + payload[0] = 0xCA; + payload[1] = 0xFE; + payload[2] = 0x12; + payload[3] = 0x34; + payload[4] = (uint8_t)(packet_cnt >> 24); + payload[5] = (uint8_t)(packet_cnt >> 16); + payload[6] = (uint8_t)(packet_cnt >> 8); + payload[7] = (uint8_t)(packet_cnt >> 0); + for (j = 8; j < 16; j++) { + payload[j] = (uint8_t)tinymt32_generate_uint32(&tinymt); + } + for (j = 0; j < 16; j++) { + printf("%02X ", payload[j]); + } + printf("\n"); + } +#endif + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void usage(void) { + printf("Missing parameters: ./payload_gen dev_id pkt_cnt pkt_size\n"); + printf(" dev_id: hex string for 4-bytes dev_id\n"); + printf(" pkt_cnt: unsigned int used to initialize the pseudo-random generator\n"); + printf(" pkt_size: paylaod size in bytes [0..255]\n"); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void remove_spaces(char *str) +{ + /* To keep track of non-space character count */ + int count = 0; + + /* Traverse the given string. If current character + is not space, then place it at index 'count++' */ + for (int i = 0; str[i]; i++) { + if (str[i] != ' ') { + str[count++] = str[i]; /* here count is incremented */ + } + } + str[count] = '\0'; +} diff --git a/tools/reset_lgw.sh b/tools/reset_lgw.sh new file mode 100755 index 0000000..0136d72 --- /dev/null +++ b/tools/reset_lgw.sh @@ -0,0 +1,70 @@ +#!/bin/sh + +# This script is intended to be used on SX1302 CoreCell platform, it performs +# the following actions: +# - export/unpexort GPIO23 and GPIO18 used to reset the SX1302 chip and to enable the LDOs +# +# Usage examples: +# ./reset_lgw.sh stop +# ./reset_lgw.sh start + +# GPIO mapping has to be adapted with HW +# + +SX1302_RESET_PIN=23 +SX1302_POWER_EN_PIN=18 + +WAIT_GPIO() { + sleep 0.1 +} + +init() { + # setup GPIOs + echo "$SX1302_RESET_PIN" > /sys/class/gpio/export; WAIT_GPIO + echo "$SX1302_POWER_EN_PIN" > /sys/class/gpio/export; WAIT_GPIO + + # set GPIOs as output + echo "out" > /sys/class/gpio/gpio$SX1302_RESET_PIN/direction; WAIT_GPIO + echo "out" > /sys/class/gpio/gpio$SX1302_POWER_EN_PIN/direction; WAIT_GPIO +} + +reset() { + echo "CoreCell reset through GPIO$SX1302_RESET_PIN..." + echo "CoreCell power enable through GPIO$SX1302_POWER_EN_PIN..." + + # write output for SX1302 CoreCell power_enable and reset + echo "1" > /sys/class/gpio/gpio$SX1302_POWER_EN_PIN/value; WAIT_GPIO + + echo "1" > /sys/class/gpio/gpio$SX1302_RESET_PIN/value; WAIT_GPIO + echo "0" > /sys/class/gpio/gpio$SX1302_RESET_PIN/value; WAIT_GPIO +} + +term() { + # cleanup all GPIOs + if [ -d /sys/class/gpio/gpio$SX1302_RESET_PIN ] + then + echo "$SX1302_RESET_PIN" > /sys/class/gpio/unexport; WAIT_GPIO + fi + if [ -d /sys/class/gpio/gpio$SX1302_POWER_EN_PIN ] + then + echo "$SX1302_POWER_EN_PIN" > /sys/class/gpio/unexport; WAIT_GPIO + fi +} + +case "$1" in + start) + term # just in case + init + reset + ;; + stop) + reset + term + ;; + *) + echo "Usage: $0 {start|stop}" + exit 1 + ;; +esac + +exit 0 \ No newline at end of file diff --git a/util_chip_id/Makefile b/util_chip_id/Makefile new file mode 100644 index 0000000..baed400 --- /dev/null +++ b/util_chip_id/Makefile @@ -0,0 +1,83 @@ +### get external defined data + +include ../target.cfg + +### User defined build options + +ARCH ?= +CROSS_COMPILE ?= +BUILD_MODE := release +OBJDIR = obj + +### ----- AVOID MODIFICATIONS BELLOW ------ AVOID MODIFICATIONS BELLOW ----- ### + +ifeq '$(BUILD_MODE)' 'alpha' + $(warning /\/\/\/ Building in 'alpha' mode \/\/\/\) + WARN_CFLAGS := + OPT_CFLAGS := -O0 + DEBUG_CFLAGS := -g + LDFLAGS := +else ifeq '$(BUILD_MODE)' 'debug' + $(warning /\/\/\/ Building in 'debug' mode \/\/\/\) + WARN_CFLAGS := -Wall -Wextra + OPT_CFLAGS := -O2 + DEBUG_CFLAGS := -g + LDFLAGS := +else ifeq '$(BUILD_MODE)' 'release' + $(warning /\/\/\/ Building in 'release' mode \/\/\/\) + WARN_CFLAGS := -Wall -Wextra + OPT_CFLAGS := -O2 -ffunction-sections -fdata-sections + DEBUG_CFLAGS := + LDFLAGS := -Wl,--gc-sections +else + $(error BUILD_MODE must be set to either 'alpha', 'debug' or 'release') +endif + +### Application-specific variables +APP_NAME := chip_id +APP_LIBS := -lloragw -lm -ltinymt32 -lrt + +### Environment constants +LIB_PATH := ../libloragw + +### Expand build options +CFLAGS := -std=c99 $(WARN_CFLAGS) $(OPT_CFLAGS) $(DEBUG_CFLAGS) +CC := $(CROSS_COMPILE)gcc +AR := $(CROSS_COMPILE)ar + +### General build targets +all: $(APP_NAME) + +clean: + rm -f obj/*.o + rm -f $(APP_NAME) + +install: +ifneq ($(strip $(TARGET_IP)),) + ifneq ($(strip $(TARGET_DIR)),) + ifneq ($(strip $(TARGET_USR)),) + @echo "---- Copying chip_id files to $(TARGET_IP):$(TARGET_DIR)" + @ssh $(TARGET_USR)@$(TARGET_IP) "mkdir -p $(TARGET_DIR)" + @scp chip_id $(TARGET_USR)@$(TARGET_IP):$(TARGET_DIR) + else + @echo "ERROR: TARGET_USR is not configured in target.cfg" + endif + else + @echo "ERROR: TARGET_DIR is not configured in target.cfg" + endif +else + @echo "ERROR: TARGET_IP is not configured in target.cfg" +endif + +$(OBJDIR): + mkdir -p $(OBJDIR) + +### Compile main program +$(OBJDIR)/$(APP_NAME).o: src/$(APP_NAME).c | $(OBJDIR) + $(CC) -c $< -o $@ $(CFLAGS) -Iinc -I../libloragw/inc + +### Link everything together +$(APP_NAME): $(OBJDIR)/$(APP_NAME).o + $(CC) -L$(LIB_PATH) -L../libtools $^ -o $@ $(LDFLAGS) $(APP_LIBS) + +### EOF diff --git a/util_chip_id/readme.md b/util_chip_id/readme.md new file mode 100644 index 0000000..879f788 --- /dev/null +++ b/util_chip_id/readme.md @@ -0,0 +1,53 @@ + ______ _ + / _____) _ | | + ( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | + (______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Utility to get SX1302 chip EUI +============================== + + +## 1. Introduction + +This utility configures the SX1302 to be able to retrieve its EUI. +It can then be used as a Gateway ID. + +## 2. Command line options + +### 2.1. General options ### + +`-h` +will display a short help and version informations. + +### 2.2. SPI options ### + +`-d filename` +use the Linux SPI device driver, but with an explicit path, for systems with +several SPI device drivers, or uncommon numbering scheme. + +## 3. Legal notice + +The information presented in this project documentation does not form part of +any quotation or contract, is believed to be accurate and reliable and may be +changed without notice. No liability will be accepted by the publisher for any +consequence of its use. Publication thereof does not convey nor imply any +license under patent or other industrial or intellectual property rights. +Semtech assumes no responsibility or liability whatsoever for any failure or +unexpected operation resulting from misuse, neglect improper installation, +repair or improper handling or unusual physical or electrical stress +including, but not limited to, exposure to parameters beyond the specified +maximum ratings or operation outside the specified range. + +SEMTECH PRODUCTS ARE NOT DESIGNED, INTENDED, AUTHORIZED OR WARRANTED TO BE +SUITABLE FOR USE IN LIFE-SUPPORT APPLICATIONS, DEVICES OR SYSTEMS OR OTHER +CRITICAL APPLICATIONS. INCLUSION OF SEMTECH PRODUCTS IN SUCH APPLICATIONS IS +UNDERSTOOD TO BE UNDERTAKEN SOLELY AT THE CUSTOMER'S OWN RISK. Should a +customer purchase or use Semtech products for any such unauthorized +application, the customer shall indemnify and hold Semtech and its officers, +employees, subsidiaries, affiliates, and distributors harmless against all +claims, costs damages and attorney fees which could arise. + +*EOF* \ No newline at end of file diff --git a/util_chip_id/src/chip_id.c b/util_chip_id/src/chip_id.c new file mode 100644 index 0000000..160372d --- /dev/null +++ b/util_chip_id/src/chip_id.c @@ -0,0 +1,209 @@ +/* + ______ _ + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Utility to get SX1302 chip EUI + +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 +#include +#include +#include +#include +#include +#include /* sigaction */ +#include /* getopt_long */ + +#include "loragw_hal.h" +#include "loragw_reg.h" +#include "loragw_aux.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define RAND_RANGE(min, max) (rand() % (max + 1 - min) + min) + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" + +#define DEFAULT_CLK_SRC 0 +#define DEFAULT_FREQ_HZ 868500000U + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ + +/* describe command line options */ +void usage(void) { + printf("Library version information: %s\n", lgw_version_info()); + printf("Available options:\n"); + printf(" -h print this help\n"); + printf(" -d [path] Path the spidev file (ex: /dev/spidev0.0)\n"); + printf(" -k Concentrator clock source (Radio A or Radio B) [0..1]\n"); + printf(" -r Radio type (1255, 1257, 1250)\n"); +} + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char **argv) +{ + int i, x; + unsigned int arg_u; + uint8_t clocksource = 0; + lgw_radio_type_t radio_type = LGW_RADIO_TYPE_SX1250; + + struct lgw_conf_board_s boardconf; + struct lgw_conf_rxrf_s rfconf; + uint64_t eui; + + /* SPI interfaces */ + const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; + const char * spidev_path = spidev_path_default; + + /* Parameter parsing */ + int option_index = 0; + static struct option long_options[] = { + {0, 0, 0, 0} + }; + + /* parse command line options */ + while ((i = getopt_long (argc, argv, "hd:", long_options, &option_index)) != -1) { + switch (i) { + case 'h': + usage(); + return -1; + break; + + case 'd': + spidev_path = optarg; + break; + + case 'r': /* Radio type */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || ((arg_u != 1255) && (arg_u != 1257) && (arg_u != 1250))) { + printf("ERROR: argument parsing of -r argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + switch (arg_u) { + case 1255: + radio_type = LGW_RADIO_TYPE_SX1255; + break; + case 1257: + radio_type = LGW_RADIO_TYPE_SX1257; + break; + default: /* 1250 */ + radio_type = LGW_RADIO_TYPE_SX1250; + break; + } + } + break; + + case 'k': /* Clock Source */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 1)) { + printf("ERROR: argument parsing of -k argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + clocksource = (uint8_t)arg_u; + } + break; + + default: + printf("ERROR: argument parsing\n"); + usage(); + return -1; + } + } + + /* Board reset */ + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + + /* Configure the gateway */ + memset( &boardconf, 0, sizeof boardconf); + boardconf.lorawan_public = true; + boardconf.clksrc = clocksource; + boardconf.full_duplex = false; + strncpy(boardconf.spidev_path, spidev_path, sizeof boardconf.spidev_path); + if (lgw_board_setconf(&boardconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure board\n"); + return EXIT_FAILURE; + } + + memset( &rfconf, 0, sizeof rfconf); + rfconf.enable = true; /* rf chain 0 needs to be enabled for calibration to work on sx1257 */ + rfconf.freq_hz = 868500000; /* dummy */ + rfconf.type = radio_type; + rfconf.tx_enable = false; + if (lgw_rxrf_setconf(0, &rfconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure rxrf 0\n"); + return EXIT_FAILURE; + } + + memset( &rfconf, 0, sizeof rfconf); + rfconf.enable = (clocksource == 1) ? true : false; + rfconf.freq_hz = 868500000; /* dummy */ + rfconf.type = radio_type; + rfconf.tx_enable = false; + if (lgw_rxrf_setconf(1, &rfconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure rxrf 1\n"); + return EXIT_FAILURE; + } + + x = lgw_start(); + if (x != 0) { + printf("ERROR: failed to start the gateway\n"); + return EXIT_FAILURE; + } + + /* get the concentrator EUI */ + x = lgw_get_eui(&eui); + if (x != LGW_HAL_SUCCESS) { + printf("ERROR: failed to get concentrator EUI\n"); + } else { + printf("\nINFO: concentrator EUI: 0x%016llX\n\n", eui); + } + + /* Stop the gateway */ + x = lgw_stop(); + if (x != 0) { + printf("ERROR: failed to stop the gateway\n"); + return EXIT_FAILURE; + } + + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + + return 0; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/util_net_downlink/Makefile b/util_net_downlink/Makefile new file mode 100644 index 0000000..01829d2 --- /dev/null +++ b/util_net_downlink/Makefile @@ -0,0 +1,83 @@ +### get external defined data + +include ../target.cfg + +### User defined build options + +ARCH ?= +CROSS_COMPILE ?= +BUILD_MODE := release +OBJDIR = obj + +### ----- AVOID MODIFICATIONS BELLOW ------ AVOID MODIFICATIONS BELLOW ----- ### + +ifeq '$(BUILD_MODE)' 'alpha' + $(warning /\/\/\/ Building in 'alpha' mode \/\/\/\) + WARN_CFLAGS := + OPT_CFLAGS := -O0 + DEBUG_CFLAGS := -g + LDFLAGS := +else ifeq '$(BUILD_MODE)' 'debug' + $(warning /\/\/\/ Building in 'debug' mode \/\/\/\) + WARN_CFLAGS := -Wall -Wextra + OPT_CFLAGS := -O2 + DEBUG_CFLAGS := -g + LDFLAGS := +else ifeq '$(BUILD_MODE)' 'release' + $(warning /\/\/\/ Building in 'release' mode \/\/\/\) + WARN_CFLAGS := -Wall -Wextra + OPT_CFLAGS := -O2 -ffunction-sections -fdata-sections + DEBUG_CFLAGS := + LDFLAGS := -Wl,--gc-sections +else + $(error BUILD_MODE must be set to either 'alpha', 'debug' or 'release') +endif + +### Application-specific variables +APP_NAME := net_downlink +APP_LIBS := -lparson -lbase64 -lpthread + +### Environment constants +LIB_PATH := ../libtools + +### Expand build options +CFLAGS := -std=c99 $(WARN_CFLAGS) $(OPT_CFLAGS) $(DEBUG_CFLAGS) +CC := $(CROSS_COMPILE)gcc +AR := $(CROSS_COMPILE)ar + +### General build targets +all: $(APP_NAME) + +clean: + rm -f obj/*.o + rm -f $(APP_NAME) + +install: +ifneq ($(strip $(TARGET_IP)),) + ifneq ($(strip $(TARGET_DIR)),) + ifneq ($(strip $(TARGET_USR)),) + @echo "---- Copying net_downlink files to $(TARGET_IP):$(TARGET_DIR)" + @ssh $(TARGET_USR)@$(TARGET_IP) "mkdir -p $(TARGET_DIR)" + @scp net_downlink $(TARGET_USR)@$(TARGET_IP):$(TARGET_DIR) + else + @echo "ERROR: TARGET_USR is not configured in target.cfg" + endif + else + @echo "ERROR: TARGET_DIR is not configured in target.cfg" + endif +else + @echo "ERROR: TARGET_IP is not configured in target.cfg" +endif + +$(OBJDIR): + mkdir -p $(OBJDIR) + +### Compile main program +$(OBJDIR)/$(APP_NAME).o: src/$(APP_NAME).c | $(OBJDIR) + $(CC) -c $< -o $@ $(CFLAGS) -Iinc -I../libtools/inc + +### Link everything together +$(APP_NAME): $(OBJDIR)/$(APP_NAME).o + $(CC) -L$(LIB_PATH) $^ -o $@ $(LDFLAGS) $(APP_LIBS) + +### EOF diff --git a/util_net_downlink/readme.md b/util_net_downlink/readme.md new file mode 100644 index 0000000..cb86f48 --- /dev/null +++ b/util_net_downlink/readme.md @@ -0,0 +1,62 @@ + ______ _ + / _____) _ | | + ( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | + (______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Utility: Downlink server +======================== + +## 1. Introduction + +This utility allows to send regular downlink requests to the packet forwarder +running on the gateway. + +The downlinks are sent in 'immediate' mode, meaning that the SX1302 will send +the incoming packet over the air as soon as it receives it. + +So, the net_downlink utility will construct a JSON 'txpk' object based on given +command line arguments, and send it on a UDP socket on the given port. Then the +packet forwarder receives it on its downlink socket, parses the JSON object to +build the packet buffer to be sent to the concentrator board. + +This utility can be compiled and run on the gateway itself, or on a PC. + +Optionally, the net_downlink utility can forward the received uplinks +(PUSH_DATA) to another UDP server, which can be useful for uplink Packet Error +Rate measurement while performing downlink testing (full-duplex testing etc...) + +In can also be used as a UDP packet logger, logging all uplinks in a CSV file. + +## 2. Dependencies + +A packet forwarder must be running to receive downlink packets and send it to +the concentrator board. + +## 3. Usage + +### 3.1. Packet Forwarder configuration + +The 'global_conf.json' file provided with the packet forwarder can be used, only +the 'server_address' must be set to 'localhost' if net_downlink is running on +the gateway itself, or set to the IP address of the PC on which the utility is +running. + +### 3.2. Launching the packet forwarder + +The packet forwarder has to be launched with the global_conf.json described in +3.1. + + `./lora_pkt_fwd -c global_conf.json` + +### 3.3. Launching net_downlink + +The net_downlink utility can be started with various command line arguments. + +In order to get the available options, and some examples, run: + +`./net_downlink -h` + +To stop the application, press Ctrl+C. diff --git a/util_net_downlink/src/net_downlink.c b/util_net_downlink/src/net_downlink.c new file mode 100644 index 0000000..03169bf --- /dev/null +++ b/util_net_downlink/src/net_downlink.c @@ -0,0 +1,1423 @@ +/* + ______ _ + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + + Description: + Network packet sender, sends UDP packets to a running packet forwarder + Network packet receiver, receives UDP packets from a running packet forwarder. + + 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 /* printf, fprintf, sprintf, fopen, fputs */ +#include /* EXIT_* */ +#include /* usleep */ +#include /* bool type */ + +#include /* memset */ +#include /* time, clock_gettime, strftime, gmtime, clock_nanosleep*/ +#include /* error messages */ +#include /* timeval */ + +#include /* socket specific definitions */ +#include /* INET constants and stuff */ +#include /* IP address conversion stuff */ +#include /* gai_strerror */ + +#include /* sigaction */ + +#include + +#include "parson.h" +#include "base64.h" + +/* -------------------------------------------------------------------------- */ +/* --- MACROS & CONSTANTS --------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) + +#define PROTOCOL_VERSION 2 + +/* Get a particular bit value from a byte */ +/* b: any byte + p: index >=0 + n: number of bits >=1 */ +/** + @brief Get a particular bit value from a byte + @param b [in] Any byte from which we want a bit value + @param p [in] Position of the bit in the byte [0..7] + @param n [in] Number of bits we want to get + @return The value corresponding the requested bits + */ +#define TAKE_N_BITS_FROM( b, p, n ) ((b) >> (p)) & ((1 << (n)) - 1) + +/* Constants */ +#define DEFAULT_LORA_BW 125 /* LoRa modulation bandwidth, kHz */ +#define DEFAULT_LORA_SF 7 /* LoRa SF */ +#define DEFAULT_LORA_CR "4/5" /* LoRa CR */ +#define DEFAULT_FSK_FDEV 25 /* FSK frequency deviation */ +#define DEFAULT_FSK_BR 50 /* FSK bitrate */ +#define DEFAULT_LORA_PREAMBLE_SIZE 8 /* LoRa preamble size */ +#define DEFAULT_PAYLOAD_SIZE 4 /* payload size, bytes */ +#define PUSH_TIMEOUT_MS 100 + +/* -------------------------------------------------------------------------- */ +/* --- CUSTOM TYPES --------------------------------------------------------- */ + +typedef enum +{ + PKT_PUSH_DATA = 0, + PKT_PUSH_ACK = 1, + PKT_PULL_DATA = 2, + PKT_PULL_RESP = 3, + PKT_PULL_ACK = 4, + PKT_TX_ACK = 5 +} pkt_type_t; + +typedef struct +{ + uint32_t nb_loop[2]; /* number of downlinks to be sent on each RF chain */ + uint32_t delay_ms[2]; /* delay between 2 downlinks on each RF chain */ + int sock; /* socket file descriptor */ + double freq_mhz[2]; + double freq_step; + uint8_t freq_nb; + uint8_t rf_chain; + uint16_t bandwidth_khz; + char modulation_rf0[8]; + char modulation_rf1[8]; + uint8_t spread_factor[2]; + char coding_rate[8]; + float br_kbps; + uint8_t fdev_khz; + int8_t rf_power[2]; + uint16_t preamb_size[2]; + uint8_t pl_size[2]; + bool ipol; +} thread_params_t; + +/* -------------------------------------------------------------------------- */ +/* --- GLOBAL VARIABLES ----------------------------------------------------- */ + +/* Signal handling variables */ +static int exit_sig = 0; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */ +static int quit_sig = 0; /* 1 -> application terminates without shutting down the hardware */ + +/* Socket info variables */ +static bool sockaddr_valid = false; +static struct sockaddr_storage dist_addr_down; +static socklen_t addr_len_down = sizeof dist_addr_down; + +/* Thread variables */ +static pthread_mutex_t mx_sockaddr = PTHREAD_MUTEX_INITIALIZER; /* control access to the sockaddr info */ + +/* -------------------------------------------------------------------------- */ +/* --- SUBFUNCTIONS DECLARATION --------------------------------------------- */ + +static void sig_handler( int sigio ); +static void usage( void ); +static void * thread_down_rf0( const void * arg ); +static void * thread_down_rf1( const void * arg ); +static void log_csv(FILE * file, uint8_t * buf); + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main( int argc, char **argv ) +{ + int i, j, x; /* loop variable and temporary variable for return value */ + static struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ + unsigned arg_u = 0; + unsigned arg_u2 = 0; + double arg_f = 0.0; + double arg_f_step = 0.0; + double arg_f2 = 0.0; + int arg_i = 0; + int arg_i2 = 0; + char arg_s[8]; + char arg_s2[8]; + bool parse_err = false; + + /* Logging file variables */ + const char * log_fname = NULL; /* pointer to a string we won't touch */ + FILE * log_file = NULL; + bool is_first = true; + + /* Server socket creation */ + int sock; /* socket file descriptor */ + 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]; + const char * port_arg = NULL; + struct sockaddr_storage dist_addr; + socklen_t addr_len = sizeof dist_addr; + + /* Uplink forwarder */ + bool fwd_uplink = false; + int sock_fwd = -1; /* socket file descriptor */ + char serv_addr[64] = "127.0.0.1"; + char serv_port_fwd[8] = "1700"; + struct timeval push_timeout_half = {0, (PUSH_TIMEOUT_MS * 500)}; + + /* Variables for receiving and sending packets */ + uint8_t databuf_up[32768]; + uint8_t databuf_ack[4]; + int byte_nb; + + /* Variables for protocol management */ + uint32_t raw_mac_h; /* Most Significant Nibble, network order */ + uint32_t raw_mac_l; /* Least Significant Nibble, network order */ + uint64_t gw_mac; /* MAC address of the client (gateway) */ + uint8_t ack_command; + bool no_ack; + + /* Downlink variables */ + thread_params_t thread_params = { + .nb_loop = {0, 0}, + .delay_ms = {1000, 1000}, + .bandwidth_khz = DEFAULT_LORA_BW, + .spread_factor = {DEFAULT_LORA_SF, DEFAULT_LORA_SF}, + .modulation_rf0 = {"LORA"}, + .modulation_rf1 = {"LORA"}, + .coding_rate = DEFAULT_LORA_CR, + .br_kbps = DEFAULT_FSK_BR, + .fdev_khz = DEFAULT_FSK_FDEV, + .rf_power = {27, 27}, + .preamb_size = {DEFAULT_LORA_PREAMBLE_SIZE, DEFAULT_LORA_PREAMBLE_SIZE}, + .pl_size = {DEFAULT_PAYLOAD_SIZE, DEFAULT_PAYLOAD_SIZE}, + .freq_step = 0.2, + .freq_nb = 1, + .ipol = false + }; + + /* Threads ID */ + pthread_t thrid_down_rf0; + pthread_t thrid_down_rf1; + + /* Parse command line options */ + while( ( i = getopt( argc, argv, "b:c:f:hij:l:p:r:s:t:x:z:A:F:P:m:d:q:" ) ) != -1 ) + { + switch( i ) + { + case 'h': + usage( ); + return EXIT_SUCCESS; + break; + + case 'l': + log_fname = optarg; + break; + + case 'P': + port_arg = optarg; + break; + + case 'A': + fwd_uplink = true; + strncpy( serv_addr, optarg, strlen( optarg )); + break; + + case 'F': + strncpy( serv_port_fwd, optarg, strlen( optarg )); + break; + + case 'f': /* -f target frequency in MHz */ + j = sscanf( optarg, "%lf,%lf", &arg_f, &arg_f2 ); + switch( j ) + { + case 2: + if( (arg_f2 < 30.0) || (arg_f2 > 3000.0) ) + { + parse_err = true; + } + else + { + thread_params.freq_mhz[1] = arg_f2; + } + /* No break */ + case 1: + if( (arg_f < 30.0) || (arg_f > 3000.0) ) + { + parse_err = true; + } + else + { + thread_params.freq_mhz[0] = arg_f; + } + break; + default: + parse_err = true; + } + if( parse_err ) + { + printf( "ERROR: argument parsing of -f argument\n" ); + usage( ); + return EXIT_FAILURE; + } + break; + + case 'j': + j = sscanf( optarg, "%u:%lf", &arg_u, &arg_f_step ); + switch( j ) + { + case 2: + if( (arg_f_step < 0.05) || (arg_f_step > 20.0) ) + { + parse_err = true; + } + else + { + thread_params.freq_step = arg_f_step; + } + /* No break */ + case 1: + if( (arg_u == 0) || (arg_u > 100) ) + { + parse_err = true; + } + else + { + thread_params.freq_nb = arg_u; + } + break; + default: + parse_err = true; + } + if( parse_err ) + { + printf( "ERROR: argument parsing of -j argument\n" ); + usage( ); + return EXIT_FAILURE; + } + break; + + case 'b': /* -b LoRa modulation bandwidth */ + j = sscanf( optarg, "%u", &arg_u ); + if( (j != 1) || ((arg_u != 125) && (arg_u != 250) && (arg_u != 500)) ) + { + printf( "ERROR: argument parsing of -b argument\n" ); + usage( ); + return EXIT_FAILURE; + } + else + { + thread_params.bandwidth_khz = (uint16_t)arg_u; + } + break; + + case 's': /* -s LoRa Spreading Factor */ + j = sscanf( optarg, "%u,%u", &arg_u, &arg_u2 ); + switch( j ) + { + case 2: + if( (arg_u2 < 5) || (arg_u2 > 12) ) + { + parse_err = true; + } + else + { + thread_params.spread_factor[1] = (uint8_t)arg_u2; + } + /* No break */ + case 1: + if( (arg_u < 5) || (arg_u > 12) ) + { + parse_err = true; + } + else + { + thread_params.spread_factor[0] = (uint8_t)arg_u; + } + break; + default: + parse_err = true; + } + if( parse_err ) + { + printf( "ERROR: argument parsing of -s argument\n" ); + usage( ); + return EXIT_FAILURE; + } + break; + + case 'c': /* -c LoRa Coding Rate */ + j = sscanf( optarg, "%s", arg_s ); + if( j != 1 ) + { + printf( "ERROR: argument parsing of -c argument\n" ); + usage( ); + return EXIT_FAILURE; + } + else + { + strncpy( thread_params.coding_rate, arg_s, strlen(arg_s)); + } + break; + + case 'm': /* -m Modulation */ + j = sscanf( optarg, "%[^,],%[^,]", arg_s, arg_s2 ); + switch( j ) + { + case 2: + strncpy( thread_params.modulation_rf1, arg_s2, strlen(arg_s2)); + thread_params.modulation_rf1[strlen(arg_s2)] = '\0'; + /* No break */ + case 1: + strncpy( thread_params.modulation_rf0, arg_s, strlen(arg_s)); + thread_params.modulation_rf0[strlen(arg_s)] = '\0'; + break; + default: + parse_err = true; + } + if( parse_err ) + { + printf( "ERROR: argument parsing of -m argument\n" ); + usage( ); + return EXIT_FAILURE; + } + break; + + case 'd': /* FSK frequency deviation */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u < 1) || (arg_u > 250)) { + printf("ERROR: invalid FSK frequency deviation\n"); + usage( ); + return EXIT_FAILURE; + } else { + thread_params.fdev_khz = (uint8_t)arg_u; + } + break; + + case 'q': /* FSK bitrate */ + i = sscanf(optarg, "%lf", &arg_f); + if ((i != 1) || (arg_f < 0.5) || (arg_f > 250)) { + printf("ERROR: invalid FSK bitrate\n"); + usage( ); + return EXIT_FAILURE; + } else { + thread_params.br_kbps = arg_f; + } + break; + + case 'p': /* -p RF power (dBm) */ + j = sscanf( optarg, "%i,%i", &arg_i, &arg_i2 ); + switch( j ) + { + case 2: + if( (arg_i2 < -60) || (arg_i2 > 60) ) + { + parse_err = true; + } + else + { + thread_params.rf_power[1] = (int8_t)arg_i2; + } + /* No break */ + case 1: + if( (arg_i < -60) || (arg_i > 60) ) + { + parse_err = true; + } + else + { + thread_params.rf_power[0] = (int8_t)arg_i; + } + break; + default: + parse_err = true; + } + if( parse_err ) + { + printf( "ERROR: argument parsing of -p argument\n" ); + usage( ); + return EXIT_FAILURE; + } + break; + + case 'r': /* -r preamble size */ + j = sscanf( optarg, "%u,%u", &arg_u, &arg_u2 ); + switch( j ) + { + case 2: + if( (arg_u2 < 5) || (arg_u2 > 65535) ) + { + parse_err = true; + } + else + { + thread_params.preamb_size[1] = (uint16_t)arg_u2; + } + /* No break */ + case 1: + if( (arg_u < 5) || (arg_u > 65535) ) + { + parse_err = true; + } + else + { + thread_params.preamb_size[0] = (uint16_t)arg_u; + } + break; + default: + parse_err = true; + } + if( parse_err ) + { + printf( "ERROR: argument parsing of -r argument\n" ); + usage( ); + return EXIT_FAILURE; + } + break; + + case 'z': /* -z payload length (bytes) */ + j = sscanf( optarg, "%u,%u", &arg_u, &arg_u2 ); + switch( j ) + { + case 2: + if( arg_u2 > 255 ) + { + parse_err = true; + } + else + { + thread_params.pl_size[1] = (uint8_t)arg_u2; + } + /* No break */ + case 1: + if( arg_u > 255 ) + { + parse_err = true; + } + else + { + thread_params.pl_size[0] = (uint8_t)arg_u; + } + break; + default: + parse_err = true; + } + if( parse_err ) + { + printf( "ERROR: argument parsing of -z argument\n" ); + usage( ); + return EXIT_FAILURE; + } + break; + + case 'i': + thread_params.ipol = true; + break; + + case 't': + j = sscanf( optarg, "%u,%u", &arg_u, &arg_u2 ); + switch( j ) + { + case 2: + thread_params.delay_ms[1] = (uint32_t)arg_u2; + /* No break */ + case 1: + thread_params.delay_ms[0] = (uint32_t)arg_u; + break; + default: + parse_err = true; + } + if( parse_err ) + { + printf( "ERROR: argument parsing of -t argument\n" ); + usage( ); + return EXIT_FAILURE; + } + break; + + case 'x': + j = sscanf( optarg, "%u,%u", &arg_u, &arg_u2 ); + switch( j ) + { + case 2: + thread_params.nb_loop[1] = (uint32_t)arg_u2; + /* No break */ + case 1: + thread_params.nb_loop[0] = (uint32_t)arg_u; + break; + default: + parse_err = true; + } + if( parse_err ) + { + printf( "ERROR: argument parsing of -x argument\n" ); + usage( ); + return EXIT_FAILURE; + } + break; + + default: + printf( "ERROR: argument parsing options, use -h option for help\n" ); + usage( ); + return EXIT_FAILURE; + } + } + + /* Check input arguments */ + if( port_arg == NULL ) + { + printf( "ERROR: missing argument, use -h option for help\n" ); + usage( ); + return EXIT_FAILURE; + } + + /* Start message */ + printf( "+++ Start of network uplink logger (30ms delay) +++\n" ); + + /* Configure socket for uplink forwarding if required */ + if( fwd_uplink == true ) + { + /* Prepare hints to open network sockets */ + memset( &hints, 0, sizeof hints ); + hints.ai_family = AF_UNSPEC; /* should handle IP v4 or v6 automatically */ + hints.ai_socktype = SOCK_DGRAM; /* we want UDP sockets */ + hints.ai_protocol = IPPROTO_UDP; /* we want UDP sockets */ + hints.ai_flags = AI_ADDRCONFIG; /* do not return IPv6 results if there is no IPv6 network connection, same with IPv4 */ + + /* Look for server address w/ upstream port */ + x = getaddrinfo( serv_addr, serv_port_fwd, &hints, &result ); + if( x != 0 ) + { + printf( "ERROR: [up] getaddrinfo on address %s (PORT %s) returned %s\n", serv_addr, serv_port_fwd, gai_strerror( x ) ); + return EXIT_FAILURE; + } + + /* Try to open UDP socket for upstream traffic */ + for( q = result; q != NULL; q = q->ai_next ) + { + sock_fwd = socket( q->ai_family, q->ai_socktype, q->ai_protocol ); + if( sock_fwd == -1 ) continue; /* try next field */ + else break; /* success, get out of loop */ + } + if( q == NULL ) + { + printf( "ERROR: [up] failed to open socket to any of server %s addresses (port %s)\n", serv_addr, serv_port_fwd ); + return EXIT_FAILURE; + } + else + { + getnameinfo( q->ai_addr, q->ai_addrlen, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST ); + printf( "INFO: socket %i opened for upstream traffic, host: %s, port: %s\n", sock_fwd, host_name, port_name ); + } + + /* Connect the UDP socket so we can send/receive packet with the server only */ + x = connect( sock_fwd, q->ai_addr, q->ai_addrlen ); + if( x != 0 ) + { + printf( "ERROR: [up] connect returned %s\n", strerror( errno ) ); + return EXIT_FAILURE; + } + + /* Free the result of getaddrinfo */ + freeaddrinfo( result ); + + /* Set upstream socket RX timeout */ + x = setsockopt( sock_fwd, SOL_SOCKET, SO_RCVTIMEO, (void *)&(push_timeout_half), sizeof push_timeout_half ); + if( x != 0 ) + { + printf( "ERROR: [up] setsockopt returned %s\n", strerror( errno ) ); + exit( EXIT_FAILURE ); + } + } + + /* Prepare hints to open network sockets */ + memset( &hints, 0, sizeof hints ); + hints.ai_family = AF_UNSPEC; /* should handle IP v4 or v6 automatically */ + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; /* will assign local IP automatically */ + + /* Look for address */ + x = getaddrinfo( NULL, port_arg, &hints, &result ); + if( x != 0 ) + { + printf( "ERROR: getaddrinfo returned %s\n", gai_strerror( x ) ); + return EXIT_FAILURE; + } + + /* Try to open socket and bind it */ + for( q = result; q != NULL; q = q->ai_next ) + { + sock = socket( q->ai_family, q->ai_socktype, q->ai_protocol ); + if( sock == -1 ) + { + continue; /* socket failed, try next field */ + } + else + { + x = bind( sock, q->ai_addr, q->ai_addrlen ); + if( x == -1 ) + { + shutdown( sock, SHUT_RDWR ); + continue; /* bind failed, try next field */ + } + else + { + thread_params.sock = sock; + break; /* success, get out of loop */ + } + } + } + if( q == NULL ) + { + printf( "ERROR: failed to open socket or to bind to it\n" ); + 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 ); + printf( "INFO: result %i host:%s service:%s\n", i, host_name, port_name ); + ++i; + } + return EXIT_FAILURE; + } + printf( "INFO: util_net_downlink listening on port %s\n", port_arg ); + freeaddrinfo( result ); + + /* Open log file */ + if( log_fname ) + { + log_file = fopen( log_fname, "w+" ); /* create log file, overwrite if file already exist */ + if( log_file == NULL ) + { + printf( "ERROR: impossible to create log file %s\n", log_fname ); + return EXIT_FAILURE; + } + } + + /* Configure signal handling */ + sigemptyset( &sigact.sa_mask ); + sigact.sa_flags = 0; + sigact.sa_handler = sig_handler; + sigaction( SIGQUIT, &sigact, NULL ); + sigaction( SIGINT, &sigact, NULL ); + sigaction( SIGTERM, &sigact, NULL ); + + i = pthread_create( &thrid_down_rf0, NULL, (void * (*)( void * ))thread_down_rf0, (void*)&thread_params ); + if( i != 0 ) + { + printf( "ERROR: [main] impossible to create downstream thread\n" ); + return EXIT_FAILURE; + } + + i = pthread_create( &thrid_down_rf1, NULL, (void * (*)( void * ))thread_down_rf1, (void*)&thread_params ); + if( i != 0 ) + { + printf( "ERROR: [main] impossible to create downstream thread for RF1\n" ); + return EXIT_FAILURE; + } + + /* Loop until user quits */ + while( ( quit_sig != 1 ) && ( exit_sig != 1 ) ) + { + /* Wait to receive a packet */ + memset( databuf_up, 0, 4096 ); + byte_nb = recvfrom( sock, databuf_up, sizeof databuf_up, 0, (struct sockaddr *)&dist_addr, &addr_len ); + if( byte_nb == -1 ) + { + printf( "ERROR: recvfrom returned %s \n", strerror( errno ) ); + continue; + } + + /* Display info about the sender */ + x = getnameinfo( (struct sockaddr *)&dist_addr, addr_len, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST ); + if( x == -1 ) + { + printf( "ERROR: getnameinfo returned %s \n", gai_strerror( x ) ); + return EXIT_FAILURE; + } + printf( " -> pkt in , host %s (port %s), %i bytes", host_name, port_name, byte_nb ); + + /* Check and parse the payload */ + if( byte_nb < 12 ) + { + /* Not enough bytes for packet from gateway */ + printf( " (too short for GW <-> MAC protocol)\n" ); + continue; + } + /* Don't touch the token in position 1-2, it will be sent back "as is" for acknowledgement */ + + /* Check protocol version number */ + if( databuf_up[0] != PROTOCOL_VERSION ) + { + printf( ", invalid version %u\n", databuf_up[0] ); + continue; + } + raw_mac_h = *( (uint32_t *)( databuf_up + 4 ) ); + raw_mac_l = *( (uint32_t *)( databuf_up + 8 ) ); + gw_mac = ( (uint64_t)ntohl( raw_mac_h ) << 32 ) + (uint64_t)ntohl( raw_mac_l ); + + /* Interpret gateway command and select ACK to be sent */ + switch( databuf_up[3] ) + { + case PKT_PUSH_DATA: + printf( ", PUSH_DATA from gateway 0x%08X%08X\n", (uint32_t)( gw_mac >> 32 ), (uint32_t)( gw_mac & 0xFFFFFFFF ) ); + ack_command = PKT_PUSH_ACK; + no_ack = false; + if( fwd_uplink == false ) + { + printf( "<- pkt out, PUSH_ACK for host %s (port %s)", host_name, port_name ); + } + else + { + /* Forward uplink if required */ + printf( "<- pkt out, PUSH_ACK for host %s (port %s), FORWARD PUSH_DATA to %s (port %s)", host_name, port_name, serv_addr, serv_port_fwd ); + x = send( sock_fwd, (void *)databuf_up, byte_nb, 0 ); + if( x == -1 ) + { + printf( "ERROR: failed to forward uplink packet - %s (%d)\n", strerror(errno), errno); + } + } + break; + + case PKT_PULL_DATA: + printf( ", PULL_DATA from gateway 0x%08X%08X\n", (uint32_t)( gw_mac >> 32 ), (uint32_t)( gw_mac & 0xFFFFFFFF ) ); + ack_command = PKT_PULL_ACK; + no_ack = false; + printf( "<- pkt out, PULL_ACK for host %s (port %s)", host_name, port_name ); + /* Record who sent the PULL_DATA for the downlink thread to known where to send PULL_RESP */ + memcpy( &dist_addr_down, &dist_addr, sizeof(struct sockaddr_storage) ); + memcpy( &addr_len_down, &addr_len, sizeof(socklen_t) ); + pthread_mutex_lock( &mx_sockaddr ); + sockaddr_valid = true; + pthread_mutex_unlock( &mx_sockaddr ); + break; + + case PKT_TX_ACK: + printf( ", TX_ACK from gateway 0x%08X%08X\n", (uint32_t)( gw_mac >> 32 ), (uint32_t)( gw_mac & 0xFFFFFFFF ) ); + no_ack = true; + break; + + default: + printf( ", unexpected command %u\n", databuf_up[3] ); + continue; + } + + /* Add some artificial latency */ + usleep( 30000 ); /* 30 ms */ + + /* Send acknowledge and check return value */ + if( no_ack == false ) + { + memset( databuf_ack, 0, 4 ); + databuf_ack[0] = PROTOCOL_VERSION; + databuf_ack[1] = databuf_up[1]; + databuf_ack[2] = databuf_up[2]; + databuf_ack[3] = ack_command; + byte_nb = sendto( sock, (void *)databuf_ack, 4, 0, (struct sockaddr *)&dist_addr, addr_len ); + if( byte_nb == -1 ) + { + printf( ", send error:%s\n", strerror( errno ) ); + } + else + { + printf( ", %i bytes sent for ACK\n", byte_nb ); + } + } + + /* Log uplinks to file */ + if( databuf_up[3] == PKT_PUSH_DATA ) + { + if( log_fname != NULL ) + { + if( is_first == true ) + { + fprintf(log_file, "tmst,chan,rfch,freq,mid,stat,modu,datr,bw,codr,rssic,rssis,lsnr,size,data\n"); + is_first = false; + } + log_csv( log_file, &databuf_up[12] ); + } + } + } + + /* Wait for downstream thread to finish */ + pthread_join( thrid_down_rf0, NULL ); + pthread_join( thrid_down_rf1, NULL ); + + printf( "INFO: Exiting uplink logger\n" ); + + /* Close log file */ + if( (log_fname != NULL) && (log_file != NULL) ) + { + fclose( log_file ); + log_file = NULL; + } + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +static void log_csv(FILE * file, uint8_t * buf) +{ + JSON_Object * rxpk = NULL; + JSON_Object * root = NULL; + JSON_Array * rxpk_array = NULL; + JSON_Value * root_val = NULL; + JSON_Value * val = NULL; + int i, j, rxpk_nb, x; + const char * str; /* pointer to sub-strings in the JSON data */ + short x0, x1; + uint8_t payload[255]; + uint8_t size; + + if( file == NULL ) + { + printf("ERROR: no file opened\n"); + return; + } + + /* Parse JSON string */ + root_val = json_parse_string( (const char *)buf ); /* JSON offset */ + root = json_value_get_object( root_val ); + if( root == NULL ) + { + printf( "ERROR: not a valid JSON string\n" ); + json_value_free( root_val ); + return; + } + + /* Get all packets from array */ + rxpk_array = json_object_get_array( root, "rxpk" ); + if( rxpk_array != NULL) + { + rxpk_nb = (int)json_array_get_count( rxpk_array ); + for( i = 0; i < rxpk_nb; i++ ) + { + rxpk = json_array_get_object( rxpk_array, i ); + if( rxpk == NULL) + { + printf("ERROR: failed to get rxpk object\n"); + json_value_free( root_val ); + return; + } + + /* Parse rxpk fields */ + val = json_object_get_value( rxpk, "tmst" ); + if( json_value_get_type( val ) != JSONNumber ) + { + printf( "ERROR: wrong type for tmst\n" ); + json_value_free( root_val ); + return; + } + fprintf(file, "%u", (uint32_t)json_value_get_number( val ) ); + + val = json_object_get_value( rxpk, "chan" ); + if( json_value_get_type( val ) != JSONNumber ) + { + printf( "ERROR: wrong type for chan\n" ); + json_value_free( root_val ); + return; + } + fprintf(file, ",%u", (uint8_t)json_value_get_number( val ) ); + + val = json_object_get_value( rxpk, "rfch" ); + if( json_value_get_type( val ) != JSONNumber ) + { + printf( "ERROR: wrong type for rfch\n" ); + json_value_free( root_val ); + return; + } + fprintf(file, ",%u", (uint8_t)json_value_get_number( val ) ); + + val = json_object_get_value( rxpk, "freq" ); + if( json_value_get_type( val ) != JSONNumber ) + { + printf( "ERROR: wrong type for rfch\n" ); + json_value_free( root_val ); + return; + } + fprintf(file, ",%f", json_value_get_number( val ) ); + + val = json_object_get_value( rxpk, "mid" ); + if( json_value_get_type( val ) != JSONNumber ) + { + printf( "ERROR: wrong type for mid\n" ); + json_value_free( root_val ); + return; + } + fprintf(file, ",%u", (uint8_t)json_value_get_number( val ) ); + + val = json_object_get_value( rxpk, "stat" ); + if( json_value_get_type( val ) != JSONNumber ) + { + printf( "ERROR: wrong type for stat\n" ); + json_value_free( root_val ); + return; + } + fprintf(file, ",%d", (int8_t)json_value_get_number( val ) ); + + val = json_object_get_value( rxpk, "modu" ); + if( json_value_get_type( val ) != JSONString ) + { + printf( "ERROR: wrong type for stat\n" ); + json_value_free( root_val ); + return; + } + str = json_value_get_string( val ); + fprintf(file, ",%s", str ); + if( strcmp( str, "LORA" ) == 0 ) + { + val = json_object_get_value( rxpk, "datr" ); + if( json_value_get_type( val ) != JSONString ) + { + printf( "ERROR: wrong type for datr\n" ); + json_value_free( root_val ); + return; + } + str = json_value_get_string( val ); + x = sscanf( str, "SF%2hdBW%3hd", &x0, &x1 ); + if( x != 2 ) + { + printf( "ERROR: format error in \"rxpk.datr\"\n" ); + json_value_free( root_val ); + return; + } + fprintf(file, ",%d,%d", x0, x1 ); + + val = json_object_get_value( rxpk, "codr" ); + if( json_value_get_type( val ) != JSONString ) + { + printf( "ERROR: wrong type for codr\n" ); + json_value_free( root_val ); + return; + } + fprintf(file, ",%s", json_value_get_string( val ) ); + + val = json_object_get_value( rxpk, "rssi" ); + if( json_value_get_type( val ) != JSONNumber ) + { + printf( "ERROR: wrong type for rssic\n" ); + json_value_free( root_val ); + return; + } + fprintf(file, ",%.1f", json_value_get_number( val ) ); + + val = json_object_get_value( rxpk, "rssis" ); + if( json_value_get_type( val ) != JSONNumber ) + { + printf( "ERROR: wrong type for rssis\n" ); + json_value_free( root_val ); + return; + } + fprintf(file, ",%.1f", json_value_get_number( val ) ); + + val = json_object_get_value( rxpk, "lsnr" ); + if( json_value_get_type( val ) != JSONNumber ) + { + printf( "ERROR: wrong type for lsnr\n" ); + json_value_free( root_val ); + return; + } + fprintf(file, ",%.1f", json_value_get_number( val ) ); + } + else if( strcmp( str, "FSK" ) == 0 ) + { + val = json_object_get_value( rxpk, "datr" ); + if( json_value_get_type( val ) != JSONNumber ) + { + printf( "ERROR: wrong type for datr\n" ); + json_value_free( root_val ); + return; + } + fprintf(file, ",%d,,", (uint32_t)json_value_get_number( val ) ); /* bw,codr fields are left empty */ + + val = json_object_get_value( rxpk, "rssi" ); + if( json_value_get_type( val ) != JSONNumber ) + { + printf( "ERROR: wrong type for rssic\n" ); + json_value_free( root_val ); + return; + } + fprintf(file, ",%.1f,,", json_value_get_number( val ) ); /* rssis,lsnr fields are left empty */ + } + else + { + printf("ERROR: unknown modulation %s\n", str); + json_value_free( root_val ); + return; + } + + val = json_object_get_value( rxpk, "size" ); + if( json_value_get_type( val ) != JSONNumber ) + { + printf( "ERROR: wrong type for size\n" ); + json_value_free( root_val ); + return; + } + size = (uint8_t)json_value_get_number( val ); + fprintf(file, ",%u", size ); + + val = json_object_get_value( rxpk, "data" ); + if( json_value_get_type( val ) != JSONString ) + { + printf( "ERROR: wrong type for data\n" ); + json_value_free( root_val ); + return; + } + str = json_value_get_string( val ); + x = b64_to_bin( str, strlen( str ), payload, sizeof payload ); + if( x != size ) + { + printf( "ERROR: mismatch between .size and .data size once converter to binary\n" ); + json_value_free( root_val ); + return; + } + fprintf(file, "," ); + for( j = 0; j < size; j++ ) + { + fprintf(file, "%02x", payload[j] ); + } + + /* End line */ + fprintf(file, "\n" ); + } + } + + fflush(file); + json_value_free( root_val ); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +static void usage( void ) +{ + printf( "~~~ Available options ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" ); + printf( " -h print this help\n" ); + printf( " -f Target frequency in MHz for RF0,RF1\n" ); + printf( " -j : Number of channels to jump + explicit offset in MHz between channels\n" ); + printf( " -m Modulation [\"LORA\",\"FSK\"] for RF0,RF1\n" ); + printf( " -b LoRa bandwidth in kHz [125, 250, 500]\n" ); + printf( " -s LoRa Spreading Factor [5-12] for RF0,RF1\n" ); + printf( " -c LoRa Coding Rate [\"4/5\", \"4/6\", ...]\n" ); + printf( " -d FSK frequency deviation in kHz [1:250]\n"); + printf( " -q FSK bitrate in kbps [0.5:250]\n"); + printf( " -p RF power (dBm) for RF0,RF1\n" ); + printf( " -r Preamble size (symbols, [6..65535]) for RF0,RF1\n" ); + printf( " -z Payload size (bytes, [0..255]) for RF0,RF1\n" ); + printf( " -i Set inverted polarity true\n" ); + printf( " -t Number of milliseconds between two downlinks for RF0,RF1\n" ); + printf( " -x Number of downlinks to be sent for RF0,RF1\n" ); + printf( " -P UDP port of the Packet Forwarder\n" ); + printf( " -A IP address to be used for uplink forwarding (optional)\n" ); + printf( " -F UDP port to be used for uplink forwarding (optional)\n" ); + printf( " -l uplink logging CSV filename (optional)\n" ); + printf( " -B Bypass downlink, for uplink logging only (optional)\n" ); + printf( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" ); + printf( "~~~ Examples ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" ); + printf( " Log uplinks into a CSV file, no downlink:\n" ); + printf( " ./net_downlink -P 1730 -l log.csv\n" ); + printf( " Send downlinks on RF chain 0 only:\n" ); + printf( " ./net_downlink -f 865.1 -s 7 -b 125 -r 8 -t 500 -x 10 -P 1730\n" ); + printf( " Send downlinks on RF chain 1 only:\n" ); + printf( " ./net_downlink -f 865.1,865.3 -s 7,8 -t 1000,1000 -x 0,10 -P 1730\n" ); + printf( " Send downlinks on both RF chain 0 and 1:\n" ); + printf( " ./net_downlink -f 865.1,865.9 -s 10,7 -t 500,1000 -x 5,10 -P 1730\n" ); + printf( " Trigger continuous TX on both RF chain 0 and 1:\n" ); + printf( " ./net_downlink -f 865.1,865.9 -s 11,12 -x 1,1 -r 65535,65535 -P 1730\n" ); + printf( " Log uplinks into CSV file while continuous TX is running (full_duplex testing):\n" ); + printf( " ./net_downlink -f 864.5 -s 12 -x 1 -r 65535 -P 1730 -l log.csv\n" ); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +static void sig_handler( int sigio ) +{ + if( sigio == SIGQUIT ) + { + quit_sig = 1; + } + else if( ( sigio == SIGINT ) || ( sigio == SIGTERM ) ) + { + exit_sig = 1; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void prepare_downlink_json( const thread_params_t * params, uint8_t rf_chain, uint32_t pkt_sent, JSON_Value * root_val ) +{ + int j; + + JSON_Value *val = NULL; + JSON_Object *root_obj = NULL; + JSON_Object *obj = NULL; + + char datarate_string[16]; + uint8_t payload[255]; + uint8_t payload_b64[341]; + double freq; + uint8_t sf; + int8_t rf_pwr; + uint16_t pream_sz; + const char *modulation = (rf_chain == 0) ? params->modulation_rf0 : params->modulation_rf1; + + memset( datarate_string, 0, sizeof datarate_string ); + memset( payload, 0, sizeof payload ); + memset( payload_b64, 0, sizeof payload_b64 ); + + root_obj = json_value_get_object( root_val ); + if( root_obj == NULL ) + { + printf( "ERROR: failed to get JSON root object\n" ); + } + else + { + json_object_set_value( root_obj, "txpk", json_value_init_object( ) ); + obj = json_object_get_object( root_obj, "txpk" ); + + /* Set downlink parameters */ + json_object_set_boolean( obj, "imme", true ); + freq = params->freq_mhz[rf_chain] + ((pkt_sent % params->freq_nb) * params->freq_step); + json_object_set_number( obj, "freq", freq ); + json_object_set_number( obj, "rfch", rf_chain ); + rf_pwr = params->rf_power[rf_chain]; + json_object_set_number( obj, "powe", rf_pwr ); + if( strncmp( modulation, "LORA", 4 ) == 0 ) + { + json_object_set_string( obj, "modu", "LORA" ); + sf = params->spread_factor[rf_chain]; + sprintf( datarate_string, "SF%uBW%u", sf, params->bandwidth_khz); + json_object_set_string( obj, "datr", datarate_string ); + json_object_set_string( obj, "codr", params->coding_rate ); + } else if( strncmp( modulation, "FSK", 3 ) == 0 ) { + json_object_set_string( obj, "modu", "FSK" ); + json_object_set_number( obj, "datr", params->br_kbps * 1E3 ); + json_object_set_number( obj, "fdev", params->fdev_khz * 1E3 ); + } else { + printf( "ERROR: wrong modulation\n" ); + } + json_object_set_boolean( obj, "ipol", params->ipol ); + pream_sz = params->preamb_size[rf_chain]; + json_object_set_number( obj, "prea", pream_sz ); + json_object_set_boolean( obj, "ncrc", true ); + json_object_set_number( obj, "size", params->pl_size[rf_chain] ); + + /* Fill last bytes of payload with downlink counter (32 bits) */ + for( j = 0; j < params->pl_size[rf_chain]; j++ ) + { + payload[params->pl_size[rf_chain] - ( j + 1 )] = (uint8_t)( (pkt_sent >> (j * 8)) & 0xFF ); + } + /* Convert payload to base64 */ + j = bin_to_b64( payload, params->pl_size[rf_chain], (char *)(payload_b64), 341 ); /* 255 bytes = 340 chars in b64 + null char */ + if( j >= 0 ) + { + json_object_set_string( obj, "data", (char *)(payload_b64) ); + } + else + { + printf( "ERROR: failed to convert payload to base64 string\n" ); + } + + /* Free memory */ + json_value_free( val ); + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +static void * thread_down_rf0( const void * arg ) +{ + int x; + int byte_nb; + const thread_params_t *params = ( (thread_params_t*)arg ); + + /* Server socket creation */ + char host_name[64]; + char port_name[64]; + + /* JSON variables */ + JSON_Value *root_val = NULL; + char *serialized_string = NULL; + + /* Downstream data variables */ + uint8_t databuf_down[4096]; + uint32_t nb_loop; + uint32_t pkt_sent = 0; + + /* Global loop is the max loop defined */ + nb_loop = params->nb_loop[0]; + while( !exit_sig && !quit_sig && (pkt_sent < nb_loop) && (nb_loop > 0) ) + { + /* Wait for socket address to be valid */ + pthread_mutex_lock( &mx_sockaddr ); + if( sockaddr_valid == false ) + { + pthread_mutex_unlock( &mx_sockaddr ); + printf( "Waiting for socket to be ready...\n" ); + usleep( 500000 ); /* 500 ms */ + continue; + } + pthread_mutex_unlock( &mx_sockaddr ); + + /* Display info about the sender */ + x = getnameinfo( (struct sockaddr *)&dist_addr_down, addr_len_down, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST ); + if( x == -1 ) + { + printf( "ERROR: getnameinfo returned %s \n", gai_strerror( x ) ); + usleep( 10000); /* 10 ms */ + continue; + } + + /* Prepare JSON object to be sent */ + root_val = json_value_init_object( ); + if( root_val == NULL ) + { + printf( "ERROR: failed to initialize JSON root object\n" ); + } + else + { + /* Prepare the txpk JSON object */ + prepare_downlink_json( params, 0, pkt_sent, root_val ); + + /* Convert JSON object to string */ + serialized_string = json_serialize_to_string( root_val ); + printf( "%s\n", serialized_string ); + + /* Send JSON string to socket */ + memset( databuf_down, 0, 4096 ); + databuf_down[0] = PROTOCOL_VERSION; + databuf_down[1] = 0; + databuf_down[2] = 0; + databuf_down[3] = PKT_PULL_RESP; + memcpy( &databuf_down[4], (uint8_t*)serialized_string, strlen(serialized_string) ); + byte_nb = sendto( params->sock, (void *)databuf_down, strlen(serialized_string) + 4, 0, (struct sockaddr *)&dist_addr_down, addr_len_down ); + if( byte_nb == -1 ) + { + printf( "ERROR: failed to send downlink to socket - %s\n", strerror( errno ) ); + } + else + { + printf( "<- pkt out, PULL_RESP for host %s (port %s), %i bytes sent for downlink (%d)\n", host_name, port_name, byte_nb, pkt_sent ); + } + + /* free JSON memory */ + json_free_serialized_string( serialized_string ); + json_value_free( root_val ); + } + + /* One more downlink sent */ + pkt_sent += 1; + /* Wait before sending next downlink */ + usleep( params->delay_ms[0] * 1E3 ); + } + + /* Exit */ + printf( "\nINFO: End of downstream thread for RF 0\n" ); + return NULL; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +static void * thread_down_rf1( const void * arg ) +{ + int x; + int byte_nb; + const thread_params_t *params = ( (thread_params_t*)arg ); + + /* Server socket creation */ + char host_name[64]; + char port_name[64]; + + /* JSON variables */ + JSON_Value *root_val = NULL; + char *serialized_string = NULL; + + /* Downstream data variables */ + uint8_t databuf_down[4096]; + uint32_t nb_loop; + uint32_t pkt_sent = 0; + + /* Global loop is the max loop defined */ + nb_loop = params->nb_loop[1]; + while( !exit_sig && !quit_sig && (pkt_sent < nb_loop) && (nb_loop > 0) ) + { + /* Wait for socket address to be valid */ + pthread_mutex_lock( &mx_sockaddr ); + if( sockaddr_valid == false ) + { + pthread_mutex_unlock( &mx_sockaddr ); + printf( "Waiting for socket to be ready...\n" ); + usleep( 500000 ); /* 500 ms */ + continue; + } + pthread_mutex_unlock( &mx_sockaddr ); + + /* Display info about the sender */ + x = getnameinfo( (struct sockaddr *)&dist_addr_down, addr_len_down, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST ); + if( x == -1 ) + { + printf( "ERROR: getnameinfo returned %s \n", gai_strerror( x ) ); + usleep( 10000); /* 10 ms */ + continue; + } + + /* Prepare JSON object to be sent */ + root_val = json_value_init_object( ); + if( root_val == NULL ) + { + printf( "ERROR: failed to initialize JSON root object\n" ); + } + else + { + /* Prepare the txpk JSON object */ + prepare_downlink_json( params, 1, pkt_sent, root_val ); + + /* Convert JSON object to string */ + serialized_string = json_serialize_to_string( root_val ); + printf( "%s\n", serialized_string ); + + /* Send JSON string to socket */ + memset( databuf_down, 0, 4096 ); + databuf_down[0] = PROTOCOL_VERSION; + databuf_down[1] = 0; + databuf_down[2] = 0; + databuf_down[3] = PKT_PULL_RESP; + memcpy( &databuf_down[4], (uint8_t*)serialized_string, strlen(serialized_string) ); + byte_nb = sendto( params->sock, (void *)databuf_down, strlen(serialized_string) + 4, 0, (struct sockaddr *)&dist_addr_down, addr_len_down ); + if( byte_nb == -1 ) + { + printf( "ERROR: failed to send downlink to socket - %s\n", strerror( errno ) ); + } + else + { + printf( "<- pkt out, PULL_RESP for host %s (port %s), %i bytes sent for downlink (%d)\n", host_name, port_name, byte_nb, pkt_sent ); + } + + /* free JSON memory */ + json_free_serialized_string( serialized_string ); + json_value_free( root_val ); + } + + /* One more downlink sent */ + pkt_sent += 1; + /* Wait before sending next downlink */ + usleep( params->delay_ms[1] * 1E3 ); + } + + /* Exit */ + printf( "\nINFO: End of downstream thread for RF1\n" ); + return NULL; +} + +/* --- EOF ------------------------------------------------------------------ */