From 9372172a221a4114e4ddd362543278f8cfdc123e Mon Sep 17 00:00:00 2001 From: loradar <56890699+loradar@users.noreply.github.com> Date: Thu, 4 Jun 2020 03:20:44 +1000 Subject: [PATCH] Add files via upload --- PCAP Output/local_packet_forwarder/LICENSE | 49 + PCAP Output/local_packet_forwarder/Makefile | 22 + .../local_packet_forwarder/PROTOCOL.TXT | 455 +++ PCAP Output/local_packet_forwarder/VERSION | 1 + .../lora_pkt_fwd/Makefile | 71 + .../cfg/global_conf.json.PCB_E286.EU868.basic | 216 ++ .../global_conf.json.PCB_E286.EU868.beacon | 231 ++ .../cfg/global_conf.json.PCB_E286.EU868.gps | 227 ++ .../cfg/global_conf.json.PCB_E336.EU868.basic | 225 ++ .../global_conf.json.PCB_E336.EU868.beacon | 234 ++ .../cfg/global_conf.json.PCB_E336.EU868.gps | 231 ++ .../cfg/global_conf.json.US902.basic | 104 + .../cfg/global_conf.json.US902.gps | 110 + .../lora_pkt_fwd/global_conf.json | 237 ++ .../lora_pkt_fwd/inc/base64.h | 62 + .../lora_pkt_fwd/inc/jitqueue.h | 156 + .../lora_pkt_fwd/inc/parson.h | 111 + .../lora_pkt_fwd/inc/timersync.h | 32 + .../lora_pkt_fwd/inc/trace.h | 37 + .../lora_pkt_fwd/local_conf.json | 7 + .../lora_pkt_fwd/lora_pkt_fwd | Bin 0 -> 144628 bytes .../lora_pkt_fwd/obj/base64.o | Bin 0 -> 3564 bytes .../lora_pkt_fwd/obj/jitqueue.o | Bin 0 -> 7288 bytes .../lora_pkt_fwd/obj/lora_pkt_fwd.o | Bin 0 -> 45956 bytes .../lora_pkt_fwd/obj/parson.o | Bin 0 -> 11100 bytes .../lora_pkt_fwd/obj/timersync.o | Bin 0 -> 2516 bytes .../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 +++++++++++++++++ .../lora_pkt_fwd/src/parson.c | 782 +++++ .../lora_pkt_fwd/src/timersync.c | 144 + .../lora_pkt_fwd/update_gwid.sh | 31 + PCAP Output/local_packet_forwarder/readme.md | 231 ++ .../local_packet_forwarder/util_ack/Makefile | 33 + .../util_ack/obj/util_ack.o | Bin 0 -> 3464 bytes .../local_packet_forwarder/util_ack/readme.md | 65 + .../util_ack/src/util_ack.c | 193 ++ .../local_packet_forwarder/util_ack/util_ack | Bin 0 -> 8828 bytes .../local_packet_forwarder/util_sink/Makefile | 33 + .../util_sink/obj/util_sink.o | Bin 0 -> 2512 bytes .../util_sink/readme.md | 62 + .../util_sink/src/util_sink.c | 125 + .../util_sink/util_sink | Bin 0 -> 8716 bytes .../util_tx_test/Makefile | 36 + .../util_tx_test/inc/base64.h | 62 + .../util_tx_test/obj/base64.o | Bin 0 -> 3564 bytes .../util_tx_test/obj/util_tx_test.o | Bin 0 -> 9752 bytes .../util_tx_test/readme.md | 75 + .../util_tx_test/src/base64.c | 308 ++ .../util_tx_test/src/util_tx_test.c | 500 ++++ .../util_tx_test/util_tx_test | Bin 0 -> 18096 bytes 52 files changed, 9294 insertions(+) create mode 100644 PCAP Output/local_packet_forwarder/LICENSE create mode 100644 PCAP Output/local_packet_forwarder/Makefile create mode 100644 PCAP Output/local_packet_forwarder/PROTOCOL.TXT create mode 100644 PCAP Output/local_packet_forwarder/VERSION create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/Makefile create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.basic create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.beacon create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.gps create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.basic create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.beacon create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.gps create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.US902.basic create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.US902.gps create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/global_conf.json create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/inc/base64.h create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/inc/jitqueue.h create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/inc/parson.h create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/inc/timersync.h create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/inc/trace.h create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/local_conf.json create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/lora_pkt_fwd create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/obj/base64.o create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/obj/jitqueue.o create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/obj/lora_pkt_fwd.o create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/obj/parson.o create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/obj/timersync.o create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/readme.md create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/src/base64.c create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/src/jitqueue.c create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/src/lora_pkt_fwd.c create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/src/parson.c create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/src/timersync.c create mode 100644 PCAP Output/local_packet_forwarder/lora_pkt_fwd/update_gwid.sh create mode 100644 PCAP Output/local_packet_forwarder/readme.md create mode 100644 PCAP Output/local_packet_forwarder/util_ack/Makefile create mode 100644 PCAP Output/local_packet_forwarder/util_ack/obj/util_ack.o create mode 100644 PCAP Output/local_packet_forwarder/util_ack/readme.md create mode 100644 PCAP Output/local_packet_forwarder/util_ack/src/util_ack.c create mode 100644 PCAP Output/local_packet_forwarder/util_ack/util_ack create mode 100644 PCAP Output/local_packet_forwarder/util_sink/Makefile create mode 100644 PCAP Output/local_packet_forwarder/util_sink/obj/util_sink.o create mode 100644 PCAP Output/local_packet_forwarder/util_sink/readme.md create mode 100644 PCAP Output/local_packet_forwarder/util_sink/src/util_sink.c create mode 100644 PCAP Output/local_packet_forwarder/util_sink/util_sink create mode 100644 PCAP Output/local_packet_forwarder/util_tx_test/Makefile create mode 100644 PCAP Output/local_packet_forwarder/util_tx_test/inc/base64.h create mode 100644 PCAP Output/local_packet_forwarder/util_tx_test/obj/base64.o create mode 100644 PCAP Output/local_packet_forwarder/util_tx_test/obj/util_tx_test.o create mode 100644 PCAP Output/local_packet_forwarder/util_tx_test/readme.md create mode 100644 PCAP Output/local_packet_forwarder/util_tx_test/src/base64.c create mode 100644 PCAP Output/local_packet_forwarder/util_tx_test/src/util_tx_test.c create mode 100644 PCAP Output/local_packet_forwarder/util_tx_test/util_tx_test diff --git a/PCAP Output/local_packet_forwarder/LICENSE b/PCAP Output/local_packet_forwarder/LICENSE new file mode 100644 index 0000000..e646fe9 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/LICENSE @@ -0,0 +1,49 @@ +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. + + +--- For the 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, +ITNESS 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. + diff --git a/PCAP Output/local_packet_forwarder/Makefile b/PCAP Output/local_packet_forwarder/Makefile new file mode 100644 index 0000000..aee59c7 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/Makefile @@ -0,0 +1,22 @@ +### Environment constants + +LGW_PATH ?= ../../lora_gateway/libloragw +ARCH ?= +CROSS_COMPILE ?= +export + +### general build targets + +all: + $(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 lora_pkt_fwd + $(MAKE) clean -e -C util_ack + $(MAKE) clean -e -C util_sink + $(MAKE) clean -e -C util_tx_test + +### EOF diff --git a/PCAP Output/local_packet_forwarder/PROTOCOL.TXT b/PCAP Output/local_packet_forwarder/PROTOCOL.TXT new file mode 100644 index 0000000..3d57410 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/PROTOCOL.TXT @@ -0,0 +1,455 @@ + ______ _ + / _____) _ | | + ( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | + (______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Basic communication protocol between Lora gateway and server +============================================================= + + +1. Introduction +---------------- + +The protocol between the gateway and the server is purposefully very basic and +for demonstration purpose only, or for use on private and reliable networks. + +There is no authentication of the gateway or the server, and the acknowledges +are only used for network quality assessment, not to correct UDP datagrams +losses (no retries). + + +2. System schematic and definitions +------------------------------------ + + ((( Y ))) + | + | + + - -|- - - - - - - - - - - - - + xxxxxxxxxxxx +--------+ + | +--+-----------+ +------+ | xx x x xxx | | + | | | | | | xx Internet xx | | + | | Concentrator |<--->| Host |<-------xx or xx-------->| | + | | | SPI | | | xx Intranet xx | Server | + | +--------------+ +------+ | xxxx x xxxx | | + | ^ ^ | xxxxxxxx | | + | | PPS +-------+ NMEA | | | | + | +-----| GPS |-------+ | +--------+ + | | (opt) | | + | +-------+ | + | | + | Gateway | + +- - - - - - - - - - - - - - - -+ + +__Concentrator__: radio RX/TX board, based on Semtech multichannel modems +(SX130x), transceivers (SX135x) and/or low-power stand-alone modems (SX127x). + +__Host__: embedded computer on which the packet forwarder is run. Drives the +concentrator through a SPI link. + +__GPS__: GNSS (GPS, Galileo, GLONASS, etc) receiver with a "1 Pulse Per Second" +output and a serial link to the host to send NMEA frames containing time and +geographical coordinates data. Optional. + +__Gateway__: a device composed of at least one radio concentrator, a host, some +network connection to the internet or a private network (Ethernet, 3G, Wifi, +microwave link), and optionally a GPS receiver for synchronization. + +__Server__: an abstract computer that will process the RF packets received and +forwarded by the gateway, and issue RF packets in response that the gateway +will have to emit. + +It is assumed that the gateway can be behind a NAT or a firewall stopping any +incoming connection. +It is assumed that the server has an static IP address (or an address solvable +through a DNS service) and is able to receive incoming connections on a +specific port. + + +3. Upstream protocol +--------------------- + +### 3.1. Sequence diagram ### + + +---------+ +---------+ + | Gateway | | Server | + +---------+ +---------+ + | -----------------------------------\ | + |-| When 1-N RF packets are received | | + | ------------------------------------ | + | | + | PUSH_DATA (token X, GW MAC, JSON payload) | + |------------------------------------------------------------->| + | | + | PUSH_ACK (token X) | + |<-------------------------------------------------------------| + | ------------------------------\ | + | | process packets *after* ack |-| + | ------------------------------- | + | | + +### 3.2. PUSH_DATA packet ### + +That packet type is used by the gateway mainly to forward the RF packets +received, and associated metadata, to the server. + + Bytes | Function +:------:|--------------------------------------------------------------------- + 0 | protocol version = 2 + 1-2 | random token + 3 | PUSH_DATA identifier 0x00 + 4-11 | Gateway unique identifier (MAC address) + 12-end | JSON object, starting with {, ending with }, see section 4 + +### 3.3. PUSH_ACK packet ### + +That packet type is used by the server to acknowledge immediately all the +PUSH_DATA packets received. + + Bytes | Function +:------:|--------------------------------------------------------------------- + 0 | protocol version = 2 + 1-2 | same token as the PUSH_DATA packet to acknowledge + 3 | PUSH_ACK identifier 0x01 + + +4. Upstream JSON data structure +-------------------------------- + +The root object can contain an array named "rxpk": + +``` json +{ + "rxpk":[ {...}, ...] +} +``` + +That array contains at least one JSON object, each object contain a RF packet +and associated metadata with the following fields: + + Name | Type | Function +:----:|:------:|-------------------------------------------------------------- + time | string | UTC time of pkt RX, us precision, ISO 8601 'compact' format + tmst | number | Internal timestamp of "RX finished" event (32b unsigned) + freq | number | RX central frequency in MHz (unsigned float, Hz precision) + chan | number | Concentrator "IF" channel used for RX (unsigned integer) + rfch | number | Concentrator "RF chain" used for RX (unsigned integer) + stat | number | CRC status: 1 = OK, -1 = fail, 0 = no CRC + modu | string | Modulation identifier "LORA" or "FSK" + datr | string | LoRa datarate identifier (eg. SF12BW500) + datr | number | FSK datarate (unsigned, in bits per second) + codr | string | LoRa ECC coding rate identifier + rssi | number | RSSI in dBm (signed integer, 1 dB precision) + lsnr | number | Lora SNR ratio in dB (signed float, 0.1 dB precision) + size | number | RF packet payload size in bytes (unsigned integer) + data | string | Base64 encoded RF packet payload, padded + +Example (white-spaces, indentation and newlines added for readability): + +``` json +{"rxpk":[ + { + "time":"2013-03-31T16:21:17.528002Z", + "tmst":3512348611, + "chan":2, + "rfch":0, + "freq":866.349812, + "stat":1, + "modu":"LORA", + "datr":"SF7BW125", + "codr":"4/6", + "rssi":-35, + "lsnr":5.1, + "size":32, + "data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84" + },{ + "time":"2013-03-31T16:21:17.530974Z", + "tmst":3512348514, + "chan":9, + "rfch":1, + "freq":869.1, + "stat":1, + "modu":"FSK", + "datr":50000, + "rssi":-75, + "size":16, + "data":"VEVTVF9QQUNLRVRfMTIzNA==" + },{ + "time":"2013-03-31T16:21:17.532038Z", + "tmst":3316387610, + "chan":0, + "rfch":0, + "freq":863.00981, + "stat":1, + "modu":"LORA", + "datr":"SF10BW125", + "codr":"4/7", + "rssi":-38, + "lsnr":5.5, + "size":32, + "data":"ysgRl452xNLep9S1NTIg2lomKDxUgn3DJ7DE+b00Ass" + } +]} +``` + +The root object can also contain an object named "stat" : + +``` json +{ + "rxpk":[ {...}, ...], + "stat":{...} +} +``` + +It is possible for a packet to contain no "rxpk" array but a "stat" object. + +``` json +{ + "stat":{...} +} +``` + +That object contains the status of the gateway, with the following fields: + + Name | Type | Function +:----:|:------:|-------------------------------------------------------------- + time | string | UTC 'system' time of the gateway, ISO 8601 'expanded' format + lati | number | GPS latitude of the gateway in degree (float, N is +) + long | number | GPS latitude of the gateway in degree (float, E is +) + alti | number | GPS altitude of the gateway in meter RX (integer) + rxnb | number | Number of radio packets received (unsigned integer) + rxok | number | Number of radio packets received with a valid PHY CRC + rxfw | number | Number of radio packets forwarded (unsigned integer) + ackr | number | Percentage of upstream datagrams that were acknowledged + dwnb | number | Number of downlink datagrams received (unsigned integer) + txnb | number | Number of packets emitted (unsigned integer) + +Example (white-spaces, indentation and newlines added for readability): + +``` json +{"stat":{ + "time":"2014-01-12 08:59:28 GMT", + "lati":46.24000, + "long":3.25230, + "alti":145, + "rxnb":2, + "rxok":2, + "rxfw":2, + "ackr":100.0, + "dwnb":2, + "txnb":2 +}} +``` + + +5. Downstream protocol +----------------------- + +### 5.1. Sequence diagram ### + + +---------+ +---------+ + | Gateway | | Server | + +---------+ +---------+ + | -----------------------------------\ | + |-| Every N seconds (keepalive time) | | + | ------------------------------------ | + | | + | PULL_DATA (token Y, MAC@) | + |------------------------------------------------------------->| + | | + | PULL_ACK (token Y) | + |<-------------------------------------------------------------| + | | + + +---------+ +---------+ + | Gateway | | Server | + +---------+ +---------+ + | ------------------------------------------------------\ | + | | Anytime after first PULL_DATA for each packet to TX |-| + | ------------------------------------------------------- | + | | + | PULL_RESP (token Z, JSON payload) | + |<-------------------------------------------------------------| + | | + | TX_ACK (token Z, JSON payload) | + |------------------------------------------------------------->| + +### 5.2. PULL_DATA packet ### + +That packet type is used by the gateway to poll data from the server. + +This data exchange is initialized by the gateway because it might be +impossible for the server to send packets to the gateway if the gateway is +behind a NAT. + +When the gateway initialize the exchange, the network route towards the +server will open and will allow for packets to flow both directions. +The gateway must periodically send PULL_DATA packets to be sure the network +route stays open for the server to be used at any time. + + Bytes | Function +:------:|--------------------------------------------------------------------- + 0 | protocol version = 2 + 1-2 | random token + 3 | PULL_DATA identifier 0x02 + 4-11 | Gateway unique identifier (MAC address) + +### 5.3. PULL_ACK packet ### + +That packet type is used by the server to confirm that the network route is +open and that the server can send PULL_RESP packets at any time. + + Bytes | Function +:------:|--------------------------------------------------------------------- + 0 | protocol version = 2 + 1-2 | same token as the PULL_DATA packet to acknowledge + 3 | PULL_ACK identifier 0x04 + +### 5.4. PULL_RESP packet ### + +That packet type is used by the server to send RF packets and associated +metadata that will have to be emitted by the gateway. + + Bytes | Function +:------:|--------------------------------------------------------------------- + 0 | protocol version = 2 + 1-2 | random token + 3 | PULL_RESP identifier 0x03 + 4-end | JSON object, starting with {, ending with }, see section 6 + +### 5.5. TX_ACK packet ### + +That packet type is used by the gateway to send a feedback to the server +to inform if a downlink request has been accepted or rejected by the gateway. +The datagram may optionnaly contain a JSON string to give more details on +acknoledge. If no JSON is present (empty string), this means than no error +occured. + + Bytes | Function +:------:|--------------------------------------------------------------------- + 0 | protocol version = 2 + 1-2 | same token as the PULL_RESP packet to acknowledge + 3 | TX_ACK identifier 0x05 + 4-end | [optional] JSON object, starting with {, ending with }, see section 6 + +6. Downstream JSON data structure +---------------------------------- + +The root object of PULL_RESP packet must contain an object named "txpk": + +``` json +{ + "txpk": {...} +} +``` + +That object contain a RF packet to be emitted and associated metadata with the following fields: + + Name | Type | Function +:----:|:------:|-------------------------------------------------------------- + imme | bool | Send packet immediately (will ignore tmst & time) + tmst | number | Send packet on a certain timestamp value (will ignore time) + time | string | Send packet at a certain time (GPS synchronization required) + freq | number | TX central frequency in MHz (unsigned float, Hz precision) + rfch | number | Concentrator "RF chain" used for TX (unsigned integer) + powe | number | TX output power in dBm (unsigned integer, dBm precision) + modu | string | Modulation identifier "LORA" or "FSK" + datr | string | LoRa datarate identifier (eg. SF12BW500) + datr | number | FSK datarate (unsigned, in bits per second) + codr | string | LoRa ECC coding rate identifier + fdev | number | FSK frequency deviation (unsigned integer, in Hz) + ipol | bool | Lora modulation polarization inversion + prea | number | RF preamble size (unsigned integer) + size | number | RF packet payload size in bytes (unsigned integer) + data | string | Base64 encoded RF packet payload, padding optional + ncrc | bool | If true, disable the CRC of the physical layer (optional) + +Most fields are optional. +If a field is omitted, default parameters will be used. + +Examples (white-spaces, indentation and newlines added for readability): + +``` json +{"txpk":{ + "imme":true, + "freq":864.123456, + "rfch":0, + "powe":14, + "modu":"LORA", + "datr":"SF11BW125", + "codr":"4/6", + "ipol":false, + "size":32, + "data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v" +}} +``` + +``` json +{"txpk":{ + "imme":true, + "freq":861.3, + "rfch":0, + "powe":12, + "modu":"FSK", + "datr":50000, + "fdev":3000, + "size":32, + "data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v" +}} +``` + +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. +* Added parameters for FSK bitrate and frequency deviation for downstream. + +### v1.1 ### + +* Added syntax for status report JSON object on upstream. + +### v1.0 ### + +* Initial version. diff --git a/PCAP Output/local_packet_forwarder/VERSION b/PCAP Output/local_packet_forwarder/VERSION new file mode 100644 index 0000000..4a36342 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/VERSION @@ -0,0 +1 @@ +3.0.0 diff --git a/PCAP Output/local_packet_forwarder/lora_pkt_fwd/Makefile b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/Makefile new file mode 100644 index 0000000..7c54c60 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/Makefile @@ -0,0 +1,71 @@ +### Application-specific constants + +APP_NAME := lora_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 + +ifeq ($(CFG_SPI),native) + LIBS := -lloragw -lrt -lpthread -lm +else ifeq ($(CFG_SPI),ftdi) + LIBS := -lloragw -lrt -lpthread -lm -lmpsse +endif + +### 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) -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 $(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/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.basic b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.basic new file mode 100644 index 0000000..e60f583 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.basic @@ -0,0 +1,216 @@ +{ + "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_freq_min": 863000000, + "tx_freq_max": 870000000 + }, + "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 + } +} + diff --git a/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.beacon b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.beacon new file mode 100644 index 0000000..81bdf68 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.beacon @@ -0,0 +1,231 @@ +{ + "SX1301_conf": { + "lorawan_public": true, +<<<<<<< HEAD:beacon_pkt_fwd/global_conf.json + "clksrc": 0, /* radio_0 provides clock to concentrator */ +======= + "clksrc": 1, /* radio_1 provides clock to concentrator */ + "antenna_gain": 0, /* antenna gain, in dBi */ +>>>>>>> f31ee2e4eb6bcbb0c93ef685716842e151c8bd9f:lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.beacon + "radio_0": { + "enable": true, + "type": "SX1257", + "freq": 868200000, + "rssi_offset": -166.0, + "tx_enable": true, + "tx_freq_min": 863000000, + "tx_freq_max": 870000000 + }, + "radio_1": { + "enable": true, + "type": "SX1257", + "freq": 869200000, + "rssi_offset": -166.0, + "tx_enable": false + }, + "chan_multiSF_0": { + /* Lora MAC channel, 125kHz, all SF, 868.1 MHz */ + "enable": true, + "radio": 0, + "if": -100000 + }, + "chan_multiSF_1": { + /* Lora MAC channel, 125kHz, all SF, 868.3 MHz */ + "enable": true, + "radio": 0, + "if": 100000 + }, + "chan_multiSF_2": { + /* Lora MAC channel, 125kHz, all SF, 868.5 MHz */ + "enable": true, + "radio": 0, + "if": 300000 + }, + "chan_multiSF_3": { + /* Lora MAC channel, 125kHz, all SF, 868.85 MHz */ + "enable": true, + "radio": 1, + "if": -350000 + }, + "chan_multiSF_4": { + /* Lora MAC channel, 125kHz, all SF, 869.05 MHz */ + "enable": true, + "radio": 1, + "if": -150000 + }, + "chan_multiSF_5": { + /* Lora MAC channel, 125kHz, all SF, 869.525 MHz */ + "enable": true, + "radio": 1, + "if": 325000 + }, + "chan_multiSF_6": { + "enable": false, + "radio": 0, + "if": 0 + }, + "chan_multiSF_7": { + "enable": false, + "radio": 0, + "if": 0 + }, + "chan_Lora_std": { + /* Lora MAC channel, 250kHz, SF7, 868.3 MHz */ + "enable": true, + "radio": 0, + "if": 100000, + "bandwidth": 250000, + "spread_factor": 7 + }, + "chan_FSK": { + /* FSK 50kbps channel, 868.3 MHz */ + "enable": true, + "radio": 0, + "if": 100000, + "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": 10, + "rf_power": 20, + "dig_gain": 0 + }, + "tx_lut_12": { + /* TX gain table, index 12 */ + "pa_gain": 3, + "mix_gain": 11, + "rf_power": 23, + "dig_gain": 0 + }, + "tx_lut_13": { + /* TX gain table, index 13 */ + "pa_gain": 3, + "mix_gain": 12, + "rf_power": 24, + "dig_gain": 0 + }, + "tx_lut_14": { + /* TX gain table, index 14 */ + "pa_gain": 3, + "mix_gain": 13, + "rf_power": 25, + "dig_gain": 0 + }, + "tx_lut_15": { + /* TX gain table, index 15 */ + "pa_gain": 3, + "mix_gain": 15, + "rf_power": 26, + "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, +<<<<<<< HEAD:beacon_pkt_fwd/global_conf.json + "forward_crc_disabled": false +======= + "forward_crc_disabled": false, + /* GPS configuration */ + "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 +>>>>>>> f31ee2e4eb6bcbb0c93ef685716842e151c8bd9f:lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.beacon + } +} + diff --git a/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.gps b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.gps new file mode 100644 index 0000000..eb05591 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.gps @@ -0,0 +1,227 @@ +{ + "SX1301_conf": { + "lorawan_public": true, +<<<<<<< HEAD:gps_pkt_fwd/global_conf.json + "clksrc": 0, /* radio_0 provides clock to concentrator */ +======= + "clksrc": 1, /* radio_1 provides clock to concentrator */ + "antenna_gain": 0, /* antenna gain, in dBi */ +>>>>>>> f31ee2e4eb6bcbb0c93ef685716842e151c8bd9f:lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.gps + "radio_0": { + "enable": true, + "type": "SX1257", + "freq": 868200000, + "rssi_offset": -166.0, + "tx_enable": true, + "tx_freq_min": 863000000, + "tx_freq_max": 870000000 + }, + "radio_1": { + "enable": true, + "type": "SX1257", + "freq": 869200000, + "rssi_offset": -166.0, + "tx_enable": false + }, + "chan_multiSF_0": { + /* Lora MAC channel, 125kHz, all SF, 868.1 MHz */ + "enable": true, + "radio": 0, + "if": -100000 + }, + "chan_multiSF_1": { + /* Lora MAC channel, 125kHz, all SF, 868.3 MHz */ + "enable": true, + "radio": 0, + "if": 100000 + }, + "chan_multiSF_2": { + /* Lora MAC channel, 125kHz, all SF, 868.5 MHz */ + "enable": true, + "radio": 0, + "if": 300000 + }, + "chan_multiSF_3": { + /* Lora MAC channel, 125kHz, all SF, 868.85 MHz */ + "enable": true, + "radio": 1, + "if": -350000 + }, + "chan_multiSF_4": { + /* Lora MAC channel, 125kHz, all SF, 869.05 MHz */ + "enable": true, + "radio": 1, + "if": -150000 + }, + "chan_multiSF_5": { + /* Lora MAC channel, 125kHz, all SF, 869.525 MHz */ + "enable": true, + "radio": 1, + "if": 325000 + }, + "chan_multiSF_6": { + "enable": false, + "radio": 0, + "if": 0 + }, + "chan_multiSF_7": { + "enable": false, + "radio": 0, + "if": 0 + }, + "chan_Lora_std": { + /* Lora MAC channel, 250kHz, SF7, 868.3 MHz */ + "enable": true, + "radio": 0, + "if": 100000, + "bandwidth": 250000, + "spread_factor": 7 + }, + "chan_FSK": { + /* FSK 50kbps channel, 868.3 MHz */ + "enable": true, + "radio": 0, + "if": 100000, + "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": 10, + "rf_power": 20, + "dig_gain": 0 + }, + "tx_lut_12": { + /* TX gain table, index 12 */ + "pa_gain": 3, + "mix_gain": 11, + "rf_power": 23, + "dig_gain": 0 + }, + "tx_lut_13": { + /* TX gain table, index 13 */ + "pa_gain": 3, + "mix_gain": 12, + "rf_power": 24, + "dig_gain": 0 + }, + "tx_lut_14": { + /* TX gain table, index 14 */ + "pa_gain": 3, + "mix_gain": 13, + "rf_power": 25, + "dig_gain": 0 + }, + "tx_lut_15": { + /* TX gain table, index 15 */ + "pa_gain": 3, + "mix_gain": 15, + "rf_power": 26, + "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, +<<<<<<< HEAD:gps_pkt_fwd/global_conf.json + "forward_crc_disabled": false +======= + "forward_crc_disabled": false, + /* GPS configuration */ + "gps_tty_path": "/dev/ttyAMA0", + /* GPS reference coordinates */ + "ref_latitude": 0.0, + "ref_longitude": 0.0, + "ref_altitude": 0 +>>>>>>> f31ee2e4eb6bcbb0c93ef685716842e151c8bd9f:lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.gps + } +} diff --git a/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.basic b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.basic new file mode 100644 index 0000000..a975861 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.basic @@ -0,0 +1,225 @@ +{ + "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_freq_min": 863000000, + "tx_freq_max": 870000000 + }, + "radio_1": { + "enable": true, + "type": "SX1257", + "freq": 868500000, + "rssi_offset": -165.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": 3 + }, + "tx_lut_1": { + /* TX gain table, index 1 */ + "pa_gain": 0, + "mix_gain": 10, + "rf_power": -3, + "dig_gain": 3 + }, + "tx_lut_2": { + /* TX gain table, index 2 */ + "pa_gain": 0, + "mix_gain": 10, + "rf_power": 0, + "dig_gain": 1 + }, + "tx_lut_3": { + /* TX gain table, index 3 */ + "pa_gain": 0, + "mix_gain": 14, + "rf_power": 3, + "dig_gain": 2 + }, + "tx_lut_4": { + /* TX gain table, index 4 */ + "pa_gain": 1, + "mix_gain": 10, + "rf_power": 6, + "dig_gain": 3 + }, + "tx_lut_5": { + /* TX gain table, index 5 */ + "pa_gain": 1, + "mix_gain": 12, + "rf_power": 10, + "dig_gain": 2 + }, + "tx_lut_6": { + /* TX gain table, index 6 */ + "pa_gain": 1, + "mix_gain": 12, + "rf_power": 11, + "dig_gain": 1 + }, + "tx_lut_7": { + /* TX gain table, index 7 */ + "pa_gain": 1, + "mix_gain": 12, + "rf_power": 12, + "dig_gain": 0 + }, + "tx_lut_8": { + /* TX gain table, index 8 */ + "pa_gain": 1, + "mix_gain": 14, + "rf_power": 13, + "dig_gain": 2 + }, + "tx_lut_9": { + /* TX gain table, index 9 */ + "pa_gain": 1, + "mix_gain": 13, + "rf_power": 14, + "dig_gain": 0 + }, + "tx_lut_10": { + /* TX gain table, index 10 */ + "pa_gain": 2, + "mix_gain": 9, + "rf_power": 16, + "dig_gain": 2 + }, + "tx_lut_11": { + /* TX gain table, index 11 */ + "pa_gain": 2, + "mix_gain": 11, + "rf_power": 20, + "dig_gain": 1 + }, + "tx_lut_12": { + /* TX gain table, index 12 */ + "pa_gain": 2, + "mix_gain": 13, + "rf_power": 23, + "dig_gain": 1 + }, + "tx_lut_13": { + /* TX gain table, index 13 */ + "pa_gain": 2, + "mix_gain": 15, + "rf_power": 25, + "dig_gain": 2 + }, + "tx_lut_14": { + /* TX gain table, index 14 */ + "pa_gain": 3, + "mix_gain": 10, + "rf_power": 26, + "dig_gain": 2 + }, + "tx_lut_15": { + /* TX gain table, index 15 */ + "pa_gain": 3, + "mix_gain": 10, + "rf_power": 27, + "dig_gain": 1 + } + }, + + "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 + } +} + diff --git a/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.beacon b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.beacon new file mode 100644 index 0000000..839675f --- /dev/null +++ b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.beacon @@ -0,0 +1,234 @@ +{ + "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_freq_min": 863000000, + "tx_freq_max": 870000000 + }, + "radio_1": { + "enable": true, + "type": "SX1257", + "freq": 868500000, + "rssi_offset": -165.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": 3 + }, + "tx_lut_1": { + /* TX gain table, index 1 */ + "pa_gain": 0, + "mix_gain": 10, + "rf_power": -3, + "dig_gain": 3 + }, + "tx_lut_2": { + /* TX gain table, index 2 */ + "pa_gain": 0, + "mix_gain": 10, + "rf_power": 0, + "dig_gain": 1 + }, + "tx_lut_3": { + /* TX gain table, index 3 */ + "pa_gain": 0, + "mix_gain": 14, + "rf_power": 3, + "dig_gain": 2 + }, + "tx_lut_4": { + /* TX gain table, index 4 */ + "pa_gain": 1, + "mix_gain": 10, + "rf_power": 6, + "dig_gain": 3 + }, + "tx_lut_5": { + /* TX gain table, index 5 */ + "pa_gain": 1, + "mix_gain": 12, + "rf_power": 10, + "dig_gain": 2 + }, + "tx_lut_6": { + /* TX gain table, index 6 */ + "pa_gain": 1, + "mix_gain": 12, + "rf_power": 11, + "dig_gain": 1 + }, + "tx_lut_7": { + /* TX gain table, index 7 */ + "pa_gain": 1, + "mix_gain": 12, + "rf_power": 12, + "dig_gain": 0 + }, + "tx_lut_8": { + /* TX gain table, index 8 */ + "pa_gain": 1, + "mix_gain": 14, + "rf_power": 13, + "dig_gain": 2 + }, + "tx_lut_9": { + /* TX gain table, index 9 */ + "pa_gain": 1, + "mix_gain": 13, + "rf_power": 14, + "dig_gain": 0 + }, + "tx_lut_10": { + /* TX gain table, index 10 */ + "pa_gain": 2, + "mix_gain": 9, + "rf_power": 16, + "dig_gain": 2 + }, + "tx_lut_11": { + /* TX gain table, index 11 */ + "pa_gain": 2, + "mix_gain": 11, + "rf_power": 20, + "dig_gain": 1 + }, + "tx_lut_12": { + /* TX gain table, index 12 */ + "pa_gain": 2, + "mix_gain": 13, + "rf_power": 23, + "dig_gain": 1 + }, + "tx_lut_13": { + /* TX gain table, index 13 */ + "pa_gain": 2, + "mix_gain": 15, + "rf_power": 25, + "dig_gain": 2 + }, + "tx_lut_14": { + /* TX gain table, index 14 */ + "pa_gain": 3, + "mix_gain": 10, + "rf_power": 26, + "dig_gain": 2 + }, + "tx_lut_15": { + /* TX gain table, index 15 */ + "pa_gain": 3, + "mix_gain": 10, + "rf_power": 27, + "dig_gain": 1 + } + }, + + "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", + /* 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/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.gps b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.gps new file mode 100644 index 0000000..5759619 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.gps @@ -0,0 +1,231 @@ +{ + "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_freq_min": 863000000, + "tx_freq_max": 870000000 + }, + "radio_1": { + "enable": true, + "type": "SX1257", + "freq": 868500000, + "rssi_offset": -165.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": 3 + }, + "tx_lut_1": { + /* TX gain table, index 1 */ + "pa_gain": 0, + "mix_gain": 10, + "rf_power": -3, + "dig_gain": 3 + }, + "tx_lut_2": { + /* TX gain table, index 2 */ + "pa_gain": 0, + "mix_gain": 10, + "rf_power": 0, + "dig_gain": 1 + }, + "tx_lut_3": { + /* TX gain table, index 3 */ + "pa_gain": 0, + "mix_gain": 14, + "rf_power": 3, + "dig_gain": 2 + }, + "tx_lut_4": { + /* TX gain table, index 4 */ + "pa_gain": 1, + "mix_gain": 10, + "rf_power": 6, + "dig_gain": 3 + }, + "tx_lut_5": { + /* TX gain table, index 5 */ + "pa_gain": 1, + "mix_gain": 12, + "rf_power": 10, + "dig_gain": 2 + }, + "tx_lut_6": { + /* TX gain table, index 6 */ + "pa_gain": 1, + "mix_gain": 12, + "rf_power": 11, + "dig_gain": 1 + }, + "tx_lut_7": { + /* TX gain table, index 7 */ + "pa_gain": 1, + "mix_gain": 12, + "rf_power": 12, + "dig_gain": 0 + }, + "tx_lut_8": { + /* TX gain table, index 8 */ + "pa_gain": 1, + "mix_gain": 14, + "rf_power": 13, + "dig_gain": 2 + }, + "tx_lut_9": { + /* TX gain table, index 9 */ + "pa_gain": 1, + "mix_gain": 13, + "rf_power": 14, + "dig_gain": 0 + }, + "tx_lut_10": { + /* TX gain table, index 10 */ + "pa_gain": 2, + "mix_gain": 9, + "rf_power": 16, + "dig_gain": 2 + }, + "tx_lut_11": { + /* TX gain table, index 11 */ + "pa_gain": 2, + "mix_gain": 11, + "rf_power": 20, + "dig_gain": 1 + }, + "tx_lut_12": { + /* TX gain table, index 12 */ + "pa_gain": 2, + "mix_gain": 13, + "rf_power": 23, + "dig_gain": 1 + }, + "tx_lut_13": { + /* TX gain table, index 13 */ + "pa_gain": 2, + "mix_gain": 15, + "rf_power": 25, + "dig_gain": 2 + }, + "tx_lut_14": { + /* TX gain table, index 14 */ + "pa_gain": 3, + "mix_gain": 10, + "rf_power": 26, + "dig_gain": 2 + }, + "tx_lut_15": { + /* TX gain table, index 15 */ + "pa_gain": 3, + "mix_gain": 10, + "rf_power": 27, + "dig_gain": 1 + } + }, + + "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", + /* GPS reference coordinates */ + "ref_latitude": 0.0, + "ref_longitude": 0.0, + "ref_altitude": 0 + } +} + diff --git a/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.US902.basic b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.US902.basic new file mode 100644 index 0000000..3914956 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.US902.basic @@ -0,0 +1,104 @@ +{ + "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_freq_min": 902000000, + "tx_freq_max": 928000000 + }, + "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, 903.0 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 + } +} + diff --git a/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.US902.gps b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.US902.gps new file mode 100644 index 0000000..ddd0928 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/cfg/global_conf.json.US902.gps @@ -0,0 +1,110 @@ +{ + "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_freq_min": 902000000, + "tx_freq_max": 928000000 + }, + "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, 903.0 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", + /* GPS reference coordinates */ + "ref_latitude": 0.0, + "ref_longitude": 0.0, + "ref_altitude": 0 + } +} + diff --git a/PCAP Output/local_packet_forwarder/lora_pkt_fwd/global_conf.json b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/global_conf.json new file mode 100644 index 0000000..a40b76a --- /dev/null +++ b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/global_conf.json @@ -0,0 +1,237 @@ +{ + "SX1301_conf": { + "lorawan_public": true, + "clksrc": 0, /* radio_1 provides clock to concentrator */ + "antenna_gain": 0, /* antenna gain, in dBi */ + "radio_0": { + "enable": true, + "type": "SX1257", + "freq": 917200000, + "rssi_offset": -166.0, + "tx_enable": true, + "tx_freq_min": 915000000, + "tx_freq_max": 928000000 + }, + "radio_1": { + "enable": true, + "type": "SX1257", + "freq": 917900000, + "rssi_offset": -166.0, + "tx_enable": false + }, + "chan_multiSF_0": { + /* Lora MAC channel, 125kHz, all SF, 916.8 MHz */ + "enable": true, + "radio": 0, + "if": -400000 + }, + "chan_multiSF_1": { + /* Lora MAC channel, 125kHz, all SF, 917.0 MHz */ + "enable": true, + "radio": 0, + "if": -200000 + }, + "chan_multiSF_2": { + /* Lora MAC channel, 125kHz, all SF, 917.2 MHz */ + "enable": true, + "radio": 0, + "if": 0 + }, + "chan_multiSF_3": { + /* Lora MAC channel, 125kHz, all SF, 917.4 MHz */ + "enable": true, + "radio": 0, + "if": 200000 + }, + "chan_multiSF_4": { + /* Lora MAC channel, 125kHz, all SF, 917.6 MHz */ + "enable": true, + "radio": 1, + "if": -300000 + }, + "chan_multiSF_5": { + /* Lora MAC channel, 125kHz, all SF, 917.8 MHz */ + "enable": true, + "radio": 1, + "if": -100000 + }, + "chan_multiSF_6": { + /* Lora MAC channel, 125kHz, all SF, 918.0 MHz */ + "enable": true, + "radio": 1, + "if": 100000 + }, + "chan_multiSF_7": { + /* Lora MAC channel, 125kHz, all SF, 918.2 MHz */ + "enable": true, + "radio": 1, + "if": 300000 + }, + "chan_Lora_std": { + /* Lora MAC channel, 500kHz, SF8, 917.5 MHz */ + "enable": true, + "radio": 0, + "if": 300000, + "bandwidth": 500000, + "spread_factor": 8 + }, + "chan_FSK": { + /* Disabled */ + "enable": false, + "radio": 0, + "if": 300000, + "bandwidth": 250000, + "datarate": 100000, + "freq_deviation" : 25000 + }, + "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": { + /* change with default server address/ports, or overwrite in local_conf.json */ + "gateway_ID": "b827ebFFFE9e119b", + /* Devices */ + "gps": false, + "beacon": false, + "monitor": false, + "upstream": false, + "downstream": false, + "ghoststream": false, + "radiostream": false, + "statusstream": false, + /* node server */ + "server_address": "router.au.thethings.network", + "serv_port_up": 1700, + "serv_port_down": 1700, + /* node servers for poly packet server (max 4) */ + "servers": + [ { "server_address": "localhost", + "serv_port_up": 1700, + "serv_port_down": 1700, + "serv_enabled": true }], + /* adjust the following parameters for your network */ + "keepalive_interval": 12, + "stat_interval": 20, + "push_timeout_ms": 120, + "synch_word": 52 , + /* forward only valid packets */ + "forward_crc_valid": true, + "forward_crc_error": true, + "forward_crc_disabled": true, + /* Email of gateway operator, max 40 chars*/ + "contact_email": "operator@gateway.tst", + /* Public description of this device, max 64 chars */ + "description": "Update me" + } +} \ No newline at end of file diff --git a/PCAP Output/local_packet_forwarder/lora_pkt_fwd/inc/base64.h b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/inc/base64.h new file mode 100644 index 0000000..e57eb47 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/inc/base64.h @@ -0,0 +1,62 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (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/PCAP Output/local_packet_forwarder/lora_pkt_fwd/inc/jitqueue.h b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/inc/jitqueue.h new file mode 100644 index 0000000..b674f75 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/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/PCAP Output/local_packet_forwarder/lora_pkt_fwd/inc/parson.h b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/inc/parson.h new file mode 100644 index 0000000..fd29e9b --- /dev/null +++ b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/inc/parson.h @@ -0,0 +1,111 @@ +/* + 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/PCAP Output/local_packet_forwarder/lora_pkt_fwd/inc/timersync.h b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/inc/timersync.h new file mode 100644 index 0000000..497e538 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/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/PCAP Output/local_packet_forwarder/lora_pkt_fwd/inc/trace.h b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/inc/trace.h new file mode 100644 index 0000000..a067851 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/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/PCAP Output/local_packet_forwarder/lora_pkt_fwd/local_conf.json b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/local_conf.json new file mode 100644 index 0000000..bc8d0dd --- /dev/null +++ b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/local_conf.json @@ -0,0 +1,7 @@ +{ +/* 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": "b827ebFFFE9e119b" + } +} diff --git a/PCAP Output/local_packet_forwarder/lora_pkt_fwd/lora_pkt_fwd b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/lora_pkt_fwd new file mode 100644 index 0000000000000000000000000000000000000000..6d1b81083f15e0b76b26f25330031cac46bea3ea GIT binary patch literal 144628 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 literal 0 HcmV?d00001 diff --git a/PCAP Output/local_packet_forwarder/lora_pkt_fwd/obj/jitqueue.o b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/obj/jitqueue.o new file mode 100644 index 0000000000000000000000000000000000000000..8981e0e02f9c07dae4071284167769c797db49de GIT binary patch literal 7288 zcmbtZZ){Z8l|OI(*v58~0o>qHC)|{AYRGsPCoT|gRvEAf4yJf;OrQ;^8P5!17JFtg zGY&@3-2pod1-x-=Vs#00-b#ExRi(00KA;s@IV~Zmi?&E>A=E^xH}mqggoY7W~xJ^an4tPFnnEhr{0Rlsf;W^m+@u!%FgN#m3eL>; zMX;k-*PSe!+o%>?kf(C*-r|YvapFtH+4?+J`52E}Ydz%+mbHq!!E*Abx~)6aJX)lH zsyDUd3}1_CIAHgG&CM-$VT>pE?k(uu=JiJ#iCkKZqZ5G3$=kU1AbXv zcYUnrx_kCx(3o!w>*R{HfxOYR##+mk^IWd!v&QUiP8OBvlbX{L)M_zLCDCc}fWPKw zk*Hl$#8HiHAyP*aQYYDWK%J4OMp1c5 zr(e7Q|H0o;@RdDk!I6?a&&?PisY|p=hhLokF*jEod^B&+g(<@om@)#8PyEH>8vBaG zjKS-!C8};ek-yXM_lVy(e3{equB56zOmv&H%ZB&8GX~a1?04%vIrr#FvxDez)Vai#?@w4r5Q%_+j(Ql_V z`7}k_2!Ee3YCP@Q`0~oq%ZDzOt{!@)xoW9#_ zosnVa4U4=n8o;{rra@xNI1JtZcs$nB#Xdwz)o0jWff=I%Hg<&aMkjQLeqw~6@51_o z^*b>Sk5iz>GB0#&*%8FY2VIt5lD>zV%~tIoI)`3|LNtf(9rQoK=LFvan&V)emkT{b zFJjRFzY*ea-IX_-h>1|%Q05Kx3=a{A9m`KuQh6YIIm9h5%6lR z3FFZ9!1g+PJcjl5K~@!f+o<>AtOPocKhGC}7^gtyCOSUeR#}?epTEPodPU~yGYb8h z0ynVd=k+TX?-6H>I<5=(qeVyV1HA$4qJz}}si?KKh`tQ+CIr9degLjtZ zjq@w>#=l^1c06~1r-H}(s}8DMGYVeh0M{AL(UTayQ@1n!9hvUOG{QLbuUdRZ#yPK)rt(Ypj2C(%7|R(r`?3JdYusy9>RbQQuFy#6}t8 zo;QUcMs0$j`s8>lLT=`htbGgft*cwr`EEHC5!wHGc#f+fg5fTSAZk9pRTxhy~`x?MyPrP#yhYE^B1l^jv6$Iy7aGQXQUUr!{D*L z6YDJ;gHlBHJ&_yrZ{U04^S?%oaua1ypRR52Q}BC&4x?Uu1iJPR(M1&Tm(V|h&j)@l z=$+^{AR+jEiJ%`sAGMv3gl0R3f7`(4=9=EB)gvc*k4_j3$cd`lNzg##)$zHx(^bI} z#zTnPY);iZp0~l@uE!9c)rgxw{|D#?fjvME@CD#FtuXg!pRI3Wd`_$|P6k$(@%*`G z1#)}B;CQ;>hl}8!ft>w`ey!4=kffVD#+K@{pz~mE1M2msr0p@S*W-)P~iE@^@U&oyB`)yv<#zJk(S&FbC~- zm#*x;RC*S3R2`~7t+-V3v@I)5IGq~ToC~#6bPw^JRyN*ud4QKiv?>znP5$k<<6Fldh zdvD9DQitytj9Gq3(l`(1nDdhETA}Kdu>VcB+d9K3yj4=8TBSU5{O)uGRJ@N-uf)cp zTm0(Zy)BbQS0C=Bz>f^_%;20que$};vl;hWcuOjQcJr-S{+?txq3*r)C2z?F+g4$n zSNpufuh#zH4{p6UV}BLT_NCJCM7Hmhq1aH&q+^Ny-QU#H)7{f1;)z$o1M#Rxh121| zST>gCd3J8-+4kJF&26H&xmj$Cg(Jy?h^CXNR4gi*B17qP4AW=hgR#GC9P(jkWPfa) zUd4l|7t9edRsQTbGU><~?8h|TA0cYYv^7TC8Z%9++Qf6)p6zZEFGBp1Fcm~RBVvQ8 z?7`|kp@-dM!|_B$H1f9D_DtKKGp65k$igwAStOFt*o%$Pm)b-mIRp<4W#Aw1vj`F(o$zN#=_DCih z9!z28*kDE+NGB67`^+T=ha&rMI7B%5YB&*r=`!vA*ph!_%_iG4ac_r_I!_bU?9xE;!xlU(wbB%7(w zgXs3!B*ybEO}olymLDslS$?vNX8BAR&2s*m&}!EFV%}(Les3AgP!!h5DHqGO6*JB8zyg*`ud*Tv8}!Lx$bR!eg5_9o?5q%)4CR% zP|v2`-tXP7x^^uM4)^h^$DEP)whO1NcaO>Gi)Z@yW~5YhA73ebgSZTb`v#Jc{R>z_ z36nWL7mu{+R2eG~&(a_+i&)ltT4iu$%9UR+`)QLsU)%e#xM56IDi*_nL)i=ukE9M- z%(9o-Uh~yEEKivBI!kJ7AEA0)(v618fip0=ivt*Su>u&@8xOf`=Z+OhA zM-IenAJgC;mVB;5%>RkxpJ!Y}bRGCG#&I2DImfC2Ki~ zjG%iNp*PNm{hw#V_)wK4-^B=djuC#DW`sYkO8!m87NRQDTb}P}Alum~ajV2$iMu84 zkr7i1&lyG( z>T8Vah;HG0vpyGb-hVA4^con!_euU%M)+Y|;(5kZ$S20t_$`4E)$4y4q z`H`f_pE)>~4fg}HX$$w+y>x8Ak29VnKR-Rm-=E2lKb^D)3wczFzm@!`7lZh7hWvQZ z#QdAL?P$h}5T(Jtq365)a5kHc_oH59u(7Y!RzM9$g44uod}nB|*=`88*}jQ?;c^O^ z6z&bg>@Lm-+-Z)-R?V9Vt~6<+z1ix&COZ;%Ep9lfrhd6zy(zK&;rY11_2zt7%4|Ge zlk79tdTck(%_+|<2));AA!zQhe6vKJm)rY`iI6Vyjdl17NwfvC&?|4O!)*^f>>?9(?R$u;<&Quj9nKt}PGJz1H4iY5NC=Za-r~xM!HKl1CAYjxeBScLV-y~!vBr+spGLi77 zZPX|c6B|ofS%qfaF8eOEeBHEVx73Z@v{Fr5*v(ej@>=XpCOmCwAx-z=?MwU7@?FRx{66FQjo7*tzD&j zshWQ@P17>20j6mF*)Bbrn&s_FbLnrV-FV@)k#ujLfv{0cf7>9N~UQ^}aK!zuI@^o`3B-ll%2P!(H7GFNt=JSScV|DP{h_{}seIzpT-sC#-1D|~R~TjQQ(p79%zHbc{1EA}5zaT&r(oZcQC zsX(~)`b%eY=)<(J3l~1j{88_j;vSR#vs&e-tG)B{nJ*Q8>3?6i@LD@)rcFoL$)CCD z#c^%>>t`5t-}dR_8QZ6Ueo4tX23h1u-~Q4#<0~NZ8I;Y6|32a~UV7_vf!1f7Tl3Z! zct2E?oA_tqZqpkUto9|M;F&;ux>29rPxcvFHOiyy9@{_sK)k@$9iOgsj9K-gdHwMm zZ@2O3R8wcS$E-KE9vztmzvlPHn-O>VsyV`fe4LToQdQ8=^<}=ewLwj#b z(K3dF_lWSZoE8uJ#-rdlXp@(Uyl(a$Gx9wj7<(i&Iyz0$K4g7PWSVv7@^!>LTE%Eu^$YQh z=p$|~+686TA*WsIj#Kw`Pj{TwKvrgDHZpdT%yvjQFoG{8HuN*5+)M*y_ zPSNXqM$vY-ufn}#ws(1Tcf7J6{dAE1)NEUA@CCySmkyT+=YhLay-^SL-R;!TLpx%& zBXvxHZe!5t=(axN=xcCq!o6YUTaA2SZ&hC#$Vm3JbfhVTEM5AlHRdhd{*oRaB0uuZ zxLhA;PmX=n*P!=I(Yrp@9lvTYI&zKnov~}QgJY@Mm&P)+F9N&9BHC|%sp=i&eec2r zRbIE~{vWV)GepMV_VeRC%jS-=zpkAW{wEdA_R60WZt}BVv`rDtepiifXPtCiZ`w5K zWVOS!umf!~%=&cl2d3cv4eHu2{5c8x6&>9_+#PqrmQQ@{tubf0Fs6)KpbzW$a$`}~ zOxr~py9;`idp|HZ_hH7J$G&nh-PAKxdmZ)Lt?Kt`)U{Q|o28Bq3txlec`*OdFpmB*G3%VyUj*H%Z}sH34>-x^l9i zK67reA9LQPJocHnsE@PLO!*VnUChI|A{{&@gopOu>t9M&otamG~8zir; zE65A|Hj&p)k=J@j^Pa7rus^bo==ul7&H6FpA5dni4{2IY+@(cl=WEA|-3Zrh`eln> z_2+Hq&tbDayA$E)zv$;y|D7mee}wJ5Z|-=iRxuJsea8?+|4H~yi2oJ%hqIFPpwhs; zPuEvs4qH2#^1HrQAH!U>|7uO!1J?z207kAI7=h#3s2Ay3$Mko>@6?guZ7TdvN z?uqWM_Z<5|4)zr35{5Oj3Y&|tbF)ppGzq&FVPg^oyP)_`)YlG57}lIB4Eu@KdL%4G z!l2Y^?GomaFb#criw8CaHmNt&tfO_XD|%usGTXZx?cInz;(@&l>D^f0b{pw>bcE@c ze!ZlvLRxFhFgSBy{MwA}Gq<9jB*J8Frkywoc~-d9uZocmI%p!CGG;->9J^eR!Ex@k zhlAGxUb|eY{%LC{7i-eddibA2I-iu~NAPp5X1RvoN4b=r`zfr0mH&J2r-=XG$nV)_ zgi_4<3PNsfqQ2Dpq~?S5%m-tPbyT`AY46#G>~j#~^KJ1gv*(ANarbv$gjsj+F8I4& zS!H4$)3mj-Cl+9SDtz`=YyOP2mnS-M%4O_bmIGG-w-;_dT*$3yJK*>k09FD| z06kcYR>PfwKNlE*>xA12*AItvqLzitQ5l>L=SBxFhYP^ z1n+Sy6e7S5xV>-(;SRx_hueX;14ugncM|!OA^$e$c?9k_Tvm#v<->*HI^c%jPQvLJ zOvP~RaC_iR!JUVTA`z|Ur(}QfkLFhNrJ*<(>EO>n9Mhg?KQ@wC(Pwy&hW25G7kk;R@0)vmdwP$b`;QEc z!P8);ZJD zHnvOp9gg(j+P*YYz0x62+h+t+ddz3@=8j*t?Hx5&R5;SGe5@0n{%~L!O@)2a)!mBkci6yZ;htHzVy2kT#68VMp4Cqz&8C+S+`*#8)7`Li~O^ zFV>bAimFaZ5bwEl-RaVq&#QX&UV<-2_*M&_$H9jouH>DEy*v-w#q~7%&sp>z)YV9T z;rCQOIyGzRy1EPLkJ{s)qt(WTl{~Cv(8l*~`@Yiiz$JKTOO*`=Z@KWYueXDr{e^o9 zOK#Z4`+)FH2d~K&9nHl)apJkR#*+IHbH1+E-Z8eqZae4KA?^Fdvr~ls0O2o93Qx{s z6I$OKxU~Gj?<@V@0^cc|F`7EWMn3>edvRRCtoa~33um;jTcw#WPWbqNm2h2fXbWvG z@B|z-_*xn0)4*&jX8iEy11sV92?6`z21#Q*=YcDSI}OY-{N1F!loWrfiV`w znHt7XiXV0oHi>rZDruKuSFcertJg@w{K@qo*A1yy3uIzl=n5Y*QmT((jobZeO$QD} zU9fHQe6XRJgU`ma7bmqg}rqd)ga3Sf3&9G^~vySQ`&uF{x#v-r$O^H*pXAr{FR{cOd@cNS{#% zouL!Xe!nx;AN$3~0?4{ei;m_&-dpiwBR6A>ba+9Z(Xan_q+jnI8AjV5EaaN~n`8an z_Y{8=efHZp=Vco9k@bbWMi;_d-sj@$3(Ygv#gfmhkc~5=55txW{k@SC?|TOGtwb8i z`1ZnMBe$adD61N>d?Kq_WGxX{1(5YK$jbk2AI@$E#`*_ODt$cQ|N7geKGc7)*VL;L zvd*H%`}4v4J46tcb!S$lM}o5)YqzA>ihKUU>r z8M)881$q7>_#)u5>W6%+Yt~1iF2J`BeE!SGqb$nY4L-EI){g$%19ukAgH1#~(1$TF z0Ne{V1a}f{49<;s-UUp zp0EETMVt2l?qGg_yO{sN9Z0eEfl<8Rb)%&4bpvzK=>Iq4m~rmdJjk^Ci z=Bst10i@H!Ujx4%b0FJ$57s1!J*zJ|l7o6Ek~o(Z8T~Hq1v(3VZscIERf7Co-bHb& ziMa=x?eeB#rM7PLb2tym@$OUk!2kY>;AeOd_`#v>elkkB= zeBJRm{^)2%JI3K|%nQ-zNE-AAdw=`ORM-F?_^7Ab`$GH=TaOuGoY$lbV!e-kc4}dt z@y8z=Gp1{w$JqRPwD;S_9E`0n@h4J9FdUV@8fP&zSFh%y@U(x5mGr zKQ`hEcV-2YFz;u`9~<$d2^d`kFzBbQ|ZJk4yxb$aZOhdg6FL()IW*QxG zcRa+k%DU0pK7q4N?{l&3xbrLUL}vqMVrs|;d%NSg`aIaD-DB18S5~8)an_kx z&HZaP?8NTGdFQXdKXcn-IQMwWxaWpIJRfbbar@zM4Sdr)&%}>oyfNSOO*_X^Hks|g zxq3G8y$QUUzXMaqy3zL_XBO})Ko_tYxL1?@jC;&9eP@jJQbV1kX*++#`7+)6J(TTP zRi8i5e{S@n?%0o6cm1fdub^!{i?KX}xP!L1gNXYw;&^V2S#abFKmF*~%q6ovi6M^x z{y86^y<^F8Vl`YCW4;&TI{?SeI~ech;jf;Jy$ak3xV>=g=%_t#!*FA84uNw7Um&z8dYN1HTEo;Tb_1@L%OazvuiK z`vqi}Y1l{7^)A&n*e`O>A2VY|4c~(KsvX%k_QBq8e1F6H81_>gF{a`8W`9b@SgZ#x z(`qulndVDKvjgFpw$MbsHF4af~Yin|I3V*mIpoIB~@DTLn#ydB7U zvoomYiWIZ1Dwmr1Psg}mz8n)~yL73#j4YgQd=7owTFK*MQp281G2! z_r0sp4y?yNpng!l>WpJ8{IvIKzVMmz1?LAfA5`1tgO2zg8Ya&N1&iiiG#`}EUcpAB z`&V82ahw&ErkeFZTX)Y5!8mQ5Hi&y+^q2bE=Nt8qf%=Jce)mUaAA=5Uk_MxS_l)nS zq2JuLxI6AbpGk%N^?LRhmW};1$}!|xfqHu;j&p+1exx0GXr+?n)-ktXy@@l)5zg%_ zXBp;SwgvrzlFuOK^8wf(H)N+&W3Kd}Uxj%;{9JqxHhus$iE=2f4e2dee)zR^t}V?u z%%!!B-itnviZ>AVYufv5I6F^4U0^RTZ#(?7ovYyA3cnBSjkuBJptpf875WpP%Tc}x z(0ACdBDgD2T9 zVN0LGxO!Walk4X(DaRq~4W{WkV=T`h@ApQsQ3um-XYPicso0} z!{y$|e7-q0ZICwj1-$2YPt}QK8#1xqal^jSu5yg{KRREnpPV*z-=8o1u>Lyqg=uE} zT79|=@_z|EcK}ZT+ktUl2as=fI)VQV>;fJI_PjIS=mhoxzXRL@>;Ucsz6p#@Gv)6G zKg+B3MT6#^#%fE>^M?n&H_GrJc?a{{IFPinb!M%d!eI@SVeoI@6bLA+^*t${fqkb-Xt6bP5 zZ#8T)Y%KRBT$8)KE90rKQ{1x^RCgNz?6a&r6>TlcI}LM4ChDjc^Tro&ZW6}(56p!l z#hTfNzBqVH@wqV;ta><`YSzox?Kn$bWS%D%74KGg7Zsc9>r}k!O4XhptH(SMhW;Vw z$9W?F+L|}4Ib(QfAKLctc%^r@F#~hQyW8;&5bc_ZvSKat2Fg3yzp&5v;r#A6`!)40 zft-BkXzBEor9a1BtTukZJ6pY*dy#ftt>M>=z5u`O+hHGB4jYKM8s%nwF7K+?G}!hp6y?R*QMVfEtX=OmcKERe)Mk%iof-E;>3?o4 z9p&L($iw=_KFHet#8S+9JMA*D{K=o6IgW955_BErPHp?{Gt;+!6y+Zne|DR>_gR${ z9T^1AAb4)R_Ly;@#oTGq^Kg}5r_DJ!A9r$~LvTItm&28U zcL;76E(YgC`dq~410!%9a3>+R6Rreq2zhtHRlxN^uY=I_EaaSn^Py6B&Y2F~bmSS} zIVUWs2D$w7z>l`mIv9?y9=PKOn*i;B8$e!cyJ@J`3fRW=-uZK?_4#x7mae3D_Z--- zYfFlsokJdL{CdzvUqF3xOs07|V>iGbLR#{0zmkeK3b2c#;Mv8s4*JQwo6&wnusdAO zq6y~R0=ih}PV9}^TxNQ_&-gGO;ZJ?CJD!5^t@T7l$9AtYdJ%uiBD~F*{ejBoN3cEn zCCv=jglm_8Pc!fGesqnA6Jgi|+~;kEeCjql%glpsb!!oJQfL?UkFaSYpF-Gagr%YH zlArrZoSmt(+?!-$Z1A4C1o!QpYbW!uADd~+yNDDQ=12!miO4FI@R>99amrf;c~*Xl zgeM?8*1h*xISZyvX_D8&I2W?(okxId2J{_125~1lYFs`U~Iv&qJE_!)lRll_4zr>DBkd*;YFcZ`IR4;X7vA zp^a;V&sBo8i@r0?^`q6c1BE|DKlEb`)iMst9?vc9o{hGyMi|SFet|R9XzFRvcL(;P zhi3h3q+=1*hOnt!Z{w`Q^GvK4;idVX#~irNDC69#KZ|wybFl-EQ=Z+0xGsd7`+D5H z8Dp?rsf91a4(hv&Lzrh%u=Xj{v}LKl6`Ho|z#!+u^`rmC+nw?;=y)>k=OaJ4=7SN? z+}EhL^P#Ny2H)E-FU}*kfiDgFf#b3N9Kp%b$T^JD4y-YHkcMjymJf9>%6`ncoQ5@l zHJ{8t{~yB~#Cc`VE&T;!2PRCtIUSa;Ho=Y}>KB_;g@;Etw%?0!8pA+^#h~v z4;Owjf<0Phnkmz&afI=W#jxkS5wC=&`=X=2fjub^dOGM|fku0b{$I3*f6nAF zcoX`%rVkrMp62*GoMXAX!??#;$2WcG+r!2k@LvOe>fo@dTejpHKR(Px|iV*2iJ+TWe(M!ZkA2_RODm3|uRxJI+}aLcjgkGqPQj z{p#_@{{{M6_c33G|A6Rgt%Dy&*ztGg8_`&I++WT6&Z*B}Qar1%UR)iY=3O10qo>rkaooMeGf=-8 z?4>8%jrzyF1n&uMMA}rZ59bcikqFZA-FzzUpQ7mZ_VqsJ&IeJhoEekL|83}BAYood z+zjk1EL-;Ra~FQ|HSpev`uK0WJF{%W?{S7n`+kbM8D=;BFA_+{9O+D zuR$K)u3$-)NIO^3-i3THPUoe1Xp0_FZ9sc<68RM(KOFDRTM8arXC!nXKWz%_)sK;1 zG2*e8Hf4BfFn2sO`g^n?({mkW*+|&OG}zjV{EW$U(+vGmAdmL;%aAvL`JqgBxF&M- znDfb-=;!MZ{!g%D)eadokij(s`&S*}X{+e3g`agpe>MEnoBk)@XP)#w2LC~ayaynU zXD&x@FY819X-6Ar9`kIa{uSKIJ=AAB|MH9Rz*D%p!hPY~uRR}k_dk#Q`cIVoz>;^K z=KG0pp*ky$CGfn@BE8Sk9gk?QCe}%4gZI%7)g0aq*(x8Od1j1t1LviA=|0#O^f~TD z_%=|DZ^X?+A3Lq}DOpyYIA6;+DPc!ollUEnVWCf;j5upo>2d$5_E%ap-qTB%hP658 z;r+tTGr5@(mx(#tjW+3$IL(**UJ1{}nJyx6-$6RQcgx2(vgR1(!@ZiR`y-=xJC{-x z!*>vU(bQ1c57OY~!2YD;3^*NUz;G|8Tl{Z89`_*D$ERc81eXODhQt5b0sK>+uW2r? z4`b1sD^Ncl=3-Hk1D6Gt372c?_@OSCBbY6S_Qlzas?!fM7vt^=?ohx;V6-@3H$=n{m_E&KrYGV4Y$b*jBDswTs#?^)R6q}_n; z3Q2oi1Km`+^(WiVrESNXN9>1?j&YsEU&L85_To5)>MS zH0KVcJA}6FH0%2}e8<7CE}N__kyS4|2f$M;{vX57Jej7)mZnG2@GWt%@XmFa`?);` z%XfqgA`D$f3!!4z&&q)LaC^|+18`$-$R;>CZ3$-uwLS9n)3j|Md0Qeqk$ zw&m*Me;a*R{JyJFwF~%I=f3P5dEPmFhF8K0 z1akzl1hZT5{Wb5;RG)1_8p<3TeAPJOebp%WkAM5+?RWp%FLkkh2Vpx(zPr;XF5Ic| zj^XWT%3yT#AjZ2-TOQvJd)ANn^$?uW6MA5+F>k+*{kLNDDfF4?TJNtJ$Nm^{?KBz? z=FxkNEcsqAbzdlb3#SRD9F(`sE%r|OGs{~qVN8Q%;JixcQz>EPf~A70er)_``+0nO z>y@wq!5qOX!GA#?$?EMh_G8~gy;(+<*Y9}|??lYAD&~(erqx0Rmh%}$IqOl*z!c@I znWCHrQ!g%Or-U(|mB^!QcAwEEVIe`jouz)epf9m>QJ>-StbkpLj#%F#uxx=J>HW9& z85-L4sJ2t}tD_$5U$Gy{gHFXF3u#AREPU4}_P(pW!XEbuW8RWH)U zU&I)9`LRCua-Wgb6CF7k?vAf)LjP#T_d3zWSsLD&LGE-N_qwUw%D$wbeR(d;Fl$a? zUM~OhSWi@pP9VQ_lBj`UPCCH--V?gnE{c-lw0*5@^K@TjzJ6nXYo>(tT zn--3aa7?gXuEt!&`fy_{k{<3hwCZjH>(#hbSBf8bvwj#pOu-idU)08TBkmtwTtIuVc60`0Q&u^6dMzIG(d+SU zs>C5{*41Xc&EMX425X2>y|B+%iTTcrHcv%c-H5junV3I&mYcSN;fzm_cC_egK;z6o zwM!;sRl|?(q#H$WUbsrQ3OGMpIb06pPJ5&8Oe*eQ+)=b4_K{*{1wQ4Y$GrgOrf}_W zd*Kekor2>Bcjuap1$P!2JRhzaE(~`7j-P(S9a@C5Tet}5BA||S!&%@CxKrRO15#BC(ERiZ(x3fN{+m39<@?$$ghk;F!NuURZo(NZTs>SD+)f;$hl17Qc?_zr0p z7z0j#{~XfCMCUTd*$eszT+goQ<8zeM zH(waPY1g~se5*0$Gta_KYij>{W6xvLZ@h0;|JComXxD#)H zqq|~WKkk*rdT}56Jp5>X&?Q57+Z%g6Run^@i0w0qihFTy{&K8%u-ot<&WAAu8!+1Q z!;JAV&_%@^MhJ1`jDx=&{!aMI;4g>kg!98yx?VK`vtBjUFL>1`g$`xlD@Ax$C*I%! zi{Z)#p$`zX7Ax1#|1{_W+U04FRltvT8b*rV9;=3&3LX7a--ml3oGIwf#wszN2H@7i zRl`*xp5^tSZg?JnGl3hQL_NG%_%q|aHzriQ?0n;0EFIR3z5_dzk8t{DU_6#?fAx$P zZ*i(oRu{_3{=XVw6AOMkLVL+^)&cq4PbA(6rOa3P%|uwW_{X07*7%R`rs_A3eQP|7 zuo<}9o$+{{dgsA+dUNpI0qcELJ<3w**=O`hTHe(K5Ed1h-~T;88879JXCocUc1Yqr zL>@ly4>BHm@3|5mLVC^%$Azy9b|2(3dh^N4gtvUeN)XZ;JRnjrBHsllhw+>m^T~2U7;~u0}aiwQEOqa}GkfY(MN6 z(v`yR#`mQuV?E{f^ZhC2i$yr=@j#lFwH&u&~*V?-sKtG04^Ibvid?Oq3IfwG@kbEU>Q|Ch%7yG}Cv4MUy z2>*dXj2HM1!(R=adiV$6-?Z!1GyTtCZTS%Rm!rQ!_XEI#aQ$$H;0ECi!<8@%@CY39 z(zM4%v2dJs->!-A``+w_n;8Es&I=f4wZ{ZI9_?0dSnK}v@Hp$g z2z>0jGx4tCI_bYz;OE;Z@{)&dUiqfT$u|dl6%M`~Q}A5}zDfsQbPB#tLf1nGXBiF) z4hU{S9QzadBhM4rpYr`?-{MSxK}VLXpOlA=#y4o#U68Bz2sw!y@=4(T88$VYmm;5 z&Nueh)*$geUC7wu;7x%ZA(uIJ>8}?*>xyIi$h7%saOy5H2EBVlw>^UD9Od2zSFc&7 zuWAhFO?CRyHKB$;O{AfzQIFI&2WtY_sWuQ8eQ`_`>_P_JtU z1v4~L1Xu!1L6fPrsWDR1&w~qMwA#?7mgY;#F{3Y&-mDKtc6osUT~g)+GLky3 zu0b6J^hlGAN~mkt*xDS_H#F5W!$5~NM5=1*HcDMJCOOO&VwuWUR3!M81RHBMgo2kY zzbQ`GE~CHXyQI0LrJ*WP)4VYlN$TsUQ;}(7y^LVAyCu7ELse~kO=Dv)^cVF_vR+2C zCAS6oMyl$XgV>y3svn5XN!BZoTZ;}BY1kaBYHi^f_Akk{v0p}dvOXehRe@lrW@}Yp zZ9TL23-TT8m(3zk=ZQp1|GGR(_RHsy(08i-jK*P|)ig$ejg2)`8(|tQQ-^l8%Vuhh zvq|iE0X?vy0R{$T4Yfw9@*0ql$v5TriL~j|MhCB+H!0nca6-{U*(Ue>yq3rF0#968 zLz=my9!ml>wNmngZc73U8!rmm+|YJWSaV%fxM@qU*(%FQF%`6l6_tAV!xj3LhDg1h z*Qz%(27+xtWf&8BPt_){HnqXVNOMgDW&|?sVKqPybZ)K*G&Gr2ce%P#Y-X(`(^y8x ztaFP+4>YvUHo@@O41;|@U_3Mb0f(5NOZ-OP^M zYR1ZMsc&ix1@sL;P8>+8qo~Sv*UePKD(4b3h-EwNVN|bbv)NZK4zI!SqD=>MFY>nr z8*8^hk;aWdy(Jv1ZK!L2b*HID9XHouhG9K(2C&-FR64IgH&qDe`D(_>Yq=Q{23jcC zY!jTQ0ioZ$_1R{dQ{?k9EW4|xp*`n{{6 zq+YhIKu15&H79CcB#yg^+=AYmI$WrzgUl$fEojs>8*N1 z60@9^j6@AuvQ+lYWGvaF@uli2A;YY}$ziNLXI>ki%$9~gq~4)JOPCA6s=At5SPFYv zqL7y6pPaI)VU3a%o!9zT)v>dX34K?uyPy8c^ignFdPx}(VpXM?&c@=XJylQSmyVa9 zQDq5#vZ|6{%2)(~PdBJRmJG*o8mrX^?bk(l|D|d}1#SpqSuG-1fmhH+ZG}^|5-a8^ zS^!p?ra!obCSG*0*6fJB%z8b!{@#d!Y^mAmSb$4t)xD*fmKO>syJ(ix;(uWIO1&l! zXb!fheS~F?%yn;`xA4wTs4_!q2{u0+Y_1YUOGh(QuWxFJa05Xbey{)1(iY~mEJQ^y z73NH=1zYj_4SKa&@>;_!5%lQII%B!3nXJu4=>tt$uv#(8L;sbe*0fE*U>KXJr-M}u zG~t+^rjS9V#fl5lw#jv|Lg)l7LFgDzb2`RF#3oYoVrWB4q@fm_rJ*H^b>x)_47aw_ zt95M?R!f^(L|&qf{12}yt14Yyv0UfccQKfzRB*G>4YS-9n6s+d=GrPXAt(A-xTbbf zFw&wo2Wx|YWYy!PWKx>qyds&F;bb2% z+jwfZnBFg`=EiVKRV1>tDqI8OnUHgje;pdGxuGUx_B6&`TzS&nwB}%4RS1n3X${a+ zh%Re`b-`w=D1y2bcd>-LIz15FNZX)fU^{CGG0S6U==DRSr&%qtP~76S+69@X^%^`$cA7I`h8UxmDrS=Pgkf=u6)giDKxQ-7?Gi@ zbNx1}UXwCf8a6hfh1mVr`GT7pA`$e@8Z50id#O3ArZv*^RBJ<|if1BNQ-+|7S+~m( zvzSqOO&u3%NYmKVxEQ^?v1tpIyBi_O|8RM^8bV5zWh-fPSU1}jM%u!gs*qygvhAQ3 zs;;|m8RHhNTvJ|t@49=}JXqylzViN66}Wd(HN zb9WPIX{z1S6pmosWXo-%z>lV1tYcf1$wZpA%aF?IWnw{D1$$| zDS{b(xO4lk-i|4#0g>}eiVPtblWZ^PS4U@{#(im=rJ+9NGV4;Ra zN>=Sxu#kpu;j)E!-lB%ZdETYwKNa*XOZ>AW&wFR6;W1Qb!9uPYkV#={!NTUc+IsP_ zL&Ce{&QKkASd4|s_zuJJF9u=x`N+av-&as0iL`esnXD4%T)t-Qa-|bi6wRR5t;Q~j z`6TFK2aV@6li~}hi;cg~)H#v9kPkuZx`w!6n5yNhMa^V+R99Q^C>H6^bH$@e@9-+T z!%Ms(xwf_0ibwB8D(YL?6kvIaZlhQ`eW!!I%Rv`UqSvfmP5O%TK$byS_a*X&It2?u zEsfBHUC`7`+6JpOGcU6)*(vS$T(-@VJs$FT6i4~qI`kim3HTvPwOI}7z@h(!1{`ZO zRc*Ml=;Gd0b!`w`>^P9t14NA2xfK5uy=-ky}1|Xx1cR5o|tR>_`8EY}j3fmVp z!|p9y_P9+SuC6X^aT58PY>|3SiY>}cCrqr4Gm@Y>riN}ooVbb=(rdzD91Sa@kfAMJ zyjVA903G}FR#>$B+NRb6bHVo`ateqF@Ds9j_EEgv7 z*lm`n9;THh7?F)whnZ&+G6q7y#!v7lINaP6X{v1sRbj>30#l^|8v+39JpFM~P@vV+ zJg85q?q#^Emi93DR2@uWv9*YUH_@4}o?F~hx430%V=cue+t?;^?W%SDWHX}n%7(K}+Zh$sWKKEx3@=1X6MePw+15x zdc)>$6Gk}>)OdPLhDxl?J`z8I!Ojs7gty}Y9V5)nH{ad5qW^;yB@nl=E;jI8-C zD`V|yoG4(WV_hpilDbcrI<21nB=)CjTZz>LE!pJt8Ruv@(~=chvTm_~e*%+FO(6ef zfNcLvb6N5UoqGWXZ_-@j(l}|Vl`+Lsa}mGnuuzH2h1Ht8lu8)r$$dxZ#A%C%QFyb! z#Y=(Cqcn9U(2OJ4CTs~O&zp`itBKWo-~i#b+;WSx9+Y}tZJuOF6{}PCd*tX7%o&fE zYqn+j?R-D4(kq@x>X$7q*Fy~(n$b0MS>$1u)HUgpfCV+dT9=jM)B%$@Ch~@wMs?K5 zG6#YiS~ser+aNKhB;k_^_EdVDjk-kz|wIp}hbgp;1ety8IBy;Uhdne;U{JHW=Kbwiul zFDLuFDRJ_0tSJo3fkc?a)Nz+uT<5EoB|Nc$k>Ps31p`smDW;H$icgv6gh;AH*wfeJ zl}f`l*}f#o!}A#oGoF{Lj)g^yb;u4O`TjL)E1-yNVSrN+Z3W&RU(%E>>uH`Kzep;KP837(wClxZ$D z%opi4WxD(Bt*~BiU6F1TSP0`@oO{5;7Tr+?Q|2?th+aW?)Skw)RLOkua50Ns`WT9) zWh~-{V@G*Z|6{=85Bt|utX;MIfhQQDX^V8ufT{&;R|*@{I8HPRwxT?-Tf}4gsx|k6 zT(Yz-FRwt+tC!zfo(y?#O%<3dI%BeY3`1UO1aTO^9Rl2jt&t#yFZhzzBU~SBgh}E6 zYK&Z-7n&!Ihm*NN9?M#agV;%>HS>`(WV>`#9^EbsN>L~9a?_pbCpo2f%Zt?IGdOkK$T=`(& zJ!vj|pab7XOp(?+#Q$s3Mw*&>aGQoOHH#C znMh-mO(Il#W3)=QHrpSgDnaXy@I9tuDWm7tY~-nVr~#d!*6fG($|2L_MbE{YY-bMJ zTUIZ>f7LB$-=^jO7O&{f%aS)tc}woVtCCRSvdE+p7e74ZV?U#uzd-=T`y$w0c}?mN zrq?`;I|aTN(sAHH+km>l=veh&DNO*3fSJFVTXCOconYWD!#qmIwF;~njyrK1j3ZZA z9Uh6ITnkw)%g-{w-+)8Yw#E%SD5b9n$C9S6Zj0%|7?UGg9HVZrI3tnW$T)GJV~38 zoIki&NQzer6y6JA4uzfI8j34%b$?I$Ez6J~Rw34$59?sf5$4?ew@S0h@Hn#Ff@2eM=2m zD#VS~R=ouaR9u^JZDcK_d3~ohM3yYUSBR(7hv$x#Hlhp4hj*;%RNd+>=EFp8P;ETn zU~tpL4EL;hoxY zYF87i+0dZ5a^X$8Q=2v|-8I9Nk#em&%QeR}H#OUJy=z`dj_byhg)VSR`q%^9+V3vd zBU3edtfr;nKl>+Db88-Z0QhGMT?9-4jjv&=-%m4xU5cM^7R~s|L^fu8hmGca4e3Gr zO$tk%^|vzk4QuAVKGT#&n(~f-*Zi3ArL&SWzPqoHu1-tJJ3c2FPkH;VOVXbb`gBT? zzj|&mp8Wi~5a!3sfBp4In(;k0n);R9Fgc#T1808B^lrAJ`k4CjH(o88>3#SatBp}14EG)H@ti9L$ed{$d-=_;MS~^tm4wc4=>mVh>dZZ3sw*bAjf;B>g_2cL2-a ztoRq<_uJ?_@K?gQ;h6p(f$MGbKf_-QXQh7^{(3mp6#0JwGd zjUEBrZ=?Sk^dTFa0uy}LM$ZL3V54sVJ!qrv27SavuK~?Ew)A@(bPTlF#%`?vG`~~a zk!O~#1$04vQvc^cdu{aVpo?tuH$fNM=(j=hd&^e&e*)TPqdx>)YNIcJF0;{DNXYLn zTl(st1E8TYoLlpPuD8YC4Z7Wy{sGV(Hu?$Boi-X`5}#CUy0T}90MPbNsy4$;Vzj3_ z=nU;C$mjg#hl1Ba!_R=WKD1>oIOs21kwC`(oipA+f6GDh?*m#Nrhnf-PdMoH30rOE zH{C(s;Ghc~^hyW4&OtxsplcoU4hQ`?2OV|L|KgzEanL_=(7$ugGZS->S>D+W`W6R$ zn}c5MpdWG23vFG3_0PZ0!TG*8(`^5G2Y-u${;Y%M-(R#ol-J{+Uw6=7cF_F}`kx(i zp{>hO{yUENpMoCB!<>aR68mr55&t^}{U-;FAB0JKC|`Hb1rEB%L4U$QKjENT9W?(o zrS)Nc&pGIq9rSJo{Tk>JoNCkNvb_J`h=0RDf7?NS-$DPxLE{IG)u;C=%1_gN<%mD$ zpf5OR{@A4TVfkh`=xhhAJLts@dYOa1*Fit*psO5oql4b&pgTZwy>?FO^K*{)sDtiz z(BF2@?>Ok6IOtzE=wE^6db1jJ#rFKaj(EEXXMOVTp;{l(a~<@J4*Dk0VU&&Y(JahA zOC=sbivBog`Yl?0IkyTgKC9}s1eVllxiz>B-dKe&H1+bI2N0%@=MVf2fC^Rw_rJFH`8+7n zcyy3_RzK-ce0>cb=|Fk&Njr<8g>md+J(}07*WYykZF8Fn#cLgnQmYcrcGOE~GV*z~ zItM3jTWwQw^Cab~nl?@TmSm(2-%R9}2-O2-eitp_uW8e8=&@O;rVe41YE-nbi7)V} zsHK~FnV!@}9owl$^-Np7`zBAN<+-&`DqlRDp5hI3V-VjMz?T~9HOm)bsW#)A)K!5k znnHYc7YbYFo@ib<4#rz~9v)4}gtJ>dg%4gbQXSGx8Jm=Es;}{ks!BDu@}VCz2URCf zryi;btheZv>fo_dZ-(l%Puoefk1cWoz94TSRlS=5tzoOrCu#FdVHK|r5`h7pvQ_cS zI1z-(+|ty%X)+&b%T%RJopPZ@(WBLI7rp4>8c$x-xr-?%umxX-M=wo^MpH0jB z=tQ=v=NQ2Gu(JfJtmf5{NZV3_TEVwk!VJkB}j7b$5TQ_fj zS+<4300fl^&29J=vZaG!kYY-n_N0?z@VbPDkbuV8&0)K6>bjw+2|vEjsPU=pq_Qwl zl2%nUVElgUV^u9JxU#J?MTNI&(yYq7Ti;VKl{LR;gs=EE22=pPU9hw@QmZB3J8K&^ ztM;#DgR`v^g(>7|t*QzJwGmbbj~Dno0tBcN#;S%u!_#=-WaX<2OJd+*{y+&>!^mhU zGSoz9Q(d5DD+x|-i3>_KRj6SbY%~WhIvu_&(5&W;ZB30qvo_2QXja5jUC`Fqtf~Yv zD;j|3#v0yst0&%=JfM4>wt15(sni_@A&sF@+{~*HqfU_=hfv$}Lw&U4MwW|ZWE`(w zG~SY{5P9T+YN%_{YSjIN7TLzG3K`ZT1!a*Dgqq-60=}h0=H>$i4PBKju4c1DCNeug z2H#{T;mjY_4_40_yyi7Kmlnaz;mB4sJ{mUSE30fhDJdGt^0ES&mG*2s^=%I^p2&W( zS$MJu#Y<#UgZ2w)E%mJt)%K<$O<_E{!xu(4)tN?FH7n1!l}8T5Rf!}XW4IRmSEa{7 zh9*(^hNix0go3C6QALjz>!}p%W7~l_Td;p8wqb8X+=}1#B;vbGJjcqxmxu-={jp(}tl;DE3V$ozH?aURi5e9Anx$-!+G&tG!zV^V{}&*E=+5Zkft zBtC=Ri6cIXGY_1ji12J8!t*8EM?|_Z;&aHC2)-S}XK{{1MEbqN4(w5gh(Anx z7W*_J;$uX}nIJxo-;jgOq>G6!;CzLMbY(=O3rqM8;!d28NcdhN(jAugBNBg9;wL2h zyo9?^N0gUCWPU{EM}*vRi4RD8yTo@%e6Pg!OZ>3l2_hOfAA5PqDly<0QtX6JOHQ-k9=ANxy(IBjQe+UlJj&jR^g^h+Uc%75Xp{ z{6~o0$WQ3gLgNRQReTolWt^82KZkq->m@!y+=>21M1BW|$nOvlgX<&_`HT^fPdfHC zq_c>iy+rVr5K*o&!3x1@BFfi6L_S?azt~1O=|srM zCL-Sg2``fHQVFk=@M;MUOL&KbcS-nO2|py^0}_6ci1KMOfiGel6Oo@zgdX`s=ut)l zUx*049YpYT5_jSJmx%ftB%Ps(g7gT z?GWr0JRmqA*m)KBpdZg5sK*hZhl$Ydl;C-Z&$`+S&lmIwRtO#@Lf#P~Uz{U$ zqg`upsaFa$bR4Anomt;8DS0!Q+A_1o`*s$sZFuC5S)V zrNU1OP7q;R&I)}_=<`DJ0G4vxg6V>pf*!#v!EC`CL7fQsxkBd)76^I;iv)`WO9Xv_ zrGjOG<$`{}3c*Uj^@7!c0l|8~kYHFaBG@LlL$F=2L$Fh@ORz_ZZCjI(lyuatQH-D<|SOMI8a?~(We5UN7Nc3Ev~(`z8FKgddmi81eHMk3^JH%K}1vCK2+piO4r!!uhwY z8SayCzl5)s@OlaFl<-~&=ikPrp8Y}(5FvM%i2P1U_?U#Bm+*AF3t)a(MCw5VznAzD z>@~3mKkWQNbY~*LgbL zcjSBl>p`HJcLnPOLxN$!h+v!G4#9T86F}}mP7|>Y$;~$DVj}3U&>cb#2t6z`-ucL?1s*df>{*d@rnX~^`wf_nt_3hoy?Ab3!)U+|FNfZ(9u5hC;*68fmn z!$KbyP6GERAJSTWwkbiW7+y@Gy1+>3ny5q$fJTQDyY z(XWmQW-b7YepEyRpO3g5^Bb`p?MTGIBqBar zXkF-BBJ?OAMzPM2c%Q_V5h2en^m>U82py96h|uj4-zjvj(0heGAauXb1417W`YaLl zK)=bT2M0xnT`NX4wr-%nI?-Eh({H=Y)hk-0-8xiH+A=pVoxw`~=1bYSd2=X`f z$rlyeFL*%kpkTk?A;H6f1A>Es{Jnn48xlM!I4pQv@Pyz=BJ_?4o|5n}!P9~hf@cNK z37!|!3e0@ng6V>pf*!#v!EC`CL0vFcFki4h&?{IZSS(l~=p&*WrGjM=UM}dD@Cu&`8gKJF z^>dbp`q|+HVtjTIF+TSaF+Tf=9FIisA18u;f=GKxggkelNoNv4=Ma&uK(LGmy$%ym zKSzi?n1_WPBceQ6OM#G^Lxh}sp}j(v2wf`ldgABNPQ=}4CnC~!5s`k6&{3fe3Vldu zzQ^Ufa$N9);7P%l;3>h=K*~Q$g#7f|O*%_3he*AJ_6b%HQ6KAtju26wJw()JOyVbq zpU1pdWX6{e5g#J%MmrIae+Loy3BnYKtwrCO86-WFSyI(D-%+#QXdg4tEtoFo0rEa0TTmCw7c3&89{Jvy`IiX#1WN_W z1bcy8pGAonrw54`rvt?QfPTbb^v7ku|HQZ>wm~lO$FOt6_fT&{)JL_%M}*!(gg*NP z4@&%?#19csp5uZsi9ajx+Q-fKbmG5b91EQzG~bQ09`lJPZ-LNWp^F5I1xo~df*w>g z^Uoq4M*k+ZLmm;~==NI9x3MmeaKD6C5@({kL zvHnx{R#B{zT%g<0p2SzsFNw&nSi*foNM<*g&-ETp@mo(%T6aNkCEs3unB3(Uk z5OzVr4+x$jei!)&J?TCR@_iUHOt0>>Ah(hcaK%n=L$)w)A4BG@LlL$F=2g9urj#8lWH3GWf?72G4ZS1>BLpNMn^ zggz+9do}8DNa7C*Js>zJctmhW@TlOh;Bn#?(XWI)DHszxB{)X>GWwg)yst}n2X38R1P>CQM*kK1klXm!sBzML7RynI1*&xmk!&j?iajKKAbNBR(P2;)%bUgB2RS>iU#S48l~1jmR- zH$g->iZg&OKt2)W;jf2qzS}{BoKE6i$Rqv$_L7Kn>9BR=&n1GdfQbA`h}4$|zBVHG zI*7>ku*Am%(=$zeuV4uge7qMV@;;DqqD1I9NQC@h;tw&dh(AL41jhu^uR=J|dx)=~ z{|l`PT||U^DwXhZ39lp`MY{^t6OW-BM9AwPBEK#{ZAr`4&5@c7z(})+^_H(W7+Qk2 zXAw>Pl90A!LraUc#0msI9<$&T(~{;U{*VCE7cME%mdIl>Z3%t=Ex6>K2OnMx&e~1d zlI3e3SW*+gdz=k;(bl3}`DZ>@F@0(1!s=6ue@j0u1OQ!7eLjMJ(JeHeqA-_z4(&fr zT?MQ37)FXW4@h}1o9fdJS!klf$C7s|Lg=^n&VjEhKf#vBmum>Se&pl5t@W|wc@bdO z@9q9=qKO{)G{A8mLScWG)QgEt8stX>m$Bo(9{bb|&uRZcjVNGIr%LZPkQY z{eE|Ut6QC9%Veh0h2yio_kQoa-+S-(e*4?ysXy58h^A@EyEIj=N|I80mpC=yMhEpO zs;Zp3(bY@eKC7RxKO7%_N5vD?JxWyuj9gLaxgyI#Dpzpo61jqRuc>D&r>r$$eH!{9 zGgr{iCajBYI$?!b?zJ^hXa4MZ1?17EFAh(Xt3>fX#>Xf0SkHcEwdM*RjE|3m!1qZh ze-HOGIvrp7wnCkzp0>2OXJ5h!VN8XwDqu&*eEE9FaO=WopYFRjtn{p{U3%J5m-=kY zeAQm0HWzEy2GqeQ^k*qGqD<%+`z>Wq?{!BHtL5lbfZL!CyBBKaK1-R^YUEA+Lw$osLL zGxpfNGj>%xSE$1JM_O~myL0*CQ`vlxcHRxW=ao9id1~g%Lz?-Tt;Jyz%1=(R^BQdI zw-)JZtj%g;QG5T)^M}y>3LK;ki*`G|-=)p%^#6aM`%yM;2u_}R?F?&CQ&t}pIRrMN zuL_)lQ}9vxaKOA~2lRexKgyJmwSR1Wub@>{7w^Nq1X0IwuWT{OLSo8|JHRT8>6~}?cJ%5|YxFO0mVEd0O`bcB z;pu96pXJ?qZNl)=&pr5TAz=Rf5N!#rg8ZdJ^j*ic0Q&K4djsQQS=Bfko#$QK2p@07 zIPqM8aYm`E8?z8YW@G+J_1i2DspD?BnXs@WT(4rt?6;$>{k9VWAd_P_gDRJ>a9}GV zeJ63|wiFr5RO?AQkj7Y9XRKnV^$`olWF(@R3v*B(O8m7QdI2`bnrQKUYnDnB{u5`P zWwVT2VK(e=*DFyRMR~|PiFG-4eN5JkWDKam`R9DNr&=yo;QX<-uKj(bF%;$q`>HiP z7erjHUPU`$AJ;=0%oU78uBfH)Gvij&JZdZW4P$@_Jx#yIV!TpDu39s+0OJ&Fo(KP# zdlR-X2C`30XB}cg>*9HLDurcvwD?&tV&n>yn2#5Om65RKv%IxGuO%)Ih18>kTb!eQ z+GhNYc=p3)#?%Tuo}aBhm&e(d8!_O2`hFb)RceWU7}DUQY#W6y1TdFiyf4r7r2SPG zlXLhN)Ypj5IdMNB>lJs`I7B_z=*0N=#o>eSo0;^UV!VZY@Pi0reE{P`R5lEsuBLCy z)V^EVa~^CTM0@y1Ww7eZyKEn*$~r`Un1g%G)aM_U*%Z1>o4!)kK75<)S4n%EIcH84 z?wl54UF6*4opZx{!SPw_L#z*H9)5@OH*zo9&oVjRL=mJjRWj~{;BOH#8xCT>w8Q}` zg8lN&j&qKYLxhb-^7OBFvDcI8u&*p1U43zQ9`;p79PrMSKF!{tY}l_e-TqLA`$PKo z$KTHD+rJ0peS13obuQPLZY`;!RKDmkLBE^Ne1Yo*YFI0q=K_B9jHVs?E^IvJuMd5K z`k>laz%$@pCy!Zv>^jepV~cSNzK-+H^Q(^8Zoh86!tohL*+1JO&zaQT-OppFbJ|x1 z;OlH3s5<7*^K)+=UOyjY2mRus?BF`g)IR(%?U}31)V^X$`@EC4(f*KcPwCL=3N_O>E2Ys~s8n zaQxyh&eBLEegZy$oYy#R2et2A=lKem$DQYgX0EaL>_py*=YjiN^_~M>ymk6eC!n7w zjzRCb^a&d#^%+6`>^GntbH<4H#)x1H5R+6c z3tO1O_uI;N-413Ecg+3PKE$0D^pk}^<=i6Yv&i_ewG}=PKrZ}4=wHUeeee_h6dr7z zF7H3d&(r_^jn5C}G>5E6-vR99K|6$B6u$_5++WT3rS0EW`aSpB+k1ZKkRFjvB}bqQz^Bzs2Tf^YHlhrFJhZ&^$q00 z{kE>DXPKw7{{)|}xr9}P@uK>G9We)-IRC0rtDc6=9OPbe^xTR$cWf+PLH<4)ZRppP zLhs!e@4ZFdGbaihzIcPVIQw5m`{M})@@&T$ zJCZ;y$Mw^9Pvq!Vv1VcQ4LnzJ1+Jg!!}^FX&<}!Uu9|g$!w2q!jr09BdU~GET*REh z;xl-EcD_^4XRvWoe1^V4zxiGK@#(Ka+q+l{nQ{IJ{y4`u29TZ?nDxfOR+ZY{3H z+RmQKb2u>JKTTX)1M8`9{<8<|b9~NV4V-g}aqmaHcV;+G#Eya*#QO+z_Va8&X9;Xm z=!5ST)Qt=dz&DQCkN+0WJiMDIoXIL}-}NfVneIfNQN{gqpCMJ>-*-J?9+dY>_yqQG zN}PbMx3AvbZTpzL`?n82e(!$qA>Pl?Cu0H6hx4p!of-pZ%b@ux{HeP5!3=$V2WJ=` zDHcW;htTI@SszRcc?0}aV(1XY4w!G)G3e)^KZ|=l&E8h<-4ec`j05u{@Qr^(JI~hF zaIYbL#Nfj}M<4G&_S<@823sG)*gr+Pd(^;i7(U_5AA9)|v6cQndm{<=jQ zY$g8=T!2S3zMUxKqRyk*07LnuHow0qd?yI0qgV&#Ot0I&#_Qpe z)txr*N9taYz80acBgPr~2)@hSgl`_i{XyJ^p)(3OnsD182I7pk`40QijMsYjEoPXDC%)bY1KHs=UWqNj}aI3Z_J9~Mv6dmr_qEy%J&Q6x!yG*HNh@O?W zUxxd2Yu2nX>Nh2O(``GGUB-j46)|(^@|E}Xq<1dAuOrp=(C+0++*0FybNLs|2bMpm zR>#mdnM$_pR9YQw!w;&8iikE_s|?Hw)oAmzPX`xhpV1ZuYPGupi*Sm|E`Lc#y+7`+ z4o>>Au2c|z{8vzg9NSA}nz}=T7r_A9<6Dy&oKRp=q7>`gOXCL^M_vh1e+hU4zY7@3 zYkzCPNVh$|XS$c$|0y`JC$s;vKKb`WZU))kMsN)EeIoAw_rR$eF@`r@E1bx^-0^n! zWX?y;mTL6J@ghE%_3K&aTyA~BC$m0>7dQ8E>(BY*J*XIrq7NKY&z^|4s4^b&ORqiq z8>%VE9BRxbv%UczPTk8LugNESHDpkQo&746VPSk%P)#g^QyNrwqWi8OA`hOm6Bp8O z_1uKy#-!)Vi@A!{zaK=m&x=+@8W!JdICuK zR+$`xADzdtFow*-etib=HOO(1O~^G^M*1V`*FmO#dG&t^`J7MAKpuv?Ui6PZHsKq4 zMg9R~@1i~DA)iNm9q!rx&mdog%=1M#51Ibz)&B-^gHNswAcKJ1Ao?-Ldm+Omo%U-X zAB1d(ycO~}pWFrc3go!d_d|}rDLwm7Lgsn!>IWfbee%nYulnS-AnQ0E-uUlBuJy@b z>~}q6KKD4k&q7}7lTFC2KKT*Idm(%C+Xk8E&s&dP$ge;ilK%EX_AajPvyged*5aPy zor2ti7a~*S^N>3sUlsWk*5kZ+^(vk0>Fw@{wX3?M>PaO#TDI@(OsTpKsc7lV^mKLX zPOERGGA-$z?)Fq~Z>oc(JG;KA9!Jxb?zYEM?U|OIRPW9|OHC^3Vxw;-JMq}*N$u+X zcB-Yld)Ka1SEkqLJ>}NLU_vHls_otBRF~S`n@W94ZO^3eOzKHJv9qf~?MilbcDJkT zG*4}B@9f6YYkR7vr@Kedy7u&ws->kp^JF^J($+%FmX@8p-R%!O)Y9AAp6uFAtxj}} z_D)Amqg`nhJw3@MrQbq5=NeEdoMdgO*k`Qcla5{V8n=C^%s*7A)HhVA&_9DxsecBg z(n)5y#c=5q-7wSL;Tplyn408kZi#a!oz?Sev^=phv!isTeD%~Xt9A46+Qn{cs{Mi+ zV`}T&SWvsfjmfq5A|4`E)ZT|UMItaU2dG_&I4g3NM4op}VHj zfcU8JBSXT7Fe>DKOtHR3xIkDdG=z1+dSQdm6gCQ13Y&y+A)gf-XT5NPFd=LfZV_%1 zwhB9hJA|FWv@j#wBit+O6ZQ)a3Xcf0!sEgL;VEHGcvg5$cwRUtd{KBo_>%CF@UrlV z@D<@z;Wgo#!ncHZ;gE1xctbcUyeS+L-V!oRclKWx5k`f&utvB*SSvJyb;5dKgOJZu z_TMO6DQptPg=>ZDh0S0s9)vHE%kkU}f$-~Q@_zU~xk4%C^YrhRM7}0$LOM#lEo39U z)Qg-GHXzSpc{BL{+LO>964oJ)qP$0VK{)1>M^OJI^h-i-kZeN!CX6Dlp==79g}0C| z+=+SUNK$GyA-5nQHwxn*>pMiwkkHGDJRr;o&x6#zOd0FN{GaO`73#to;R0c;&=A%M z>xB(MQ`jh6DQptPg=>ZDg&TwkVY6_HaGS7I*dg2@>=dSj8Q~t`USXfGUwBY>M3@yG z7Y+zd33I};!gIp&!a?DS!VAKegqMVug;#{H2(JpS3EvdHCCm$lgu}uc!cpN(;h6B2 zP<=|yuP`Ev3Uy(PaDlK^Xb9_s^}+_BDQpz36gCOt!nMNn!VSWNuvxf8xJ}q9>=5n{ zb_z2fpIcessX6X>IV(I*;(WYGLNB6&n~`&qINz-#&S@Ws`g6j|!kcqlz34obbtLrG zi`*)5jzqk-oOUeobMcaHVhqSodRz_&;0cQ%c+w)p-N&!l*=8b=o8BJKr)t zFESn*&UMz;7YjHRFQib8{!DsUa;FWpz`5Ap7v-Mdxa{*`A@zCv3VIK?x6Z%R;cvar zDa1cIKf3rX&%P}vgKcTAih9dD0rd1pg!_pr4!u4+<0?_I7(d#l*2Ws+Y6q({5;M+fOk>*6xG_nkFU zYlK3j33(}@l&VlL>Am-gD30d-K7__^2QVYehbPy<^70zZB-WZK zxUrR}^-Q8JK8i$2b_6;%URi%ytv>d1WPCE&v(b(m#;=!blFREMc^UY< zO*FkePSn{SWB)e}e;fROQnC-6NBg8?H(x5R28kZG*J<8@rd@E?+vM$mBIWhTE#-C2 zq23p!IzTZUrgjijaJRK!N6rY5Hb#C@iy>F+67Eh82bL&>R@YP2Fu zttTc%CkMpinyI9+nm#9vC8ot(KBJ1Wc|)w9@ih~)sAHfcN#qVYmp840=6vk1920p# zF+V$Ns@7oFzHG)Ez~`&m=6AS&z*?5V$NTpPA}UxN{uU3>J}=(k#6xrl$o;U#0W}L!bHn`2wHSAT&ZuXu zAh{2c?+H>1je3Olv=teh^@Bj(TfTum+q)b82yyl;V(UEqT7&+21OL2%-)Z3Y8~DQp z{#^rKYv9B`Ys0*#r=>LQRcLR9lF8B26T_!YOim<|QlH%4OSyUHx;Zj(uRyY>YxDk% z{FmEVu+AB(l1b)@mO7uz=F{)e^RA-qIt@*?X31065~4!UGATQEA!(>{$qT#)$MUlo z<)Y)}H^DNrxwLN41x2%xIg`|RpsCH#`J&fB^4>M)t&3>UZA5stDT*IemT|lLn+=4B z-Ukn~5M5&2Li7X2cM$!=h;dICJBgk$Vtfea7~M(K#u&x?jL>Bn5x>TW`EM{{o-e%k z9Y)M^j}f~2jJw>s5!j9Ozyq%kJz_*&zc7kKzcV7QKRl#>%SkdX<}4)zvN%IB&AG~BqQ$<7kN}n>Ln?i&*kvOlBDM?RT`T(-D6q`4o*5Yby89+lskpf zG_m{tX4nEDRopz+_8d^nDp`MQ31SjX=R+bQV2cDv4(bsQh_kmqIFg#?bc zUFXXhZV3+^Cq7etqMm~_Y%^#G6}ZpFx`S@l`T9CWe-9j3gf0TdX7t<9IKK!Q*BGIi bU2Y=!wZ`1sn7)WsuRDvUu$Ba3{| | + || | 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/PCAP Output/local_packet_forwarder/lora_pkt_fwd/src/base64.c b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/src/base64.c new file mode 100644 index 0000000..8ba908e --- /dev/null +++ b/PCAP Output/local_packet_forwarder/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/PCAP Output/local_packet_forwarder/lora_pkt_fwd/src/jitqueue.c b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/src/jitqueue.c new file mode 100644 index 0000000..50c4038 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/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/PCAP Output/local_packet_forwarder/lora_pkt_fwd/src/lora_pkt_fwd.c b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/src/lora_pkt_fwd.c new file mode 100644 index 0000000..31a3743 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/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/PCAP Output/local_packet_forwarder/lora_pkt_fwd/src/parson.c b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/src/parson.c new file mode 100644 index 0000000..8756531 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/src/parson.c @@ -0,0 +1,782 @@ +/* + 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/PCAP Output/local_packet_forwarder/lora_pkt_fwd/src/timersync.c b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/src/timersync.c new file mode 100644 index 0000000..df5837c --- /dev/null +++ b/PCAP Output/local_packet_forwarder/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/PCAP Output/local_packet_forwarder/lora_pkt_fwd/update_gwid.sh b/PCAP Output/local_packet_forwarder/lora_pkt_fwd/update_gwid.sh new file mode 100644 index 0000000..2aeb87f --- /dev/null +++ b/PCAP Output/local_packet_forwarder/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/PCAP Output/local_packet_forwarder/readme.md b/PCAP Output/local_packet_forwarder/readme.md new file mode 100644 index 0000000..cbd924a --- /dev/null +++ b/PCAP Output/local_packet_forwarder/readme.md @@ -0,0 +1,231 @@ +#Attention +This is a fork with the needed modifications to be able to use lora_gateway +with a Multitech MTAC-LORA (MultiConnect mCard) board instead of a +IoT Starter Kit platform. + + + + + + / _____) _ | | + ( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | + (______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Lora network packet forwarder project +====================================== + +1. Core program: lora_pkt_fwd +------------------------------- + +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 ))) + | + | + +- -|- - - - - - - - - - - - -+ xxxxxxxxxxxx +--------+ + |+--+-----------+ +------+| xx x x xxx | | + || | | || xx Internet xx | | + || Concentrator |<----+ Host |<------xx or xx-------->| | + || | SPI | || xx Intranet xx | Server | + |+--------------+ +------+| xxxx x xxxx | | + | ^ ^ | xxxxxxxx | | + | | PPS +-----+ NMEA | | | | + | +------| GPS |-------+ | +--------+ + | +-----+ | + | | + | Gateway | + +- - - - - - - - - - - - - - -+ + +Uplink: radio packets received by the gateway, with metadata added by the +gateway, forwarded to the server. Might also include gateway status. + +Downlink: packets generated by the server, with additional metadata, to be +transmitted by the gateway on the radio channel. Might also include +configuration data for the gateway. + +2. Helper programs +------------------- + +Those programs are included in the project to provide examples on how to +communicate with the packet forwarder, and to help the system builder use it +without having to implement a full Lora network server. + +### 3.1. util_sink ### + +The packet sink is a simple helper program listening on a single port for UDP +datagrams, and displaying a message each time one is received. The content of +the datagram itself is ignored. + +### 3.2. util_ack ### + +The packet acknowledger is a simple helper program listening on a single UDP +port and responding to PUSH_DATA datagrams with PUSH_ACK, and to PULL_DATA +datagrams with PULL_ACK. + +### 3.3. util_tx_test ### + +The network packet sender is a simple helper program used to send packets +through the gateway-to-server downlink route. + +4. Helper scripts +----------------- + +### 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. + +### 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. +* base64: fixed padding check. +* Updated all makefiles to handle the creation of obj directory when necessary. +* [gps/beacon]_pkt_fwd: fixed crash on exit when GPS not enabled. +* [*]_pkt_fwd: added a cfg/ directory containing different flavours or the +global_conf.json file for different boards: Ref Design PCB_E336 (GW1.5-27dBm), +Ref Design PCB_E286 (GW1.0), Ref Design with US902 frequency plan. + +### v2.2.0 - 2015-10-08 ### + +* Removed FTDI support in makefiles to align with HAL v3.2.0. +* Force IPv4 mode usage on UDP socket, instead of auto. The auto mode was +causing an issue to properly resolve LoRa server hostname given in JSON +configuration file (MariaDB issue: https://mariadb.atlassian.net/browse/MDEV-4356, +https://mariadb.atlassian.net/browse/MDEV-4379). + +### v2.1.0 - 2015-06-29 ### + +* Added helper script for concentrator reset through GPIO, needed on IoT +Starter Kit (reset_pkt_fwd.sh). +* The same reset_pkt_fwd.sh script also allows to automatically update the +Gateway_ID field in JSON configuration file, with board MAC address. +* Updated JSON configuration file with proper default value for IoT Starter +Kit: server address set to local server, GPS device path set to proper value +(/dev/ttyAMA0). + +### v2.0.0 - 2015-04-30 ### + +* Changed: Several configuration parameters are now set dynamically from the +JSON configuration file: RSSI offset, concentrator clock source, radio type, +TX gain table, network type. The HAL does not need to be recompiled any more to +update those parameters. An example for IoT Starter Kit platform is provided in +global_conf.json for basic, gps and beacon packet_forwarder. +* Removed: Band frequency JSON configuration file has been removed. An example +for EU 868MHz is provided in global_conf.json for basic, gps and beacon packet +forwarder. +* Changed: Updated makefiles to allow cross compilation from environment +variable (ARCH, CROSS_COMPILE). + +** WARNING: ** +** Update your JSON configuration file with new dynamic parameters. ** + +### v1.4.1 - 2015-01-23 ### + +* Bugfix: fixed LP-116, fdev parameter parsed incorrectly, making FSK TX fail. +* Bugfix: fixed a platform-dependant minor rounding issue. +* Beta: updated beacon format, partially aligned with latest class B proposal. + +### v1.4.0 - 2014-10-16 ### + +* Feature: Adding TX FSK support. +* Feature: optional auto-quit if a certain number of PULL_ACK is missed. +* Feature: upstream and downstream ping time is displayed on console. +* Bugfix: some beacons were missed at high beaconing frequency. +* Bugfix: critical snprintf error caused a crash for long payloads. +* FSK bitrate now appears in the upstream JSON. + +### v1.3.0 - 2014-03-28 ### + +* Feature: adding preliminary beacon support for class B development. +* Solved warnings with 64b integer printf when compiling on x86_64. +* Updated build system for easier deployment on various hardware. +* Changed threads organization in the forwarder programs. +* Removed duplicate protocol documentation. + +### v1.2.0 - 2014-02-03 ### + +* Feature: added a GPS-enabled packet forwarder, used to timestamp received +packet with a globally-synchronous microsecond-accurate timestamp. +* Feature: GPS packet forwarder sends status report on the uplink, protocol +specification updated accordingly (report include gateway geolocation). +* Feature: packets can be sent without CRC at radio layer. +* Bugfix: no more crash with base64 padded input. +* Bugfix: no more rounding errors on the 'freq' value sent to server. +* A minimum preamble of 6 Lora symbol is enforced for optimum sensitivity. +* Padded Base64 is sent on uplink, downlink accepts padded and unpadded Base64. +* Updated the Parson JSON library to a version that supports comments. +* Added .md (Markdown) extension to readme files for better Github viewing. + +### v1.1.0 - 2013-12-09 ### + +* Feature: added packet filtering parameters to the JSON configuration files. +* Bugfix: will not send a datagram if all the packets returned by the receive() +function have been filtered out. +* Bugfix: removed leading zeros for the timestamp in the upstream JSON because +it is not compliant with JSON standard (might be interpreted as octal number). +* Removed TXT extension for README files for better Github integration. +* Cleaned-up documentation, moving change log to top README. +* Modified Makefiles to ease cross-compilation. + +### v1.0.0 - 2013-11-22 ### + +* Initial release of the packet forwarder, protocol specifications and helper +programs. + +6. Legal notice +---------------- + +The information presented in this project documentation does not form part of +any quotation or contract, is believed to be accurate and reliable and may be +changed without notice. No liability will be accepted by the publisher for any +consequence of its use. Publication thereof does not convey nor imply any +license under patent or other industrial or intellectual property rights. +Semtech assumes no responsibility or liability whatsoever for any failure or +unexpected operation resulting from misuse, neglect improper installation, +repair or improper handling or unusual physical or electrical stress +including, but not limited to, exposure to parameters beyond the specified +maximum ratings or operation outside the specified range. + +SEMTECH PRODUCTS ARE NOT DESIGNED, INTENDED, AUTHORIZED OR WARRANTED TO BE +SUITABLE FOR USE IN LIFE-SUPPORT APPLICATIONS, DEVICES OR SYSTEMS OR OTHER +CRITICAL APPLICATIONS. INCLUSION OF SEMTECH PRODUCTS IN SUCH APPLICATIONS IS +UNDERSTOOD TO BE UNDERTAKEN SOLELY AT THE CUSTOMER’S OWN RISK. Should a +customer purchase or use Semtech products for any such unauthorized +application, the customer shall indemnify and hold Semtech and its officers, +employees, subsidiaries, affiliates, and distributors harmless against all +claims, costs damages and attorney fees which could arise. + +*EOF* diff --git a/PCAP Output/local_packet_forwarder/util_ack/Makefile b/PCAP Output/local_packet_forwarder/util_ack/Makefile new file mode 100644 index 0000000..4990099 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/util_ack/Makefile @@ -0,0 +1,33 @@ +### Application-specific constants + +APP_NAME := util_ack + +### Constant symbols + +CC := $(CROSS_COMPILE)gcc +AR := $(CROSS_COMPILE)ar + +CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I. + +OBJDIR = obj + +### General build targets + +all: $(APP_NAME) + +clean: + rm -f $(OBJDIR)/*.o + rm -f $(APP_NAME) + +### Main program compilation and assembly + +$(OBJDIR): + mkdir -p $(OBJDIR) + +$(OBJDIR)/%.o: src/%.c | $(OBJDIR) + $(CC) -c $(CFLAGS) $< -o $@ + +$(APP_NAME): $(OBJDIR)/$(APP_NAME).o + $(CC) $< -o $@ + +### EOF \ No newline at end of file diff --git a/PCAP Output/local_packet_forwarder/util_ack/obj/util_ack.o b/PCAP Output/local_packet_forwarder/util_ack/obj/util_ack.o new file mode 100644 index 0000000000000000000000000000000000000000..a41937e8749df8adb72780c0b748c85dbfc5e7ea GIT binary patch literal 3464 zcmb7HU1%KF6+W}Ol5M4F)~?bh?!>unEh;G{1A|4cW0%+ zyF0_oU0DW0MRCD}3@nKf0*=t8(2|E1LdatvhUU=$Kjguby7lfdAx;Z*-||qZerNWM zyq=P@y>R!t=jWcEd(S;1{rtq~=QK^DXlOJ>_bH;aheFA~WC&yQFnxvYote0M;`Ur8 zb#tz>mTh$tROsqiKiEMuGR0YQH(?+2pbhx-XWQiYZ_lHVBkKG9yHv)xzVZwIa4?b&y_ zwAN}L1}`nvgW>eL^6PXDf!BO@Gf0WogXW%1*uECZMrb3%wO?-rdeiS*cl^#;!vB>Zj!SCqljo=jdcL{MOg@0#9pMDX#scfq~AX@Ew z+V2c0*+YmOIs7rd`*x6^pLEmEqeSnV{=BvI8T%Ya_x$Sl_23WS-OM5G{HtNSw{~L< zn<-^)K;g5y5le0($UXVNC&Ot!;PXubZ)zX4XZPO@j{NY0PmN}eO*R57)wKHLE*Mc{dpMkYj8?_hY9o+k(-+7qmGp>W{n*S-^dm8mNOud?; zML!60$oic{_ND5L?@}xL5@&z5`}eJ_y|*}THL?k!x3T^wroddrBWz-38$^Etehl9G zz+ud3%v+dc@UCDI_T~ZSJY&h65)Elp7mU&cG40r{6!k`}Xu8LTh)&GUpP8Q$OQtl+ zW!I`#Y~h-+;nvNv81;te`4^r$GX<8{s7f(viDlc9Q=^{nOn1d9nOF`*He9o`QgQ8C zzX1UkksLniM$L@-2vtZ-92d?7DXh8}51qif7z=|P^$w1+>*6J8dN4;~Oxm{amN}D( z?TV8xifL@0o0%1kYfHOiR}Zq@IIOK0RjVvkOxLsQx)^P6JmaEp?x&{~o}D>6Bf@}} z3~638E{W{wX!h87o~%aG6C#YsZb-FdX7Ez$scV z1hjZZ@4mOorD}Q7tXuUZfzwmB0j-m>vr}Sh-te5FWz@yd%#loXB6sX5*RgU>mCfS! z8o7f#t~i{{eK-4b?kLS<5TaokMT@ipSPmSe!NHx{E^R2WCpD}+sy&t*(H__KCUk9I z;+rVt{}}ze6#ZkXUm^Lx7m6s!51?-(!Ra4KO5v;3KT`Av$kW?useU1L4~Fvl%)$Fh ztdg`dM&rxZH_tABxdOWAqPV^fNK~M=|=v7`+&y zXJd3VMxzaF503vA+Y}Mgzlzb#82ws|z7(T>8>1;I%S?$57<8aaHN&b?<)Ujzlgw31 zQq8P+rlh`hM!i}mu5zk4XmPSa71uPQen(N?;$2aLB5%1N%l5@OEg9B=C*AO&QNyd6 zrbGPnq)kpkdbF?rQQcms+9gAx$B-w>;Be?!O#Q1w#~4vu1x6fb@V$FzK&wy9i0g*p zf2{cT7;!E--T~(|$%uXY_7Vc;#E9o~R^fStFDYD9SXQ{K&`~HAt}48&a82PAg;y26 ztgxwYT_I(>OEqZ}fzs{aas-#TWkOpOno<|witY(-AUBi_P2OOKlyf>GbqzpH3 zbY;raZE0pszHn{=nxzYrnVFx<7*e`cv7tV2xcC2M;3~te7j>e^*d|i9zjPw}$!lA3s qOM}7x2H|hMFqbrtbL~3@dIQP*9=vEo_VDx{(Ax<^E0Gc~()%|r7B7?l literal 0 HcmV?d00001 diff --git a/PCAP Output/local_packet_forwarder/util_ack/readme.md b/PCAP Output/local_packet_forwarder/util_ack/readme.md new file mode 100644 index 0000000..9de6a40 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/util_ack/readme.md @@ -0,0 +1,65 @@ + / _____) _ | | + ( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | + (______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Utility: packet acknowledger +============================= + +1. Introduction +---------------- + +The packet acknowledger is a simple helper program listening on a single UDP +port and responding to PUSH_DATA datagrams with PUSH_ACK, and to PULL_DATA +datagrams with PULL_ACK. + +Informations about the datagrams received and the answers send are display on +screen to help communication debugging. + +Packets not following the protocol detailed in the PROTOCOL.TXT document in the +basic_pkt_fwt directory are ignored. + +2. Dependencies +---------------- + +This program follows the v1.1 version of the gateway-to-server protocol. + +3. Usage +--------- + +Start the program with the port number as first and only argument. + +To stop the application, press Ctrl+C. + +4. 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. + +*EOF* \ No newline at end of file diff --git a/PCAP Output/local_packet_forwarder/util_ack/src/util_ack.c b/PCAP Output/local_packet_forwarder/util_ack/src/util_ack.c new file mode 100644 index 0000000..7acd32e --- /dev/null +++ b/PCAP Output/local_packet_forwarder/util_ack/src/util_ack.c @@ -0,0 +1,193 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Description: + Network sink, receives UDP packets and sends an acknowledge + +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 /* printf, fprintf, sprintf, fopen, fputs */ +#include /* usleep */ + +#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 */ + +/* -------------------------------------------------------------------------- */ +/* --- 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 */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#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 + +/* -------------------------------------------------------------------------- */ +/* --- 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); + } + } +} diff --git a/PCAP Output/local_packet_forwarder/util_ack/util_ack b/PCAP Output/local_packet_forwarder/util_ack/util_ack new file mode 100644 index 0000000000000000000000000000000000000000..0a370efefbc9532ba714ab83e1b4bc60f0f54d42 GIT binary patch literal 8828 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 Yy}zA8uV{OGW$^2rgQ10#5;Cp#UzeF2(EtDd literal 0 HcmV?d00001 diff --git a/PCAP Output/local_packet_forwarder/util_sink/Makefile b/PCAP Output/local_packet_forwarder/util_sink/Makefile new file mode 100644 index 0000000..cc6a751 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/util_sink/Makefile @@ -0,0 +1,33 @@ +### Application-specific constants + +APP_NAME := util_sink + +### Constant symbols + +CC := $(CROSS_COMPILE)gcc +AR := $(CROSS_COMPILE)ar + +CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I. + +OBJDIR = obj + +### General build targets + +all: $(APP_NAME) + +clean: + rm -f $(OBJDIR)/*.o + rm -f $(APP_NAME) + +### Main program compilation and assembly + +$(OBJDIR): + mkdir -p $(OBJDIR) + +$(OBJDIR)/%.o: src/%.c | $(OBJDIR) + $(CC) -c $(CFLAGS) $< -o $@ + +$(APP_NAME): $(OBJDIR)/$(APP_NAME).o + $(CC) $< -o $@ + +### EOF diff --git a/PCAP Output/local_packet_forwarder/util_sink/obj/util_sink.o b/PCAP Output/local_packet_forwarder/util_sink/obj/util_sink.o new file mode 100644 index 0000000000000000000000000000000000000000..4a885bce1bd367bca14e90ad889a3b0949bf003b GIT binary patch literal 2512 zcma)8OK%%h6h0H%X%bR!OG?w?(hEvNP}{*S>4T`MT=fwUq_rZ7x=Y6PI2qNR8O>bx zA)%5DLIRCcsZk{aQD)g?CH9B~i!RF#K&ovib4q~G)FHuq6a%dO>RDfIoe5+^z71+C+B}#>SiAav8~f+-QNBWXGx(|Bf4W(OEqpo74=KN-HXAm47D~nMWN!N% zmG>RzVpDW3xedRAJq^lswBDWZdVf~D$Gqk|_Hl}$y$sik+R8YidCo`QMSF*Ol*(6K zK9fJOu3u4Ga-SYyvVpORAu#S^lrb*5+M2!~+R|vQxQ2OEoU$!P3bWm+>dxs2I={Sp zX?a1c=~AoL9m8x`!qH{hG4;BbbtlNt-F8!oSwpN_u3VUPg{wQ)jGB(=1meg3j$XUg zaIDs_vta$AC55fkuIf^RdX9p?BCKN8JK{1re&@T>S<_36BS5HShj8o*9s$8 z4oj+r8EOoP1Rr0#aA|mXO~aMCX_#xmGLs}QTReMqLCh^{u3a@WQyf=6P>V-OCl5Kc zQ94xDs~@&Y??qB^v{*V(JXShRXH?X$=~~qwWd_sSaT*;RQ^u8vk=@xTWskBqGp+1X z_Ky^lw?^I>Ze|!W4U4k$S3i;mZ7l}k5^@gGBi%F$NV|u*iBiQ9@2B}gDIXjDIwM$N*EyXcrymX{OJJ~>CdM4 zPg4Aq6n`$o*He5e#ebXPZ>9LJQ~YL%r$HB~HJZ_ArcN!*FsX6fF{DoV4MS2(Z@Idp zp?;$1@)BQLYS`#8*`S7_>q*10v8lCmZa(x|)JU|lqC1Xhtu(EgCJoCZcfBp^)^(HC zG-Jh;4%C)IuB>B1R79UW)Nv&&ABem+fq&j+2Wg%=e zN49N9ELO8`Nu4LPX0=+n2`AH%y1IDr@)20pu9A9Yc}dlzbc|{nZxr_G{}Q$&?C_-P z=2MQ?j*#BQm*+{h|C`;ru;GsVFS}wKZ#XaaZ$il{aTtPejB>14g~Kz<`ORh<9-NPR zl-#UTPXBcG!4IO!{hT9Lm@stCJTxMN$3W6sO(`o{J$7b xV#heNJo+6@k05*na(*wlzB~r6bvV^8Iil|&ODKtlR(Z*;f@kqsVgyX={sAE`4-fzV literal 0 HcmV?d00001 diff --git a/PCAP Output/local_packet_forwarder/util_sink/readme.md b/PCAP Output/local_packet_forwarder/util_sink/readme.md new file mode 100644 index 0000000..9bf8992 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/util_sink/readme.md @@ -0,0 +1,62 @@ + / _____) _ | | + ( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | + (______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Utility: packet sink +===================== + +1. Introduction +---------------- + +The packet sink is a simple helper program listening on a single port for UDP +datagrams, and displaying a message each time one is received. The content of +the datagram itself is ignored. + +This allow to test another software (locally or on another computer) that +sends UDP datagrams without having ICMP 'port closed' errors each time. + +2. Dependencies +---------------- + +None. + +3. Usage +--------- + +Start the program with the port number as first and only argument. + +To stop the application, press Ctrl+C. + +4. 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. + +*EOF* \ No newline at end of file diff --git a/PCAP Output/local_packet_forwarder/util_sink/src/util_sink.c b/PCAP Output/local_packet_forwarder/util_sink/src/util_sink.c new file mode 100644 index 0000000..3c09c9b --- /dev/null +++ b/PCAP Output/local_packet_forwarder/util_sink/src/util_sink.c @@ -0,0 +1,125 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Description: + 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 +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +/* fix an issue between POSIX and C99 */ +#if __STDC_VERSION__ >= 199901L + #define _XOPEN_SOURCE 600 +#else + #define _XOPEN_SOURCE 500 +#endif + +#include /* C99 types */ +#include /* printf, fprintf, sprintf, fopen, fputs */ + +#include /* 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 */ + +/* -------------------------------------------------------------------------- */ +/* --- 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 */ + +/* -------------------------------------------------------------------------- */ +/* --- 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); + } +} diff --git a/PCAP Output/local_packet_forwarder/util_sink/util_sink b/PCAP Output/local_packet_forwarder/util_sink/util_sink new file mode 100644 index 0000000000000000000000000000000000000000..7a80ae5103967f33b9ba963e87b92dc68936d938 GIT binary patch literal 8716 zcmeHMeQaCR6~8Zb(zK-1EiN@(fVYuKWi*aMLusqRoPMM&OWQ0Bh)EqUFZN5~)v>+j z=YDM62&{Yz@LJY@#zYryLKTxL)J;rc8fBftHVJ_!0;CO?7RS7;DKH8GCK#*t`@QFz zzPxqXCh^zAX;1Dw_x$d;=YGC>u5aDY-K8jskW?p@3QGGM714oo$EEla6!jfK6|?k4lvuLz7BsQ&_|#rpMp%LUiz2=K8Lo+>rqaYx-Z#Q65_uQ>xaO- z(0ki|4`pQ5r*CcP)IeK0*_uve3VT|0b7Xihn$Jd;L*-y-gSd@bZ)B0+c+h7OZG^!r zqyDjbU-{X@Bctm6ziz+H7+ zVfD!c7b`z`{>s7L$ZNa5v+~`0o5d9$KGGIwZ=QYh!vlJt>*HTswR7R3)4R?}+cG^O zGe}kNut#RwD}3_jeR7LWW*te)Z88MK`BniyuBZdRa4;QRtc6UaYMq<~XnH>>% zD~U>CaJQMV3^AC4L2FP9nualLUDN0k+Sg4>8_`o4G&D4l&D2UXv9Wve+I8C1(GIsW zby%(c=^uAB_k>)J;Z$laSNYFt(r}4ywNy%{8VtF)ecUK+ZRq8m}ornl>P7=H2m8Glc8tsn|%1ACvBM@^W^XI z`#gEelTUl{Nl$)o&Y4%>(9F#LTC`iMAeKZUfp zRGd7~Q=AkB;rl({W2MSye{mAa>~uUoEEuq?;dr^5bERjZ5dha8R_wnh99d^%{~2Ki(N z{-I6~_&XhZslsyBuMd^VMdX{~WAyp-SPWRy9YWc|o_c#A)3-d*959>Y(bjh4z7Q#+n>`-^Sn^!J2PJjFhTFpgm{W}Cn|eOA#=%uxs16A#&6K{@?V zdhVFyoV<;Gy#U|1XPdDHJ_d6y>;ta>KMURg78qYKQK|$(hhLuyn?V)(N3`0Z6PTwH zt#%XqYl82qV4r?Ap-#L(5l3e$B8>8|I&KGI2PW#&Lw~{7xpG;xP3b_pv01oZ;QVL? zrWrOwY~4hI*iQZZA(RO>uUu4ZD<|M1{Uo)7#_bm3L!A?y^GoF)U4GlduS3VmwCk_O zI++j#(HnvW2U2p0iw>R~S+YS8(ChB{@{aq6Z=B^HN`r6s*8(sRb@|nAi zH)3s7l=%M1VC;d{XJcF&L!~m;4s8d;P36B~etNjRBHK+5Vr}ER}Sb<^H@nT7S87&X&wT!%) z&Sr+D{Tb9#X?S2|)ojklsP1P|&6+IX2asvXLIaz(c5U-^FP+L;MkbXRQnMM^2>s~) zNA*^$(ogBr5%_cj{*On%!uO;bOyXZM1(zWqeg$THz&q|jFo|)Rz&C*>{6R2dKgMB=@Tmw=5{$scu4+?=UXai7z6<_a~R; z3hHJ5xC zv3Aa}J@zl`k>}A~O_un$M^Art)aIY#%lh)%q&;p)Da-mfPyLn9^B#-@9C5~5u1B75 zo1l=g0-5%CHoG)e@W7L@wEu`lPycvEyR<-j19&O(B&DA1yA%;Wkfu=}=Z~?4OYV9I zPLIDkUW`Gc-aj9-FZUl$^CN&tnI}5+a=wtm*>g_j{CSD~oPb`A2Vaq1fGp2#1^e?A z$g+RbzXh4!6q17d%JGxu3y^OAr`KnbqaXIj0aRuGcoHtaeBKFJ?k5%VeOeBkqsNRv zmis@1`TVR;-wAoR<`4C&X5bfzxsc_4T7dl#_u;!COX7F1zgD0A@eR)O>GwmH{)XW1 z{XYE?pZu6dR$=ovWVycQ*DVsyc=T-l705hq-1g^*DWCph$Xq}Dz;gVW+)oT7%6NnF z$t!*GvzR~cc-;eg@;s!y8-Vxfdm($r{}#wRPu8Oz@d=;(-9GudKKY1GKJJsBhb-IU z_`K@VH$ax>YDSTw82y+rRk=r z@70WqW$wjWEnauEWMO1vFRFMX4M=MS)JPAuvM`$64ro_z=avoHhOO)2rt7oJ�Ib z+WIeV?cB0?9Z(*P^Byd;josVUc6Mvqy1KS+=+pW-*LH89Ip@hZkuNy@LLSKHr{9ac zui5eVxz}UONa~he0~?tn{F09aUYPr2?JaR%ue~K0VVH7Tl-`_sz@CYm@s^E>`K&gq zXOd~dd3&FM-MkGZlc|hW$Qw!dT0R4DuHzgkG(Gc4-izhqgjaT7@x3Le?TmtF_=NKS zFW>RKV9z_hx7hoX?=AN{<9LhR=M!&So#1`%XHIWnZqv$ zhM5!5Ox7}@oohF@TKZ5;!ch<%D5UVCSt==_l!o>Eu!tu2W?;`vEz{NPGR!=G%y3{e zL4(dT(!{%&T-p**XT+nHu?Kl>6R5Jzii&pjZi(WDl;j=}HHO_i;=^iFV^Tcjv4db_u=q)a8QZggE<$(xxeY3`#m2; zPP-hZGFaMk?!QIfeK$23(sIkP<^;%DI*FG;k$sXudJs2xcEp /* 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/PCAP Output/local_packet_forwarder/util_tx_test/obj/base64.o b/PCAP Output/local_packet_forwarder/util_tx_test/obj/base64.o new file mode 100644 index 0000000000000000000000000000000000000000..fc05177c417e93494906c45937bec3eae5ad2905 GIT binary patch literal 3564 zcma)8Z){uD6+ibq+jWe`+>$|>1@=0UktwV5LPOS7lsF1&T2a8HP1{YCYCCp`wKOsI zi`Ge#mNu>+ zcb|DJleQaO|L*y7&pG$pbI;4Ke!O>=5Q2gssGq)~h>}T95*_xSpOSP3ZKkX>q6Ro7kIg`|$0t554WfkA-~+dS8G z{IaWNOgs9_l0|gYZ<~Z)}#~j9fZLKNv_uL@P zulZRF{9J4MNomri^cR86`;iZ!Uu<5$nYg66u2a15MiytAyo>9h6SeSdnm?h|-bHK~(L9ScuKj`3%B+hMI zanEg8aZiTw_X>EgfEUgmHp`Z`Q6_kGAu zgdTNANo{Z}eYgWPL@VAp>d6Gw5jtcyQAd2Xd*xYC*9jH$2jItP*?nGr-by0>s=nS7 zm6Hu6i+LU~Wv;l9M`3^WDZ5wt?}h)~annwSOU)pL4RopbFhx9{gq%e|tNQ!yUvO^< za`IOo=P6dX)C_VJNBy+@X8zU`(r08h><7Mf`F`}la*-0MyaZ%+H9$vuoxrX#esT_yFln?#N1i+Ho}AFPGk_ixn2 zPUsTvP5FCyU!bSq?P_?tn!e%&@3{-SuG`*okc)e{*LiP*sDg7+Cfj(xyYr2XsGBO) z&AU{7D|PelqMp1+bl6cWKMyz#YGsmZ)XN`!(1T37g4jjiJO^CRj@6z9i5RK&^Rh8LqZLa>^9!?vX3XRb ztu`~iFk95-<_)b-(kk<{TDdS=BAOT<-=XzS=4+KgIX|b3q_?H@twY-fs+IE4K(SQ# z(ZbO8eXjPPKJ-KVp`j5PLldTaDPJg)=*2QRLa|s}bclqqDbgvvCGJtX#Aea0^oaYE z@8SY(8(~g_ZnnZ=mBLaY6@THOQiLxfGz5JE)D_G)lBi&RA4}V2gs0EnJAe4{ccB~5 zZ0}l#=KUYz(fGsPzaFAlzCU`aoa^5ozWo~^+KV^#l{{E6sucC(Z8O^&J&sDKme5eC zZAi-F_|L~5MZhK;{2@s*aP9BFWZ5hx%O`-rVEK~)0c0BA0WTk2$O-E|4Vur-ewaQE z>_V7$z#hE>RU^HPdW$qYUo5d~dM00` z(($rEyz)4Z(9ue@JZBuGno%rOtHc)A5bSw|zGgY35t1RM9A?iwM3*Jv2&CX_5s7h$ zNs0S`J@{zONL-Zog2WW|_H0HD7-6@U5&GLDW+lEL(UizFr=X);X55IHm3UQR7NyR1 z4>5AQjCZ3JCEk$uxx~G=6PD*B&Pse!;=2+pAp8A<5q?)C-jLXjyX--e?1)x#&aVA(*OS5UCbljkHF>mB4U8vP4 zU7crwwTIHfl%AeHcC0i96?Cmqdg6(xtu-S*{WD6BO+KE^8%DKUSTIU8-0J@~@U7>o zZ@(b^R=yE>PDqD0kP1Rir;JAt{QwX6I5skm_p@Fcvrp2v4sWc6Y%zm%c?@8({{Pa` zp~HI~#N@&Dj`&OOm;JJQR3hv1m_UmNzj@Z-xmzN@8^64@m9*nK1Sw*xNHt=;!GHj= zEgp<0kM{T@^wz=T{)RI2^1}Ut^AwyFOL`5A-||9^z#dBG7QJ)$c+Lb$z(DUmbrGq% literal 0 HcmV?d00001 diff --git a/PCAP Output/local_packet_forwarder/util_tx_test/obj/util_tx_test.o b/PCAP Output/local_packet_forwarder/util_tx_test/obj/util_tx_test.o new file mode 100644 index 0000000000000000000000000000000000000000..572ab1eaf4aaa077e94f9591a219097f38d2effd GIT binary patch literal 9752 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 +#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/PCAP Output/local_packet_forwarder/util_tx_test/src/util_tx_test.c b/PCAP Output/local_packet_forwarder/util_tx_test/src/util_tx_test.c new file mode 100644 index 0000000..aad76d8 --- /dev/null +++ b/PCAP Output/local_packet_forwarder/util_tx_test/src/util_tx_test.c @@ -0,0 +1,500 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Description: + Ask a gateway to emit packets using GW <-> server protocol + +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 sprintf fopen fputs */ +#include /* getopt access usleep */ + +#include /* memset */ +#include /* sigaction */ +#include /* exit codes */ +#include /* error messages */ + +#include /* socket specific definitions */ +#include /* INET constants and stuff */ +#include /* IP address conversion stuff */ +#include /* gai_strerror */ + +#include "base64.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#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 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 + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */ + +/* signal handling variables */ +struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ +static int exit_sig = 0; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */ +static int quit_sig = 0; /* 1 -> application terminates without shutting down the hardware */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ + +static void sig_handler(int sigio); + +void usage (void); + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +static void sig_handler(int sigio) { + if (sigio == SIGQUIT) { + quit_sig = 1;; + } else if ((sigio == SIGINT) || (sigio == SIGTERM)) { + exit_sig = 1; + } +} + +/* describe command line options */ +void usage(void) { + MSG("Usage: util_tx_test {options}\n"); + MSG("Available options:\n"); + MSG(" -h print this help\n"); + MSG(" -n port number for gateway link\n"); + MSG(" -f target frequency in MHz\n"); + MSG(" -m Modulation type ['LORA, 'FSK']\n"); + MSG(" -s Spreading Factor [7:12]\n"); + MSG(" -b Modulation bandwidth in kHz [125,250,500]\n"); + MSG(" -d FSK frequency deviation in kHz [1:250]\n"); + MSG(" -r FSK bitrate in kbps [0.5:250]\n"); + MSG(" -p RF power (dBm)\n"); + MSG(" -z Payload size in bytes [9:255]\n"); + MSG(" -t pause between packets (ms)\n"); + MSG(" -x numbers of times the sequence is repeated\n"); + MSG(" -v test ID, inserted in payload for PER test [0:255]\n"); + MSG(" -i send packet using inverted modulation polarity \n"); +} + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char **argv) +{ + int i, j, x; + unsigned int xu; + char arg_s[64]; + + /* application parameters */ + char mod[64] = "LORA"; /* LoRa modulation by default */ + float f_target = 866.0; /* target frequency */ + int sf = 10; /* SF10 by default */ + int bw = 125; /* 125kHz bandwidth by default */ + int pow = 14; /* 14 dBm by default */ + int delay = 1000; /* 1 second between packets by default */ + int repeat = 1; /* sweep only once by default */ + bool invert = false; + float br_kbps = 50; /* 50 kbps by default */ + uint8_t fdev_khz = 25; /* 25 khz by default */ + + /* packet payload variables */ + int payload_size = 9; /* minimum size for PER frame */ + uint8_t payload_bin[255]; + char payload_b64[341]; + int payload_index; + + /* PER payload variables */ + uint8_t id = 0; + + /* 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 serv_port[8] = "1680"; + 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[500]; + int buff_index; + int byte_nb; + + /* variables for gateway identification */ + 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) */ + + /* 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 */ + + /* parse command line options */ + while ((i = getopt (argc, argv, "hn:f:m:s:b:d:r:p:z:t:x:v:i")) != -1) { + switch (i) { + case 'h': + usage(); + return EXIT_FAILURE; + break; + + case 'n': /* -n port number for gateway link */ + strncpy(serv_port, optarg, sizeof serv_port); + break; + + case 'f': /* -f target frequency in MHz */ + i = sscanf(optarg, "%f", &f_target); + if ((i != 1) || (f_target < 30.0) || (f_target > 3000.0)) { + MSG("ERROR: invalid TX frequency\n"); + return EXIT_FAILURE; + } + break; + + case 'm': /* -m Modulation type */ + i = sscanf(optarg, "%s", arg_s); + if ((i != 1) || ((strcmp(arg_s, "LORA") != 0) && (strcmp(arg_s, "FSK")))) { + MSG("ERROR: invalid modulation type\n"); + usage(); + return EXIT_FAILURE; + } else { + sprintf(mod, "%s", arg_s); + } + break; + + case 's': /* -s Spreading Factor */ + i = sscanf(optarg, "%i", &sf); + if ((i != 1) || (sf < 7) || (sf > 12)) { + MSG("ERROR: invalid spreading factor\n"); + return EXIT_FAILURE; + } + break; + + case 'b': /* -b Modulation bandwidth in kHz */ + i = sscanf(optarg, "%i", &bw); + if ((i != 1) || ((bw != 125) && (bw != 250) && (bw != 500))) { + MSG("ERROR: invalid LORA bandwidth\n"); + return EXIT_FAILURE; + } + break; + + case 'd': /* -d FSK frequency deviation */ + i = sscanf(optarg, "%u", &xu); + if ((i != 1) || (xu < 1) || (xu > 250)) { + MSG("ERROR: invalid FSK frequency deviation\n"); + usage(); + return EXIT_FAILURE; + } else { + fdev_khz = (uint8_t)xu; + } + break; + + case 'r': /* -q FSK bitrate */ + i = sscanf(optarg, "%f", &br_kbps); + if ((i != 1) || (br_kbps < 0.5) || (br_kbps > 250)) { + MSG("ERROR: invalid FSK bitrate\n"); + usage(); + return EXIT_FAILURE; + } + break; + + case 'p': /* -p RF power */ + i = sscanf(optarg, "%i", &pow); + if ((i != 1) || (pow < 0) || (pow > 30)) { + MSG("ERROR: invalid RF power\n"); + return EXIT_FAILURE; + } + break; + + case 'z': /* -z Payload size */ + i = sscanf(optarg, "%i", &payload_size); + if ((i != 1) || (payload_size < 9) || (payload_size > 255)) { + MSG("ERROR: invalid payload size\n"); + usage(); + return EXIT_FAILURE; + } + break; + + case 't': /* -t pause between RF packets (ms) */ + i = sscanf(optarg, "%i", &delay); + if ((i != 1) || (delay < 0)) { + MSG("ERROR: invalid time between RF packets\n"); + return EXIT_FAILURE; + } + break; + + case 'x': /* -x numbers of times the sequence is repeated */ + i = sscanf(optarg, "%u", &repeat); + if ((i != 1) || (repeat < 1)) { + MSG("ERROR: invalid number of repeats\n"); + return EXIT_FAILURE; + } + break; + + case 'v': /* -v test Id */ + i = sscanf(optarg, "%u", &xu); + if ((i != 1) || ((xu < 1) && (xu > 255))) { + MSG("ERROR: invalid Id\n"); + return EXIT_FAILURE; + } else { + id = (uint8_t)xu; + } + break; + + case 'i': /* -i send packet using inverted modulation polarity */ + invert = true; + break; + + default: + MSG("ERROR: argument parsing failure, use -h option for help\n"); + usage(); + return EXIT_FAILURE; + } + } + + /* compose local address (auto-complete a structure for socket) */ + i = getaddrinfo(NULL, serv_port, &hints, &result); + if (i != 0) { + MSG("ERROR: getaddrinfo returned %s\n", gai_strerror(i)); + exit(EXIT_FAILURE); + } + + /* try to open socket and bind to 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"); + exit(EXIT_FAILURE); + } + freeaddrinfo(result); + + /* configure signal handling */ + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigact.sa_handler = sig_handler; + sigaction(SIGQUIT, &sigact, NULL); + sigaction(SIGINT, &sigact, NULL); + sigaction(SIGTERM, &sigact, NULL); + + /* display setup summary */ + if (strcmp(mod, "FSK") == 0) { + MSG("INFO: %i FSK pkts @%f MHz (FDev %u kHz, Bitrate %.2f kbps, %uB payload) %i dBm, %i ms between each\n", repeat, f_target, fdev_khz, br_kbps, payload_size, pow, delay); + } else { + MSG("INFO: %i LoRa pkts @%f MHz (BW %u kHz, SF%i, %uB payload) %i dBm, %i ms between each\n", repeat, f_target, bw, sf, payload_size, pow, delay); + } + + /* wait to receive a PULL_DATA request packet */ + MSG("INFO: waiting to receive a PULL_DATA request on port %s\n", serv_port); + while (1) { + byte_nb = recvfrom(sock, databuf, sizeof databuf, 0, (struct sockaddr *)&dist_addr, &addr_len); + if ((quit_sig == 1) || (exit_sig == 1)) { + exit(EXIT_SUCCESS); + } else if (byte_nb < 0) { + MSG("WARNING: recvfrom returned an error\n"); + } else if ((byte_nb < 12) || (databuf[0] != PROTOCOL_VERSION) || (databuf[3] != PKT_PULL_DATA)) { + MSG("INFO: packet received, not PULL_DATA request\n"); + } else { + break; /* success! */ + } + } + + /* retrieve gateway MAC from the request */ + 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); + + /* 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); + } + MSG("INFO: PULL_DATA request received from gateway 0x%08X%08X (host %s, port %s)\n", (uint32_t)(gw_mac >> 32), (uint32_t)(gw_mac & 0xFFFFFFFF), host_name, port_name); + + /* PKT_PULL_RESP datagrams header */ + databuf[0] = PROTOCOL_VERSION; + databuf[1] = 0; /* no token */ + databuf[2] = 0; /* no token */ + databuf[3] = PKT_PULL_RESP; + buff_index = 4; + + /* start of JSON structure */ + memcpy((void *)(databuf + buff_index), (void *)"{\"txpk\":{\"imme\":true", 20); + buff_index += 20; + + /* TX frequency */ + i = snprintf((char *)(databuf + buff_index), 20, ",\"freq\":%.6f", f_target); + if ((i>=0) && (i < 20)) { + buff_index += i; + } else { + MSG("ERROR: snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + + /* RF channel */ + memcpy((void *)(databuf + buff_index), (void *)",\"rfch\":0", 9); + buff_index += 9; + + /* TX power */ + i = snprintf((char *)(databuf + buff_index), 12, ",\"powe\":%i", pow); + if ((i>=0) && (i < 12)) { + buff_index += i; + } else { + MSG("ERROR: snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + + /* modulation type and parameters */ + if (strcmp(mod, "FSK") == 0) { + i = snprintf((char *)(databuf + buff_index), 50, ",\"modu\":\"FSK\",\"datr\":%u,\"fdev\":%u", (unsigned int)(br_kbps*1e3), (unsigned int)(fdev_khz*1e3)); + if ((i>=0) && (i < 50)) { + buff_index += i; + } else { + MSG("ERROR: snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + } else { + i = snprintf((char *)(databuf + buff_index), 50, ",\"modu\":\"LORA\",\"datr\":\"SF%iBW%i\",\"codr\":\"4/6\"", sf, bw); + if ((i>=0) && (i < 50)) { + buff_index += i; + } else { + MSG("ERROR: snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + } + + /* signal polarity */ + if (invert) { + memcpy((void *)(databuf + buff_index), (void *)",\"ipol\":true", 12); + buff_index += 12; + } else { + memcpy((void *)(databuf + buff_index), (void *)",\"ipol\":false", 13); + buff_index += 13; + } + + /* Preamble size */ + memcpy((void *)(databuf + buff_index), (void *)",\"prea\":8", 9); + buff_index += 9; + + /* payload size */ + i = snprintf((char *)(databuf + buff_index), 12, ",\"size\":%i", payload_size); + if ((i>=0) && (i < 12)) { + buff_index += i; + } else { + MSG("ERROR: snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + + /* payload JSON object */ + memcpy((void *)(databuf + buff_index), (void *)",\"data\":\"", 9); + buff_index += 9; + payload_index = buff_index; /* keep the value where the payload content start */ + + /* payload place-holder & end of JSON structure */ + x = bin_to_b64(payload_bin, payload_size, payload_b64, sizeof payload_b64); /* dummy conversion to get exact size */ + if (x >= 0) { + buff_index += x; + } else { + MSG("ERROR: bin_to_b64 failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + + /* Close JSON structure */ + memcpy((void *)(databuf + buff_index), (void *)"\"}}", 3); + buff_index += 3; /* ends up being the total length of payload */ + + /* main loop */ + for (i = 0; i < repeat; ++i) { + /* fill payload */ + payload_bin[0] = id; + payload_bin[1] = (uint8_t)(i >> 24); + payload_bin[2] = (uint8_t)(i >> 16); + payload_bin[3] = (uint8_t)(i >> 8); + payload_bin[4] = (uint8_t)(i); + payload_bin[5] = 'P'; + payload_bin[6] = 'E'; + payload_bin[7] = 'R'; + payload_bin[8] = (uint8_t)(payload_bin[0] + payload_bin[1] + payload_bin[2] + payload_bin[3] + payload_bin[4] + payload_bin[5] + payload_bin[6] + payload_bin[7]); + for (j = 0; j < (payload_size - 9); j++) { + payload_bin[9+j] = j; + } + +#if 0 + for (j = 0; j < payload_size; j++ ) { + printf("0x%02X ", payload_bin[j]); + } + printf("\n"); +#endif + + /* encode the payload in Base64 */ + x = bin_to_b64(payload_bin, payload_size, payload_b64, sizeof payload_b64); + if (x >= 0) { + memcpy((void *)(databuf + payload_index), (void *)payload_b64, x); + } else { + MSG("ERROR: bin_to_b64 failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + + /* send packet to the gateway */ + byte_nb = sendto(sock, (void *)databuf, buff_index, 0, (struct sockaddr *)&dist_addr, addr_len); + if (byte_nb == -1) { + MSG("WARNING: sendto returned an error %s\n", strerror(errno)); + } else { + MSG("INFO: packet #%i sent successfully\n", i); + } + + /* wait inter-packet delay */ + usleep(delay * 1000); + + /* exit loop on user signals */ + if ((quit_sig == 1) || (exit_sig == 1)) { + break; + } + } + + exit(EXIT_SUCCESS); +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/PCAP Output/local_packet_forwarder/util_tx_test/util_tx_test b/PCAP Output/local_packet_forwarder/util_tx_test/util_tx_test new file mode 100644 index 0000000000000000000000000000000000000000..db4321752483ebf9e02fb172cb4cf72f73536378 GIT binary patch literal 18096 zcmeHve{_`9o%ela5=df@fus^rl!uU%SVJH%G=2#Gn0%YnQ?wV z!jIhu=@EsLSXs()pqcIAxNX<7#;t4Jt=dSbT34!V?Y^vHJ3o9(1fl3lTcxbo&*%9u zc^K^3)A#Io|MJ4kz26`Ad++_;@BQBU-1|K9*UMM0kR(YM!6M29q4FCfQA6ZX#4M3r zBV>^a+D+m{(u1eVg*aUCk|X98wqd7#yZ zM9)d$UDUBx#A(2GE490SZqy3xiU$^=n2V*~Jv2JIdt z;sJO~V0jy26X@ppF9FBM@@!jGr@ytT(=)%*-yQ9l?+$f!_$tGJ%3DEFc_@Q?E7xuy zN>CqcGXrH5A`(aXKYY!y?qRQT(~Ilo&b#BmO4l>b41IMPXynh)W8N1UrsX;&|HNTy ztk3&$mRE>5kON6u9U{vnEaatJwtq5WE@B?yb%;|Djqn9zigchD=`1~eBhrqK?i&BM zLyyg}Kf8O%9gh4bPo3)D*|YDV@m=SqO z+m~(9AK}vm5h>yCBMzG6e~>{h$$*b!z_(<;k7mF>&VW~CzykB8jWcDT2nX7>cq1Yl z3AF`#g}28a5$$fj3$Qm73WS8q z+qTsg3Umn&-Jy2W>VYH?4R?CILE+mL@<+U^+});%^#xIF1da1{dm;hh3wgcirm$yS zK{SPZM!(#lh^x!(?-t>1y_Cz<-WBN9NiMN+^{S=ITnj31Pv>eh^;zHx{<42oou8hP zCP9D1FvIVkLeFy}ki-?Lutm(%?Pe!z;|s{s1`FYYXe)&?;D?dk0LNit3Wrk26b___ zDV)zNrVuDIg)=E-3WFdI*{gh(0#S;H=`>BLS9#HZ? zyB_78_5o!R;LA&5sa(L4BbJIl=IW6FWix2IMq;Tx@OFS_C+d*FbLq%$&tEz+bpFyK zNFNzGFAc{MEsg;t2>xc2du{!Y@+8`^6S$Mhh7=3R_k%W*?HL|WYLS=vVySF7mY9S# zO%}1lqu|GGBsE(Sd9A{cEE3L?_;Bc31(G;>c#0%0A)e0H(!F{bMC#P@q%)qmP7<#m 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 BOmqMM literal 0 HcmV?d00001