├── .gitignore ├── LICENSE ├── Makefile ├── PROTOCOL.TXT ├── VERSION ├── daemon └── run_pkt_fwd.sh ├── lora_pkt_fwd ├── Makefile ├── cfg │ ├── global_conf_PicoV1p0_EU.json │ └── global_conf_PicoV1p0_US.json ├── global_conf.json ├── inc │ ├── base64.h │ ├── jitqueue.h │ ├── parson.h │ ├── timersync.h │ └── trace.h ├── readme.md ├── src │ ├── base64.c │ ├── jitqueue.c │ ├── lora_pkt_fwd.c │ ├── parson.c │ └── timersync.c └── update_gwid.sh ├── readme.md ├── util_ack ├── Makefile ├── readme.md └── src │ └── util_ack.c ├── util_sink ├── Makefile ├── readme.md └── src │ └── util_sink.c └── util_tx_test ├── Makefile ├── inc └── base64.h ├── readme.md └── src ├── base64.c └── util_tx_test.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.swp 3 | *.bak 4 | /lora_pkt_fwd/global_conf.jsonus 5 | /lora_pkt_fwd/global_confcp.json 6 | /lora_pkt_fwd/global_confus.json 7 | /lora_pkt_fwd/global_conf.json.save 8 | /lora_pkt_fwd/global_conf.json.save.1 9 | /lora_pkt_fwd/global_conf.jsonaflb 10 | /lora_pkt_fwd/global_conf.jsonok 11 | /lora_pkt_fwd/global_conf.jsont 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013, SEMTECH S.A. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the Semtech corporation nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL SEMTECH S.A. BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | 27 | --- For the parson library --- 28 | 29 | Parson ( http://kgabis.github.com/parson/ ) 30 | Copyright (C) 2012 Krzysztof Gabis 31 | 32 | Permission is hereby granted, free of charge, to any person obtaining a copy 33 | of this software and associated documentation files (the "Software"), to deal 34 | in the Software without restriction, including without limitation the rights 35 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 36 | copies of the Software, and to permit persons to whom the Software is 37 | furnished to do so, subject to the following conditions: 38 | 39 | The above copyright notice and this permission notice shall be included in 40 | all copies or substantial portions of the Software. 41 | 42 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 44 | ITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 45 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 46 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 47 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 48 | THE SOFTWARE. 49 | 50 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ### Environment constants 2 | 3 | LGW_PATH ?= ../../picoGW_hal/libloragw 4 | ARCH ?= 5 | CROSS_COMPILE ?= 6 | export 7 | 8 | ### general build targets 9 | 10 | all: 11 | $(MAKE) all -e -C lora_pkt_fwd 12 | $(MAKE) all -e -C util_ack 13 | $(MAKE) all -e -C util_sink 14 | $(MAKE) all -e -C util_tx_test 15 | 16 | clean: 17 | $(MAKE) clean -e -C lora_pkt_fwd 18 | $(MAKE) clean -e -C util_ack 19 | $(MAKE) clean -e -C util_sink 20 | $(MAKE) clean -e -C util_tx_test 21 | 22 | ### EOF 23 | -------------------------------------------------------------------------------- /PROTOCOL.TXT: -------------------------------------------------------------------------------- 1 | ______ _ 2 | / _____) _ | | 3 | ( (____ _____ ____ _| |_ _____ ____| |__ 4 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 5 | _____) ) ____| | | || |_| ____( (___| | | | 6 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 7 | (C)2013 Semtech-Cycleo 8 | 9 | Basic communication protocol between Lora gateway and server 10 | ============================================================= 11 | 12 | 13 | 1. Introduction 14 | ---------------- 15 | 16 | The protocol between the gateway and the server is purposefully very basic and 17 | for demonstration purpose only, or for use on private and reliable networks. 18 | 19 | There is no authentication of the gateway or the server, and the acknowledges 20 | are only used for network quality assessment, not to correct UDP datagrams 21 | losses (no retries). 22 | 23 | 24 | 2. System schematic and definitions 25 | ------------------------------------ 26 | 27 | ((( Y ))) 28 | | 29 | | 30 | + - -|- - - - - - - - - - - - - + xxxxxxxxxxxx +--------+ 31 | | +--+-----------+ +------+ | xx x x xxx | | 32 | | | | | | | xx Internet xx | | 33 | | | Concentrator |<--->| Host |<-------xx or xx-------->| | 34 | | | | SPI | | | xx Intranet xx | Server | 35 | | +--------------+ +------+ | xxxx x xxxx | | 36 | | ^ ^ | xxxxxxxx | | 37 | | | PPS +-------+ NMEA | | | | 38 | | +-----| GPS |-------+ | +--------+ 39 | | | (opt) | | 40 | | +-------+ | 41 | | | 42 | | Gateway | 43 | +- - - - - - - - - - - - - - - -+ 44 | 45 | __Concentrator__: radio RX/TX board, based on Semtech multichannel modems 46 | (SX130x), transceivers (SX135x) and/or low-power stand-alone modems (SX127x). 47 | 48 | __Host__: embedded computer on which the packet forwarder is run. Drives the 49 | concentrator through a SPI link. 50 | 51 | __GPS__: GNSS (GPS, Galileo, GLONASS, etc) receiver with a "1 Pulse Per Second" 52 | output and a serial link to the host to send NMEA frames containing time and 53 | geographical coordinates data. Optional. 54 | 55 | __Gateway__: a device composed of at least one radio concentrator, a host, some 56 | network connection to the internet or a private network (Ethernet, 3G, Wifi, 57 | microwave link), and optionally a GPS receiver for synchronization. 58 | 59 | __Server__: an abstract computer that will process the RF packets received and 60 | forwarded by the gateway, and issue RF packets in response that the gateway 61 | will have to emit. 62 | 63 | It is assumed that the gateway can be behind a NAT or a firewall stopping any 64 | incoming connection. 65 | It is assumed that the server has an static IP address (or an address solvable 66 | through a DNS service) and is able to receive incoming connections on a 67 | specific port. 68 | 69 | 70 | 3. Upstream protocol 71 | --------------------- 72 | 73 | ### 3.1. Sequence diagram ### 74 | 75 | +---------+ +---------+ 76 | | Gateway | | Server | 77 | +---------+ +---------+ 78 | | -----------------------------------\ | 79 | |-| When 1-N RF packets are received | | 80 | | ------------------------------------ | 81 | | | 82 | | PUSH_DATA (token X, GW MAC, JSON payload) | 83 | |------------------------------------------------------------->| 84 | | | 85 | | PUSH_ACK (token X) | 86 | |<-------------------------------------------------------------| 87 | | ------------------------------\ | 88 | | | process packets *after* ack |-| 89 | | ------------------------------- | 90 | | | 91 | 92 | ### 3.2. PUSH_DATA packet ### 93 | 94 | That packet type is used by the gateway mainly to forward the RF packets 95 | received, and associated metadata, to the server. 96 | 97 | Bytes | Function 98 | :------:|--------------------------------------------------------------------- 99 | 0 | protocol version = 2 100 | 1-2 | random token 101 | 3 | PUSH_DATA identifier 0x00 102 | 4-11 | Gateway unique identifier (MAC address) 103 | 12-end | JSON object, starting with {, ending with }, see section 4 104 | 105 | ### 3.3. PUSH_ACK packet ### 106 | 107 | That packet type is used by the server to acknowledge immediately all the 108 | PUSH_DATA packets received. 109 | 110 | Bytes | Function 111 | :------:|--------------------------------------------------------------------- 112 | 0 | protocol version = 2 113 | 1-2 | same token as the PUSH_DATA packet to acknowledge 114 | 3 | PUSH_ACK identifier 0x01 115 | 116 | 117 | 4. Upstream JSON data structure 118 | -------------------------------- 119 | 120 | The root object can contain an array named "rxpk": 121 | 122 | ``` json 123 | { 124 | "rxpk":[ {...}, ...] 125 | } 126 | ``` 127 | 128 | That array contains at least one JSON object, each object contain a RF packet 129 | and associated metadata with the following fields: 130 | 131 | Name | Type | Function 132 | :----:|:------:|-------------------------------------------------------------- 133 | time | string | UTC time of pkt RX, us precision, ISO 8601 'compact' format 134 | tmst | number | Internal timestamp of "RX finished" event (32b unsigned) 135 | freq | number | RX central frequency in MHz (unsigned float, Hz precision) 136 | chan | number | Concentrator "IF" channel used for RX (unsigned integer) 137 | rfch | number | Concentrator "RF chain" used for RX (unsigned integer) 138 | stat | number | CRC status: 1 = OK, -1 = fail, 0 = no CRC 139 | modu | string | Modulation identifier "LORA" or "FSK" 140 | datr | string | LoRa datarate identifier (eg. SF12BW500) 141 | datr | number | FSK datarate (unsigned, in bits per second) 142 | codr | string | LoRa ECC coding rate identifier 143 | rssi | number | RSSI in dBm (signed integer, 1 dB precision) 144 | lsnr | number | Lora SNR ratio in dB (signed float, 0.1 dB precision) 145 | size | number | RF packet payload size in bytes (unsigned integer) 146 | data | string | Base64 encoded RF packet payload, padded 147 | 148 | Example (white-spaces, indentation and newlines added for readability): 149 | 150 | ``` json 151 | {"rxpk":[ 152 | { 153 | "time":"2013-03-31T16:21:17.528002Z", 154 | "tmst":3512348611, 155 | "chan":2, 156 | "rfch":0, 157 | "freq":866.349812, 158 | "stat":1, 159 | "modu":"LORA", 160 | "datr":"SF7BW125", 161 | "codr":"4/6", 162 | "rssi":-35, 163 | "lsnr":5.1, 164 | "size":32, 165 | "data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84" 166 | },{ 167 | "time":"2013-03-31T16:21:17.530974Z", 168 | "tmst":3512348514, 169 | "chan":9, 170 | "rfch":1, 171 | "freq":869.1, 172 | "stat":1, 173 | "modu":"FSK", 174 | "datr":50000, 175 | "rssi":-75, 176 | "size":16, 177 | "data":"VEVTVF9QQUNLRVRfMTIzNA==" 178 | },{ 179 | "time":"2013-03-31T16:21:17.532038Z", 180 | "tmst":3316387610, 181 | "chan":0, 182 | "rfch":0, 183 | "freq":863.00981, 184 | "stat":1, 185 | "modu":"LORA", 186 | "datr":"SF10BW125", 187 | "codr":"4/7", 188 | "rssi":-38, 189 | "lsnr":5.5, 190 | "size":32, 191 | "data":"ysgRl452xNLep9S1NTIg2lomKDxUgn3DJ7DE+b00Ass" 192 | } 193 | ]} 194 | ``` 195 | 196 | The root object can also contain an object named "stat" : 197 | 198 | ``` json 199 | { 200 | "rxpk":[ {...}, ...], 201 | "stat":{...} 202 | } 203 | ``` 204 | 205 | It is possible for a packet to contain no "rxpk" array but a "stat" object. 206 | 207 | ``` json 208 | { 209 | "stat":{...} 210 | } 211 | ``` 212 | 213 | That object contains the status of the gateway, with the following fields: 214 | 215 | Name | Type | Function 216 | :----:|:------:|-------------------------------------------------------------- 217 | time | string | UTC 'system' time of the gateway, ISO 8601 'expanded' format 218 | lati | number | GPS latitude of the gateway in degree (float, N is +) 219 | long | number | GPS latitude of the gateway in degree (float, E is +) 220 | alti | number | GPS altitude of the gateway in meter RX (integer) 221 | rxnb | number | Number of radio packets received (unsigned integer) 222 | rxok | number | Number of radio packets received with a valid PHY CRC 223 | rxfw | number | Number of radio packets forwarded (unsigned integer) 224 | ackr | number | Percentage of upstream datagrams that were acknowledged 225 | dwnb | number | Number of downlink datagrams received (unsigned integer) 226 | txnb | number | Number of packets emitted (unsigned integer) 227 | 228 | Example (white-spaces, indentation and newlines added for readability): 229 | 230 | ``` json 231 | {"stat":{ 232 | "time":"2014-01-12 08:59:28 GMT", 233 | "lati":46.24000, 234 | "long":3.25230, 235 | "alti":145, 236 | "rxnb":2, 237 | "rxok":2, 238 | "rxfw":2, 239 | "ackr":100.0, 240 | "dwnb":2, 241 | "txnb":2 242 | }} 243 | ``` 244 | 245 | 246 | 5. Downstream protocol 247 | ----------------------- 248 | 249 | ### 5.1. Sequence diagram ### 250 | 251 | +---------+ +---------+ 252 | | Gateway | | Server | 253 | +---------+ +---------+ 254 | | -----------------------------------\ | 255 | |-| Every N seconds (keepalive time) | | 256 | | ------------------------------------ | 257 | | | 258 | | PULL_DATA (token Y, MAC@) | 259 | |------------------------------------------------------------->| 260 | | | 261 | | PULL_ACK (token Y) | 262 | |<-------------------------------------------------------------| 263 | | | 264 | 265 | +---------+ +---------+ 266 | | Gateway | | Server | 267 | +---------+ +---------+ 268 | | ------------------------------------------------------\ | 269 | | | Anytime after first PULL_DATA for each packet to TX |-| 270 | | ------------------------------------------------------- | 271 | | | 272 | | PULL_RESP (token Z, JSON payload) | 273 | |<-------------------------------------------------------------| 274 | | | 275 | | TX_ACK (token Z, JSON payload) | 276 | |------------------------------------------------------------->| 277 | 278 | ### 5.2. PULL_DATA packet ### 279 | 280 | That packet type is used by the gateway to poll data from the server. 281 | 282 | This data exchange is initialized by the gateway because it might be 283 | impossible for the server to send packets to the gateway if the gateway is 284 | behind a NAT. 285 | 286 | When the gateway initialize the exchange, the network route towards the 287 | server will open and will allow for packets to flow both directions. 288 | The gateway must periodically send PULL_DATA packets to be sure the network 289 | route stays open for the server to be used at any time. 290 | 291 | Bytes | Function 292 | :------:|--------------------------------------------------------------------- 293 | 0 | protocol version = 2 294 | 1-2 | random token 295 | 3 | PULL_DATA identifier 0x02 296 | 4-11 | Gateway unique identifier (MAC address) 297 | 298 | ### 5.3. PULL_ACK packet ### 299 | 300 | That packet type is used by the server to confirm that the network route is 301 | open and that the server can send PULL_RESP packets at any time. 302 | 303 | Bytes | Function 304 | :------:|--------------------------------------------------------------------- 305 | 0 | protocol version = 2 306 | 1-2 | same token as the PULL_DATA packet to acknowledge 307 | 3 | PULL_ACK identifier 0x04 308 | 309 | ### 5.4. PULL_RESP packet ### 310 | 311 | That packet type is used by the server to send RF packets and associated 312 | metadata that will have to be emitted by the gateway. 313 | 314 | Bytes | Function 315 | :------:|--------------------------------------------------------------------- 316 | 0 | protocol version = 2 317 | 1-2 | random token 318 | 3 | PULL_RESP identifier 0x03 319 | 4-end | JSON object, starting with {, ending with }, see section 6 320 | 321 | ### 5.5. TX_ACK packet ### 322 | 323 | That packet type is used by the gateway to send a feedback to the server 324 | to inform if a downlink request has been accepted or rejected by the gateway. 325 | The datagram may optionnaly contain a JSON string to give more details on 326 | acknoledge. If no JSON is present (empty string), this means than no error 327 | occured. 328 | 329 | Bytes | Function 330 | :------:|--------------------------------------------------------------------- 331 | 0 | protocol version = 2 332 | 1-2 | same token as the PULL_RESP packet to acknowledge 333 | 3 | TX_ACK identifier 0x05 334 | 4-11 | Gateway unique identifier (MAC address) 335 | 12-end | [optional] JSON object, starting with {, ending with }, see section 6 336 | 337 | 6. Downstream JSON data structure 338 | ---------------------------------- 339 | 340 | The root object of PULL_RESP packet must contain an object named "txpk": 341 | 342 | ``` json 343 | { 344 | "txpk": {...} 345 | } 346 | ``` 347 | 348 | That object contain a RF packet to be emitted and associated metadata with the following fields: 349 | 350 | Name | Type | Function 351 | :----:|:------:|-------------------------------------------------------------- 352 | imme | bool | Send packet immediately (will ignore tmst & time) 353 | tmst | number | Send packet on a certain timestamp value (will ignore time) 354 | time | string | Send packet at a certain time (GPS synchronization required) 355 | freq | number | TX central frequency in MHz (unsigned float, Hz precision) 356 | rfch | number | Concentrator "RF chain" used for TX (unsigned integer) 357 | powe | number | TX output power in dBm (unsigned integer, dBm precision) 358 | modu | string | Modulation identifier "LORA" or "FSK" 359 | datr | string | LoRa datarate identifier (eg. SF12BW500) 360 | datr | number | FSK datarate (unsigned, in bits per second) 361 | codr | string | LoRa ECC coding rate identifier 362 | fdev | number | FSK frequency deviation (unsigned integer, in Hz) 363 | ipol | bool | Lora modulation polarization inversion 364 | prea | number | RF preamble size (unsigned integer) 365 | size | number | RF packet payload size in bytes (unsigned integer) 366 | data | string | Base64 encoded RF packet payload, padding optional 367 | ncrc | bool | If true, disable the CRC of the physical layer (optional) 368 | 369 | Most fields are optional. 370 | If a field is omitted, default parameters will be used. 371 | 372 | Examples (white-spaces, indentation and newlines added for readability): 373 | 374 | ``` json 375 | {"txpk":{ 376 | "imme":true, 377 | "freq":864.123456, 378 | "rfch":0, 379 | "powe":14, 380 | "modu":"LORA", 381 | "datr":"SF11BW125", 382 | "codr":"4/6", 383 | "ipol":false, 384 | "size":32, 385 | "data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v" 386 | }} 387 | ``` 388 | 389 | ``` json 390 | {"txpk":{ 391 | "imme":true, 392 | "freq":861.3, 393 | "rfch":0, 394 | "powe":12, 395 | "modu":"FSK", 396 | "datr":50000, 397 | "fdev":3000, 398 | "size":32, 399 | "data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v" 400 | }} 401 | ``` 402 | 403 | The root object of TX_ACK packet must contain an object named "txpk_ack": 404 | 405 | ``` json 406 | { 407 | "txpk_ack": {...} 408 | } 409 | ``` 410 | 411 | That object contain status information concerning the associated PULL_RESP packet. 412 | 413 | Name | Type | Function 414 | :----:|:------:|------------------------------------------------------------------------------ 415 | error | string | Indication about success or type of failure that occured for downlink request. 416 | 417 | The possible values of "error" field are: 418 | 419 | Value | Definition 420 | :-----------------:|--------------------------------------------------------------------- 421 | NONE | Packet has been programmed for downlink 422 | TOO_LATE | Rejected because it was already too late to program this packet for downlink 423 | TOO_EARLY | Rejected because downlink packet timestamp is too much in advance 424 | COLLISION_PACKET | Rejected because there was already a packet programmed in requested timeframe 425 | COLLISION_BEACON | Rejected because there was already a beacon planned in requested timeframe 426 | TX_FREQ | Rejected because requested frequency is not supported by TX RF chain 427 | TX_POWER | Rejected because requested power is not supported by gateway 428 | GPS_UNLOCKED | Rejected because GPS is unlocked, so GPS timestamp cannot be used 429 | 430 | Examples (white-spaces, indentation and newlines added for readability): 431 | 432 | ``` json 433 | {"txpk_ack":{ 434 | "error":"COLLISION_PACKET" 435 | }} 436 | ``` 437 | 438 | 7. Revisions 439 | ------------- 440 | 441 | ### v1.3 ### 442 | 443 | * Added downlink feedback from gateway to server (PULL_RESP -> TX_ACK) 444 | 445 | ### v1.2 ### 446 | 447 | * Added value of FSK bitrate for upstream. 448 | * Added parameters for FSK bitrate and frequency deviation for downstream. 449 | 450 | ### v1.1 ### 451 | 452 | * Added syntax for status report JSON object on upstream. 453 | 454 | ### v1.0 ### 455 | 456 | * Initial version. 457 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /daemon/run_pkt_fwd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | usage() { 4 | echo missing working root directory path 5 | echo usage: $0 [start/restart/check/stop] PATH 6 | echo example: $0 check /home/pi/lora-net/ 7 | exit 8 | } 9 | 10 | # 11 | # Check input parameters 12 | # 13 | if [ -z "$2" ]; then 14 | usage 15 | fi 16 | 17 | # 18 | # Global variables 19 | # 20 | DIR=$2 21 | 22 | # 23 | # Functions 24 | # 25 | start() { 26 | echo "Start packet forwarder..." 27 | cd $DIR/lora_gateway 28 | ./reset_lgw.sh start 29 | cd $DIR/packet_forwarder/lora_pkt_fwd 30 | ./lora_pkt_fwd 31 | } 32 | 33 | stop() { 34 | echo "Stop packet forwarder" 35 | sudo killall lora_pkt_fwd 36 | } 37 | 38 | check() { 39 | ps -ef | grep -v grep | grep -w 'lora_pkt_fwd' > /dev/null 40 | result=$? 41 | if [ "${result}" -eq "0" ] ; then 42 | echo "`date`: lora_pkt_fwd is already running" 43 | exit 0 44 | fi 45 | start 46 | } 47 | 48 | # 49 | # Main 50 | # 51 | 52 | case "$1" in 53 | start) 54 | start 55 | ;; 56 | stop) 57 | stop 58 | ;; 59 | restart) 60 | stop 61 | start 62 | ;; 63 | check) 64 | check 65 | ;; 66 | *) 67 | usage 68 | exit 1 69 | ;; 70 | esac 71 | 72 | exit 0 73 | 74 | 75 | -------------------------------------------------------------------------------- /lora_pkt_fwd/Makefile: -------------------------------------------------------------------------------- 1 | ### Application-specific constants 2 | 3 | APP_NAME := lora_pkt_fwd 4 | 5 | ### Environment constants 6 | 7 | LGW_PATH ?= ../../lora_gateway/libloragw 8 | ARCH ?= 9 | CROSS_COMPILE ?= 10 | 11 | OBJDIR = obj 12 | INCLUDES = $(wildcard inc/*.h) 13 | 14 | ### External constant definitions 15 | # must get library build option to know if mpsse must be linked or not 16 | 17 | include $(LGW_PATH)/library.cfg 18 | RELEASE_VERSION := `cat ../VERSION` 19 | 20 | ### Constant symbols 21 | 22 | CC := $(CROSS_COMPILE)gcc 23 | AR := $(CROSS_COMPILE)ar 24 | 25 | CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I. 26 | VFLAG := -D VERSION_STRING="\"$(RELEASE_VERSION)\"" 27 | 28 | ### Constants for Lora concentrator HAL library 29 | # List the library sub-modules that are used by the application 30 | 31 | LGW_INC = 32 | ifneq ($(wildcard $(LGW_PATH)/inc/config.h),) 33 | # only for HAL version 1.3 and beyond 34 | LGW_INC += $(LGW_PATH)/inc/config.h 35 | endif 36 | LGW_INC += $(LGW_PATH)/inc/loragw_hal.h 37 | 38 | ### Linking options 39 | 40 | LIBS := -lloragw -lrt -lpthread -lm 41 | 42 | ### General build targets 43 | 44 | all: $(APP_NAME) 45 | 46 | clean: 47 | rm -f $(OBJDIR)/*.o 48 | rm -f $(APP_NAME) 49 | 50 | ### Sub-modules compilation 51 | 52 | $(OBJDIR): 53 | mkdir -p $(OBJDIR) 54 | 55 | $(OBJDIR)/%.o: src/%.c $(INCLUDES) | $(OBJDIR) 56 | $(CC) -c $(CFLAGS) -I$(LGW_PATH)/inc $< -o $@ 57 | 58 | ### Main program compilation and assembly 59 | 60 | $(OBJDIR)/$(APP_NAME).o: src/$(APP_NAME).c $(LGW_INC) $(INCLUDES) | $(OBJDIR) 61 | $(CC) -c $(CFLAGS) $(VFLAG) -I$(LGW_PATH)/inc $< -o $@ 62 | 63 | $(APP_NAME): $(OBJDIR)/$(APP_NAME).o $(LGW_PATH)/libloragw.a $(OBJDIR)/parson.o $(OBJDIR)/base64.o $(OBJDIR)/jitqueue.o $(OBJDIR)/timersync.o 64 | $(CC) -L$(LGW_PATH) $< $(OBJDIR)/parson.o $(OBJDIR)/base64.o $(OBJDIR)/jitqueue.o $(OBJDIR)/timersync.o -o $@ $(LIBS) 65 | 66 | ### EOF 67 | -------------------------------------------------------------------------------- /lora_pkt_fwd/cfg/global_conf_PicoV1p0_EU.json: -------------------------------------------------------------------------------- 1 | { 2 | "SX1301_conf": { 3 | "lorawan_public": true, 4 | "clksrc": 1, /* radio_1 provides clock to concentrator */ 5 | "antenna_gain": 0, /* antenna gain, in dBi */ 6 | "radio_0": { 7 | "enable": true, 8 | "type": "SX1257", 9 | "freq": 867500000, 10 | "rssi_offset": -164.0, 11 | "tx_enable": true, 12 | "tx_freq_min": 863000000, 13 | "tx_freq_max": 870000000 14 | }, 15 | "radio_1": { 16 | "enable": true, 17 | "type": "SX1257", 18 | "freq": 868500000, 19 | "rssi_offset": -164.0, 20 | "tx_enable": false 21 | }, 22 | "chan_multiSF_0": { 23 | /* Lora MAC channel, 125kHz, all SF, 868.1 MHz */ 24 | "enable": true, 25 | "radio": 1, 26 | "if": -400000 27 | }, 28 | "chan_multiSF_1": { 29 | /* Lora MAC channel, 125kHz, all SF, 868.3 MHz */ 30 | "enable": true, 31 | "radio": 1, 32 | "if": -200000 33 | }, 34 | "chan_multiSF_2": { 35 | /* Lora MAC channel, 125kHz, all SF, 868.5 MHz */ 36 | "enable": true, 37 | "radio": 1, 38 | "if": 0 39 | }, 40 | "chan_multiSF_3": { 41 | /* Lora MAC channel, 125kHz, all SF, 867.1 MHz */ 42 | "enable": true, 43 | "radio": 0, 44 | "if": -400000 45 | }, 46 | "chan_multiSF_4": { 47 | /* Lora MAC channel, 125kHz, all SF, 867.3 MHz */ 48 | "enable": true, 49 | "radio": 0, 50 | "if": -200000 51 | }, 52 | "chan_multiSF_5": { 53 | /* Lora MAC channel, 125kHz, all SF, 867.5 MHz */ 54 | "enable": true, 55 | "radio": 0, 56 | "if": 0 57 | }, 58 | "chan_multiSF_6": { 59 | /* Lora MAC channel, 125kHz, all SF, 867.7 MHz */ 60 | "enable": true, 61 | "radio": 0, 62 | "if": 200000 63 | }, 64 | "chan_multiSF_7": { 65 | /* Lora MAC channel, 125kHz, all SF, 867.9 MHz */ 66 | "enable": true, 67 | "radio": 0, 68 | "if": 400000 69 | }, 70 | "chan_Lora_std": { 71 | /* Lora MAC channel, 250kHz, SF7, 868.3 MHz */ 72 | "enable": true, 73 | "radio": 1, 74 | "if": -200000, 75 | "bandwidth": 250000, 76 | "spread_factor": 7 77 | }, 78 | "chan_FSK": { 79 | /* FSK 50kbps channel, 868.8 MHz */ 80 | "enable": true, 81 | "radio": 1, 82 | "if": 300000, 83 | "bandwidth": 125000, 84 | "datarate": 50000 85 | }, 86 | "tx_lut_0": { 87 | /* TX gain table, index 0 */ 88 | "pa_gain": 0, 89 | "mix_gain": 5, 90 | "rf_power": 9, 91 | "dig_gain": 3 92 | }, 93 | "tx_lut_1": { 94 | /* TX gain table, index 1 */ 95 | "pa_gain": 0, 96 | "mix_gain": 5, 97 | "rf_power": 9, 98 | "dig_gain": 3 99 | }, 100 | "tx_lut_2": { 101 | /* TX gain table, index 2 */ 102 | "pa_gain": 0, 103 | "mix_gain": 5, 104 | "rf_power": 9, 105 | "dig_gain": 3 106 | }, 107 | "tx_lut_3": { 108 | /* TX gain table, index 3 */ 109 | "pa_gain": 0, 110 | "mix_gain": 5, 111 | "rf_power": 9, 112 | "dig_gain": 3 113 | }, 114 | "tx_lut_4": { 115 | /* TX gain table, index 4 */ 116 | "pa_gain": 0, 117 | "mix_gain": 5, 118 | "rf_power": 9, 119 | "dig_gain": 3 120 | }, 121 | "tx_lut_5": { 122 | /* TX gain table, index 5 */ 123 | "pa_gain": 0, 124 | "mix_gain": 5, 125 | "rf_power": 9, 126 | "dig_gain": 3 127 | }, 128 | "tx_lut_6": { 129 | /* TX gain table, index 6 */ 130 | "pa_gain": 0, 131 | "mix_gain": 5, 132 | "rf_power": 9, 133 | "dig_gain": 3 134 | }, 135 | "tx_lut_7": { 136 | /* TX gain table, index 7 */ 137 | "pa_gain": 0, 138 | "mix_gain": 6, 139 | "rf_power": 11, 140 | "dig_gain": 3 141 | }, 142 | "tx_lut_8": { 143 | /* TX gain table, index 8 */ 144 | "pa_gain": 0, 145 | "mix_gain": 5, 146 | "rf_power": 13, 147 | "dig_gain": 2 148 | }, 149 | "tx_lut_9": { 150 | /* TX gain table, index 9 */ 151 | "pa_gain": 0, 152 | "mix_gain": 8, 153 | "rf_power": 14, 154 | "dig_gain": 3 155 | }, 156 | "tx_lut_10": { 157 | /* TX gain table, index 10 */ 158 | "pa_gain": 0, 159 | "mix_gain": 6, 160 | "rf_power": 15, 161 | "dig_gain": 2 162 | }, 163 | "tx_lut_11": { 164 | /* TX gain table, index 11 */ 165 | "pa_gain": 0, 166 | "mix_gain": 6, 167 | "rf_power": 16, 168 | "dig_gain": 1 169 | }, 170 | "tx_lut_12": { 171 | /* TX gain table, index 12 */ 172 | "pa_gain": 0, 173 | "mix_gain": 9, 174 | "rf_power": 17, 175 | "dig_gain": 3 176 | }, 177 | "tx_lut_13": { 178 | /* TX gain table, index 13 */ 179 | "pa_gain": 0, 180 | "mix_gain": 10, 181 | "rf_power": 18, 182 | "dig_gain": 3 183 | }, 184 | "tx_lut_14": { 185 | /* TX gain table, index 14 */ 186 | "pa_gain": 0, 187 | "mix_gain": 11, 188 | "rf_power": 19, 189 | "dig_gain": 3 190 | }, 191 | "tx_lut_15": { 192 | /* TX gain table, index 15 */ 193 | "pa_gain": 0, 194 | "mix_gain": 12, 195 | "rf_power": 20, 196 | "dig_gain": 3 197 | } 198 | }, 199 | 200 | "gateway_conf": { 201 | "gateway_ID": "AA555A0000240409", 202 | /* change with default server address/ports, or overwrite in local_conf.json */ 203 | "server_address": "localhost", 204 | "serv_port_up": 1680, 205 | "serv_port_down": 1680, 206 | /* adjust the following parameters for your network */ 207 | "keepalive_interval": 10, 208 | "stat_interval": 30, 209 | "push_timeout_ms": 100, 210 | /* forward only valid packets */ 211 | "forward_crc_valid": true, 212 | "forward_crc_error": false, 213 | "forward_crc_disabled": false 214 | } 215 | } 216 | 217 | -------------------------------------------------------------------------------- /lora_pkt_fwd/cfg/global_conf_PicoV1p0_US.json: -------------------------------------------------------------------------------- 1 | { 2 | "SX1301_conf": { 3 | "lorawan_public": true, 4 | "clksrc": 1, /* radio_1 provides clock to concentrator */ 5 | "antenna_gain": 0, /* antenna gain, in dBi */ 6 | "radio_0": { 7 | "enable": true, 8 | "type": "SX1257", 9 | "freq": 902700000, 10 | "rssi_offset": -164.0, 11 | "tx_enable": true, 12 | "tx_freq_min": 902000000, 13 | "tx_freq_max": 928000000 14 | }, 15 | "radio_1": { 16 | "enable": true, 17 | "type": "SX1257", 18 | "freq": 903400000, 19 | "rssi_offset": -164.0, 20 | "tx_enable": false 21 | }, 22 | "chan_multiSF_0": { 23 | /* Lora MAC channel, 125kHz, all SF, 902.3 MHz */ 24 | "enable": true, 25 | "radio": 0, 26 | "if": -400000 27 | }, 28 | "chan_multiSF_1": { 29 | /* Lora MAC channel, 125kHz, all SF, 902.5 MHz */ 30 | "enable": true, 31 | "radio": 0, 32 | "if": -200000 33 | }, 34 | "chan_multiSF_2": { 35 | /* Lora MAC channel, 125kHz, all SF, 902.7 MHz */ 36 | "enable": true, 37 | "radio": 0, 38 | "if": 0 39 | }, 40 | "chan_multiSF_3": { 41 | /* Lora MAC channel, 125kHz, all SF, 902.9 MHz */ 42 | "enable": true, 43 | "radio": 0, 44 | "if": 200000 45 | }, 46 | "chan_multiSF_4": { 47 | /* Lora MAC channel, 125kHz, all SF, 903.1 MHz */ 48 | "enable": true, 49 | "radio": 1, 50 | "if": -300000 51 | }, 52 | "chan_multiSF_5": { 53 | /* Lora MAC channel, 125kHz, all SF, 903.3 MHz */ 54 | "enable": true, 55 | "radio": 1, 56 | "if": -100000 57 | }, 58 | "chan_multiSF_6": { 59 | /* Lora MAC channel, 125kHz, all SF, 903.5 MHz */ 60 | "enable": true, 61 | "radio": 1, 62 | "if": 100000 63 | }, 64 | "chan_multiSF_7": { 65 | /* Lora MAC channel, 125kHz, all SF, 903.7 MHz */ 66 | "enable": true, 67 | "radio": 1, 68 | "if": 300000 69 | }, 70 | "chan_Lora_std": { 71 | /* Lora MAC channel, 500kHz, SF8, 903.0 MHz */ 72 | "enable": true, 73 | "radio": 0, 74 | "if": 300000, 75 | "bandwidth": 500000, 76 | "spread_factor": 8 77 | }, 78 | "chan_FSK": { 79 | /* FSK 100kbps channel, 903.0 MHz */ 80 | "enable": false, 81 | "radio": 0, 82 | "if": 300000, 83 | "bandwidth": 250000, 84 | "datarate": 100000 85 | }, 86 | "tx_lut_0": { 87 | /* TX gain table, index 0 */ 88 | "pa_gain": 0, 89 | "mix_gain": 5, 90 | "rf_power": 9, 91 | "dig_gain": 3 92 | }, 93 | "tx_lut_1": { 94 | /* TX gain table, index 1 */ 95 | "pa_gain": 0, 96 | "mix_gain": 5, 97 | "rf_power": 9, 98 | "dig_gain": 3 99 | }, 100 | "tx_lut_2": { 101 | /* TX gain table, index 2 */ 102 | "pa_gain": 0, 103 | "mix_gain": 5, 104 | "rf_power": 9, 105 | "dig_gain": 3 106 | }, 107 | "tx_lut_3": { 108 | /* TX gain table, index 3 */ 109 | "pa_gain": 0, 110 | "mix_gain": 5, 111 | "rf_power": 9, 112 | "dig_gain": 3 113 | }, 114 | "tx_lut_4": { 115 | /* TX gain table, index 4 */ 116 | "pa_gain": 0, 117 | "mix_gain": 5, 118 | "rf_power": 9, 119 | "dig_gain": 3 120 | }, 121 | "tx_lut_5": { 122 | /* TX gain table, index 5 */ 123 | "pa_gain": 0, 124 | "mix_gain": 5, 125 | "rf_power": 9, 126 | "dig_gain": 3 127 | }, 128 | "tx_lut_6": { 129 | /* TX gain table, index 6 */ 130 | "pa_gain": 0, 131 | "mix_gain": 6, 132 | "rf_power": 11, 133 | "dig_gain": 3 134 | }, 135 | "tx_lut_7": { 136 | /* TX gain table, index 7 */ 137 | "pa_gain": 0, 138 | "mix_gain": 5, 139 | "rf_power": 13, 140 | "dig_gain": 2 141 | }, 142 | "tx_lut_8": { 143 | /* TX gain table, index 8 */ 144 | "pa_gain": 0, 145 | "mix_gain": 8, 146 | "rf_power": 14, 147 | "dig_gain": 3 148 | }, 149 | "tx_lut_9": { 150 | /* TX gain table, index 9 */ 151 | "pa_gain": 0, 152 | "mix_gain": 10, 153 | "rf_power": 15, 154 | "dig_gain": 2 155 | }, 156 | "tx_lut_10": { 157 | /* TX gain table, index 10 */ 158 | "pa_gain": 0, 159 | "mix_gain": 6, 160 | "rf_power": 16, 161 | "dig_gain": 1 162 | }, 163 | "tx_lut_11": { 164 | /* TX gain table, index 11 */ 165 | "pa_gain": 0, 166 | "mix_gain": 9, 167 | "rf_power": 17, 168 | "dig_gain": 3 169 | }, 170 | "tx_lut_12": { 171 | /* TX gain table, index 12 */ 172 | "pa_gain": 0, 173 | "mix_gain": 10, 174 | "rf_power": 18, 175 | "dig_gain": 3 176 | }, 177 | "tx_lut_13": { 178 | /* TX gain table, index 13 */ 179 | "pa_gain": 0, 180 | "mix_gain": 11, 181 | "rf_power": 19, 182 | "dig_gain": 3 183 | }, 184 | "tx_lut_14": { 185 | /* TX gain table, index 14 */ 186 | "pa_gain": 0, 187 | "mix_gain": 12, 188 | "rf_power": 20, 189 | "dig_gain": 3 190 | }, 191 | "tx_lut_15": { 192 | /* TX gain table, index 15 */ 193 | "pa_gain": 0, 194 | "mix_gain": 13, 195 | "rf_power": 21, 196 | "dig_gain": 2 197 | } 198 | }, 199 | 200 | "gateway_conf": { 201 | "gateway_ID": "AA555A0000240409", 202 | /* change with default server address/ports, or overwrite in local_conf.json */ 203 | "server_address": "localhost", 204 | "serv_port_up": 1680, 205 | "serv_port_down": 1680, 206 | /* adjust the following parameters for your network */ 207 | "keepalive_interval": 10, 208 | "stat_interval": 30, 209 | "push_timeout_ms": 100, 210 | /* forward only valid packets */ 211 | "forward_crc_valid": true, 212 | "forward_crc_error": false, 213 | "forward_crc_disabled": false 214 | } 215 | } 216 | 217 | -------------------------------------------------------------------------------- /lora_pkt_fwd/global_conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "SX1301_conf": { 3 | "lorawan_public": true, 4 | "clksrc": 1, /* radio_1 provides clock to concentrator */ 5 | "antenna_gain": 0, /* antenna gain, in dBi */ 6 | "radio_0": { 7 | "enable": true, 8 | "type": "SX1257", 9 | "freq": 867500000, 10 | "rssi_offset": -164.0, 11 | "tx_enable": true, 12 | "tx_freq_min": 863000000, 13 | "tx_freq_max": 870000000 14 | }, 15 | "radio_1": { 16 | "enable": true, 17 | "type": "SX1257", 18 | "freq": 868500000, 19 | "rssi_offset": -164.0, 20 | "tx_enable": false 21 | }, 22 | "chan_multiSF_0": { 23 | /* Lora MAC channel, 125kHz, all SF, 868.1 MHz */ 24 | "enable": true, 25 | "radio": 1, 26 | "if": -400000 27 | }, 28 | "chan_multiSF_1": { 29 | /* Lora MAC channel, 125kHz, all SF, 868.3 MHz */ 30 | "enable": true, 31 | "radio": 1, 32 | "if": -200000 33 | }, 34 | "chan_multiSF_2": { 35 | /* Lora MAC channel, 125kHz, all SF, 868.5 MHz */ 36 | "enable": true, 37 | "radio": 1, 38 | "if": 0 39 | }, 40 | "chan_multiSF_3": { 41 | /* Lora MAC channel, 125kHz, all SF, 867.1 MHz */ 42 | "enable": true, 43 | "radio": 0, 44 | "if": -400000 45 | }, 46 | "chan_multiSF_4": { 47 | /* Lora MAC channel, 125kHz, all SF, 867.3 MHz */ 48 | "enable": true, 49 | "radio": 0, 50 | "if": -200000 51 | }, 52 | "chan_multiSF_5": { 53 | /* Lora MAC channel, 125kHz, all SF, 867.5 MHz */ 54 | "enable": true, 55 | "radio": 0, 56 | "if": 0 57 | }, 58 | "chan_multiSF_6": { 59 | /* Lora MAC channel, 125kHz, all SF, 867.7 MHz */ 60 | "enable": true, 61 | "radio": 0, 62 | "if": 200000 63 | }, 64 | "chan_multiSF_7": { 65 | /* Lora MAC channel, 125kHz, all SF, 867.9 MHz */ 66 | "enable": true, 67 | "radio": 0, 68 | "if": 400000 69 | }, 70 | "chan_Lora_std": { 71 | /* Lora MAC channel, 250kHz, SF7, 868.3 MHz */ 72 | "enable": true, 73 | "radio": 1, 74 | "if": -200000, 75 | "bandwidth": 250000, 76 | "spread_factor": 7 77 | }, 78 | "chan_FSK": { 79 | /* FSK 50kbps channel, 868.8 MHz */ 80 | "enable": true, 81 | "radio": 1, 82 | "if": 300000, 83 | "bandwidth": 125000, 84 | "datarate": 50000 85 | }, 86 | "tx_lut_0": { 87 | /* TX gain table, index 0 */ 88 | "pa_gain": 0, 89 | "mix_gain": 5, 90 | "rf_power": 9, 91 | "dig_gain": 3 92 | }, 93 | "tx_lut_1": { 94 | /* TX gain table, index 1 */ 95 | "pa_gain": 0, 96 | "mix_gain": 5, 97 | "rf_power": 9, 98 | "dig_gain": 3 99 | }, 100 | "tx_lut_2": { 101 | /* TX gain table, index 2 */ 102 | "pa_gain": 0, 103 | "mix_gain": 5, 104 | "rf_power": 9, 105 | "dig_gain": 3 106 | }, 107 | "tx_lut_3": { 108 | /* TX gain table, index 3 */ 109 | "pa_gain": 0, 110 | "mix_gain": 5, 111 | "rf_power": 9, 112 | "dig_gain": 3 113 | }, 114 | "tx_lut_4": { 115 | /* TX gain table, index 4 */ 116 | "pa_gain": 0, 117 | "mix_gain": 5, 118 | "rf_power": 9, 119 | "dig_gain": 3 120 | }, 121 | "tx_lut_5": { 122 | /* TX gain table, index 5 */ 123 | "pa_gain": 0, 124 | "mix_gain": 5, 125 | "rf_power": 9, 126 | "dig_gain": 3 127 | }, 128 | "tx_lut_6": { 129 | /* TX gain table, index 6 */ 130 | "pa_gain": 0, 131 | "mix_gain": 5, 132 | "rf_power": 9, 133 | "dig_gain": 3 134 | }, 135 | "tx_lut_7": { 136 | /* TX gain table, index 7 */ 137 | "pa_gain": 0, 138 | "mix_gain": 6, 139 | "rf_power": 11, 140 | "dig_gain": 3 141 | }, 142 | "tx_lut_8": { 143 | /* TX gain table, index 8 */ 144 | "pa_gain": 0, 145 | "mix_gain": 5, 146 | "rf_power": 13, 147 | "dig_gain": 2 148 | }, 149 | "tx_lut_9": { 150 | /* TX gain table, index 9 */ 151 | "pa_gain": 0, 152 | "mix_gain": 8, 153 | "rf_power": 14, 154 | "dig_gain": 3 155 | }, 156 | "tx_lut_10": { 157 | /* TX gain table, index 10 */ 158 | "pa_gain": 0, 159 | "mix_gain": 6, 160 | "rf_power": 15, 161 | "dig_gain": 2 162 | }, 163 | "tx_lut_11": { 164 | /* TX gain table, index 11 */ 165 | "pa_gain": 0, 166 | "mix_gain": 6, 167 | "rf_power": 16, 168 | "dig_gain": 1 169 | }, 170 | "tx_lut_12": { 171 | /* TX gain table, index 12 */ 172 | "pa_gain": 0, 173 | "mix_gain": 9, 174 | "rf_power": 17, 175 | "dig_gain": 3 176 | }, 177 | "tx_lut_13": { 178 | /* TX gain table, index 13 */ 179 | "pa_gain": 0, 180 | "mix_gain": 10, 181 | "rf_power": 18, 182 | "dig_gain": 3 183 | }, 184 | "tx_lut_14": { 185 | /* TX gain table, index 14 */ 186 | "pa_gain": 0, 187 | "mix_gain": 11, 188 | "rf_power": 19, 189 | "dig_gain": 3 190 | }, 191 | "tx_lut_15": { 192 | /* TX gain table, index 15 */ 193 | "pa_gain": 0, 194 | "mix_gain": 12, 195 | "rf_power": 20, 196 | "dig_gain": 3 197 | } 198 | }, 199 | 200 | "gateway_conf": { 201 | "gateway_ID": "AA555A0000240409", 202 | /* change with default server address/ports, or overwrite in local_conf.json */ 203 | "server_address": "localhost", 204 | "serv_port_up": 1680, 205 | "serv_port_down": 1680, 206 | /* adjust the following parameters for your network */ 207 | "keepalive_interval": 10, 208 | "stat_interval": 30, 209 | "push_timeout_ms": 100, 210 | /* forward only valid packets */ 211 | "forward_crc_valid": true, 212 | "forward_crc_error": false, 213 | "forward_crc_disabled": false 214 | } 215 | } 216 | 217 | -------------------------------------------------------------------------------- /lora_pkt_fwd/inc/base64.h: -------------------------------------------------------------------------------- 1 | /* 2 | / _____) _ | | 3 | ( (____ _____ ____ _| |_ _____ ____| |__ 4 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 5 | _____) ) ____| | | || |_| ____( (___| | | | 6 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 7 | (C)2013 Semtech-Cycleo 8 | 9 | Description: 10 | Base64 encoding & decoding library 11 | 12 | License: Revised BSD License, see LICENSE.TXT file include in the project 13 | Maintainer: Sylvain Miermont 14 | */ 15 | 16 | 17 | #ifndef _BASE64_H 18 | #define _BASE64_H 19 | 20 | /* -------------------------------------------------------------------------- */ 21 | /* --- DEPENDANCIES --------------------------------------------------------- */ 22 | 23 | #include /* C99 types */ 24 | 25 | /* -------------------------------------------------------------------------- */ 26 | /* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ 27 | 28 | /** 29 | @brief Encode binary data in Base64 string (no padding) 30 | @param in pointer to a table of binary data 31 | @param size number of bytes to be encoded to base64 32 | @param out pointer to a string where the function will output encoded data 33 | @param max_len max length of the out string (including null char) 34 | @return >=0 length of the resulting string (w/o null char), -1 for error 35 | */ 36 | int bin_to_b64_nopad(const uint8_t * in, int size, char * out, int max_len); 37 | 38 | /** 39 | @brief Decode Base64 string to binary data (no padding) 40 | @param in string containing only base64 valid characters 41 | @param size number of characters to be decoded from base64 (w/o null char) 42 | @param out pointer to a data buffer where the function will output decoded data 43 | @param out_max_len usable size of the output data buffer 44 | @return >=0 number of bytes written to the data buffer, -1 for error 45 | */ 46 | int b64_to_bin_nopad(const char * in, int size, uint8_t * out, int max_len); 47 | 48 | /* === derivative functions === */ 49 | 50 | /** 51 | @brief Encode binary data in Base64 string (with added padding) 52 | */ 53 | int bin_to_b64(const uint8_t * in, int size, char * out, int max_len); 54 | 55 | /** 56 | @brief Decode Base64 string to binary data (remove padding if necessary) 57 | */ 58 | int b64_to_bin(const char * in, int size, uint8_t * out, int max_len); 59 | 60 | #endif 61 | 62 | /* --- EOF ------------------------------------------------------------------ */ 63 | -------------------------------------------------------------------------------- /lora_pkt_fwd/inc/jitqueue.h: -------------------------------------------------------------------------------- 1 | /* 2 | / _____) _ | | 3 | ( (____ _____ ____ _| |_ _____ ____| |__ 4 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 5 | _____) ) ____| | | || |_| ____( (___| | | | 6 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 7 | (C)2013 Semtech-Cycleo 8 | 9 | Description: 10 | LoRa concentrator : Just In Time TX scheduling queue 11 | 12 | License: Revised BSD License, see LICENSE.TXT file include in the project 13 | Maintainer: Michael Coracin 14 | */ 15 | 16 | 17 | #ifndef _LORA_PKTFWD_JIT_H 18 | #define _LORA_PKTFWD_JIT_H 19 | 20 | /* -------------------------------------------------------------------------- */ 21 | /* --- DEPENDANCIES --------------------------------------------------------- */ 22 | 23 | #include /* C99 types */ 24 | #include /* bool type */ 25 | #include /* timeval */ 26 | 27 | #include "loragw_hal.h" 28 | 29 | /* -------------------------------------------------------------------------- */ 30 | /* --- PUBLIC CONSTANTS ----------------------------------------------------- */ 31 | 32 | #define JIT_QUEUE_MAX 32 /* Maximum number of packets to be stored in JiT queue */ 33 | #define JIT_NUM_BEACON_IN_QUEUE 3 /* Number of beacons to be loaded in JiT queue at any time */ 34 | 35 | /* -------------------------------------------------------------------------- */ 36 | /* --- PUBLIC TYPES --------------------------------------------------------- */ 37 | 38 | enum jit_pkt_type_e { 39 | JIT_PKT_TYPE_DOWNLINK_CLASS_A, 40 | JIT_PKT_TYPE_DOWNLINK_CLASS_B, 41 | JIT_PKT_TYPE_DOWNLINK_CLASS_C, 42 | JIT_PKT_TYPE_BEACON 43 | }; 44 | 45 | enum jit_error_e { 46 | JIT_ERROR_OK, /* Packet ok to be sent */ 47 | JIT_ERROR_TOO_LATE, /* Too late to send this packet */ 48 | JIT_ERROR_TOO_EARLY, /* Too early to queue this packet */ 49 | JIT_ERROR_FULL, /* Downlink queue is full */ 50 | JIT_ERROR_EMPTY, /* Downlink queue is empty */ 51 | JIT_ERROR_COLLISION_PACKET, /* A packet is already enqueued for this timeframe */ 52 | JIT_ERROR_COLLISION_BEACON, /* A beacon is planned for this timeframe */ 53 | JIT_ERROR_TX_FREQ, /* The required frequency for downlink is not supported */ 54 | JIT_ERROR_TX_POWER, /* The required power for downlink is not supported */ 55 | JIT_ERROR_GPS_UNLOCKED, /* GPS timestamp could not be used as GPS is unlocked */ 56 | JIT_ERROR_INVALID /* Packet is invalid */ 57 | }; 58 | 59 | struct jit_node_s { 60 | /* API fields */ 61 | struct lgw_pkt_tx_s pkt; /* TX packet */ 62 | enum jit_pkt_type_e pkt_type; /* Packet type: Downlink, Beacon... */ 63 | 64 | /* Internal fields */ 65 | uint32_t pre_delay; /* Amount of time before packet timestamp to be reserved */ 66 | uint32_t post_delay; /* Amount of time after packet timestamp to be reserved (time on air) */ 67 | }; 68 | 69 | struct jit_queue_s { 70 | uint8_t num_pkt; /* Total number of packets in the queue (downlinks, beacons...) */ 71 | uint8_t num_beacon; /* Number of beacons in the queue */ 72 | struct jit_node_s nodes[JIT_QUEUE_MAX]; /* Nodes/packets array in the queue */ 73 | }; 74 | 75 | /* -------------------------------------------------------------------------- */ 76 | /* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ 77 | 78 | /** 79 | @brief Check if a JiT queue is full. 80 | 81 | @param queue[in] Just in Time queue to be checked. 82 | @return true if queue is full, false otherwise. 83 | */ 84 | bool jit_queue_is_full(struct jit_queue_s *queue); 85 | 86 | /** 87 | @brief Check if a JiT queue is empty. 88 | 89 | @param queue[in] Just in Time queue to be checked. 90 | @return true if queue is empty, false otherwise. 91 | */ 92 | bool jit_queue_is_empty(struct jit_queue_s *queue); 93 | 94 | /** 95 | @brief Initialize a Just in Time queue. 96 | 97 | @param queue[in] Just in Time queue to be initialized. Memory should have been allocated already. 98 | 99 | This function is used to reset every elements in the allocated queue. 100 | */ 101 | void jit_queue_init(struct jit_queue_s *queue); 102 | 103 | /** 104 | @brief Add a packet in a Just-in-Time queue 105 | 106 | @param queue[in/out] Just in Time queue in which the packet should be inserted 107 | @param time[in] Current concentrator time 108 | @param packet[in] Packet to be queued in JiT queue 109 | @param pkt_type[in] Type of packet to be queued: Downlink, Beacon 110 | @return success if the function was able to queue the packet 111 | 112 | This function is typically used when a packet is received from server for downlink. 113 | It will check if packet can be queued, with several criterias. Once the packet is queued, it has to be 114 | sent over the air. So all checks should happen before the packet being actually in the queue. 115 | */ 116 | 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); 117 | 118 | /** 119 | @brief Dequeue a packet from a Just-in-Time queue 120 | 121 | @param queue[in/out] Just in Time queue from which the packet should be removed 122 | @param index[in] in the queue where to get the packet to be removed 123 | @param packet[out] that was at index 124 | @param pkt_type[out] Type of packet dequeued: Downlink, Beacon 125 | @return success if the function was able to dequeue the packet 126 | 127 | This function is typically used when a packet is about to be placed on concentrator buffer for TX. 128 | The index is generally got using the jit_peek function. 129 | */ 130 | 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); 131 | 132 | /** 133 | @brief Check if there is a packet soon to be sent from the JiT queue. 134 | 135 | @param queue[in] Just in Time queue to parse for peeking a packet 136 | @param time[in] Current concentrator time 137 | @param pkt_idx[out] Packet index which is soon to be dequeued. 138 | @return success if the function was able to parse the queue. pkt_idx is set to -1 if no packet found. 139 | 140 | This function is typically used to check in JiT queue if there is a packet soon to be sent. 141 | It search the packet with the highest priority in queue, and check if its timestamp is near 142 | enough the current concentrator time. 143 | */ 144 | enum jit_error_e jit_peek(struct jit_queue_s *queue, struct timeval *time, int *pkt_idx); 145 | 146 | /** 147 | @brief Debug function to print the queue's content on console 148 | 149 | @param queue[in] Just in Time queue to be displayed 150 | @param show_all[in] Indicates if empty nodes have to be displayed or not 151 | */ 152 | void jit_print_queue(struct jit_queue_s *queue, bool show_all, int debug_level); 153 | 154 | #endif 155 | /* --- EOF ------------------------------------------------------------------ */ 156 | -------------------------------------------------------------------------------- /lora_pkt_fwd/inc/parson.h: -------------------------------------------------------------------------------- 1 | /* 2 | Parson ( http://kgabis.github.com/parson/ ) 3 | Copyright (c) 2012 - 2016 Krzysztof Gabis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | */ 23 | 24 | #ifndef parson_parson_h 25 | #define parson_parson_h 26 | 27 | #ifdef __cplusplus 28 | extern "C" 29 | { 30 | #endif 31 | 32 | #include /* size_t */ 33 | 34 | /* Types and enums */ 35 | typedef struct json_object_t JSON_Object; 36 | typedef struct json_array_t JSON_Array; 37 | typedef struct json_value_t JSON_Value; 38 | 39 | enum json_value_type { 40 | JSONError = -1, 41 | JSONNull = 1, 42 | JSONString = 2, 43 | JSONNumber = 3, 44 | JSONObject = 4, 45 | JSONArray = 5, 46 | JSONBoolean = 6 47 | }; 48 | typedef int JSON_Value_Type; 49 | 50 | enum json_result_t { 51 | JSONSuccess = 0, 52 | JSONFailure = -1 53 | }; 54 | typedef int JSON_Status; 55 | 56 | typedef void * (*JSON_Malloc_Function)(size_t); 57 | typedef void (*JSON_Free_Function)(void *); 58 | 59 | /* Call only once, before calling any other function from parson API. If not called, malloc and free 60 | from stdlib will be used for all allocations */ 61 | void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun); 62 | 63 | /* Parses first JSON value in a file, returns NULL in case of error */ 64 | JSON_Value * json_parse_file(const char *filename); 65 | 66 | /* Parses first JSON value in a file and ignores comments (/ * * / and //), 67 | returns NULL in case of error */ 68 | JSON_Value * json_parse_file_with_comments(const char *filename); 69 | 70 | /* Parses first JSON value in a string, returns NULL in case of error */ 71 | JSON_Value * json_parse_string(const char *string); 72 | 73 | /* Parses first JSON value in a string and ignores comments (/ * * / and //), 74 | returns NULL in case of error */ 75 | JSON_Value * json_parse_string_with_comments(const char *string); 76 | 77 | /* Serialization */ 78 | size_t json_serialization_size(const JSON_Value *value); /* returns 0 on fail */ 79 | JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes); 80 | JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename); 81 | char * json_serialize_to_string(const JSON_Value *value); 82 | 83 | /* Pretty serialization */ 84 | size_t json_serialization_size_pretty(const JSON_Value *value); /* returns 0 on fail */ 85 | JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes); 86 | JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename); 87 | char * json_serialize_to_string_pretty(const JSON_Value *value); 88 | 89 | void json_free_serialized_string(char *string); /* frees string from json_serialize_to_string and json_serialize_to_string_pretty */ 90 | 91 | /* Comparing */ 92 | int json_value_equals(const JSON_Value *a, const JSON_Value *b); 93 | 94 | /* Validation 95 | This is *NOT* JSON Schema. It validates json by checking if object have identically 96 | named fields with matching types. 97 | For example schema {"name":"", "age":0} will validate 98 | {"name":"Joe", "age":25} and {"name":"Joe", "age":25, "gender":"m"}, 99 | but not {"name":"Joe"} or {"name":"Joe", "age":"Cucumber"}. 100 | In case of arrays, only first value in schema is checked against all values in tested array. 101 | Empty objects ({}) validate all objects, empty arrays ([]) validate all arrays, 102 | null validates values of every type. 103 | */ 104 | JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value); 105 | 106 | /* 107 | * JSON Object 108 | */ 109 | JSON_Value * json_object_get_value (const JSON_Object *object, const char *name); 110 | const char * json_object_get_string (const JSON_Object *object, const char *name); 111 | JSON_Object * json_object_get_object (const JSON_Object *object, const char *name); 112 | JSON_Array * json_object_get_array (const JSON_Object *object, const char *name); 113 | double json_object_get_number (const JSON_Object *object, const char *name); /* returns 0 on fail */ 114 | int json_object_get_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */ 115 | 116 | /* dotget functions enable addressing values with dot notation in nested objects, 117 | just like in structs or c++/java/c# objects (e.g. objectA.objectB.value). 118 | Because valid names in JSON can contain dots, some values may be inaccessible 119 | this way. */ 120 | JSON_Value * json_object_dotget_value (const JSON_Object *object, const char *name); 121 | const char * json_object_dotget_string (const JSON_Object *object, const char *name); 122 | JSON_Object * json_object_dotget_object (const JSON_Object *object, const char *name); 123 | JSON_Array * json_object_dotget_array (const JSON_Object *object, const char *name); 124 | double json_object_dotget_number (const JSON_Object *object, const char *name); /* returns 0 on fail */ 125 | int json_object_dotget_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */ 126 | 127 | /* Functions to get available names */ 128 | size_t json_object_get_count(const JSON_Object *object); 129 | const char * json_object_get_name (const JSON_Object *object, size_t index); 130 | 131 | /* Creates new name-value pair or frees and replaces old value with a new one. 132 | * json_object_set_value does not copy passed value so it shouldn't be freed afterwards. */ 133 | JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value); 134 | JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string); 135 | JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number); 136 | JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean); 137 | JSON_Status json_object_set_null(JSON_Object *object, const char *name); 138 | 139 | /* Works like dotget functions, but creates whole hierarchy if necessary. 140 | * json_object_dotset_value does not copy passed value so it shouldn't be freed afterwards. */ 141 | JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value); 142 | JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string); 143 | JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number); 144 | JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean); 145 | JSON_Status json_object_dotset_null(JSON_Object *object, const char *name); 146 | 147 | /* Frees and removes name-value pair */ 148 | JSON_Status json_object_remove(JSON_Object *object, const char *name); 149 | 150 | /* Works like dotget function, but removes name-value pair only on exact match. */ 151 | JSON_Status json_object_dotremove(JSON_Object *object, const char *key); 152 | 153 | /* Removes all name-value pairs in object */ 154 | JSON_Status json_object_clear(JSON_Object *object); 155 | 156 | /* 157 | *JSON Array 158 | */ 159 | JSON_Value * json_array_get_value (const JSON_Array *array, size_t index); 160 | const char * json_array_get_string (const JSON_Array *array, size_t index); 161 | JSON_Object * json_array_get_object (const JSON_Array *array, size_t index); 162 | JSON_Array * json_array_get_array (const JSON_Array *array, size_t index); 163 | double json_array_get_number (const JSON_Array *array, size_t index); /* returns 0 on fail */ 164 | int json_array_get_boolean(const JSON_Array *array, size_t index); /* returns -1 on fail */ 165 | size_t json_array_get_count (const JSON_Array *array); 166 | 167 | /* Frees and removes value at given index, does nothing and returns JSONFailure if index doesn't exist. 168 | * Order of values in array may change during execution. */ 169 | JSON_Status json_array_remove(JSON_Array *array, size_t i); 170 | 171 | /* Frees and removes from array value at given index and replaces it with given one. 172 | * Does nothing and returns JSONFailure if index doesn't exist. 173 | * json_array_replace_value does not copy passed value so it shouldn't be freed afterwards. */ 174 | JSON_Status json_array_replace_value(JSON_Array *array, size_t i, JSON_Value *value); 175 | JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string); 176 | JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number); 177 | JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean); 178 | JSON_Status json_array_replace_null(JSON_Array *array, size_t i); 179 | 180 | /* Frees and removes all values from array */ 181 | JSON_Status json_array_clear(JSON_Array *array); 182 | 183 | /* Appends new value at the end of array. 184 | * json_array_append_value does not copy passed value so it shouldn't be freed afterwards. */ 185 | JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value); 186 | JSON_Status json_array_append_string(JSON_Array *array, const char *string); 187 | JSON_Status json_array_append_number(JSON_Array *array, double number); 188 | JSON_Status json_array_append_boolean(JSON_Array *array, int boolean); 189 | JSON_Status json_array_append_null(JSON_Array *array); 190 | 191 | /* 192 | *JSON Value 193 | */ 194 | JSON_Value * json_value_init_object (void); 195 | JSON_Value * json_value_init_array (void); 196 | JSON_Value * json_value_init_string (const char *string); /* copies passed string */ 197 | JSON_Value * json_value_init_number (double number); 198 | JSON_Value * json_value_init_boolean(int boolean); 199 | JSON_Value * json_value_init_null (void); 200 | JSON_Value * json_value_deep_copy (const JSON_Value *value); 201 | void json_value_free (JSON_Value *value); 202 | 203 | JSON_Value_Type json_value_get_type (const JSON_Value *value); 204 | JSON_Object * json_value_get_object (const JSON_Value *value); 205 | JSON_Array * json_value_get_array (const JSON_Value *value); 206 | const char * json_value_get_string (const JSON_Value *value); 207 | double json_value_get_number (const JSON_Value *value); 208 | int json_value_get_boolean(const JSON_Value *value); 209 | 210 | /* Same as above, but shorter */ 211 | JSON_Value_Type json_type (const JSON_Value *value); 212 | JSON_Object * json_object (const JSON_Value *value); 213 | JSON_Array * json_array (const JSON_Value *value); 214 | const char * json_string (const JSON_Value *value); 215 | double json_number (const JSON_Value *value); 216 | int json_boolean(const JSON_Value *value); 217 | 218 | #ifdef __cplusplus 219 | } 220 | #endif 221 | 222 | #endif 223 | -------------------------------------------------------------------------------- /lora_pkt_fwd/inc/timersync.h: -------------------------------------------------------------------------------- 1 | /* 2 | / _____) _ | | 3 | ( (____ _____ ____ _| |_ _____ ____| |__ 4 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 5 | _____) ) ____| | | || |_| ____( (___| | | | 6 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 7 | (C)2013 Semtech-Cycleo 8 | 9 | Description: 10 | LoRa concentrator : Just In Time TX scheduling queue 11 | 12 | License: Revised BSD License, see LICENSE.TXT file include in the project 13 | Maintainer: Michael Coracin 14 | */ 15 | 16 | 17 | #ifndef _LORA_PKTFWD_TIMERSYNC_H 18 | #define _LORA_PKTFWD_TIMERSYNC_H 19 | 20 | /* -------------------------------------------------------------------------- */ 21 | /* --- DEPENDANCIES --------------------------------------------------------- */ 22 | 23 | #include /* timeval */ 24 | 25 | /* -------------------------------------------------------------------------- */ 26 | /* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ 27 | 28 | int get_concentrator_time(struct timeval *concent_time, struct timeval unix_time); 29 | 30 | void thread_timersync(void); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /lora_pkt_fwd/inc/trace.h: -------------------------------------------------------------------------------- 1 | /* 2 | / _____) _ | | 3 | ( (____ _____ ____ _| |_ _____ ____| |__ 4 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 5 | _____) ) ____| | | || |_| ____( (___| | | | 6 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 7 | (C)2013 Semtech-Cycleo 8 | 9 | Description: 10 | LoRa concentrator : Packet Forwarder trace helpers 11 | 12 | License: Revised BSD License, see LICENSE.TXT file include in the project 13 | Maintainer: Michael Coracin 14 | */ 15 | 16 | 17 | #ifndef _LORA_PKTFWD_TRACE_H 18 | #define _LORA_PKTFWD_TRACE_H 19 | 20 | #define DEBUG_PKT_FWD 0 21 | #define DEBUG_JIT 0 22 | #define DEBUG_JIT_ERROR 1 23 | #define DEBUG_TIMERSYNC 0 24 | #define DEBUG_BEACON 0 25 | #define DEBUG_LOG 1 26 | 27 | #define MSG(args...) printf(args) /* message that is destined to the user */ 28 | #define MSG_DEBUG(FLAG, fmt, ...) \ 29 | do { \ 30 | if (FLAG) \ 31 | fprintf(stdout, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); \ 32 | } while (0) 33 | 34 | 35 | 36 | #endif 37 | /* --- EOF ------------------------------------------------------------------ */ 38 | -------------------------------------------------------------------------------- /lora_pkt_fwd/readme.md: -------------------------------------------------------------------------------- 1 | / _____) _ | | 2 | ( (____ _____ ____ _| |_ _____ ____| |__ 3 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 4 | _____) ) ____| | | || |_| ____( (___| | | | 5 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 6 | (C)2013 Semtech-Cycleo 7 | 8 | Lora Gateway packet forwarder 9 | ============================= 10 | 11 | 1. Introduction 12 | ---------------- 13 | 14 | The packet forwarder is a program running on the host of a Lora gateway that 15 | forwards RF packets receive by the concentrator to a server through a IP/UDP 16 | link, and emits RF packets that are sent by the server. It can also emit a 17 | network-wide GPS-synchronous beacon signal used for coordinating all nodes of 18 | the network. 19 | 20 | To learn more about the network protocol between the gateway and the server, 21 | please read the PROTOCOL.TXT document. 22 | 23 | 2. System schematic and definitions 24 | ------------------------------------ 25 | 26 | ((( Y ))) 27 | | 28 | | 29 | +- -|- - - - - - - - - - - - -+ xxxxxxxxxxxx +--------+ 30 | |+--+-----------+ +------+| xx x x xxx | | 31 | || | | || xx Internet xx | | 32 | || Concentrator |<----+ Host |<------xx or xx-------->| | 33 | || | SPI | || xx Intranet xx | Server | 34 | |+--------------+ +------+| xxxx x xxxx | | 35 | | ^ ^ | xxxxxxxx | | 36 | | | PPS +-----+ NMEA | | | | 37 | | +------| GPS |-------+ | +--------+ 38 | | +-----+ | 39 | | | 40 | | Gateway | 41 | +- - - - - - - - - - - - - - -+ 42 | 43 | Concentrator: radio RX/TX board, based on Semtech multichannel modems (SX130x), 44 | transceivers (SX135x) and/or low-power stand-alone modems (SX127x). 45 | 46 | Host: embedded computer on which the packet forwarder is run. Drives the 47 | concentrator through a SPI link. 48 | 49 | Gateway: a device composed of at least one radio concentrator, a host, some 50 | network connection to the internet or a private network (Ethernet, 3G, Wifi, 51 | microwave link), and optionally a GPS receiver for synchronization. 52 | 53 | Server: an abstract computer that will process the RF packets received and 54 | forwarded by the gateway, and issue RF packets in response that the gateway 55 | will have to emit. 56 | 57 | 58 | 3. Dependencies 59 | ---------------- 60 | 61 | This program uses the Parson library (http://kgabis.github.com/parson/) by 62 | Krzysztof Gabis for JSON parsing. 63 | Many thanks to him for that very practical and well written library. 64 | 65 | This program is statically linked with the libloragw Lora concentrator library. 66 | It was tested with v1.3.0 of the library but should work with any later 67 | version provided the API is v1 or a later backward-compatible API. 68 | Data structures of the received packets are accessed by name (ie. not at a 69 | binary level) so new functionalities can be added to the API without affecting 70 | that program at all. 71 | 72 | This program follows the v1.3 version of the gateway-to-server protocol. 73 | 74 | The last dependency is the hardware concentrator (based on FPGA or SX130x 75 | chips) that must be matched with the proper version of the HAL. 76 | 77 | 4. Usage 78 | --------- 79 | 80 | * Pick the global_conf.json file from cfg/ directory that fit with your 81 | platform, region and feature need. 82 | * Update the JSON configuration (global and local) files, as explained below. 83 | * For IoT Starter Kit only, run: 84 | ./reset_lgw.sh stop 85 | ./reset_lgw.sh start 86 | * Run: 87 | ./update_gwid.sh local_conf.json (OPTIONAL) 88 | ./lora_pkt_fwd 89 | 90 | To stop the application, press Ctrl+C. 91 | Unless it is manually stopped or encounter a critical error, the program will 92 | run forever. 93 | 94 | There are no command line launch options. 95 | 96 | The way the program takes configuration files into account is the following: 97 | * if there is a debug_conf.json parse it, others are ignored 98 | * if there is a global_conf.json parse it, look for the next file 99 | * if there is a local_conf.json parse it 100 | If some parameters are defined in both global and local configuration files, 101 | the local definition overwrites the global definition. 102 | 103 | The global configuration file should be exactly the same throughout your 104 | network, contain all global parameters (parameters for "sensor" radio 105 | channels) and preferably default "safe" values for parameters that are 106 | specific for each gateway (eg. specify a default MAC address). 107 | 108 | As some of the parameters (like 'rssi_offset', 'tx_lut_*') are board dependant, 109 | several flavours of the global_conf.json file are provided in the cfg/ 110 | directory. 111 | * global_conf.json.PCB_E286.EU868.*: to be used for Semtech reference design 112 | board with PCB name PCB_E286 (also called Gateway Board v1.0 (no FPGA)). 113 | Configured for Europe 868MHz channels. 114 | * global_conf.json.PCB_E336.EU868.*:to be used for Semtech reference design 115 | board with PCB name PCB_E336 (also called Gateway Board v1.5 (with FPGA)). 116 | Configured for Europe 868MHz channels. 117 | * global_conf.json.US902.*: to be used for Semtech reference design v1.0 or 118 | v1.5. (No calibration done for RSSI offset and TX gains yet). 119 | Configured for US 902MHz channels. 120 | 121 | Beside board related flavours, there are "features" flavours named "basic", 122 | "gps", "beacon". 123 | * global_conf.json.*.basic: to be used for basic packet forwarder usage, with 124 | no GPS. 125 | * global_conf.json.*.gps: to be used when the platform has a GPS receiver. 126 | * global_conf.json.*.beacon: to be used when the platform has a GPS receiver 127 | and we want the packet forwarder to emit beacons for synchronized networks. 128 | 129 | Rename the one you need to global_conf.json before launching the packet 130 | forwarder. 131 | 132 | The local configuration file should contain parameters that are specific to 133 | each gateway (eg. MAC address, frequency for backhaul radio channels). 134 | 135 | In each configuration file, the program looks for a JSON object named 136 | "SX1301_conf" that should contain the parameters for the Lora concentrator 137 | board (RF channels definition, modem parameters, etc) and another JSON object 138 | called "gateway_conf" that should contain the gateway parameters (gateway MAC 139 | address, IP address of the server, keep-alive time, etc). 140 | 141 | To learn more about the JSON configuration format, read the provided JSON 142 | files and the libloragw API documentation. 143 | 144 | Every X seconds (parameter settable in the configuration files) the program 145 | display statistics on the RF packets received and sent, and the network 146 | datagrams received and sent. 147 | The program also send some statistics to the server in JSON format. 148 | 149 | 5. "Just-In-Time" downlink scheduling 150 | ------------------------------------- 151 | 152 | The LoRa concentrator can have only one TX packet programmed for departure at a 153 | time. The actual departure of a downlink packet will be done based on its 154 | timestamp, when the concentrator internal counter reaches timestamp’s value. 155 | The departure of a beacon will be done based on a GPS PPS event. 156 | It may happen that, due to network variable latency, the gateway receives one 157 | or many downlink packets from the server while a TX is already programmed in the 158 | concentrator. The packet forwarder has to store and order incoming downlink 159 | packets (in a queue), so that they can all be programmed in the concentrator at 160 | the proper time and sent over the air. 161 | Possible failures that may occur and that have to be reported to the server are: 162 | - It is too early or too late to send a given packet 163 | - A packet collides with another packet already queued 164 | - A packet collides with a beacon 165 | - TX RF parameters (frequency, power) are not supported by gateway 166 | - Gateway’s GPS is unlocked, so cannot process Class B downlink 167 | It is called "Just-in-Time" (JiT) scheduling, because the packet forwarder will 168 | program a downlink or a beacon packet in the concentrator just before it has to 169 | be sent over the air. 170 | Another benefit of JiT is to optimize the gateway downlink capacity by avoiding 171 | to keep the concentrator TX buffer busy for too long. 172 | 173 | In order to achieve "Just-in-Time" scheduling, the following software elements 174 | have been added: 175 | - A JiT queue, with associated enqueue/peek/dequeue functions and packet 176 | acceptance criterias. It is where downlink packets are stored, waiting to be 177 | sent. 178 | - A JiT thread, which regularly checks if there is a packet in the JiT queue 179 | ready to be programmed in the concentrator, based on current concentrator 180 | internal time. 181 | - A Timer synchronization thread to keep the concentrator clock and Unix clock 182 | synchronized so that host processor can determine if a packet with a given 183 | timestamp can be programmed in the concentrator or not. 184 | 185 | 5.1. Concentrator vs Unix time synchronization 186 | 187 | In order for the host to know if an incoming downlink packet can or cannot be 188 | queued in JiT queue for later transmission, it has to check if the timestamp of 189 | the packet designates a time later than the current concentrator counter or if 190 | it is already too late to be passed to the concentrator. 191 | In order to get current concentrator time, we can use the lgw_get_trigcnt() HAL 192 | function. The problem is that the sample register used to read this value can be 193 | configured in 2 different ways: 194 | - Real time mode: when GPS is disabled, the value read in sample register is 195 | the actual concentrator counter value. 196 | - PPS mode: when GPS is enabled, the value read in sample register is the 197 | value that the concentrator counter had when last GPS’s PPS occurred. So 198 | this changes every second only. 199 | As in our case GPS is enabled (LGW_GPS_EN==1), we need to have a way to get the 200 | actual concentrator current time, at any time. 201 | For this, a new thread has been added to the packet forwarder (thread_timersync) 202 | which will regularly: 203 | - Disable GPS mode of SX1301 counter sampler 204 | - Get current Unix time 205 | - Get current SX1301 counter 206 | - Compute the offset between Unix and SX1301 clocks and store it 207 | - Re-enable GPS mode of SX1301 counter sampler 208 | Then a new function has been added to estimate the current concentrator counter 209 | at any time based on the current Unix time and offset computed by the timersync 210 | thread. 211 | 212 | In addition to this, the Concentrator vs Unix time synchronization is used by 213 | the JiT thread to determine if a packet in the JiT queue has to be sent to the 214 | concentrator for transmission. 215 | So basically it is used for queueing and dequeuing packets to/from the JiT queue. 216 | 217 | 5.2. Concentrator vs GPS time synchronization 218 | 219 | There are 2 cases for which we need to convert a GPS time to concentrator 220 | counter: 221 | - Class B downlink: when the “time” field of JSON “txpk” is filled instead 222 | of the “tmst” field, we need to be able to determine if the packet can be 223 | queued in JiT queue or not, based on its corresponding concentrator 224 | counter value. 225 | Note: even if a Class-B downlink is given with a GPS timestamp, the 226 | concentrator TX mode is configured as “TIMESTAMP”, and not “ON_GPS”. So 227 | at the end, it is the counter value which will be used for transmission. 228 | - Beacons: beacons transmission time is based on GPS clock, and the 229 | concentrator TX mode is configured as “ON_GPS” for accurate beacon 230 | transmission on GPS PPS event. In this case, the concentrator does not 231 | need the packet counter to be set. But, as the JiT thread decides if a 232 | packet has to be peeked or not, based on its concentrator counter, we need 233 | to have the beacon packet counter set (see next chapter for more details 234 | on JiT scheduling). 235 | We also need to convert a SX1301 counter value to GPS UTC time when we receive 236 | an uplink, in order to fill the “time” field of JSON “rxpk” structure. 237 | 238 | 5.3. TX scheduling 239 | 240 | The JiT queue implemented is a static array of nodes, where each node contains: 241 | - the downlink packet, with its type (beacon, downlink class A, B or C) 242 | - a “pre delay” which depends on packet type (BEACON_GUARD, TX_START_DELAY…) 243 | - a “post delay” which depends on packet type (“time on air” of this packet 244 | computed based on its size, datarate and coderate, or BEACON_RESERVED) 245 | 246 | Several functions are implemented to manipulate this queue or get info from it: 247 | - init: initialize array with default values 248 | - is full / is empty: gives queue status 249 | - enqueue: checks if the given packet can be queued or not, based on several 250 | criteria’s 251 | - peek: checks if the queue contains a packet that must be passed 252 | immediately to the concentrator for transmission and returns corresponding 253 | index if any. 254 | - dequeue: actually removes from the queue the packet at index given by peek 255 | function 256 | 257 | The queue is always kept sorted on ascending timestamp order. 258 | 259 | The JiT thread will regularly check in the JiT queue if there is a packet to be 260 | sent soon. If a packet is matching, it is dequeued and programmed in the 261 | concentrator TX buffer. 262 | 263 | 5.4. Fine tuning parameters 264 | 265 | There are few parameters of the JiT queue which could be tweaked to adapt to 266 | different system constraints. 267 | 268 | - inc/jitqueue.h: 269 | JIT_QUEUE_MAX: The maximum number of nodes in the queue. 270 | - src/jitqueue.c: 271 | TX_JIT_DELAY: The number of milliseconds a packet is programmed in the 272 | concentrator TX buffer before its actual departure time. 273 | TX_MARGIN_DELAY: Packet collision check margin 274 | 275 | 6. License 276 | ----------- 277 | 278 | Copyright (C) 2013, SEMTECH S.A. 279 | All rights reserved. 280 | 281 | Redistribution and use in source and binary forms, with or without 282 | modification, are permitted provided that the following conditions are met: 283 | 284 | * Redistributions of source code must retain the above copyright 285 | notice, this list of conditions and the following disclaimer. 286 | * Redistributions in binary form must reproduce the above copyright 287 | notice, this list of conditions and the following disclaimer in the 288 | documentation and/or other materials provided with the distribution. 289 | * Neither the name of the Semtech corporation nor the 290 | names of its contributors may be used to endorse or promote products 291 | derived from this software without specific prior written permission. 292 | 293 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 294 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 295 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 296 | DISCLAIMED. IN NO EVENT SHALL SEMTECH S.A. BE LIABLE FOR ANY 297 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 298 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 299 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 300 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 301 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 302 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 303 | 304 | 6. License for Parson library 305 | ------------------------------ 306 | 307 | Parson ( http://kgabis.github.com/parson/ ) 308 | Copyright (C) 2012 Krzysztof Gabis 309 | 310 | Permission is hereby granted, free of charge, to any person obtaining a copy 311 | of this software and associated documentation files (the "Software"), to deal 312 | in the Software without restriction, including without limitation the rights 313 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 314 | copies of the Software, and to permit persons to whom the Software is 315 | furnished to do so, subject to the following conditions: 316 | 317 | The above copyright notice and this permission notice shall be included in 318 | all copies or substantial portions of the Software. 319 | 320 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 321 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 322 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 323 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 324 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 325 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 326 | THE SOFTWARE. 327 | 328 | *EOF* 329 | -------------------------------------------------------------------------------- /lora_pkt_fwd/src/base64.c: -------------------------------------------------------------------------------- 1 | /* 2 | / _____) _ | | 3 | ( (____ _____ ____ _| |_ _____ ____| |__ 4 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 5 | _____) ) ____| | | || |_| ____( (___| | | | 6 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 7 | (C)2013 Semtech-Cycleo 8 | 9 | Description: 10 | Base64 encoding & decoding library 11 | 12 | License: Revised BSD License, see LICENSE.TXT file include in the project 13 | Maintainer: Sylvain Miermont 14 | */ 15 | 16 | 17 | /* -------------------------------------------------------------------------- */ 18 | /* --- DEPENDANCIES --------------------------------------------------------- */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "base64.h" 25 | 26 | /* -------------------------------------------------------------------------- */ 27 | /* --- PRIVATE MACROS ------------------------------------------------------- */ 28 | 29 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) 30 | #define CRIT(a) fprintf(stderr, "\nCRITICAL file:%s line:%d msg:%s\n", __FILE__, __LINE__, a);exit(EXIT_FAILURE) 31 | 32 | //#define DEBUG(args...) fprintf(stderr,"debug: " args) /* diagnostic message that is destined to the user */ 33 | #define DEBUG(args...) 34 | 35 | /* -------------------------------------------------------------------------- */ 36 | /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ 37 | 38 | /* -------------------------------------------------------------------------- */ 39 | /* --- PRIVATE MODULE-WIDE VARIABLES ---------------------------------------- */ 40 | 41 | static char code_62 = '+'; /* RFC 1421 standard character for code 62 */ 42 | static char code_63 = '/'; /* RFC 1421 standard character for code 63 */ 43 | static char code_pad = '='; /* RFC 1421 padding character if padding */ 44 | 45 | /* -------------------------------------------------------------------------- */ 46 | /* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ 47 | 48 | /** 49 | @brief Convert a code in the range 0-63 to an ASCII character 50 | */ 51 | char code_to_char(uint8_t x); 52 | 53 | /** 54 | @brief Convert an ASCII character to a code in the range 0-63 55 | */ 56 | uint8_t char_to_code(char x); 57 | 58 | /* -------------------------------------------------------------------------- */ 59 | /* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ 60 | 61 | char code_to_char(uint8_t x) { 62 | if (x <= 25) { 63 | return 'A' + x; 64 | } else if ((x >= 26) && (x <= 51)) { 65 | return 'a' + (x - 26); 66 | } else if ((x >= 52) && (x <= 61)) { 67 | return '0' + (x - 52); 68 | } else if (x == 62) { 69 | return code_62; 70 | } else if (x == 63) { 71 | return code_63; 72 | } else { 73 | DEBUG("ERROR: %i IS OUT OF RANGE 0-63 FOR BASE64 ENCODING\n", x); 74 | exit(EXIT_FAILURE); 75 | } //TODO: improve error management 76 | } 77 | 78 | uint8_t char_to_code(char x) { 79 | if ((x >= 'A') && (x <= 'Z')) { 80 | return (uint8_t)x - (uint8_t)'A'; 81 | } else if ((x >= 'a') && (x <= 'z')) { 82 | return (uint8_t)x - (uint8_t)'a' + 26; 83 | } else if ((x >= '0') && (x <= '9')) { 84 | return (uint8_t)x - (uint8_t)'0' + 52; 85 | } else if (x == code_62) { 86 | return 62; 87 | } else if (x == code_63) { 88 | return 63; 89 | } else { 90 | DEBUG("ERROR: %c (0x%x) IS INVALID CHARACTER FOR BASE64 DECODING\n", x, x); 91 | exit(EXIT_FAILURE); 92 | } //TODO: improve error management 93 | } 94 | 95 | /* -------------------------------------------------------------------------- */ 96 | /* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ 97 | 98 | int bin_to_b64_nopad(const uint8_t * in, int size, char * out, int max_len) { 99 | int i; 100 | int result_len; /* size of the result */ 101 | int full_blocks; /* number of 3 unsigned chars / 4 characters blocks */ 102 | int last_bytes; /* number of unsigned chars <3 in the last block */ 103 | int last_chars; /* number of characters <4 in the last block */ 104 | uint32_t b; 105 | 106 | /* check input values */ 107 | if ((out == NULL) || (in == NULL)) { 108 | DEBUG("ERROR: NULL POINTER AS OUTPUT IN BIN_TO_B64\n"); 109 | return -1; 110 | } 111 | if (size == 0) { 112 | *out = 0; /* null string */ 113 | return 0; 114 | } 115 | 116 | /* calculate the number of base64 'blocks' */ 117 | full_blocks = size / 3; 118 | last_bytes = size % 3; 119 | switch (last_bytes) { 120 | case 0: /* no byte left to encode */ 121 | last_chars = 0; 122 | break; 123 | case 1: /* 1 byte left to encode -> +2 chars */ 124 | last_chars = 2; 125 | break; 126 | case 2: /* 2 bytes left to encode -> +3 chars */ 127 | last_chars = 3; 128 | break; 129 | default: 130 | CRIT("switch default that should not be possible"); 131 | } 132 | 133 | /* check if output buffer is big enough */ 134 | result_len = (4 * full_blocks) + last_chars; 135 | if (max_len < (result_len + 1)) { /* 1 char added for string terminator */ 136 | DEBUG("ERROR: OUTPUT BUFFER TOO SMALL IN BIN_TO_B64\n"); 137 | return -1; 138 | } 139 | 140 | /* process all the full blocks */ 141 | for (i = 0; i < full_blocks; ++i) { 142 | b = (0xFF & in[3 * i] ) << 16; 143 | b |= (0xFF & in[3 * i + 1]) << 8; 144 | b |= 0xFF & in[3 * i + 2]; 145 | out[4 * i + 0] = code_to_char((b >> 18) & 0x3F); 146 | out[4 * i + 1] = code_to_char((b >> 12) & 0x3F); 147 | out[4 * i + 2] = code_to_char((b >> 6 ) & 0x3F); 148 | out[4 * i + 3] = code_to_char( b & 0x3F); 149 | } 150 | 151 | /* process the last 'partial' block and terminate string */ 152 | i = full_blocks; 153 | if (last_chars == 0) { 154 | out[4 * i] = 0; /* null character to terminate string */ 155 | } else if (last_chars == 2) { 156 | b = (0xFF & in[3 * i] ) << 16; 157 | out[4 * i + 0] = code_to_char((b >> 18) & 0x3F); 158 | out[4 * i + 1] = code_to_char((b >> 12) & 0x3F); 159 | out[4 * i + 2] = 0; /* null character to terminate string */ 160 | } else if (last_chars == 3) { 161 | b = (0xFF & in[3 * i] ) << 16; 162 | b |= (0xFF & in[3 * i + 1]) << 8; 163 | out[4 * i + 0] = code_to_char((b >> 18) & 0x3F); 164 | out[4 * i + 1] = code_to_char((b >> 12) & 0x3F); 165 | out[4 * i + 2] = code_to_char((b >> 6 ) & 0x3F); 166 | out[4 * i + 3] = 0; /* null character to terminate string */ 167 | } 168 | 169 | return result_len; 170 | } 171 | 172 | int b64_to_bin_nopad(const char * in, int size, uint8_t * out, int max_len) { 173 | int i; 174 | int result_len; /* size of the result */ 175 | int full_blocks; /* number of 3 unsigned chars / 4 characters blocks */ 176 | int last_chars; /* number of characters <4 in the last block */ 177 | int last_bytes; /* number of unsigned chars <3 in the last block */ 178 | uint32_t b; 179 | ; 180 | 181 | /* check input values */ 182 | if ((out == NULL) || (in == NULL)) { 183 | DEBUG("ERROR: NULL POINTER AS OUTPUT OR INPUT IN B64_TO_BIN\n"); 184 | return -1; 185 | } 186 | if (size == 0) { 187 | return 0; 188 | } 189 | 190 | /* calculate the number of base64 'blocks' */ 191 | full_blocks = size / 4; 192 | last_chars = size % 4; 193 | switch (last_chars) { 194 | case 0: /* no char left to decode */ 195 | last_bytes = 0; 196 | break; 197 | case 1: /* only 1 char left is an error */ 198 | DEBUG("ERROR: ONLY ONE CHAR LEFT IN B64_TO_BIN\n"); 199 | return -1; 200 | case 2: /* 2 chars left to decode -> +1 byte */ 201 | last_bytes = 1; 202 | break; 203 | case 3: /* 3 chars left to decode -> +2 bytes */ 204 | last_bytes = 2; 205 | break; 206 | default: 207 | CRIT("switch default that should not be possible"); 208 | } 209 | 210 | /* check if output buffer is big enough */ 211 | result_len = (3 * full_blocks) + last_bytes; 212 | if (max_len < result_len) { 213 | DEBUG("ERROR: OUTPUT BUFFER TOO SMALL IN B64_TO_BIN\n"); 214 | return -1; 215 | } 216 | 217 | /* process all the full blocks */ 218 | for (i = 0; i < full_blocks; ++i) { 219 | b = (0x3F & char_to_code(in[4 * i] )) << 18; 220 | b |= (0x3F & char_to_code(in[4 * i + 1])) << 12; 221 | b |= (0x3F & char_to_code(in[4 * i + 2])) << 6; 222 | b |= 0x3F & char_to_code(in[4 * i + 3]); 223 | out[3 * i + 0] = (b >> 16) & 0xFF; 224 | out[3 * i + 1] = (b >> 8 ) & 0xFF; 225 | out[3 * i + 2] = b & 0xFF; 226 | } 227 | 228 | /* process the last 'partial' block */ 229 | i = full_blocks; 230 | if (last_bytes == 1) { 231 | b = (0x3F & char_to_code(in[4 * i] )) << 18; 232 | b |= (0x3F & char_to_code(in[4 * i + 1])) << 12; 233 | out[3 * i + 0] = (b >> 16) & 0xFF; 234 | if (((b >> 12) & 0x0F) != 0) { 235 | DEBUG("WARNING: last character contains unusable bits\n"); 236 | } 237 | } else if (last_bytes == 2) { 238 | b = (0x3F & char_to_code(in[4 * i] )) << 18; 239 | b |= (0x3F & char_to_code(in[4 * i + 1])) << 12; 240 | b |= (0x3F & char_to_code(in[4 * i + 2])) << 6; 241 | out[3 * i + 0] = (b >> 16) & 0xFF; 242 | out[3 * i + 1] = (b >> 8 ) & 0xFF; 243 | if (((b >> 6) & 0x03) != 0) { 244 | DEBUG("WARNING: last character contains unusable bits\n"); 245 | } 246 | } 247 | 248 | return result_len; 249 | } 250 | 251 | int bin_to_b64(const uint8_t * in, int size, char * out, int max_len) { 252 | int ret; 253 | 254 | ret = bin_to_b64_nopad(in, size, out, max_len); 255 | 256 | if (ret == -1) { 257 | return -1; 258 | } 259 | switch (ret % 4) { 260 | case 0: /* nothing to do */ 261 | return ret; 262 | case 1: 263 | DEBUG("ERROR: INVALID UNPADDED BASE64 STRING\n"); 264 | return -1; 265 | case 2: /* 2 chars in last block, must add 2 padding char */ 266 | if (max_len >= (ret + 2 + 1)) { 267 | out[ret] = code_pad; 268 | out[ret + 1] = code_pad; 269 | out[ret + 2] = 0; 270 | return ret + 2; 271 | } else { 272 | DEBUG("ERROR: not enough room to add padding in bin_to_b64\n"); 273 | return -1; 274 | } 275 | case 3: /* 3 chars in last block, must add 1 padding char */ 276 | if (max_len >= (ret + 1 + 1)) { 277 | out[ret] = code_pad; 278 | out[ret + 1] = 0; 279 | return ret + 1; 280 | } else { 281 | DEBUG("ERROR: not enough room to add padding in bin_to_b64\n"); 282 | return -1; 283 | } 284 | default: 285 | CRIT("switch default that should not be possible"); 286 | } 287 | } 288 | 289 | int b64_to_bin(const char * in, int size, uint8_t * out, int max_len) { 290 | if (in == NULL) { 291 | DEBUG("ERROR: NULL POINTER AS OUTPUT OR INPUT IN B64_TO_BIN\n"); 292 | return -1; 293 | } 294 | if ((size % 4 == 0) && (size >= 4)) { /* potentially padded Base64 */ 295 | if (in[size - 2] == code_pad) { /* 2 padding char to ignore */ 296 | return b64_to_bin_nopad(in, size - 2, out, max_len); 297 | } else if (in[size - 1] == code_pad) { /* 1 padding char to ignore */ 298 | return b64_to_bin_nopad(in, size - 1, out, max_len); 299 | } else { /* no padding to ignore */ 300 | return b64_to_bin_nopad(in, size, out, max_len); 301 | } 302 | } else { /* treat as unpadded Base64 */ 303 | return b64_to_bin_nopad(in, size, out, max_len); 304 | } 305 | } 306 | 307 | 308 | /* --- EOF ------------------------------------------------------------------ */ 309 | -------------------------------------------------------------------------------- /lora_pkt_fwd/src/jitqueue.c: -------------------------------------------------------------------------------- 1 | /* 2 | / _____) _ | | 3 | ( (____ _____ ____ _| |_ _____ ____| |__ 4 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 5 | _____) ) ____| | | || |_| ____( (___| | | | 6 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 7 | (C)2013 Semtech-Cycleo 8 | 9 | Description: 10 | LoRa concentrator : Just In Time TX scheduling queue 11 | 12 | License: Revised BSD License, see LICENSE.TXT file include in the project 13 | Maintainer: Michael Coracin 14 | */ 15 | 16 | /* -------------------------------------------------------------------------- */ 17 | /* --- DEPENDANCIES --------------------------------------------------------- */ 18 | 19 | #define _GNU_SOURCE /* needed for qsort_r to be defined */ 20 | #include /* qsort_r */ 21 | #include /* printf, fprintf, snprintf, fopen, fputs */ 22 | #include /* memset, memcpy */ 23 | #include 24 | #include 25 | #include 26 | 27 | #include "trace.h" 28 | #include "jitqueue.h" 29 | 30 | /* -------------------------------------------------------------------------- */ 31 | /* --- PRIVATE MACROS ------------------------------------------------------- */ 32 | 33 | /* -------------------------------------------------------------------------- */ 34 | /* --- PRIVATE CONSTANTS & TYPES -------------------------------------------- */ 35 | #define TX_START_DELAY 1500 /* microseconds */ 36 | /* TODO: get this value from HAL? */ 37 | #define TX_MARGIN_DELAY 1000 /* Packet overlap margin in microseconds */ 38 | /* TODO: How much margin should we take? */ 39 | #define TX_JIT_DELAY 50000 /* Pre-delay to program packet for TX in microseconds */ 40 | #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 */ 41 | 42 | #define BEACON_GUARD 3000000 /* Interval where no ping slot can be placed, 43 | to ensure beacon can be sent */ 44 | #define BEACON_RESERVED 2120000 /* Time on air of the beacon, with some margin */ 45 | 46 | /* -------------------------------------------------------------------------- */ 47 | /* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */ 48 | static pthread_mutex_t mx_jit_queue = PTHREAD_MUTEX_INITIALIZER; /* control access to JIT queue */ 49 | 50 | /* -------------------------------------------------------------------------- */ 51 | /* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ 52 | 53 | /* -------------------------------------------------------------------------- */ 54 | /* --- PUBLIC FUNCTIONS DEFINITION ----------------------------------------- */ 55 | 56 | bool jit_queue_is_full(struct jit_queue_s *queue) { 57 | bool result; 58 | 59 | pthread_mutex_lock(&mx_jit_queue); 60 | 61 | result = (queue->num_pkt == JIT_QUEUE_MAX) ? true : false; 62 | 63 | pthread_mutex_unlock(&mx_jit_queue); 64 | 65 | return result; 66 | } 67 | 68 | bool jit_queue_is_empty(struct jit_queue_s *queue) { 69 | bool result; 70 | 71 | pthread_mutex_lock(&mx_jit_queue); 72 | 73 | result = (queue->num_pkt == 0) ? true : false; 74 | 75 | pthread_mutex_unlock(&mx_jit_queue); 76 | 77 | return result; 78 | } 79 | 80 | void jit_queue_init(struct jit_queue_s *queue) { 81 | int i; 82 | 83 | pthread_mutex_lock(&mx_jit_queue); 84 | 85 | memset(queue, 0, sizeof(*queue)); 86 | for (i = 0; i < JIT_QUEUE_MAX; i++) { 87 | queue->nodes[i].pre_delay = 0; 88 | queue->nodes[i].post_delay = 0; 89 | } 90 | 91 | pthread_mutex_unlock(&mx_jit_queue); 92 | } 93 | 94 | int compare(const void *a, const void *b, void *arg) { 95 | struct jit_node_s *p = (struct jit_node_s *)a; 96 | struct jit_node_s *q = (struct jit_node_s *)b; 97 | int *counter = (int *)arg; 98 | int p_count, q_count; 99 | 100 | p_count = p->pkt.count_us; 101 | q_count = q->pkt.count_us; 102 | 103 | if (p_count > q_count) { 104 | *counter = *counter + 1; 105 | } 106 | 107 | return p_count - q_count; 108 | } 109 | 110 | void jit_sort_queue(struct jit_queue_s *queue) { 111 | int counter = 0; 112 | 113 | if (queue->num_pkt == 0) { 114 | return; 115 | } 116 | 117 | MSG_DEBUG(DEBUG_JIT, "sorting queue in ascending order packet timestamp - queue size:%u\n", queue->num_pkt); 118 | qsort_r(queue->nodes, queue->num_pkt, sizeof(queue->nodes[0]), compare, &counter); 119 | MSG_DEBUG(DEBUG_JIT, "sorting queue done - swapped:%d\n", counter); 120 | } 121 | 122 | 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) { 123 | if (((p1_count_us - p2_count_us) <= (p1_pre_delay + p2_post_delay + TX_MARGIN_DELAY)) || 124 | ((p2_count_us - p1_count_us) <= (p2_pre_delay + p1_post_delay + TX_MARGIN_DELAY))) { 125 | return true; 126 | } else { 127 | return false; 128 | } 129 | } 130 | 131 | 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) { 132 | int i = 0; 133 | uint32_t time_us = time->tv_sec * 1000000UL + time->tv_usec; /* convert time in µs */ 134 | uint32_t packet_post_delay = 0; 135 | uint32_t packet_pre_delay = 0; 136 | uint32_t target_pre_delay = 0; 137 | enum jit_error_e err_collision; 138 | uint32_t asap_count_us; 139 | 140 | MSG_DEBUG(DEBUG_JIT, "Current concentrator time is %u, pkt_type=%d\n", time_us, pkt_type); 141 | 142 | if (packet == NULL) { 143 | MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: invalid parameter\n"); 144 | return JIT_ERROR_INVALID; 145 | } 146 | 147 | if (jit_queue_is_full(queue)) { 148 | MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: cannot enqueue packet, JIT queue is full\n"); 149 | return JIT_ERROR_FULL; 150 | } 151 | 152 | /* Compute packet pre/post delays depending on packet's type */ 153 | switch (pkt_type) { 154 | case JIT_PKT_TYPE_DOWNLINK_CLASS_A: 155 | case JIT_PKT_TYPE_DOWNLINK_CLASS_B: 156 | case JIT_PKT_TYPE_DOWNLINK_CLASS_C: 157 | packet_pre_delay = TX_START_DELAY + TX_JIT_DELAY; 158 | packet_post_delay = lgw_time_on_air(packet) * 1000UL; /* in us */ 159 | break; 160 | case JIT_PKT_TYPE_BEACON: 161 | /* As defined in LoRaWAN spec */ 162 | packet_pre_delay = TX_START_DELAY + BEACON_GUARD + TX_JIT_DELAY; 163 | packet_post_delay = BEACON_RESERVED; 164 | break; 165 | default: 166 | break; 167 | } 168 | 169 | pthread_mutex_lock(&mx_jit_queue); 170 | 171 | /* An immediate downlink becomes a timestamped downlink "ASAP" */ 172 | /* Set the packet count_us to the first available slot */ 173 | if (pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_C) { 174 | /* change tx_mode to timestamped */ 175 | packet->tx_mode = TIMESTAMPED; 176 | 177 | /* Search for the ASAP timestamp to be given to the packet */ 178 | asap_count_us = time_us + 1E6; /* TODO: Take 1 second margin, to be refined */ 179 | if (queue->num_pkt == 0) { 180 | /* If the jit queue is empty, we can insert this packet */ 181 | MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink, first in JiT queue (count_us=%u)\n", asap_count_us); 182 | } else { 183 | /* Else we can try to insert it: 184 | - ASAP meaning NOW + MARGIN 185 | - at the last index of the queue 186 | - between 2 downlinks in the queue 187 | */ 188 | 189 | /* First, try if the ASAP time collides with an already enqueued downlink */ 190 | for (i = 0; i < queue->num_pkt; i++) { 191 | 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) { 192 | 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); 193 | break; 194 | } 195 | } 196 | if (i == queue->num_pkt) { 197 | /* No collision with ASAP time, we can insert it */ 198 | MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink ASAP at %u (no collision)\n", asap_count_us); 199 | } else { 200 | /* Search for the best slot then */ 201 | for (i = 0; i < queue->num_pkt; i++) { 202 | asap_count_us = queue->nodes[i].pkt.count_us + queue->nodes[i].post_delay + packet_pre_delay + TX_JIT_DELAY + TX_MARGIN_DELAY; 203 | if (i == (queue->num_pkt - 1)) { 204 | /* Last packet index, we can insert after this one */ 205 | MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink, last in JiT queue (count_us=%u)\n", asap_count_us); 206 | } else { 207 | /* Check if packet can be inserted between this index and the next one */ 208 | 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); 209 | 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) { 210 | MSG_DEBUG(DEBUG_JIT, "DEBUG: failed to insert IMMEDIATE downlink (count_us=%u), continue...\n", asap_count_us); 211 | continue; 212 | } else { 213 | MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink (count_us=%u)\n", asap_count_us); 214 | break; 215 | } 216 | } 217 | } 218 | } 219 | } 220 | /* Set packet with ASAP timestamp */ 221 | packet->count_us = asap_count_us; 222 | } 223 | 224 | /* Check criteria_1: is it already too late to send this packet ? 225 | * The packet should arrive at least at (tmst - TX_START_DELAY) to be programmed into concentrator 226 | * Note: - Also add some margin, to be checked how much is needed, if needed 227 | * - Valid for both Downlinks and Beacon packets 228 | * 229 | * Warning: unsigned arithmetic (handle roll-over) 230 | * t_packet < t_current + TX_START_DELAY + MARGIN 231 | */ 232 | if ((packet->count_us - time_us) <= (TX_START_DELAY + TX_MARGIN_DELAY + TX_JIT_DELAY)) { 233 | 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); 234 | pthread_mutex_unlock(&mx_jit_queue); 235 | return JIT_ERROR_TOO_LATE; 236 | } 237 | 238 | /* Check criteria_2: Does packet timestamp seem plausible compared to current time 239 | * We do not expect the server to program a downlink too early compared to current time 240 | * Class A: downlink has to be sent in a 1s or 2s time window after RX 241 | * Class B: downlink has to occur in a 128s time window 242 | * Class C: no check needed, departure time has been calculated previously 243 | * So let's define a safe delay above which we can say that the packet is out of bound: TX_MAX_ADVANCE_DELAY 244 | * Note: - Valid for Downlinks only, not for Beacon packets 245 | * 246 | * Warning: unsigned arithmetic (handle roll-over) 247 | t_packet > t_current + TX_MAX_ADVANCE_DELAY 248 | */ 249 | if ((pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_A) || (pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_B)) { 250 | if ((packet->count_us - time_us) > TX_MAX_ADVANCE_DELAY) { 251 | 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); 252 | pthread_mutex_unlock(&mx_jit_queue); 253 | return JIT_ERROR_TOO_EARLY; 254 | } 255 | } 256 | 257 | /* Check criteria_3: does this new packet overlap with a packet already enqueued ? 258 | * Note: - need to take into account packet's pre_delay and post_delay of each packet 259 | * - Valid for both Downlinks and beacon packets 260 | * - Beacon guard can be ignored if we try to queue a Class A downlink 261 | */ 262 | for (i = 0; i < queue->num_pkt; i++) { 263 | /* We ignore Beacon Guard for Class A/C downlinks */ 264 | 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)) { 265 | target_pre_delay = TX_START_DELAY; 266 | } else { 267 | target_pre_delay = queue->nodes[i].pre_delay; 268 | } 269 | 270 | /* Check if there is a collision 271 | * Warning: unsigned arithmetic (handle roll-over) 272 | * t_packet_new - pre_delay_packet_new < t_packet_prev + post_delay_packet_prev (OVERLAP on post delay) 273 | * t_packet_new + post_delay_packet_new > t_packet_prev - pre_delay_packet_prev (OVERLAP on pre delay) 274 | */ 275 | 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) { 276 | switch (queue->nodes[i].pkt_type) { 277 | case JIT_PKT_TYPE_DOWNLINK_CLASS_A: 278 | case JIT_PKT_TYPE_DOWNLINK_CLASS_B: 279 | case JIT_PKT_TYPE_DOWNLINK_CLASS_C: 280 | 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); 281 | err_collision = JIT_ERROR_COLLISION_PACKET; 282 | break; 283 | case JIT_PKT_TYPE_BEACON: 284 | if (pkt_type != JIT_PKT_TYPE_BEACON) { 285 | /* do not overload logs for beacon/beacon collision, as it is expected to happen with beacon pre-scheduling algorith used */ 286 | 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); 287 | } 288 | err_collision = JIT_ERROR_COLLISION_BEACON; 289 | break; 290 | default: 291 | MSG("ERROR: Unknown packet type, should not occur, BUG?\n"); 292 | assert(0); 293 | break; 294 | } 295 | pthread_mutex_unlock(&mx_jit_queue); 296 | return err_collision; 297 | } 298 | } 299 | 300 | /* Finally enqueue it */ 301 | /* Insert packet at the end of the queue */ 302 | memcpy(&(queue->nodes[queue->num_pkt].pkt), packet, sizeof(struct lgw_pkt_tx_s)); 303 | queue->nodes[queue->num_pkt].pre_delay = packet_pre_delay; 304 | queue->nodes[queue->num_pkt].post_delay = packet_post_delay; 305 | queue->nodes[queue->num_pkt].pkt_type = pkt_type; 306 | if (pkt_type == JIT_PKT_TYPE_BEACON) { 307 | queue->num_beacon++; 308 | } 309 | queue->num_pkt++; 310 | /* Sort the queue in ascending order of packet timestamp */ 311 | jit_sort_queue(queue); 312 | 313 | /* Done */ 314 | pthread_mutex_unlock(&mx_jit_queue); 315 | 316 | jit_print_queue(queue, false, DEBUG_JIT); 317 | 318 | 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); 319 | 320 | return JIT_ERROR_OK; 321 | } 322 | 323 | 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) { 324 | if (packet == NULL) { 325 | MSG("ERROR: invalid parameter\n"); 326 | return JIT_ERROR_INVALID; 327 | } 328 | 329 | if ((index < 0) || (index >= JIT_QUEUE_MAX)) { 330 | MSG("ERROR: invalid parameter\n"); 331 | return JIT_ERROR_INVALID; 332 | } 333 | 334 | if (jit_queue_is_empty(queue)) { 335 | MSG("ERROR: cannot dequeue packet, JIT queue is empty\n"); 336 | return JIT_ERROR_EMPTY; 337 | } 338 | 339 | pthread_mutex_lock(&mx_jit_queue); 340 | 341 | /* Dequeue requested packet */ 342 | memcpy(packet, &(queue->nodes[index].pkt), sizeof(struct lgw_pkt_tx_s)); 343 | queue->num_pkt--; 344 | *pkt_type = queue->nodes[index].pkt_type; 345 | if (*pkt_type == JIT_PKT_TYPE_BEACON) { 346 | queue->num_beacon--; 347 | MSG_DEBUG(DEBUG_BEACON, "--- Beacon dequeued ---\n"); 348 | } 349 | 350 | /* Replace dequeued packet with last packet of the queue */ 351 | memcpy(&(queue->nodes[index]), &(queue->nodes[queue->num_pkt]), sizeof(struct jit_node_s)); 352 | memset(&(queue->nodes[queue->num_pkt]), 0, sizeof(struct jit_node_s)); 353 | 354 | /* Sort queue in ascending order of packet timestamp */ 355 | jit_sort_queue(queue); 356 | 357 | /* Done */ 358 | pthread_mutex_unlock(&mx_jit_queue); 359 | 360 | jit_print_queue(queue, false, DEBUG_JIT); 361 | 362 | MSG_DEBUG(DEBUG_JIT, "dequeued packet with count_us=%u from index %d\n", packet->count_us, index); 363 | 364 | return JIT_ERROR_OK; 365 | } 366 | 367 | enum jit_error_e jit_peek(struct jit_queue_s *queue, struct timeval *time, int *pkt_idx) { 368 | /* Return index of node containing a packet inline with given time */ 369 | int i = 0; 370 | int idx_highest_priority = -1; 371 | uint32_t time_us; 372 | 373 | if ((time == NULL) || (pkt_idx == NULL)) { 374 | MSG("ERROR: invalid parameter\n"); 375 | return JIT_ERROR_INVALID; 376 | } 377 | 378 | if (jit_queue_is_empty(queue)) { 379 | return JIT_ERROR_EMPTY; 380 | } 381 | 382 | time_us = time->tv_sec * 1000000UL + time->tv_usec; 383 | 384 | pthread_mutex_lock(&mx_jit_queue); 385 | 386 | /* Search for highest priority packet to be sent */ 387 | for (i = 0; i < queue->num_pkt; i++) { 388 | /* First check if that packet is outdated: 389 | * If a packet seems too much in advance, and was not rejected at enqueue time, 390 | * it means that we missed it for peeking, we need to drop it 391 | * 392 | * Warning: unsigned arithmetic 393 | * t_packet > t_current + TX_MAX_ADVANCE_DELAY 394 | */ 395 | if ((queue->nodes[i].pkt.count_us - time_us) >= TX_MAX_ADVANCE_DELAY) { 396 | /* We drop the packet to avoid lock-up */ 397 | queue->num_pkt--; 398 | if (queue->nodes[i].pkt_type == JIT_PKT_TYPE_BEACON) { 399 | queue->num_beacon--; 400 | MSG("WARNING: --- Beacon dropped (current_time=%u, packet_time=%u) ---\n", time_us, queue->nodes[i].pkt.count_us); 401 | } else { 402 | MSG("WARNING: --- Packet dropped (current_time=%u, packet_time=%u) ---\n", time_us, queue->nodes[i].pkt.count_us); 403 | } 404 | 405 | /* Replace dropped packet with last packet of the queue */ 406 | memcpy(&(queue->nodes[i]), &(queue->nodes[queue->num_pkt]), sizeof(struct jit_node_s)); 407 | memset(&(queue->nodes[queue->num_pkt]), 0, sizeof(struct jit_node_s)); 408 | 409 | /* Sort queue in ascending order of packet timestamp */ 410 | jit_sort_queue(queue); 411 | 412 | /* restart loop after purge to find packet to be sent */ 413 | i = 0; 414 | continue; 415 | } 416 | 417 | /* Then look for highest priority packet to be sent: 418 | * Warning: unsigned arithmetic (handle roll-over) 419 | * t_packet < t_highest 420 | */ 421 | if ((idx_highest_priority == -1) || (((queue->nodes[i].pkt.count_us - time_us) < (queue->nodes[idx_highest_priority].pkt.count_us - time_us)))) { 422 | idx_highest_priority = i; 423 | } 424 | } 425 | 426 | /* Peek criteria 1: look for a packet to be sent in next TX_JIT_DELAY ms timeframe 427 | * Warning: unsigned arithmetic (handle roll-over) 428 | * t_packet < t_current + TX_JIT_DELAY 429 | */ 430 | if ((queue->nodes[idx_highest_priority].pkt.count_us - time_us) < TX_JIT_DELAY) { 431 | *pkt_idx = idx_highest_priority; 432 | MSG_DEBUG(DEBUG_JIT, "peek packet with count_us=%u at index %d\n", 433 | queue->nodes[idx_highest_priority].pkt.count_us, idx_highest_priority); 434 | } else { 435 | *pkt_idx = -1; 436 | } 437 | 438 | pthread_mutex_unlock(&mx_jit_queue); 439 | 440 | return JIT_ERROR_OK; 441 | } 442 | 443 | void jit_print_queue(struct jit_queue_s *queue, bool show_all, int debug_level) { 444 | int i = 0; 445 | int loop_end; 446 | 447 | if (jit_queue_is_empty(queue)) { 448 | MSG_DEBUG(debug_level, "INFO: [jit] queue is empty\n"); 449 | } else { 450 | pthread_mutex_lock(&mx_jit_queue); 451 | 452 | MSG_DEBUG(debug_level, "INFO: [jit] queue contains %d packets:\n", queue->num_pkt); 453 | MSG_DEBUG(debug_level, "INFO: [jit] queue contains %d beacons:\n", queue->num_beacon); 454 | loop_end = (show_all == true) ? JIT_QUEUE_MAX : queue->num_pkt; 455 | for (i = 0; i < loop_end; i++) { 456 | MSG_DEBUG(debug_level, " - node[%d]: count_us=%u - type=%d\n", 457 | i, 458 | queue->nodes[i].pkt.count_us, 459 | queue->nodes[i].pkt_type); 460 | } 461 | 462 | pthread_mutex_unlock(&mx_jit_queue); 463 | } 464 | } 465 | 466 | -------------------------------------------------------------------------------- /lora_pkt_fwd/src/timersync.c: -------------------------------------------------------------------------------- 1 | /* 2 | / _____) _ | | 3 | ( (____ _____ ____ _| |_ _____ ____| |__ 4 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 5 | _____) ) ____| | | || |_| ____( (___| | | | 6 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 7 | (C)2013 Semtech-Cycleo 8 | 9 | Description: 10 | LoRa concentrator : Timer synchronization 11 | Provides synchronization between unix, concentrator and gps clocks 12 | 13 | License: Revised BSD License, see LICENSE.TXT file include in the project 14 | Maintainer: Michael Coracin 15 | */ 16 | 17 | /* -------------------------------------------------------------------------- */ 18 | /* --- DEPENDANCIES --------------------------------------------------------- */ 19 | 20 | #include /* printf, fprintf, snprintf, fopen, fputs */ 21 | #include /* C99 types */ 22 | #include 23 | 24 | #include "trace.h" 25 | #include "timersync.h" 26 | #include "loragw_hal.h" 27 | #include "loragw_reg.h" 28 | #include "loragw_aux.h" 29 | 30 | /* -------------------------------------------------------------------------- */ 31 | /* --- PRIVATE CONSTANTS & TYPES -------------------------------------------- */ 32 | 33 | /* -------------------------------------------------------------------------- */ 34 | /* --- PRIVATE MACROS ------------------------------------------------------- */ 35 | 36 | #define timersub(a, b, result) \ 37 | do { \ 38 | (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ 39 | (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ 40 | if ((result)->tv_usec < 0) { \ 41 | --(result)->tv_sec; \ 42 | (result)->tv_usec += 1000000; \ 43 | } \ 44 | } while (0) 45 | 46 | /* -------------------------------------------------------------------------- */ 47 | /* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */ 48 | 49 | static pthread_mutex_t mx_timersync = PTHREAD_MUTEX_INITIALIZER; /* control access to timer sync offsets */ 50 | static struct timeval offset_unix_concent = {0, 0}; /* timer offset between unix host and concentrator */ 51 | 52 | /* -------------------------------------------------------------------------- */ 53 | /* --- PRIVATE SHARED VARIABLES (GLOBAL) ------------------------------------ */ 54 | extern bool exit_sig; 55 | extern bool quit_sig; 56 | extern pthread_mutex_t mx_concent; 57 | 58 | /* -------------------------------------------------------------------------- */ 59 | /* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ 60 | 61 | int get_concentrator_time(struct timeval *concent_time, struct timeval unix_time) { 62 | struct timeval local_timeval; 63 | 64 | if (concent_time == NULL) { 65 | MSG("ERROR: %s invalid parameter\n", __FUNCTION__); 66 | return -1; 67 | } 68 | 69 | pthread_mutex_lock(&mx_timersync); /* protect global variable access */ 70 | timersub(&unix_time, &offset_unix_concent, &local_timeval); 71 | pthread_mutex_unlock(&mx_timersync); 72 | 73 | /* TODO: handle sx1301 coutner wrap-up !! */ 74 | concent_time->tv_sec = local_timeval.tv_sec; 75 | concent_time->tv_usec = local_timeval.tv_usec; 76 | 77 | MSG_DEBUG(DEBUG_TIMERSYNC, " --> TIME: unix current time is %ld,%ld\n", unix_time.tv_sec, unix_time.tv_usec); 78 | MSG_DEBUG(DEBUG_TIMERSYNC, " offset is %ld,%ld\n", offset_unix_concent.tv_sec, offset_unix_concent.tv_usec); 79 | MSG_DEBUG(DEBUG_TIMERSYNC, " sx1301 current time is %ld,%ld\n", local_timeval.tv_sec, local_timeval.tv_usec); 80 | 81 | return 0; 82 | } 83 | 84 | /* ---------------------------------------------------------------------------------------------- */ 85 | /* --- THREAD 6: REGULARLAY MONITOR THE OFFSET BETWEEN UNIX CLOCK AND CONCENTRATOR CLOCK -------- */ 86 | 87 | void thread_timersync(void) { 88 | struct timeval unix_timeval; 89 | struct timeval concentrator_timeval; 90 | uint32_t sx1301_timecount = 0; 91 | struct timeval offset_previous = {0, 0}; 92 | struct timeval offset_drift = {0, 0}; /* delta between current and previous offset */ 93 | 94 | while (!exit_sig && !quit_sig) { 95 | /* Get current unix time */ 96 | gettimeofday(&unix_timeval, NULL); 97 | 98 | /* Get current concentrator counter value (1MHz) */ 99 | lgw_get_trigcnt(&sx1301_timecount); 100 | 101 | concentrator_timeval.tv_sec = sx1301_timecount / 1000000UL; 102 | concentrator_timeval.tv_usec = sx1301_timecount - (concentrator_timeval.tv_sec * 1000000UL); 103 | 104 | /* Compute offset between unix and concentrator timers, with microsecond precision */ 105 | offset_previous.tv_sec = offset_unix_concent.tv_sec; 106 | offset_previous.tv_usec = offset_unix_concent.tv_usec; 107 | 108 | pthread_mutex_lock(&mx_timersync); /* protect global variable access */ 109 | timersub(&unix_timeval, &concentrator_timeval, &offset_unix_concent); 110 | pthread_mutex_unlock(&mx_timersync); 111 | 112 | timersub(&offset_unix_concent, &offset_previous, &offset_drift); 113 | 114 | MSG_DEBUG(DEBUG_TIMERSYNC, " sx1301 = %u (µs) - timeval (%ld,%ld)\n", 115 | sx1301_timecount, 116 | concentrator_timeval.tv_sec, 117 | concentrator_timeval.tv_usec); 118 | MSG_DEBUG(DEBUG_TIMERSYNC, " unix_timeval = %ld,%ld\n", unix_timeval.tv_sec, unix_timeval.tv_usec); 119 | 120 | MSG("INFO: host/sx1301 time offset=(%lds:%ldµs) - drift=%ldµs\n", 121 | offset_unix_concent.tv_sec, 122 | offset_unix_concent.tv_usec, 123 | offset_drift.tv_sec * 1000000UL + offset_drift.tv_usec); 124 | 125 | /* delay next sync */ 126 | /* If we consider a crystal oscillator precision of about 20ppm worst case, and a clock 127 | running at 1MHz, this would mean 1µs drift every 50000µs (10000000/20). 128 | As here the time precision is not critical, we should be able to cope with at least 1ms drift, 129 | which should occur after 50s (50000µs * 1000). 130 | Let's set the thread sleep to 1 minute for now */ 131 | wait_ms(60000); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /lora_pkt_fwd/update_gwid.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script is a helper to update the Gateway_ID field of given 4 | # JSON configuration file, as a EUI-64 address generated from the 64-bits 5 | # STM32 unique ID of the PicoCell Gateway board. 6 | # 7 | # Usage examples: 8 | # ./update_gwid.sh ./local_conf.json 9 | 10 | iot_sk_update_gwid() { 11 | # get gateway ID from STM32 unique ID to generate an EUI-64 address 12 | GWID=$(./util_chip_id) 13 | 14 | # replace the default gateway ID by actual GWID, in given JSON configuration file 15 | sed -i 's/\(^\s*"gateway_ID":\s*"\).\{16\}"\s*\(,\?\).*$/\1'${GWID}'"\2/' $1 16 | 17 | echo "Gateway_ID set to "$GWID" in file "$1 18 | } 19 | 20 | if [ $# -ne 1 ] 21 | then 22 | echo "Usage: $0 [filename]" 23 | echo " filename: Path to JSON file containing Gateway_ID for packet forwarder" 24 | exit 1 25 | fi 26 | 27 | # Check if util_chip_id is there, necessary to get STM32 unique ID 28 | if ! [ -x "$(command -v ./util_chip_id)" ]; then 29 | echo 'Error: ./util_chip_id not found.' >&2 30 | exit 1 31 | fi 32 | 33 | iot_sk_update_gwid $1 34 | 35 | exit 0 36 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lora-net/picoGW_packet_forwarder/f25d7b5210a99c3c1a204b512ef12b06001c9887/readme.md -------------------------------------------------------------------------------- /util_ack/Makefile: -------------------------------------------------------------------------------- 1 | ### Application-specific constants 2 | 3 | APP_NAME := util_ack 4 | 5 | ### Constant symbols 6 | 7 | CC := $(CROSS_COMPILE)gcc 8 | AR := $(CROSS_COMPILE)ar 9 | 10 | CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I. 11 | 12 | OBJDIR = obj 13 | 14 | ### General build targets 15 | 16 | all: $(APP_NAME) 17 | 18 | clean: 19 | rm -f $(OBJDIR)/*.o 20 | rm -f $(APP_NAME) 21 | 22 | ### Main program compilation and assembly 23 | 24 | $(OBJDIR): 25 | mkdir -p $(OBJDIR) 26 | 27 | $(OBJDIR)/%.o: src/%.c | $(OBJDIR) 28 | $(CC) -c $(CFLAGS) $< -o $@ 29 | 30 | $(APP_NAME): $(OBJDIR)/$(APP_NAME).o 31 | $(CC) $< -o $@ 32 | 33 | ### EOF -------------------------------------------------------------------------------- /util_ack/readme.md: -------------------------------------------------------------------------------- 1 | / _____) _ | | 2 | ( (____ _____ ____ _| |_ _____ ____| |__ 3 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 4 | _____) ) ____| | | || |_| ____( (___| | | | 5 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 6 | (C)2013 Semtech-Cycleo 7 | 8 | Utility: packet acknowledger 9 | ============================= 10 | 11 | 1. Introduction 12 | ---------------- 13 | 14 | The packet acknowledger is a simple helper program listening on a single UDP 15 | port and responding to PUSH_DATA datagrams with PUSH_ACK, and to PULL_DATA 16 | datagrams with PULL_ACK. 17 | 18 | Informations about the datagrams received and the answers send are display on 19 | screen to help communication debugging. 20 | 21 | Packets not following the protocol detailed in the PROTOCOL.TXT document in the 22 | basic_pkt_fwt directory are ignored. 23 | 24 | 2. Dependencies 25 | ---------------- 26 | 27 | This program follows the v1.1 version of the gateway-to-server protocol. 28 | 29 | 3. Usage 30 | --------- 31 | 32 | Start the program with the port number as first and only argument. 33 | 34 | To stop the application, press Ctrl+C. 35 | 36 | 4. License 37 | ----------- 38 | 39 | Copyright (C) 2013, SEMTECH S.A. 40 | All rights reserved. 41 | 42 | Redistribution and use in source and binary forms, with or without 43 | modification, are permitted provided that the following conditions are met: 44 | 45 | * Redistributions of source code must retain the above copyright 46 | notice, this list of conditions and the following disclaimer. 47 | * Redistributions in binary form must reproduce the above copyright 48 | notice, this list of conditions and the following disclaimer in the 49 | documentation and/or other materials provided with the distribution. 50 | * Neither the name of the Semtech corporation nor the 51 | names of its contributors may be used to endorse or promote products 52 | derived from this software without specific prior written permission. 53 | 54 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 55 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 56 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 57 | DISCLAIMED. IN NO EVENT SHALL SEMTECH S.A. BE LIABLE FOR ANY 58 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 59 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 60 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 61 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 62 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 63 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 64 | 65 | *EOF* -------------------------------------------------------------------------------- /util_ack/src/util_ack.c: -------------------------------------------------------------------------------- 1 | /* 2 | / _____) _ | | 3 | ( (____ _____ ____ _| |_ _____ ____| |__ 4 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 5 | _____) ) ____| | | || |_| ____( (___| | | | 6 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 7 | (C)2013 Semtech-Cycleo 8 | 9 | Description: 10 | Network sink, receives UDP packets and sends an acknowledge 11 | 12 | License: Revised BSD License, see LICENSE.TXT file include in the project 13 | Maintainer: Sylvain Miermont 14 | */ 15 | 16 | 17 | /* -------------------------------------------------------------------------- */ 18 | /* --- DEPENDANCIES --------------------------------------------------------- */ 19 | 20 | /* fix an issue between POSIX and C99 */ 21 | #if __STDC_VERSION__ >= 199901L 22 | #define _XOPEN_SOURCE 600 23 | #else 24 | #define _XOPEN_SOURCE 500 25 | #endif 26 | 27 | #include /* C99 types */ 28 | #include /* printf, fprintf, sprintf, fopen, fputs */ 29 | #include /* usleep */ 30 | 31 | #include /* memset */ 32 | #include /* time, clock_gettime, strftime, gmtime, clock_nanosleep*/ 33 | #include /* atoi, exit */ 34 | #include /* error messages */ 35 | 36 | #include /* socket specific definitions */ 37 | #include /* INET constants and stuff */ 38 | #include /* IP address conversion stuff */ 39 | #include /* gai_strerror */ 40 | 41 | /* -------------------------------------------------------------------------- */ 42 | /* --- PRIVATE MACROS ------------------------------------------------------- */ 43 | 44 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) 45 | #define STRINGIFY(x) #x 46 | #define STR(x) STRINGIFY(x) 47 | #define MSG(args...) fprintf(stderr, args) /* message that is destined to the user */ 48 | 49 | /* -------------------------------------------------------------------------- */ 50 | /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ 51 | 52 | #define PROTOCOL_VERSION 2 53 | 54 | #define PKT_PUSH_DATA 0 55 | #define PKT_PUSH_ACK 1 56 | #define PKT_PULL_DATA 2 57 | #define PKT_PULL_RESP 3 58 | #define PKT_PULL_ACK 4 59 | 60 | /* -------------------------------------------------------------------------- */ 61 | /* --- MAIN FUNCTION -------------------------------------------------------- */ 62 | 63 | int main(int argc, char **argv) { 64 | int i; /* loop variable and temporary variable for return value */ 65 | 66 | /* server socket creation */ 67 | int sock; /* socket file descriptor */ 68 | struct addrinfo hints; 69 | struct addrinfo *result; /* store result of getaddrinfo */ 70 | struct addrinfo *q; /* pointer to move into *result data */ 71 | char host_name[64]; 72 | char port_name[64]; 73 | 74 | /* variables for receiving and sending packets */ 75 | struct sockaddr_storage dist_addr; 76 | socklen_t addr_len = sizeof dist_addr; 77 | uint8_t databuf[4096]; 78 | int byte_nb; 79 | 80 | /* variables for protocol management */ 81 | uint32_t raw_mac_h; /* Most Significant Nibble, network order */ 82 | uint32_t raw_mac_l; /* Least Significant Nibble, network order */ 83 | uint64_t gw_mac; /* MAC address of the client (gateway) */ 84 | uint8_t ack_command; 85 | 86 | /* check if port number was passed as parameter */ 87 | if (argc != 2) { 88 | MSG("Usage: util_ack \n"); 89 | exit(EXIT_FAILURE); 90 | } 91 | 92 | /* prepare hints to open network sockets */ 93 | memset(&hints, 0, sizeof hints); 94 | hints.ai_family = AF_UNSPEC; /* should handle IP v4 or v6 automatically */ 95 | hints.ai_socktype = SOCK_DGRAM; 96 | hints.ai_flags = AI_PASSIVE; /* will assign local IP automatically */ 97 | 98 | /* look for address */ 99 | i = getaddrinfo(NULL, argv[1], &hints, &result); 100 | if (i != 0) { 101 | MSG("ERROR: getaddrinfo returned %s\n", gai_strerror(i)); 102 | exit(EXIT_FAILURE); 103 | } 104 | 105 | /* try to open socket and bind it */ 106 | for (q = result; q != NULL; q = q->ai_next) { 107 | sock = socket(q->ai_family, q->ai_socktype, q->ai_protocol); 108 | if (sock == -1) { 109 | continue; /* socket failed, try next field */ 110 | } else { 111 | i = bind(sock, q->ai_addr, q->ai_addrlen); 112 | if (i == -1) { 113 | shutdown(sock, SHUT_RDWR); 114 | continue; /* bind failed, try next field */ 115 | } else { 116 | break; /* success, get out of loop */ 117 | } 118 | } 119 | } 120 | if (q == NULL) { 121 | MSG("ERROR: failed to open socket or to bind to it\n"); 122 | i = 1; 123 | for (q = result; q != NULL; q = q->ai_next) { 124 | getnameinfo(q->ai_addr, q->ai_addrlen, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST); 125 | MSG("INFO: result %i host:%s service:%s\n", i, host_name, port_name); 126 | ++i; 127 | } 128 | exit(EXIT_FAILURE); 129 | } 130 | MSG("INFO: util_ack listening on port %s\n", argv[1]); 131 | freeaddrinfo(result); 132 | 133 | while (1) { 134 | /* wait to receive a packet */ 135 | byte_nb = recvfrom(sock, databuf, sizeof databuf, 0, (struct sockaddr *)&dist_addr, &addr_len); 136 | if (byte_nb == -1) { 137 | MSG("ERROR: recvfrom returned %s \n", strerror(errno)); 138 | exit(EXIT_FAILURE); 139 | } 140 | 141 | /* display info about the sender */ 142 | i = getnameinfo((struct sockaddr *)&dist_addr, addr_len, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST); 143 | if (i == -1) { 144 | MSG("ERROR: getnameinfo returned %s \n", gai_strerror(i)); 145 | exit(EXIT_FAILURE); 146 | } 147 | printf(" -> pkt in , host %s (port %s), %i bytes", host_name, port_name, byte_nb); 148 | 149 | /* check and parse the payload */ 150 | if (byte_nb < 12) { /* not enough bytes for packet from gateway */ 151 | printf(" (too short for GW <-> MAC protocol)\n"); 152 | continue; 153 | } 154 | /* don't touch the token in position 1-2, it will be sent back "as is" for acknowledgement */ 155 | if (databuf[0] != PROTOCOL_VERSION) { /* check protocol version number */ 156 | printf(", invalid version %u\n", databuf[0]); 157 | continue; 158 | } 159 | raw_mac_h = *((uint32_t *)(databuf + 4)); 160 | raw_mac_l = *((uint32_t *)(databuf + 8)); 161 | gw_mac = ((uint64_t)ntohl(raw_mac_h) << 32) + (uint64_t)ntohl(raw_mac_l); 162 | 163 | /* interpret gateway command */ 164 | switch (databuf[3]) { 165 | case PKT_PUSH_DATA: 166 | printf(", PUSH_DATA from gateway 0x%08X%08X\n", (uint32_t)(gw_mac >> 32), (uint32_t)(gw_mac & 0xFFFFFFFF)); 167 | ack_command = PKT_PUSH_ACK; 168 | printf("<- pkt out, PUSH_ACK for host %s (port %s)", host_name, port_name); 169 | break; 170 | case PKT_PULL_DATA: 171 | printf(", PULL_DATA from gateway 0x%08X%08X\n", (uint32_t)(gw_mac >> 32), (uint32_t)(gw_mac & 0xFFFFFFFF)); 172 | ack_command = PKT_PULL_ACK; 173 | printf("<- pkt out, PULL_ACK for host %s (port %s)", host_name, port_name); 174 | break; 175 | default: 176 | printf(", unexpected command %u\n", databuf[3]); 177 | continue; 178 | } 179 | 180 | /* add some artificial latency */ 181 | usleep(30000); /* 30 ms */ 182 | 183 | /* send acknowledge and check return value */ 184 | databuf[3] = ack_command; 185 | byte_nb = sendto(sock, (void *)databuf, 4, 0, (struct sockaddr *)&dist_addr, addr_len); 186 | if (byte_nb == -1) { 187 | printf(", send error:%s\n", strerror(errno)); 188 | } else { 189 | printf(", %i bytes sent\n", byte_nb); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /util_sink/Makefile: -------------------------------------------------------------------------------- 1 | ### Application-specific constants 2 | 3 | APP_NAME := util_sink 4 | 5 | ### Constant symbols 6 | 7 | CC := $(CROSS_COMPILE)gcc 8 | AR := $(CROSS_COMPILE)ar 9 | 10 | CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I. 11 | 12 | OBJDIR = obj 13 | 14 | ### General build targets 15 | 16 | all: $(APP_NAME) 17 | 18 | clean: 19 | rm -f $(OBJDIR)/*.o 20 | rm -f $(APP_NAME) 21 | 22 | ### Main program compilation and assembly 23 | 24 | $(OBJDIR): 25 | mkdir -p $(OBJDIR) 26 | 27 | $(OBJDIR)/%.o: src/%.c | $(OBJDIR) 28 | $(CC) -c $(CFLAGS) $< -o $@ 29 | 30 | $(APP_NAME): $(OBJDIR)/$(APP_NAME).o 31 | $(CC) $< -o $@ 32 | 33 | ### EOF 34 | -------------------------------------------------------------------------------- /util_sink/readme.md: -------------------------------------------------------------------------------- 1 | / _____) _ | | 2 | ( (____ _____ ____ _| |_ _____ ____| |__ 3 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 4 | _____) ) ____| | | || |_| ____( (___| | | | 5 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 6 | (C)2013 Semtech-Cycleo 7 | 8 | Utility: packet sink 9 | ===================== 10 | 11 | 1. Introduction 12 | ---------------- 13 | 14 | The packet sink is a simple helper program listening on a single port for UDP 15 | datagrams, and displaying a message each time one is received. The content of 16 | the datagram itself is ignored. 17 | 18 | This allow to test another software (locally or on another computer) that 19 | sends UDP datagrams without having ICMP 'port closed' errors each time. 20 | 21 | 2. Dependencies 22 | ---------------- 23 | 24 | None. 25 | 26 | 3. Usage 27 | --------- 28 | 29 | Start the program with the port number as first and only argument. 30 | 31 | To stop the application, press Ctrl+C. 32 | 33 | 4. License 34 | ----------- 35 | 36 | Copyright (C) 2013, SEMTECH S.A. 37 | All rights reserved. 38 | 39 | Redistribution and use in source and binary forms, with or without 40 | modification, are permitted provided that the following conditions are met: 41 | 42 | * Redistributions of source code must retain the above copyright 43 | notice, this list of conditions and the following disclaimer. 44 | * Redistributions in binary form must reproduce the above copyright 45 | notice, this list of conditions and the following disclaimer in the 46 | documentation and/or other materials provided with the distribution. 47 | * Neither the name of the Semtech corporation nor the 48 | names of its contributors may be used to endorse or promote products 49 | derived from this software without specific prior written permission. 50 | 51 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 52 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 53 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 54 | DISCLAIMED. IN NO EVENT SHALL SEMTECH S.A. BE LIABLE FOR ANY 55 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 56 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 57 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 58 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 59 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 60 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 61 | 62 | *EOF* -------------------------------------------------------------------------------- /util_sink/src/util_sink.c: -------------------------------------------------------------------------------- 1 | /* 2 | / _____) _ | | 3 | ( (____ _____ ____ _| |_ _____ ____| |__ 4 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 5 | _____) ) ____| | | || |_| ____( (___| | | | 6 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 7 | (C)2013 Semtech-Cycleo 8 | 9 | Description: 10 | Network sink, receives UDP packets on certain ports and discards them 11 | 12 | License: Revised BSD License, see LICENSE.TXT file include in the project 13 | Maintainer: Sylvain Miermont 14 | */ 15 | 16 | 17 | /* -------------------------------------------------------------------------- */ 18 | /* --- DEPENDANCIES --------------------------------------------------------- */ 19 | 20 | /* fix an issue between POSIX and C99 */ 21 | #if __STDC_VERSION__ >= 199901L 22 | #define _XOPEN_SOURCE 600 23 | #else 24 | #define _XOPEN_SOURCE 500 25 | #endif 26 | 27 | #include /* C99 types */ 28 | #include /* printf, fprintf, sprintf, fopen, fputs */ 29 | 30 | #include /* memset */ 31 | #include /* time, clock_gettime, strftime, gmtime, clock_nanosleep*/ 32 | #include /* atoi, exit */ 33 | #include /* error messages */ 34 | 35 | #include /* socket specific definitions */ 36 | #include /* INET constants and stuff */ 37 | #include /* IP address conversion stuff */ 38 | #include /* gai_strerror */ 39 | 40 | /* -------------------------------------------------------------------------- */ 41 | /* --- PRIVATE MACROS ------------------------------------------------------- */ 42 | 43 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) 44 | #define STRINGIFY(x) #x 45 | #define STR(x) STRINGIFY(x) 46 | #define MSG(args...) fprintf(stderr, args) /* message that is destined to the user */ 47 | 48 | /* -------------------------------------------------------------------------- */ 49 | /* --- MAIN FUNCTION -------------------------------------------------------- */ 50 | 51 | int main(int argc, char **argv) { 52 | int i; /* loop variable and temporary variable for return value */ 53 | 54 | /* server socket creation */ 55 | int sock; /* socket file descriptor */ 56 | struct addrinfo hints; 57 | struct addrinfo *result; /* store result of getaddrinfo */ 58 | struct addrinfo *q; /* pointer to move into *result data */ 59 | char host_name[64]; 60 | char port_name[64]; 61 | 62 | /* variables for receiving packets */ 63 | struct sockaddr_storage dist_addr; 64 | socklen_t addr_len = sizeof dist_addr; 65 | uint8_t databuf[4096]; 66 | int byte_nb; 67 | 68 | /* check if port number was passed as parameter */ 69 | if (argc != 2) { 70 | MSG("Usage: util_sink \n"); 71 | exit(EXIT_FAILURE); 72 | } 73 | 74 | /* prepare hints to open network sockets */ 75 | memset(&hints, 0, sizeof hints); 76 | hints.ai_family = AF_UNSPEC; /* should handle IP v4 or v6 automatically */ 77 | hints.ai_socktype = SOCK_DGRAM; 78 | hints.ai_flags = AI_PASSIVE; /* will assign local IP automatically */ 79 | 80 | /* look for address */ 81 | i = getaddrinfo(NULL, argv[1], &hints, &result); 82 | if (i != 0) { 83 | MSG("ERROR: getaddrinfo returned %s\n", gai_strerror(i)); 84 | exit(EXIT_FAILURE); 85 | } 86 | 87 | /* try to open socket and bind it */ 88 | for (q = result; q != NULL; q = q->ai_next) { 89 | sock = socket(q->ai_family, q->ai_socktype, q->ai_protocol); 90 | if (sock == -1) { 91 | continue; /* socket failed, try next field */ 92 | } else { 93 | i = bind(sock, q->ai_addr, q->ai_addrlen); 94 | if (i == -1) { 95 | shutdown(sock, SHUT_RDWR); 96 | continue; /* bind failed, try next field */ 97 | } else { 98 | break; /* success, get out of loop */ 99 | } 100 | } 101 | } 102 | if (q == NULL) { 103 | MSG("ERROR: failed to open socket or to bind to it\n"); 104 | i = 1; 105 | for (q = result; q != NULL; q = q->ai_next) { 106 | getnameinfo(q->ai_addr, q->ai_addrlen, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST); 107 | MSG("result %i host:%s service:%s\n", i, host_name, port_name); 108 | ++i; 109 | } 110 | exit(EXIT_FAILURE); 111 | } 112 | MSG("INFO: util_sink listening on port %s\n", argv[1]); 113 | freeaddrinfo(result); 114 | 115 | while (1) { 116 | byte_nb = recvfrom(sock, databuf, sizeof databuf, 0, (struct sockaddr *)&dist_addr, &addr_len); 117 | if (byte_nb == -1) { 118 | MSG("ERROR: recvfrom returned %s \n", strerror(errno)); 119 | exit(EXIT_FAILURE); 120 | } 121 | getnameinfo((struct sockaddr *)&dist_addr, addr_len, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST); 122 | printf("Got packet from host %s port %s, %i bytes long\n", host_name, port_name, byte_nb); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /util_tx_test/Makefile: -------------------------------------------------------------------------------- 1 | ### Application-specific constants 2 | 3 | APP_NAME := util_tx_test 4 | 5 | ### Constant symbols 6 | 7 | CC := $(CROSS_COMPILE)gcc 8 | AR := $(CROSS_COMPILE)ar 9 | 10 | CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I. 11 | 12 | OBJDIR = obj 13 | INCLUDES = $(wildcard inc/*.h) 14 | 15 | ### General build targets 16 | 17 | all: $(APP_NAME) 18 | 19 | clean: 20 | rm -f $(OBJDIR)/*.o 21 | rm -f $(APP_NAME) 22 | 23 | ### Sub-modules compilation 24 | 25 | $(OBJDIR): 26 | mkdir -p $(OBJDIR) 27 | 28 | $(OBJDIR)/%.o: src/%.c $(INCLUDES) | $(OBJDIR) 29 | $(CC) -c $(CFLAGS) $< -o $@ 30 | 31 | ### Main program assembly 32 | 33 | $(APP_NAME): $(OBJDIR)/$(APP_NAME).o $(OBJDIR)/base64.o 34 | $(CC) $< $(OBJDIR)/base64.o -o $@ 35 | 36 | ### EOF 37 | -------------------------------------------------------------------------------- /util_tx_test/inc/base64.h: -------------------------------------------------------------------------------- 1 | /* 2 | / _____) _ | | 3 | ( (____ _____ ____ _| |_ _____ ____| |__ 4 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 5 | _____) ) ____| | | || |_| ____( (___| | | | 6 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 7 | (C)2013 Semtech-Cycleo 8 | 9 | Description: 10 | Base64 encoding & decoding library 11 | 12 | License: Revised BSD License, see LICENSE.TXT file include in the project 13 | Maintainer: Sylvain Miermont 14 | */ 15 | 16 | 17 | #ifndef _BASE64_H 18 | #define _BASE64_H 19 | 20 | /* -------------------------------------------------------------------------- */ 21 | /* --- DEPENDANCIES --------------------------------------------------------- */ 22 | 23 | #include /* C99 types */ 24 | 25 | /* -------------------------------------------------------------------------- */ 26 | /* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ 27 | 28 | /** 29 | @brief Encode binary data in Base64 string (no padding) 30 | @param in pointer to a table of binary data 31 | @param size number of bytes to be encoded to base64 32 | @param out pointer to a string where the function will output encoded data 33 | @param max_len max length of the out string (including null char) 34 | @return >=0 length of the resulting string (w/o null char), -1 for error 35 | */ 36 | int bin_to_b64_nopad(const uint8_t * in, int size, char * out, int max_len); 37 | 38 | /** 39 | @brief Decode Base64 string to binary data (no padding) 40 | @param in string containing only base64 valid characters 41 | @param size number of characters to be decoded from base64 (w/o null char) 42 | @param out pointer to a data buffer where the function will output decoded data 43 | @param out_max_len usable size of the output data buffer 44 | @return >=0 number of bytes written to the data buffer, -1 for error 45 | */ 46 | int b64_to_bin_nopad(const char * in, int size, uint8_t * out, int max_len); 47 | 48 | /* === derivative functions === */ 49 | 50 | /** 51 | @brief Encode binary data in Base64 string (with added padding) 52 | */ 53 | int bin_to_b64(const uint8_t * in, int size, char * out, int max_len); 54 | 55 | /** 56 | @brief Decode Base64 string to binary data (remove padding if necessary) 57 | */ 58 | int b64_to_bin(const char * in, int size, uint8_t * out, int max_len); 59 | 60 | #endif 61 | 62 | /* --- EOF ------------------------------------------------------------------ */ 63 | -------------------------------------------------------------------------------- /util_tx_test/readme.md: -------------------------------------------------------------------------------- 1 | / _____) _ | | 2 | ( (____ _____ ____ _| |_ _____ ____| |__ 3 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 4 | _____) ) ____| | | || |_| ____( (___| | | | 5 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 6 | (C)2013 Semtech-Cycleo 7 | 8 | Utility: network packet sender 9 | =============================== 10 | 11 | 1. Introduction 12 | ---------------- 13 | 14 | The network packet sender is a simple helper program used to send packets 15 | through the gateway-to-server downlink route. 16 | 17 | The program start by waiting for a gateway to send it a PULL_DATA datagram. 18 | After that, it will send back to the gateway a specified amount of PULL_RESP 19 | datagrams, each containing a packet to be sent immediately and a variable 20 | payload. 21 | 22 | 2. Dependencies 23 | ---------------- 24 | 25 | This program follows the v1.1 version of the gateway-to-server protocol. 26 | 27 | 3. Usage 28 | --------- 29 | 30 | The application runs until the specified number of packets have been sent. 31 | Press Ctrl+C to stop the application before that. 32 | 33 | Use the -h option to get help and details about available options. 34 | 35 | The packets are [9-n] bytes long, and have following payload content: 36 | +----------+---------------+---------------+---------------+---------------+---+---+---+---+---+---+---+---+ 37 | | Id | PktCnt[31:24] | PktCnt[23:16] | PktCnt[15:8] | PktCnt[7:0] | P | E | R |FCS| 0 | 1 |...| n | 38 | +----------+---------------+---------------+---------------+---------------+---+---+---+---+---+---+---+---+ 39 | 40 | Id : User defined ID to differentiate sender at receiver side. (8 bits) 41 | PktCnt : Packet counter incremented at each transmission. (32 bits) 42 | ‘P’, ‘E’, ‘R’ : ASCII values for characters 'P', 'E' and 'R'. 43 | FCS : Checksum: 8-bits sum of Id, PktCnt[31 :24] , PktCnt[23 :16] , PktCnt[15 :8] , PktCnt[7:0], ‘P’,’E’,’R’ 44 | 0,1, ..., n : Padding bytes up until user specified payload length. 45 | 46 | 4. License 47 | ----------- 48 | 49 | Copyright (C) 2013, SEMTECH S.A. 50 | All rights reserved. 51 | 52 | Redistribution and use in source and binary forms, with or without 53 | modification, are permitted provided that the following conditions are met: 54 | 55 | * Redistributions of source code must retain the above copyright 56 | notice, this list of conditions and the following disclaimer. 57 | * Redistributions in binary form must reproduce the above copyright 58 | notice, this list of conditions and the following disclaimer in the 59 | documentation and/or other materials provided with the distribution. 60 | * Neither the name of the Semtech corporation nor the 61 | names of its contributors may be used to endorse or promote products 62 | derived from this software without specific prior written permission. 63 | 64 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 65 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 66 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 67 | DISCLAIMED. IN NO EVENT SHALL SEMTECH S.A. BE LIABLE FOR ANY 68 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 69 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 70 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 71 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 72 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 73 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 74 | 75 | *EOF* 76 | -------------------------------------------------------------------------------- /util_tx_test/src/base64.c: -------------------------------------------------------------------------------- 1 | /* 2 | / _____) _ | | 3 | ( (____ _____ ____ _| |_ _____ ____| |__ 4 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 5 | _____) ) ____| | | || |_| ____( (___| | | | 6 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 7 | (C)2013 Semtech-Cycleo 8 | 9 | Description: 10 | Base64 encoding & decoding library 11 | 12 | License: Revised BSD License, see LICENSE.TXT file include in the project 13 | Maintainer: Sylvain Miermont 14 | */ 15 | 16 | 17 | /* -------------------------------------------------------------------------- */ 18 | /* --- DEPENDANCIES --------------------------------------------------------- */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "base64.h" 25 | 26 | /* -------------------------------------------------------------------------- */ 27 | /* --- PRIVATE MACROS ------------------------------------------------------- */ 28 | 29 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) 30 | #define CRIT(a) fprintf(stderr, "\nCRITICAL file:%s line:%d msg:%s\n", __FILE__, __LINE__, a);exit(EXIT_FAILURE) 31 | 32 | //#define DEBUG(args...) fprintf(stderr,"debug: " args) /* diagnostic message that is destined to the user */ 33 | #define DEBUG(args...) 34 | 35 | /* -------------------------------------------------------------------------- */ 36 | /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ 37 | 38 | /* -------------------------------------------------------------------------- */ 39 | /* --- PRIVATE MODULE-WIDE VARIABLES ---------------------------------------- */ 40 | 41 | static char code_62 = '+'; /* RFC 1421 standard character for code 62 */ 42 | static char code_63 = '/'; /* RFC 1421 standard character for code 63 */ 43 | static char code_pad = '='; /* RFC 1421 padding character if padding */ 44 | 45 | /* -------------------------------------------------------------------------- */ 46 | /* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ 47 | 48 | /** 49 | @brief Convert a code in the range 0-63 to an ASCII character 50 | */ 51 | char code_to_char(uint8_t x); 52 | 53 | /** 54 | @brief Convert an ASCII character to a code in the range 0-63 55 | */ 56 | uint8_t char_to_code(char x); 57 | 58 | /* -------------------------------------------------------------------------- */ 59 | /* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ 60 | 61 | char code_to_char(uint8_t x) { 62 | if (x <= 25) { 63 | return 'A' + x; 64 | } else if ((x >= 26) && (x <= 51)) { 65 | return 'a' + (x - 26); 66 | } else if ((x >= 52) && (x <= 61)) { 67 | return '0' + (x - 52); 68 | } else if (x == 62) { 69 | return code_62; 70 | } else if (x == 63) { 71 | return code_63; 72 | } else { 73 | DEBUG("ERROR: %i IS OUT OF RANGE 0-63 FOR BASE64 ENCODING\n", x); 74 | exit(EXIT_FAILURE); 75 | } //TODO: improve error management 76 | } 77 | 78 | uint8_t char_to_code(char x) { 79 | if ((x >= 'A') && (x <= 'Z')) { 80 | return (uint8_t)x - (uint8_t)'A'; 81 | } else if ((x >= 'a') && (x <= 'z')) { 82 | return (uint8_t)x - (uint8_t)'a' + 26; 83 | } else if ((x >= '0') && (x <= '9')) { 84 | return (uint8_t)x - (uint8_t)'0' + 52; 85 | } else if (x == code_62) { 86 | return 62; 87 | } else if (x == code_63) { 88 | return 63; 89 | } else { 90 | DEBUG("ERROR: %c (0x%x) IS INVALID CHARACTER FOR BASE64 DECODING\n", x, x); 91 | exit(EXIT_FAILURE); 92 | } //TODO: improve error management 93 | } 94 | 95 | /* -------------------------------------------------------------------------- */ 96 | /* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ 97 | 98 | int bin_to_b64_nopad(const uint8_t * in, int size, char * out, int max_len) { 99 | int i; 100 | int result_len; /* size of the result */ 101 | int full_blocks; /* number of 3 unsigned chars / 4 characters blocks */ 102 | int last_bytes; /* number of unsigned chars <3 in the last block */ 103 | int last_chars; /* number of characters <4 in the last block */ 104 | uint32_t b; 105 | 106 | /* check input values */ 107 | if ((out == NULL) || (in == NULL)) { 108 | DEBUG("ERROR: NULL POINTER AS OUTPUT IN BIN_TO_B64\n"); 109 | return -1; 110 | } 111 | if (size == 0) { 112 | *out = 0; /* null string */ 113 | return 0; 114 | } 115 | 116 | /* calculate the number of base64 'blocks' */ 117 | full_blocks = size / 3; 118 | last_bytes = size % 3; 119 | switch (last_bytes) { 120 | case 0: /* no byte left to encode */ 121 | last_chars = 0; 122 | break; 123 | case 1: /* 1 byte left to encode -> +2 chars */ 124 | last_chars = 2; 125 | break; 126 | case 2: /* 2 bytes left to encode -> +3 chars */ 127 | last_chars = 3; 128 | break; 129 | default: 130 | CRIT("switch default that should not be possible"); 131 | } 132 | 133 | /* check if output buffer is big enough */ 134 | result_len = (4 * full_blocks) + last_chars; 135 | if (max_len < (result_len + 1)) { /* 1 char added for string terminator */ 136 | DEBUG("ERROR: OUTPUT BUFFER TOO SMALL IN BIN_TO_B64\n"); 137 | return -1; 138 | } 139 | 140 | /* process all the full blocks */ 141 | for (i = 0; i < full_blocks; ++i) { 142 | b = (0xFF & in[3 * i] ) << 16; 143 | b |= (0xFF & in[3 * i + 1]) << 8; 144 | b |= 0xFF & in[3 * i + 2]; 145 | out[4 * i + 0] = code_to_char((b >> 18) & 0x3F); 146 | out[4 * i + 1] = code_to_char((b >> 12) & 0x3F); 147 | out[4 * i + 2] = code_to_char((b >> 6 ) & 0x3F); 148 | out[4 * i + 3] = code_to_char( b & 0x3F); 149 | } 150 | 151 | /* process the last 'partial' block and terminate string */ 152 | i = full_blocks; 153 | if (last_chars == 0) { 154 | out[4 * i] = 0; /* null character to terminate string */ 155 | } else if (last_chars == 2) { 156 | b = (0xFF & in[3 * i] ) << 16; 157 | out[4 * i + 0] = code_to_char((b >> 18) & 0x3F); 158 | out[4 * i + 1] = code_to_char((b >> 12) & 0x3F); 159 | out[4 * i + 2] = 0; /* null character to terminate string */ 160 | } else if (last_chars == 3) { 161 | b = (0xFF & in[3 * i] ) << 16; 162 | b |= (0xFF & in[3 * i + 1]) << 8; 163 | out[4 * i + 0] = code_to_char((b >> 18) & 0x3F); 164 | out[4 * i + 1] = code_to_char((b >> 12) & 0x3F); 165 | out[4 * i + 2] = code_to_char((b >> 6 ) & 0x3F); 166 | out[4 * i + 3] = 0; /* null character to terminate string */ 167 | } 168 | 169 | return result_len; 170 | } 171 | 172 | int b64_to_bin_nopad(const char * in, int size, uint8_t * out, int max_len) { 173 | int i; 174 | int result_len; /* size of the result */ 175 | int full_blocks; /* number of 3 unsigned chars / 4 characters blocks */ 176 | int last_chars; /* number of characters <4 in the last block */ 177 | int last_bytes; /* number of unsigned chars <3 in the last block */ 178 | uint32_t b; 179 | ; 180 | 181 | /* check input values */ 182 | if ((out == NULL) || (in == NULL)) { 183 | DEBUG("ERROR: NULL POINTER AS OUTPUT OR INPUT IN B64_TO_BIN\n"); 184 | return -1; 185 | } 186 | if (size == 0) { 187 | return 0; 188 | } 189 | 190 | /* calculate the number of base64 'blocks' */ 191 | full_blocks = size / 4; 192 | last_chars = size % 4; 193 | switch (last_chars) { 194 | case 0: /* no char left to decode */ 195 | last_bytes = 0; 196 | break; 197 | case 1: /* only 1 char left is an error */ 198 | DEBUG("ERROR: ONLY ONE CHAR LEFT IN B64_TO_BIN\n"); 199 | return -1; 200 | case 2: /* 2 chars left to decode -> +1 byte */ 201 | last_bytes = 1; 202 | break; 203 | case 3: /* 3 chars left to decode -> +2 bytes */ 204 | last_bytes = 2; 205 | break; 206 | default: 207 | CRIT("switch default that should not be possible"); 208 | } 209 | 210 | /* check if output buffer is big enough */ 211 | result_len = (3 * full_blocks) + last_bytes; 212 | if (max_len < result_len) { 213 | DEBUG("ERROR: OUTPUT BUFFER TOO SMALL IN B64_TO_BIN\n"); 214 | return -1; 215 | } 216 | 217 | /* process all the full blocks */ 218 | for (i = 0; i < full_blocks; ++i) { 219 | b = (0x3F & char_to_code(in[4 * i] )) << 18; 220 | b |= (0x3F & char_to_code(in[4 * i + 1])) << 12; 221 | b |= (0x3F & char_to_code(in[4 * i + 2])) << 6; 222 | b |= 0x3F & char_to_code(in[4 * i + 3]); 223 | out[3 * i + 0] = (b >> 16) & 0xFF; 224 | out[3 * i + 1] = (b >> 8 ) & 0xFF; 225 | out[3 * i + 2] = b & 0xFF; 226 | } 227 | 228 | /* process the last 'partial' block */ 229 | i = full_blocks; 230 | if (last_bytes == 1) { 231 | b = (0x3F & char_to_code(in[4 * i] )) << 18; 232 | b |= (0x3F & char_to_code(in[4 * i + 1])) << 12; 233 | out[3 * i + 0] = (b >> 16) & 0xFF; 234 | if (((b >> 12) & 0x0F) != 0) { 235 | DEBUG("WARNING: last character contains unusable bits\n"); 236 | } 237 | } else if (last_bytes == 2) { 238 | b = (0x3F & char_to_code(in[4 * i] )) << 18; 239 | b |= (0x3F & char_to_code(in[4 * i + 1])) << 12; 240 | b |= (0x3F & char_to_code(in[4 * i + 2])) << 6; 241 | out[3 * i + 0] = (b >> 16) & 0xFF; 242 | out[3 * i + 1] = (b >> 8 ) & 0xFF; 243 | if (((b >> 6) & 0x03) != 0) { 244 | DEBUG("WARNING: last character contains unusable bits\n"); 245 | } 246 | } 247 | 248 | return result_len; 249 | } 250 | 251 | int bin_to_b64(const uint8_t * in, int size, char * out, int max_len) { 252 | int ret; 253 | 254 | ret = bin_to_b64_nopad(in, size, out, max_len); 255 | 256 | if (ret == -1) { 257 | return -1; 258 | } 259 | switch (ret % 4) { 260 | case 0: /* nothing to do */ 261 | return ret; 262 | case 1: 263 | DEBUG("ERROR: INVALID UNPADDED BASE64 STRING\n"); 264 | return -1; 265 | case 2: /* 2 chars in last block, must add 2 padding char */ 266 | if (max_len >= (ret + 2 + 1)) { 267 | out[ret] = code_pad; 268 | out[ret + 1] = code_pad; 269 | out[ret + 2] = 0; 270 | return ret + 2; 271 | } else { 272 | DEBUG("ERROR: not enough room to add padding in bin_to_b64\n"); 273 | return -1; 274 | } 275 | case 3: /* 3 chars in last block, must add 1 padding char */ 276 | if (max_len >= (ret + 1 + 1)) { 277 | out[ret] = code_pad; 278 | out[ret + 1] = 0; 279 | return ret + 1; 280 | } else { 281 | DEBUG("ERROR: not enough room to add padding in bin_to_b64\n"); 282 | return -1; 283 | } 284 | default: 285 | CRIT("switch default that should not be possible"); 286 | } 287 | } 288 | 289 | int b64_to_bin(const char * in, int size, uint8_t * out, int max_len) { 290 | if (in == NULL) { 291 | DEBUG("ERROR: NULL POINTER AS OUTPUT OR INPUT IN B64_TO_BIN\n"); 292 | return -1; 293 | } 294 | if ((size % 4 == 0) && (size >= 4)) { /* potentially padded Base64 */ 295 | if (in[size - 2] == code_pad) { /* 2 padding char to ignore */ 296 | return b64_to_bin_nopad(in, size - 2, out, max_len); 297 | } else if (in[size - 1] == code_pad) { /* 1 padding char to ignore */ 298 | return b64_to_bin_nopad(in, size - 1, out, max_len); 299 | } else { /* no padding to ignore */ 300 | return b64_to_bin_nopad(in, size, out, max_len); 301 | } 302 | } else { /* treat as unpadded Base64 */ 303 | return b64_to_bin_nopad(in, size, out, max_len); 304 | } 305 | } 306 | 307 | 308 | /* --- EOF ------------------------------------------------------------------ */ 309 | -------------------------------------------------------------------------------- /util_tx_test/src/util_tx_test.c: -------------------------------------------------------------------------------- 1 | /* 2 | / _____) _ | | 3 | ( (____ _____ ____ _| |_ _____ ____| |__ 4 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 5 | _____) ) ____| | | || |_| ____( (___| | | | 6 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 7 | (C)2013 Semtech-Cycleo 8 | 9 | Description: 10 | Ask a gateway to emit packets using GW <-> server protocol 11 | 12 | License: Revised BSD License, see LICENSE.TXT file include in the project 13 | Maintainer: Sylvain Miermont 14 | */ 15 | 16 | 17 | /* -------------------------------------------------------------------------- */ 18 | /* --- DEPENDANCIES --------------------------------------------------------- */ 19 | 20 | /* fix an issue between POSIX and C99 */ 21 | #if __STDC_VERSION__ >= 199901L 22 | #define _XOPEN_SOURCE 600 23 | #else 24 | #define _XOPEN_SOURCE 500 25 | #endif 26 | 27 | #include /* C99 types */ 28 | #include /* bool type */ 29 | #include /* printf fprintf sprintf fopen fputs */ 30 | #include /* getopt access usleep */ 31 | 32 | #include /* memset */ 33 | #include /* sigaction */ 34 | #include /* exit codes */ 35 | #include /* error messages */ 36 | 37 | #include /* socket specific definitions */ 38 | #include /* INET constants and stuff */ 39 | #include /* IP address conversion stuff */ 40 | #include /* gai_strerror */ 41 | 42 | #include "base64.h" 43 | 44 | /* -------------------------------------------------------------------------- */ 45 | /* --- PRIVATE MACROS ------------------------------------------------------- */ 46 | 47 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) 48 | #define MSG(args...) fprintf(stderr, args) /* message that is destined to the user */ 49 | 50 | /* -------------------------------------------------------------------------- */ 51 | /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ 52 | 53 | #define PROTOCOL_VERSION 2 54 | 55 | #define PKT_PUSH_DATA 0 56 | #define PKT_PUSH_ACK 1 57 | #define PKT_PULL_DATA 2 58 | #define PKT_PULL_RESP 3 59 | #define PKT_PULL_ACK 4 60 | 61 | /* -------------------------------------------------------------------------- */ 62 | /* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */ 63 | 64 | /* signal handling variables */ 65 | struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ 66 | static int exit_sig = 0; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */ 67 | static int quit_sig = 0; /* 1 -> application terminates without shutting down the hardware */ 68 | 69 | /* -------------------------------------------------------------------------- */ 70 | /* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ 71 | 72 | static void sig_handler(int sigio); 73 | 74 | void usage (void); 75 | 76 | /* -------------------------------------------------------------------------- */ 77 | /* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ 78 | 79 | static void sig_handler(int sigio) { 80 | if (sigio == SIGQUIT) { 81 | quit_sig = 1;; 82 | } else if ((sigio == SIGINT) || (sigio == SIGTERM)) { 83 | exit_sig = 1; 84 | } 85 | } 86 | 87 | /* describe command line options */ 88 | void usage(void) { 89 | MSG("Usage: util_tx_test {options}\n"); 90 | MSG("Available options:\n"); 91 | MSG(" -h print this help\n"); 92 | MSG(" -n port number for gateway link\n"); 93 | MSG(" -f target frequency in MHz\n"); 94 | MSG(" -m Modulation type ['LORA, 'FSK']\n"); 95 | MSG(" -s Spreading Factor [7:12]\n"); 96 | MSG(" -b Modulation bandwidth in kHz [125,250,500]\n"); 97 | MSG(" -d FSK frequency deviation in kHz [1:250]\n"); 98 | MSG(" -r FSK bitrate in kbps [0.5:250]\n"); 99 | MSG(" -p RF power (dBm)\n"); 100 | MSG(" -z Payload size in bytes [9:255]\n"); 101 | MSG(" -t pause between packets (ms)\n"); 102 | MSG(" -x numbers of times the sequence is repeated\n"); 103 | MSG(" -v test ID, inserted in payload for PER test [0:255]\n"); 104 | MSG(" -i send packet using inverted modulation polarity \n"); 105 | } 106 | 107 | /* -------------------------------------------------------------------------- */ 108 | /* --- MAIN FUNCTION -------------------------------------------------------- */ 109 | 110 | int main(int argc, char **argv) { 111 | int i, j, x; 112 | unsigned int xu; 113 | char arg_s[64]; 114 | 115 | /* application parameters */ 116 | char mod[64] = "LORA"; /* LoRa modulation by default */ 117 | float f_target = 866.0; /* target frequency */ 118 | int sf = 10; /* SF10 by default */ 119 | int bw = 125; /* 125kHz bandwidth by default */ 120 | int pow = 14; /* 14 dBm by default */ 121 | int delay = 1000; /* 1 second between packets by default */ 122 | int repeat = 1; /* sweep only once by default */ 123 | bool invert = false; 124 | float br_kbps = 50; /* 50 kbps by default */ 125 | uint8_t fdev_khz = 25; /* 25 khz by default */ 126 | 127 | /* packet payload variables */ 128 | int payload_size = 9; /* minimum size for PER frame */ 129 | uint8_t payload_bin[255]; 130 | char payload_b64[341]; 131 | int payload_index; 132 | 133 | /* PER payload variables */ 134 | uint8_t id = 0; 135 | 136 | /* server socket creation */ 137 | int sock; /* socket file descriptor */ 138 | struct addrinfo hints; 139 | struct addrinfo *result; /* store result of getaddrinfo */ 140 | struct addrinfo *q; /* pointer to move into *result data */ 141 | char serv_port[8] = "1680"; 142 | char host_name[64]; 143 | char port_name[64]; 144 | 145 | /* variables for receiving and sending packets */ 146 | struct sockaddr_storage dist_addr; 147 | socklen_t addr_len = sizeof dist_addr; 148 | uint8_t databuf[500]; 149 | int buff_index; 150 | int byte_nb; 151 | 152 | /* variables for gateway identification */ 153 | uint32_t raw_mac_h; /* Most Significant Nibble, network order */ 154 | uint32_t raw_mac_l; /* Least Significant Nibble, network order */ 155 | uint64_t gw_mac; /* MAC address of the client (gateway) */ 156 | 157 | /* prepare hints to open network sockets */ 158 | memset(&hints, 0, sizeof hints); 159 | hints.ai_family = AF_UNSPEC; /* should handle IP v4 or v6 automatically */ 160 | hints.ai_socktype = SOCK_DGRAM; 161 | hints.ai_flags = AI_PASSIVE; /* will assign local IP automatically */ 162 | 163 | /* parse command line options */ 164 | while ((i = getopt (argc, argv, "hn:f:m:s:b:d:r:p:z:t:x:v:i")) != -1) { 165 | switch (i) { 166 | case 'h': 167 | usage(); 168 | return EXIT_FAILURE; 169 | break; 170 | 171 | case 'n': /* -n port number for gateway link */ 172 | strncpy(serv_port, optarg, sizeof serv_port); 173 | break; 174 | 175 | case 'f': /* -f target frequency in MHz */ 176 | i = sscanf(optarg, "%f", &f_target); 177 | if ((i != 1) || (f_target < 30.0) || (f_target > 3000.0)) { 178 | MSG("ERROR: invalid TX frequency\n"); 179 | return EXIT_FAILURE; 180 | } 181 | break; 182 | 183 | case 'm': /* -m Modulation type */ 184 | i = sscanf(optarg, "%63s", arg_s); 185 | if ((i != 1) || ((strcmp(arg_s, "LORA") != 0) && (strcmp(arg_s, "FSK")))) { 186 | MSG("ERROR: invalid modulation type\n"); 187 | usage(); 188 | return EXIT_FAILURE; 189 | } else { 190 | sprintf(mod, "%s", arg_s); 191 | } 192 | break; 193 | 194 | case 's': /* -s Spreading Factor */ 195 | i = sscanf(optarg, "%i", &sf); 196 | if ((i != 1) || (sf < 7) || (sf > 12)) { 197 | MSG("ERROR: invalid spreading factor\n"); 198 | return EXIT_FAILURE; 199 | } 200 | break; 201 | 202 | case 'b': /* -b Modulation bandwidth in kHz */ 203 | i = sscanf(optarg, "%i", &bw); 204 | if ((i != 1) || ((bw != 125) && (bw != 250) && (bw != 500))) { 205 | MSG("ERROR: invalid LORA bandwidth\n"); 206 | return EXIT_FAILURE; 207 | } 208 | break; 209 | 210 | case 'd': /* -d FSK frequency deviation */ 211 | i = sscanf(optarg, "%u", &xu); 212 | if ((i != 1) || (xu < 1) || (xu > 250)) { 213 | MSG("ERROR: invalid FSK frequency deviation\n"); 214 | usage(); 215 | return EXIT_FAILURE; 216 | } else { 217 | fdev_khz = (uint8_t)xu; 218 | } 219 | break; 220 | 221 | case 'r': /* -q FSK bitrate */ 222 | i = sscanf(optarg, "%f", &br_kbps); 223 | if ((i != 1) || (br_kbps < 0.5) || (br_kbps > 250)) { 224 | MSG("ERROR: invalid FSK bitrate\n"); 225 | usage(); 226 | return EXIT_FAILURE; 227 | } 228 | break; 229 | 230 | case 'p': /* -p RF power */ 231 | i = sscanf(optarg, "%i", &pow); 232 | if ((i != 1) || (pow < 0) || (pow > 30)) { 233 | MSG("ERROR: invalid RF power\n"); 234 | return EXIT_FAILURE; 235 | } 236 | break; 237 | 238 | case 'z': /* -z Payload size */ 239 | i = sscanf(optarg, "%i", &payload_size); 240 | if ((i != 1) || (payload_size < 9) || (payload_size > 255)) { 241 | MSG("ERROR: invalid payload size\n"); 242 | usage(); 243 | return EXIT_FAILURE; 244 | } 245 | break; 246 | 247 | case 't': /* -t pause between RF packets (ms) */ 248 | i = sscanf(optarg, "%i", &delay); 249 | if ((i != 1) || (delay < 0)) { 250 | MSG("ERROR: invalid time between RF packets\n"); 251 | return EXIT_FAILURE; 252 | } 253 | break; 254 | 255 | case 'x': /* -x numbers of times the sequence is repeated */ 256 | i = sscanf(optarg, "%d", &repeat); 257 | if ((i != 1) || (repeat < 1)) { 258 | MSG("ERROR: invalid number of repeats\n"); 259 | return EXIT_FAILURE; 260 | } 261 | break; 262 | 263 | case 'v': /* -v test Id */ 264 | i = sscanf(optarg, "%u", &xu); 265 | if ((i != 1) || ((xu < 1) || (xu > 255))) { 266 | MSG("ERROR: invalid Id\n"); 267 | return EXIT_FAILURE; 268 | } else { 269 | id = (uint8_t)xu; 270 | } 271 | break; 272 | 273 | case 'i': /* -i send packet using inverted modulation polarity */ 274 | invert = true; 275 | break; 276 | 277 | default: 278 | MSG("ERROR: argument parsing failure, use -h option for help\n"); 279 | usage(); 280 | return EXIT_FAILURE; 281 | } 282 | } 283 | 284 | /* compose local address (auto-complete a structure for socket) */ 285 | i = getaddrinfo(NULL, serv_port, &hints, &result); 286 | if (i != 0) { 287 | MSG("ERROR: getaddrinfo returned %s\n", gai_strerror(i)); 288 | exit(EXIT_FAILURE); 289 | } 290 | 291 | /* try to open socket and bind to it */ 292 | for (q = result; q != NULL; q = q->ai_next) { 293 | sock = socket(q->ai_family, q->ai_socktype, q->ai_protocol); 294 | if (sock == -1) { 295 | continue; /* socket failed, try next field */ 296 | } else { 297 | i = bind(sock, q->ai_addr, q->ai_addrlen); 298 | if (i == -1) { 299 | shutdown(sock, SHUT_RDWR); 300 | continue; /* bind failed, try next field */ 301 | } else { 302 | break; /* success, get out of loop */ 303 | } 304 | } 305 | } 306 | if (q == NULL) { 307 | MSG("ERROR: failed to open socket or to bind to it\n"); 308 | exit(EXIT_FAILURE); 309 | } 310 | freeaddrinfo(result); 311 | 312 | /* configure signal handling */ 313 | sigemptyset(&sigact.sa_mask); 314 | sigact.sa_flags = 0; 315 | sigact.sa_handler = sig_handler; 316 | sigaction(SIGQUIT, &sigact, NULL); 317 | sigaction(SIGINT, &sigact, NULL); 318 | sigaction(SIGTERM, &sigact, NULL); 319 | 320 | /* display setup summary */ 321 | if (strcmp(mod, "FSK") == 0) { 322 | MSG("INFO: %i FSK pkts @%f MHz (FDev %u kHz, Bitrate %.2f kbps, %dB payload) %i dBm, %i ms between each\n", repeat, f_target, fdev_khz, br_kbps, payload_size, pow, delay); 323 | } else { 324 | MSG("INFO: %i LoRa pkts @%f MHz (BW %d kHz, SF%i, %dB payload) %i dBm, %i ms between each\n", repeat, f_target, bw, sf, payload_size, pow, delay); 325 | } 326 | 327 | /* wait to receive a PULL_DATA request packet */ 328 | MSG("INFO: waiting to receive a PULL_DATA request on port %s\n", serv_port); 329 | while (1) { 330 | byte_nb = recvfrom(sock, databuf, sizeof databuf, 0, (struct sockaddr *)&dist_addr, &addr_len); 331 | if ((quit_sig == 1) || (exit_sig == 1)) { 332 | exit(EXIT_SUCCESS); 333 | } else if (byte_nb < 0) { 334 | MSG("WARNING: recvfrom returned an error\n"); 335 | } else if ((byte_nb < 12) || (databuf[0] != PROTOCOL_VERSION) || (databuf[3] != PKT_PULL_DATA)) { 336 | MSG("INFO: packet received, not PULL_DATA request\n"); 337 | } else { 338 | break; /* success! */ 339 | } 340 | } 341 | 342 | /* retrieve gateway MAC from the request */ 343 | raw_mac_h = *((uint32_t *)(databuf + 4)); 344 | raw_mac_l = *((uint32_t *)(databuf + 8)); 345 | gw_mac = ((uint64_t)ntohl(raw_mac_h) << 32) + (uint64_t)ntohl(raw_mac_l); 346 | 347 | /* display info about the sender */ 348 | i = getnameinfo((struct sockaddr *)&dist_addr, addr_len, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST); 349 | if (i == -1) { 350 | MSG("ERROR: getnameinfo returned %s \n", gai_strerror(i)); 351 | exit(EXIT_FAILURE); 352 | } 353 | 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); 354 | 355 | /* PKT_PULL_RESP datagrams header */ 356 | databuf[0] = PROTOCOL_VERSION; 357 | databuf[1] = 0; /* no token */ 358 | databuf[2] = 0; /* no token */ 359 | databuf[3] = PKT_PULL_RESP; 360 | buff_index = 4; 361 | 362 | /* start of JSON structure */ 363 | memcpy((void *)(databuf + buff_index), (void *)"{\"txpk\":{\"imme\":true", 20); 364 | buff_index += 20; 365 | 366 | /* TX frequency */ 367 | i = snprintf((char *)(databuf + buff_index), 20, ",\"freq\":%.6f", f_target); 368 | if ((i >= 0) && (i < 20)) { 369 | buff_index += i; 370 | } else { 371 | MSG("ERROR: snprintf failed line %d\n", (__LINE__ - 4)); 372 | exit(EXIT_FAILURE); 373 | } 374 | 375 | /* RF channel */ 376 | memcpy((void *)(databuf + buff_index), (void *)",\"rfch\":0", 9); 377 | buff_index += 9; 378 | 379 | /* TX power */ 380 | i = snprintf((char *)(databuf + buff_index), 12, ",\"powe\":%i", pow); 381 | if ((i >= 0) && (i < 12)) { 382 | buff_index += i; 383 | } else { 384 | MSG("ERROR: snprintf failed line %d\n", (__LINE__ - 4)); 385 | exit(EXIT_FAILURE); 386 | } 387 | 388 | /* modulation type and parameters */ 389 | if (strcmp(mod, "FSK") == 0) { 390 | i = snprintf((char *)(databuf + buff_index), 50, ",\"modu\":\"FSK\",\"datr\":%u,\"fdev\":%u", (unsigned int)(br_kbps * 1e3), (unsigned int)(fdev_khz * 1e3)); 391 | if ((i >= 0) && (i < 50)) { 392 | buff_index += i; 393 | } else { 394 | MSG("ERROR: snprintf failed line %d\n", (__LINE__ - 4)); 395 | exit(EXIT_FAILURE); 396 | } 397 | } else { 398 | i = snprintf((char *)(databuf + buff_index), 50, ",\"modu\":\"LORA\",\"datr\":\"SF%iBW%i\",\"codr\":\"4/6\"", sf, bw); 399 | if ((i >= 0) && (i < 50)) { 400 | buff_index += i; 401 | } else { 402 | MSG("ERROR: snprintf failed line %d\n", (__LINE__ - 4)); 403 | exit(EXIT_FAILURE); 404 | } 405 | } 406 | 407 | /* signal polarity */ 408 | if (invert) { 409 | memcpy((void *)(databuf + buff_index), (void *)",\"ipol\":true", 12); 410 | buff_index += 12; 411 | } else { 412 | memcpy((void *)(databuf + buff_index), (void *)",\"ipol\":false", 13); 413 | buff_index += 13; 414 | } 415 | 416 | /* Preamble size */ 417 | if (strcmp(mod, "LORA") == 0) { 418 | memcpy((void *)(databuf + buff_index), (void *)",\"prea\":8", 9); 419 | buff_index += 9; 420 | } 421 | 422 | /* payload size */ 423 | i = snprintf((char *)(databuf + buff_index), 12, ",\"size\":%i", payload_size); 424 | if ((i >= 0) && (i < 12)) { 425 | buff_index += i; 426 | } else { 427 | MSG("ERROR: snprintf failed line %d\n", (__LINE__ - 4)); 428 | exit(EXIT_FAILURE); 429 | } 430 | 431 | /* payload JSON object */ 432 | memcpy((void *)(databuf + buff_index), (void *)",\"data\":\"", 9); 433 | buff_index += 9; 434 | payload_index = buff_index; /* keep the value where the payload content start */ 435 | 436 | /* payload place-holder & end of JSON structure */ 437 | x = bin_to_b64(payload_bin, payload_size, payload_b64, sizeof payload_b64); /* dummy conversion to get exact size */ 438 | if (x >= 0) { 439 | buff_index += x; 440 | } else { 441 | MSG("ERROR: bin_to_b64 failed line %d\n", (__LINE__ - 4)); 442 | exit(EXIT_FAILURE); 443 | } 444 | 445 | /* Close JSON structure */ 446 | memcpy((void *)(databuf + buff_index), (void *)"\"}}", 3); 447 | buff_index += 3; /* ends up being the total length of payload */ 448 | 449 | /* main loop */ 450 | for (i = 0; i < repeat; ++i) { 451 | /* fill payload */ 452 | payload_bin[0] = id; 453 | payload_bin[1] = (uint8_t)(i >> 24); 454 | payload_bin[2] = (uint8_t)(i >> 16); 455 | payload_bin[3] = (uint8_t)(i >> 8); 456 | payload_bin[4] = (uint8_t)(i); 457 | payload_bin[5] = 'P'; 458 | payload_bin[6] = 'E'; 459 | payload_bin[7] = 'R'; 460 | 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]); 461 | for (j = 0; j < (payload_size - 9); j++) { 462 | payload_bin[9 + j] = j; 463 | } 464 | 465 | #if 0 466 | for (j = 0; j < payload_size; j++ ) { 467 | printf("0x%02X ", payload_bin[j]); 468 | } 469 | printf("\n"); 470 | #endif 471 | 472 | /* encode the payload in Base64 */ 473 | x = bin_to_b64(payload_bin, payload_size, payload_b64, sizeof payload_b64); 474 | if (x >= 0) { 475 | memcpy((void *)(databuf + payload_index), (void *)payload_b64, x); 476 | } else { 477 | MSG("ERROR: bin_to_b64 failed line %d\n", (__LINE__ - 4)); 478 | exit(EXIT_FAILURE); 479 | } 480 | 481 | /* send packet to the gateway */ 482 | byte_nb = sendto(sock, (void *)databuf, buff_index, 0, (struct sockaddr *)&dist_addr, addr_len); 483 | if (byte_nb == -1) { 484 | MSG("WARNING: sendto returned an error %s\n", strerror(errno)); 485 | } else { 486 | MSG("INFO: packet #%i sent successfully\n", i); 487 | } 488 | 489 | /* wait inter-packet delay */ 490 | usleep(delay * 1000); 491 | 492 | /* exit loop on user signals */ 493 | if ((quit_sig == 1) || (exit_sig == 1)) { 494 | break; 495 | } 496 | } 497 | 498 | 499 | exit(EXIT_SUCCESS); 500 | } 501 | 502 | /* --- EOF ------------------------------------------------------------------ */ 503 | --------------------------------------------------------------------------------