diff options
| -rw-r--r-- | README | 22 | ||||
| -rw-r--r-- | libloragw/Makefile | 16 | ||||
| -rw-r--r-- | libloragw/README | 23 | ||||
| -rw-r--r-- | libloragw/VERSION | 4 | ||||
| -rw-r--r-- | libloragw/doc/CHANGELOG.TXT | 8 | ||||
| -rw-r--r-- | libloragw/doc/MANUAL.TXT | 59 | ||||
| -rw-r--r-- | libloragw/inc/loragw_aux.h | 2 | ||||
| -rw-r--r-- | libloragw/inc/loragw_gps.h | 179 | ||||
| -rw-r--r-- | libloragw/inc/loragw_hal.h | 7 | ||||
| -rw-r--r-- | libloragw/library.cfg | 1 | ||||
| -rw-r--r-- | libloragw/src/loragw_gps.c | 573 | ||||
| -rw-r--r-- | libloragw/src/loragw_hal.c | 52 | ||||
| -rw-r--r-- | libloragw/tst/test_loragw_gps.c | 177 |
13 files changed, 1071 insertions, 52 deletions
@@ -50,5 +50,27 @@ contain little information, on no protocol (ie. MAC address) information but can be used to assess the functionality of a gateway downlink using other gateways as receivers. +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/libloragw/Makefile b/libloragw/Makefile index db45383..2b91fa8 100644 --- a/libloragw/Makefile +++ b/libloragw/Makefile @@ -17,7 +17,7 @@ endif # general build targets -all: libloragw.a test_loragw_spi test_loragw_reg test_loragw_hal +all: libloragw.a test_loragw_spi test_loragw_reg test_loragw_hal test_loragw_gps clean: rm -f *.a @@ -43,8 +43,8 @@ endif # static library -libloragw.a: obj/loragw_hal.o obj/loragw_reg.o obj/loragw_spi.o obj/loragw_aux.o - $(CROSS_COMPILE)ar rcs libloragw.a obj/loragw_hal.o obj/loragw_reg.o obj/loragw_spi.o obj/loragw_aux.o +libloragw.a: obj/loragw_hal.o obj/loragw_gps.o obj/loragw_reg.o obj/loragw_spi.o obj/loragw_aux.o + $(CROSS_COMPILE)ar rcs libloragw.a obj/loragw_hal.o obj/loragw_gps.o obj/loragw_reg.o obj/loragw_spi.o obj/loragw_aux.o # library module target @@ -62,9 +62,12 @@ endif obj/loragw_reg.o: .conf_ok src/loragw_reg.c inc/loragw_reg.h inc/loragw_spi.h $(CROSS_COMPILE)$(CC) -c $(C99FLAGS) src/loragw_reg.c -o obj/loragw_reg.o $(FLAG_REG) -obj/loragw_hal.o: .conf_ok VERSION src/loragw_hal.c src/arb_fw.var src/agc_fw.var inc/loragw_hal.h inc/loragw_reg.h inc/loragw_spi.h inc/loragw_aux.h +obj/loragw_hal.o: .conf_ok src/loragw_hal.c inc/loragw_hal.h inc/loragw_reg.h inc/loragw_aux.h VERSION src/arb_fw.var src/agc_fw.var $(CROSS_COMPILE)$(CC) -c $(C99FLAGS) src/loragw_hal.c -o obj/loragw_hal.o -D LGW_PHY="\"$(LGW_PHY)\"" $(FLAG_HAL) +obj/loragw_gps.o: .conf_ok src/loragw_gps.c inc/loragw_gps.h + $(CROSS_COMPILE)$(CC) -c $(C99FLAGS) src/loragw_gps.c -o obj/loragw_gps.o $(FLAG_GPS) + # test programs test_loragw_spi: tst/test_loragw_spi.c obj/loragw_spi.o @@ -72,7 +75,10 @@ test_loragw_spi: tst/test_loragw_spi.c obj/loragw_spi.o test_loragw_reg: tst/test_loragw_reg.c obj/loragw_reg.o obj/loragw_spi.o $(CROSS_COMPILE)$(CC) $(C99FLAGS) tst/test_loragw_reg.c obj/loragw_reg.o obj/loragw_spi.o -o test_loragw_reg $(LDFLAGS) - + test_loragw_hal: tst/test_loragw_hal.c obj/loragw_hal.o obj/loragw_reg.o obj/loragw_spi.o obj/loragw_aux.o $(CROSS_COMPILE)$(CC) $(C99FLAGS) tst/test_loragw_hal.c obj/loragw_hal.o obj/loragw_reg.o obj/loragw_spi.o obj/loragw_aux.o -o test_loragw_hal $(LDFLAGS) +test_loragw_gps: tst/test_loragw_gps.c obj/loragw_gps.o obj/loragw_hal.o obj/loragw_reg.o obj/loragw_spi.o obj/loragw_aux.o + $(CROSS_COMPILE)$(CC) $(C99FLAGS) tst/test_loragw_gps.c obj/loragw_gps.o obj/loragw_hal.o obj/loragw_reg.o obj/loragw_spi.o obj/loragw_aux.o -o test_loragw_gps $(LDFLAGS) + diff --git a/libloragw/README b/libloragw/README index da4cd4d..821eff6 100644 --- a/libloragw/README +++ b/libloragw/README @@ -29,27 +29,4 @@ Contain library C sources. Contain the C sources for test programs to validate SPI link, register access and hardware functionality. -2. 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/libloragw/VERSION b/libloragw/VERSION index 3440bb1..b44ab5c 100644 --- a/libloragw/VERSION +++ b/libloragw/VERSION @@ -1,8 +1,8 @@ /* Software library version: */ -#define VERSION_LIBRARY "1.1.0" +#define VERSION_LIBRARY "1.2.0" /* API version */ -#define VERSION_API "1.0" +#define VERSION_API "1" /* Accepted value of CHIP_ID (SPI registers) must match reg default value in loragw_reg.c */ #define ACCEPT_CHIP_ID "1" diff --git a/libloragw/doc/CHANGELOG.TXT b/libloragw/doc/CHANGELOG.TXT index 4a7d489..bdaab2d 100644 --- a/libloragw/doc/CHANGELOG.TXT +++ b/libloragw/doc/CHANGELOG.TXT @@ -1,6 +1,14 @@ Lora Gateway HAL changelog ========================== + v1.2.0 +--------------------- + + * Added feature: new GPS module in the library for synchronization + * Removed feature: no more missed deadline detection in TX because of incompatibility with GPS + * Added documentation for GPS and legal notice + * Added flags in Makefiles for easier cross-compilation + v1.1.0 --------------------- diff --git a/libloragw/doc/MANUAL.TXT b/libloragw/doc/MANUAL.TXT index a086761..7fdefeb 100644 --- a/libloragw/doc/MANUAL.TXT +++ b/libloragw/doc/MANUAL.TXT @@ -22,14 +22,15 @@ used to send and receive packets wirelessly using Lora or FSK modulations. 2. Components of the library ---------------------------- -The library is composed of 4 modules: +The library is composed of 5 modules: * loragw_hal * loragw_reg * loragw_spi * loragw_aux +* loragw_gps -The library also contains 3 test program to demonstrate code use and check +The library also contains 4 test programs to demonstrate code use and check functionality. ### 2.1. loragw_hal ### @@ -113,6 +114,33 @@ 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 UTC 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 NMEA sentences (using lgw_parse_nmea) + +And each time an RMC sentence has been received: +* get the concentrator timestamp (using lgw_get_trigcnt, mutex needed to + protect access to the concentrator) +* get the UTC time contained in the NMEA sentence (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 UTC time (using lgw_cnt2utc) or +the other way around (using lgw_utc2cnt). 3. Software dependencies ------------------------ @@ -167,6 +195,33 @@ Edit library.cfg to chose which SPI physical interface you want to use. 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. + +The GPS receiver __MUST__ send RMC NMEA sentences (starting with "$G<any +character>RMC") shortly after sending a PPS pulse on to allow internal +concentrator timestamps to be converted to absolute UTC time. +If the GPS receiver sends a GGA sentence, the gateway 3D position will also be +available. + +The PPS pulse must be sent to the pin 22 of connector CONN400 on the Semtech +FPGA-based nano-concentrator board. Ground is available on pins 2 and 12 of +the same connector. +The pin is loaded by an FPGA internal pull-down, and the signal level coming +in the FPGA must be 3.3V. +Timing is captured on the rising edge of the PPS signal. 5. Usage -------- diff --git a/libloragw/inc/loragw_aux.h b/libloragw/inc/loragw_aux.h index daaf3e3..8f485b4 100644 --- a/libloragw/inc/loragw_aux.h +++ b/libloragw/inc/loragw_aux.h @@ -7,7 +7,7 @@ ©2013 Semtech-Cycleo Description: - Lora gateway auxiliary functions + Lora gateway library common auxiliary functions License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont diff --git a/libloragw/inc/loragw_gps.h b/libloragw/inc/loragw_gps.h new file mode 100644 index 0000000..3884fd5 --- /dev/null +++ b/libloragw/inc/loragw_gps.h @@ -0,0 +1,179 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + ©2013 Semtech-Cycleo + +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 +Maintainer: Sylvain Miermont +*/ + + +#ifndef _LORAGW_GPS_H +#define _LORAGW_GPS_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +/* fix an issue between POSIX and C99 */ +#if __STDC_VERSION__ >= 199901L + #define _XOPEN_SOURCE 600 +#else + #define _XOPEN_SOURCE 500 +#endif + +#include <stdint.h> /* C99 types */ +#include <time.h> /* time library */ +#include <termios.h> /* speed_t */ + +/* -------------------------------------------------------------------------- */ +/* --- 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) */ + double xtal_err; /*!> clock error estimation (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 */ + /* 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_POSITION, /*!> */ + UBX_TIME /*!> */ +}; + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +#define LGW_GPS_SUCCESS 0 +#define LGW_GPS_ERROR -1 + +/* -------------------------------------------------------------------------- */ +/* --- 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 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(char* serial_buff, int buff_size); + +/** +@brief Get the GPS solution (space & time) for the gateway + +@param utc pointer to store UTC 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 parsing function +lgw_parse_nmea. 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 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 coord_s* loc, struct coord_s* err); + +/** +@brief Take a timestamp and UTC time and refresh reference for time conversion + +@param ref pointer to time reference structure +@param old_ref previous time reference (NULL for initial fix) +@param utc UTC time, with ns precision (leap seconds are ignored) +@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); + +/** +@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 a Lora gateway +@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 a Lora gateway +@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); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_hal.h b/libloragw/inc/loragw_hal.h index 41d1d98..022313f 100644 --- a/libloragw/inc/loragw_hal.h +++ b/libloragw/inc/loragw_hal.h @@ -278,6 +278,13 @@ int lgw_send(struct lgw_pkt_tx_s pkt_data); int lgw_status(uint8_t select, uint8_t *code); /** +@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 Allow user to check the version/options of the library once compiled @return pointer on a human-readable null terminated string */ diff --git a/libloragw/library.cfg b/libloragw/library.cfg index 8e3ba0e..0e8c742 100644 --- a/libloragw/library.cfg +++ b/libloragw/library.cfg @@ -15,6 +15,7 @@ FLAG_AUX= -D DEBUG_AUX=0 FLAG_SPI= -D DEBUG_SPI=0 FLAG_REG= -D DEBUG_REG=0 FLAG_HAL= -D DEBUG_HAL=0 +FLAG_GPS= -D DEBUG_GPS=0 # The flags bellow define which physical link to the nano board will be used # Pick one and comment the other(s) diff --git a/libloragw/src/loragw_gps.c b/libloragw/src/loragw_gps.c new file mode 100644 index 0000000..84db3b8 --- /dev/null +++ b/libloragw/src/loragw_gps.c @@ -0,0 +1,573 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + ©2013 Semtech-Cycleo + +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 +Maintainer: Sylvain Miermont +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +/* fix an issue between POSIX and C99 */ +#if __STDC_VERSION__ >= 199901L + #define _XOPEN_SOURCE 600 +#else + #define _XOPEN_SOURCE 500 +#endif + +#include <stdint.h> /* C99 types */ +#include <stdbool.h> /* bool type */ +#include <stdio.h> /* printf fprintf */ +#include <string.h> /* memcpy */ + +#include <time.h> /* struct timespec */ +#include <fcntl.h> /* open */ +#include <termios.h> /* tcflush */ +#include <math.h> /* modf */ + +#include <stdlib.h> // DEBUG + +#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_ARRAY(a,b,c) for(a=0;a<b;++a) fprintf(stderr,"%x.",c[a]);fprintf(stderr,"end\n") + #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_GPS_ERROR;} +#else + #define DEBUG_MSG(args...) + #define DEBUG_ARRAY(a,b,c) for(a=0;a!=0;){} + #define CHECK_NULL(a) if(a==NULL){return LGW_GPS_ERROR;} +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define TS_CPS 1E6 /* count-per-second of the timestamp counter */ +#define PLUS_10PPM 1.00001 +#define MINUS_10PPM 0.99999 +#define DEFAULT_BAUDRATE B9600 + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + + +/* result of the NMEA parsing */ +static short gps_yea = 0; /* year (2 or 4 digits) */ +static short gps_mon = 0; /* month (1-12) */ +static short gps_day = 0; /* day of the month (1-31) */ +static short gps_hou = 0; /* hours (0-23) */ +static short gps_min = 0; /* minutes (0-59) */ +static short gps_sec = 0; /* seconds (0-60)(60 is for leap second) */ +static float gps_fra = 0.0; /* fractions of seconds (<1) */ +static bool gps_time_ok = false; + +static short gps_dla = 0; /* degrees of latitude */ +static double gps_mla = 0.0; /* minutes of latitude */ +static char gps_ola = 0; /* orientation (N-S) of latitude */ +static short gps_dlo = 0; /* degrees of longitude */ +static double gps_mlo = 0.0; /* minutes of longitude */ +static char gps_olo = 0; /* orientation (E-W) of longitude */ +static short gps_alt = 0; /* altitude */ +static bool gps_pos_ok = false; + +static char gps_mod = 'N'; /* GPS mode (N no fix, A autonomous, D differential) */ +static short gps_sat = 0; /* number of satellites used for fix */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ + +int nmea_checksum(char *nmea_string, int buff_size, char *checksum); + +char nibble_to_hexchar(uint8_t a); + +bool validate_nmea_checksum(char *serial_buff, int buff_size); + +bool match_label(char *s, char *label, int size, char wildcard); + +int str_chop(char *s, int buff_size, char separator, int *idx_ary, int max_idx); + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +/* +Calculate the checksum for a NMEA string +Skip the first '$' if necessary and calculate checksum until '*' character is +reached (or buff_size exceeded). +Checksum must point to a 2-byte (or more) char array. +Return position of the checksum in the string +*/ +int nmea_checksum(char *nmea_string, int buff_size, char *checksum) { + int i = 0; + uint8_t check_num = 0; + + /* check input parameters */ + if ((nmea_string == NULL) || (checksum == NULL) || (buff_size <= 1)) { + DEBUG_MSG("Invalid parameters for nmea_checksum\n"); + return -1; + } + + /* skip the first '$' if necessary */ + if (nmea_string[i] == '$') { + i += 1; + } + + /* xor until '*' or max length is reached */ + while (nmea_string[i] != '*') { + check_num ^= nmea_string[i]; + i += 1; + if (i >= 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; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +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 +*/ +bool validate_nmea_checksum(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 +*/ +bool match_label(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_familly, 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 */ + + /* 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_familly != NULL) { + DEBUG_MSG("WARNING: gps_familly parameter ignored for now\n"); // TODO + } + + /* 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; + } + + /* update baudrates */ + cfsetispeed(&ttyopt, DEFAULT_BAUDRATE); + cfsetospeed(&ttyopt, DEFAULT_BAUDRATE); + + /* update terminal parameters */ + 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 */ + ttyopt.c_iflag |= IGNPAR; /* ignore bytes with parity errors */ + ttyopt.c_iflag |= ICRNL; /* map CR to NL */ + ttyopt.c_iflag |= IGNCR; /* Ignore carriage return on input */ + ttyopt.c_lflag |= ICANON; /* enable canonical input */ + + /* 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); + + /* initialize global variables */ + gps_time_ok = false; + gps_pos_ok = false; + gps_mod = 'N'; + + return LGW_GPS_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +enum gps_msg lgw_parse_nmea(char *serial_buff, int buff_size) { + int i, j, k; + int str_index[30]; /* string index from the string chopping */ + int nb_fields; /* number of strings detected by string chopping */ + + /* check input parameters */ + if (serial_buff == NULL) { + return UNKNOWN; + } + + /* display received serial data and checksum */ + DEBUG_MSG("Note: parsing NMEA frame> %s", serial_buff); + + /* 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 (match_label(serial_buff, "$G?RMC", 6, '?')) { + /* + NMEA sentence format: $xxRMC,time,status,lat,NS,long,EW,spd,cog,date,mv,mvEW,posMode*cs<CR><LF> + 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 + */ + if (!validate_nmea_checksum(serial_buff, buff_size)) { + DEBUG_MSG("Warning: invalid RMC sentence (bad checksum)\n"); + return INVALID; + } + nb_fields = str_chop(serial_buff, buff_size, ',', str_index, ARRAY_SIZE(str_index)); + if (nb_fields != 13) { + DEBUG_MSG("Warning: invalid RMC sentence (number of fields)\n"); + return INVALID; + } + /* parse GPS status */ + gps_mod = *(serial_buff + 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(serial_buff + str_index[1], "%2hd%2hd%2hd%4f", &gps_hou, &gps_min, &gps_sec, &gps_fra); + j = sscanf(serial_buff + 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<CR><LF> + Valid fix: $GPGGA,092725.00,4717.11399,N,00833.91590,E,1,08,1.01,499.6,M,48.0,M,,*5B + */ + if (!validate_nmea_checksum(serial_buff, buff_size)) { + DEBUG_MSG("Warning: invalid GGA sentence (bad checksum)\n"); + return INVALID; + } + nb_fields = str_chop(serial_buff, buff_size, ',', str_index, ARRAY_SIZE(str_index)); + if (nb_fields != 15) { + DEBUG_MSG("Warning: invalid GGA sentence (number of fields)\n"); + return INVALID; + } + /* parse number of satellites used for fix */ + sscanf(serial_buff + str_index[7], "%hd", &gps_sat); + /* parse 3D coordinates */ + i = sscanf(serial_buff + str_index[2], "%2hd%10lf", &gps_dla, &gps_mla); + gps_ola = *(serial_buff + str_index[3]); + j = sscanf(serial_buff + str_index[4], "%3hd%10lf", &gps_dlo, &gps_mlo); + gps_olo = *(serial_buff + str_index[5]); + k = sscanf(serial_buff + 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 INVALID; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_gps_get(struct timespec *utc, struct coord_s *loc, struct coord_s *err) { + struct tm x; + time_t y; + + 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; + } |
