summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSylvain Miermont <smiermont@semtech.com>2013-12-19 17:41:02 +0100
committerSylvain Miermont <smiermont@semtech.com>2013-12-19 17:41:02 +0100
commitf9203f1a4a9b635cbe96670ed5a5e5309bb10697 (patch)
tree1c57c1cc7d61a52e02b0948e36930639f1b52583
parentb665027754e4a050b5c3ea2da999dce8bf3ab710 (diff)
downloadlora_gateway-f9203f1a4a9b635cbe96670ed5a5e5309bb10697.tar.gz
lora_gateway-f9203f1a4a9b635cbe96670ed5a5e5309bb10697.tar.bz2
lora_gateway-f9203f1a4a9b635cbe96670ed5a5e5309bb10697.zip
v1.2.0v1.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
-rw-r--r--README22
-rw-r--r--libloragw/Makefile16
-rw-r--r--libloragw/README23
-rw-r--r--libloragw/VERSION4
-rw-r--r--libloragw/doc/CHANGELOG.TXT8
-rw-r--r--libloragw/doc/MANUAL.TXT59
-rw-r--r--libloragw/inc/loragw_aux.h2
-rw-r--r--libloragw/inc/loragw_gps.h179
-rw-r--r--libloragw/inc/loragw_hal.h7
-rw-r--r--libloragw/library.cfg1
-rw-r--r--libloragw/src/loragw_gps.c573
-rw-r--r--libloragw/src/loragw_hal.c52
-rw-r--r--libloragw/tst/test_loragw_gps.c177
13 files changed, 1071 insertions, 52 deletions
diff --git a/README b/README
index 52f7ab2..7ab10c0 100644
--- a/README
+++ b/README
@@ -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;
+ }
+ 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);
+ 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 (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) {
+ 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 = (count_us - ref->count_us) / TS_CPS; /* uncorrected by xtal_err */
+ utc_diff = (utc.tv_sec - (ref->utc).tv_sec) + 1E-9 * (utc.tv_nsec - (ref->utc).tv_nsec);
+ if (utc_diff == 0.0) { // prevent divide by zero
+ DEBUG_MSG("ERROR: ATTEMPT TO DIVIDE BY ZERO\n");
+ return LGW_GPS_ERROR;
+ }
+
+ /* detect aberrant points by measuring if slope limits are exceeded */
+ 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;
+ }
+
+ /* 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 = utc;
+ ref->xtal_err = (0.9 * ref->xtal_err) + (0.1 * slope); /* 10% smoothing factor */
+ 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 = utc;
+ /* 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;
+}
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/src/loragw_hal.c b/libloragw/src/loragw_hal.c
index c2329fc..4319d5e 100644
--- a/libloragw/src/loragw_hal.c
+++ b/libloragw/src/loragw_hal.c
@@ -296,7 +296,7 @@ int setup_sx1257(uint8_t rf_chain, uint32_t freq_hz) {
DEBUG_MSG("ERROR: INVALID RF_CHAIN\n");
return -1;
}
-
+
sx125x_write(rf_chain, 0x10, SX125x_TX_DAC_CLK_SEL + 2); /* Enable 'clock out' for both radios */
sx125x_write(rf_chain, 0x26, 0X2D); /* Disable gm of oscillator block */
@@ -669,7 +669,9 @@ int lgw_start(void) {
/* configure Lora 'multi' demodulators aka. Lora 'sensor' channels (IF0-3) */
j = 0;
- for(i=0;i<=7;++i) j += (if_rf_chain[i] == 1 ? 1 << i : 0); /* transform bool array into binary word */
+ for(i=0; i<=7; ++i) {
+ j += (if_rf_chain[i] == 1 ? 1 << i : 0); /* transform bool array into binary word */
+ }
lgw_reg_w(LGW_RADIO_SELECT, j); /* IF mapping to radio A/B (per bit, 0=A, 1=B) */
lgw_reg_w(LGW_IF_FREQ_0, IF_HZ_TO_REG(if_freq[0])); /* default -384 */
@@ -694,7 +696,9 @@ int lgw_start(void) {
case BW_125KHZ: lgw_reg_w(LGW_MBWSSF_MODEM_BW,0); break;
case BW_250KHZ: lgw_reg_w(LGW_MBWSSF_MODEM_BW,1); break;
case BW_500KHZ: lgw_reg_w(LGW_MBWSSF_MODEM_BW,2); break;
- default: DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", lora_rx_bw); return LGW_HAL_ERROR;
+ default:
+ DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", lora_rx_bw);
+ return LGW_HAL_ERROR;
}
switch(lora_rx_sf) {
case DR_LORA_SF7: lgw_reg_w(LGW_MBWSSF_RATE_SF,7); break;
@@ -703,7 +707,9 @@ int lgw_start(void) {
case DR_LORA_SF10: lgw_reg_w(LGW_MBWSSF_RATE_SF,10); break;
case DR_LORA_SF11: lgw_reg_w(LGW_MBWSSF_RATE_SF,11); break;
case DR_LORA_SF12: lgw_reg_w(LGW_MBWSSF_RATE_SF,12); break;
- default: DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", lora_rx_sf); return LGW_HAL_ERROR;
+ default:
+ DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", lora_rx_sf);
+ return LGW_HAL_ERROR;
}
lgw_reg_w(LGW_MBWSSF_PPM_OFFSET, lora_rx_ppm_offset); /* default 0 */
lgw_reg_w(LGW_MBWSSF_MODEM_ENABLE, 1); /* default 0 */
@@ -730,6 +736,9 @@ int lgw_start(void) {
lgw_reg_w(LGW_MCU_RST_0, 0);
lgw_reg_w(LGW_MCU_RST_1, 0);
+ /* enable GPS event capture */
+ lgw_reg_w(LGW_GPS_EN,1);
+
/* enable LEDs */
lgw_reg_w(LGW_GPIO_MODE,31);
// lgw_reg_w(LGW_GPIO_SELECT_OUTPUT,0); /* default 0 */
@@ -786,7 +795,6 @@ int lgw_receive(uint8_t max_pkt, struct lgw_pkt_rx_s *pkt_data) {
/* how many packets are in the RX buffer ? Break if zero */
if (buff[0] == 0) {
- DEBUG_MSG("Note: RX packet buffer empty, receive function returning nothing\n");
break; /* no more packets to fetch, exit out of FOR loop */
}
@@ -949,8 +957,6 @@ int lgw_send(struct lgw_pkt_tx_s pkt_data) {
int transfer_size = 0; /* data to transfer from host to TX databuffer */
int payload_offset = 0; /* start of the payload content in the databuffer */
uint8_t power_nibble = 0; /* 4-bit value to set the firmware TX power */
- uint32_t current_tstamp; /* current timestamp, to check for missed TX deadlines */
- uint32_t deadline_tstamp; /* packet must be scheduled before that timestamp value is reached */
/* check if the gateway is running */
if (lgw_is_started == false) {
@@ -1023,11 +1029,6 @@ int lgw_send(struct lgw_pkt_tx_s pkt_data) {
}
// TODO: implement LUT in the firmware and matched value in the HAL
- /* reset TX command flags */
- lgw_reg_w(LGW_TX_TRIG_IMMEDIATE, 0);
- lgw_reg_w(LGW_TX_TRIG_DELAYED, 0);
- lgw_reg_w(LGW_TX_TRIG_GPS, 0);
-
/* fixed metadata, useful payload and misc metadata compositing */
transfer_size = TX_METADATA_NB + pkt_data.size; /* */
payload_offset = TX_METADATA_NB; /* start the payload just after the metadata */
@@ -1148,6 +1149,11 @@ int lgw_send(struct lgw_pkt_tx_s pkt_data) {
/* copy payload from user struct to buffer containing metadata */
memcpy((void *)(buff + payload_offset), (void *)(pkt_data.payload), pkt_data.size);
+ /* reset TX command flags */
+ lgw_reg_w(LGW_TX_TRIG_IMMEDIATE, 0);
+ lgw_reg_w(LGW_TX_TRIG_DELAYED, 0);
+ lgw_reg_w(LGW_TX_TRIG_GPS, 0);
+
/* put metadata + payload in the TX data buffer */
lgw_reg_w(LGW_TX_DATA_BUF_ADDR, 0);
lgw_reg_wb(LGW_TX_DATA_BUF_DATA, buff, transfer_size);
@@ -1161,13 +1167,6 @@ int lgw_send(struct lgw_pkt_tx_s pkt_data) {
case TIMESTAMPED:
lgw_reg_w(LGW_TX_TRIG_DELAYED, 1);
- lgw_reg_r(LGW_TIMESTAMP, (int32_t *)&current_tstamp); /* unusable value if GPS is enabled */
- deadline_tstamp = pkt_data.count_us - TX_START_DELAY; /* time at which the controller with start TX sequence */
- if ((deadline_tstamp - current_tstamp) > 0x7FFFFFFF) {
- lgw_reg_w(LGW_TX_TRIG_DELAYED, 0); /* cancel TX if deadline was missed */
- DEBUG_MSG("ERROR: MISSED TX DEADLINE\n");
- return LGW_HAL_ERROR; // should return a specific error message
- }
break;
case ON_GPS:
@@ -1216,6 +1215,21 @@ int lgw_status(uint8_t select, uint8_t *code) {
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+int lgw_get_trigcnt(uint32_t* trig_cnt_us) {
+ int i;
+ int32_t val;
+
+ i = lgw_reg_r(LGW_TIMESTAMP, &val);
+ if (i == LGW_REG_SUCCESS) {
+ *trig_cnt_us = (uint32_t)val;
+ return LGW_HAL_SUCCESS;
+ } else {
+ return LGW_HAL_ERROR;
+ }
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
const char* lgw_version_info() {
return lgw_version_string;
}
diff --git a/libloragw/tst/test_loragw_gps.c b/libloragw/tst/test_loragw_gps.c
new file mode 100644
index 0000000..77e5325
--- /dev/null
+++ b/libloragw/tst/test_loragw_gps.c
@@ -0,0 +1,177 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ ©2013 Semtech-Cycleo
+
+Description:
+ Minimum test program for the loragw_gps 'library'
+
+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 */
+#include <string.h> /* memset */
+#include <signal.h> /* sigaction */
+#include <stdlib.h> /* exit */
+#include <unistd.h> /* read */
+
+#include "loragw_hal.h"
+#include "loragw_gps.h"
+#include "loragw_aux.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- 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 DECLARATION ---------------------------------------- */
+
+static void sig_handler(int sigio);
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
+
+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()
+{
+ struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */
+
+ int i;
+ char tmp_str[80];
+
+ /* serial variables */
+ char serial_buff[128]; /* buffer to receive GPS data */
+ ssize_t nb_char;
+ int gps_tty_dev; /* file descriptor to the serial port of the GNSS module */
+
+ /* NMEA variables */
+ enum gps_msg latest_msg; /* keep track of latest NMEA message parsed */
+
+ /* variables for PPM pulse GPS synchronization */
+ uint32_t ppm_tstamp;
+ struct timespec ppm_utc;
+ struct tref ppm_ref;
+
+ /* variables for timestamp <-> UTC conversions */
+ uint32_t x, z;
+ struct timespec y;
+
+ /* 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());
+
+ /* Open and configure GPS */
+ i = lgw_gps_enable("/dev/ttyACM0", NULL, 0, &gps_tty_dev);
+ if (i != LGW_GPS_SUCCESS) {
+ printf("ERROR: IMPOSSIBLE TO ENABLE GPS\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* start concentrator */
+ lgw_start();
+
+ /* 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)) {
+ /* blocking canonical read on serial port */
+ nb_char = read(gps_tty_dev, serial_buff, sizeof(serial_buff)-1);
+ if (nb_char <= 0) {
+ printf("Warning: read() returned value <= 0\n");
+ continue;
+ } else {
+ serial_buff[nb_char] = 0;
+ }
+
+ /* parse the received NMEA */
+ latest_msg = lgw_parse_nmea(serial_buff, sizeof(serial_buff));
+
+ if (latest_msg == NMEA_RMC) {
+
+ printf("\n~~ RMC NMEA sentence, triggering synchronization attempt ~~\n");
+
+ /* get UTC time for synchronization */
+ i = lgw_gps_get(&ppm_utc, NULL, NULL);
+ if (i != LGW_GPS_SUCCESS) {
+ printf(" No valid reference UTC time available, synchronization impossible.\n");
+ continue;
+ }
+ /* get timestamp for synchronization */
+ i = lgw_get_trigcnt(&ppm_tstamp);
+ if (i != LGW_HAL_SUCCESS) {
+ printf(" Failed to read timestamp, synchronization impossible.\n");
+ continue;
+ }
+ /* try to update synchronize time reference with the new UTC & timestamp */
+ i = lgw_gps_sync(&ppm_ref, ppm_tstamp, ppm_utc);
+ if (i != LGW_GPS_SUCCESS) {
+ printf(" Synchronization error.\n");
+ continue;
+ }
+ /* display result */
+ printf(" * Synchronization successful *\n");
+ strftime(tmp_str, sizeof(tmp_str), "%F %T", gmtime(&(ppm_ref.utc.tv_sec)));
+ printf(" UTC reference time: %s.%09ldZ\n", tmp_str, ppm_ref.utc.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;
+ printf(" * Test of timestamp counter <-> UTC value conversion *\n");
+ printf(" Test value: %u\n", x);
+ lgw_cnt2utc(ppm_ref, x, &y);
+ strftime(tmp_str, sizeof(tmp_str), "%F %T", gmtime(&(y.tv_sec)));
+ printf(" Conversion to UTC: %s.%09ldZ\n", tmp_str, y.tv_nsec);
+ lgw_utc2cnt(ppm_ref, y, &z);
+ printf(" Converted back: %u\n", z);
+ }
+ }
+
+ /* clean up before leaving */
+ if (exit_sig == 1) {
+ lgw_stop();
+ }
+
+ printf("\nEnd of test for loragw_gps.c\n");
+ exit(EXIT_SUCCESS);
+}
+
+/* --- EOF ------------------------------------------------------------------ */