From f31ee2e4eb6bcbb0c93ef685716842e151c8bd9f Mon Sep 17 00:00:00 2001 From: Michael Coracin Date: Mon, 6 Jun 2016 12:20:53 +0200 Subject: [PATCH] v3.0.0 * Merged all different flavours of packet forwarder into one unique lora_pkt_fwd Note: Various flavours can still be achieved using the corresponding global_conf.json.XXX file provided in lora_pkt_fwd/cfg. * Added downlink "just-in-time" scheduling to optimize downlink capacity. * Updated Gateway <-> NetworkServer protocol to describe the new format of "tx_ack" message. * Added "Listen-Before-Talk" configuration. * Splitted reset_pkt_fwd.sh script in 2 different scripts: - reset_lgw.sh, provided with the HAL (lora_gateway) - update_gwid.sh, provided with lora_pkt_fwd WARNING: Gateway <-> Network Server protocol version has changed. Please refer to PROTOCOL.txt file. --- Makefile | 8 +- PROTOCOL.TXT | 70 +- VERSION | 2 +- basic_pkt_fwd/Makefile | 66 - basic_pkt_fwd/inc/base64.h | 62 - basic_pkt_fwd/readme.md | 183 -- basic_pkt_fwd/src/base64.c | 308 -- basic_pkt_fwd/src/basic_pkt_fwd.c | 1584 ---------- beacon_pkt_fwd/Makefile | 67 - .../cfg/global_conf.json.PCB_E286.EU868 | 215 -- beacon_pkt_fwd/global_conf.json | 214 -- beacon_pkt_fwd/inc/base64.h | 62 - beacon_pkt_fwd/inc/parson.h | 111 - beacon_pkt_fwd/local_conf.json | 7 - beacon_pkt_fwd/readme.md | 187 -- beacon_pkt_fwd/src/base64.c | 308 -- beacon_pkt_fwd/src/beacon_pkt_fwd.c | 2216 -------------- beacon_pkt_fwd/src/parson.c | 782 ----- gps_pkt_fwd/cfg/global_conf.json.US902 | 103 - gps_pkt_fwd/inc/parson.h | 111 - gps_pkt_fwd/local_conf.json | 7 - gps_pkt_fwd/readme.md | 185 -- gps_pkt_fwd/src/base64.c | 308 -- gps_pkt_fwd/src/gps_pkt_fwd.c | 1951 ------------ gps_pkt_fwd/src/parson.c | 782 ----- {gps_pkt_fwd => lora_pkt_fwd}/Makefile | 8 +- .../cfg/global_conf.json.PCB_E286.EU868.basic | 6 +- .../global_conf.json.PCB_E286.EU868.beacon | 14 +- .../cfg/global_conf.json.PCB_E286.EU868.gps | 11 +- .../cfg/global_conf.json.PCB_E336.EU868.basic | 15 +- .../global_conf.json.PCB_E336.EU868.beacon | 23 +- .../cfg/global_conf.json.PCB_E336.EU868.gps | 20 +- .../cfg/global_conf.json.US902.basic | 8 +- .../cfg/global_conf.json.US902.gps | 13 +- .../global_conf.json | 15 +- {gps_pkt_fwd => lora_pkt_fwd}/inc/base64.h | 4 +- lora_pkt_fwd/inc/jitqueue.h | 156 + {basic_pkt_fwd => lora_pkt_fwd}/inc/parson.h | 0 lora_pkt_fwd/inc/timersync.h | 32 + lora_pkt_fwd/inc/trace.h | 37 + .../local_conf.json | 0 lora_pkt_fwd/readme.md | 329 +++ lora_pkt_fwd/src/base64.c | 308 ++ lora_pkt_fwd/src/jitqueue.c | 528 ++++ lora_pkt_fwd/src/lora_pkt_fwd.c | 2631 +++++++++++++++++ {basic_pkt_fwd => lora_pkt_fwd}/src/parson.c | 0 lora_pkt_fwd/src/timersync.c | 144 + lora_pkt_fwd/update_gwid.sh | 31 + readme.md | 63 +- reset_pkt_fwd.sh | 85 - util_ack/src/util_ack.c | 298 +- util_sink/src/util_sink.c | 172 +- util_tx_test/inc/base64.h | 4 +- util_tx_test/readme.md | 2 +- util_tx_test/src/base64.c | 464 +-- util_tx_test/src/util_tx_test.c | 4 +- 56 files changed, 4886 insertions(+), 10438 deletions(-) delete mode 100644 basic_pkt_fwd/Makefile delete mode 100644 basic_pkt_fwd/inc/base64.h delete mode 100644 basic_pkt_fwd/readme.md delete mode 100644 basic_pkt_fwd/src/base64.c delete mode 100644 basic_pkt_fwd/src/basic_pkt_fwd.c delete mode 100644 beacon_pkt_fwd/Makefile delete mode 100644 beacon_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868 delete mode 100644 beacon_pkt_fwd/global_conf.json delete mode 100644 beacon_pkt_fwd/inc/base64.h delete mode 100644 beacon_pkt_fwd/inc/parson.h delete mode 100644 beacon_pkt_fwd/local_conf.json delete mode 100644 beacon_pkt_fwd/readme.md delete mode 100644 beacon_pkt_fwd/src/base64.c delete mode 100644 beacon_pkt_fwd/src/beacon_pkt_fwd.c delete mode 100644 beacon_pkt_fwd/src/parson.c delete mode 100644 gps_pkt_fwd/cfg/global_conf.json.US902 delete mode 100644 gps_pkt_fwd/inc/parson.h delete mode 100644 gps_pkt_fwd/local_conf.json delete mode 100644 gps_pkt_fwd/readme.md delete mode 100644 gps_pkt_fwd/src/base64.c delete mode 100644 gps_pkt_fwd/src/gps_pkt_fwd.c delete mode 100644 gps_pkt_fwd/src/parson.c rename {gps_pkt_fwd => lora_pkt_fwd}/Makefile (83%) rename basic_pkt_fwd/global_conf.json => lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.basic (97%) rename gps_pkt_fwd/global_conf.json => lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.beacon (93%) rename gps_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868 => lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.gps (94%) rename basic_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868 => lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.basic (92%) rename gps_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868 => lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.beacon (88%) rename beacon_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868 => lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.gps (90%) rename basic_pkt_fwd/cfg/global_conf.json.US902 => lora_pkt_fwd/cfg/global_conf.json.US902.basic (93%) rename beacon_pkt_fwd/cfg/global_conf.json.US902 => lora_pkt_fwd/cfg/global_conf.json.US902.gps (88%) rename basic_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868 => lora_pkt_fwd/global_conf.json (92%) rename {gps_pkt_fwd => lora_pkt_fwd}/inc/base64.h (96%) create mode 100644 lora_pkt_fwd/inc/jitqueue.h rename {basic_pkt_fwd => lora_pkt_fwd}/inc/parson.h (100%) create mode 100644 lora_pkt_fwd/inc/timersync.h create mode 100644 lora_pkt_fwd/inc/trace.h rename {basic_pkt_fwd => lora_pkt_fwd}/local_conf.json (100%) create mode 100644 lora_pkt_fwd/readme.md create mode 100644 lora_pkt_fwd/src/base64.c create mode 100644 lora_pkt_fwd/src/jitqueue.c create mode 100644 lora_pkt_fwd/src/lora_pkt_fwd.c rename {basic_pkt_fwd => lora_pkt_fwd}/src/parson.c (100%) create mode 100644 lora_pkt_fwd/src/timersync.c create mode 100755 lora_pkt_fwd/update_gwid.sh delete mode 100755 reset_pkt_fwd.sh diff --git a/Makefile b/Makefile index 976a6072..aee59c75 100644 --- a/Makefile +++ b/Makefile @@ -8,17 +8,13 @@ export ### general build targets all: - $(MAKE) all -e -C basic_pkt_fwd - $(MAKE) all -e -C gps_pkt_fwd - $(MAKE) all -e -C beacon_pkt_fwd + $(MAKE) all -e -C lora_pkt_fwd $(MAKE) all -e -C util_ack $(MAKE) all -e -C util_sink $(MAKE) all -e -C util_tx_test clean: - $(MAKE) clean -e -C basic_pkt_fwd - $(MAKE) clean -e -C gps_pkt_fwd - $(MAKE) clean -e -C beacon_pkt_fwd + $(MAKE) clean -e -C lora_pkt_fwd $(MAKE) clean -e -C util_ack $(MAKE) clean -e -C util_sink $(MAKE) clean -e -C util_tx_test diff --git a/PROTOCOL.TXT b/PROTOCOL.TXT index 2324235c..3d574104 100644 --- a/PROTOCOL.TXT +++ b/PROTOCOL.TXT @@ -96,7 +96,7 @@ received, and associated metadata, to the server. Bytes | Function :------:|--------------------------------------------------------------------- - 0 | protocol version = 1 + 0 | protocol version = 2 1-2 | random token 3 | PUSH_DATA identifier 0x00 4-11 | Gateway unique identifier (MAC address) @@ -109,7 +109,7 @@ PUSH_DATA packets received. Bytes | Function :------:|--------------------------------------------------------------------- - 0 | protocol version = 1 + 0 | protocol version = 2 1-2 | same token as the PUSH_DATA packet to acknowledge 3 | PUSH_ACK identifier 0x01 @@ -269,9 +269,11 @@ Example (white-spaces, indentation and newlines added for readability): | | Anytime after first PULL_DATA for each packet to TX |-| | ------------------------------------------------------- | | | - | PULL_RESP (token 0, JSON payload) | + | PULL_RESP (token Z, JSON payload) | |<-------------------------------------------------------------| | | + | TX_ACK (token Z, JSON payload) | + |------------------------------------------------------------->| ### 5.2. PULL_DATA packet ### @@ -288,7 +290,7 @@ route stays open for the server to be used at any time. Bytes | Function :------:|--------------------------------------------------------------------- - 0 | protocol version = 1 + 0 | protocol version = 2 1-2 | random token 3 | PULL_DATA identifier 0x02 4-11 | Gateway unique identifier (MAC address) @@ -300,7 +302,7 @@ open and that the server can send PULL_RESP packets at any time. Bytes | Function :------:|--------------------------------------------------------------------- - 0 | protocol version = 1 + 0 | protocol version = 2 1-2 | same token as the PULL_DATA packet to acknowledge 3 | PULL_ACK identifier 0x04 @@ -311,16 +313,30 @@ metadata that will have to be emitted by the gateway. Bytes | Function :------:|--------------------------------------------------------------------- - 0 | protocol version = 1 - 1-2 | unused bytes + 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-end | [optional] JSON object, starting with {, ending with }, see section 6 6. Downstream JSON data structure ---------------------------------- -The root object must contain an object named "txpk": +The root object of PULL_RESP packet must contain an object named "txpk": ``` json { @@ -383,10 +399,48 @@ Examples (white-spaces, indentation and newlines added for readability): }} ``` +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 | Indication about success or type of failure that occured for downlink request. + +The possible values of "error" field are: + + Value | Definition +:-----------------:|--------------------------------------------------------------------- + NONE | Packet has been programmed for downlink + 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 + TX_POWER | Rejected because requested power is not supported by gateway + GPS_UNLOCKED | Rejected because GPS is unlocked, so GPS timestamp cannot be used + +Examples (white-spaces, indentation and newlines added for readability): + +``` json +{"txpk_ack":{ + "error":"COLLISION_PACKET" +}} +``` 7. Revisions ------------- +### v1.3 ### + +* Added downlink feedback from gateway to server (PULL_RESP -> TX_ACK) + ### v1.2 ### * Added value of FSK bitrate for upstream. diff --git a/VERSION b/VERSION index c043eea7..4a36342f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.2.1 +3.0.0 diff --git a/basic_pkt_fwd/Makefile b/basic_pkt_fwd/Makefile deleted file mode 100644 index ca247167..00000000 --- a/basic_pkt_fwd/Makefile +++ /dev/null @@ -1,66 +0,0 @@ -### Application-specific constants - -APP_NAME := basic_pkt_fwd - -### Environment constants - -LGW_PATH ?= ../../lora_gateway/libloragw -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. -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 -lrt -lpthread - -### General build targets - -all: $(APP_NAME) - -clean: - rm -f $(OBJDIR)/*.o - rm -f $(APP_NAME) - -### Sub-modules compilation - -$(OBJDIR): - mkdir -p $(OBJDIR) - -$(OBJDIR)/%.o: src/%.c $(INCLUDES) | $(OBJDIR) - $(CC) -c $(CFLAGS) $< -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)/parson.o $(OBJDIR)/base64.o - $(CC) -L$(LGW_PATH) $< $(OBJDIR)/parson.o $(OBJDIR)/base64.o -o $@ $(LIBS) - -### EOF diff --git a/basic_pkt_fwd/inc/base64.h b/basic_pkt_fwd/inc/base64.h deleted file mode 100644 index c0369aad..00000000 --- a/basic_pkt_fwd/inc/base64.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - / _____) _ | | -( (____ _____ ____ _| |_ _____ ____| |__ - \____ \| ___ | (_ _) ___ |/ ___) _ \ - _____) ) ____| | | || |_| ____( (___| | | | -(______/|_____)_|_|_| \__)_____)\____)_| |_| - (C)2013 Semtech-Cycleo - -Description: - Base64 encoding & decoding library - -License: Revised BSD License, see LICENSE.TXT file include in the project -Maintainer: Sylvain Miermont -*/ - - -#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/basic_pkt_fwd/readme.md b/basic_pkt_fwd/readme.md deleted file mode 100644 index 62255e78..00000000 --- a/basic_pkt_fwd/readme.md +++ /dev/null @@ -1,183 +0,0 @@ - / _____) _ | | - ( (____ _____ ____ _| |_ _____ ____| |__ - \____ \| ___ | (_ _) ___ |/ ___) _ \ - _____) ) ____| | | || |_| ____( (___| | | | - (______/|_____)_|_|_| \__)_____)\____)_| |_| - (C)2013 Semtech-Cycleo - -Lora Gateway basic packet forwarder -==================================== - -1. Introduction ----------------- - -The basic packet forwarder is a program running on the host of a Lora Gateway -that forward RF packets receive by the concentrator to a server through a -IP/UDP link, and emits RF packets that are sent by the server. - -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 | | - | Gateway | | | - +- - - - - - - - - - - - - - -+ +--------+ - -Concentrator: radio RX/TX board, based on Semtech multichannel modems (SX1301), -transceivers (SX125x) 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. -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.1 version of the gateway-to-server protocol. - -The last dependency is the hardware concentrator (based on SX1301 chips) that -must be matched with the proper version of the HAL. - -4. Usage ---------- - -1. Update JSON configuration files, as explained below. -2. For IoT Starter Kit only, run: - ./reset_pkd_fwd.sh stop - ./reset_pkd_fwd.sh start local_conf.json -3. Run: - ./basic_pkt_fwd - -To stop the application, press Ctrl+C. -Unless it is manually stopped or encounter a critical error, the program will -run forever. - -There are no command line launch options. - -The way the program takes configuration files into account is the following: - * if there is a debug_conf.json parse it, others are ignored - * if there is a global_conf.json parse it, look for the next file - * if there is a local_conf.json parse it -If some parameters are defined in both global and local configuration files, -the local definition overwrites the global definition. - -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). -As some of the parameters (like 'rssi_offset', 'tx_lut_*') are board dependant, -several flavours of the global_conf.json file are provided in the cfg/ -directory. - * global_conf.json.PCB_E286.EU868: to be used for Semtech reference design - board with PCB name PCB_E286 (also called Gateway Board v1.0 (no FPGA)). - Configured for Europe 868MHz channels. - * global_conf.json.PCB_E336.EU868: to be used for Semtech reference design - board with PCB name PCB_E336 (also called Gateway Board v1.5 (with FPGA)). - Configured for Europe 868MHz channels. - * global_conf.json.US902: to be used for Semtech reference design v1.0 or - v1.5. (No calibration done for RSSI offset and TX gains yet). - Configured for US 902MHz channels. -Rename the one you need to global_conf.json before launching the packet -forwarder. - -The local configuration file should contain parameters that are specific to -each gateway (eg. MAC address, frequency for backhaul radio channels). - -In each configuration file, the program looks for a JSON object named -"SX1301_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. - -This basic variant of the packet forwarder does not send status report to the -server. - -5. License ------------ - -Copyright (C) 2013, 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/basic_pkt_fwd/src/base64.c b/basic_pkt_fwd/src/base64.c deleted file mode 100644 index ca7a815d..00000000 --- a/basic_pkt_fwd/src/base64.c +++ /dev/null @@ -1,308 +0,0 @@ -/* - / _____) _ | | -( (____ _____ ____ _| |_ _____ ____| |__ - \____ \| ___ | (_ _) ___ |/ ___) _ \ - _____) ) ____| | | || |_| ____( (___| | | | -(______/|_____)_|_|_| \__)_____)\____)_| |_| - (C)2013 Semtech-Cycleo - -Description: - Base64 encoding & decoding library - -License: Revised BSD License, see LICENSE.TXT file include in the project -Maintainer: Sylvain Miermont -*/ - - -/* -------------------------------------------------------------------------- */ -/* --- 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/basic_pkt_fwd/src/basic_pkt_fwd.c b/basic_pkt_fwd/src/basic_pkt_fwd.c deleted file mode 100644 index 35eb2df6..00000000 --- a/basic_pkt_fwd/src/basic_pkt_fwd.c +++ /dev/null @@ -1,1584 +0,0 @@ -/* - / _____) _ | | -( (____ _____ ____ _| |_ _____ ____| |__ - \____ \| ___ | (_ _) ___ |/ ___) _ \ - _____) ) ____| | | || |_| ____( (___| | | | -(______/|_____)_|_|_| \__)_____)\____)_| |_| - (C)2013 Semtech-Cycleo - -Description: - Configure Lora concentrator and forward packets to a server - -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 /* 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 /* socket specific definitions */ -#include /* INET constants and stuff */ -#include /* IP address conversion stuff */ -#include /* gai_strerror */ - -#include - -#include "parson.h" -#include "base64.h" -#include "loragw_hal.h" -#include "loragw_aux.h" - -/* -------------------------------------------------------------------------- */ -/* --- PRIVATE MACROS ------------------------------------------------------- */ - -#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) -#define STRINGIFY(x) #x -#define STR(x) STRINGIFY(x) -#define MSG(args...) printf(args) /* message that is destined to the user */ -#define TRACE() fprintf(stderr, "@ %s %d\n", __FUNCTION__, __LINE__); - -/* -------------------------------------------------------------------------- */ -/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ - -#ifndef VERSION_STRING - #define VERSION_STRING "undefined" -#endif - -#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 FETCH_SLEEP_MS 10 /* nb of ms waited when a fetch return no packets */ - -#define PROTOCOL_VERSION 1 - -#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 NB_PKT_MAX 8 /* 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 4 - -#define TX_BUFF_SIZE ((540 * NB_PKT_MAX) + 30) - -/* -------------------------------------------------------------------------- */ -/* --- 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 */ -static pthread_mutex_t mx_concent = PTHREAD_MUTEX_INITIALIZER; /* control access to the concentrator */ - -/* 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 */ - -/* auto-quit function */ -static uint32_t autoquit_threshold = 0; /* enable auto-quit after a number of non-acknowledged PULL_DATA (0 = disabled)*/ - -/* -------------------------------------------------------------------------- */ -/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ - -static void sig_handler(int sigio); - -static int parse_SX1301_configuration(const char * conf_file); - -static int parse_gateway_configuration(const char * conf_file); - -static double difftimespec(struct timespec end, struct timespec beginning); - -/* threads */ -void thread_up(void); -void thread_down(void); - -/* -------------------------------------------------------------------------- */ -/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ - -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_SX1301_configuration(const char * conf_file) { - int i; - 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[] = "SX1301_conf"; - JSON_Value *root_val = NULL; - JSON_Object *conf_obj = NULL; - JSON_Value *val = NULL; - struct lgw_conf_board_s boardconf; - struct lgw_conf_rxrf_s rfconf; - struct lgw_conf_rxif_s ifconf; - uint32_t sf, bw, fdev; - struct lgw_tx_gain_lut_s txlut; - - /* 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 SX1301 parameters\n", conf_file, conf_obj_name); - } - - /* set board configuration */ - memset(&boardconf, 0, sizeof boardconf); /* initialize configuration structure */ - 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; - } - MSG("INFO: lorawan_public %d, clksrc %d\n", boardconf.lorawan_public, boardconf.clksrc); - /* all parameters parsed, submitting configuration to the HAL */ - if (lgw_board_setconf(boardconf) != LGW_HAL_SUCCESS) { - MSG("WARNING: Failed to configure board\n"); - } - - /* set configuration for tx gains */ - memset(&txlut, 0, sizeof txlut); /* initialize configuration structure */ - for (i = 0; i < TX_GAIN_LUT_SIZE_MAX; i++) { - snprintf(param_name, sizeof param_name, "tx_lut_%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 tx gain lut %i\n", i); - continue; - } - txlut.size++; /* update TX LUT size based on JSON object found in configuration file */ - /* there is an object to configure that TX gain index, let's parse it */ - snprintf(param_name, sizeof param_name, "tx_lut_%i.pa_gain", i); - val = json_object_dotget_value(conf_obj, param_name); - if (json_value_get_type(val) == JSONNumber) { - txlut.lut[i].pa_gain = (uint8_t)json_value_get_number(val); - } else { - MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); - txlut.lut[i].pa_gain = 0; - } - snprintf(param_name, sizeof param_name, "tx_lut_%i.dac_gain", i); - val = json_object_dotget_value(conf_obj, param_name); - if (json_value_get_type(val) == JSONNumber) { - txlut.lut[i].dac_gain = (uint8_t)json_value_get_number(val); - } else { - txlut.lut[i].dac_gain = 3; /* This is the only dac_gain supported for now */ - } - snprintf(param_name, sizeof param_name, "tx_lut_%i.dig_gain", i); - val = json_object_dotget_value(conf_obj, param_name); - if (json_value_get_type(val) == JSONNumber) { - txlut.lut[i].dig_gain = (uint8_t)json_value_get_number(val); - } else { - MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); - txlut.lut[i].dig_gain = 0; - } - snprintf(param_name, sizeof param_name, "tx_lut_%i.mix_gain", i); - val = json_object_dotget_value(conf_obj, param_name); - if (json_value_get_type(val) == JSONNumber) { - txlut.lut[i].mix_gain = (uint8_t)json_value_get_number(val); - } else { - MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); - txlut.lut[i].mix_gain = 0; - } - snprintf(param_name, sizeof param_name, "tx_lut_%i.rf_power", i); - val = json_object_dotget_value(conf_obj, param_name); - if (json_value_get_type(val) == JSONNumber) { - txlut.lut[i].rf_power = (int8_t)json_value_get_number(val); - } else { - MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); - txlut.lut[i].rf_power = 0; - } - } - /* all parameters parsed, submitting configuration to the HAL */ - MSG("INFO: Configuring TX LUT with %u indexes\n", txlut.size); - if (lgw_txgain_setconf(&txlut) != LGW_HAL_SUCCESS) { - MSG("WARNING: Failed to configure concentrator TX Gain LUT\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.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 { - MSG("WARNING: invalid radio type: %s (should be SX1255 or SX1257)\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); - } 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("WARNING: invalid configuration for radio %i\n", i); - } - } - - /* 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 7 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("WARNING: invalid configuration for Lora multi-SF channel %i\n", i); - } - } - - /* 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 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; - } - MSG("INFO: Lora std channel> radio %i, IF %i Hz, %u Hz bw, SF %u\n", ifconf.rf_chain, ifconf.freq_hz, bw, sf); - } - if (lgw_rxif_setconf(8, ifconf) != LGW_HAL_SUCCESS) { - MSG("WARNING: invalid configuration for Lora standard channel\n"); - } - } - - /* 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; - 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; - 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("WARNING: invalid configuration for FSK channel\n"); - } - } - 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")); - - /* 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 double difftimespec(struct timespec end, struct timespec beginning) { - double x; - - x = 1E-9 * (double)(end.tv_nsec - beginning.tv_nsec); - x += (double)(end.tv_sec - beginning.tv_sec); - - return x; -} - -/* -------------------------------------------------------------------------- */ -/* --- MAIN FUNCTION -------------------------------------------------------- */ - -int main(void) -{ - struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ - int i; /* loop variable and temporary variable for return value */ - - /* configuration file related */ - char *global_cfg_path= "global_conf.json"; /* contain global (typ. network-wide) configuration */ - char *local_cfg_path = "local_conf.json"; /* contain node specific configuration, overwrite global parameters for parameters that are defined in both */ - char *debug_cfg_path = "debug_conf.json"; /* if present, all other configuration files are ignored */ - - /* threads */ - pthread_t thrid_up; - pthread_t thrid_down; - - /* 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; - - /* 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; - - /* display version informations */ - MSG("*** Basic Packet Forwarder for Lora Gateway ***\nVersion: " VERSION_STRING "\n"); - MSG("*** Lora concentrator 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(debug_cfg_path, R_OK) == 0) { /* if there is a debug conf, parse only the debug conf */ - MSG("INFO: found debug configuration file %s, parsing it\n", debug_cfg_path); - MSG("INFO: other configuration files will be ignored\n"); - parse_SX1301_configuration(debug_cfg_path); - parse_gateway_configuration(debug_cfg_path); - } else if (access(global_cfg_path, R_OK) == 0) { /* if there is a global conf, parse it and then try to parse local conf */ - MSG("INFO: found global configuration file %s, parsing it\n", global_cfg_path); - parse_SX1301_configuration(global_cfg_path); - parse_gateway_configuration(global_cfg_path); - if (access(local_cfg_path, R_OK) == 0) { - MSG("INFO: found local configuration file %s, parsing it\n", local_cfg_path); - MSG("INFO: redefined parameters will overwrite global parameters\n"); - parse_SX1301_configuration(local_cfg_path); - parse_gateway_configuration(local_cfg_path); - } - } else if (access(local_cfg_path, R_OK) == 0) { /* if there is only a local conf, parse it and that's all */ - MSG("INFO: found local configuration file %s, parsing it\n", local_cfg_path); - parse_SX1301_configuration(local_cfg_path); - parse_gateway_configuration(local_cfg_path); - } else { - MSG("ERROR: [main] failed to find any configuration file named %s, %s OR %s\n", global_cfg_path, local_cfg_path, debug_cfg_path); - exit(EXIT_FAILURE); - } - - /* 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); - - /* 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); - } - - /* 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); - } - - /* 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; - 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; - 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; - } - - /* 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); - printf("##### END #####\n"); - } - - /* 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 */ - - /* 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"); - } - } - - MSG("INFO: Exiting packet forwarder program\n"); - exit(EXIT_SUCCESS); -} - -/* -------------------------------------------------------------------------- */ -/* --- THREAD 1: RECEIVING PACKETS AND FORWARDING THEM ---------------------- */ - -void thread_up(void) { - int i, j; /* loop variables */ - unsigned pkt_in_dgram; /* nb on Lora packet in the current datagram */ - - /* 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 timestamp variables until we get accurate GPS time */ - struct timespec fetch_time; - struct tm * x; - char fetch_timestamp[28]; /* timestamp as a text string */ - - /* 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; - - /* 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); - } else if (nb_pkt == 0) { - wait_ms(FETCH_SLEEP_MS); /* wait a short time if no packets */ - continue; - } - - /* local timestamp generation until we get accurate GPS time */ - clock_gettime(CLOCK_REALTIME, &fetch_time); - x = gmtime(&(fetch_time.tv_sec)); /* split the UNIX timestamp to its calendar components */ - snprintf(fetch_timestamp, sizeof fetch_timestamp, "%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, (fetch_time.tv_nsec)/1000); /* ISO 8601 format */ - - /* 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]; - - /* 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->rssi); - 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); - - /* 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; - } - - /* 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 (system time based), 37 useful chars */ - memcpy((void *)(buff_up + buff_index), (void *)",\"time\":\"???????????????????????????\"", 37); - memcpy((void *)(buff_up + buff_index + 9), (void *)fetch_timestamp, 27); - buff_index += 37; - - /* 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", p->if_chain, p->rf_chain, ((double)p->freq_hz / 1e6)); - 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\n"); - 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_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\n"); - 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\n"); - 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\n"); - memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"?\"", 11); - buff_index += 11; - exit(EXIT_FAILURE); - } - - /* Lora SNR, 11-13 useful chars */ - 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); - } - } 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\n"); - exit(EXIT_FAILURE); - } - - /* Packet RSSI, payload size, 18-23 useful chars */ - j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"rssi\":%.0f,\"size\":%u", p->rssi, 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; - } - - /* restart fetch sequence without sending empty JSON if all packets have been filtered out */ - if (pkt_in_dgram == 0) { - continue; - } - - /* end of packet array */ - buff_up[buff_index] = ']'; - ++buff_index; - - /* 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 EMITTING PACKETS ------------------------ */ - -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; - - /* auto-quit variable */ - uint32_t autoquit_cnt = 0; /* count the number of PULL_DATA sent since the latest PULL_ACK */ - - /* 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; - - 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); - - /* 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\n"); - 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 :)\n"); /* 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; - 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); - MSG("INFO: [down] a packet will be sent on timestamp value %u\n", txpkt.count_us); - } else { - MSG("WARNING: [down] only \"immediate\" and \"timestamp\" modes supported, TX aborted\n"); - json_value_free(root_val); - continue; - } - } - - /* 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); - } - - /* 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 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; - - /* transfer data and metadata to the concentrator, and schedule TX */ - pthread_mutex_lock(&mx_concent); /* may have to wait for a fetch to finish */ - i = lgw_send(txpkt); - pthread_mutex_unlock(&mx_concent); /* free concentrator ASAP */ - if (i == LGW_HAL_ERROR) { - meas_nb_tx_fail += 1; - pthread_mutex_unlock(&mx_meas_dw); - MSG("WARNING: [down] lgw_send failed\n"); - continue; - } else { - meas_nb_tx_ok += 1; - pthread_mutex_unlock(&mx_meas_dw); - } - } - } - MSG("\nINFO: End of downstream thread\n"); -} - -/* --- EOF ------------------------------------------------------------------ */ diff --git a/beacon_pkt_fwd/Makefile b/beacon_pkt_fwd/Makefile deleted file mode 100644 index 0c19acc9..00000000 --- a/beacon_pkt_fwd/Makefile +++ /dev/null @@ -1,67 +0,0 @@ -### Application-specific constants - -APP_NAME := beacon_pkt_fwd - -### Environment constants - -LGW_PATH ?= ../../lora_gateway/libloragw -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. -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 -LGW_INC += $(LGW_PATH)/inc/loragw_gps.h - -### Linking options - -LIBS := -lloragw -lrt -lpthread -lm - -### General build targets - -all: $(APP_NAME) - -clean: - rm -f $(OBJDIR)/*.o - rm -f $(APP_NAME) - -### Sub-modules compilation - -$(OBJDIR): - mkdir -p $(OBJDIR) - -$(OBJDIR)/%.o: src/%.c $(INCLUDES) | $(OBJDIR) - $(CC) -c $(CFLAGS) $< -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)/parson.o $(OBJDIR)/base64.o - $(CC) -L$(LGW_PATH) $< $(OBJDIR)/parson.o $(OBJDIR)/base64.o -o $@ $(LIBS) - -### EOF diff --git a/beacon_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868 b/beacon_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868 deleted file mode 100644 index e44c6e03..00000000 --- a/beacon_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868 +++ /dev/null @@ -1,215 +0,0 @@ -{ - "SX1301_conf": { - "lorawan_public": true, - "clksrc": 1, /* radio_1 provides clock to concentrator */ - "radio_0": { - "enable": true, - "type": "SX1257", - "freq": 867500000, - "rssi_offset": -166.0, - "tx_enable": true - }, - "radio_1": { - "enable": true, - "type": "SX1257", - "freq": 868500000, - "rssi_offset": -166.0, - "tx_enable": false - }, - "chan_multiSF_0": { - /* Lora MAC channel, 125kHz, all SF, 868.1 MHz */ - "enable": true, - "radio": 1, - "if": -400000 - }, - "chan_multiSF_1": { - /* Lora MAC channel, 125kHz, all SF, 868.3 MHz */ - "enable": true, - "radio": 1, - "if": -200000 - }, - "chan_multiSF_2": { - /* Lora MAC channel, 125kHz, all SF, 868.5 MHz */ - "enable": true, - "radio": 1, - "if": 0 - }, - "chan_multiSF_3": { - /* Lora MAC channel, 125kHz, all SF, 867.1 MHz */ - "enable": true, - "radio": 0, - "if": -400000 - }, - "chan_multiSF_4": { - /* Lora MAC channel, 125kHz, all SF, 867.3 MHz */ - "enable": true, - "radio": 0, - "if": -200000 - }, - "chan_multiSF_5": { - /* Lora MAC channel, 125kHz, all SF, 867.5 MHz */ - "enable": true, - "radio": 0, - "if": 0 - }, - "chan_multiSF_6": { - /* Lora MAC channel, 125kHz, all SF, 867.7 MHz */ - "enable": true, - "radio": 0, - "if": 200000 - }, - "chan_multiSF_7": { - /* Lora MAC channel, 125kHz, all SF, 867.9 MHz */ - "enable": true, - "radio": 0, - "if": 400000 - }, - "chan_Lora_std": { - /* Lora MAC channel, 250kHz, SF7, 868.3 MHz */ - "enable": true, - "radio": 1, - "if": -200000, - "bandwidth": 250000, - "spread_factor": 7 - }, - "chan_FSK": { - /* FSK 50kbps channel, 868.8 MHz */ - "enable": true, - "radio": 1, - "if": 300000, - "bandwidth": 125000, - "datarate": 50000 - }, - "tx_lut_0": { - /* TX gain table, index 0 */ - "pa_gain": 0, - "mix_gain": 8, - "rf_power": -6, - "dig_gain": 0 - }, - "tx_lut_1": { - /* TX gain table, index 1 */ - "pa_gain": 0, - "mix_gain": 10, - "rf_power": -3, - "dig_gain": 0 - }, - "tx_lut_2": { - /* TX gain table, index 2 */ - "pa_gain": 0, - "mix_gain": 12, - "rf_power": 0, - "dig_gain": 0 - }, - "tx_lut_3": { - /* TX gain table, index 3 */ - "pa_gain": 1, - "mix_gain": 8, - "rf_power": 3, - "dig_gain": 0 - }, - "tx_lut_4": { - /* TX gain table, index 4 */ - "pa_gain": 1, - "mix_gain": 10, - "rf_power": 6, - "dig_gain": 0 - }, - "tx_lut_5": { - /* TX gain table, index 5 */ - "pa_gain": 1, - "mix_gain": 12, - "rf_power": 10, - "dig_gain": 0 - }, - "tx_lut_6": { - /* TX gain table, index 6 */ - "pa_gain": 1, - "mix_gain": 13, - "rf_power": 11, - "dig_gain": 0 - }, - "tx_lut_7": { - /* TX gain table, index 7 */ - "pa_gain": 2, - "mix_gain": 9, - "rf_power": 12, - "dig_gain": 0 - }, - "tx_lut_8": { - /* TX gain table, index 8 */ - "pa_gain": 1, - "mix_gain": 15, - "rf_power": 13, - "dig_gain": 0 - }, - "tx_lut_9": { - /* TX gain table, index 9 */ - "pa_gain": 2, - "mix_gain": 10, - "rf_power": 14, - "dig_gain": 0 - }, - "tx_lut_10": { - /* TX gain table, index 10 */ - "pa_gain": 2, - "mix_gain": 11, - "rf_power": 16, - "dig_gain": 0 - }, - "tx_lut_11": { - /* TX gain table, index 11 */ - "pa_gain": 3, - "mix_gain": 9, - "rf_power": 20, - "dig_gain": 0 - }, - "tx_lut_12": { - /* TX gain table, index 12 */ - "pa_gain": 3, - "mix_gain": 10, - "rf_power": 23, - "dig_gain": 0 - }, - "tx_lut_13": { - /* TX gain table, index 13 */ - "pa_gain": 3, - "mix_gain": 11, - "rf_power": 25, - "dig_gain": 0 - }, - "tx_lut_14": { - /* TX gain table, index 14 */ - "pa_gain": 3, - "mix_gain": 12, - "rf_power": 26, - "dig_gain": 0 - }, - "tx_lut_15": { - /* TX gain table, index 15 */ - "pa_gain": 3, - "mix_gain": 14, - "rf_power": 27, - "dig_gain": 0 - } - }, - - "gateway_conf": { - "gateway_ID": "AA555A0000000000", - /* change with default server address/ports, or overwrite in local_conf.json */ - "server_address": "localhost", - "serv_port_up": 1680, - "serv_port_down": 1680, - /* 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/ttyAMA0" - } -} - diff --git a/beacon_pkt_fwd/global_conf.json b/beacon_pkt_fwd/global_conf.json deleted file mode 100644 index 13700a47..00000000 --- a/beacon_pkt_fwd/global_conf.json +++ /dev/null @@ -1,214 +0,0 @@ -{ - "SX1301_conf": { - "lorawan_public": true, - "clksrc": 1, /* radio_1 provides clock to concentrator */ - "radio_0": { - "enable": true, - "type": "SX1257", - "freq": 867500000, - "rssi_offset": -166.0, - "tx_enable": true - }, - "radio_1": { - "enable": true, - "type": "SX1257", - "freq": 868500000, - "rssi_offset": -166.0, - "tx_enable": false - }, - "chan_multiSF_0": { - /* Lora MAC channel, 125kHz, all SF, 868.1 MHz */ - "enable": true, - "radio": 1, - "if": -400000 - }, - "chan_multiSF_1": { - /* Lora MAC channel, 125kHz, all SF, 868.3 MHz */ - "enable": true, - "radio": 1, - "if": -200000 - }, - "chan_multiSF_2": { - /* Lora MAC channel, 125kHz, all SF, 868.5 MHz */ - "enable": true, - "radio": 1, - "if": 0 - }, - "chan_multiSF_3": { - /* Lora MAC channel, 125kHz, all SF, 867.1 MHz */ - "enable": true, - "radio": 0, - "if": -400000 - }, - "chan_multiSF_4": { - /* Lora MAC channel, 125kHz, all SF, 867.3 MHz */ - "enable": true, - "radio": 0, - "if": -200000 - }, - "chan_multiSF_5": { - /* Lora MAC channel, 125kHz, all SF, 867.5 MHz */ - "enable": true, - "radio": 0, - "if": 0 - }, - "chan_multiSF_6": { - /* Lora MAC channel, 125kHz, all SF, 867.7 MHz */ - "enable": true, - "radio": 0, - "if": 200000 - }, - "chan_multiSF_7": { - /* Lora MAC channel, 125kHz, all SF, 867.9 MHz */ - "enable": true, - "radio": 0, - "if": 400000 - }, - "chan_Lora_std": { - /* Lora MAC channel, 250kHz, SF7, 868.3 MHz */ - "enable": true, - "radio": 1, - "if": -200000, - "bandwidth": 250000, - "spread_factor": 7 - }, - "chan_FSK": { - /* FSK 50kbps channel, 868.8 MHz */ - "enable": true, - "radio": 1, - "if": 300000, - "bandwidth": 125000, - "datarate": 50000 - }, - "tx_lut_0": { - /* TX gain table, index 0 */ - "pa_gain": 0, - "mix_gain": 8, - "rf_power": -6, - "dig_gain": 0 - }, - "tx_lut_1": { - /* TX gain table, index 1 */ - "pa_gain": 0, - "mix_gain": 10, - "rf_power": -3, - "dig_gain": 0 - }, - "tx_lut_2": { - /* TX gain table, index 2 */ - "pa_gain": 0, - "mix_gain": 12, - "rf_power": 0, - "dig_gain": 0 - }, - "tx_lut_3": { - /* TX gain table, index 3 */ - "pa_gain": 1, - "mix_gain": 8, - "rf_power": 3, - "dig_gain": 0 - }, - "tx_lut_4": { - /* TX gain table, index 4 */ - "pa_gain": 1, - "mix_gain": 10, - "rf_power": 6, - "dig_gain": 0 - }, - "tx_lut_5": { - /* TX gain table, index 5 */ - "pa_gain": 1, - "mix_gain": 12, - "rf_power": 10, - "dig_gain": 0 - }, - "tx_lut_6": { - /* TX gain table, index 6 */ - "pa_gain": 1, - "mix_gain": 13, - "rf_power": 11, - "dig_gain": 0 - }, - "tx_lut_7": { - /* TX gain table, index 7 */ - "pa_gain": 2, - "mix_gain": 9, - "rf_power": 12, - "dig_gain": 0 - }, - "tx_lut_8": { - /* TX gain table, index 8 */ - "pa_gain": 1, - "mix_gain": 15, - "rf_power": 13, - "dig_gain": 0 - }, - "tx_lut_9": { - /* TX gain table, index 9 */ - "pa_gain": 2, - "mix_gain": 10, - "rf_power": 14, - "dig_gain": 0 - }, - "tx_lut_10": { - /* TX gain table, index 10 */ - "pa_gain": 2, - "mix_gain": 11, - "rf_power": 16, - "dig_gain": 0 - }, - "tx_lut_11": { - /* TX gain table, index 11 */ - "pa_gain": 3, - "mix_gain": 9, - "rf_power": 20, - "dig_gain": 0 - }, - "tx_lut_12": { - /* TX gain table, index 12 */ - "pa_gain": 3, - "mix_gain": 10, - "rf_power": 23, - "dig_gain": 0 - }, - "tx_lut_13": { - /* TX gain table, index 13 */ - "pa_gain": 3, - "mix_gain": 11, - "rf_power": 25, - "dig_gain": 0 - }, - "tx_lut_14": { - /* TX gain table, index 14 */ - "pa_gain": 3, - "mix_gain": 12, - "rf_power": 26, - "dig_gain": 0 - }, - "tx_lut_15": { - /* TX gain table, index 15 */ - "pa_gain": 3, - "mix_gain": 14, - "rf_power": 27, - "dig_gain": 0 - } - }, - - "gateway_conf": { - "gateway_ID": "AA555A0000000000", - /* change with default server address/ports, or overwrite in local_conf.json */ - "server_address": "localhost", - "serv_port_up": 1680, - "serv_port_down": 1680, - /* 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/ttyAMA0" - } -} diff --git a/beacon_pkt_fwd/inc/base64.h b/beacon_pkt_fwd/inc/base64.h deleted file mode 100644 index c0369aad..00000000 --- a/beacon_pkt_fwd/inc/base64.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - / _____) _ | | -( (____ _____ ____ _| |_ _____ ____| |__ - \____ \| ___ | (_ _) ___ |/ ___) _ \ - _____) ) ____| | | || |_| ____( (___| | | | -(______/|_____)_|_|_| \__)_____)\____)_| |_| - (C)2013 Semtech-Cycleo - -Description: - Base64 encoding & decoding library - -License: Revised BSD License, see LICENSE.TXT file include in the project -Maintainer: Sylvain Miermont -*/ - - -#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/beacon_pkt_fwd/inc/parson.h b/beacon_pkt_fwd/inc/parson.h deleted file mode 100644 index fd29e9b4..00000000 --- a/beacon_pkt_fwd/inc/parson.h +++ /dev/null @@ -1,111 +0,0 @@ -/* - Parson ( http://kgabis.github.com/parson/ ) - Copyright (C) 2013 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 */ - -#define PARSON_VERSION 20131130 - -/* Types and enums */ -typedef struct json_object_t JSON_Object; -typedef struct json_array_t JSON_Array; -typedef struct json_value_t JSON_Value; - -typedef enum json_value_type { - JSONError = 0, - JSONNull = 1, - JSONString = 2, - JSONNumber = 3, - JSONObject = 4, - JSONArray = 5, - JSONBoolean = 6 -} JSON_Value_Type; - - -/* 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); - -/* 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); -int json_object_get_boolean(const JSON_Object *object, const char *name); - -/* 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); -int json_object_dotget_boolean(const JSON_Object *object, const char *name); - -/* 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); - -/* 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); -int json_array_get_boolean(const JSON_Array *array, size_t index); -size_t json_array_get_count (const JSON_Array *array); - -/* JSON 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); -void json_value_free (JSON_Value *value); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/beacon_pkt_fwd/local_conf.json b/beacon_pkt_fwd/local_conf.json deleted file mode 100644 index cf6ff56d..00000000 --- a/beacon_pkt_fwd/local_conf.json +++ /dev/null @@ -1,7 +0,0 @@ -{ -/* Put there parameters that are different for each gateway (eg. pointing one gateway to a test server while the others stay in production) */ -/* Settings defined in global_conf will be overwritten by those in local_conf */ - "gateway_conf": { - "gateway_ID": "AA555A0000000101" /* you must pick a unique 64b number for each gateway (represented by an hex string) */ - } -} diff --git a/beacon_pkt_fwd/readme.md b/beacon_pkt_fwd/readme.md deleted file mode 100644 index dcb1ba50..00000000 --- a/beacon_pkt_fwd/readme.md +++ /dev/null @@ -1,187 +0,0 @@ - / _____) _ | | - ( (____ _____ ____ _| |_ _____ ____| |__ - \____ \| ___ | (_ _) ___ |/ ___) _ \ - _____) ) ____| | | || |_| ____( (___| | | | - (______/|_____)_|_|_| \__)_____)\____)_| |_| - (C)2013 Semtech-Cycleo - -Lora Gateway packet forwarder with beacon extension -=================================================== - -1. Introduction ----------------- - -The beaconing packet forwarder is a program running on the host of a Lora -Gateway that forward 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 -also emits a network-wide GPS-synchronous beacon signal used for coordinating -all the 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 (SX1301), -transceivers (SX125x) 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. -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.1 version of the gateway-to-server protocol. - -The last dependency is the hardware concentrator (based on SX1301 chips) that -must be matched with the proper version of the HAL. - -4. Usage ---------- - -1. Update JSON configuration files, as explained below. -2. For IoT Starter Kit only, run: - ./reset_pkd_fwd.sh stop - ./reset_pkd_fwd.sh start local_conf.json -3. Run: - ./beacon_pkt_fwd - -To stop the application, press Ctrl+C. -Unless it is manually stopped or encounter a critical error, the program will -run forever. - -There are no command line launch options. - -The way the program takes configuration files into account is the following: - * if there is a debug_conf.json parse it, others are ignored - * if there is a global_conf.json parse it, look for the next file - * if there is a local_conf.json parse it -If some parameters are defined in both global and local configuration files, -the local definition overwrites the global definition. - -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). -As some of the parameters (like 'rssi_offset', 'tx_lut_*') are board dependant, -several flavours of the global_conf.json file are provided in the cfg/ -directory. - * global_conf.json.PCB_E286.EU868: to be used for Semtech reference design - board with PCB name PCB_E286 (also called Gateway Board v1.0 (no FPGA)). - Configured for Europe 868MHz channels. - * global_conf.json.PCB_E336.EU868: to be used for Semtech reference design - board with PCB name PCB_E336 (also called Gateway Board v1.5 (with FPGA)). - Configured for Europe 868MHz channels. - * global_conf.json.US902: to be used for Semtech reference design v1.0 or - v1.5. (No calibration done for RSSI offset and TX gains yet). - Configured for US 902MHz channels. -Rename the one you need to global_conf.json before launching the packet -forwarder. - -The local configuration file should contain parameters that are specific to -each gateway (eg. MAC address, frequency for backhaul radio channels). - -In each configuration file, the program looks for a JSON object named -"SX1301_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. License ------------ - -Copyright (C) 2013, 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/beacon_pkt_fwd/src/base64.c b/beacon_pkt_fwd/src/base64.c deleted file mode 100644 index ca7a815d..00000000 --- a/beacon_pkt_fwd/src/base64.c +++ /dev/null @@ -1,308 +0,0 @@ -/* - / _____) _ | | -( (____ _____ ____ _| |_ _____ ____| |__ - \____ \| ___ | (_ _) ___ |/ ___) _ \ - _____) ) ____| | | || |_| ____( (___| | | | -(______/|_____)_|_|_| \__)_____)\____)_| |_| - (C)2013 Semtech-Cycleo - -Description: - Base64 encoding & decoding library - -License: Revised BSD License, see LICENSE.TXT file include in the project -Maintainer: Sylvain Miermont -*/ - - -/* -------------------------------------------------------------------------- */ -/* --- 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/beacon_pkt_fwd/src/beacon_pkt_fwd.c b/beacon_pkt_fwd/src/beacon_pkt_fwd.c deleted file mode 100644 index a439ab41..00000000 --- a/beacon_pkt_fwd/src/beacon_pkt_fwd.c +++ /dev/null @@ -1,2216 +0,0 @@ -/* - / _____) _ | | -( (____ _____ ____ _| |_ _____ ____| |__ - \____ \| ___ | (_ _) ___ |/ ___) _ \ - _____) ) ____| | | || |_| ____( (___| | | | -(______/|_____)_|_|_| \__)_____)\____)_| |_| - (C)2013 Semtech-Cycleo - -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 -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 /* 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 "parson.h" -#include "base64.h" -#include "loragw_hal.h" -#include "loragw_gps.h" -#include "loragw_aux.h" - -/* -------------------------------------------------------------------------- */ -/* --- PRIVATE MACROS ------------------------------------------------------- */ - -#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) -#define STRINGIFY(x) #x -#define STR(x) STRINGIFY(x) -#define MSG(args...) printf(args) /* message that is destined to the user */ -#define TRACE() fprintf(stderr, "@ %s %d\n", __FUNCTION__, __LINE__); - -/* -------------------------------------------------------------------------- */ -/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ - -#ifndef VERSION_STRING - #define VERSION_STRING "undefined" -#endif - -#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 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 NB_PKT_MAX 8 /* 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 4 - -#define STATUS_SIZE 200 -#define TX_BUFF_SIZE ((540 * NB_PKT_MAX) + 30 + STATUS_SIZE) - -/* -------------------------------------------------------------------------- */ -/* --- 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 */ -static 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]; /* path of the TTY port GPS is connected on */ -static int gps_tty_fd; /* file descriptor of the GPS TTY port */ -static bool gps_enabled; /* 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 UTC <-> 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 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 = 128; /* set beaconing period, must be a sub-multiple of 86400, the nb of sec in a day */ -static uint32_t beacon_offset = 0; /* must be < beacon_period, set when the beacon is emitted */ -static uint32_t beacon_freq_hz = 0; /* TX beacon frequency, in Hz */ -static bool beacon_next_pps = false; /* signal to prepare beacon packet for TX, no need for mutex */ - -/* auto-quit function */ -static uint32_t autoquit_threshold = 0; /* enable auto-quit after a number of non-acknowledged PULL_DATA (0 = disabled)*/ - -/* -------------------------------------------------------------------------- */ -/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ - -static void sig_handler(int sigio); - -static int parse_SX1301_configuration(const char * conf_file); - -static int parse_gateway_configuration(const char * conf_file); - -static uint16_t crc_ccit(const uint8_t * data, unsigned size); - -static uint8_t crc8_ccit(const uint8_t * data, unsigned size); - -static double difftimespec(struct timespec end, struct timespec beginning); - -/* threads */ -void thread_up(void); -void thread_down(void); -void thread_gps(void); -void thread_valid(void); - -/* -------------------------------------------------------------------------- */ -/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ - -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_SX1301_configuration(const char * conf_file) { - int i; - 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[] = "SX1301_conf"; - JSON_Value *root_val = NULL; - JSON_Object *conf_obj = NULL; - JSON_Value *val = NULL; - struct lgw_conf_board_s boardconf; - struct lgw_conf_rxrf_s rfconf; - struct lgw_conf_rxif_s ifconf; - uint32_t sf, bw, fdev; - struct lgw_tx_gain_lut_s txlut; - - /* 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 SX1301 parameters\n", conf_file, conf_obj_name); - } - - /* set board configuration */ - memset(&boardconf, 0, sizeof boardconf); /* initialize configuration structure */ - 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; - } - MSG("INFO: lorawan_public %d, clksrc %d\n", boardconf.lorawan_public, boardconf.clksrc); - /* all parameters parsed, submitting configuration to the HAL */ - if (lgw_board_setconf(boardconf) != LGW_HAL_SUCCESS) { - MSG("WARNING: Failed to configure board\n"); - } - - /* set configuration for tx gains */ - memset(&txlut, 0, sizeof txlut); /* initialize configuration structure */ - for (i = 0; i < TX_GAIN_LUT_SIZE_MAX; i++) { - snprintf(param_name, sizeof param_name, "tx_lut_%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 tx gain lut %i\n", i); - continue; - } - txlut.size++; /* update TX LUT size based on JSON object found in configuration file */ - /* there is an object to configure that TX gain index, let's parse it */ - snprintf(param_name, sizeof param_name, "tx_lut_%i.pa_gain", i); - val = json_object_dotget_value(conf_obj, param_name); - if (json_value_get_type(val) == JSONNumber) { - txlut.lut[i].pa_gain = (uint8_t)json_value_get_number(val); - } else { - MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); - txlut.lut[i].pa_gain = 0; - } - snprintf(param_name, sizeof param_name, "tx_lut_%i.dac_gain", i); - val = json_object_dotget_value(conf_obj, param_name); - if (json_value_get_type(val) == JSONNumber) { - txlut.lut[i].dac_gain = (uint8_t)json_value_get_number(val); - } else { - txlut.lut[i].dac_gain = 3; /* This is the only dac_gain supported for now */ - } - snprintf(param_name, sizeof param_name, "tx_lut_%i.dig_gain", i); - val = json_object_dotget_value(conf_obj, param_name); - if (json_value_get_type(val) == JSONNumber) { - txlut.lut[i].dig_gain = (uint8_t)json_value_get_number(val); - } else { - MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); - txlut.lut[i].dig_gain = 0; - } - snprintf(param_name, sizeof param_name, "tx_lut_%i.mix_gain", i); - val = json_object_dotget_value(conf_obj, param_name); - if (json_value_get_type(val) == JSONNumber) { - txlut.lut[i].mix_gain = (uint8_t)json_value_get_number(val); - } else { - MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); - txlut.lut[i].mix_gain = 0; - } - snprintf(param_name, sizeof param_name, "tx_lut_%i.rf_power", i); - val = json_object_dotget_value(conf_obj, param_name); - if (json_value_get_type(val) == JSONNumber) { - txlut.lut[i].rf_power = (int8_t)json_value_get_number(val); - } else { - MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); - txlut.lut[i].rf_power = 0; - } - } - /* all parameters parsed, submitting configuration to the HAL */ - MSG("INFO: Configuring TX LUT with %u indexes\n", txlut.size); - if (lgw_txgain_setconf(&txlut) != LGW_HAL_SUCCESS) { - MSG("WARNING: Failed to configure concentrator TX Gain LUT\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.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 { - MSG("WARNING: invalid radio type: %s (should be SX1255 or SX1257)\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); - } 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("WARNING: invalid configuration for radio %i\n", i); - } - } - - /* 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 7 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("WARNING: invalid configuration for Lora multi-SF channel %i\n", i); - } - } - - /* 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 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; - } - MSG("INFO: Lora std channel> radio %i, IF %i Hz, %u Hz bw, SF %u\n", ifconf.rf_chain, ifconf.freq_hz, bw, sf); - } - if (lgw_rxif_setconf(8, ifconf) != LGW_HAL_SUCCESS) { - MSG("WARNING: invalid configuration for Lora standard channel\n"); - } - } - - /* 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; - 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; - 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("WARNING: invalid configuration for FSK channel\n"); - } - } - 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); - MSG("INFO: Beaconing period is configured to %u seconds\n", beacon_period); - } - - /* Beacon signal period (optional) */ - val = json_object_get_value(conf_obj, "beacon_offset"); - if (val != NULL) { - beacon_offset = (uint32_t)json_value_get_number(val); - MSG("INFO: Beaconing signal offset is configured to %u seconds\n", beacon_offset); - } - - /* 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); - } - - /* 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 uint16_t crc_ccit(const uint8_t * data, unsigned size) { - const uint16_t crc_poly = 0x1021; /* CCITT */ - const uint16_t init_val = 0xFFFF; /* CCITT */ - uint16_t x = init_val; - unsigned i, j; - - if (data == NULL) { - return 0; - } - - for (i=0; i>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); - - /* 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); - } - - /* 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); - } - - /* 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; - 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; - 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; - //cp_gps_err = meas_gps_err; - pthread_mutex_unlock(&mx_meas_gps); - } - - /* overwrite with reference coordinates if function is enabled */ - if (gps_fake_enable == true) { - gps_enabled = true; - coord_ok = 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); - 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 (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 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 { - 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)) { - 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 */ - if (gps_enabled == true) { - pthread_cancel(thrid_gps); /* don't wait for GPS thread */ - pthread_cancel(thrid_valid); /* don't wait for validation thread */ - } - - /* 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"); - } - } - - MSG("INFO: Exiting packet forwarder program\n"); - exit(EXIT_SUCCESS); -} - -/* -------------------------------------------------------------------------- */ -/* --- THREAD 1: RECEIVING PACKETS AND FORWARDING THEM ---------------------- */ - -void thread_up(void) { - int i, j; /* loop variables */ - unsigned pkt_in_dgram; /* nb on Lora packet in the current datagram */ - - /* 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 */ - - /* report management variable */ - bool send_report = false; - - /* 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; - } - - /* 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]; - - /* 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->rssi); - 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); - - /* 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; - } - - /* 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); - } - } - } - - /* 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", p->if_chain, p->rf_chain, ((double)p->freq_hz / 1e6)); - 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\n"); - 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_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\n"); - 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\n"); - 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\n"); - memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"?\"", 11); - buff_index += 11; - exit(EXIT_FAILURE); - } - - /* Lora SNR, 11-13 useful chars */ - 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); - } - } 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\n"); - exit(EXIT_FAILURE); - } - - /* Packet RSSI, payload size, 18-23 useful chars */ - j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"rssi\":%.0f,\"size\":%u", p->rssi, 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; - } - - /* 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 EMITTING PACKETS ------------------------ */ - -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; - short x2, x3, x4; - double x5, x6; - - /* variables to send on UTC timestamp */ - struct tref local_ref; /* time reference used for UTC <-> timestamp conversion */ - struct tm utc_vector; /* for collecting the elements of the UTC time */ - struct timespec utc_tx; /* UTC time that needs to be converted to timestamp */ - - /* beacon variables */ - struct lgw_pkt_tx_s beacon_pkt; - uint8_t tx_status_var; - - /* beacon data fields, byte 0 is Least Significant Byte */ - uint32_t field_netid = 0xC0FFEE; /* ID, 3 bytes only */ - uint32_t field_time; /* variable field */ - uint8_t field_crc1; /* variable field */ - uint8_t field_info = 0; - int32_t field_latitude; /* 3 bytes, derived from reference latitude */ - int32_t field_longitude; /* 3 bytes, derived from reference longitude */ - uint16_t field_crc2; - - /* auto-quit variable */ - uint32_t autoquit_cnt = 0; /* count the number of PULL_DATA sent since the latest PULL_ACK */ - - /* 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 packet parameters */ - beacon_pkt.tx_mode = ON_GPS; /* send on PPS pulse */ - beacon_pkt.rf_chain = 0; /* antenna A */ - beacon_pkt.rf_power = 14; - beacon_pkt.modulation = MOD_LORA; - beacon_pkt.bandwidth = BW_125KHZ; - beacon_pkt.datarate = DR_LORA_SF9; - beacon_pkt.coderate = CR_LORA_4_5; - beacon_pkt.invert_pol = true; - beacon_pkt.preamble = 6; - beacon_pkt.no_crc = true; - beacon_pkt.no_header = true; - beacon_pkt.size = 17; - - /* fixed bacon fields (little endian) */ - beacon_pkt.payload[0] = 0xFF & field_netid; - beacon_pkt.payload[1] = 0xFF & (field_netid >> 8); - beacon_pkt.payload[2] = 0xFF & (field_netid >> 16); - /* 3-6 : time (variable) */ - /* 7 : crc1 (variable) */ - - /* 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 = 0x00FFFFFF & (int32_t)((reference_coord.lon / 180.0) * (double)(1<<23)); /* +180 = -180 = 0x800000 */ - - /* optional beacon fields */ - beacon_pkt.payload[ 8] = field_info; - beacon_pkt.payload[ 9] = 0xFF & field_latitude; - beacon_pkt.payload[10] = 0xFF & (field_latitude >> 8); - beacon_pkt.payload[11] = 0xFF & (field_latitude >> 16); - beacon_pkt.payload[12] = 0xFF & field_longitude; - beacon_pkt.payload[13] = 0xFF & (field_longitude >> 8); - beacon_pkt.payload[14] = 0xFF & (field_longitude >> 16); - - field_crc2 = crc_ccit((beacon_pkt.payload + 8), 7); /* CRC optional 7 bytes */ - beacon_pkt.payload[15] = 0xFF & field_crc2; - beacon_pkt.payload[16] = 0xFF & (field_crc2 >> 8); - - 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); - - /* if beacon must be prepared, load it and wait for it to trigger */ - if ((beacon_next_pps == true) && (gps_enabled == true)) { - pthread_mutex_lock(&mx_timeref); - beacon_next_pps = false; - if ((gps_ref_valid == true) && (xtal_correct_ok == true)) { - field_time = time_reference_gps.utc.tv_sec + 1; /* the beacon is prepared 1 sec before becon time */ - pthread_mutex_unlock(&mx_timeref); - - /* load time in beacon payload */ - beacon_pkt.payload[ 9] = 0xFF & field_time; - beacon_pkt.payload[10] = 0xFF & (field_time >> 8); - beacon_pkt.payload[11] = 0xFF & (field_time >> 16); - beacon_pkt.payload[12] = 0xFF & (field_time >> 24); - - /* calculate CRC */ - field_crc1 = crc8_ccit(beacon_pkt.payload, 7); /* CRC for the first 7 bytes */ - beacon_pkt.payload[7] = field_crc1; - - /* apply frequency correction to beacon TX frequency */ - pthread_mutex_lock(&mx_xcorr); - beacon_pkt.freq_hz = (uint32_t)(xtal_correct * (double)beacon_freq_hz); - pthread_mutex_unlock(&mx_xcorr); - MSG("NOTE: [down] beacon ready to send (frequency %u Hz)\n", beacon_pkt.freq_hz); - - /* display beacon payload */ - MSG("--- Beacon payload ---\n"); - for (i=0; i<24; ++i) { - MSG("0x%02X", beacon_pkt.payload[i]); - if (i%8 == 7) { - MSG("\n"); - } else { - MSG(" - "); - } - } - if (i%8 != 0) { - MSG("\n"); - } - MSG("--- end of payload ---\n"); - - /* send bacon packet and check for status */ - pthread_mutex_lock(&mx_concent); /* may have to wait for a fetch to finish */ - i = lgw_send(beacon_pkt); - pthread_mutex_unlock(&mx_concent); /* free concentrator ASAP */ - if (i == LGW_HAL_ERROR) { - MSG("WARNING: [down] failed to send beacon packet\n"); - } else { - tx_status_var = TX_STATUS_UNKNOWN; - for (i=0; (i < (1500/BEACON_POLL_MS)) && (tx_status_var != TX_FREE); ++i) { - wait_ms(BEACON_POLL_MS); - pthread_mutex_lock(&mx_concent); - lgw_status(TX_STATUS, &tx_status_var); - pthread_mutex_unlock(&mx_concent); - } - if (tx_status_var == TX_FREE) { - MSG("NOTE: [down] beacon sent successfully\n"); - } else { - MSG("WARNING: [down] beacon was scheduled but failed to TX\n"); - } - } - } else { - pthread_mutex_unlock(&mx_timeref); - } - } - - /* 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\n"); - 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 :)\n"); /* 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; - 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); - MSG("INFO: [down] a packet will be sent on timestamp value %u\n", txpkt.count_us); - } else { - /* TX procedure: send on UTC time (converted to timestamp value) */ - str = json_object_get_string(txpk_obj, "time"); - if (str == NULL) { - MSG("WARNING: [down] no mandatory \"txpk.tmst\" or \"txpk.time\" 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 UTC time, TX aborted\n"); - json_value_free(root_val); - continue; - } - } else { - MSG("WARNING: [down] GPS disabled, impossible to send packet on specific UTC time, TX aborted\n"); - json_value_free(root_val); - continue; - } - - i = sscanf (str, "%4hd-%2hd-%2hdT%2hd:%2hd:%9lf", &x0, &x1, &x2, &x3, &x4, &x5); - if (i != 6 ) { - MSG("WARNING: [down] \"txpk.time\" must follow ISO 8601 format, TX aborted\n"); - json_value_free(root_val); - continue; - } - x5 = modf(x5, &x6); /* x6 get the integer part of x5, x5 the fractional part */ - utc_vector.tm_year = x0 - 1900; /* years since 1900 */ - utc_vector.tm_mon = x1 - 1; /* months since January */ - utc_vector.tm_mday = x2; /* day of the month 1-31 */ - utc_vector.tm_hour = x3; /* hours since midnight */ - utc_vector.tm_min = x4; /* minutes after the hour */ - utc_vector.tm_sec = (int)x6; - utc_tx.tv_sec = mktime(&utc_vector) - timezone; - utc_tx.tv_nsec = (long)(1e9 * x5); - - /* transform UTC time to timestamp */ - i = lgw_utc2cnt(local_ref, utc_tx, &(txpkt.count_us)); - if (i != LGW_GPS_SUCCESS) { - MSG("WARNING: [down] could not convert UTC 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 UTC time)\n", txpkt.count_us); - } - } - } - - /* 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); - } - - /* 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 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; - - /* transfer data and metadata to the concentrator, and schedule TX */ - pthread_mutex_lock(&mx_concent); /* may have to wait for a fetch to finish */ - i = lgw_send(txpkt); - pthread_mutex_unlock(&mx_concent); /* free concentrator ASAP */ - if (i == LGW_HAL_ERROR) { - meas_nb_tx_fail += 1; - pthread_mutex_unlock(&mx_meas_dw); - MSG("WARNING: [down] lgw_send failed\n"); - continue; - } else { - meas_nb_tx_ok += 1; - pthread_mutex_unlock(&mx_meas_dw); - } - } - } - MSG("\nINFO: End of downstream thread\n"); -} - -/* -------------------------------------------------------------------------- */ -/* --- THREAD 3: PARSE GPS MESSAGE AND KEEP GATEWAY IN SYNC ----------------- */ - -void thread_gps(void) { - int i; - - /* serial variables */ - char serial_buff[128]; /* buffer to receive GPS data */ - ssize_t nb_char; - - /* variables for PPM pulse GPS synchronization */ - enum gps_msg latest_msg; /* keep track of latest NMEA message parsed */ - struct timespec utc_time; /* UTC time associated with PPS pulse */ - uint32_t trig_tstamp; /* concentrator timestamp associated with PPM pulse */ - - /* position variable */ - struct coord_s coord; - struct coord_s gpserr; - - /* variables for beaconing */ - uint32_t sec_of_cycle; - - /* initialize some variables before loop */ - memset(serial_buff, 0, sizeof serial_buff); - - while (!exit_sig && !quit_sig) { - /* blocking canonical read on serial port */ - nb_char = read(gps_tty_fd, serial_buff, sizeof(serial_buff)-1); - if (nb_char <= 0) { - MSG("WARNING: [gps] read() returned value <= 0\n"); - continue; - } else { - serial_buff[nb_char] = 0; /* add null terminator, just to be sure */ - } - - /* parse the received NMEA */ - latest_msg = lgw_parse_nmea(serial_buff, sizeof(serial_buff)); - - if (latest_msg == NMEA_RMC) { /* trigger sync only on RMC frames */ - - /* get UTC time for synchronization */ - i = lgw_gps_get(&utc_time, NULL, NULL); - if (i != LGW_GPS_SUCCESS) { - MSG("WARNING: [gps] could not get UTC time from GPS\n"); - continue; - } - - /* check if beacon must be sent */ - if (beacon_period > 0) { - sec_of_cycle = (utc_time.tv_sec + 1) % (time_t)(beacon_period); - if (sec_of_cycle == beacon_offset) { - beacon_next_pps = true; - } else { - beacon_next_pps = false; - } - } - - /* 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"); - continue; - } - - /* try to update time reference with the new UTC & timestamp */ - pthread_mutex_lock(&mx_timeref); - i = lgw_gps_sync(&time_reference_gps, trig_tstamp, utc_time); - pthread_mutex_unlock(&mx_timeref); - if (i != LGW_GPS_SUCCESS) { - MSG("WARNING: [gps] GPS out of sync, keeping previous time reference\n"); - continue; - } - - /* update gateway coordinates */ - i = lgw_gps_get(NULL, &coord, &gpserr); - 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); - } - } - MSG("\nINFO: End of GPS thread\n"); -} - -/* -------------------------------------------------------------------------- */ -/* --- THREAD 4: 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; - } 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; - 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/beacon_pkt_fwd/src/parson.c b/beacon_pkt_fwd/src/parson.c deleted file mode 100644 index 8756531d..00000000 --- a/beacon_pkt_fwd/src/parson.c +++ /dev/null @@ -1,782 +0,0 @@ -/* - Parson ( http://kgabis.github.com/parson/ ) - Copyright (C) 2013 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. -*/ - -#include "parson.h" - -#include -#include -#include -#include - -#define ERROR 0 -#define SUCCESS 1 -#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 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)) - -#define parson_malloc(a) malloc(a) -#define parson_free(a) free((void*)a) -#define parson_realloc(a, b) realloc(a, b) - -/* Type definitions */ -typedef union json_value_value { - const 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 { - const 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 int try_realloc(void **ptr, size_t new_size); -static char * parson_strndup(const char *string, size_t n); -static int is_utf(const unsigned char *string); -static int is_decimal(const char *string, size_t length); - -/* JSON Object */ -static JSON_Object * json_object_init(void); -static int json_object_add(JSON_Object *object, const char *name, JSON_Value *value); -static int json_object_resize(JSON_Object *object, size_t 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 int json_array_add(JSON_Array *array, JSON_Value *value); -static int json_array_resize(JSON_Array *array, size_t capacity); -static void json_array_free(JSON_Array *array); - -/* JSON Value */ -static JSON_Value * json_value_init_object(void); -static JSON_Value * json_value_init_array(void); -static JSON_Value * json_value_init_string(const char *string); -static JSON_Value * json_value_init_number(double number); -static JSON_Value * json_value_init_boolean(int boolean); -static JSON_Value * json_value_init_null(void); - -/* Parser */ -static void skip_quotes(const char **string); -static const char * get_processed_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); - -/* Various */ -static int try_realloc(void **ptr, size_t new_size) { - void *reallocated_ptr = parson_realloc(*ptr, new_size); - if (!reallocated_ptr) - return ERROR; - *ptr = reallocated_ptr; - return SUCCESS; -} - -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 int is_utf(const unsigned char *s) { - return isxdigit(s[0]) && isxdigit(s[1]) && isxdigit(s[2]) && isxdigit(s[3]); -} - -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; - char *file_contents; - if (!fp) - return NULL; - fseek(fp, 0L, SEEK_END); - file_size = ftell(fp); - 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 = (const char**)NULL; - new_obj->values = (JSON_Value**)NULL; - new_obj->capacity = 0; - new_obj->count = 0; - return new_obj; -} - -static int json_object_add(JSON_Object *object, const char *name, JSON_Value *value) { - size_t index; - if (object->count >= object->capacity) { - size_t new_capacity = MAX(object->capacity * 2, STARTING_CAPACITY); - if (new_capacity > OBJECT_MAX_CAPACITY) - return ERROR; - if (json_object_resize(object, new_capacity) == ERROR) - return ERROR; - } - if (json_object_get_value(object, name) != NULL) - return ERROR; - index = object->count; - object->names[index] = parson_strndup(name, strlen(name)); - if (!object->names[index]) - return ERROR; - object->values[index] = value; - object->count++; - return SUCCESS; -} - -static int json_object_resize(JSON_Object *object, size_t capacity) { - if (try_realloc((void**)&object->names, capacity * sizeof(char*)) == ERROR) - return ERROR; - if (try_realloc((void**)&object->values, capacity * sizeof(JSON_Value*)) == ERROR) - return ERROR; - object->capacity = capacity; - return SUCCESS; -} - -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 int 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 ERROR; - if (!json_array_resize(array, new_capacity)) - return ERROR; - } - array->items[array->count] = value; - array->count++; - return SUCCESS; -} - -static int json_array_resize(JSON_Array *array, size_t capacity) { - if (try_realloc((void**)&array->items, capacity * sizeof(JSON_Value*)) == ERROR) - return ERROR; - array->capacity = capacity; - return SUCCESS; -} - -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_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; -} - -static 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; -} - -static JSON_Value * json_value_init_string(const 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; -} - -static 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; -} - -static 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; - return new_value; -} - -static 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; -} - -/* 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); -} - -/* Returns contents of a string inside double quotes and parses escaped - characters inside. - Example: "\u006Corem ipsum" -> lorem ipsum */ -static const char * get_processed_string(const char **string) { - const char *string_start = *string; - char *output, *processed_ptr, *unprocessed_ptr, current_char; - unsigned int utf_val; - skip_quotes(string); - if (**string == '\0') - return NULL; - output = parson_strndup(string_start + 1, *string - string_start - 2); - if (!output) - return NULL; - processed_ptr = unprocessed_ptr = output; - while (*unprocessed_ptr) { - current_char = *unprocessed_ptr; - if (current_char == '\\') { - unprocessed_ptr++; - current_char = *unprocessed_ptr; - switch (current_char) { - case '\"': case '\\': case '/': break; - case 'b': current_char = '\b'; break; - case 'f': current_char = '\f'; break; - case 'n': current_char = '\n'; break; - case 'r': current_char = '\r'; break; - case 't': current_char = '\t'; break; - case 'u': - unprocessed_ptr++; - if (!is_utf((const unsigned char*)unprocessed_ptr) || - sscanf(unprocessed_ptr, "%4x", &utf_val) == EOF) { - parson_free(output); - return NULL; - } - if (utf_val < 0x80) { - current_char = utf_val; - } else if (utf_val < 0x800) { - *processed_ptr++ = (utf_val >> 6) | 0xC0; - current_char = ((utf_val | 0x80) & 0xBF); - } else { - *processed_ptr++ = (utf_val >> 12) | 0xE0; - *processed_ptr++ = (((utf_val >> 6) | 0x80) & 0xBF); - current_char = ((utf_val | 0x80) & 0xBF); - } - unprocessed_ptr += 3; - break; - default: - parson_free(output); - return NULL; - break; - } - } else if ((unsigned char)current_char < 0x20) { /* 0x00-0x19 are invalid characters for json string (http://www.ietf.org/rfc/rfc4627.txt) */ - parson_free(output); - return NULL; - } - *processed_ptr = current_char; - processed_ptr++; - unprocessed_ptr++; - } - *processed_ptr = '\0'; - if (try_realloc((void**)&output, strlen(output) + 1) == ERROR) - return NULL; - return output; -} - -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); - const char *new_key = NULL; - if (!output_value) - return NULL; - skip_char(string); - skip_whitespaces(string); - if (**string == '}') { /* empty object */ - skip_char(string); - return output_value; - } - while (**string != '\0') { - new_key = get_processed_string(string); - skip_whitespaces(string); - if (!new_key || **string != ':') { - json_value_free(output_value); - return NULL; - } - skip_char(string); - new_value = parse_value(string, nesting); - if (!new_value) { - parson_free(new_key); - json_value_free(output_value); - return NULL; - } - if(!json_object_add(output_object, new_key, new_value)) { - 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)) == ERROR) { - 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) == ERROR) { - 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)) == ERROR) { - json_value_free(output_value); - return NULL; - } - skip_char(string); - return output_value; -} - -static JSON_Value * parse_string_value(const char **string) { - const char *new_string = get_processed_string(string); - if (!new_string) - return NULL; - return json_value_init_string(new_string); -} - -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; -} - -/* Parser API */ -JSON_Value * json_parse_file(const char *filename) { - char *file_contents = read_file(filename); - JSON_Value *output_value = NULL; - if (!file_contents) - 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) - 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 || (*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_strndup(string, strlen(string)); - if (!string_mutable_copy) - 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) { - 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); -} diff --git a/gps_pkt_fwd/cfg/global_conf.json.US902 b/gps_pkt_fwd/cfg/global_conf.json.US902 deleted file mode 100644 index 38dcd9ab..00000000 --- a/gps_pkt_fwd/cfg/global_conf.json.US902 +++ /dev/null @@ -1,103 +0,0 @@ -{ - "SX1301_conf": { - "lorawan_public": true, - "clksrc": 1, /* radio_1 provides clock to concentrator */ - "radio_0": { - "enable": true, - "type": "SX1257", - "freq": 902700000, - "rssi_offset": -166.0, - "tx_enable": true - }, - "radio_1": { - "enable": true, - "type": "SX1257", - "freq": 903400000, - "rssi_offset": -166.0, - "tx_enable": false - }, - "chan_multiSF_0": { - /* Lora MAC channel, 125kHz, all SF, 902.3 MHz */ - "enable": true, - "radio": 0, - "if": -400000 - }, - "chan_multiSF_1": { - /* Lora MAC channel, 125kHz, all SF, 902.5 MHz */ - "enable": true, - "radio": 0, - "if": -200000 - }, - "chan_multiSF_2": { - /* Lora MAC channel, 125kHz, all SF, 902.7 MHz */ - "enable": true, - "radio": 0, - "if": 0 - }, - "chan_multiSF_3": { - /* Lora MAC channel, 125kHz, all SF, 902.9 MHz */ - "enable": true, - "radio": 0, - "if": 200000 - }, - "chan_multiSF_4": { - /* Lora MAC channel, 125kHz, all SF, 903.1 MHz */ - "enable": true, - "radio": 1, - "if": -300000 - }, - "chan_multiSF_5": { - /* Lora MAC channel, 125kHz, all SF, 903.3 MHz */ - "enable": true, - "radio": 1, - "if": -100000 - }, - "chan_multiSF_6": { - /* Lora MAC channel, 125kHz, all SF, 903.5 MHz */ - "enable": true, - "radio": 1, - "if": 100000 - }, - "chan_multiSF_7": { - /* Lora MAC channel, 125kHz, all SF, 903.7 MHz */ - "enable": true, - "radio": 1, - "if": 300000 - }, - "chan_Lora_std": { - /* Lora MAC channel, 500kHz, SF8, 903.0 MHz */ - "enable": true, - "radio": 0, - "if": 300000, - "bandwidth": 500000, - "spread_factor": 8 - }, - "chan_FSK": { - /* FSK 100kbps channel, 927.3 MHz */ - "enable": false, - "radio": 0, - "if": 300000, - "bandwidth": 250000, - "datarate": 100000 - } - }, - - "gateway_conf": { - "gateway_ID": "AA555A0000000000", - /* change with default server address/ports, or overwrite in local_conf.json */ - "server_address": "localhost", - "serv_port_up": 1680, - "serv_port_down": 1680, - /* 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/ttyAMA0" - } -} - diff --git a/gps_pkt_fwd/inc/parson.h b/gps_pkt_fwd/inc/parson.h deleted file mode 100644 index fd29e9b4..00000000 --- a/gps_pkt_fwd/inc/parson.h +++ /dev/null @@ -1,111 +0,0 @@ -/* - Parson ( http://kgabis.github.com/parson/ ) - Copyright (C) 2013 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 */ - -#define PARSON_VERSION 20131130 - -/* Types and enums */ -typedef struct json_object_t JSON_Object; -typedef struct json_array_t JSON_Array; -typedef struct json_value_t JSON_Value; - -typedef enum json_value_type { - JSONError = 0, - JSONNull = 1, - JSONString = 2, - JSONNumber = 3, - JSONObject = 4, - JSONArray = 5, - JSONBoolean = 6 -} JSON_Value_Type; - - -/* 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); - -/* 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); -int json_object_get_boolean(const JSON_Object *object, const char *name); - -/* 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); -int json_object_dotget_boolean(const JSON_Object *object, const char *name); - -/* 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); - -/* 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); -int json_array_get_boolean(const JSON_Array *array, size_t index); -size_t json_array_get_count (const JSON_Array *array); - -/* JSON 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); -void json_value_free (JSON_Value *value); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/gps_pkt_fwd/local_conf.json b/gps_pkt_fwd/local_conf.json deleted file mode 100644 index cf6ff56d..00000000 --- a/gps_pkt_fwd/local_conf.json +++ /dev/null @@ -1,7 +0,0 @@ -{ -/* Put there parameters that are different for each gateway (eg. pointing one gateway to a test server while the others stay in production) */ -/* Settings defined in global_conf will be overwritten by those in local_conf */ - "gateway_conf": { - "gateway_ID": "AA555A0000000101" /* you must pick a unique 64b number for each gateway (represented by an hex string) */ - } -} diff --git a/gps_pkt_fwd/readme.md b/gps_pkt_fwd/readme.md deleted file mode 100644 index bd991113..00000000 --- a/gps_pkt_fwd/readme.md +++ /dev/null @@ -1,185 +0,0 @@ - / _____) _ | | - ( (____ _____ ____ _| |_ _____ ____| |__ - \____ \| ___ | (_ _) ___ |/ ___) _ \ - _____) ) ____| | | || |_| ____( (___| | | | - (______/|_____)_|_|_| \__)_____)\____)_| |_| - (C)2013 Semtech-Cycleo - -Lora Gateway packet forwarder with GPS extensions -================================================= - -1. Introduction ----------------- - -The GPS packet forwarder is a program running on the host of a Lora Gateway -that forward RF packets receive by the concentrator to a server through a -IP/UDP link, and emits RF packets that are sent by the server. - -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 (SX1301), -transceivers (SX125x) 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. -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.1 version of the gateway-to-server protocol. - -The last dependency is the hardware concentrator (based on SX1301 chips) that -must be matched with the proper version of the HAL. - -4. Usage ---------- - -1. Update JSON configuration files, as explained below. -2. For IoT Starter Kit only, run: - ./reset_pkd_fwd.sh stop - ./reset_pkd_fwd.sh start local_conf.json -3. Run: - ./gps_pkt_fwd - -To stop the application, press Ctrl+C. -Unless it is manually stopped or encounter a critical error, the program will -run forever. - -There are no command line launch options. - -The way the program takes configuration files into account is the following: - * if there is a debug_conf.json parse it, others are ignored - * if there is a global_conf.json parse it, look for the next file - * if there is a local_conf.json parse it -If some parameters are defined in both global and local configuration files, -the local definition overwrites the global definition. - -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). -As some of the parameters (like 'rssi_offset', 'tx_lut_*') are board dependant, -several flavours of the global_conf.json file are provided in the cfg/ -directory. - * global_conf.json.PCB_E286.EU868: to be used for Semtech reference design - board with PCB name PCB_E286 (also called Gateway Board v1.0 (no FPGA)). - Configured for Europe 868MHz channels. - * global_conf.json.PCB_E336.EU868: to be used for Semtech reference design - board with PCB name PCB_E336 (also called Gateway Board v1.5 (with FPGA)). - Configured for Europe 868MHz channels. - * global_conf.json.US902: to be used for Semtech reference design v1.0 or - v1.5. (No calibration done for RSSI offset and TX gains yet). - Configured for US 902MHz channels. -Rename the one you need to global_conf.json before launching the packet -forwarder. - -The local configuration file should contain parameters that are specific to -each gateway (eg. MAC address, frequency for backhaul radio channels). - -In each configuration file, the program looks for a JSON object named -"SX1301_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. License ------------ - -Copyright (C) 2013, 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/gps_pkt_fwd/src/base64.c b/gps_pkt_fwd/src/base64.c deleted file mode 100644 index ca7a815d..00000000 --- a/gps_pkt_fwd/src/base64.c +++ /dev/null @@ -1,308 +0,0 @@ -/* - / _____) _ | | -( (____ _____ ____ _| |_ _____ ____| |__ - \____ \| ___ | (_ _) ___ |/ ___) _ \ - _____) ) ____| | | || |_| ____( (___| | | | -(______/|_____)_|_|_| \__)_____)\____)_| |_| - (C)2013 Semtech-Cycleo - -Description: - Base64 encoding & decoding library - -License: Revised BSD License, see LICENSE.TXT file include in the project -Maintainer: Sylvain Miermont -*/ - - -/* -------------------------------------------------------------------------- */ -/* --- 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/gps_pkt_fwd/src/gps_pkt_fwd.c b/gps_pkt_fwd/src/gps_pkt_fwd.c deleted file mode 100644 index fe3dc97b..00000000 --- a/gps_pkt_fwd/src/gps_pkt_fwd.c +++ /dev/null @@ -1,1951 +0,0 @@ -/* - / _____) _ | | -( (____ _____ ____ _| |_ _____ ____| |__ - \____ \| ___ | (_ _) ___ |/ ___) _ \ - _____) ) ____| | | || |_| ____( (___| | | | -(______/|_____)_|_|_| \__)_____)\____)_| |_| - (C)2013 Semtech-Cycleo - -Description: - Configure Lora concentrator and forward packets to a server - Use GPS for packet timestamping. - -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 /* 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 "parson.h" -#include "base64.h" -#include "loragw_hal.h" -#include "loragw_gps.h" -#include "loragw_aux.h" - -/* -------------------------------------------------------------------------- */ -/* --- PRIVATE MACROS ------------------------------------------------------- */ - -#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) -#define STRINGIFY(x) #x -#define STR(x) STRINGIFY(x) -#define MSG(args...) printf(args) /* message that is destined to the user */ -#define TRACE() fprintf(stderr, "@ %s %d\n", __FUNCTION__, __LINE__); - -/* -------------------------------------------------------------------------- */ -/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ - -#ifndef VERSION_STRING - #define VERSION_STRING "undefined" -#endif - -#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 PROTOCOL_VERSION 1 - -#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 NB_PKT_MAX 8 /* 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 4 - -#define STATUS_SIZE 200 -#define TX_BUFF_SIZE ((540 * NB_PKT_MAX) + 30 + STATUS_SIZE) - -/* -------------------------------------------------------------------------- */ -/* --- 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 */ -static pthread_mutex_t mx_concent = PTHREAD_MUTEX_INITIALIZER; /* control access to the concentrator */ - -/* GPS configuration and synchronization */ -static char gps_tty_path[64]; /* path of the TTY port GPS is connected on */ -static int gps_tty_fd; /* file descriptor of the GPS TTY port */ -static bool gps_enabled; /* 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 UTC <-> timestamp conversion */ - -/* Reference coordinates, to send in status instead of GPS coordiantes if 'fake' is enabled */ -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 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 */ - -/* auto-quit function */ -static uint32_t autoquit_threshold = 0; /* enable auto-quit after a number of non-acknowledged PULL_DATA (0 = disabled)*/ - -/* -------------------------------------------------------------------------- */ -/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ - -static void sig_handler(int sigio); - -static int parse_SX1301_configuration(const char * conf_file); - -static int parse_gateway_configuration(const char * conf_file); - -static double difftimespec(struct timespec end, struct timespec beginning); - -/* threads */ -void thread_up(void); -void thread_down(void); -void thread_gps(void); -void thread_valid(void); - -/* -------------------------------------------------------------------------- */ -/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ - -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_SX1301_configuration(const char * conf_file) { - int i; - 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[] = "SX1301_conf"; - JSON_Value *root_val = NULL; - JSON_Object *conf_obj = NULL; - JSON_Value *val = NULL; - struct lgw_conf_board_s boardconf; - struct lgw_conf_rxrf_s rfconf; - struct lgw_conf_rxif_s ifconf; - uint32_t sf, bw, fdev; - struct lgw_tx_gain_lut_s txlut; - - /* 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 SX1301 parameters\n", conf_file, conf_obj_name); - } - - /* set board configuration */ - memset(&boardconf, 0, sizeof boardconf); /* initialize configuration structure */ - 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; - } - MSG("INFO: lorawan_public %d, clksrc %d\n", boardconf.lorawan_public, boardconf.clksrc); - /* all parameters parsed, submitting configuration to the HAL */ - if (lgw_board_setconf(boardconf) != LGW_HAL_SUCCESS) { - MSG("WARNING: Failed to configure board\n"); - } - - /* set configuration for tx gains */ - memset(&txlut, 0, sizeof txlut); /* initialize configuration structure */ - for (i = 0; i < TX_GAIN_LUT_SIZE_MAX; i++) { - snprintf(param_name, sizeof param_name, "tx_lut_%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 tx gain lut %i\n", i); - continue; - } - txlut.size++; /* update TX LUT size based on JSON object found in configuration file */ - /* there is an object to configure that TX gain index, let's parse it */ - snprintf(param_name, sizeof param_name, "tx_lut_%i.pa_gain", i); - val = json_object_dotget_value(conf_obj, param_name); - if (json_value_get_type(val) == JSONNumber) { - txlut.lut[i].pa_gain = (uint8_t)json_value_get_number(val); - } else { - MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); - txlut.lut[i].pa_gain = 0; - } - snprintf(param_name, sizeof param_name, "tx_lut_%i.dac_gain", i); - val = json_object_dotget_value(conf_obj, param_name); - if (json_value_get_type(val) == JSONNumber) { - txlut.lut[i].dac_gain = (uint8_t)json_value_get_number(val); - } else { - txlut.lut[i].dac_gain = 3; /* This is the only dac_gain supported for now */ - } - snprintf(param_name, sizeof param_name, "tx_lut_%i.dig_gain", i); - val = json_object_dotget_value(conf_obj, param_name); - if (json_value_get_type(val) == JSONNumber) { - txlut.lut[i].dig_gain = (uint8_t)json_value_get_number(val); - } else { - MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); - txlut.lut[i].dig_gain = 0; - } - snprintf(param_name, sizeof param_name, "tx_lut_%i.mix_gain", i); - val = json_object_dotget_value(conf_obj, param_name); - if (json_value_get_type(val) == JSONNumber) { - txlut.lut[i].mix_gain = (uint8_t)json_value_get_number(val); - } else { - MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); - txlut.lut[i].mix_gain = 0; - } - snprintf(param_name, sizeof param_name, "tx_lut_%i.rf_power", i); - val = json_object_dotget_value(conf_obj, param_name); - if (json_value_get_type(val) == JSONNumber) { - txlut.lut[i].rf_power = (int8_t)json_value_get_number(val); - } else { - MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); - txlut.lut[i].rf_power = 0; - } - } - /* all parameters parsed, submitting configuration to the HAL */ - MSG("INFO: Configuring TX LUT with %u indexes\n", txlut.size); - if (lgw_txgain_setconf(&txlut) != LGW_HAL_SUCCESS) { - MSG("WARNING: Failed to configure concentrator TX Gain LUT\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.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 { - MSG("WARNING: invalid radio type: %s (should be SX1255 or SX1257)\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); - } 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("WARNING: invalid configuration for radio %i\n", i); - } - } - - /* 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 7 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("WARNING: invalid configuration for Lora multi-SF channel %i\n", i); - } - } - - /* 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 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; - } - MSG("INFO: Lora std channel> radio %i, IF %i Hz, %u Hz bw, SF %u\n", ifconf.rf_chain, ifconf.freq_hz, bw, sf); - } - if (lgw_rxif_setconf(8, ifconf) != LGW_HAL_SUCCESS) { - MSG("WARNING: invalid configuration for Lora standard channel\n"); - } - } - - /* 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; - 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; - 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("WARNING: invalid configuration for FSK channel\n"); - } - } - 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"); - } - } - - /* 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 double difftimespec(struct timespec end, struct timespec beginning) { - double x; - - x = 1E-9 * (double)(end.tv_nsec - beginning.tv_nsec); - x += (double)(end.tv_sec - beginning.tv_sec); - - return x; -} - -/* -------------------------------------------------------------------------- */ -/* --- MAIN FUNCTION -------------------------------------------------------- */ - -int main(void) -{ - struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ - int i; /* loop variable and temporary variable for return value */ - - /* configuration file related */ - char *global_cfg_path= "global_conf.json"; /* contain global (typ. network-wide) configuration */ - char *local_cfg_path = "local_conf.json"; /* contain node specific configuration, overwrite global parameters for parameters that are defined in both */ - char *debug_cfg_path = "debug_conf.json"; /* if present, all other configuration files are ignored */ - - /* threads */ - pthread_t thrid_up; - pthread_t thrid_down; - pthread_t thrid_gps; - pthread_t thrid_valid; - - /* 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; - - /* GPS coordinates variables */ - bool coord_ok = false; - struct coord_s cp_gps_coord = {0.0, 0.0, 0}; - //struct coord_s cp_gps_err; - - /* 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; - - /* display version informations */ - MSG("*** GPS Packet Forwarder for Lora Gateway ***\nVersion: " VERSION_STRING "\n"); - MSG("*** Lora concentrator 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(debug_cfg_path, R_OK) == 0) { /* if there is a debug conf, parse only the debug conf */ - MSG("INFO: found debug configuration file %s, parsing it\n", debug_cfg_path); - MSG("INFO: other configuration files will be ignored\n"); - parse_SX1301_configuration(debug_cfg_path); - parse_gateway_configuration(debug_cfg_path); - } else if (access(global_cfg_path, R_OK) == 0) { /* if there is a global conf, parse it and then try to parse local conf */ - MSG("INFO: found global configuration file %s, parsing it\n", global_cfg_path); - parse_SX1301_configuration(global_cfg_path); - parse_gateway_configuration(global_cfg_path); - if (access(local_cfg_path, R_OK) == 0) { - MSG("INFO: found local configuration file %s, parsing it\n", local_cfg_path); - MSG("INFO: redefined parameters will overwrite global parameters\n"); - parse_SX1301_configuration(local_cfg_path); - parse_gateway_configuration(local_cfg_path); - } - } else if (access(local_cfg_path, R_OK) == 0) { /* if there is only a local conf, parse it and that's all */ - MSG("INFO: found local configuration file %s, parsing it\n", local_cfg_path); - parse_SX1301_configuration(local_cfg_path); - parse_gateway_configuration(local_cfg_path); - } else { - MSG("ERROR: [main] failed to find any configuration file named %s, %s OR %s\n", global_cfg_path, local_cfg_path, debug_cfg_path); - exit(EXIT_FAILURE); - } - - /* Start GPS a.s.a.p., to allow it to lock */ - i = lgw_gps_enable(gps_tty_path, NULL, 0, &gps_tty_fd); - 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); - - /* 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); - } - - /* 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); - } - - /* 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; - 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; - 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; - //cp_gps_err = meas_gps_err; - pthread_mutex_unlock(&mx_meas_gps); - } - - /* overwrite with reference coordinates if function is enabled */ - if (gps_fake_enable == true) { - gps_enabled = true; - coord_ok = 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); - 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 (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 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 { - 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)) { - 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 */ - if (gps_enabled == true) { - pthread_cancel(thrid_gps); /* don't wait for GPS thread */ - pthread_cancel(thrid_valid); /* don't wait for validation thread */ - } - - /* 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"); - } - } - - MSG("INFO: Exiting packet forwarder program\n"); - exit(EXIT_SUCCESS); -} - -/* -------------------------------------------------------------------------- */ -/* --- THREAD 1: RECEIVING PACKETS AND FORWARDING THEM ---------------------- */ - -void thread_up(void) { - int i, j; /* loop variables */ - unsigned pkt_in_dgram; /* nb on Lora packet in the current datagram */ - - /* 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 */ - - /* report management variable */ - bool send_report = false; - - /* 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; - } - - /* 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]; - - /* 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->rssi); - 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); - - /* 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; - } - - /* 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); - } - } - } - - /* 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", p->if_chain, p->rf_chain, ((double)p->freq_hz / 1e6)); - 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\n"); - 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_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\n"); - 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\n"); - 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\n"); - memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"?\"", 11); - buff_index += 11; - exit(EXIT_FAILURE); - } - - /* Lora SNR, 11-13 useful chars */ - 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); - } - } 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\n"); - exit(EXIT_FAILURE); - } - - /* Packet RSSI, payload size, 18-23 useful chars */ - j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"rssi\":%.0f,\"size\":%u", p->rssi, 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; - } - - /* 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 EMITTING PACKETS ------------------------ */ - -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; - short x2, x3, x4; - double x5, x6; - - /* variables to send on UTC timestamp */ - struct tref local_ref; /* time reference used for UTC <-> timestamp conversion */ - struct tm utc_vector; /* for collecting the elements of the UTC time */ - struct timespec utc_tx; /* UTC time that needs to be converted to timestamp */ - - /* auto-quit variable */ - uint32_t autoquit_cnt = 0; /* count the number of PULL_DATA sent since the latest PULL_ACK */ - - /* 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; - - 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); - - /* 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\n"); - 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 :)\n"); /* 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; - 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); - MSG("INFO: [down] a packet will be sent on timestamp value %u\n", txpkt.count_us); - } else { - /* TX procedure: send on UTC time (converted to timestamp value) */ - str = json_object_get_string(txpk_obj, "time"); - if (str == NULL) { - MSG("WARNING: [down] no mandatory \"txpk.tmst\" or \"txpk.time\" 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 UTC time, TX aborted\n"); - json_value_free(root_val); - continue; - } - } else { - MSG("WARNING: [down] GPS disabled, impossible to send packet on specific UTC time, TX aborted\n"); - json_value_free(root_val); - continue; - } - - i = sscanf (str, "%4hd-%2hd-%2hdT%2hd:%2hd:%9lf", &x0, &x1, &x2, &x3, &x4, &x5); - if (i != 6 ) { - MSG("WARNING: [down] \"txpk.time\" must follow ISO 8601 format, TX aborted\n"); - json_value_free(root_val); - continue; - } - x5 = modf(x5, &x6); /* x6 get the integer part of x5, x5 the fractional part */ - utc_vector.tm_year = x0 - 1900; /* years since 1900 */ - utc_vector.tm_mon = x1 - 1; /* months since January */ - utc_vector.tm_mday = x2; /* day of the month 1-31 */ - utc_vector.tm_hour = x3; /* hours since midnight */ - utc_vector.tm_min = x4; /* minutes after the hour */ - utc_vector.tm_sec = (int)x6; - utc_tx.tv_sec = mktime(&utc_vector) - timezone; - utc_tx.tv_nsec = (long)(1e9 * x5); - - /* transform UTC time to timestamp */ - i = lgw_utc2cnt(local_ref, utc_tx, &(txpkt.count_us)); - if (i != LGW_GPS_SUCCESS) { - MSG("WARNING: [down] could not convert UTC 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 UTC time)\n", txpkt.count_us); - } - } - } - - /* 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); - } - - /* 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 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; - - /* transfer data and metadata to the concentrator, and schedule TX */ - pthread_mutex_lock(&mx_concent); /* may have to wait for a fetch to finish */ - i = lgw_send(txpkt); - pthread_mutex_unlock(&mx_concent); /* free concentrator ASAP */ - if (i == LGW_HAL_ERROR) { - meas_nb_tx_fail += 1; - pthread_mutex_unlock(&mx_meas_dw); - MSG("WARNING: [down] lgw_send failed\n"); - continue; - } else { - meas_nb_tx_ok += 1; - pthread_mutex_unlock(&mx_meas_dw); - } - } - } - MSG("\nINFO: End of downstream thread\n"); -} - -/* -------------------------------------------------------------------------- */ -/* --- THREAD 3: PARSE GPS MESSAGE AND KEEP GATEWAY IN SYNC ----------------- */ - -void thread_gps(void) { - int i; - - /* serial variables */ - char serial_buff[128]; /* buffer to receive GPS data */ - ssize_t nb_char; - - /* variables for PPM pulse GPS synchronization */ - enum gps_msg latest_msg; /* keep track of latest NMEA message parsed */ - struct timespec utc_time; /* UTC time associated with PPS pulse */ - uint32_t trig_tstamp; /* concentrator timestamp associated with PPM pulse */ - - /* position variable */ - struct coord_s coord; - struct coord_s gpserr; - - /* initialize some variables before loop */ - memset(serial_buff, 0, sizeof serial_buff); - - while (!exit_sig && !quit_sig) { - /* blocking canonical read on serial port */ - nb_char = read(gps_tty_fd, serial_buff, sizeof(serial_buff)-1); - if (nb_char <= 0) { - MSG("WARNING: [gps] read() returned value <= 0\n"); - continue; - } else { - serial_buff[nb_char] = 0; /* add null terminator, just to be sure */ - } - - /* parse the received NMEA */ - latest_msg = lgw_parse_nmea(serial_buff, sizeof(serial_buff)); - - if (latest_msg == NMEA_RMC) { /* trigger sync only on RMC frames */ - - /* get UTC time for synchronization */ - i = lgw_gps_get(&utc_time, NULL, NULL); - if (i != LGW_GPS_SUCCESS) { - MSG("WARNING: [gps] could not get UTC time from GPS\n"); - continue; - } - - /* 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"); - continue; - } - - /* try to update time reference with the new UTC & timestamp */ - pthread_mutex_lock(&mx_timeref); - i = lgw_gps_sync(&time_reference_gps, trig_tstamp, utc_time); - pthread_mutex_unlock(&mx_timeref); - if (i != LGW_GPS_SUCCESS) { - MSG("WARNING: [gps] GPS out of sync, keeping previous time reference\n"); - continue; - } - - /* update gateway coordinates */ - i = lgw_gps_get(NULL, &coord, &gpserr); - 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); - } - } - MSG("\nINFO: End of GPS thread\n"); -} - -/* -------------------------------------------------------------------------- */ -/* --- THREAD 4: CHECK TIME REFERENCE AND CALCULATE XTAL CORRECTION --------- */ - -void thread_valid(void) { - - /* GPS reference validation variables */ - long gps_ref_age = 0; - - /* 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; - } else { - /* time ref is too old, invalidate */ - gps_ref_valid = false; - } - pthread_mutex_unlock(&mx_timeref); - - } - MSG("\nINFO: End of validation thread\n"); -} - -/* --- EOF ------------------------------------------------------------------ */ diff --git a/gps_pkt_fwd/src/parson.c b/gps_pkt_fwd/src/parson.c deleted file mode 100644 index 8756531d..00000000 --- a/gps_pkt_fwd/src/parson.c +++ /dev/null @@ -1,782 +0,0 @@ -/* - Parson ( http://kgabis.github.com/parson/ ) - Copyright (C) 2013 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. -*/ - -#include "parson.h" - -#include -#include -#include -#include - -#define ERROR 0 -#define SUCCESS 1 -#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 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)) - -#define parson_malloc(a) malloc(a) -#define parson_free(a) free((void*)a) -#define parson_realloc(a, b) realloc(a, b) - -/* Type definitions */ -typedef union json_value_value { - const 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 { - const 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 int try_realloc(void **ptr, size_t new_size); -static char * parson_strndup(const char *string, size_t n); -static int is_utf(const unsigned char *string); -static int is_decimal(const char *string, size_t length); - -/* JSON Object */ -static JSON_Object * json_object_init(void); -static int json_object_add(JSON_Object *object, const char *name, JSON_Value *value); -static int json_object_resize(JSON_Object *object, size_t 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 int json_array_add(JSON_Array *array, JSON_Value *value); -static int json_array_resize(JSON_Array *array, size_t capacity); -static void json_array_free(JSON_Array *array); - -/* JSON Value */ -static JSON_Value * json_value_init_object(void); -static JSON_Value * json_value_init_array(void); -static JSON_Value * json_value_init_string(const char *string); -static JSON_Value * json_value_init_number(double number); -static JSON_Value * json_value_init_boolean(int boolean); -static JSON_Value * json_value_init_null(void); - -/* Parser */ -static void skip_quotes(const char **string); -static const char * get_processed_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); - -/* Various */ -static int try_realloc(void **ptr, size_t new_size) { - void *reallocated_ptr = parson_realloc(*ptr, new_size); - if (!reallocated_ptr) - return ERROR; - *ptr = reallocated_ptr; - return SUCCESS; -} - -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 int is_utf(const unsigned char *s) { - return isxdigit(s[0]) && isxdigit(s[1]) && isxdigit(s[2]) && isxdigit(s[3]); -} - -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; - char *file_contents; - if (!fp) - return NULL; - fseek(fp, 0L, SEEK_END); - file_size = ftell(fp); - 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 = (const char**)NULL; - new_obj->values = (JSON_Value**)NULL; - new_obj->capacity = 0; - new_obj->count = 0; - return new_obj; -} - -static int json_object_add(JSON_Object *object, const char *name, JSON_Value *value) { - size_t index; - if (object->count >= object->capacity) { - size_t new_capacity = MAX(object->capacity * 2, STARTING_CAPACITY); - if (new_capacity > OBJECT_MAX_CAPACITY) - return ERROR; - if (json_object_resize(object, new_capacity) == ERROR) - return ERROR; - } - if (json_object_get_value(object, name) != NULL) - return ERROR; - index = object->count; - object->names[index] = parson_strndup(name, strlen(name)); - if (!object->names[index]) - return ERROR; - object->values[index] = value; - object->count++; - return SUCCESS; -} - -static int json_object_resize(JSON_Object *object, size_t capacity) { - if (try_realloc((void**)&object->names, capacity * sizeof(char*)) == ERROR) - return ERROR; - if (try_realloc((void**)&object->values, capacity * sizeof(JSON_Value*)) == ERROR) - return ERROR; - object->capacity = capacity; - return SUCCESS; -} - -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 int 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 ERROR; - if (!json_array_resize(array, new_capacity)) - return ERROR; - } - array->items[array->count] = value; - array->count++; - return SUCCESS; -} - -static int json_array_resize(JSON_Array *array, size_t capacity) { - if (try_realloc((void**)&array->items, capacity * sizeof(JSON_Value*)) == ERROR) - return ERROR; - array->capacity = capacity; - return SUCCESS; -} - -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_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; -} - -static 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; -} - -static JSON_Value * json_value_init_string(const 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; -} - -static 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; -} - -static 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; - return new_value; -} - -static 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; -} - -/* 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); -} - -/* Returns contents of a string inside double quotes and parses escaped - characters inside. - Example: "\u006Corem ipsum" -> lorem ipsum */ -static const char * get_processed_string(const char **string) { - const char *string_start = *string; - char *output, *processed_ptr, *unprocessed_ptr, current_char; - unsigned int utf_val; - skip_quotes(string); - if (**string == '\0') - return NULL; - output = parson_strndup(string_start + 1, *string - string_start - 2); - if (!output) - return NULL; - processed_ptr = unprocessed_ptr = output; - while (*unprocessed_ptr) { - current_char = *unprocessed_ptr; - if (current_char == '\\') { - unprocessed_ptr++; - current_char = *unprocessed_ptr; - switch (current_char) { - case '\"': case '\\': case '/': break; - case 'b': current_char = '\b'; break; - case 'f': current_char = '\f'; break; - case 'n': current_char = '\n'; break; - case 'r': current_char = '\r'; break; - case 't': current_char = '\t'; break; - case 'u': - unprocessed_ptr++; - if (!is_utf((const unsigned char*)unprocessed_ptr) || - sscanf(unprocessed_ptr, "%4x", &utf_val) == EOF) { - parson_free(output); - return NULL; - } - if (utf_val < 0x80) { - current_char = utf_val; - } else if (utf_val < 0x800) { - *processed_ptr++ = (utf_val >> 6) | 0xC0; - current_char = ((utf_val | 0x80) & 0xBF); - } else { - *processed_ptr++ = (utf_val >> 12) | 0xE0; - *processed_ptr++ = (((utf_val >> 6) | 0x80) & 0xBF); - current_char = ((utf_val | 0x80) & 0xBF); - } - unprocessed_ptr += 3; - break; - default: - parson_free(output); - return NULL; - break; - } - } else if ((unsigned char)current_char < 0x20) { /* 0x00-0x19 are invalid characters for json string (http://www.ietf.org/rfc/rfc4627.txt) */ - parson_free(output); - return NULL; - } - *processed_ptr = current_char; - processed_ptr++; - unprocessed_ptr++; - } - *processed_ptr = '\0'; - if (try_realloc((void**)&output, strlen(output) + 1) == ERROR) - return NULL; - return output; -} - -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); - const char *new_key = NULL; - if (!output_value) - return NULL; - skip_char(string); - skip_whitespaces(string); - if (**string == '}') { /* empty object */ - skip_char(string); - return output_value; - } - while (**string != '\0') { - new_key = get_processed_string(string); - skip_whitespaces(string); - if (!new_key || **string != ':') { - json_value_free(output_value); - return NULL; - } - skip_char(string); - new_value = parse_value(string, nesting); - if (!new_value) { - parson_free(new_key); - json_value_free(output_value); - return NULL; - } - if(!json_object_add(output_object, new_key, new_value)) { - 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)) == ERROR) { - 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) == ERROR) { - 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)) == ERROR) { - json_value_free(output_value); - return NULL; - } - skip_char(string); - return output_value; -} - -static JSON_Value * parse_string_value(const char **string) { - const char *new_string = get_processed_string(string); - if (!new_string) - return NULL; - return json_value_init_string(new_string); -} - -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; -} - -/* Parser API */ -JSON_Value * json_parse_file(const char *filename) { - char *file_contents = read_file(filename); - JSON_Value *output_value = NULL; - if (!file_contents) - 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) - 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 || (*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_strndup(string, strlen(string)); - if (!string_mutable_copy) - 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) { - 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); -} diff --git a/gps_pkt_fwd/Makefile b/lora_pkt_fwd/Makefile similarity index 83% rename from gps_pkt_fwd/Makefile rename to lora_pkt_fwd/Makefile index 6032cbb5..1330d622 100644 --- a/gps_pkt_fwd/Makefile +++ b/lora_pkt_fwd/Makefile @@ -1,6 +1,6 @@ ### Application-specific constants -APP_NAME := gps_pkt_fwd +APP_NAME := lora_pkt_fwd ### Environment constants @@ -54,14 +54,14 @@ $(OBJDIR): mkdir -p $(OBJDIR) $(OBJDIR)/%.o: src/%.c $(INCLUDES) | $(OBJDIR) - $(CC) -c $(CFLAGS) $< -o $@ + $(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)/parson.o $(OBJDIR)/base64.o - $(CC) -L$(LGW_PATH) $< $(OBJDIR)/parson.o $(OBJDIR)/base64.o -o $@ $(LIBS) +$(APP_NAME): $(OBJDIR)/$(APP_NAME).o $(LGW_PATH)/libloragw.a $(OBJDIR)/parson.o $(OBJDIR)/base64.o $(OBJDIR)/jitqueue.o $(OBJDIR)/timersync.o + $(CC) -L$(LGW_PATH) $< $(OBJDIR)/parson.o $(OBJDIR)/base64.o $(OBJDIR)/jitqueue.o $(OBJDIR)/timersync.o -o $@ $(LIBS) ### EOF diff --git a/basic_pkt_fwd/global_conf.json b/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.basic similarity index 97% rename from basic_pkt_fwd/global_conf.json rename to lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.basic index 15b2e3f5..e60f5839 100644 --- a/basic_pkt_fwd/global_conf.json +++ b/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.basic @@ -2,12 +2,15 @@ "SX1301_conf": { "lorawan_public": true, "clksrc": 1, /* radio_1 provides clock to concentrator */ + "antenna_gain": 0, /* antenna gain, in dBi */ "radio_0": { "enable": true, "type": "SX1257", "freq": 867500000, "rssi_offset": -166.0, - "tx_enable": true + "tx_enable": true, + "tx_freq_min": 863000000, + "tx_freq_max": 870000000 }, "radio_1": { "enable": true, @@ -210,3 +213,4 @@ "forward_crc_disabled": false } } + diff --git a/gps_pkt_fwd/global_conf.json b/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.beacon similarity index 93% rename from gps_pkt_fwd/global_conf.json rename to lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.beacon index e44c6e03..ffbfb5d0 100644 --- a/gps_pkt_fwd/global_conf.json +++ b/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.beacon @@ -2,12 +2,15 @@ "SX1301_conf": { "lorawan_public": true, "clksrc": 1, /* radio_1 provides clock to concentrator */ + "antenna_gain": 0, /* antenna gain, in dBi */ "radio_0": { "enable": true, "type": "SX1257", "freq": 867500000, "rssi_offset": -166.0, - "tx_enable": true + "tx_enable": true, + "tx_freq_min": 863000000, + "tx_freq_max": 870000000 }, "radio_1": { "enable": true, @@ -209,7 +212,14 @@ "forward_crc_error": false, "forward_crc_disabled": false, /* GPS configuration */ - "gps_tty_path": "/dev/ttyAMA0" + "gps_tty_path": "/dev/ttyAMA0", + /* GPS reference coordinates */ + "ref_latitude": 0.0, + "ref_longitude": 0.0, + "ref_altitude": 0, + /* Beaconing parameters */ + "beacon_period": 128, + "beacon_freq_hz": 869525000 } } diff --git a/gps_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868 b/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.gps similarity index 94% rename from gps_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868 rename to lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.gps index e44c6e03..86852feb 100644 --- a/gps_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868 +++ b/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.gps @@ -2,12 +2,15 @@ "SX1301_conf": { "lorawan_public": true, "clksrc": 1, /* radio_1 provides clock to concentrator */ + "antenna_gain": 0, /* antenna gain, in dBi */ "radio_0": { "enable": true, "type": "SX1257", "freq": 867500000, "rssi_offset": -166.0, - "tx_enable": true + "tx_enable": true, + "tx_freq_min": 863000000, + "tx_freq_max": 870000000 }, "radio_1": { "enable": true, @@ -209,7 +212,11 @@ "forward_crc_error": false, "forward_crc_disabled": false, /* GPS configuration */ - "gps_tty_path": "/dev/ttyAMA0" + "gps_tty_path": "/dev/ttyAMA0", + /* GPS reference coordinates */ + "ref_latitude": 0.0, + "ref_longitude": 0.0, + "ref_altitude": 0 } } diff --git a/basic_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868 b/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.basic similarity index 92% rename from basic_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868 rename to lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.basic index 65cafe13..a9758615 100644 --- a/basic_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868 +++ b/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.basic @@ -2,12 +2,24 @@ "SX1301_conf": { "lorawan_public": true, "clksrc": 1, /* radio_1 provides clock to concentrator */ + "lbt_cfg": { + "enable": false, + "rssi_target": 160, /* rssi in dBm = -lbt_rssi_target/2 */ + "nb_channel": 1, + "start_freq": 869525000, + "scan_time_us": 5000, + "tx_delay_1ch_us": 4000000, + "tx_delay_2ch_us": 4000000 + }, + "antenna_gain": 0, /* antenna gain, in dBi */ "radio_0": { "enable": true, "type": "SX1257", "freq": 867500000, "rssi_offset": -165.0, - "tx_enable": true + "tx_enable": true, + "tx_freq_min": 863000000, + "tx_freq_max": 870000000 }, "radio_1": { "enable": true, @@ -210,3 +222,4 @@ "forward_crc_disabled": false } } + diff --git a/gps_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868 b/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.beacon similarity index 88% rename from gps_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868 rename to lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.beacon index f978bdc0..839675fb 100644 --- a/gps_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868 +++ b/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.beacon @@ -2,12 +2,24 @@ "SX1301_conf": { "lorawan_public": true, "clksrc": 1, /* radio_1 provides clock to concentrator */ + "lbt_cfg": { + "enable": false, + "rssi_target": 160, /* rssi in dBm = -lbt_rssi_target/2 */ + "nb_channel": 1, + "start_freq": 869525000, + "scan_time_us": 5000, + "tx_delay_1ch_us": 4000000, + "tx_delay_2ch_us": 4000000 + }, + "antenna_gain": 0, /* antenna gain, in dBi */ "radio_0": { "enable": true, "type": "SX1257", "freq": 867500000, "rssi_offset": -165.0, - "tx_enable": true + "tx_enable": true, + "tx_freq_min": 863000000, + "tx_freq_max": 870000000 }, "radio_1": { "enable": true, @@ -209,7 +221,14 @@ "forward_crc_error": false, "forward_crc_disabled": false, /* GPS configuration */ - "gps_tty_path": "/dev/ttyAMA0" + "gps_tty_path": "/dev/ttyAMA0", + /* GPS reference coordinates */ + "ref_latitude": 0.0, + "ref_longitude": 0.0, + "ref_altitude": 0, + /* Beaconing parameters */ + "beacon_period": 128, + "beacon_freq_hz": 869525000 } } diff --git a/beacon_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868 b/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.gps similarity index 90% rename from beacon_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868 rename to lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.gps index f978bdc0..57596194 100644 --- a/beacon_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868 +++ b/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.gps @@ -2,12 +2,24 @@ "SX1301_conf": { "lorawan_public": true, "clksrc": 1, /* radio_1 provides clock to concentrator */ + "lbt_cfg": { + "enable": false, + "rssi_target": 160, /* rssi in dBm = -lbt_rssi_target/2 */ + "nb_channel": 1, + "start_freq": 869525000, + "scan_time_us": 5000, + "tx_delay_1ch_us": 4000000, + "tx_delay_2ch_us": 4000000 + }, + "antenna_gain": 0, /* antenna gain, in dBi */ "radio_0": { "enable": true, "type": "SX1257", "freq": 867500000, "rssi_offset": -165.0, - "tx_enable": true + "tx_enable": true, + "tx_freq_min": 863000000, + "tx_freq_max": 870000000 }, "radio_1": { "enable": true, @@ -209,7 +221,11 @@ "forward_crc_error": false, "forward_crc_disabled": false, /* GPS configuration */ - "gps_tty_path": "/dev/ttyAMA0" + "gps_tty_path": "/dev/ttyAMA0", + /* GPS reference coordinates */ + "ref_latitude": 0.0, + "ref_longitude": 0.0, + "ref_altitude": 0 } } diff --git a/basic_pkt_fwd/cfg/global_conf.json.US902 b/lora_pkt_fwd/cfg/global_conf.json.US902.basic similarity index 93% rename from basic_pkt_fwd/cfg/global_conf.json.US902 rename to lora_pkt_fwd/cfg/global_conf.json.US902.basic index 0209e435..3914956f 100644 --- a/basic_pkt_fwd/cfg/global_conf.json.US902 +++ b/lora_pkt_fwd/cfg/global_conf.json.US902.basic @@ -2,12 +2,15 @@ "SX1301_conf": { "lorawan_public": true, "clksrc": 1, /* radio_1 provides clock to concentrator */ + "antenna_gain": 0, /* antenna gain, in dBi */ "radio_0": { "enable": true, "type": "SX1257", "freq": 902700000, "rssi_offset": -166.0, - "tx_enable": true + "tx_enable": true, + "tx_freq_min": 902000000, + "tx_freq_max": 928000000 }, "radio_1": { "enable": true, @@ -73,7 +76,7 @@ "spread_factor": 8 }, "chan_FSK": { - /* FSK 100kbps channel, 927.3 MHz */ + /* FSK 100kbps channel, 903.0 MHz */ "enable": false, "radio": 0, "if": 300000, @@ -98,3 +101,4 @@ "forward_crc_disabled": false } } + diff --git a/beacon_pkt_fwd/cfg/global_conf.json.US902 b/lora_pkt_fwd/cfg/global_conf.json.US902.gps similarity index 88% rename from beacon_pkt_fwd/cfg/global_conf.json.US902 rename to lora_pkt_fwd/cfg/global_conf.json.US902.gps index 38dcd9ab..ddd0928a 100644 --- a/beacon_pkt_fwd/cfg/global_conf.json.US902 +++ b/lora_pkt_fwd/cfg/global_conf.json.US902.gps @@ -2,12 +2,15 @@ "SX1301_conf": { "lorawan_public": true, "clksrc": 1, /* radio_1 provides clock to concentrator */ + "antenna_gain": 0, /* antenna gain, in dBi */ "radio_0": { "enable": true, "type": "SX1257", "freq": 902700000, "rssi_offset": -166.0, - "tx_enable": true + "tx_enable": true, + "tx_freq_min": 902000000, + "tx_freq_max": 928000000 }, "radio_1": { "enable": true, @@ -73,7 +76,7 @@ "spread_factor": 8 }, "chan_FSK": { - /* FSK 100kbps channel, 927.3 MHz */ + /* FSK 100kbps channel, 903.0 MHz */ "enable": false, "radio": 0, "if": 300000, @@ -97,7 +100,11 @@ "forward_crc_error": false, "forward_crc_disabled": false, /* GPS configuration */ - "gps_tty_path": "/dev/ttyAMA0" + "gps_tty_path": "/dev/ttyAMA0", + /* GPS reference coordinates */ + "ref_latitude": 0.0, + "ref_longitude": 0.0, + "ref_altitude": 0 } } diff --git a/basic_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868 b/lora_pkt_fwd/global_conf.json similarity index 92% rename from basic_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868 rename to lora_pkt_fwd/global_conf.json index 15b2e3f5..589c419c 100644 --- a/basic_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868 +++ b/lora_pkt_fwd/global_conf.json @@ -2,12 +2,24 @@ "SX1301_conf": { "lorawan_public": true, "clksrc": 1, /* radio_1 provides clock to concentrator */ + "lbt_cfg": { + "enable": false, + "rssi_target": 160, /* rssi in dBm = -lbt_rssi_target/2 */ + "nb_channel": 1, + "start_freq": 869525000, + "scan_time_us": 5000, + "tx_delay_1ch_us": 4000000, + "tx_delay_2ch_us": 4000000 + }, + "antenna_gain": 0, /* antenna gain, in dBi */ "radio_0": { "enable": true, "type": "SX1257", "freq": 867500000, "rssi_offset": -166.0, - "tx_enable": true + "tx_enable": true, + "tx_freq_min": 863000000, + "tx_freq_max": 870000000 }, "radio_1": { "enable": true, @@ -210,3 +222,4 @@ "forward_crc_disabled": false } } + diff --git a/gps_pkt_fwd/inc/base64.h b/lora_pkt_fwd/inc/base64.h similarity index 96% rename from gps_pkt_fwd/inc/base64.h rename to lora_pkt_fwd/inc/base64.h index c0369aad..e57eb47c 100644 --- a/gps_pkt_fwd/inc/base64.h +++ b/lora_pkt_fwd/inc/base64.h @@ -7,7 +7,7 @@ (C)2013 Semtech-Cycleo Description: - Base64 encoding & decoding library + Base64 encoding & decoding library License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -20,7 +20,7 @@ Maintainer: Sylvain Miermont /* -------------------------------------------------------------------------- */ /* --- DEPENDANCIES --------------------------------------------------------- */ -#include /* C99 types */ +#include /* C99 types */ /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ diff --git a/lora_pkt_fwd/inc/jitqueue.h b/lora_pkt_fwd/inc/jitqueue.h new file mode 100644 index 00000000..b674f751 --- /dev/null +++ b/lora_pkt_fwd/inc/jitqueue.h @@ -0,0 +1,156 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Description: + LoRa concentrator : Just In Time TX scheduling queue + +License: Revised BSD License, see LICENSE.TXT file include in the project +Maintainer: Michael Coracin +*/ + + +#ifndef _LORA_PKTFWD_JIT_H +#define _LORA_PKTFWD_JIT_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* bool type */ +#include /* timeval */ + +#include "loragw_hal.h" +#include "loragw_gps.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[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, struct timeval *time, 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[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, struct timeval *time, 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/basic_pkt_fwd/inc/parson.h b/lora_pkt_fwd/inc/parson.h similarity index 100% rename from basic_pkt_fwd/inc/parson.h rename to lora_pkt_fwd/inc/parson.h diff --git a/lora_pkt_fwd/inc/timersync.h b/lora_pkt_fwd/inc/timersync.h new file mode 100644 index 00000000..497e538e --- /dev/null +++ b/lora_pkt_fwd/inc/timersync.h @@ -0,0 +1,32 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Description: + LoRa concentrator : Just In Time TX scheduling queue + +License: Revised BSD License, see LICENSE.TXT file include in the project +Maintainer: Michael Coracin +*/ + + +#ifndef _LORA_PKTFWD_TIMERSYNC_H +#define _LORA_PKTFWD_TIMERSYNC_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* timeval */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +int get_concentrator_time(struct timeval *concent_time, struct timeval unix_time); + +void thread_timersync(void); + +#endif diff --git a/lora_pkt_fwd/inc/trace.h b/lora_pkt_fwd/inc/trace.h new file mode 100644 index 00000000..a0678511 --- /dev/null +++ b/lora_pkt_fwd/inc/trace.h @@ -0,0 +1,37 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Description: + LoRa concentrator : Packet Forwarder trace helpers + +License: Revised BSD License, see LICENSE.TXT file include in the project +Maintainer: Michael Coracin +*/ + + +#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) + + + +#endif +/* --- EOF ------------------------------------------------------------------ */ diff --git a/basic_pkt_fwd/local_conf.json b/lora_pkt_fwd/local_conf.json similarity index 100% rename from basic_pkt_fwd/local_conf.json rename to lora_pkt_fwd/local_conf.json diff --git a/lora_pkt_fwd/readme.md b/lora_pkt_fwd/readme.md new file mode 100644 index 00000000..52296146 --- /dev/null +++ b/lora_pkt_fwd/readme.md @@ -0,0 +1,329 @@ + / _____) _ | | + ( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | + (______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Lora Gateway 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 from cfg/ directory that fit with your +platform, region and feature need. +* Update the JSON configuration (global and local) files, as explained below. +* For IoT Starter Kit only, run: + ./reset_lgw.sh stop + ./reset_lgw.sh start +* Run: + ./update_gwid.sh local_conf.json (OPTIONAL) + ./lora_pkt_fwd + +To stop the application, press Ctrl+C. +Unless it is manually stopped or encounter a critical error, the program will +run forever. + +There are no command line launch options. + +The way the program takes configuration files into account is the following: + * if there is a debug_conf.json parse it, others are ignored + * if there is a global_conf.json parse it, look for the next file + * if there is a local_conf.json parse it +If some parameters are defined in both global and local configuration files, +the local definition overwrites the global definition. + +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). + +As some of the parameters (like 'rssi_offset', 'tx_lut_*') are board dependant, +several flavours of the global_conf.json file are provided in the cfg/ +directory. +* global_conf.json.PCB_E286.EU868.*: to be used for Semtech reference design + board with PCB name PCB_E286 (also called Gateway Board v1.0 (no FPGA)). + Configured for Europe 868MHz channels. +* global_conf.json.PCB_E336.EU868.*:to be used for Semtech reference design + board with PCB name PCB_E336 (also called Gateway Board v1.5 (with FPGA)). + Configured for Europe 868MHz channels. +* global_conf.json.US902.*: to be used for Semtech reference design v1.0 or + v1.5. (No calibration done for RSSI offset and TX gains yet). + Configured for US 902MHz channels. + +Beside board related flavours, there are "features" flavours named "basic", +"gps", "beacon". +* global_conf.json.*.basic: to be used for basic packet forwarder usage, with +no GPS. +* global_conf.json.*.gps: to be used when the platform has a GPS receiver. +* global_conf.json.*.beacon: to be used when the platform has a GPS receiver +and we want the packet forwarder to emit beacons for synchronized networks. + +Rename the one you need to global_conf.json before launching the packet +forwarder. + +The local configuration file should contain parameters that are specific to +each gateway (eg. MAC address, frequency for backhaul radio channels). + +In each configuration file, the program looks for a JSON object named +"SX1301_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. +- A Timer synchronization thread to keep the concentrator clock and Unix clock +synchronized so that host processor can determine if a packet with a given +timestamp can be programmed in the concentrator or not. + +5.1. Concentrator vs Unix time synchronization + +In order for the host to know if an incoming downlink packet can or cannot be +queued in JiT queue for later transmission, it has to check if the timestamp of +the packet designates a time later than the current concentrator counter or if +it is already too late to be passed to the concentrator. +In order to get current concentrator time, we can use the lgw_get_trigcnt() HAL +function. The problem is that the sample register used to read this value can be +configured in 2 different ways: + - Real time mode: when GPS is disabled, the value read in sample register is + the actual concentrator counter value. + - PPS mode: when GPS is enabled, the value read in sample register is the + value +that the concentrator counter had when last GPS’s PPS occurred. So this changes +every second only. +As in our case GPS is enabled (LGW_GPS_EN==1), we need to have a way to get the +actual concentrator current time, at any time. +For this, a new thread has been added to the packet forwarder (thread_timersync) +which will regularly: + - Disable GPS mode of SX1301 counter sampler + - Get current Unix time + - Get current SX1301 counter + - Compute the offset between Unix and SX1301 clocks and store it + - Re-enable GPS mode of SX1301 counter sampler +Then a new function has been added to estimate the current concentrator counter +at any time based on the current Unix time and offset computed by the timersync +thread. + +In addition to this, the Concentrator vs Unix time synchronization is used by +the JiT thread to determine if a packet in the JiT queue has to be sent to the +concentrator for transmission. +So basically it is used for queueing and dequeuing packets to/from the JiT queue. + +5.2. 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 SX1301 counter value to GPS UTC time when we receive +an uplink, in order to fill the “time” field of JSON “rxpk” structure. + +5.3. 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.4. 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) 2013, 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/lora_pkt_fwd/src/base64.c b/lora_pkt_fwd/src/base64.c new file mode 100644 index 00000000..8ba908e5 --- /dev/null +++ b/lora_pkt_fwd/src/base64.c @@ -0,0 +1,308 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Description: + Base64 encoding & decoding library + +License: Revised BSD License, see LICENSE.TXT file include in the project +Maintainer: Sylvain Miermont +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- 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/lora_pkt_fwd/src/jitqueue.c b/lora_pkt_fwd/src/jitqueue.c new file mode 100644 index 00000000..50c40389 --- /dev/null +++ b/lora_pkt_fwd/src/jitqueue.c @@ -0,0 +1,528 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Description: + LoRa concentrator : Just In Time TX scheduling queue + +License: Revised BSD License, see LICENSE.TXT file include in the project +Maintainer: Michael Coracin +*/ + +/* -------------------------------------------------------------------------- */ +/* --- 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 ----------------------------------------- */ +static uint32_t time_on_air(struct lgw_pkt_tx_s *packet, bool isBeacon) { + uint8_t SF, H, DE; + uint16_t BW; + uint32_t payloadSymbNb, Tpacket; + double Tsym, Tpreamble, Tpayload; + + switch (packet->bandwidth) { + case BW_125KHZ: + BW = 125; + break; + case BW_250KHZ: + BW = 250; + break; + case BW_500KHZ: + BW = 500; + break; + default: + MSG("ERROR: Cannot compute time on air for this packet, unsupported bandwidth (%u)\n", packet->bandwidth); + return 0; + } + + switch (packet->datarate) { + case DR_LORA_SF7: + SF = 7; + break; + case DR_LORA_SF8: + SF = 8; + break; + case DR_LORA_SF9: + SF = 9; + break; + case DR_LORA_SF10: + SF = 10; + break; + case DR_LORA_SF11: + SF = 11; + break; + case DR_LORA_SF12: + SF = 12; + break; + default: + MSG("ERROR: Cannot compute time on air for this packet, unsupported datarate (%u)\n", packet->datarate); + return 0; + } + + /* Duration of 1 symbol */ + Tsym = pow(2, SF) / BW; + + /* Duration of preamble */ + Tpreamble = (8 + 4.25) * Tsym; /* 8 programmed symbols in preamble */ + + /* Duration of payload */ + H = (isBeacon==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; + + Tpacket = Tpreamble + Tpayload; + + return Tpacket; +} + +/* -------------------------------------------------------------------------- */ +/* --- 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, struct timeval *time, struct lgw_pkt_tx_s *packet, enum jit_pkt_type_e pkt_type) { + int i = 0; + uint32_t time_us = time->tv_sec * 1000000UL + time->tv_usec; /* convert time in µs */ + 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 = time_on_air(packet, false) * 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, struct timeval *time, int *pkt_idx) { + /* Return index of node containing a packet inline with given time */ + int i = 0; + int idx_highest_priority = -1; + uint32_t time_us; + + if ((time == NULL) || (pkt_idx == NULL)) { + MSG("ERROR: invalid parameter\n"); + return JIT_ERROR_INVALID; + } + + if (jit_queue_is_empty(queue)) { + return JIT_ERROR_EMPTY; + } + + time_us = time->tv_sec * 1000000UL + time->tv_usec; + + 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/lora_pkt_fwd/src/lora_pkt_fwd.c b/lora_pkt_fwd/src/lora_pkt_fwd.c new file mode 100644 index 00000000..31a3743c --- /dev/null +++ b/lora_pkt_fwd/src/lora_pkt_fwd.c @@ -0,0 +1,2631 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +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 +Maintainer: Michael Coracin +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- 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 + +#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 "timersync.h" +#include "parson.h" +#include "base64.h" +#include "loragw_hal.h" +#include "loragw_gps.h" +#include "loragw_aux.h" +#include "loragw_reg.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 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 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 8 /* 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 4 + +#define STATUS_SIZE 200 +#define TX_BUFF_SIZE ((540 * NB_PKT_MAX) + 30 + STATUS_SIZE) + +/* -------------------------------------------------------------------------- */ +/* --- 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 UTC <-> 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 = 0; /* TX beacon frequency, in Hz */ + +/* 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; + +/* Gateway specificities */ +static int8_t antenna_gain = 0; + +/* TX capabilities */ +static struct lgw_tx_gain_lut_s txlut; /* 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 */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ + +static void sig_handler(int sigio); + +static int parse_SX1301_configuration(const char * conf_file); + +static int parse_gateway_configuration(const char * conf_file); + +static uint16_t crc_ccit(const uint8_t * data, unsigned size); + +static double difftimespec(struct timespec end, struct timespec beginning); + +/* threads */ +void thread_up(void); +void thread_down(void); +void thread_gps(void); +void thread_valid(void); +void thread_jit(void); +void thread_timersync(void); + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +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_SX1301_configuration(const char * conf_file) { + int i; + 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[] = "SX1301_conf"; + JSON_Value *root_val = NULL; + JSON_Object *conf_obj = NULL; + JSON_Value *val = NULL; + struct lgw_conf_board_s boardconf; + struct lgw_conf_lbt_s lbtconf; + struct lgw_conf_rxrf_s rfconf; + struct lgw_conf_rxif_s ifconf; + uint32_t sf, bw, fdev; + + /* 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 SX1301 parameters\n", conf_file, conf_obj_name); + } + + /* set board configuration */ + memset(&boardconf, 0, sizeof boardconf); /* initialize configuration structure */ + 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; + } + MSG("INFO: lorawan_public %d, clksrc %d\n", boardconf.lorawan_public, boardconf.clksrc); + /* all parameters parsed, submitting configuration to the HAL */ + if (lgw_board_setconf(boardconf) != LGW_HAL_SUCCESS) { + MSG("WARNING: Failed to configure board\n"); + } + + /* LBT struct*/ + memset(&lbtconf, 0, sizeof lbtconf); /* initialize configuration structure */ + val = json_object_get_value(conf_obj, "lbt_cfg"); /* fetch value (if possible) */ + if (json_value_get_type(val) != JSONObject) { + MSG("INFO: no configuration for LBT\n"); + } else { + val = json_object_dotget_value(conf_obj, "lbt_cfg.enable"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONBoolean) { + lbtconf.enable = (bool)json_value_get_boolean(val); + } else { + MSG("WARNING: Data type for lbt_cfg.enable seems wrong, please check\n"); + lbtconf.enable = false; + } + val = json_object_dotget_value(conf_obj, "lbt_cfg.rssi_target"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + lbtconf.rssi_target = (uint8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for lbt_cfg.rssi_target seems wrong, please check\n"); + lbtconf.rssi_target = 0; + } + val = json_object_dotget_value(conf_obj, "lbt_cfg.nb_channel"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + lbtconf.nb_channel = (uint8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for lbt_cfg.nb_channel seems wrong, please check\n"); + lbtconf.nb_channel = 0; + } + val = json_object_dotget_value(conf_obj, "lbt_cfg.start_freq"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + lbtconf.start_freq = (uint32_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for lbt_cfg.start_freq seems wrong, please check\n"); + lbtconf.start_freq = 0; + } + val = json_object_dotget_value(conf_obj, "lbt_cfg.scan_time_us"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + lbtconf.scan_time_us = (uint32_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for lbt_cfg.scan_time_us seems wrong, please check\n"); + lbtconf.scan_time_us = 0; + } + val = json_object_dotget_value(conf_obj, "lbt_cfg.tx_delay_1ch_us"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + lbtconf.tx_delay_1ch_us = (uint32_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for lbt_cfg.tx_delay_1ch_us seems wrong, please check\n"); + lbtconf.tx_delay_1ch_us = 0; + } + val = json_object_dotget_value(conf_obj, "lbt_cfg.tx_delay_2ch_us"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + lbtconf.tx_delay_2ch_us = (uint32_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for lbt_cfg.tx_delay_2ch_us seems wrong, please check\n"); + lbtconf.tx_delay_2ch_us = 0; + } + } + /* all parameters parsed, submitting configuration to the HAL */ + if (lgw_lbt_setconf(lbtconf) != LGW_HAL_SUCCESS) { + MSG("WARNING: Failed to configure lbt\n"); + } + + /* set antenna gain configuration */ + val = json_object_get_value(conf_obj, "antenna_gain"); /* fetch value (if possible) */ + 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 configuration for tx gains */ + memset(&txlut, 0, sizeof txlut); /* initialize configuration structure */ + for (i = 0; i < TX_GAIN_LUT_SIZE_MAX; i++) { + snprintf(param_name, sizeof param_name, "tx_lut_%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 tx gain lut %i\n", i); + continue; + } + txlut.size++; /* update TX LUT size based on JSON object found in configuration file */ + /* there is an object to configure that TX gain index, let's parse it */ + snprintf(param_name, sizeof param_name, "tx_lut_%i.pa_gain", i); + val = json_object_dotget_value(conf_obj, param_name); + if (json_value_get_type(val) == JSONNumber) { + txlut.lut[i].pa_gain = (uint8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); + txlut.lut[i].pa_gain = 0; + } + snprintf(param_name, sizeof param_name, "tx_lut_%i.dac_gain", i); + val = json_object_dotget_value(conf_obj, param_name); + if (json_value_get_type(val) == JSONNumber) { + txlut.lut[i].dac_gain = (uint8_t)json_value_get_number(val); + } else { + txlut.lut[i].dac_gain = 3; /* This is the only dac_gain supported for now */ + } + snprintf(param_name, sizeof param_name, "tx_lut_%i.dig_gain", i); + val = json_object_dotget_value(conf_obj, param_name); + if (json_value_get_type(val) == JSONNumber) { + txlut.lut[i].dig_gain = (uint8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); + txlut.lut[i].dig_gain = 0; + } + snprintf(param_name, sizeof param_name, "tx_lut_%i.mix_gain", i); + val = json_object_dotget_value(conf_obj, param_name); + if (json_value_get_type(val) == JSONNumber) { + txlut.lut[i].mix_gain = (uint8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); + txlut.lut[i].mix_gain = 0; + } + snprintf(param_name, sizeof param_name, "tx_lut_%i.rf_power", i); + val = json_object_dotget_value(conf_obj, param_name); + if (json_value_get_type(val) == JSONNumber) { + txlut.lut[i].rf_power = (int8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); + txlut.lut[i].rf_power = 0; + } + } + /* all parameters parsed, submitting configuration to the HAL */ + MSG("INFO: Configuring TX LUT with %u indexes\n", txlut.size); + if (lgw_txgain_setconf(&txlut) != LGW_HAL_SUCCESS) { + MSG("WARNING: Failed to configure concentrator TX Gain LUT\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.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 { + MSG("WARNING: invalid radio type: %s (should be SX1255 or SX1257)\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); + } + } + } 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("WARNING: invalid configuration for radio %i\n", i); + } + } + + /* 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 7 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("WARNING: invalid configuration for Lora multi-SF channel %i\n", i); + } + } + + /* 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 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; + } + MSG("INFO: Lora std channel> radio %i, IF %i Hz, %u Hz bw, SF %u\n", ifconf.rf_chain, ifconf.freq_hz, bw, sf); + } + if (lgw_rxif_setconf(8, ifconf) != LGW_HAL_SUCCESS) { + MSG("WARNING: invalid configuration for Lora standard channel\n"); + } + } + + /* 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; + 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; + 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("WARNING: invalid configuration for FSK channel\n"); + } + } + 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); + 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); + } + + /* 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 uint16_t crc_ccit(const uint8_t * data, unsigned size) { + const uint16_t crc_poly = 0x1021; /* CCITT */ + const uint16_t init_val = 0xFFFF; /* CCITT */ + uint16_t x = init_val; + unsigned i, j; + + if (data == NULL) { + return 0; + } + + for (i=0; i>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); + + /* 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); + } + + /* 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); + } + i = pthread_create( &thrid_timersync, NULL, (void * (*)(void *))thread_timersync, NULL); + if (i != 0) { + MSG("ERROR: [main] impossible to create Timer Sync 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; + //cp_gps_err = meas_gps_err; + pthread_mutex_unlock(&mx_meas_gps); + } + + /* overwrite with reference coordinates if function is enabled */ + if (gps_fake_enable == true) { + gps_enabled = true; + coord_ok = 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("# 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"); + jit_print_queue (&jit_queue, 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 (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 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 { + 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)) { + 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 */ + pthread_cancel(thrid_timersync); /* don't wait for timer sync 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 */ + } + + /* 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"); + } + } + + MSG("INFO: Exiting packet forwarder program\n"); + exit(EXIT_SUCCESS); +} + +/* -------------------------------------------------------------------------- */ +/* --- THREAD 1: RECEIVING PACKETS AND FORWARDING THEM ---------------------- */ + +void thread_up(void) { + int i, j; /* loop variables */ + unsigned pkt_in_dgram; /* nb on Lora packet in the current datagram */ + + /* 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 */ + + /* 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; + } + + /* 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 */ + 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; + + /* 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; + printf( "\nINFO: Received pkt from mote: %08X (fcnt=%u)\n", mote_addr, mote_fcnt ); + 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->rssi); + 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); + + /* 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; + } + + /* 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); + } + } + } + + /* 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", p->if_chain, p->rf_chain, ((double)p->freq_hz / 1e6)); + 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\n"); + 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_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\n"); + 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\n"); + 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\n"); + memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"?\"", 11); + buff_index += 11; + exit(EXIT_FAILURE); + } + + /* Lora SNR, 11-13 useful chars */ + 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); + } + } 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\n"); + exit(EXIT_FAILURE); + } + + /* Packet RSSI, payload size, 18-23 useful chars */ + j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"rssi\":%.0f,\"size\":%u", p->rssi, 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; + } + + /* 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 ---------- */ + +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; + short x2, x3, x4; + double x5, x6; + + /* variables to send on UTC timestamp */ + struct tref local_ref; /* time reference used for UTC <-> timestamp conversion */ + struct tm utc_vector; /* for collecting the elements of the UTC time */ + struct timespec utc_tx; /* UTC time that needs to be converted to timestamp */ + + /* beacon variables */ + struct lgw_pkt_tx_s beacon_pkt; + uint8_t beacon_loop; + 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 */ + uint8_t field_info = 0; + 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 */ + struct timeval current_unix_time; + struct timeval current_concentrator_time; + enum jit_error_e jit_result = JIT_ERROR_OK; + enum jit_pkt_type_e downlink_type; + + /* 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 = 14; + beacon_pkt.modulation = MOD_LORA; + beacon_pkt.bandwidth = BW_125KHZ; + beacon_pkt.datarate = DR_LORA_SF9; + 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; + beacon_pkt.size = 17; + + /* fixed bacon fields (little endian) */ + beacon_pkt.payload[0] = 0x0; /* RFU */ + beacon_pkt.payload[1] = 0x0; /* RFU */ + /* 2-5 : time (variable) */ + /* 6-7 : crc1 (variable) */ + + /* 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 = 0x00FFFFFF & (int32_t)((reference_coord.lon / 180.0) * (double)(1<<23)); /* +180 = -180 = 0x800000 */ + + /* optional beacon fields */ + beacon_pkt.payload[ 8] = field_info; + beacon_pkt.payload[ 9] = 0xFF & field_latitude; + beacon_pkt.payload[10] = 0xFF & (field_latitude >> 8); + beacon_pkt.payload[11] = 0xFF & (field_latitude >> 16); + beacon_pkt.payload[12] = 0xFF & field_longitude; + beacon_pkt.payload[13] = 0xFF & (field_longitude >> 8); + beacon_pkt.payload[14] = 0xFF & (field_longitude >> 16); + + /* CRC of the optional beacon fileds */ + field_crc2 = crc_ccit((beacon_pkt.payload + 8), 7); + beacon_pkt.payload[15] = 0xFF & field_crc2; + beacon_pkt.payload[16] = 0xFF & (field_crc2 >> 8); + + /* JIT queue initialization */ + jit_queue_init(&jit_queue); + + 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.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 = [0:50ms] */ + if (last_beacon_gps_time.tv_sec == 0) { + /* if no beacon has been queued, get next slot from current UTC time */ + diff_beacon_time = time_reference_gps.utc.tv_sec % ((time_t)beacon_period); + next_beacon_gps_time.tv_sec = time_reference_gps.utc.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; + + MSG_DEBUG(DEBUG_BEACON, "GPS-now : %s", ctime(&time_reference_gps.utc.tv_sec)); + MSG_DEBUG(DEBUG_BEACON, "GPS-last: %s", ctime(&last_beacon_gps_time.tv_sec)); + MSG_DEBUG(DEBUG_BEACON, "GPS-next: %s", ctime(&next_beacon_gps_time.tv_sec)); + + /* convert UTC time to concentrator time, and set packet counter for JiT trigger */ + lgw_utc2cnt(time_reference_gps, next_beacon_gps_time, &(beacon_pkt.count_us)); + pthread_mutex_unlock(&mx_timeref); + + /* apply frequency correction to beacon TX frequency */ + pthread_mutex_lock(&mx_xcorr); + beacon_pkt.freq_hz = (uint32_t)(xtal_correct * (double)beacon_freq_hz); + pthread_mutex_unlock(&mx_xcorr); + + /* load time in beacon payload */ + beacon_pkt.payload[2] = 0xFF & next_beacon_gps_time.tv_sec; + beacon_pkt.payload[3] = 0xFF & (next_beacon_gps_time.tv_sec >> 8); + beacon_pkt.payload[4] = 0xFF & (next_beacon_gps_time.tv_sec >> 16); + beacon_pkt.payload[5] = 0xFF & (next_beacon_gps_time.tv_sec >> 24); + + /* calculate CRC */ + field_crc1 = crc_ccit(beacon_pkt.payload, 6); /* CRC for the first 6 bytes */ + beacon_pkt.payload[6] = 0xFF & field_crc1; + beacon_pkt.payload[7] = 0xFF & (field_crc1 >> 8); + + /* Insert beacon packet in JiT queue */ + gettimeofday(¤t_unix_time, NULL); + get_concentrator_time(¤t_concentrator_time, current_unix_time); + jit_result = jit_enqueue(&jit_queue, ¤t_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("--- Beacon queued (count_us=%u) - payload: ---\n", beacon_pkt.count_us); + for (i=0; i<24; ++i) { + MSG("0x%02X", beacon_pkt.payload[i]); + if (i%8 == 7) { + MSG("\n"); + } else { + MSG(" - "); + } + } + if (i%8 != 0) { + MSG("\n"); + } + MSG("--- end of payload ---\n"); + } else { + /* 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 UTC time (converted to timestamp value) */ + str = json_object_get_string(txpk_obj, "time"); + if (str == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.tmst\" or \"txpk.time\" 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 UTC 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); + continue; + } + } else { + MSG("WARNING: [down] GPS disabled, impossible to send packet on specific UTC 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); + continue; + } + + i = sscanf (str, "%4hd-%2hd-%2hdT%2hd:%2hd:%9lf", &x0, &x1, &x2, &x3, &x4, &x5); + if (i != 6 ) { + MSG("WARNING: [down] \"txpk.time\" must follow ISO 8601 format, TX aborted\n"); + json_value_free(root_val); + continue; + } + x5 = modf(x5, &x6); /* x6 get the integer part of x5, x5 the fractional part */ + utc_vector.tm_year = x0 - 1900; /* years since 1900 */ + utc_vector.tm_mon = x1 - 1; /* months since January */ + utc_vector.tm_mday = x2; /* day of the month 1-31 */ + utc_vector.tm_hour = x3; /* hours since midnight */ + utc_vector.tm_min = x4; /* minutes after the hour */ + utc_vector.tm_sec = (int)x6; + utc_tx.tv_sec = mktime(&utc_vector) - timezone; + utc_tx.tv_nsec = (long)(1e9 * x5); + + /* transform UTC time to timestamp */ + i = lgw_utc2cnt(local_ref, utc_tx, &(txpkt.count_us)); + if (i != LGW_GPS_SUCCESS) { + MSG("WARNING: [down] could not convert UTC 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 UTC 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 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); + + /* check TX parameter before trying to queue packet */ + jit_result = JIT_ERROR_OK; + 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]); + } + if (jit_result == JIT_ERROR_OK) { + for (i=0; i -1) { + jit_result = jit_dequeue(&jit_queue, pkt_index, &pkt, &pkt_type); + if (jit_result == JIT_ERROR_OK) { + /* update beacon stats */ + if (pkt_type == JIT_PKT_TYPE_BEACON) { + pthread_mutex_lock(&mx_meas_dw); + meas_nb_beacon_sent += 1; + pthread_mutex_unlock(&mx_meas_dw); + } + + /* check if concentrator is free for sending new packet */ + result = lgw_status(TX_STATUS, &tx_status); + if (result == LGW_HAL_ERROR) { + MSG("WARNING: [jit] lgw_status failed\n"); + } else { + if (tx_status == TX_EMITTING) { + MSG("ERROR: concentrator is currently emitting\n"); + print_tx_status(tx_status); + continue; + } else if (tx_status == TX_SCHEDULED) { + MSG("WARNING: a downlink was already scheduled, overwritting it...\n"); + 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\n"); + 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: count_us=%u\n", pkt.count_us); + } + } else { + MSG("ERROR: jit_dequeue failed with %d\n", jit_result); + } + } + } else if (jit_result == JIT_ERROR_EMPTY) { + /* Do nothing, it can happen */ + } else { + MSG("ERROR: jit_peek failed with %d\n", jit_result); + } + } +} + +/* -------------------------------------------------------------------------- */ +/* --- THREAD 4: PARSE GPS MESSAGE AND KEEP GATEWAY IN SYNC ----------------- */ + +void thread_gps(void) { + int i; + + /* serial variables */ + char serial_buff[128]; /* buffer to receive GPS data */ + ssize_t nb_char; + + /* variables for PPM pulse GPS synchronization */ + enum gps_msg latest_msg; /* keep track of latest NMEA message parsed */ + struct timespec utc_time; /* UTC time associated with PPS pulse */ + uint32_t trig_tstamp; /* concentrator timestamp associated with PPM pulse */ + + /* position variable */ + struct coord_s coord; + struct coord_s gpserr; + + /* initialize some variables before loop */ + memset(serial_buff, 0, sizeof serial_buff); + + while (!exit_sig && !quit_sig) { + /* blocking canonical read on serial port */ + nb_char = read(gps_tty_fd, serial_buff, sizeof(serial_buff)-1); + if (nb_char <= 0) { + MSG("WARNING: [gps] read() returned value <= 0\n"); + continue; + } else { + serial_buff[nb_char] = 0; /* add null terminator, just to be sure */ + } + + /* parse the received NMEA */ + latest_msg = lgw_parse_nmea(serial_buff, sizeof(serial_buff)); + + if (latest_msg == NMEA_RMC) { /* trigger sync only on RMC frames */ + + /* get UTC time for synchronization */ + i = lgw_gps_get(&utc_time, NULL, NULL); + if (i != LGW_GPS_SUCCESS) { + MSG("WARNING: [gps] could not get UTC time from GPS\n"); + continue; + } + + /* 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"); + continue; + } + + /* try to update time reference with the new UTC & timestamp */ + pthread_mutex_lock(&mx_timeref); + i = lgw_gps_sync(&time_reference_gps, trig_tstamp, utc_time); + pthread_mutex_unlock(&mx_timeref); + if (i != LGW_GPS_SUCCESS) { + MSG("WARNING: [gps] GPS out of sync, keeping previous time reference\n"); + continue; + } + + /* update gateway coordinates */ + i = lgw_gps_get(NULL, &coord, &gpserr); + 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); + } + } + 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; + } 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; + 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/basic_pkt_fwd/src/parson.c b/lora_pkt_fwd/src/parson.c similarity index 100% rename from basic_pkt_fwd/src/parson.c rename to lora_pkt_fwd/src/parson.c diff --git a/lora_pkt_fwd/src/timersync.c b/lora_pkt_fwd/src/timersync.c new file mode 100644 index 00000000..df5837c5 --- /dev/null +++ b/lora_pkt_fwd/src/timersync.c @@ -0,0 +1,144 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Description: + LoRa concentrator : Timer synchronization + Provides synchronization between unix, concentrator and gps clocks + +License: Revised BSD License, see LICENSE.TXT file include in the project +Maintainer: Michael Coracin +*/ + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* printf, fprintf, snprintf, fopen, fputs */ +#include /* C99 types */ +#include + +#include "trace.h" +#include "timersync.h" +#include "loragw_hal.h" +#include "loragw_reg.h" +#include "loragw_aux.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS & TYPES -------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define timersub(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((result)->tv_usec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_usec += 1000000; \ + } \ + } while (0) + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */ + +static pthread_mutex_t mx_timersync = PTHREAD_MUTEX_INITIALIZER; /* control access to timer sync offsets */ +static struct timeval offset_unix_concent = {0,0}; /* timer offset between unix host and concentrator */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE SHARED VARIABLES (GLOBAL) ------------------------------------ */ +extern bool exit_sig; +extern bool quit_sig; +extern pthread_mutex_t mx_concent; + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int get_concentrator_time(struct timeval *concent_time, struct timeval unix_time) { + struct timeval local_timeval; + + if (concent_time == NULL) { + MSG("ERROR: %s invalid parameter\n", __FUNCTION__); + return -1; + } + + pthread_mutex_lock(&mx_timersync); /* protect global variable access */ + timersub(&unix_time, &offset_unix_concent, &local_timeval); + pthread_mutex_unlock(&mx_timersync); + + /* TODO: handle sx1301 coutner wrap-up !! */ + concent_time->tv_sec = local_timeval.tv_sec; + concent_time->tv_usec = local_timeval.tv_usec; + + MSG_DEBUG(DEBUG_TIMERSYNC, " --> TIME: unix current time is %ld,%ld\n", unix_time.tv_sec, unix_time.tv_usec); + MSG_DEBUG(DEBUG_TIMERSYNC, " offset is %ld,%ld\n", offset_unix_concent.tv_sec, offset_unix_concent.tv_usec); + MSG_DEBUG(DEBUG_TIMERSYNC, " sx1301 current time is %ld,%ld\n", local_timeval.tv_sec, local_timeval.tv_usec); + + return 0; +} + +/* ---------------------------------------------------------------------------------------------- */ +/* --- THREAD 6: REGULARLAY MONITOR THE OFFSET BETWEEN UNIX CLOCK AND CONCENTRATOR CLOCK -------- */ + +void thread_timersync(void) { + struct timeval unix_timeval; + struct timeval concentrator_timeval; + uint32_t sx1301_timecount = 0; + struct timeval offset_previous = {0,0}; + struct timeval offset_drift = {0,0}; /* delta between current and previous offset */ + + while (!exit_sig && !quit_sig) { + /* Regularly disable GPS mode of concentrator's counter, in order to get + real timer value for synchronizing with host's unix timer */ + MSG("\nINFO: Disabling GPS mode for concentrator's counter...\n"); + pthread_mutex_lock(&mx_concent); /* TODO: Is it necessary to protect here? */ + lgw_reg_w(LGW_GPS_EN, 0); + pthread_mutex_unlock(&mx_concent); + + /* Get current unix time */ + gettimeofday(&unix_timeval, NULL); + + /* Get current concentrator counter value (1MHz) */ + lgw_get_trigcnt(&sx1301_timecount); + concentrator_timeval.tv_sec = sx1301_timecount / 1000000UL; + concentrator_timeval.tv_usec = sx1301_timecount - (concentrator_timeval.tv_sec * 1000000UL); + + /* Compute offset between unix and concentrator timers, with microsecond precision */ + offset_previous.tv_sec = offset_unix_concent.tv_sec; + offset_previous.tv_usec = offset_unix_concent.tv_usec; + + /* TODO: handle sx1301 coutner wrap-up */ + pthread_mutex_lock(&mx_timersync); /* protect global variable access */ + timersub(&unix_timeval, &concentrator_timeval, &offset_unix_concent); + pthread_mutex_unlock(&mx_timersync); + + timersub(&offset_unix_concent, &offset_previous, &offset_drift); + + MSG_DEBUG(DEBUG_TIMERSYNC, " sx1301 = %u (µs) - timeval (%ld,%ld)\n", + sx1301_timecount, + concentrator_timeval.tv_sec, + concentrator_timeval.tv_usec); + MSG_DEBUG(DEBUG_TIMERSYNC, " unix_timeval = %ld,%ld\n", unix_timeval.tv_sec, unix_timeval.tv_usec); + + MSG("INFO: host/sx1301 time offset=(%lds:%ldµs) - drift=%ldµs\n", + offset_unix_concent.tv_sec, + offset_unix_concent.tv_usec, + offset_drift.tv_sec * 1000000UL + offset_drift.tv_usec); + MSG("INFO: Enabling GPS mode for concentrator's counter.\n\n"); + pthread_mutex_lock(&mx_concent); /* TODO: Is it necessary to protect here? */ + lgw_reg_w(LGW_GPS_EN, 1); + pthread_mutex_unlock(&mx_concent); + + /* delay next sync */ + /* If we consider a crystal oscillator precision of about 20ppm worst case, and a clock + running at 1MHz, this would mean 1µs drift every 50000µs (10000000/20). + As here the time precision is not critical, we should be able to cope with at least 1ms drift, + which should occur after 50s (50000µs * 1000). + Let's set the thread sleep to 1 minute for now */ + wait_ms(60000); + } +} diff --git a/lora_pkt_fwd/update_gwid.sh b/lora_pkt_fwd/update_gwid.sh new file mode 100755 index 00000000..2aeb87f5 --- /dev/null +++ b/lora_pkt_fwd/update_gwid.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +# This script is a helper to update the Gateway_ID field of given +# JSON configuration file, as a EUI-64 address generated from the 48-bits MAC +# address of the device it is run from. +# +# Usage examples: +# ./update_gwid.sh ./local_conf.json + +iot_sk_update_gwid() { + # get gateway ID from its MAC address to generate an EUI-64 address + GWID_MIDFIX="FFFE" + GWID_BEGIN=$(ip link show eth0 | awk '/ether/ {print $2}' | awk -F\: '{print $1$2$3}') + GWID_END=$(ip link show eth0 | awk '/ether/ {print $2}' | awk -F\: '{print $4$5$6}') + + # replace last 8 digits of default gateway ID by actual GWID, in given JSON configuration file + sed -i 's/\(^\s*"gateway_ID":\s*"\).\{16\}"\s*\(,\?\).*$/\1'${GWID_BEGIN}${GWID_MIDFIX}${GWID_END}'"\2/' $1 + + echo "Gateway_ID set to "$GWID_BEGIN$GWID_MIDFIX$GWID_END" in file "$1 +} + +if [ $# -ne 1 ] +then + echo "Usage: $0 [filename]" + echo " filename: Path to JSON file containing Gateway_ID for packet forwarder" + exit 1 +fi + +iot_sk_update_gwid $1 + +exit 0 diff --git a/readme.md b/readme.md index 9db1b28e..439fa9e3 100644 --- a/readme.md +++ b/readme.md @@ -8,12 +8,14 @@ Lora network packet forwarder project ====================================== -1. Core program: basic_pkt_fwd +1. Core program: lora_pkt_fwd ------------------------------- -The basic packet forwarder is a program running on the host of a Lora Gateway -that forward RF packets receive by the concentrator to a server through a -IP/UDP link, and emits RF packets that are sent by the server. +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. ((( Y ))) | @@ -24,9 +26,13 @@ IP/UDP link, and emits RF packets that are sent by the server. || Concentrator |<----+ Host |<------xx or xx-------->| | || | SPI | || xx Intranet xx | Server | |+--------------+ +------+| xxxx x xxxx | | - | | xxxxxxxx | | - | Gateway | | | - +- - - - - - - - - - - - - - -+ +--------+ + | ^ ^ | 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. @@ -35,20 +41,7 @@ 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. -2. Packet forwarder derivatives --------------------------------- - -### 2.1 gps_pkt_fwd ### - -This derivative of the basic_packet_forwarder add support for a GPS receiver -for absolute time synchronization and gateway localisation. - -### 2.2 beacon_pkt_fwd ### - -This derivative of the gps_packet_forwarder uses GPS reference to send beacon -packets at very accurate time intervals for node synchronization. - -3. Helper programs +2. Helper programs ------------------- Those programs are included in the project to provide examples on how to @@ -75,17 +68,37 @@ through the gateway-to-server downlink route. 4. Helper scripts ----------------- -### 4.1. reset_pkt_fwd.sh +### 4.1. lora_gateway/reset_lgw.sh + +This script, provided with the HAL (lora_gateway), must be launched on IoT Start +Kit platform to reset concentrator chip through GPIO, before starting any +application using the concentrator, like the packet forwarder. -This script must be launched on IoT Start Kit platform to reset concentrator -chip through GPIO, before starting any packet forwarder. -It also allows automatic update of Gateway_ID with unique MAC address, in +### 4.2. packet_forwarder/lora_pkt_fwd/update_gwid.sh + +This script allows automatic update of Gateway_ID with unique MAC address, in packet forwarder JSON configuration file. Please refer to the script header for more details. 5. Changelog ------------- +### v3.0.0 - 2016-05-19 ### + +* Merged all different flavours of packet forwarder into one unique lora_pkt_fwd + Note: Various flavours can still be achieved using the corresponding + global_conf.json.XXX file provided in lora_pkt_fwd/cfg. +* Added downlink "just-in-time" scheduling to optimize downlink capacity. +* Updated Gateway <-> NetworkServer protocol to describe the new format of + "tx_ack" message. +* Added "Listen-Before-Talk" configuration. +* Splitted reset_pkt_fwd.sh script in 2 different scripts: + - reset_lgw.sh, provided with the HAL (lora_gateway) + - update_gwid.sh, provided with lora_pkt_fwd + +WARNING: Gateway <-> Network Server protocol version has changed. Please refer + to PROTOCOL.txt file. + ### v2.2.1 - 2016-04-12 ### * util_tx_test: added FSK support and specific payload for easier PER testing. diff --git a/reset_pkt_fwd.sh b/reset_pkt_fwd.sh deleted file mode 100755 index 651d92c9..00000000 --- a/reset_pkt_fwd.sh +++ /dev/null @@ -1,85 +0,0 @@ -#!/bin/sh - -# This script is intended to be used on IoT Starter Kit platform only, it -# performs the following actions: -# - export/unpexort GPIO7 used to reset the SX1301 chip -# - optionaly update the Gateway_ID field of given JSON configuration -# file, as a EUI-64 address generated from the 48-bits MAC address -# -# Usage examples: -# ./reset_pkt_fwd.sh start ./local_conf.json -# ./gps_pkt_fwd -# (stop with Ctrl-C) -# ./reset_pkt_fwd.sh stop -# ./reset_pkt_fwd.sh start -# ./gps_pkt_fwd - -# Force bypassing auto update of Gateway_ID in JSON conf file -IOT_SK_GWID_UPDATE=true - -# The reset pin of SX1301 is wired with RPi GPIO7 -IOT_SK_SX1301_RESET_PIN=7 - -WAIT_GPIO() { - sleep 0.1 -} - -iot_sk_init() { - # setup GPIO 7 - echo "$IOT_SK_SX1301_RESET_PIN" > /sys/class/gpio/export; WAIT_GPIO - - # set GPIO 7 as output - echo "out" > /sys/class/gpio/gpio$IOT_SK_SX1301_RESET_PIN/direction; WAIT_GPIO - - # write output for SX1301 reset - echo "1" > /sys/class/gpio/gpio$IOT_SK_SX1301_RESET_PIN/value; WAIT_GPIO - echo "0" > /sys/class/gpio/gpio$IOT_SK_SX1301_RESET_PIN/value; WAIT_GPIO - - # set GPIO 7 as input - echo "in" > /sys/class/gpio/gpio$IOT_SK_SX1301_RESET_PIN/direction; WAIT_GPIO -} - -iot_sk_term() { - # cleanup GPIO 7 - if [ -d /sys/class/gpio/gpio$IOT_SK_SX1301_RESET_PIN ] - then - echo "$IOT_SK_SX1301_RESET_PIN" > /sys/class/gpio/unexport; WAIT_GPIO - fi -} - -iot_sk_update_gwid() { - if [ -z "$1" ] - then - echo "Gateway_ID not set, using default" - else - # get gateway ID from its MAC address to generate an EUI-64 address - GWID_MIDFIX="FFFE" - GWID_BEGIN=$(ip link show eth0 | awk '/ether/ {print $2}' | awk -F\: '{print $1$2$3}') - GWID_END=$(ip link show eth0 | awk '/ether/ {print $2}' | awk -F\: '{print $4$5$6}') - - # replace last 8 digits of default gateway ID by actual GWID, in given JSON configuration file - sed -i 's/\(^\s*"gateway_ID":\s*"\).\{16\}"\s*\(,\?\).*$/\1'${GWID_BEGIN}${GWID_MIDFIX}${GWID_END}'"\2/' $1 - - echo "Gateway_ID set to "$GWID_BEGIN$GWID_MIDFIX$GWID_END" in file "$1 - fi -} - -case "$1" in - start) - iot_sk_term - iot_sk_init - if $IOT_SK_GWID_UPDATE; then - iot_sk_update_gwid $2 - fi - ;; - stop) - iot_sk_term - ;; - *) - echo "Usage: $0 {start|stop} [filename]" - echo " filename: Path to JSON file containing Gateway_ID for packet forwarder" - exit 1 - ;; -esac - -exit 0 diff --git a/util_ack/src/util_ack.c b/util_ack/src/util_ack.c index b1f8c0aa..7acd32ea 100644 --- a/util_ack/src/util_ack.c +++ b/util_ack/src/util_ack.c @@ -7,7 +7,7 @@ (C)2013 Semtech-Cycleo Description: - Network sink, receives UDP packets and sends an acknowledge + Network sink, receives UDP packets and sends an acknowledge License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -19,175 +19,175 @@ Maintainer: Sylvain Miermont /* fix an issue between POSIX and C99 */ #if __STDC_VERSION__ >= 199901L - #define _XOPEN_SOURCE 600 + #define _XOPEN_SOURCE 600 #else - #define _XOPEN_SOURCE 500 + #define _XOPEN_SOURCE 500 #endif -#include /* C99 types */ -#include /* printf, fprintf, sprintf, fopen, fputs */ -#include /* usleep */ +#include /* C99 types */ +#include /* printf, fprintf, sprintf, fopen, fputs */ +#include /* usleep */ -#include /* memset */ -#include /* time, clock_gettime, strftime, gmtime, clock_nanosleep*/ -#include /* atoi, exit */ -#include /* error messages */ +#include /* memset */ +#include /* time, clock_gettime, strftime, gmtime, clock_nanosleep*/ +#include /* atoi, exit */ +#include /* error messages */ #include /* socket specific definitions */ #include /* INET constants and stuff */ #include /* IP address conversion stuff */ -#include /* gai_strerror */ +#include /* gai_strerror */ /* -------------------------------------------------------------------------- */ /* --- PRIVATE MACROS ------------------------------------------------------- */ -#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) -#define STRINGIFY(x) #x -#define STR(x) STRINGIFY(x) -#define MSG(args...) fprintf(stderr, args) /* message that is destined to the user */ +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#define STRINGIFY(x) #x +#define STR(x) STRINGIFY(x) +#define MSG(args...) fprintf(stderr, args) /* message that is destined to the user */ /* -------------------------------------------------------------------------- */ /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ -#define PROTOCOL_VERSION 1 +#define PROTOCOL_VERSION 2 -#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_PUSH_DATA 0 +#define PKT_PUSH_ACK 1 +#define PKT_PULL_DATA 2 +#define PKT_PULL_RESP 3 +#define PKT_PULL_ACK 4 /* -------------------------------------------------------------------------- */ /* --- MAIN FUNCTION -------------------------------------------------------- */ int main(int argc, char **argv) { - int i; /* loop variable and temporary variable for return value */ - - /* 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]; - - /* variables for receiving and sending packets */ - struct sockaddr_storage dist_addr; - socklen_t addr_len = sizeof dist_addr; - uint8_t databuf[4096]; - 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; - - /* check if port number was passed as parameter */ - if (argc != 2) { - MSG("Usage: util_ack \n"); - 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 */ - i = getaddrinfo(NULL, argv[1], &hints, &result); - if (i != 0) { - MSG("ERROR: getaddrinfo returned %s\n", gai_strerror(i)); - exit(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 { - i = bind(sock, q->ai_addr, q->ai_addrlen); - if (i == -1) { - shutdown(sock, SHUT_RDWR); - continue; /* bind failed, try next field */ - } else { - break; /* success, get out of loop */ - } - } - } - if (q == NULL) { - MSG("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); - MSG("INFO: result %i host:%s service:%s\n", i, host_name, port_name); - ++i; - } - exit(EXIT_FAILURE); - } - MSG("INFO: util_ack listening on port %s\n", argv[1]); - freeaddrinfo(result); - - while (1) { - /* wait to receive a packet */ - byte_nb = recvfrom(sock, databuf, sizeof databuf, 0, (struct sockaddr *)&dist_addr, &addr_len); - if (byte_nb == -1) { - MSG("ERROR: recvfrom returned %s \n", strerror(errno)); - exit(EXIT_FAILURE); - } - - /* display info about the sender */ - i = getnameinfo((struct sockaddr *)&dist_addr, addr_len, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST); - if (i == -1) { - MSG("ERROR: getnameinfo returned %s \n", gai_strerror(i)); - exit(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 */ - if (databuf[0] != PROTOCOL_VERSION) { /* check protocol version number */ - printf(", invalid version %u\n", databuf[0]); - continue; - } - raw_mac_h = *((uint32_t *)(databuf+4)); - raw_mac_l = *((uint32_t *)(databuf+8)); - gw_mac = ((uint64_t)ntohl(raw_mac_h) << 32) + (uint64_t)ntohl(raw_mac_l); - - /* interpret gateway command */ - switch (databuf[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; - printf("<- pkt out, PUSH_ACK for host %s (port %s)", host_name, port_name); - 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; - printf("<- pkt out, PULL_ACK for host %s (port %s)", host_name, port_name); - break; - default: - printf(", unexpected command %u\n", databuf[3]); - continue; - } - - /* add some artificial latency */ - usleep(30000); /* 30 ms */ - - /* send acknowledge and check return value */ - databuf[3] = ack_command; - byte_nb = sendto(sock, (void *)databuf, 4, 0, (struct sockaddr *)&dist_addr, addr_len); - if (byte_nb == -1) { - printf(", send error:%s\n", strerror(errno)); - } else { - printf(", %i bytes sent\n", byte_nb); - } - } + int i; /* loop variable and temporary variable for return value */ + + /* 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]; + + /* variables for receiving and sending packets */ + struct sockaddr_storage dist_addr; + socklen_t addr_len = sizeof dist_addr; + uint8_t databuf[4096]; + 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; + + /* check if port number was passed as parameter */ + if (argc != 2) { + MSG("Usage: util_ack \n"); + 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 */ + i = getaddrinfo(NULL, argv[1], &hints, &result); + if (i != 0) { + MSG("ERROR: getaddrinfo returned %s\n", gai_strerror(i)); + exit(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 { + i = bind(sock, q->ai_addr, q->ai_addrlen); + if (i == -1) { + shutdown(sock, SHUT_RDWR); + continue; /* bind failed, try next field */ + } else { + break; /* success, get out of loop */ + } + } + } + if (q == NULL) { + MSG("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); + MSG("INFO: result %i host:%s service:%s\n", i, host_name, port_name); + ++i; + } + exit(EXIT_FAILURE); + } + MSG("INFO: util_ack listening on port %s\n", argv[1]); + freeaddrinfo(result); + + while (1) { + /* wait to receive a packet */ + byte_nb = recvfrom(sock, databuf, sizeof databuf, 0, (struct sockaddr *)&dist_addr, &addr_len); + if (byte_nb == -1) { + MSG("ERROR: recvfrom returned %s \n", strerror(errno)); + exit(EXIT_FAILURE); + } + + /* display info about the sender */ + i = getnameinfo((struct sockaddr *)&dist_addr, addr_len, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST); + if (i == -1) { + MSG("ERROR: getnameinfo returned %s \n", gai_strerror(i)); + exit(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 */ + if (databuf[0] != PROTOCOL_VERSION) { /* check protocol version number */ + printf(", invalid version %u\n", databuf[0]); + continue; + } + raw_mac_h = *((uint32_t *)(databuf+4)); + raw_mac_l = *((uint32_t *)(databuf+8)); + gw_mac = ((uint64_t)ntohl(raw_mac_h) << 32) + (uint64_t)ntohl(raw_mac_l); + + /* interpret gateway command */ + switch (databuf[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; + printf("<- pkt out, PUSH_ACK for host %s (port %s)", host_name, port_name); + 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; + printf("<- pkt out, PULL_ACK for host %s (port %s)", host_name, port_name); + break; + default: + printf(", unexpected command %u\n", databuf[3]); + continue; + } + + /* add some artificial latency */ + usleep(30000); /* 30 ms */ + + /* send acknowledge and check return value */ + databuf[3] = ack_command; + byte_nb = sendto(sock, (void *)databuf, 4, 0, (struct sockaddr *)&dist_addr, addr_len); + if (byte_nb == -1) { + printf(", send error:%s\n", strerror(errno)); + } else { + printf(", %i bytes sent\n", byte_nb); + } + } } diff --git a/util_sink/src/util_sink.c b/util_sink/src/util_sink.c index 2f9baaaa..3c09c9bb 100644 --- a/util_sink/src/util_sink.c +++ b/util_sink/src/util_sink.c @@ -7,7 +7,7 @@ (C)2013 Semtech-Cycleo Description: - Network sink, receives UDP packets on certain ports and discards them + Network sink, receives UDP packets on certain ports and discards them License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -19,107 +19,107 @@ Maintainer: Sylvain Miermont /* fix an issue between POSIX and C99 */ #if __STDC_VERSION__ >= 199901L - #define _XOPEN_SOURCE 600 + #define _XOPEN_SOURCE 600 #else - #define _XOPEN_SOURCE 500 + #define _XOPEN_SOURCE 500 #endif -#include /* C99 types */ -#include /* printf, fprintf, sprintf, fopen, fputs */ +#include /* C99 types */ +#include /* printf, fprintf, sprintf, fopen, fputs */ -#include /* memset */ -#include /* time, clock_gettime, strftime, gmtime, clock_nanosleep*/ -#include /* atoi, exit */ -#include /* error messages */ +#include /* memset */ +#include /* time, clock_gettime, strftime, gmtime, clock_nanosleep*/ +#include /* atoi, exit */ +#include /* error messages */ #include /* socket specific definitions */ #include /* INET constants and stuff */ #include /* IP address conversion stuff */ -#include /* gai_strerror */ +#include /* gai_strerror */ /* -------------------------------------------------------------------------- */ /* --- PRIVATE MACROS ------------------------------------------------------- */ -#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) -#define STRINGIFY(x) #x -#define STR(x) STRINGIFY(x) -#define MSG(args...) fprintf(stderr, args) /* message that is destined to the user */ +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#define STRINGIFY(x) #x +#define STR(x) STRINGIFY(x) +#define MSG(args...) fprintf(stderr, args) /* message that is destined to the user */ /* -------------------------------------------------------------------------- */ /* --- MAIN FUNCTION -------------------------------------------------------- */ int main(int argc, char **argv) { - int i; /* loop variable and temporary variable for return value */ - - /* 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]; - - /* variables for receiving packets */ - struct sockaddr_storage dist_addr; - socklen_t addr_len = sizeof dist_addr; - uint8_t databuf[4096]; - int byte_nb; - - /* check if port number was passed as parameter */ - if (argc != 2) { - MSG("Usage: util_sink \n"); - 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 */ - i = getaddrinfo(NULL, argv[1], &hints, &result); - if (i != 0) { - MSG("ERROR: getaddrinfo returned %s\n", gai_strerror(i)); - exit(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 { - i = bind(sock, q->ai_addr, q->ai_addrlen); - if (i == -1) { - shutdown(sock, SHUT_RDWR); - continue; /* bind failed, try next field */ - } else { - break; /* success, get out of loop */ - } - } - } - if (q == NULL) { - MSG("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); - MSG("result %i host:%s service:%s\n", i, host_name, port_name); - ++i; - } - exit(EXIT_FAILURE); - } - MSG("INFO: util_sink listening on port %s\n", argv[1]); - freeaddrinfo(result); - - while (1) { - byte_nb = recvfrom(sock, databuf, sizeof databuf, 0, (struct sockaddr *)&dist_addr, &addr_len); - if (byte_nb == -1) { - MSG("ERROR: recvfrom returned %s \n", strerror(errno)); - exit(EXIT_FAILURE); - } - getnameinfo((struct sockaddr *)&dist_addr, addr_len, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST); - printf("Got packet from host %s port %s, %i bytes long\n", host_name, port_name, byte_nb); - } + int i; /* loop variable and temporary variable for return value */ + + /* 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]; + + /* variables for receiving packets */ + struct sockaddr_storage dist_addr; + socklen_t addr_len = sizeof dist_addr; + uint8_t databuf[4096]; + int byte_nb; + + /* check if port number was passed as parameter */ + if (argc != 2) { + MSG("Usage: util_sink \n"); + 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 */ + i = getaddrinfo(NULL, argv[1], &hints, &result); + if (i != 0) { + MSG("ERROR: getaddrinfo returned %s\n", gai_strerror(i)); + exit(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 { + i = bind(sock, q->ai_addr, q->ai_addrlen); + if (i == -1) { + shutdown(sock, SHUT_RDWR); + continue; /* bind failed, try next field */ + } else { + break; /* success, get out of loop */ + } + } + } + if (q == NULL) { + MSG("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); + MSG("result %i host:%s service:%s\n", i, host_name, port_name); + ++i; + } + exit(EXIT_FAILURE); + } + MSG("INFO: util_sink listening on port %s\n", argv[1]); + freeaddrinfo(result); + + while (1) { + byte_nb = recvfrom(sock, databuf, sizeof databuf, 0, (struct sockaddr *)&dist_addr, &addr_len); + if (byte_nb == -1) { + MSG("ERROR: recvfrom returned %s \n", strerror(errno)); + exit(EXIT_FAILURE); + } + getnameinfo((struct sockaddr *)&dist_addr, addr_len, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST); + printf("Got packet from host %s port %s, %i bytes long\n", host_name, port_name, byte_nb); + } } diff --git a/util_tx_test/inc/base64.h b/util_tx_test/inc/base64.h index c0369aad..e57eb47c 100644 --- a/util_tx_test/inc/base64.h +++ b/util_tx_test/inc/base64.h @@ -7,7 +7,7 @@ (C)2013 Semtech-Cycleo Description: - Base64 encoding & decoding library + Base64 encoding & decoding library License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -20,7 +20,7 @@ Maintainer: Sylvain Miermont /* -------------------------------------------------------------------------- */ /* --- DEPENDANCIES --------------------------------------------------------- */ -#include /* C99 types */ +#include /* C99 types */ /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ diff --git a/util_tx_test/readme.md b/util_tx_test/readme.md index 91934021..52241ea7 100644 --- a/util_tx_test/readme.md +++ b/util_tx_test/readme.md @@ -37,7 +37,7 @@ The packets are [9-n] bytes long, and have following payload content: | Id | PktCnt[31:24] | PktCnt[23:16] | PktCnt[15:8] | PktCnt[7:0] | P | E | R |FCS| 0 | 1 |...| n | +----------+---------------+---------------+---------------+---------------+---+---+---+---+---+---+---+---+ -Id : User defined ID to differenciate sender at receiver side. (8 bits) +Id : User defined ID to differentiate sender at receiver side. (8 bits) PktCnt : Packet counter incremented at each transmission. (32 bits) ‘P’, ‘E’, ‘R’ : ASCII values for characters 'P', 'E' and 'R'. FCS : Checksum: 8-bits sum of Id, PktCnt[31 :24] , PktCnt[23 :16] , PktCnt[15 :8] , PktCnt[7:0], ‘P’,’E’,’R’ diff --git a/util_tx_test/src/base64.c b/util_tx_test/src/base64.c index ca7a815d..8ba908e5 100644 --- a/util_tx_test/src/base64.c +++ b/util_tx_test/src/base64.c @@ -7,7 +7,7 @@ (C)2013 Semtech-Cycleo Description: - Base64 encoding & decoding library + Base64 encoding & decoding library License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -26,10 +26,10 @@ Maintainer: Sylvain Miermont /* -------------------------------------------------------------------------- */ /* --- 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 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...) fprintf(stderr,"debug: " args) /* diagnostic message that is destined to the user */ #define DEBUG(args...) /* -------------------------------------------------------------------------- */ @@ -38,9 +38,9 @@ Maintainer: Sylvain Miermont /* -------------------------------------------------------------------------- */ /* --- 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 */ +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 ---------------------------------------- */ @@ -59,249 +59,249 @@ 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 + 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 + 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 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 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 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); - } + 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); + } } diff --git a/util_tx_test/src/util_tx_test.c b/util_tx_test/src/util_tx_test.c index 2f8cdd7d..aad76d83 100644 --- a/util_tx_test/src/util_tx_test.c +++ b/util_tx_test/src/util_tx_test.c @@ -44,13 +44,13 @@ Maintainer: Sylvain Miermont /* -------------------------------------------------------------------------- */ /* --- PRIVATE MACROS ------------------------------------------------------- */ -#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #define MSG(args...) fprintf(stderr, args) /* message that is destined to the user */ /* -------------------------------------------------------------------------- */ /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ -#define PROTOCOL_VERSION 1 +#define PROTOCOL_VERSION 2 #define PKT_PUSH_DATA 0 #define PKT_PUSH_ACK 1