mirror of
https://github.com/loradar/loradar_tool.git
synced 2026-06-26 04:41:14 +02:00
Add files via upload
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
Copyright (C) 2013, SEMTECH S.A.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Semtech corporation nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL SEMTECH S.A. BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
--- For the parson library ---
|
||||
|
||||
Parson ( http://kgabis.github.com/parson/ )
|
||||
Copyright (C) 2012 Krzysztof Gabis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
ITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
### Environment constants
|
||||
|
||||
LGW_PATH ?= ../../lora_gateway/libloragw
|
||||
ARCH ?=
|
||||
CROSS_COMPILE ?=
|
||||
export
|
||||
|
||||
### general build targets
|
||||
|
||||
all:
|
||||
$(MAKE) all -e -C lora_pkt_fwd
|
||||
$(MAKE) all -e -C util_ack
|
||||
$(MAKE) all -e -C util_sink
|
||||
$(MAKE) all -e -C util_tx_test
|
||||
|
||||
clean:
|
||||
$(MAKE) clean -e -C lora_pkt_fwd
|
||||
$(MAKE) clean -e -C util_ack
|
||||
$(MAKE) clean -e -C util_sink
|
||||
$(MAKE) clean -e -C util_tx_test
|
||||
|
||||
### EOF
|
||||
@@ -0,0 +1,455 @@
|
||||
______ _
|
||||
/ _____) _ | |
|
||||
( (____ _____ ____ _| |_ _____ ____| |__
|
||||
\____ \| ___ | (_ _) ___ |/ ___) _ \
|
||||
_____) ) ____| | | || |_| ____( (___| | | |
|
||||
(______/|_____)_|_|_| \__)_____)\____)_| |_|
|
||||
(C)2013 Semtech-Cycleo
|
||||
|
||||
Basic communication protocol between Lora gateway and server
|
||||
=============================================================
|
||||
|
||||
|
||||
1. Introduction
|
||||
----------------
|
||||
|
||||
The protocol between the gateway and the server is purposefully very basic and
|
||||
for demonstration purpose only, or for use on private and reliable networks.
|
||||
|
||||
There is no authentication of the gateway or the server, and the acknowledges
|
||||
are only used for network quality assessment, not to correct UDP datagrams
|
||||
losses (no retries).
|
||||
|
||||
|
||||
2. System schematic and definitions
|
||||
------------------------------------
|
||||
|
||||
((( Y )))
|
||||
|
|
||||
|
|
||||
+ - -|- - - - - - - - - - - - - + xxxxxxxxxxxx +--------+
|
||||
| +--+-----------+ +------+ | xx x x xxx | |
|
||||
| | | | | | xx Internet xx | |
|
||||
| | Concentrator |<--->| Host |<-------xx or xx-------->| |
|
||||
| | | SPI | | | xx Intranet xx | Server |
|
||||
| +--------------+ +------+ | xxxx x xxxx | |
|
||||
| ^ ^ | xxxxxxxx | |
|
||||
| | PPS +-------+ NMEA | | | |
|
||||
| +-----| GPS |-------+ | +--------+
|
||||
| | (opt) | |
|
||||
| +-------+ |
|
||||
| |
|
||||
| Gateway |
|
||||
+- - - - - - - - - - - - - - - -+
|
||||
|
||||
__Concentrator__: radio RX/TX board, based on Semtech multichannel modems
|
||||
(SX130x), transceivers (SX135x) and/or low-power stand-alone modems (SX127x).
|
||||
|
||||
__Host__: embedded computer on which the packet forwarder is run. Drives the
|
||||
concentrator through a SPI link.
|
||||
|
||||
__GPS__: GNSS (GPS, Galileo, GLONASS, etc) receiver with a "1 Pulse Per Second"
|
||||
output and a serial link to the host to send NMEA frames containing time and
|
||||
geographical coordinates data. Optional.
|
||||
|
||||
__Gateway__: a device composed of at least one radio concentrator, a host, some
|
||||
network connection to the internet or a private network (Ethernet, 3G, Wifi,
|
||||
microwave link), and optionally a GPS receiver for synchronization.
|
||||
|
||||
__Server__: an abstract computer that will process the RF packets received and
|
||||
forwarded by the gateway, and issue RF packets in response that the gateway
|
||||
will have to emit.
|
||||
|
||||
It is assumed that the gateway can be behind a NAT or a firewall stopping any
|
||||
incoming connection.
|
||||
It is assumed that the server has an static IP address (or an address solvable
|
||||
through a DNS service) and is able to receive incoming connections on a
|
||||
specific port.
|
||||
|
||||
|
||||
3. Upstream protocol
|
||||
---------------------
|
||||
|
||||
### 3.1. Sequence diagram ###
|
||||
|
||||
+---------+ +---------+
|
||||
| Gateway | | Server |
|
||||
+---------+ +---------+
|
||||
| -----------------------------------\ |
|
||||
|-| When 1-N RF packets are received | |
|
||||
| ------------------------------------ |
|
||||
| |
|
||||
| PUSH_DATA (token X, GW MAC, JSON payload) |
|
||||
|------------------------------------------------------------->|
|
||||
| |
|
||||
| PUSH_ACK (token X) |
|
||||
|<-------------------------------------------------------------|
|
||||
| ------------------------------\ |
|
||||
| | process packets *after* ack |-|
|
||||
| ------------------------------- |
|
||||
| |
|
||||
|
||||
### 3.2. PUSH_DATA packet ###
|
||||
|
||||
That packet type is used by the gateway mainly to forward the RF packets
|
||||
received, and associated metadata, to the server.
|
||||
|
||||
Bytes | Function
|
||||
:------:|---------------------------------------------------------------------
|
||||
0 | protocol version = 2
|
||||
1-2 | random token
|
||||
3 | PUSH_DATA identifier 0x00
|
||||
4-11 | Gateway unique identifier (MAC address)
|
||||
12-end | JSON object, starting with {, ending with }, see section 4
|
||||
|
||||
### 3.3. PUSH_ACK packet ###
|
||||
|
||||
That packet type is used by the server to acknowledge immediately all the
|
||||
PUSH_DATA packets received.
|
||||
|
||||
Bytes | Function
|
||||
:------:|---------------------------------------------------------------------
|
||||
0 | protocol version = 2
|
||||
1-2 | same token as the PUSH_DATA packet to acknowledge
|
||||
3 | PUSH_ACK identifier 0x01
|
||||
|
||||
|
||||
4. Upstream JSON data structure
|
||||
--------------------------------
|
||||
|
||||
The root object can contain an array named "rxpk":
|
||||
|
||||
``` json
|
||||
{
|
||||
"rxpk":[ {...}, ...]
|
||||
}
|
||||
```
|
||||
|
||||
That array contains at least one JSON object, each object contain a RF packet
|
||||
and associated metadata with the following fields:
|
||||
|
||||
Name | Type | Function
|
||||
:----:|:------:|--------------------------------------------------------------
|
||||
time | string | UTC time of pkt RX, us precision, ISO 8601 'compact' format
|
||||
tmst | number | Internal timestamp of "RX finished" event (32b unsigned)
|
||||
freq | number | RX central frequency in MHz (unsigned float, Hz precision)
|
||||
chan | number | Concentrator "IF" channel used for RX (unsigned integer)
|
||||
rfch | number | Concentrator "RF chain" used for RX (unsigned integer)
|
||||
stat | number | CRC status: 1 = OK, -1 = fail, 0 = no CRC
|
||||
modu | string | Modulation identifier "LORA" or "FSK"
|
||||
datr | string | LoRa datarate identifier (eg. SF12BW500)
|
||||
datr | number | FSK datarate (unsigned, in bits per second)
|
||||
codr | string | LoRa ECC coding rate identifier
|
||||
rssi | number | RSSI in dBm (signed integer, 1 dB precision)
|
||||
lsnr | number | Lora SNR ratio in dB (signed float, 0.1 dB precision)
|
||||
size | number | RF packet payload size in bytes (unsigned integer)
|
||||
data | string | Base64 encoded RF packet payload, padded
|
||||
|
||||
Example (white-spaces, indentation and newlines added for readability):
|
||||
|
||||
``` json
|
||||
{"rxpk":[
|
||||
{
|
||||
"time":"2013-03-31T16:21:17.528002Z",
|
||||
"tmst":3512348611,
|
||||
"chan":2,
|
||||
"rfch":0,
|
||||
"freq":866.349812,
|
||||
"stat":1,
|
||||
"modu":"LORA",
|
||||
"datr":"SF7BW125",
|
||||
"codr":"4/6",
|
||||
"rssi":-35,
|
||||
"lsnr":5.1,
|
||||
"size":32,
|
||||
"data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84"
|
||||
},{
|
||||
"time":"2013-03-31T16:21:17.530974Z",
|
||||
"tmst":3512348514,
|
||||
"chan":9,
|
||||
"rfch":1,
|
||||
"freq":869.1,
|
||||
"stat":1,
|
||||
"modu":"FSK",
|
||||
"datr":50000,
|
||||
"rssi":-75,
|
||||
"size":16,
|
||||
"data":"VEVTVF9QQUNLRVRfMTIzNA=="
|
||||
},{
|
||||
"time":"2013-03-31T16:21:17.532038Z",
|
||||
"tmst":3316387610,
|
||||
"chan":0,
|
||||
"rfch":0,
|
||||
"freq":863.00981,
|
||||
"stat":1,
|
||||
"modu":"LORA",
|
||||
"datr":"SF10BW125",
|
||||
"codr":"4/7",
|
||||
"rssi":-38,
|
||||
"lsnr":5.5,
|
||||
"size":32,
|
||||
"data":"ysgRl452xNLep9S1NTIg2lomKDxUgn3DJ7DE+b00Ass"
|
||||
}
|
||||
]}
|
||||
```
|
||||
|
||||
The root object can also contain an object named "stat" :
|
||||
|
||||
``` json
|
||||
{
|
||||
"rxpk":[ {...}, ...],
|
||||
"stat":{...}
|
||||
}
|
||||
```
|
||||
|
||||
It is possible for a packet to contain no "rxpk" array but a "stat" object.
|
||||
|
||||
``` json
|
||||
{
|
||||
"stat":{...}
|
||||
}
|
||||
```
|
||||
|
||||
That object contains the status of the gateway, with the following fields:
|
||||
|
||||
Name | Type | Function
|
||||
:----:|:------:|--------------------------------------------------------------
|
||||
time | string | UTC 'system' time of the gateway, ISO 8601 'expanded' format
|
||||
lati | number | GPS latitude of the gateway in degree (float, N is +)
|
||||
long | number | GPS latitude of the gateway in degree (float, E is +)
|
||||
alti | number | GPS altitude of the gateway in meter RX (integer)
|
||||
rxnb | number | Number of radio packets received (unsigned integer)
|
||||
rxok | number | Number of radio packets received with a valid PHY CRC
|
||||
rxfw | number | Number of radio packets forwarded (unsigned integer)
|
||||
ackr | number | Percentage of upstream datagrams that were acknowledged
|
||||
dwnb | number | Number of downlink datagrams received (unsigned integer)
|
||||
txnb | number | Number of packets emitted (unsigned integer)
|
||||
|
||||
Example (white-spaces, indentation and newlines added for readability):
|
||||
|
||||
``` json
|
||||
{"stat":{
|
||||
"time":"2014-01-12 08:59:28 GMT",
|
||||
"lati":46.24000,
|
||||
"long":3.25230,
|
||||
"alti":145,
|
||||
"rxnb":2,
|
||||
"rxok":2,
|
||||
"rxfw":2,
|
||||
"ackr":100.0,
|
||||
"dwnb":2,
|
||||
"txnb":2
|
||||
}}
|
||||
```
|
||||
|
||||
|
||||
5. Downstream protocol
|
||||
-----------------------
|
||||
|
||||
### 5.1. Sequence diagram ###
|
||||
|
||||
+---------+ +---------+
|
||||
| Gateway | | Server |
|
||||
+---------+ +---------+
|
||||
| -----------------------------------\ |
|
||||
|-| Every N seconds (keepalive time) | |
|
||||
| ------------------------------------ |
|
||||
| |
|
||||
| PULL_DATA (token Y, MAC@) |
|
||||
|------------------------------------------------------------->|
|
||||
| |
|
||||
| PULL_ACK (token Y) |
|
||||
|<-------------------------------------------------------------|
|
||||
| |
|
||||
|
||||
+---------+ +---------+
|
||||
| Gateway | | Server |
|
||||
+---------+ +---------+
|
||||
| ------------------------------------------------------\ |
|
||||
| | Anytime after first PULL_DATA for each packet to TX |-|
|
||||
| ------------------------------------------------------- |
|
||||
| |
|
||||
| PULL_RESP (token Z, JSON payload) |
|
||||
|<-------------------------------------------------------------|
|
||||
| |
|
||||
| TX_ACK (token Z, JSON payload) |
|
||||
|------------------------------------------------------------->|
|
||||
|
||||
### 5.2. PULL_DATA packet ###
|
||||
|
||||
That packet type is used by the gateway to poll data from the server.
|
||||
|
||||
This data exchange is initialized by the gateway because it might be
|
||||
impossible for the server to send packets to the gateway if the gateway is
|
||||
behind a NAT.
|
||||
|
||||
When the gateway initialize the exchange, the network route towards the
|
||||
server will open and will allow for packets to flow both directions.
|
||||
The gateway must periodically send PULL_DATA packets to be sure the network
|
||||
route stays open for the server to be used at any time.
|
||||
|
||||
Bytes | Function
|
||||
:------:|---------------------------------------------------------------------
|
||||
0 | protocol version = 2
|
||||
1-2 | random token
|
||||
3 | PULL_DATA identifier 0x02
|
||||
4-11 | Gateway unique identifier (MAC address)
|
||||
|
||||
### 5.3. PULL_ACK packet ###
|
||||
|
||||
That packet type is used by the server to confirm that the network route is
|
||||
open and that the server can send PULL_RESP packets at any time.
|
||||
|
||||
Bytes | Function
|
||||
:------:|---------------------------------------------------------------------
|
||||
0 | protocol version = 2
|
||||
1-2 | same token as the PULL_DATA packet to acknowledge
|
||||
3 | PULL_ACK identifier 0x04
|
||||
|
||||
### 5.4. PULL_RESP packet ###
|
||||
|
||||
That packet type is used by the server to send RF packets and associated
|
||||
metadata that will have to be emitted by the gateway.
|
||||
|
||||
Bytes | Function
|
||||
:------:|---------------------------------------------------------------------
|
||||
0 | protocol version = 2
|
||||
1-2 | random token
|
||||
3 | PULL_RESP identifier 0x03
|
||||
4-end | JSON object, starting with {, ending with }, see section 6
|
||||
|
||||
### 5.5. TX_ACK packet ###
|
||||
|
||||
That packet type is used by the gateway to send a feedback to the server
|
||||
to inform if a downlink request has been accepted or rejected by the gateway.
|
||||
The datagram may optionnaly contain a JSON string to give more details on
|
||||
acknoledge. If no JSON is present (empty string), this means than no error
|
||||
occured.
|
||||
|
||||
Bytes | Function
|
||||
:------:|---------------------------------------------------------------------
|
||||
0 | protocol version = 2
|
||||
1-2 | same token as the PULL_RESP packet to acknowledge
|
||||
3 | TX_ACK identifier 0x05
|
||||
4-end | [optional] JSON object, starting with {, ending with }, see section 6
|
||||
|
||||
6. Downstream JSON data structure
|
||||
----------------------------------
|
||||
|
||||
The root object of PULL_RESP packet must contain an object named "txpk":
|
||||
|
||||
``` json
|
||||
{
|
||||
"txpk": {...}
|
||||
}
|
||||
```
|
||||
|
||||
That object contain a RF packet to be emitted and associated metadata with the following fields:
|
||||
|
||||
Name | Type | Function
|
||||
:----:|:------:|--------------------------------------------------------------
|
||||
imme | bool | Send packet immediately (will ignore tmst & time)
|
||||
tmst | number | Send packet on a certain timestamp value (will ignore time)
|
||||
time | string | Send packet at a certain time (GPS synchronization required)
|
||||
freq | number | TX central frequency in MHz (unsigned float, Hz precision)
|
||||
rfch | number | Concentrator "RF chain" used for TX (unsigned integer)
|
||||
powe | number | TX output power in dBm (unsigned integer, dBm precision)
|
||||
modu | string | Modulation identifier "LORA" or "FSK"
|
||||
datr | string | LoRa datarate identifier (eg. SF12BW500)
|
||||
datr | number | FSK datarate (unsigned, in bits per second)
|
||||
codr | string | LoRa ECC coding rate identifier
|
||||
fdev | number | FSK frequency deviation (unsigned integer, in Hz)
|
||||
ipol | bool | Lora modulation polarization inversion
|
||||
prea | number | RF preamble size (unsigned integer)
|
||||
size | number | RF packet payload size in bytes (unsigned integer)
|
||||
data | string | Base64 encoded RF packet payload, padding optional
|
||||
ncrc | bool | If true, disable the CRC of the physical layer (optional)
|
||||
|
||||
Most fields are optional.
|
||||
If a field is omitted, default parameters will be used.
|
||||
|
||||
Examples (white-spaces, indentation and newlines added for readability):
|
||||
|
||||
``` json
|
||||
{"txpk":{
|
||||
"imme":true,
|
||||
"freq":864.123456,
|
||||
"rfch":0,
|
||||
"powe":14,
|
||||
"modu":"LORA",
|
||||
"datr":"SF11BW125",
|
||||
"codr":"4/6",
|
||||
"ipol":false,
|
||||
"size":32,
|
||||
"data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v"
|
||||
}}
|
||||
```
|
||||
|
||||
``` json
|
||||
{"txpk":{
|
||||
"imme":true,
|
||||
"freq":861.3,
|
||||
"rfch":0,
|
||||
"powe":12,
|
||||
"modu":"FSK",
|
||||
"datr":50000,
|
||||
"fdev":3000,
|
||||
"size":32,
|
||||
"data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v"
|
||||
}}
|
||||
```
|
||||
|
||||
The root object of TX_ACK packet must contain an object named "txpk_ack":
|
||||
|
||||
``` json
|
||||
{
|
||||
"txpk_ack": {...}
|
||||
}
|
||||
```
|
||||
|
||||
That object contain status information concerning the associated PULL_RESP packet.
|
||||
|
||||
Name | Type | Function
|
||||
:----:|:------:|------------------------------------------------------------------------------
|
||||
error | string | Indication about success or type of failure that occured for downlink request.
|
||||
|
||||
The possible values of "error" field are:
|
||||
|
||||
Value | Definition
|
||||
:-----------------:|---------------------------------------------------------------------
|
||||
NONE | Packet has been programmed for downlink
|
||||
TOO_LATE | Rejected because it was already too late to program this packet for downlink
|
||||
TOO_EARLY | Rejected because downlink packet timestamp is too much in advance
|
||||
COLLISION_PACKET | Rejected because there was already a packet programmed in requested timeframe
|
||||
COLLISION_BEACON | Rejected because there was already a beacon planned in requested timeframe
|
||||
TX_FREQ | Rejected because requested frequency is not supported by TX RF chain
|
||||
TX_POWER | Rejected because requested power is not supported by gateway
|
||||
GPS_UNLOCKED | Rejected because GPS is unlocked, so GPS timestamp cannot be used
|
||||
|
||||
Examples (white-spaces, indentation and newlines added for readability):
|
||||
|
||||
``` json
|
||||
{"txpk_ack":{
|
||||
"error":"COLLISION_PACKET"
|
||||
}}
|
||||
```
|
||||
|
||||
7. Revisions
|
||||
-------------
|
||||
|
||||
### v1.3 ###
|
||||
|
||||
* Added downlink feedback from gateway to server (PULL_RESP -> TX_ACK)
|
||||
|
||||
### v1.2 ###
|
||||
|
||||
* Added value of FSK bitrate for upstream.
|
||||
* Added parameters for FSK bitrate and frequency deviation for downstream.
|
||||
|
||||
### v1.1 ###
|
||||
|
||||
* Added syntax for status report JSON object on upstream.
|
||||
|
||||
### v1.0 ###
|
||||
|
||||
* Initial version.
|
||||
@@ -0,0 +1 @@
|
||||
3.0.0
|
||||
@@ -0,0 +1,71 @@
|
||||
### Application-specific constants
|
||||
|
||||
APP_NAME := lora_pkt_fwd
|
||||
|
||||
### Environment constants
|
||||
|
||||
LGW_PATH ?= ../../lora_gateway/libloragw
|
||||
ARCH ?=
|
||||
CROSS_COMPILE ?=
|
||||
|
||||
OBJDIR = obj
|
||||
INCLUDES = $(wildcard inc/*.h)
|
||||
|
||||
### External constant definitions
|
||||
# must get library build option to know if mpsse must be linked or not
|
||||
|
||||
include $(LGW_PATH)/library.cfg
|
||||
RELEASE_VERSION := `cat ../VERSION`
|
||||
|
||||
### Constant symbols
|
||||
|
||||
CC := $(CROSS_COMPILE)gcc
|
||||
AR := $(CROSS_COMPILE)ar
|
||||
|
||||
CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I.
|
||||
VFLAG := -D VERSION_STRING="\"$(RELEASE_VERSION)\""
|
||||
|
||||
### Constants for Lora concentrator HAL library
|
||||
# List the library sub-modules that are used by the application
|
||||
|
||||
LGW_INC =
|
||||
ifneq ($(wildcard $(LGW_PATH)/inc/config.h),)
|
||||
# only for HAL version 1.3 and beyond
|
||||
LGW_INC += $(LGW_PATH)/inc/config.h
|
||||
endif
|
||||
LGW_INC += $(LGW_PATH)/inc/loragw_hal.h
|
||||
LGW_INC += $(LGW_PATH)/inc/loragw_gps.h
|
||||
|
||||
### Linking options
|
||||
|
||||
ifeq ($(CFG_SPI),native)
|
||||
LIBS := -lloragw -lrt -lpthread -lm
|
||||
else ifeq ($(CFG_SPI),ftdi)
|
||||
LIBS := -lloragw -lrt -lpthread -lm -lmpsse
|
||||
endif
|
||||
|
||||
### General build targets
|
||||
|
||||
all: $(APP_NAME)
|
||||
|
||||
clean:
|
||||
rm -f $(OBJDIR)/*.o
|
||||
rm -f $(APP_NAME)
|
||||
|
||||
### Sub-modules compilation
|
||||
|
||||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
$(OBJDIR)/%.o: src/%.c $(INCLUDES) | $(OBJDIR)
|
||||
$(CC) -c $(CFLAGS) -I$(LGW_PATH)/inc $< -o $@
|
||||
|
||||
### Main program compilation and assembly
|
||||
|
||||
$(OBJDIR)/$(APP_NAME).o: src/$(APP_NAME).c $(LGW_INC) $(INCLUDES) | $(OBJDIR)
|
||||
$(CC) -c $(CFLAGS) $(VFLAG) -I$(LGW_PATH)/inc $< -o $@
|
||||
|
||||
$(APP_NAME): $(OBJDIR)/$(APP_NAME).o $(LGW_PATH)/libloragw.a $(OBJDIR)/parson.o $(OBJDIR)/base64.o $(OBJDIR)/jitqueue.o $(OBJDIR)/timersync.o
|
||||
$(CC) -L$(LGW_PATH) $< $(OBJDIR)/parson.o $(OBJDIR)/base64.o $(OBJDIR)/jitqueue.o $(OBJDIR)/timersync.o -o $@ $(LIBS)
|
||||
|
||||
### EOF
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
{
|
||||
"SX1301_conf": {
|
||||
"lorawan_public": true,
|
||||
"clksrc": 1, /* radio_1 provides clock to concentrator */
|
||||
"antenna_gain": 0, /* antenna gain, in dBi */
|
||||
"radio_0": {
|
||||
"enable": true,
|
||||
"type": "SX1257",
|
||||
"freq": 867500000,
|
||||
"rssi_offset": -166.0,
|
||||
"tx_enable": true,
|
||||
"tx_freq_min": 863000000,
|
||||
"tx_freq_max": 870000000
|
||||
},
|
||||
"radio_1": {
|
||||
"enable": true,
|
||||
"type": "SX1257",
|
||||
"freq": 868500000,
|
||||
"rssi_offset": -166.0,
|
||||
"tx_enable": false
|
||||
},
|
||||
"chan_multiSF_0": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.1 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -400000
|
||||
},
|
||||
"chan_multiSF_1": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -200000
|
||||
},
|
||||
"chan_multiSF_2": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.5 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": 0
|
||||
},
|
||||
"chan_multiSF_3": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.1 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": -400000
|
||||
},
|
||||
"chan_multiSF_4": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": -200000
|
||||
},
|
||||
"chan_multiSF_5": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.5 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 0
|
||||
},
|
||||
"chan_multiSF_6": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.7 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 200000
|
||||
},
|
||||
"chan_multiSF_7": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.9 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 400000
|
||||
},
|
||||
"chan_Lora_std": {
|
||||
/* Lora MAC channel, 250kHz, SF7, 868.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -200000,
|
||||
"bandwidth": 250000,
|
||||
"spread_factor": 7
|
||||
},
|
||||
"chan_FSK": {
|
||||
/* FSK 50kbps channel, 868.8 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": 300000,
|
||||
"bandwidth": 125000,
|
||||
"datarate": 50000
|
||||
},
|
||||
"tx_lut_0": {
|
||||
/* TX gain table, index 0 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 8,
|
||||
"rf_power": -6,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_1": {
|
||||
/* TX gain table, index 1 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 10,
|
||||
"rf_power": -3,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_2": {
|
||||
/* TX gain table, index 2 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 0,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_3": {
|
||||
/* TX gain table, index 3 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 8,
|
||||
"rf_power": 3,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_4": {
|
||||
/* TX gain table, index 4 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 6,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_5": {
|
||||
/* TX gain table, index 5 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 10,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_6": {
|
||||
/* TX gain table, index 6 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 13,
|
||||
"rf_power": 11,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_7": {
|
||||
/* TX gain table, index 7 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 9,
|
||||
"rf_power": 12,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_8": {
|
||||
/* TX gain table, index 8 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 15,
|
||||
"rf_power": 13,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_9": {
|
||||
/* TX gain table, index 9 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 14,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_10": {
|
||||
/* TX gain table, index 10 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 11,
|
||||
"rf_power": 16,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_11": {
|
||||
/* TX gain table, index 11 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 9,
|
||||
"rf_power": 20,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_12": {
|
||||
/* TX gain table, index 12 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 23,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_13": {
|
||||
/* TX gain table, index 13 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 11,
|
||||
"rf_power": 25,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_14": {
|
||||
/* TX gain table, index 14 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 26,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_15": {
|
||||
/* TX gain table, index 15 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 14,
|
||||
"rf_power": 27,
|
||||
"dig_gain": 0
|
||||
}
|
||||
},
|
||||
|
||||
"gateway_conf": {
|
||||
"gateway_ID": "AA555A0000000000",
|
||||
/* change with default server address/ports, or overwrite in local_conf.json */
|
||||
"server_address": "localhost",
|
||||
"serv_port_up": 1680,
|
||||
"serv_port_down": 1680,
|
||||
/* adjust the following parameters for your network */
|
||||
"keepalive_interval": 10,
|
||||
"stat_interval": 30,
|
||||
"push_timeout_ms": 100,
|
||||
/* forward only valid packets */
|
||||
"forward_crc_valid": true,
|
||||
"forward_crc_error": false,
|
||||
"forward_crc_disabled": false
|
||||
}
|
||||
}
|
||||
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
{
|
||||
"SX1301_conf": {
|
||||
"lorawan_public": true,
|
||||
<<<<<<< HEAD:beacon_pkt_fwd/global_conf.json
|
||||
"clksrc": 0, /* radio_0 provides clock to concentrator */
|
||||
=======
|
||||
"clksrc": 1, /* radio_1 provides clock to concentrator */
|
||||
"antenna_gain": 0, /* antenna gain, in dBi */
|
||||
>>>>>>> f31ee2e4eb6bcbb0c93ef685716842e151c8bd9f:lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.beacon
|
||||
"radio_0": {
|
||||
"enable": true,
|
||||
"type": "SX1257",
|
||||
"freq": 868200000,
|
||||
"rssi_offset": -166.0,
|
||||
"tx_enable": true,
|
||||
"tx_freq_min": 863000000,
|
||||
"tx_freq_max": 870000000
|
||||
},
|
||||
"radio_1": {
|
||||
"enable": true,
|
||||
"type": "SX1257",
|
||||
"freq": 869200000,
|
||||
"rssi_offset": -166.0,
|
||||
"tx_enable": false
|
||||
},
|
||||
"chan_multiSF_0": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.1 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": -100000
|
||||
},
|
||||
"chan_multiSF_1": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 100000
|
||||
},
|
||||
"chan_multiSF_2": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.5 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 300000
|
||||
},
|
||||
"chan_multiSF_3": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.85 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -350000
|
||||
},
|
||||
"chan_multiSF_4": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 869.05 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -150000
|
||||
},
|
||||
"chan_multiSF_5": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 869.525 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": 325000
|
||||
},
|
||||
"chan_multiSF_6": {
|
||||
"enable": false,
|
||||
"radio": 0,
|
||||
"if": 0
|
||||
},
|
||||
"chan_multiSF_7": {
|
||||
"enable": false,
|
||||
"radio": 0,
|
||||
"if": 0
|
||||
},
|
||||
"chan_Lora_std": {
|
||||
/* Lora MAC channel, 250kHz, SF7, 868.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 100000,
|
||||
"bandwidth": 250000,
|
||||
"spread_factor": 7
|
||||
},
|
||||
"chan_FSK": {
|
||||
/* FSK 50kbps channel, 868.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 100000,
|
||||
"bandwidth": 125000,
|
||||
"datarate": 50000
|
||||
},
|
||||
"tx_lut_0": {
|
||||
/* TX gain table, index 0 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 8,
|
||||
"rf_power": -6,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_1": {
|
||||
/* TX gain table, index 1 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 10,
|
||||
"rf_power": -3,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_2": {
|
||||
/* TX gain table, index 2 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 0,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_3": {
|
||||
/* TX gain table, index 3 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 8,
|
||||
"rf_power": 3,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_4": {
|
||||
/* TX gain table, index 4 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 6,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_5": {
|
||||
/* TX gain table, index 5 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 10,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_6": {
|
||||
/* TX gain table, index 6 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 13,
|
||||
"rf_power": 11,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_7": {
|
||||
/* TX gain table, index 7 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 9,
|
||||
"rf_power": 12,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_8": {
|
||||
/* TX gain table, index 8 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 15,
|
||||
"rf_power": 13,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_9": {
|
||||
/* TX gain table, index 9 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 14,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_10": {
|
||||
/* TX gain table, index 10 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 11,
|
||||
"rf_power": 16,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_11": {
|
||||
/* TX gain table, index 11 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 20,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_12": {
|
||||
/* TX gain table, index 12 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 11,
|
||||
"rf_power": 23,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_13": {
|
||||
/* TX gain table, index 13 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 24,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_14": {
|
||||
/* TX gain table, index 14 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 13,
|
||||
"rf_power": 25,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_15": {
|
||||
/* TX gain table, index 15 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 15,
|
||||
"rf_power": 26,
|
||||
"dig_gain": 0
|
||||
}
|
||||
},
|
||||
|
||||
"gateway_conf": {
|
||||
"gateway_ID": "AA555A0000000000",
|
||||
/* change with default server address/ports, or overwrite in local_conf.json */
|
||||
"server_address": "localhost",
|
||||
"serv_port_up": 1680,
|
||||
"serv_port_down": 1680,
|
||||
/* adjust the following parameters for your network */
|
||||
"keepalive_interval": 10,
|
||||
"stat_interval": 30,
|
||||
"push_timeout_ms": 100,
|
||||
/* forward only valid packets */
|
||||
"forward_crc_valid": true,
|
||||
"forward_crc_error": false,
|
||||
<<<<<<< HEAD:beacon_pkt_fwd/global_conf.json
|
||||
"forward_crc_disabled": false
|
||||
=======
|
||||
"forward_crc_disabled": false,
|
||||
/* GPS configuration */
|
||||
"gps_tty_path": "/dev/ttyAMA0",
|
||||
/* GPS reference coordinates */
|
||||
"ref_latitude": 0.0,
|
||||
"ref_longitude": 0.0,
|
||||
"ref_altitude": 0,
|
||||
/* Beaconing parameters */
|
||||
"beacon_period": 128,
|
||||
"beacon_freq_hz": 869525000
|
||||
>>>>>>> f31ee2e4eb6bcbb0c93ef685716842e151c8bd9f:lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.beacon
|
||||
}
|
||||
}
|
||||
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
{
|
||||
"SX1301_conf": {
|
||||
"lorawan_public": true,
|
||||
<<<<<<< HEAD:gps_pkt_fwd/global_conf.json
|
||||
"clksrc": 0, /* radio_0 provides clock to concentrator */
|
||||
=======
|
||||
"clksrc": 1, /* radio_1 provides clock to concentrator */
|
||||
"antenna_gain": 0, /* antenna gain, in dBi */
|
||||
>>>>>>> f31ee2e4eb6bcbb0c93ef685716842e151c8bd9f:lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.gps
|
||||
"radio_0": {
|
||||
"enable": true,
|
||||
"type": "SX1257",
|
||||
"freq": 868200000,
|
||||
"rssi_offset": -166.0,
|
||||
"tx_enable": true,
|
||||
"tx_freq_min": 863000000,
|
||||
"tx_freq_max": 870000000
|
||||
},
|
||||
"radio_1": {
|
||||
"enable": true,
|
||||
"type": "SX1257",
|
||||
"freq": 869200000,
|
||||
"rssi_offset": -166.0,
|
||||
"tx_enable": false
|
||||
},
|
||||
"chan_multiSF_0": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.1 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": -100000
|
||||
},
|
||||
"chan_multiSF_1": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 100000
|
||||
},
|
||||
"chan_multiSF_2": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.5 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 300000
|
||||
},
|
||||
"chan_multiSF_3": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.85 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -350000
|
||||
},
|
||||
"chan_multiSF_4": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 869.05 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -150000
|
||||
},
|
||||
"chan_multiSF_5": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 869.525 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": 325000
|
||||
},
|
||||
"chan_multiSF_6": {
|
||||
"enable": false,
|
||||
"radio": 0,
|
||||
"if": 0
|
||||
},
|
||||
"chan_multiSF_7": {
|
||||
"enable": false,
|
||||
"radio": 0,
|
||||
"if": 0
|
||||
},
|
||||
"chan_Lora_std": {
|
||||
/* Lora MAC channel, 250kHz, SF7, 868.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 100000,
|
||||
"bandwidth": 250000,
|
||||
"spread_factor": 7
|
||||
},
|
||||
"chan_FSK": {
|
||||
/* FSK 50kbps channel, 868.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 100000,
|
||||
"bandwidth": 125000,
|
||||
"datarate": 50000
|
||||
},
|
||||
"tx_lut_0": {
|
||||
/* TX gain table, index 0 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 8,
|
||||
"rf_power": -6,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_1": {
|
||||
/* TX gain table, index 1 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 10,
|
||||
"rf_power": -3,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_2": {
|
||||
/* TX gain table, index 2 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 0,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_3": {
|
||||
/* TX gain table, index 3 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 8,
|
||||
"rf_power": 3,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_4": {
|
||||
/* TX gain table, index 4 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 6,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_5": {
|
||||
/* TX gain table, index 5 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 10,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_6": {
|
||||
/* TX gain table, index 6 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 13,
|
||||
"rf_power": 11,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_7": {
|
||||
/* TX gain table, index 7 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 9,
|
||||
"rf_power": 12,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_8": {
|
||||
/* TX gain table, index 8 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 15,
|
||||
"rf_power": 13,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_9": {
|
||||
/* TX gain table, index 9 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 14,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_10": {
|
||||
/* TX gain table, index 10 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 11,
|
||||
"rf_power": 16,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_11": {
|
||||
/* TX gain table, index 11 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 20,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_12": {
|
||||
/* TX gain table, index 12 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 11,
|
||||
"rf_power": 23,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_13": {
|
||||
/* TX gain table, index 13 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 24,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_14": {
|
||||
/* TX gain table, index 14 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 13,
|
||||
"rf_power": 25,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_15": {
|
||||
/* TX gain table, index 15 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 15,
|
||||
"rf_power": 26,
|
||||
"dig_gain": 0
|
||||
}
|
||||
},
|
||||
|
||||
"gateway_conf": {
|
||||
"gateway_ID": "AA555A0000000000",
|
||||
/* change with default server address/ports, or overwrite in local_conf.json */
|
||||
"server_address": "localhost",
|
||||
"serv_port_up": 1680,
|
||||
"serv_port_down": 1680,
|
||||
/* adjust the following parameters for your network */
|
||||
"keepalive_interval": 10,
|
||||
"stat_interval": 30,
|
||||
"push_timeout_ms": 100,
|
||||
/* forward only valid packets */
|
||||
"forward_crc_valid": true,
|
||||
"forward_crc_error": false,
|
||||
<<<<<<< HEAD:gps_pkt_fwd/global_conf.json
|
||||
"forward_crc_disabled": false
|
||||
=======
|
||||
"forward_crc_disabled": false,
|
||||
/* GPS configuration */
|
||||
"gps_tty_path": "/dev/ttyAMA0",
|
||||
/* GPS reference coordinates */
|
||||
"ref_latitude": 0.0,
|
||||
"ref_longitude": 0.0,
|
||||
"ref_altitude": 0
|
||||
>>>>>>> f31ee2e4eb6bcbb0c93ef685716842e151c8bd9f:lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.gps
|
||||
}
|
||||
}
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
{
|
||||
"SX1301_conf": {
|
||||
"lorawan_public": true,
|
||||
"clksrc": 1, /* radio_1 provides clock to concentrator */
|
||||
"lbt_cfg": {
|
||||
"enable": false,
|
||||
"rssi_target": 160, /* rssi in dBm = -lbt_rssi_target/2 */
|
||||
"nb_channel": 1,
|
||||
"start_freq": 869525000,
|
||||
"scan_time_us": 5000,
|
||||
"tx_delay_1ch_us": 4000000,
|
||||
"tx_delay_2ch_us": 4000000
|
||||
},
|
||||
"antenna_gain": 0, /* antenna gain, in dBi */
|
||||
"radio_0": {
|
||||
"enable": true,
|
||||
"type": "SX1257",
|
||||
"freq": 867500000,
|
||||
"rssi_offset": -165.0,
|
||||
"tx_enable": true,
|
||||
"tx_freq_min": 863000000,
|
||||
"tx_freq_max": 870000000
|
||||
},
|
||||
"radio_1": {
|
||||
"enable": true,
|
||||
"type": "SX1257",
|
||||
"freq": 868500000,
|
||||
"rssi_offset": -165.0,
|
||||
"tx_enable": false
|
||||
},
|
||||
"chan_multiSF_0": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.1 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -400000
|
||||
},
|
||||
"chan_multiSF_1": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -200000
|
||||
},
|
||||
"chan_multiSF_2": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.5 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": 0
|
||||
},
|
||||
"chan_multiSF_3": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.1 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": -400000
|
||||
},
|
||||
"chan_multiSF_4": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": -200000
|
||||
},
|
||||
"chan_multiSF_5": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.5 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 0
|
||||
},
|
||||
"chan_multiSF_6": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.7 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 200000
|
||||
},
|
||||
"chan_multiSF_7": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.9 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 400000
|
||||
},
|
||||
"chan_Lora_std": {
|
||||
/* Lora MAC channel, 250kHz, SF7, 868.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -200000,
|
||||
"bandwidth": 250000,
|
||||
"spread_factor": 7
|
||||
},
|
||||
"chan_FSK": {
|
||||
/* FSK 50kbps channel, 868.8 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": 300000,
|
||||
"bandwidth": 125000,
|
||||
"datarate": 50000
|
||||
},
|
||||
"tx_lut_0": {
|
||||
/* TX gain table, index 0 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 8,
|
||||
"rf_power": -6,
|
||||
"dig_gain": 3
|
||||
},
|
||||
"tx_lut_1": {
|
||||
/* TX gain table, index 1 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 10,
|
||||
"rf_power": -3,
|
||||
"dig_gain": 3
|
||||
},
|
||||
"tx_lut_2": {
|
||||
/* TX gain table, index 2 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 0,
|
||||
"dig_gain": 1
|
||||
},
|
||||
"tx_lut_3": {
|
||||
/* TX gain table, index 3 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 14,
|
||||
"rf_power": 3,
|
||||
"dig_gain": 2
|
||||
},
|
||||
"tx_lut_4": {
|
||||
/* TX gain table, index 4 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 6,
|
||||
"dig_gain": 3
|
||||
},
|
||||
"tx_lut_5": {
|
||||
/* TX gain table, index 5 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 10,
|
||||
"dig_gain": 2
|
||||
},
|
||||
"tx_lut_6": {
|
||||
/* TX gain table, index 6 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 11,
|
||||
"dig_gain": 1
|
||||
},
|
||||
"tx_lut_7": {
|
||||
/* TX gain table, index 7 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 12,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_8": {
|
||||
/* TX gain table, index 8 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 14,
|
||||
"rf_power": 13,
|
||||
"dig_gain": 2
|
||||
},
|
||||
"tx_lut_9": {
|
||||
/* TX gain table, index 9 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 13,
|
||||
"rf_power": 14,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_10": {
|
||||
/* TX gain table, index 10 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 9,
|
||||
"rf_power": 16,
|
||||
"dig_gain": 2
|
||||
},
|
||||
"tx_lut_11": {
|
||||
/* TX gain table, index 11 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 11,
|
||||
"rf_power": 20,
|
||||
"dig_gain": 1
|
||||
},
|
||||
"tx_lut_12": {
|
||||
/* TX gain table, index 12 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 13,
|
||||
"rf_power": 23,
|
||||
"dig_gain": 1
|
||||
},
|
||||
"tx_lut_13": {
|
||||
/* TX gain table, index 13 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 15,
|
||||
"rf_power": 25,
|
||||
"dig_gain": 2
|
||||
},
|
||||
"tx_lut_14": {
|
||||
/* TX gain table, index 14 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 26,
|
||||
"dig_gain": 2
|
||||
},
|
||||
"tx_lut_15": {
|
||||
/* TX gain table, index 15 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 27,
|
||||
"dig_gain": 1
|
||||
}
|
||||
},
|
||||
|
||||
"gateway_conf": {
|
||||
"gateway_ID": "AA555A0000000000",
|
||||
/* change with default server address/ports, or overwrite in local_conf.json */
|
||||
"server_address": "localhost",
|
||||
"serv_port_up": 1680,
|
||||
"serv_port_down": 1680,
|
||||
/* adjust the following parameters for your network */
|
||||
"keepalive_interval": 10,
|
||||
"stat_interval": 30,
|
||||
"push_timeout_ms": 100,
|
||||
/* forward only valid packets */
|
||||
"forward_crc_valid": true,
|
||||
"forward_crc_error": false,
|
||||
"forward_crc_disabled": false
|
||||
}
|
||||
}
|
||||
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
{
|
||||
"SX1301_conf": {
|
||||
"lorawan_public": true,
|
||||
"clksrc": 1, /* radio_1 provides clock to concentrator */
|
||||
"lbt_cfg": {
|
||||
"enable": false,
|
||||
"rssi_target": 160, /* rssi in dBm = -lbt_rssi_target/2 */
|
||||
"nb_channel": 1,
|
||||
"start_freq": 869525000,
|
||||
"scan_time_us": 5000,
|
||||
"tx_delay_1ch_us": 4000000,
|
||||
"tx_delay_2ch_us": 4000000
|
||||
},
|
||||
"antenna_gain": 0, /* antenna gain, in dBi */
|
||||
"radio_0": {
|
||||
"enable": true,
|
||||
"type": "SX1257",
|
||||
"freq": 867500000,
|
||||
"rssi_offset": -165.0,
|
||||
"tx_enable": true,
|
||||
"tx_freq_min": 863000000,
|
||||
"tx_freq_max": 870000000
|
||||
},
|
||||
"radio_1": {
|
||||
"enable": true,
|
||||
"type": "SX1257",
|
||||
"freq": 868500000,
|
||||
"rssi_offset": -165.0,
|
||||
"tx_enable": false
|
||||
},
|
||||
"chan_multiSF_0": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.1 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -400000
|
||||
},
|
||||
"chan_multiSF_1": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -200000
|
||||
},
|
||||
"chan_multiSF_2": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.5 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": 0
|
||||
},
|
||||
"chan_multiSF_3": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.1 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": -400000
|
||||
},
|
||||
"chan_multiSF_4": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": -200000
|
||||
},
|
||||
"chan_multiSF_5": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.5 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 0
|
||||
},
|
||||
"chan_multiSF_6": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.7 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 200000
|
||||
},
|
||||
"chan_multiSF_7": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.9 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 400000
|
||||
},
|
||||
"chan_Lora_std": {
|
||||
/* Lora MAC channel, 250kHz, SF7, 868.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -200000,
|
||||
"bandwidth": 250000,
|
||||
"spread_factor": 7
|
||||
},
|
||||
"chan_FSK": {
|
||||
/* FSK 50kbps channel, 868.8 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": 300000,
|
||||
"bandwidth": 125000,
|
||||
"datarate": 50000
|
||||
},
|
||||
"tx_lut_0": {
|
||||
/* TX gain table, index 0 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 8,
|
||||
"rf_power": -6,
|
||||
"dig_gain": 3
|
||||
},
|
||||
"tx_lut_1": {
|
||||
/* TX gain table, index 1 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 10,
|
||||
"rf_power": -3,
|
||||
"dig_gain": 3
|
||||
},
|
||||
"tx_lut_2": {
|
||||
/* TX gain table, index 2 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 0,
|
||||
"dig_gain": 1
|
||||
},
|
||||
"tx_lut_3": {
|
||||
/* TX gain table, index 3 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 14,
|
||||
"rf_power": 3,
|
||||
"dig_gain": 2
|
||||
},
|
||||
"tx_lut_4": {
|
||||
/* TX gain table, index 4 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 6,
|
||||
"dig_gain": 3
|
||||
},
|
||||
"tx_lut_5": {
|
||||
/* TX gain table, index 5 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 10,
|
||||
"dig_gain": 2
|
||||
},
|
||||
"tx_lut_6": {
|
||||
/* TX gain table, index 6 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 11,
|
||||
"dig_gain": 1
|
||||
},
|
||||
"tx_lut_7": {
|
||||
/* TX gain table, index 7 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 12,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_8": {
|
||||
/* TX gain table, index 8 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 14,
|
||||
"rf_power": 13,
|
||||
"dig_gain": 2
|
||||
},
|
||||
"tx_lut_9": {
|
||||
/* TX gain table, index 9 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 13,
|
||||
"rf_power": 14,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_10": {
|
||||
/* TX gain table, index 10 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 9,
|
||||
"rf_power": 16,
|
||||
"dig_gain": 2
|
||||
},
|
||||
"tx_lut_11": {
|
||||
/* TX gain table, index 11 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 11,
|
||||
"rf_power": 20,
|
||||
"dig_gain": 1
|
||||
},
|
||||
"tx_lut_12": {
|
||||
/* TX gain table, index 12 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 13,
|
||||
"rf_power": 23,
|
||||
"dig_gain": 1
|
||||
},
|
||||
"tx_lut_13": {
|
||||
/* TX gain table, index 13 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 15,
|
||||
"rf_power": 25,
|
||||
"dig_gain": 2
|
||||
},
|
||||
"tx_lut_14": {
|
||||
/* TX gain table, index 14 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 26,
|
||||
"dig_gain": 2
|
||||
},
|
||||
"tx_lut_15": {
|
||||
/* TX gain table, index 15 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 27,
|
||||
"dig_gain": 1
|
||||
}
|
||||
},
|
||||
|
||||
"gateway_conf": {
|
||||
"gateway_ID": "AA555A0000000000",
|
||||
/* change with default server address/ports, or overwrite in local_conf.json */
|
||||
"server_address": "localhost",
|
||||
"serv_port_up": 1680,
|
||||
"serv_port_down": 1680,
|
||||
/* adjust the following parameters for your network */
|
||||
"keepalive_interval": 10,
|
||||
"stat_interval": 30,
|
||||
"push_timeout_ms": 100,
|
||||
/* forward only valid packets */
|
||||
"forward_crc_valid": true,
|
||||
"forward_crc_error": false,
|
||||
"forward_crc_disabled": false,
|
||||
/* GPS configuration */
|
||||
"gps_tty_path": "/dev/ttyAMA0",
|
||||
/* GPS reference coordinates */
|
||||
"ref_latitude": 0.0,
|
||||
"ref_longitude": 0.0,
|
||||
"ref_altitude": 0,
|
||||
/* Beaconing parameters */
|
||||
"beacon_period": 128,
|
||||
"beacon_freq_hz": 869525000
|
||||
}
|
||||
}
|
||||
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
{
|
||||
"SX1301_conf": {
|
||||
"lorawan_public": true,
|
||||
"clksrc": 1, /* radio_1 provides clock to concentrator */
|
||||
"lbt_cfg": {
|
||||
"enable": false,
|
||||
"rssi_target": 160, /* rssi in dBm = -lbt_rssi_target/2 */
|
||||
"nb_channel": 1,
|
||||
"start_freq": 869525000,
|
||||
"scan_time_us": 5000,
|
||||
"tx_delay_1ch_us": 4000000,
|
||||
"tx_delay_2ch_us": 4000000
|
||||
},
|
||||
"antenna_gain": 0, /* antenna gain, in dBi */
|
||||
"radio_0": {
|
||||
"enable": true,
|
||||
"type": "SX1257",
|
||||
"freq": 867500000,
|
||||
"rssi_offset": -165.0,
|
||||
"tx_enable": true,
|
||||
"tx_freq_min": 863000000,
|
||||
"tx_freq_max": 870000000
|
||||
},
|
||||
"radio_1": {
|
||||
"enable": true,
|
||||
"type": "SX1257",
|
||||
"freq": 868500000,
|
||||
"rssi_offset": -165.0,
|
||||
"tx_enable": false
|
||||
},
|
||||
"chan_multiSF_0": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.1 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -400000
|
||||
},
|
||||
"chan_multiSF_1": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -200000
|
||||
},
|
||||
"chan_multiSF_2": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 868.5 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": 0
|
||||
},
|
||||
"chan_multiSF_3": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.1 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": -400000
|
||||
},
|
||||
"chan_multiSF_4": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": -200000
|
||||
},
|
||||
"chan_multiSF_5": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.5 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 0
|
||||
},
|
||||
"chan_multiSF_6": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.7 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 200000
|
||||
},
|
||||
"chan_multiSF_7": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 867.9 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 400000
|
||||
},
|
||||
"chan_Lora_std": {
|
||||
/* Lora MAC channel, 250kHz, SF7, 868.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -200000,
|
||||
"bandwidth": 250000,
|
||||
"spread_factor": 7
|
||||
},
|
||||
"chan_FSK": {
|
||||
/* FSK 50kbps channel, 868.8 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": 300000,
|
||||
"bandwidth": 125000,
|
||||
"datarate": 50000
|
||||
},
|
||||
"tx_lut_0": {
|
||||
/* TX gain table, index 0 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 8,
|
||||
"rf_power": -6,
|
||||
"dig_gain": 3
|
||||
},
|
||||
"tx_lut_1": {
|
||||
/* TX gain table, index 1 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 10,
|
||||
"rf_power": -3,
|
||||
"dig_gain": 3
|
||||
},
|
||||
"tx_lut_2": {
|
||||
/* TX gain table, index 2 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 0,
|
||||
"dig_gain": 1
|
||||
},
|
||||
"tx_lut_3": {
|
||||
/* TX gain table, index 3 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 14,
|
||||
"rf_power": 3,
|
||||
"dig_gain": 2
|
||||
},
|
||||
"tx_lut_4": {
|
||||
/* TX gain table, index 4 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 6,
|
||||
"dig_gain": 3
|
||||
},
|
||||
"tx_lut_5": {
|
||||
/* TX gain table, index 5 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 10,
|
||||
"dig_gain": 2
|
||||
},
|
||||
"tx_lut_6": {
|
||||
/* TX gain table, index 6 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 11,
|
||||
"dig_gain": 1
|
||||
},
|
||||
"tx_lut_7": {
|
||||
/* TX gain table, index 7 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 12,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_8": {
|
||||
/* TX gain table, index 8 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 14,
|
||||
"rf_power": 13,
|
||||
"dig_gain": 2
|
||||
},
|
||||
"tx_lut_9": {
|
||||
/* TX gain table, index 9 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 13,
|
||||
"rf_power": 14,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_10": {
|
||||
/* TX gain table, index 10 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 9,
|
||||
"rf_power": 16,
|
||||
"dig_gain": 2
|
||||
},
|
||||
"tx_lut_11": {
|
||||
/* TX gain table, index 11 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 11,
|
||||
"rf_power": 20,
|
||||
"dig_gain": 1
|
||||
},
|
||||
"tx_lut_12": {
|
||||
/* TX gain table, index 12 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 13,
|
||||
"rf_power": 23,
|
||||
"dig_gain": 1
|
||||
},
|
||||
"tx_lut_13": {
|
||||
/* TX gain table, index 13 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 15,
|
||||
"rf_power": 25,
|
||||
"dig_gain": 2
|
||||
},
|
||||
"tx_lut_14": {
|
||||
/* TX gain table, index 14 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 26,
|
||||
"dig_gain": 2
|
||||
},
|
||||
"tx_lut_15": {
|
||||
/* TX gain table, index 15 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 27,
|
||||
"dig_gain": 1
|
||||
}
|
||||
},
|
||||
|
||||
"gateway_conf": {
|
||||
"gateway_ID": "AA555A0000000000",
|
||||
/* change with default server address/ports, or overwrite in local_conf.json */
|
||||
"server_address": "localhost",
|
||||
"serv_port_up": 1680,
|
||||
"serv_port_down": 1680,
|
||||
/* adjust the following parameters for your network */
|
||||
"keepalive_interval": 10,
|
||||
"stat_interval": 30,
|
||||
"push_timeout_ms": 100,
|
||||
/* forward only valid packets */
|
||||
"forward_crc_valid": true,
|
||||
"forward_crc_error": false,
|
||||
"forward_crc_disabled": false,
|
||||
/* GPS configuration */
|
||||
"gps_tty_path": "/dev/ttyAMA0",
|
||||
/* GPS reference coordinates */
|
||||
"ref_latitude": 0.0,
|
||||
"ref_longitude": 0.0,
|
||||
"ref_altitude": 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"SX1301_conf": {
|
||||
"lorawan_public": true,
|
||||
"clksrc": 1, /* radio_1 provides clock to concentrator */
|
||||
"antenna_gain": 0, /* antenna gain, in dBi */
|
||||
"radio_0": {
|
||||
"enable": true,
|
||||
"type": "SX1257",
|
||||
"freq": 902700000,
|
||||
"rssi_offset": -166.0,
|
||||
"tx_enable": true,
|
||||
"tx_freq_min": 902000000,
|
||||
"tx_freq_max": 928000000
|
||||
},
|
||||
"radio_1": {
|
||||
"enable": true,
|
||||
"type": "SX1257",
|
||||
"freq": 903400000,
|
||||
"rssi_offset": -166.0,
|
||||
"tx_enable": false
|
||||
},
|
||||
"chan_multiSF_0": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 902.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": -400000
|
||||
},
|
||||
"chan_multiSF_1": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 902.5 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": -200000
|
||||
},
|
||||
"chan_multiSF_2": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 902.7 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 0
|
||||
},
|
||||
"chan_multiSF_3": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 902.9 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 200000
|
||||
},
|
||||
"chan_multiSF_4": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 903.1 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -300000
|
||||
},
|
||||
"chan_multiSF_5": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 903.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -100000
|
||||
},
|
||||
"chan_multiSF_6": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 903.5 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": 100000
|
||||
},
|
||||
"chan_multiSF_7": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 903.7 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": 300000
|
||||
},
|
||||
"chan_Lora_std": {
|
||||
/* Lora MAC channel, 500kHz, SF8, 903.0 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 300000,
|
||||
"bandwidth": 500000,
|
||||
"spread_factor": 8
|
||||
},
|
||||
"chan_FSK": {
|
||||
/* FSK 100kbps channel, 903.0 MHz */
|
||||
"enable": false,
|
||||
"radio": 0,
|
||||
"if": 300000,
|
||||
"bandwidth": 250000,
|
||||
"datarate": 100000
|
||||
}
|
||||
},
|
||||
|
||||
"gateway_conf": {
|
||||
"gateway_ID": "AA555A0000000000",
|
||||
/* change with default server address/ports, or overwrite in local_conf.json */
|
||||
"server_address": "localhost",
|
||||
"serv_port_up": 1680,
|
||||
"serv_port_down": 1680,
|
||||
/* adjust the following parameters for your network */
|
||||
"keepalive_interval": 10,
|
||||
"stat_interval": 30,
|
||||
"push_timeout_ms": 100,
|
||||
/* forward only valid packets */
|
||||
"forward_crc_valid": true,
|
||||
"forward_crc_error": false,
|
||||
"forward_crc_disabled": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"SX1301_conf": {
|
||||
"lorawan_public": true,
|
||||
"clksrc": 1, /* radio_1 provides clock to concentrator */
|
||||
"antenna_gain": 0, /* antenna gain, in dBi */
|
||||
"radio_0": {
|
||||
"enable": true,
|
||||
"type": "SX1257",
|
||||
"freq": 902700000,
|
||||
"rssi_offset": -166.0,
|
||||
"tx_enable": true,
|
||||
"tx_freq_min": 902000000,
|
||||
"tx_freq_max": 928000000
|
||||
},
|
||||
"radio_1": {
|
||||
"enable": true,
|
||||
"type": "SX1257",
|
||||
"freq": 903400000,
|
||||
"rssi_offset": -166.0,
|
||||
"tx_enable": false
|
||||
},
|
||||
"chan_multiSF_0": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 902.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": -400000
|
||||
},
|
||||
"chan_multiSF_1": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 902.5 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": -200000
|
||||
},
|
||||
"chan_multiSF_2": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 902.7 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 0
|
||||
},
|
||||
"chan_multiSF_3": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 902.9 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 200000
|
||||
},
|
||||
"chan_multiSF_4": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 903.1 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -300000
|
||||
},
|
||||
"chan_multiSF_5": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 903.3 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -100000
|
||||
},
|
||||
"chan_multiSF_6": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 903.5 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": 100000
|
||||
},
|
||||
"chan_multiSF_7": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 903.7 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": 300000
|
||||
},
|
||||
"chan_Lora_std": {
|
||||
/* Lora MAC channel, 500kHz, SF8, 903.0 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 300000,
|
||||
"bandwidth": 500000,
|
||||
"spread_factor": 8
|
||||
},
|
||||
"chan_FSK": {
|
||||
/* FSK 100kbps channel, 903.0 MHz */
|
||||
"enable": false,
|
||||
"radio": 0,
|
||||
"if": 300000,
|
||||
"bandwidth": 250000,
|
||||
"datarate": 100000
|
||||
}
|
||||
},
|
||||
|
||||
"gateway_conf": {
|
||||
"gateway_ID": "AA555A0000000000",
|
||||
/* change with default server address/ports, or overwrite in local_conf.json */
|
||||
"server_address": "localhost",
|
||||
"serv_port_up": 1680,
|
||||
"serv_port_down": 1680,
|
||||
/* adjust the following parameters for your network */
|
||||
"keepalive_interval": 10,
|
||||
"stat_interval": 30,
|
||||
"push_timeout_ms": 100,
|
||||
/* forward only valid packets */
|
||||
"forward_crc_valid": true,
|
||||
"forward_crc_error": false,
|
||||
"forward_crc_disabled": false,
|
||||
/* GPS configuration */
|
||||
"gps_tty_path": "/dev/ttyAMA0",
|
||||
/* GPS reference coordinates */
|
||||
"ref_latitude": 0.0,
|
||||
"ref_longitude": 0.0,
|
||||
"ref_altitude": 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
{
|
||||
"SX1301_conf": {
|
||||
"lorawan_public": true,
|
||||
"clksrc": 0, /* radio_1 provides clock to concentrator */
|
||||
"antenna_gain": 0, /* antenna gain, in dBi */
|
||||
"radio_0": {
|
||||
"enable": true,
|
||||
"type": "SX1257",
|
||||
"freq": 917200000,
|
||||
"rssi_offset": -166.0,
|
||||
"tx_enable": true,
|
||||
"tx_freq_min": 915000000,
|
||||
"tx_freq_max": 928000000
|
||||
},
|
||||
"radio_1": {
|
||||
"enable": true,
|
||||
"type": "SX1257",
|
||||
"freq": 917900000,
|
||||
"rssi_offset": -166.0,
|
||||
"tx_enable": false
|
||||
},
|
||||
"chan_multiSF_0": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 916.8 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": -400000
|
||||
},
|
||||
"chan_multiSF_1": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 917.0 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": -200000
|
||||
},
|
||||
"chan_multiSF_2": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 917.2 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 0
|
||||
},
|
||||
"chan_multiSF_3": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 917.4 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 200000
|
||||
},
|
||||
"chan_multiSF_4": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 917.6 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -300000
|
||||
},
|
||||
"chan_multiSF_5": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 917.8 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": -100000
|
||||
},
|
||||
"chan_multiSF_6": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 918.0 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": 100000
|
||||
},
|
||||
"chan_multiSF_7": {
|
||||
/* Lora MAC channel, 125kHz, all SF, 918.2 MHz */
|
||||
"enable": true,
|
||||
"radio": 1,
|
||||
"if": 300000
|
||||
},
|
||||
"chan_Lora_std": {
|
||||
/* Lora MAC channel, 500kHz, SF8, 917.5 MHz */
|
||||
"enable": true,
|
||||
"radio": 0,
|
||||
"if": 300000,
|
||||
"bandwidth": 500000,
|
||||
"spread_factor": 8
|
||||
},
|
||||
"chan_FSK": {
|
||||
/* Disabled */
|
||||
"enable": false,
|
||||
"radio": 0,
|
||||
"if": 300000,
|
||||
"bandwidth": 250000,
|
||||
"datarate": 100000,
|
||||
"freq_deviation" : 25000
|
||||
},
|
||||
"tx_lut_0": {
|
||||
/* TX gain table, index 0 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 8,
|
||||
"rf_power": -6,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_1": {
|
||||
/* TX gain table, index 1 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 10,
|
||||
"rf_power": -3,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_2": {
|
||||
/* TX gain table, index 2 */
|
||||
"pa_gain": 0,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 0,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_3": {
|
||||
/* TX gain table, index 3 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 8,
|
||||
"rf_power": 3,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_4": {
|
||||
/* TX gain table, index 4 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 6,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_5": {
|
||||
/* TX gain table, index 5 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 10,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_6": {
|
||||
/* TX gain table, index 6 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 13,
|
||||
"rf_power": 11,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_7": {
|
||||
/* TX gain table, index 7 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 9,
|
||||
"rf_power": 12,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_8": {
|
||||
/* TX gain table, index 8 */
|
||||
"pa_gain": 1,
|
||||
"mix_gain": 15,
|
||||
"rf_power": 13,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_9": {
|
||||
/* TX gain table, index 9 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 14,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_10": {
|
||||
/* TX gain table, index 10 */
|
||||
"pa_gain": 2,
|
||||
"mix_gain": 11,
|
||||
"rf_power": 16,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_11": {
|
||||
/* TX gain table, index 11 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 9,
|
||||
"rf_power": 20,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_12": {
|
||||
/* TX gain table, index 12 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 10,
|
||||
"rf_power": 23,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_13": {
|
||||
/* TX gain table, index 13 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 11,
|
||||
"rf_power": 25,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_14": {
|
||||
/* TX gain table, index 14 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 12,
|
||||
"rf_power": 26,
|
||||
"dig_gain": 0
|
||||
},
|
||||
"tx_lut_15": {
|
||||
/* TX gain table, index 15 */
|
||||
"pa_gain": 3,
|
||||
"mix_gain": 14,
|
||||
"rf_power": 27,
|
||||
"dig_gain": 0
|
||||
}
|
||||
},
|
||||
|
||||
"gateway_conf": {
|
||||
/* change with default server address/ports, or overwrite in local_conf.json */
|
||||
"gateway_ID": "b827ebFFFE9e119b",
|
||||
/* Devices */
|
||||
"gps": false,
|
||||
"beacon": false,
|
||||
"monitor": false,
|
||||
"upstream": false,
|
||||
"downstream": false,
|
||||
"ghoststream": false,
|
||||
"radiostream": false,
|
||||
"statusstream": false,
|
||||
/* node server */
|
||||
"server_address": "router.au.thethings.network",
|
||||
"serv_port_up": 1700,
|
||||
"serv_port_down": 1700,
|
||||
/* node servers for poly packet server (max 4) */
|
||||
"servers":
|
||||
[ { "server_address": "localhost",
|
||||
"serv_port_up": 1700,
|
||||
"serv_port_down": 1700,
|
||||
"serv_enabled": true }],
|
||||
/* adjust the following parameters for your network */
|
||||
"keepalive_interval": 12,
|
||||
"stat_interval": 20,
|
||||
"push_timeout_ms": 120,
|
||||
"synch_word": 52 ,
|
||||
/* forward only valid packets */
|
||||
"forward_crc_valid": true,
|
||||
"forward_crc_error": true,
|
||||
"forward_crc_disabled": true,
|
||||
/* Email of gateway operator, max 40 chars*/
|
||||
"contact_email": "operator@gateway.tst",
|
||||
/* Public description of this device, max 64 chars */
|
||||
"description": "Update me"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
/ _____) _ | |
|
||||
( (____ _____ ____ _| |_ _____ ____| |__
|
||||
\____ \| ___ | (_ _) ___ |/ ___) _ \
|
||||
_____) ) ____| | | || |_| ____( (___| | | |
|
||||
(______/|_____)_|_|_| \__)_____)\____)_| |_|
|
||||
(C)2013 Semtech-Cycleo
|
||||
|
||||
Description:
|
||||
Base64 encoding & decoding library
|
||||
|
||||
License: Revised BSD License, see LICENSE.TXT file include in the project
|
||||
Maintainer: Sylvain Miermont
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _BASE64_H
|
||||
#define _BASE64_H
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- DEPENDANCIES --------------------------------------------------------- */
|
||||
|
||||
#include <stdint.h> /* C99 types */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */
|
||||
|
||||
/**
|
||||
@brief Encode binary data in Base64 string (no padding)
|
||||
@param in pointer to a table of binary data
|
||||
@param size number of bytes to be encoded to base64
|
||||
@param out pointer to a string where the function will output encoded data
|
||||
@param max_len max length of the out string (including null char)
|
||||
@return >=0 length of the resulting string (w/o null char), -1 for error
|
||||
*/
|
||||
int bin_to_b64_nopad(const uint8_t * in, int size, char * out, int max_len);
|
||||
|
||||
/**
|
||||
@brief Decode Base64 string to binary data (no padding)
|
||||
@param in string containing only base64 valid characters
|
||||
@param size number of characters to be decoded from base64 (w/o null char)
|
||||
@param out pointer to a data buffer where the function will output decoded data
|
||||
@param out_max_len usable size of the output data buffer
|
||||
@return >=0 number of bytes written to the data buffer, -1 for error
|
||||
*/
|
||||
int b64_to_bin_nopad(const char * in, int size, uint8_t * out, int max_len);
|
||||
|
||||
/* === derivative functions === */
|
||||
|
||||
/**
|
||||
@brief Encode binary data in Base64 string (with added padding)
|
||||
*/
|
||||
int bin_to_b64(const uint8_t * in, int size, char * out, int max_len);
|
||||
|
||||
/**
|
||||
@brief Decode Base64 string to binary data (remove padding if necessary)
|
||||
*/
|
||||
int b64_to_bin(const char * in, int size, uint8_t * out, int max_len);
|
||||
|
||||
#endif
|
||||
|
||||
/* --- EOF ------------------------------------------------------------------ */
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
/ _____) _ | |
|
||||
( (____ _____ ____ _| |_ _____ ____| |__
|
||||
\____ \| ___ | (_ _) ___ |/ ___) _ \
|
||||
_____) ) ____| | | || |_| ____( (___| | | |
|
||||
(______/|_____)_|_|_| \__)_____)\____)_| |_|
|
||||
(C)2013 Semtech-Cycleo
|
||||
|
||||
Description:
|
||||
LoRa concentrator : Just In Time TX scheduling queue
|
||||
|
||||
License: Revised BSD License, see LICENSE.TXT file include in the project
|
||||
Maintainer: Michael Coracin
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _LORA_PKTFWD_JIT_H
|
||||
#define _LORA_PKTFWD_JIT_H
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- DEPENDANCIES --------------------------------------------------------- */
|
||||
|
||||
#include <stdint.h> /* C99 types */
|
||||
#include <stdbool.h> /* bool type */
|
||||
#include <sys/time.h> /* timeval */
|
||||
|
||||
#include "loragw_hal.h"
|
||||
#include "loragw_gps.h"
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PUBLIC CONSTANTS ----------------------------------------------------- */
|
||||
|
||||
#define JIT_QUEUE_MAX 32 /* Maximum number of packets to be stored in JiT queue */
|
||||
#define JIT_NUM_BEACON_IN_QUEUE 3 /* Number of beacons to be loaded in JiT queue at any time */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PUBLIC TYPES --------------------------------------------------------- */
|
||||
|
||||
enum jit_pkt_type_e {
|
||||
JIT_PKT_TYPE_DOWNLINK_CLASS_A,
|
||||
JIT_PKT_TYPE_DOWNLINK_CLASS_B,
|
||||
JIT_PKT_TYPE_DOWNLINK_CLASS_C,
|
||||
JIT_PKT_TYPE_BEACON
|
||||
};
|
||||
|
||||
enum jit_error_e {
|
||||
JIT_ERROR_OK, /* Packet ok to be sent */
|
||||
JIT_ERROR_TOO_LATE, /* Too late to send this packet */
|
||||
JIT_ERROR_TOO_EARLY, /* Too early to queue this packet */
|
||||
JIT_ERROR_FULL, /* Downlink queue is full */
|
||||
JIT_ERROR_EMPTY, /* Downlink queue is empty */
|
||||
JIT_ERROR_COLLISION_PACKET, /* A packet is already enqueued for this timeframe */
|
||||
JIT_ERROR_COLLISION_BEACON, /* A beacon is planned for this timeframe */
|
||||
JIT_ERROR_TX_FREQ, /* The required frequency for downlink is not supported */
|
||||
JIT_ERROR_TX_POWER, /* The required power for downlink is not supported */
|
||||
JIT_ERROR_GPS_UNLOCKED, /* GPS timestamp could not be used as GPS is unlocked */
|
||||
JIT_ERROR_INVALID /* Packet is invalid */
|
||||
};
|
||||
|
||||
struct jit_node_s {
|
||||
/* API fields */
|
||||
struct lgw_pkt_tx_s pkt; /* TX packet */
|
||||
enum jit_pkt_type_e pkt_type; /* Packet type: Downlink, Beacon... */
|
||||
|
||||
/* Internal fields */
|
||||
uint32_t pre_delay; /* Amount of time before packet timestamp to be reserved */
|
||||
uint32_t post_delay; /* Amount of time after packet timestamp to be reserved (time on air) */
|
||||
};
|
||||
|
||||
struct jit_queue_s {
|
||||
uint8_t num_pkt; /* Total number of packets in the queue (downlinks, beacons...) */
|
||||
uint8_t num_beacon; /* Number of beacons in the queue */
|
||||
struct jit_node_s nodes[JIT_QUEUE_MAX]; /* Nodes/packets array in the queue */
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */
|
||||
|
||||
/**
|
||||
@brief Check if a JiT queue is full.
|
||||
|
||||
@param queue[in] Just in Time queue to be checked.
|
||||
@return true if queue is full, false otherwise.
|
||||
*/
|
||||
bool jit_queue_is_full(struct jit_queue_s *queue);
|
||||
|
||||
/**
|
||||
@brief Check if a JiT queue is empty.
|
||||
|
||||
@param queue[in] Just in Time queue to be checked.
|
||||
@return true if queue is empty, false otherwise.
|
||||
*/
|
||||
bool jit_queue_is_empty(struct jit_queue_s *queue);
|
||||
|
||||
/**
|
||||
@brief Initialize a Just in Time queue.
|
||||
|
||||
@param queue[in] Just in Time queue to be initialized. Memory should have been allocated already.
|
||||
|
||||
This function is used to reset every elements in the allocated queue.
|
||||
*/
|
||||
void jit_queue_init(struct jit_queue_s *queue);
|
||||
|
||||
/**
|
||||
@brief Add a packet in a Just-in-Time queue
|
||||
|
||||
@param queue[in/out] Just in Time queue in which the packet should be inserted
|
||||
@param time[in] Current concentrator time
|
||||
@param packet[in] Packet to be queued in JiT queue
|
||||
@param pkt_type[in] Type of packet to be queued: Downlink, Beacon
|
||||
@return success if the function was able to queue the packet
|
||||
|
||||
This function is typically used when a packet is received from server for downlink.
|
||||
It will check if packet can be queued, with several criterias. Once the packet is queued, it has to be
|
||||
sent over the air. So all checks should happen before the packet being actually in the queue.
|
||||
*/
|
||||
enum jit_error_e jit_enqueue(struct jit_queue_s *queue, struct timeval *time, struct lgw_pkt_tx_s *packet, enum jit_pkt_type_e pkt_type);
|
||||
|
||||
/**
|
||||
@brief Dequeue a packet from a Just-in-Time queue
|
||||
|
||||
@param queue[in/out] Just in Time queue from which the packet should be removed
|
||||
@param index[in] in the queue where to get the packet to be removed
|
||||
@param packet[out] that was at index
|
||||
@param pkt_type[out] Type of packet dequeued: Downlink, Beacon
|
||||
@return success if the function was able to dequeue the packet
|
||||
|
||||
This function is typically used when a packet is about to be placed on concentrator buffer for TX.
|
||||
The index is generally got using the jit_peek function.
|
||||
*/
|
||||
enum jit_error_e jit_dequeue(struct jit_queue_s *queue, int index, struct lgw_pkt_tx_s *packet, enum jit_pkt_type_e *pkt_type);
|
||||
|
||||
/**
|
||||
@brief Check if there is a packet soon to be sent from the JiT queue.
|
||||
|
||||
@param queue[in] Just in Time queue to parse for peeking a packet
|
||||
@param time[in] Current concentrator time
|
||||
@param pkt_idx[out] Packet index which is soon to be dequeued.
|
||||
@return success if the function was able to parse the queue. pkt_idx is set to -1 if no packet found.
|
||||
|
||||
This function is typically used to check in JiT queue if there is a packet soon to be sent.
|
||||
It search the packet with the highest priority in queue, and check if its timestamp is near
|
||||
enough the current concentrator time.
|
||||
*/
|
||||
enum jit_error_e jit_peek(struct jit_queue_s *queue, struct timeval *time, int *pkt_idx);
|
||||
|
||||
/**
|
||||
@brief Debug function to print the queue's content on console
|
||||
|
||||
@param queue[in] Just in Time queue to be displayed
|
||||
@param show_all[in] Indicates if empty nodes have to be displayed or not
|
||||
*/
|
||||
void jit_print_queue(struct jit_queue_s *queue, bool show_all, int debug_level);
|
||||
|
||||
#endif
|
||||
/* --- EOF ------------------------------------------------------------------ */
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
Parson ( http://kgabis.github.com/parson/ )
|
||||
Copyright (C) 2013 Krzysztof Gabis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef parson_parson_h
|
||||
#define parson_parson_h
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#include <stddef.h> /* size_t */
|
||||
|
||||
#define PARSON_VERSION 20131130
|
||||
|
||||
/* Types and enums */
|
||||
typedef struct json_object_t JSON_Object;
|
||||
typedef struct json_array_t JSON_Array;
|
||||
typedef struct json_value_t JSON_Value;
|
||||
|
||||
typedef enum json_value_type {
|
||||
JSONError = 0,
|
||||
JSONNull = 1,
|
||||
JSONString = 2,
|
||||
JSONNumber = 3,
|
||||
JSONObject = 4,
|
||||
JSONArray = 5,
|
||||
JSONBoolean = 6
|
||||
} JSON_Value_Type;
|
||||
|
||||
|
||||
/* Parses first JSON value in a file, returns NULL in case of error */
|
||||
JSON_Value * json_parse_file(const char *filename);
|
||||
|
||||
/* Parses first JSON value in a file and ignores comments (/ * * / and //),
|
||||
returns NULL in case of error */
|
||||
JSON_Value * json_parse_file_with_comments(const char *filename);
|
||||
|
||||
/* Parses first JSON value in a string, returns NULL in case of error */
|
||||
JSON_Value * json_parse_string(const char *string);
|
||||
|
||||
/* Parses first JSON value in a string and ignores comments (/ * * / and //),
|
||||
returns NULL in case of error */
|
||||
JSON_Value * json_parse_string_with_comments(const char *string);
|
||||
|
||||
/* JSON Object */
|
||||
JSON_Value * json_object_get_value (const JSON_Object *object, const char *name);
|
||||
const char * json_object_get_string (const JSON_Object *object, const char *name);
|
||||
JSON_Object * json_object_get_object (const JSON_Object *object, const char *name);
|
||||
JSON_Array * json_object_get_array (const JSON_Object *object, const char *name);
|
||||
double json_object_get_number (const JSON_Object *object, const char *name);
|
||||
int json_object_get_boolean(const JSON_Object *object, const char *name);
|
||||
|
||||
/* dotget functions enable addressing values with dot notation in nested objects,
|
||||
just like in structs or c++/java/c# objects (e.g. objectA.objectB.value).
|
||||
Because valid names in JSON can contain dots, some values may be inaccessible
|
||||
this way. */
|
||||
JSON_Value * json_object_dotget_value (const JSON_Object *object, const char *name);
|
||||
const char * json_object_dotget_string (const JSON_Object *object, const char *name);
|
||||
JSON_Object * json_object_dotget_object (const JSON_Object *object, const char *name);
|
||||
JSON_Array * json_object_dotget_array (const JSON_Object *object, const char *name);
|
||||
double json_object_dotget_number (const JSON_Object *object, const char *name);
|
||||
int json_object_dotget_boolean(const JSON_Object *object, const char *name);
|
||||
|
||||
/* Functions to get available names */
|
||||
size_t json_object_get_count(const JSON_Object *object);
|
||||
const char * json_object_get_name (const JSON_Object *object, size_t index);
|
||||
|
||||
/* JSON Array */
|
||||
JSON_Value * json_array_get_value (const JSON_Array *array, size_t index);
|
||||
const char * json_array_get_string (const JSON_Array *array, size_t index);
|
||||
JSON_Object * json_array_get_object (const JSON_Array *array, size_t index);
|
||||
JSON_Array * json_array_get_array (const JSON_Array *array, size_t index);
|
||||
double json_array_get_number (const JSON_Array *array, size_t index);
|
||||
int json_array_get_boolean(const JSON_Array *array, size_t index);
|
||||
size_t json_array_get_count (const JSON_Array *array);
|
||||
|
||||
/* JSON Value */
|
||||
JSON_Value_Type json_value_get_type (const JSON_Value *value);
|
||||
JSON_Object * json_value_get_object (const JSON_Value *value);
|
||||
JSON_Array * json_value_get_array (const JSON_Value *value);
|
||||
const char * json_value_get_string (const JSON_Value *value);
|
||||
double json_value_get_number (const JSON_Value *value);
|
||||
int json_value_get_boolean(const JSON_Value *value);
|
||||
void json_value_free (JSON_Value *value);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
/ _____) _ | |
|
||||
( (____ _____ ____ _| |_ _____ ____| |__
|
||||
\____ \| ___ | (_ _) ___ |/ ___) _ \
|
||||
_____) ) ____| | | || |_| ____( (___| | | |
|
||||
(______/|_____)_|_|_| \__)_____)\____)_| |_|
|
||||
(C)2013 Semtech-Cycleo
|
||||
|
||||
Description:
|
||||
LoRa concentrator : Just In Time TX scheduling queue
|
||||
|
||||
License: Revised BSD License, see LICENSE.TXT file include in the project
|
||||
Maintainer: Michael Coracin
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _LORA_PKTFWD_TIMERSYNC_H
|
||||
#define _LORA_PKTFWD_TIMERSYNC_H
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- DEPENDANCIES --------------------------------------------------------- */
|
||||
|
||||
#include <sys/time.h> /* timeval */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */
|
||||
|
||||
int get_concentrator_time(struct timeval *concent_time, struct timeval unix_time);
|
||||
|
||||
void thread_timersync(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
/ _____) _ | |
|
||||
( (____ _____ ____ _| |_ _____ ____| |__
|
||||
\____ \| ___ | (_ _) ___ |/ ___) _ \
|
||||
_____) ) ____| | | || |_| ____( (___| | | |
|
||||
(______/|_____)_|_|_| \__)_____)\____)_| |_|
|
||||
(C)2013 Semtech-Cycleo
|
||||
|
||||
Description:
|
||||
LoRa concentrator : Packet Forwarder trace helpers
|
||||
|
||||
License: Revised BSD License, see LICENSE.TXT file include in the project
|
||||
Maintainer: Michael Coracin
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _LORA_PKTFWD_TRACE_H
|
||||
#define _LORA_PKTFWD_TRACE_H
|
||||
|
||||
#define DEBUG_PKT_FWD 0
|
||||
#define DEBUG_JIT 0
|
||||
#define DEBUG_JIT_ERROR 1
|
||||
#define DEBUG_TIMERSYNC 0
|
||||
#define DEBUG_BEACON 0
|
||||
#define DEBUG_LOG 1
|
||||
|
||||
#define MSG(args...) printf(args) /* message that is destined to the user */
|
||||
#define MSG_DEBUG(FLAG, fmt, ...) \
|
||||
do { \
|
||||
if (FLAG) \
|
||||
fprintf(stdout, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
/* --- EOF ------------------------------------------------------------------ */
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
/* Put there parameters that are different for each gateway (eg. pointing one gateway to a test server while the others stay in production) */
|
||||
/* Settings defined in global_conf will be overwritten by those in local_conf */
|
||||
"gateway_conf": {
|
||||
"gateway_ID": "b827ebFFFE9e119b"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,329 @@
|
||||
/ _____) _ | |
|
||||
( (____ _____ ____ _| |_ _____ ____| |__
|
||||
\____ \| ___ | (_ _) ___ |/ ___) _ \
|
||||
_____) ) ____| | | || |_| ____( (___| | | |
|
||||
(______/|_____)_|_|_| \__)_____)\____)_| |_|
|
||||
(C)2013 Semtech-Cycleo
|
||||
|
||||
Lora Gateway packet forwarder
|
||||
=============================
|
||||
|
||||
1. Introduction
|
||||
----------------
|
||||
|
||||
The packet forwarder is a program running on the host of a Lora gateway that
|
||||
forwards RF packets receive by the concentrator to a server through a IP/UDP
|
||||
link, and emits RF packets that are sent by the server. It can also emit a
|
||||
network-wide GPS-synchronous beacon signal used for coordinating all nodes of
|
||||
the network.
|
||||
|
||||
To learn more about the network protocol between the gateway and the server,
|
||||
please read the PROTOCOL.TXT document.
|
||||
|
||||
2. System schematic and definitions
|
||||
------------------------------------
|
||||
|
||||
((( Y )))
|
||||
|
|
||||
|
|
||||
+- -|- - - - - - - - - - - - -+ xxxxxxxxxxxx +--------+
|
||||
|+--+-----------+ +------+| xx x x xxx | |
|
||||
|| | | || xx Internet xx | |
|
||||
|| Concentrator |<----+ Host |<------xx or xx-------->| |
|
||||
|| | SPI | || xx Intranet xx | Server |
|
||||
|+--------------+ +------+| xxxx x xxxx | |
|
||||
| ^ ^ | xxxxxxxx | |
|
||||
| | PPS +-----+ NMEA | | | |
|
||||
| +------| GPS |-------+ | +--------+
|
||||
| +-----+ |
|
||||
| |
|
||||
| Gateway |
|
||||
+- - - - - - - - - - - - - - -+
|
||||
|
||||
Concentrator: radio RX/TX board, based on Semtech multichannel modems (SX130x),
|
||||
transceivers (SX135x) and/or low-power stand-alone modems (SX127x).
|
||||
|
||||
Host: embedded computer on which the packet forwarder is run. Drives the
|
||||
concentrator through a SPI link.
|
||||
|
||||
Gateway: a device composed of at least one radio concentrator, a host, some
|
||||
network connection to the internet or a private network (Ethernet, 3G, Wifi,
|
||||
microwave link), and optionally a GPS receiver for synchronization.
|
||||
|
||||
Server: an abstract computer that will process the RF packets received and
|
||||
forwarded by the gateway, and issue RF packets in response that the gateway
|
||||
will have to emit.
|
||||
|
||||
|
||||
3. Dependencies
|
||||
----------------
|
||||
|
||||
This program uses the Parson library (http://kgabis.github.com/parson/) by
|
||||
Krzysztof Gabis for JSON parsing.
|
||||
Many thanks to him for that very practical and well written library.
|
||||
|
||||
This program is statically linked with the libloragw Lora concentrator library.
|
||||
It was tested with v1.3.0 of the library but should work with any later
|
||||
version provided the API is v1 or a later backward-compatible API.
|
||||
Data structures of the received packets are accessed by name (ie. not at a
|
||||
binary level) so new functionalities can be added to the API without affecting
|
||||
that program at all.
|
||||
|
||||
This program follows the v1.3 version of the gateway-to-server protocol.
|
||||
|
||||
The last dependency is the hardware concentrator (based on FPGA or SX130x
|
||||
chips) that must be matched with the proper version of the HAL.
|
||||
|
||||
4. Usage
|
||||
---------
|
||||
|
||||
* Pick the global_conf.json file from cfg/ directory that fit with your
|
||||
platform, region and feature need.
|
||||
* Update the JSON configuration (global and local) files, as explained below.
|
||||
* For IoT Starter Kit only, run:
|
||||
./reset_lgw.sh stop
|
||||
./reset_lgw.sh start
|
||||
* Run:
|
||||
./update_gwid.sh local_conf.json (OPTIONAL)
|
||||
./lora_pkt_fwd
|
||||
|
||||
To stop the application, press Ctrl+C.
|
||||
Unless it is manually stopped or encounter a critical error, the program will
|
||||
run forever.
|
||||
|
||||
There are no command line launch options.
|
||||
|
||||
The way the program takes configuration files into account is the following:
|
||||
* if there is a debug_conf.json parse it, others are ignored
|
||||
* if there is a global_conf.json parse it, look for the next file
|
||||
* if there is a local_conf.json parse it
|
||||
If some parameters are defined in both global and local configuration files,
|
||||
the local definition overwrites the global definition.
|
||||
|
||||
The global configuration file should be exactly the same throughout your
|
||||
network, contain all global parameters (parameters for "sensor" radio
|
||||
channels) and preferably default "safe" values for parameters that are
|
||||
specific for each gateway (eg. specify a default MAC address).
|
||||
|
||||
As some of the parameters (like 'rssi_offset', 'tx_lut_*') are board dependant,
|
||||
several flavours of the global_conf.json file are provided in the cfg/
|
||||
directory.
|
||||
* global_conf.json.PCB_E286.EU868.*: to be used for Semtech reference design
|
||||
board with PCB name PCB_E286 (also called Gateway Board v1.0 (no FPGA)).
|
||||
Configured for Europe 868MHz channels.
|
||||
* global_conf.json.PCB_E336.EU868.*:to be used for Semtech reference design
|
||||
board with PCB name PCB_E336 (also called Gateway Board v1.5 (with FPGA)).
|
||||
Configured for Europe 868MHz channels.
|
||||
* global_conf.json.US902.*: to be used for Semtech reference design v1.0 or
|
||||
v1.5. (No calibration done for RSSI offset and TX gains yet).
|
||||
Configured for US 902MHz channels.
|
||||
|
||||
Beside board related flavours, there are "features" flavours named "basic",
|
||||
"gps", "beacon".
|
||||
* global_conf.json.*.basic: to be used for basic packet forwarder usage, with
|
||||
no GPS.
|
||||
* global_conf.json.*.gps: to be used when the platform has a GPS receiver.
|
||||
* global_conf.json.*.beacon: to be used when the platform has a GPS receiver
|
||||
and we want the packet forwarder to emit beacons for synchronized networks.
|
||||
|
||||
Rename the one you need to global_conf.json before launching the packet
|
||||
forwarder.
|
||||
|
||||
The local configuration file should contain parameters that are specific to
|
||||
each gateway (eg. MAC address, frequency for backhaul radio channels).
|
||||
|
||||
In each configuration file, the program looks for a JSON object named
|
||||
"SX1301_conf" that should contain the parameters for the Lora concentrator
|
||||
board (RF channels definition, modem parameters, etc) and another JSON object
|
||||
called "gateway_conf" that should contain the gateway parameters (gateway MAC
|
||||
address, IP address of the server, keep-alive time, etc).
|
||||
|
||||
To learn more about the JSON configuration format, read the provided JSON
|
||||
files and the libloragw API documentation.
|
||||
|
||||
Every X seconds (parameter settable in the configuration files) the program
|
||||
display statistics on the RF packets received and sent, and the network
|
||||
datagrams received and sent.
|
||||
The program also send some statistics to the server in JSON format.
|
||||
|
||||
5. "Just-In-Time" downlink scheduling
|
||||
-------------------------------------
|
||||
|
||||
The LoRa concentrator can have only one TX packet programmed for departure at a
|
||||
time. The actual departure of a downlink packet will be done based on its
|
||||
timestamp, when the concentrator internal counter reaches timestamp’s value.
|
||||
The departure of a beacon will be done based on a GPS PPS event.
|
||||
It may happen that, due to network variable latency, the gateway receives one
|
||||
or many downlink packets from the server while a TX is already programmed in the
|
||||
concentrator. The packet forwarder has to store and order incoming downlink
|
||||
packets (in a queue), so that they can all be programmed in the concentrator at
|
||||
the proper time and sent over the air.
|
||||
Possible failures that may occur and that have to be reported to the server are:
|
||||
- It is too early or too late to send a given packet
|
||||
- A packet collides with another packet already queued
|
||||
- A packet collides with a beacon
|
||||
- TX RF parameters (frequency, power) are not supported by gateway
|
||||
- Gateway’s GPS is unlocked, so cannot process Class B downlink
|
||||
It is called "Just-in-Time" (JiT) scheduling, because the packet forwarder will
|
||||
program a downlink or a beacon packet in the concentrator just before it has to
|
||||
be sent over the air.
|
||||
Another benefit of JiT is to optimize the gateway downlink capacity by avoiding
|
||||
to keep the concentrator TX buffer busy for too long.
|
||||
|
||||
In order to achieve "Just-in-Time" scheduling, the following software elements
|
||||
have been added:
|
||||
- A JiT queue, with associated enqueue/peek/dequeue functions and packet
|
||||
acceptance criterias. It is where downlink packets are stored, waiting to be
|
||||
sent.
|
||||
- A JiT thread, which regularly checks if there is a packet in the JiT queue
|
||||
ready to be programmed in the concentrator, based on current concentrator
|
||||
internal time.
|
||||
- A Timer synchronization thread to keep the concentrator clock and Unix clock
|
||||
synchronized so that host processor can determine if a packet with a given
|
||||
timestamp can be programmed in the concentrator or not.
|
||||
|
||||
5.1. Concentrator vs Unix time synchronization
|
||||
|
||||
In order for the host to know if an incoming downlink packet can or cannot be
|
||||
queued in JiT queue for later transmission, it has to check if the timestamp of
|
||||
the packet designates a time later than the current concentrator counter or if
|
||||
it is already too late to be passed to the concentrator.
|
||||
In order to get current concentrator time, we can use the lgw_get_trigcnt() HAL
|
||||
function. The problem is that the sample register used to read this value can be
|
||||
configured in 2 different ways:
|
||||
- Real time mode: when GPS is disabled, the value read in sample register is
|
||||
the actual concentrator counter value.
|
||||
- PPS mode: when GPS is enabled, the value read in sample register is the
|
||||
value
|
||||
that the concentrator counter had when last GPS’s PPS occurred. So this changes
|
||||
every second only.
|
||||
As in our case GPS is enabled (LGW_GPS_EN==1), we need to have a way to get the
|
||||
actual concentrator current time, at any time.
|
||||
For this, a new thread has been added to the packet forwarder (thread_timersync)
|
||||
which will regularly:
|
||||
- Disable GPS mode of SX1301 counter sampler
|
||||
- Get current Unix time
|
||||
- Get current SX1301 counter
|
||||
- Compute the offset between Unix and SX1301 clocks and store it
|
||||
- Re-enable GPS mode of SX1301 counter sampler
|
||||
Then a new function has been added to estimate the current concentrator counter
|
||||
at any time based on the current Unix time and offset computed by the timersync
|
||||
thread.
|
||||
|
||||
In addition to this, the Concentrator vs Unix time synchronization is used by
|
||||
the JiT thread to determine if a packet in the JiT queue has to be sent to the
|
||||
concentrator for transmission.
|
||||
So basically it is used for queueing and dequeuing packets to/from the JiT queue.
|
||||
|
||||
5.2. Concentrator vs GPS time synchronization
|
||||
|
||||
There are 2 cases for which we need to convert a GPS time to concentrator
|
||||
counter:
|
||||
- Class B downlink: when the “time” field of JSON “txpk” is filled instead
|
||||
of the “tmst” field, we need to be able to determine if the packet can be
|
||||
queued in JiT queue or not, based on its corresponding concentrator
|
||||
counter value.
|
||||
Note: even if a Class-B downlink is given with a GPS timestamp, the
|
||||
concentrator TX mode is configured as “TIMESTAMP”, and not “ON_GPS”. So
|
||||
at the end, it is the counter value which will be used for transmission.
|
||||
- Beacons: beacons transmission time is based on GPS clock, and the
|
||||
concentrator TX mode is configured as “ON_GPS” for accurate beacon
|
||||
transmission on GPS PPS event. In this case, the concentrator does not
|
||||
need the packet counter to be set. But, as the JiT thread decides if a
|
||||
packet has to be peeked or not, based on its concentrator counter, we need
|
||||
to have the beacon packet counter set (see next chapter for more details
|
||||
on JiT scheduling).
|
||||
We also need to convert a SX1301 counter value to GPS UTC time when we receive
|
||||
an uplink, in order to fill the “time” field of JSON “rxpk” structure.
|
||||
|
||||
5.3. TX scheduling
|
||||
|
||||
The JiT queue implemented is a static array of nodes, where each node contains:
|
||||
- the downlink packet, with its type (beacon, downlink class A, B or C)
|
||||
- a “pre delay” which depends on packet type (BEACON_GUARD, TX_START_DELAY…)
|
||||
- a “post delay” which depends on packet type (“time on air” of this packet
|
||||
computed based on its size, datarate and coderate, or BEACON_RESERVED)
|
||||
|
||||
Several functions are implemented to manipulate this queue or get info from it:
|
||||
- init: initialize array with default values
|
||||
- is full / is empty: gives queue status
|
||||
- enqueue: checks if the given packet can be queued or not, based on several
|
||||
criteria’s
|
||||
- peek: checks if the queue contains a packet that must be passed
|
||||
immediately to the concentrator for transmission and returns corresponding
|
||||
index if any.
|
||||
- dequeue: actually removes from the queue the packet at index given by peek
|
||||
function
|
||||
|
||||
The queue is always kept sorted on ascending timestamp order.
|
||||
|
||||
The JiT thread will regularly check in the JiT queue if there is a packet to be
|
||||
sent soon. If a packet is matching, it is dequeued and programmed in the
|
||||
concentrator TX buffer.
|
||||
|
||||
5.4. Fine tuning parameters
|
||||
|
||||
There are few parameters of the JiT queue which could be tweaked to adapt to
|
||||
different system constraints.
|
||||
|
||||
- inc/jitqueue.h:
|
||||
JIT_QUEUE_MAX: The maximum number of nodes in the queue.
|
||||
- src/jitqueue.c:
|
||||
TX_JIT_DELAY: The number of milliseconds a packet is programmed in the
|
||||
concentrator TX buffer before its actual departure time.
|
||||
TX_MARGIN_DELAY: Packet collision check margin
|
||||
|
||||
6. License
|
||||
-----------
|
||||
|
||||
Copyright (C) 2013, SEMTECH S.A.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Semtech corporation nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL SEMTECH S.A. BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
6. License for Parson library
|
||||
------------------------------
|
||||
|
||||
Parson ( http://kgabis.github.com/parson/ )
|
||||
Copyright (C) 2012 Krzysztof Gabis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
*EOF*
|
||||
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
/ _____) _ | |
|
||||
( (____ _____ ____ _| |_ _____ ____| |__
|
||||
\____ \| ___ | (_ _) ___ |/ ___) _ \
|
||||
_____) ) ____| | | || |_| ____( (___| | | |
|
||||
(______/|_____)_|_|_| \__)_____)\____)_| |_|
|
||||
(C)2013 Semtech-Cycleo
|
||||
|
||||
Description:
|
||||
Base64 encoding & decoding library
|
||||
|
||||
License: Revised BSD License, see LICENSE.TXT file include in the project
|
||||
Maintainer: Sylvain Miermont
|
||||
*/
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- DEPENDANCIES --------------------------------------------------------- */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE MACROS ------------------------------------------------------- */
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
|
||||
#define CRIT(a) fprintf(stderr, "\nCRITICAL file:%s line:%u msg:%s\n", __FILE__, __LINE__,a);exit(EXIT_FAILURE)
|
||||
|
||||
//#define DEBUG(args...) fprintf(stderr,"debug: " args) /* diagnostic message that is destined to the user */
|
||||
#define DEBUG(args...)
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE MODULE-WIDE VARIABLES ---------------------------------------- */
|
||||
|
||||
static char code_62 = '+'; /* RFC 1421 standard character for code 62 */
|
||||
static char code_63 = '/'; /* RFC 1421 standard character for code 63 */
|
||||
static char code_pad = '='; /* RFC 1421 padding character if padding */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */
|
||||
|
||||
/**
|
||||
@brief Convert a code in the range 0-63 to an ASCII character
|
||||
*/
|
||||
char code_to_char(uint8_t x);
|
||||
|
||||
/**
|
||||
@brief Convert an ASCII character to a code in the range 0-63
|
||||
*/
|
||||
uint8_t char_to_code(char x);
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
|
||||
|
||||
char code_to_char(uint8_t x) {
|
||||
if (x <= 25) {
|
||||
return 'A' + x;
|
||||
} else if ((x >= 26) && (x <= 51)) {
|
||||
return 'a' + (x-26);
|
||||
} else if ((x >= 52) && (x <= 61)) {
|
||||
return '0' + (x-52);
|
||||
} else if (x == 62) {
|
||||
return code_62;
|
||||
} else if (x == 63) {
|
||||
return code_63;
|
||||
} else {
|
||||
DEBUG("ERROR: %i IS OUT OF RANGE 0-63 FOR BASE64 ENCODING\n", x);
|
||||
exit(EXIT_FAILURE);
|
||||
} //TODO: improve error management
|
||||
}
|
||||
|
||||
uint8_t char_to_code(char x) {
|
||||
if ((x >= 'A') && (x <= 'Z')) {
|
||||
return (uint8_t)x - (uint8_t)'A';
|
||||
} else if ((x >= 'a') && (x <= 'z')) {
|
||||
return (uint8_t)x - (uint8_t)'a' + 26;
|
||||
} else if ((x >= '0') && (x <= '9')) {
|
||||
return (uint8_t)x - (uint8_t)'0' + 52;
|
||||
} else if (x == code_62) {
|
||||
return 62;
|
||||
} else if (x == code_63) {
|
||||
return 63;
|
||||
} else {
|
||||
DEBUG("ERROR: %c (0x%x) IS INVALID CHARACTER FOR BASE64 DECODING\n", x, x);
|
||||
exit(EXIT_FAILURE);
|
||||
} //TODO: improve error management
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */
|
||||
|
||||
int bin_to_b64_nopad(const uint8_t * in, int size, char * out, int max_len) {
|
||||
int i;
|
||||
int result_len; /* size of the result */
|
||||
int full_blocks; /* number of 3 unsigned chars / 4 characters blocks */
|
||||
int last_bytes; /* number of unsigned chars <3 in the last block */
|
||||
int last_chars; /* number of characters <4 in the last block */
|
||||
uint32_t b;
|
||||
|
||||
/* check input values */
|
||||
if ((out == NULL) || (in == NULL)) {
|
||||
DEBUG("ERROR: NULL POINTER AS OUTPUT IN BIN_TO_B64\n");
|
||||
return -1;
|
||||
}
|
||||
if (size == 0) {
|
||||
*out = 0; /* null string */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* calculate the number of base64 'blocks' */
|
||||
full_blocks = size / 3;
|
||||
last_bytes = size % 3;
|
||||
switch (last_bytes) {
|
||||
case 0: /* no byte left to encode */
|
||||
last_chars = 0;
|
||||
break;
|
||||
case 1: /* 1 byte left to encode -> +2 chars */
|
||||
last_chars = 2;
|
||||
break;
|
||||
case 2: /* 2 bytes left to encode -> +3 chars */
|
||||
last_chars = 3;
|
||||
break;
|
||||
default:
|
||||
CRIT("switch default that should not be possible");
|
||||
}
|
||||
|
||||
/* check if output buffer is big enough */
|
||||
result_len = (4*full_blocks) + last_chars;
|
||||
if (max_len < (result_len + 1)) { /* 1 char added for string terminator */
|
||||
DEBUG("ERROR: OUTPUT BUFFER TOO SMALL IN BIN_TO_B64\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* process all the full blocks */
|
||||
for (i=0; i < full_blocks; ++i) {
|
||||
b = (0xFF & in[3*i] ) << 16;
|
||||
b |= (0xFF & in[3*i + 1]) << 8;
|
||||
b |= 0xFF & in[3*i + 2];
|
||||
out[4*i + 0] = code_to_char((b >> 18) & 0x3F);
|
||||
out[4*i + 1] = code_to_char((b >> 12) & 0x3F);
|
||||
out[4*i + 2] = code_to_char((b >> 6 ) & 0x3F);
|
||||
out[4*i + 3] = code_to_char( b & 0x3F);
|
||||
}
|
||||
|
||||
/* process the last 'partial' block and terminate string */
|
||||
i = full_blocks;
|
||||
if (last_chars == 0) {
|
||||
out[4*i] = 0; /* null character to terminate string */
|
||||
} else if (last_chars == 2) {
|
||||
b = (0xFF & in[3*i] ) << 16;
|
||||
out[4*i + 0] = code_to_char((b >> 18) & 0x3F);
|
||||
out[4*i + 1] = code_to_char((b >> 12) & 0x3F);
|
||||
out[4*i + 2] = 0; /* null character to terminate string */
|
||||
} else if (last_chars == 3) {
|
||||
b = (0xFF & in[3*i] ) << 16;
|
||||
b |= (0xFF & in[3*i + 1]) << 8;
|
||||
out[4*i + 0] = code_to_char((b >> 18) & 0x3F);
|
||||
out[4*i + 1] = code_to_char((b >> 12) & 0x3F);
|
||||
out[4*i + 2] = code_to_char((b >> 6 ) & 0x3F);
|
||||
out[4*i + 3] = 0; /* null character to terminate string */
|
||||
}
|
||||
|
||||
return result_len;
|
||||
}
|
||||
|
||||
int b64_to_bin_nopad(const char * in, int size, uint8_t * out, int max_len) {
|
||||
int i;
|
||||
int result_len; /* size of the result */
|
||||
int full_blocks; /* number of 3 unsigned chars / 4 characters blocks */
|
||||
int last_chars; /* number of characters <4 in the last block */
|
||||
int last_bytes; /* number of unsigned chars <3 in the last block */
|
||||
uint32_t b;
|
||||
;
|
||||
|
||||
/* check input values */
|
||||
if ((out == NULL) || (in == NULL)) {
|
||||
DEBUG("ERROR: NULL POINTER AS OUTPUT OR INPUT IN B64_TO_BIN\n");
|
||||
return -1;
|
||||
}
|
||||
if (size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* calculate the number of base64 'blocks' */
|
||||
full_blocks = size / 4;
|
||||
last_chars = size % 4;
|
||||
switch (last_chars) {
|
||||
case 0: /* no char left to decode */
|
||||
last_bytes = 0;
|
||||
break;
|
||||
case 1: /* only 1 char left is an error */
|
||||
DEBUG("ERROR: ONLY ONE CHAR LEFT IN B64_TO_BIN\n");
|
||||
return -1;
|
||||
case 2: /* 2 chars left to decode -> +1 byte */
|
||||
last_bytes = 1;
|
||||
break;
|
||||
case 3: /* 3 chars left to decode -> +2 bytes */
|
||||
last_bytes = 2;
|
||||
break;
|
||||
default:
|
||||
CRIT("switch default that should not be possible");
|
||||
}
|
||||
|
||||
/* check if output buffer is big enough */
|
||||
result_len = (3*full_blocks) + last_bytes;
|
||||
if (max_len < result_len) {
|
||||
DEBUG("ERROR: OUTPUT BUFFER TOO SMALL IN B64_TO_BIN\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* process all the full blocks */
|
||||
for (i=0; i < full_blocks; ++i) {
|
||||
b = (0x3F & char_to_code(in[4*i] )) << 18;
|
||||
b |= (0x3F & char_to_code(in[4*i + 1])) << 12;
|
||||
b |= (0x3F & char_to_code(in[4*i + 2])) << 6;
|
||||
b |= 0x3F & char_to_code(in[4*i + 3]);
|
||||
out[3*i + 0] = (b >> 16) & 0xFF;
|
||||
out[3*i + 1] = (b >> 8 ) & 0xFF;
|
||||
out[3*i + 2] = b & 0xFF;
|
||||
}
|
||||
|
||||
/* process the last 'partial' block */
|
||||
i = full_blocks;
|
||||
if (last_bytes == 1) {
|
||||
b = (0x3F & char_to_code(in[4*i] )) << 18;
|
||||
b |= (0x3F & char_to_code(in[4*i + 1])) << 12;
|
||||
out[3*i + 0] = (b >> 16) & 0xFF;
|
||||
if (((b >> 12) & 0x0F) != 0) {
|
||||
DEBUG("WARNING: last character contains unusable bits\n");
|
||||
}
|
||||
} else if (last_bytes == 2) {
|
||||
b = (0x3F & char_to_code(in[4*i] )) << 18;
|
||||
b |= (0x3F & char_to_code(in[4*i + 1])) << 12;
|
||||
b |= (0x3F & char_to_code(in[4*i + 2])) << 6;
|
||||
out[3*i + 0] = (b >> 16) & 0xFF;
|
||||
out[3*i + 1] = (b >> 8 ) & 0xFF;
|
||||
if (((b >> 6) & 0x03) != 0) {
|
||||
DEBUG("WARNING: last character contains unusable bits\n");
|
||||
}
|
||||
}
|
||||
|
||||
return result_len;
|
||||
}
|
||||
|
||||
int bin_to_b64(const uint8_t * in, int size, char * out, int max_len) {
|
||||
int ret;
|
||||
|
||||
ret = bin_to_b64_nopad(in, size, out, max_len);
|
||||
|
||||
if (ret == -1) {
|
||||
return -1;
|
||||
}
|
||||
switch (ret%4) {
|
||||
case 0: /* nothing to do */
|
||||
return ret;
|
||||
case 1:
|
||||
DEBUG("ERROR: INVALID UNPADDED BASE64 STRING\n");
|
||||
return -1;
|
||||
case 2: /* 2 chars in last block, must add 2 padding char */
|
||||
if (max_len >= (ret + 2 + 1)) {
|
||||
out[ret] = code_pad;
|
||||
out[ret+1] = code_pad;
|
||||
out[ret+2] = 0;
|
||||
return ret+2;
|
||||
} else {
|
||||
DEBUG("ERROR: not enough room to add padding in bin_to_b64\n");
|
||||
return -1;
|
||||
}
|
||||
case 3: /* 3 chars in last block, must add 1 padding char */
|
||||
if (max_len >= (ret + 1 + 1)) {
|
||||
out[ret] = code_pad;
|
||||
out[ret+1] = 0;
|
||||
return ret+1;
|
||||
} else {
|
||||
DEBUG("ERROR: not enough room to add padding in bin_to_b64\n");
|
||||
return -1;
|
||||
}
|
||||
default:
|
||||
CRIT("switch default that should not be possible");
|
||||
}
|
||||
}
|
||||
|
||||
int b64_to_bin(const char * in, int size, uint8_t * out, int max_len) {
|
||||
if (in == NULL) {
|
||||
DEBUG("ERROR: NULL POINTER AS OUTPUT OR INPUT IN B64_TO_BIN\n");
|
||||
return -1;
|
||||
}
|
||||
if ((size%4 == 0) && (size >= 4)) { /* potentially padded Base64 */
|
||||
if (in[size-2] == code_pad) { /* 2 padding char to ignore */
|
||||
return b64_to_bin_nopad(in, size-2, out, max_len);
|
||||
} else if (in[size-1] == code_pad) { /* 1 padding char to ignore */
|
||||
return b64_to_bin_nopad(in, size-1, out, max_len);
|
||||
} else { /* no padding to ignore */
|
||||
return b64_to_bin_nopad(in, size, out, max_len);
|
||||
}
|
||||
} else { /* treat as unpadded Base64 */
|
||||
return b64_to_bin_nopad(in, size, out, max_len);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* --- EOF ------------------------------------------------------------------ */
|
||||
@@ -0,0 +1,528 @@
|
||||
/*
|
||||
/ _____) _ | |
|
||||
( (____ _____ ____ _| |_ _____ ____| |__
|
||||
\____ \| ___ | (_ _) ___ |/ ___) _ \
|
||||
_____) ) ____| | | || |_| ____( (___| | | |
|
||||
(______/|_____)_|_|_| \__)_____)\____)_| |_|
|
||||
(C)2013 Semtech-Cycleo
|
||||
|
||||
Description:
|
||||
LoRa concentrator : Just In Time TX scheduling queue
|
||||
|
||||
License: Revised BSD License, see LICENSE.TXT file include in the project
|
||||
Maintainer: Michael Coracin
|
||||
*/
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- DEPENDANCIES --------------------------------------------------------- */
|
||||
|
||||
#define _GNU_SOURCE /* needed for qsort_r to be defined */
|
||||
#include <stdlib.h> /* qsort_r */
|
||||
#include <stdio.h> /* printf, fprintf, snprintf, fopen, fputs */
|
||||
#include <string.h> /* memset, memcpy */
|
||||
#include <pthread.h>
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "trace.h"
|
||||
#include "jitqueue.h"
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE MACROS ------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE CONSTANTS & TYPES -------------------------------------------- */
|
||||
#define TX_START_DELAY 1500 /* microseconds */
|
||||
/* TODO: get this value from HAL? */
|
||||
#define TX_MARGIN_DELAY 1000 /* Packet overlap margin in microseconds */
|
||||
/* TODO: How much margin should we take? */
|
||||
#define TX_JIT_DELAY 30000 /* Pre-delay to program packet for TX in microseconds */
|
||||
#define TX_MAX_ADVANCE_DELAY ((JIT_NUM_BEACON_IN_QUEUE + 1) * 128 * 1E6) /* Maximum advance delay accepted for a TX packet, compared to current time */
|
||||
|
||||
#define BEACON_GUARD 3000000 /* Interval where no ping slot can be placed,
|
||||
to ensure beacon can be sent */
|
||||
#define BEACON_RESERVED 2120000 /* Time on air of the beacon, with some margin */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */
|
||||
static pthread_mutex_t mx_jit_queue = PTHREAD_MUTEX_INITIALIZER; /* control access to JIT queue */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
|
||||
static uint32_t time_on_air(struct lgw_pkt_tx_s *packet, bool isBeacon) {
|
||||
uint8_t SF, H, DE;
|
||||
uint16_t BW;
|
||||
uint32_t payloadSymbNb, Tpacket;
|
||||
double Tsym, Tpreamble, Tpayload;
|
||||
|
||||
switch (packet->bandwidth) {
|
||||
case BW_125KHZ:
|
||||
BW = 125;
|
||||
break;
|
||||
case BW_250KHZ:
|
||||
BW = 250;
|
||||
break;
|
||||
case BW_500KHZ:
|
||||
BW = 500;
|
||||
break;
|
||||
default:
|
||||
MSG("ERROR: Cannot compute time on air for this packet, unsupported bandwidth (%u)\n", packet->bandwidth);
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (packet->datarate) {
|
||||
case DR_LORA_SF7:
|
||||
SF = 7;
|
||||
break;
|
||||
case DR_LORA_SF8:
|
||||
SF = 8;
|
||||
break;
|
||||
case DR_LORA_SF9:
|
||||
SF = 9;
|
||||
break;
|
||||
case DR_LORA_SF10:
|
||||
SF = 10;
|
||||
break;
|
||||
case DR_LORA_SF11:
|
||||
SF = 11;
|
||||
break;
|
||||
case DR_LORA_SF12:
|
||||
SF = 12;
|
||||
break;
|
||||
default:
|
||||
MSG("ERROR: Cannot compute time on air for this packet, unsupported datarate (%u)\n", packet->datarate);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Duration of 1 symbol */
|
||||
Tsym = pow(2, SF) / BW;
|
||||
|
||||
/* Duration of preamble */
|
||||
Tpreamble = (8 + 4.25) * Tsym; /* 8 programmed symbols in preamble */
|
||||
|
||||
/* Duration of payload */
|
||||
H = (isBeacon==false)?0:1; /* header is always enabled, except for beacons */
|
||||
DE = (SF >= 11)?1:0; /* Low datarate optimization enabled for SF11 and SF12 */
|
||||
|
||||
payloadSymbNb = 8 + (ceil((double)(8*packet->size - 4*SF + 28 + 16 - 20*H) / (double)(4*(SF - 2*DE))) * (packet->coderate + 4)); /* Explicitely cast to double to keep precision of the division */
|
||||
|
||||
Tpayload = payloadSymbNb * Tsym;
|
||||
|
||||
Tpacket = Tpreamble + Tpayload;
|
||||
|
||||
return Tpacket;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PUBLIC FUNCTIONS DEFINITION ----------------------------------------- */
|
||||
|
||||
bool jit_queue_is_full(struct jit_queue_s *queue) {
|
||||
bool result;
|
||||
|
||||
pthread_mutex_lock(&mx_jit_queue);
|
||||
|
||||
result = (queue->num_pkt == JIT_QUEUE_MAX)?true:false;
|
||||
|
||||
pthread_mutex_unlock(&mx_jit_queue);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool jit_queue_is_empty(struct jit_queue_s *queue) {
|
||||
bool result;
|
||||
|
||||
pthread_mutex_lock(&mx_jit_queue);
|
||||
|
||||
result = (queue->num_pkt == 0)?true:false;
|
||||
|
||||
pthread_mutex_unlock(&mx_jit_queue);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void jit_queue_init(struct jit_queue_s *queue) {
|
||||
int i;
|
||||
|
||||
pthread_mutex_lock(&mx_jit_queue);
|
||||
|
||||
memset(queue, 0, sizeof(*queue));
|
||||
for (i=0; i<JIT_QUEUE_MAX; i++) {
|
||||
queue->nodes[i].pre_delay = 0;
|
||||
queue->nodes[i].post_delay = 0;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&mx_jit_queue);
|
||||
}
|
||||
|
||||
int compare(const void *a, const void *b, void *arg)
|
||||
{
|
||||
struct jit_node_s *p = (struct jit_node_s *)a;
|
||||
struct jit_node_s *q = (struct jit_node_s *)b;
|
||||
int *counter = (int *)arg;
|
||||
int p_count, q_count;
|
||||
|
||||
p_count = p->pkt.count_us;
|
||||
q_count = q->pkt.count_us;
|
||||
|
||||
if (p_count > q_count)
|
||||
*counter = *counter + 1;
|
||||
|
||||
return p_count - q_count;
|
||||
}
|
||||
|
||||
void jit_sort_queue(struct jit_queue_s *queue) {
|
||||
int counter = 0;
|
||||
|
||||
if (queue->num_pkt == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
MSG_DEBUG(DEBUG_JIT, "sorting queue in ascending order packet timestamp - queue size:%u\n", queue->num_pkt);
|
||||
qsort_r(queue->nodes, queue->num_pkt, sizeof(queue->nodes[0]), compare, &counter);
|
||||
MSG_DEBUG(DEBUG_JIT, "sorting queue done - swapped:%d\n", counter);
|
||||
}
|
||||
|
||||
bool jit_collision_test(uint32_t p1_count_us, uint32_t p1_pre_delay, uint32_t p1_post_delay, uint32_t p2_count_us, uint32_t p2_pre_delay, uint32_t p2_post_delay) {
|
||||
if (((p1_count_us - p2_count_us) <= (p1_pre_delay + p2_post_delay + TX_MARGIN_DELAY)) ||
|
||||
((p2_count_us - p1_count_us) <= (p2_pre_delay + p1_post_delay + TX_MARGIN_DELAY))) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
enum jit_error_e jit_enqueue(struct jit_queue_s *queue, struct timeval *time, struct lgw_pkt_tx_s *packet, enum jit_pkt_type_e pkt_type) {
|
||||
int i = 0;
|
||||
uint32_t time_us = time->tv_sec * 1000000UL + time->tv_usec; /* convert time in µs */
|
||||
uint32_t packet_post_delay = 0;
|
||||
uint32_t packet_pre_delay = 0;
|
||||
uint32_t target_pre_delay = 0;
|
||||
enum jit_error_e err_collision;
|
||||
uint32_t asap_count_us;
|
||||
|
||||
MSG_DEBUG(DEBUG_JIT, "Current concentrator time is %u, pkt_type=%d\n", time_us, pkt_type);
|
||||
|
||||
if (packet == NULL) {
|
||||
MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: invalid parameter\n");
|
||||
return JIT_ERROR_INVALID;
|
||||
}
|
||||
|
||||
if (jit_queue_is_full(queue)) {
|
||||
MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: cannot enqueue packet, JIT queue is full\n");
|
||||
return JIT_ERROR_FULL;
|
||||
}
|
||||
|
||||
/* Compute packet pre/post delays depending on packet's type */
|
||||
switch (pkt_type) {
|
||||
case JIT_PKT_TYPE_DOWNLINK_CLASS_A:
|
||||
case JIT_PKT_TYPE_DOWNLINK_CLASS_B:
|
||||
case JIT_PKT_TYPE_DOWNLINK_CLASS_C:
|
||||
packet_pre_delay = TX_START_DELAY + TX_JIT_DELAY;
|
||||
packet_post_delay = time_on_air(packet, false) * 1000UL; /* in us */
|
||||
break;
|
||||
case JIT_PKT_TYPE_BEACON:
|
||||
/* As defined in LoRaWAN spec */
|
||||
packet_pre_delay = TX_START_DELAY + BEACON_GUARD + TX_JIT_DELAY;
|
||||
packet_post_delay = BEACON_RESERVED;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&mx_jit_queue);
|
||||
|
||||
/* An immediate downlink becomes a timestamped downlink "ASAP" */
|
||||
/* Set the packet count_us to the first available slot */
|
||||
if (pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_C) {
|
||||
/* change tx_mode to timestamped */
|
||||
packet->tx_mode = TIMESTAMPED;
|
||||
|
||||
/* Search for the ASAP timestamp to be given to the packet */
|
||||
asap_count_us = time_us + 1E6; /* TODO: Take 1 second margin, to be refined */
|
||||
if (queue->num_pkt == 0) {
|
||||
/* If the jit queue is empty, we can insert this packet */
|
||||
MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink, first in JiT queue (count_us=%u)\n", asap_count_us);
|
||||
} else {
|
||||
/* Else we can try to insert it:
|
||||
- ASAP meaning NOW + MARGIN
|
||||
- at the last index of the queue
|
||||
- between 2 downlinks in the queue
|
||||
*/
|
||||
|
||||
/* First, try if the ASAP time collides with an already enqueued downlink */
|
||||
for (i=0; i<queue->num_pkt; i++) {
|
||||
if (jit_collision_test(asap_count_us, packet_pre_delay, packet_post_delay, queue->nodes[i].pkt.count_us, queue->nodes[i].pre_delay, queue->nodes[i].post_delay) == true) {
|
||||
MSG_DEBUG(DEBUG_JIT, "DEBUG: cannot insert IMMEDIATE downlink at count_us=%u, collides with %u (index=%d)\n", asap_count_us, queue->nodes[i].pkt.count_us, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == queue->num_pkt) {
|
||||
/* No collision with ASAP time, we can insert it */
|
||||
MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink ASAP at %u (no collision)\n", asap_count_us);
|
||||
} else {
|
||||
/* Search for the best slot then */
|
||||
for (i=0; i<queue->num_pkt; i++) {
|
||||
asap_count_us = queue->nodes[i].pkt.count_us + queue->nodes[i].post_delay + packet_pre_delay + TX_JIT_DELAY + TX_MARGIN_DELAY;
|
||||
if (i == (queue->num_pkt - 1)) {
|
||||
/* Last packet index, we can insert after this one */
|
||||
MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink, last in JiT queue (count_us=%u)\n", asap_count_us);
|
||||
} else {
|
||||
/* Check if packet can be inserted between this index and the next one */
|
||||
MSG_DEBUG(DEBUG_JIT, "DEBUG: try to insert IMMEDIATE downlink (count_us=%u) between index %d and index %d?\n", asap_count_us, i, i+1);
|
||||
if (jit_collision_test(asap_count_us, packet_pre_delay, packet_post_delay, queue->nodes[i+1].pkt.count_us, queue->nodes[i+1].pre_delay, queue->nodes[i+1].post_delay) == true) {
|
||||
MSG_DEBUG(DEBUG_JIT, "DEBUG: failed to insert IMMEDIATE downlink (count_us=%u), continue...\n", asap_count_us);
|
||||
continue;
|
||||
} else {
|
||||
MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink (count_us=%u)\n", asap_count_us);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Set packet with ASAP timestamp */
|
||||
packet->count_us = asap_count_us;
|
||||
}
|
||||
|
||||
/* Check criteria_1: is it already too late to send this packet ?
|
||||
* The packet should arrive at least at (tmst - TX_START_DELAY) to be programmed into concentrator
|
||||
* Note: - Also add some margin, to be checked how much is needed, if needed
|
||||
* - Valid for both Downlinks and Beacon packets
|
||||
*
|
||||
* Warning: unsigned arithmetic (handle roll-over)
|
||||
* t_packet < t_current + TX_START_DELAY + MARGIN
|
||||
*/
|
||||
if ((packet->count_us - time_us) <= (TX_START_DELAY + TX_MARGIN_DELAY + TX_JIT_DELAY)) {
|
||||
MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: Packet REJECTED, already too late to send it (current=%u, packet=%u, type=%d)\n", time_us, packet->count_us, pkt_type);
|
||||
pthread_mutex_unlock(&mx_jit_queue);
|
||||
return JIT_ERROR_TOO_LATE;
|
||||
}
|
||||
|
||||
/* Check criteria_2: Does packet timestamp seem plausible compared to current time
|
||||
* We do not expect the server to program a downlink too early compared to current time
|
||||
* Class A: downlink has to be sent in a 1s or 2s time window after RX
|
||||
* Class B: downlink has to occur in a 128s time window
|
||||
* Class C: no check needed, departure time has been calculated previously
|
||||
* So let's define a safe delay above which we can say that the packet is out of bound: TX_MAX_ADVANCE_DELAY
|
||||
* Note: - Valid for Downlinks only, not for Beacon packets
|
||||
*
|
||||
* Warning: unsigned arithmetic (handle roll-over)
|
||||
t_packet > t_current + TX_MAX_ADVANCE_DELAY
|
||||
*/
|
||||
if ((pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_A) || (pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_B)) {
|
||||
if ((packet->count_us - time_us) > TX_MAX_ADVANCE_DELAY) {
|
||||
MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: Packet REJECTED, timestamp seems wrong, too much in advance (current=%u, packet=%u, type=%d)\n", time_us, packet->count_us, pkt_type);
|
||||
pthread_mutex_unlock(&mx_jit_queue);
|
||||
return JIT_ERROR_TOO_EARLY;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check criteria_3: does this new packet overlap with a packet already enqueued ?
|
||||
* Note: - need to take into account packet's pre_delay and post_delay of each packet
|
||||
* - Valid for both Downlinks and beacon packets
|
||||
* - Beacon guard can be ignored if we try to queue a Class A downlink
|
||||
*/
|
||||
for (i=0; i<queue->num_pkt; i++) {
|
||||
/* We ignore Beacon Guard for Class A/C downlinks */
|
||||
if (((pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_A) || (pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_C)) && (queue->nodes[i].pkt_type == JIT_PKT_TYPE_BEACON)) {
|
||||
target_pre_delay = TX_START_DELAY;
|
||||
} else {
|
||||
target_pre_delay = queue->nodes[i].pre_delay;
|
||||
}
|
||||
|
||||
/* Check if there is a collision
|
||||
* Warning: unsigned arithmetic (handle roll-over)
|
||||
* t_packet_new - pre_delay_packet_new < t_packet_prev + post_delay_packet_prev (OVERLAP on post delay)
|
||||
* t_packet_new + post_delay_packet_new > t_packet_prev - pre_delay_packet_prev (OVERLAP on pre delay)
|
||||
*/
|
||||
if (jit_collision_test(packet->count_us, packet_pre_delay, packet_post_delay, queue->nodes[i].pkt.count_us, target_pre_delay, queue->nodes[i].post_delay) == true) {
|
||||
switch (queue->nodes[i].pkt_type) {
|
||||
case JIT_PKT_TYPE_DOWNLINK_CLASS_A:
|
||||
case JIT_PKT_TYPE_DOWNLINK_CLASS_B:
|
||||
case JIT_PKT_TYPE_DOWNLINK_CLASS_C:
|
||||
MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: Packet (type=%d) REJECTED, collision with packet already programmed at %u (%u)\n", pkt_type, queue->nodes[i].pkt.count_us, packet->count_us);
|
||||
err_collision = JIT_ERROR_COLLISION_PACKET;
|
||||
break;
|
||||
case JIT_PKT_TYPE_BEACON:
|
||||
if (pkt_type != JIT_PKT_TYPE_BEACON) {
|
||||
/* do not overload logs for beacon/beacon collision, as it is expected to happen with beacon pre-scheduling algorith used */
|
||||
MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: Packet (type=%d) REJECTED, collision with beacon already programmed at %u (%u)\n", pkt_type, queue->nodes[i].pkt.count_us, packet->count_us);
|
||||
}
|
||||
err_collision = JIT_ERROR_COLLISION_BEACON;
|
||||
break;
|
||||
default:
|
||||
MSG("ERROR: Unknown packet type, should not occur, BUG?\n");
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
pthread_mutex_unlock(&mx_jit_queue);
|
||||
return err_collision;
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally enqueue it */
|
||||
/* Insert packet at the end of the queue */
|
||||
memcpy(&(queue->nodes[queue->num_pkt].pkt), packet, sizeof(struct lgw_pkt_tx_s));
|
||||
queue->nodes[queue->num_pkt].pre_delay = packet_pre_delay;
|
||||
queue->nodes[queue->num_pkt].post_delay = packet_post_delay;
|
||||
queue->nodes[queue->num_pkt].pkt_type = pkt_type;
|
||||
if (pkt_type == JIT_PKT_TYPE_BEACON) {
|
||||
queue->num_beacon++;
|
||||
}
|
||||
queue->num_pkt++;
|
||||
/* Sort the queue in ascending order of packet timestamp */
|
||||
jit_sort_queue(queue);
|
||||
|
||||
/* Done */
|
||||
pthread_mutex_unlock(&mx_jit_queue);
|
||||
|
||||
jit_print_queue(queue, false, DEBUG_JIT);
|
||||
|
||||
MSG_DEBUG(DEBUG_JIT, "enqueued packet with count_us=%u (size=%u bytes, toa=%u us, type=%u)\n", packet->count_us, packet->size, packet_post_delay, pkt_type);
|
||||
|
||||
return JIT_ERROR_OK;
|
||||
}
|
||||
|
||||
enum jit_error_e jit_dequeue(struct jit_queue_s *queue, int index, struct lgw_pkt_tx_s *packet, enum jit_pkt_type_e *pkt_type) {
|
||||
if (packet == NULL) {
|
||||
MSG("ERROR: invalid parameter\n");
|
||||
return JIT_ERROR_INVALID;
|
||||
}
|
||||
|
||||
if ((index < 0) || (index >= JIT_QUEUE_MAX)) {
|
||||
MSG("ERROR: invalid parameter\n");
|
||||
return JIT_ERROR_INVALID;
|
||||
}
|
||||
|
||||
if (jit_queue_is_empty(queue)) {
|
||||
MSG("ERROR: cannot dequeue packet, JIT queue is empty\n");
|
||||
return JIT_ERROR_EMPTY;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&mx_jit_queue);
|
||||
|
||||
/* Dequeue requested packet */
|
||||
memcpy(packet, &(queue->nodes[index].pkt), sizeof(struct lgw_pkt_tx_s));
|
||||
queue->num_pkt--;
|
||||
*pkt_type = queue->nodes[index].pkt_type;
|
||||
if (*pkt_type == JIT_PKT_TYPE_BEACON) {
|
||||
queue->num_beacon--;
|
||||
MSG_DEBUG(DEBUG_BEACON, "--- Beacon dequeued ---\n");
|
||||
}
|
||||
|
||||
/* Replace dequeued packet with last packet of the queue */
|
||||
memcpy(&(queue->nodes[index]), &(queue->nodes[queue->num_pkt]), sizeof(struct jit_node_s));
|
||||
memset(&(queue->nodes[queue->num_pkt]), 0, sizeof(struct jit_node_s));
|
||||
|
||||
/* Sort queue in ascending order of packet timestamp */
|
||||
jit_sort_queue(queue);
|
||||
|
||||
/* Done */
|
||||
pthread_mutex_unlock(&mx_jit_queue);
|
||||
|
||||
jit_print_queue(queue, false, DEBUG_JIT);
|
||||
|
||||
MSG_DEBUG(DEBUG_JIT, "dequeued packet with count_us=%u from index %d\n", packet->count_us, index);
|
||||
|
||||
return JIT_ERROR_OK;
|
||||
}
|
||||
|
||||
enum jit_error_e jit_peek(struct jit_queue_s *queue, struct timeval *time, int *pkt_idx) {
|
||||
/* Return index of node containing a packet inline with given time */
|
||||
int i = 0;
|
||||
int idx_highest_priority = -1;
|
||||
uint32_t time_us;
|
||||
|
||||
if ((time == NULL) || (pkt_idx == NULL)) {
|
||||
MSG("ERROR: invalid parameter\n");
|
||||
return JIT_ERROR_INVALID;
|
||||
}
|
||||
|
||||
if (jit_queue_is_empty(queue)) {
|
||||
return JIT_ERROR_EMPTY;
|
||||
}
|
||||
|
||||
time_us = time->tv_sec * 1000000UL + time->tv_usec;
|
||||
|
||||
pthread_mutex_lock(&mx_jit_queue);
|
||||
|
||||
/* Search for highest priority packet to be sent */
|
||||
for (i=0; i<queue->num_pkt; i++) {
|
||||
/* First check if that packet is outdated:
|
||||
* If a packet seems too much in advance, and was not rejected at enqueue time,
|
||||
* it means that we missed it for peeking, we need to drop it
|
||||
*
|
||||
* Warning: unsigned arithmetic
|
||||
* t_packet > t_current + TX_MAX_ADVANCE_DELAY
|
||||
*/
|
||||
if ((queue->nodes[i].pkt.count_us - time_us) >= TX_MAX_ADVANCE_DELAY) {
|
||||
/* We drop the packet to avoid lock-up */
|
||||
queue->num_pkt--;
|
||||
if (queue->nodes[i].pkt_type == JIT_PKT_TYPE_BEACON) {
|
||||
queue->num_beacon--;
|
||||
MSG("WARNING: --- Beacon dropped (current_time=%u, packet_time=%u) ---\n", time_us, queue->nodes[i].pkt.count_us);
|
||||
} else {
|
||||
MSG("WARNING: --- Packet dropped (current_time=%u, packet_time=%u) ---\n", time_us, queue->nodes[i].pkt.count_us);
|
||||
}
|
||||
|
||||
/* Replace dropped packet with last packet of the queue */
|
||||
memcpy(&(queue->nodes[i]), &(queue->nodes[queue->num_pkt]), sizeof(struct jit_node_s));
|
||||
memset(&(queue->nodes[queue->num_pkt]), 0, sizeof(struct jit_node_s));
|
||||
|
||||
/* Sort queue in ascending order of packet timestamp */
|
||||
jit_sort_queue(queue);
|
||||
|
||||
/* restart loop after purge to find packet to be sent */
|
||||
i = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Then look for highest priority packet to be sent:
|
||||
* Warning: unsigned arithmetic (handle roll-over)
|
||||
* t_packet < t_highest
|
||||
*/
|
||||
if ((idx_highest_priority == -1) || (((queue->nodes[i].pkt.count_us - time_us) < (queue->nodes[idx_highest_priority].pkt.count_us - time_us)))) {
|
||||
idx_highest_priority = i;
|
||||
}
|
||||
}
|
||||
|
||||
/* Peek criteria 1: look for a packet to be sent in next TX_JIT_DELAY ms timeframe
|
||||
* Warning: unsigned arithmetic (handle roll-over)
|
||||
* t_packet < t_current + TX_JIT_DELAY
|
||||
*/
|
||||
if ((queue->nodes[idx_highest_priority].pkt.count_us - time_us) < TX_JIT_DELAY) {
|
||||
*pkt_idx = idx_highest_priority;
|
||||
MSG_DEBUG(DEBUG_JIT, "peek packet with count_us=%u at index %d\n",
|
||||
queue->nodes[idx_highest_priority].pkt.count_us, idx_highest_priority);
|
||||
} else {
|
||||
*pkt_idx = -1;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&mx_jit_queue);
|
||||
|
||||
return JIT_ERROR_OK;
|
||||
}
|
||||
|
||||
void jit_print_queue(struct jit_queue_s *queue, bool show_all, int debug_level) {
|
||||
int i = 0;
|
||||
int loop_end;
|
||||
|
||||
if (jit_queue_is_empty(queue)) {
|
||||
MSG_DEBUG(debug_level, "INFO: [jit] queue is empty\n");
|
||||
} else {
|
||||
pthread_mutex_lock(&mx_jit_queue);
|
||||
|
||||
MSG_DEBUG(debug_level, "INFO: [jit] queue contains %d packets:\n", queue->num_pkt);
|
||||
MSG_DEBUG(debug_level, "INFO: [jit] queue contains %d beacons:\n", queue->num_beacon);
|
||||
loop_end = (show_all == true) ? JIT_QUEUE_MAX : queue->num_pkt;
|
||||
for (i=0; i<loop_end; i++) {
|
||||
MSG_DEBUG(debug_level, " - node[%d]: count_us=%u - type=%d\n",
|
||||
i,
|
||||
queue->nodes[i].pkt.count_us,
|
||||
queue->nodes[i].pkt_type);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&mx_jit_queue);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,782 @@
|
||||
/*
|
||||
Parson ( http://kgabis.github.com/parson/ )
|
||||
Copyright (C) 2013 Krzysztof Gabis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "parson.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#define ERROR 0
|
||||
#define SUCCESS 1
|
||||
#define STARTING_CAPACITY 15
|
||||
#define ARRAY_MAX_CAPACITY 122880 /* 15*(2^13) */
|
||||
#define OBJECT_MAX_CAPACITY 960 /* 15*(2^6) */
|
||||
#define MAX_NESTING 19
|
||||
#define sizeof_token(a) (sizeof(a) - 1)
|
||||
#define skip_char(str) ((*str)++)
|
||||
#define skip_whitespaces(str) while (isspace(**str)) { skip_char(str); }
|
||||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||
|
||||
#define parson_malloc(a) malloc(a)
|
||||
#define parson_free(a) free((void*)a)
|
||||
#define parson_realloc(a, b) realloc(a, b)
|
||||
|
||||
/* Type definitions */
|
||||
typedef union json_value_value {
|
||||
const char *string;
|
||||
double number;
|
||||
JSON_Object *object;
|
||||
JSON_Array *array;
|
||||
int boolean;
|
||||
int null;
|
||||
} JSON_Value_Value;
|
||||
|
||||
struct json_value_t {
|
||||
JSON_Value_Type type;
|
||||
JSON_Value_Value value;
|
||||
};
|
||||
|
||||
struct json_object_t {
|
||||
const char **names;
|
||||
JSON_Value **values;
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
};
|
||||
|
||||
struct json_array_t {
|
||||
JSON_Value **items;
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
};
|
||||
|
||||
/* Various */
|
||||
static char * read_file(const char *filename);
|
||||
static void remove_comments(char *string, const char *start_token, const char *end_token);
|
||||
static int try_realloc(void **ptr, size_t new_size);
|
||||
static char * parson_strndup(const char *string, size_t n);
|
||||
static int is_utf(const unsigned char *string);
|
||||
static int is_decimal(const char *string, size_t length);
|
||||
|
||||
/* JSON Object */
|
||||
static JSON_Object * json_object_init(void);
|
||||
static int json_object_add(JSON_Object *object, const char *name, JSON_Value *value);
|
||||
static int json_object_resize(JSON_Object *object, size_t capacity);
|
||||
static JSON_Value * json_object_nget_value(const JSON_Object *object, const char *name, size_t n);
|
||||
static void json_object_free(JSON_Object *object);
|
||||
|
||||
/* JSON Array */
|
||||
static JSON_Array * json_array_init(void);
|
||||
static int json_array_add(JSON_Array *array, JSON_Value *value);
|
||||
static int json_array_resize(JSON_Array *array, size_t capacity);
|
||||
static void json_array_free(JSON_Array *array);
|
||||
|
||||
/* JSON Value */
|
||||
static JSON_Value * json_value_init_object(void);
|
||||
static JSON_Value * json_value_init_array(void);
|
||||
static JSON_Value * json_value_init_string(const char *string);
|
||||
static JSON_Value * json_value_init_number(double number);
|
||||
static JSON_Value * json_value_init_boolean(int boolean);
|
||||
static JSON_Value * json_value_init_null(void);
|
||||
|
||||
/* Parser */
|
||||
static void skip_quotes(const char **string);
|
||||
static const char * get_processed_string(const char **string);
|
||||
static JSON_Value * parse_object_value(const char **string, size_t nesting);
|
||||
static JSON_Value * parse_array_value(const char **string, size_t nesting);
|
||||
static JSON_Value * parse_string_value(const char **string);
|
||||
static JSON_Value * parse_boolean_value(const char **string);
|
||||
static JSON_Value * parse_number_value(const char **string);
|
||||
static JSON_Value * parse_null_value(const char **string);
|
||||
static JSON_Value * parse_value(const char **string, size_t nesting);
|
||||
|
||||
/* Various */
|
||||
static int try_realloc(void **ptr, size_t new_size) {
|
||||
void *reallocated_ptr = parson_realloc(*ptr, new_size);
|
||||
if (!reallocated_ptr)
|
||||
return ERROR;
|
||||
*ptr = reallocated_ptr;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
static char * parson_strndup(const char *string, size_t n) {
|
||||
char *output_string = (char*)parson_malloc(n + 1);
|
||||
if (!output_string)
|
||||
return NULL;
|
||||
output_string[n] = '\0';
|
||||
strncpy(output_string, string, n);
|
||||
return output_string;
|
||||
}
|
||||
|
||||
static int is_utf(const unsigned char *s) {
|
||||
return isxdigit(s[0]) && isxdigit(s[1]) && isxdigit(s[2]) && isxdigit(s[3]);
|
||||
}
|
||||
|
||||
static int is_decimal(const char *string, size_t length) {
|
||||
if (length > 1 && string[0] == '0' && string[1] != '.')
|
||||
return 0;
|
||||
if (length > 2 && !strncmp(string, "-0", 2) && string[2] != '.')
|
||||
return 0;
|
||||
while (length--)
|
||||
if (strchr("xX", string[length]))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static char * read_file(const char * filename) {
|
||||
FILE *fp = fopen(filename, "r");
|
||||
size_t file_size;
|
||||
char *file_contents;
|
||||
if (!fp)
|
||||
return NULL;
|
||||
fseek(fp, 0L, SEEK_END);
|
||||
file_size = ftell(fp);
|
||||
rewind(fp);
|
||||
file_contents = (char*)parson_malloc(sizeof(char) * (file_size + 1));
|
||||
if (!file_contents) {
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
if (fread(file_contents, file_size, 1, fp) < 1) {
|
||||
if (ferror(fp)) {
|
||||
fclose(fp);
|
||||
parson_free(file_contents);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
file_contents[file_size] = '\0';
|
||||
return file_contents;
|
||||
}
|
||||
|
||||
static void remove_comments(char *string, const char *start_token, const char *end_token) {
|
||||
int in_string = 0, escaped = 0;
|
||||
size_t i;
|
||||
char *ptr = NULL, current_char;
|
||||
size_t start_token_len = strlen(start_token);
|
||||
size_t end_token_len = strlen(end_token);
|
||||
if (start_token_len == 0 || end_token_len == 0)
|
||||
return;
|
||||
while ((current_char = *string) != '\0') {
|
||||
if (current_char == '\\' && !escaped) {
|
||||
escaped = 1;
|
||||
string++;
|
||||
continue;
|
||||
} else if (current_char == '\"' && !escaped) {
|
||||
in_string = !in_string;
|
||||
} else if (!in_string && strncmp(string, start_token, start_token_len) == 0) {
|
||||
for(i = 0; i < start_token_len; i++)
|
||||
string[i] = ' ';
|
||||
string = string + start_token_len;
|
||||
ptr = strstr(string, end_token);
|
||||
if (!ptr)
|
||||
return;
|
||||
for (i = 0; i < (ptr - string) + end_token_len; i++)
|
||||
string[i] = ' ';
|
||||
string = ptr + end_token_len - 1;
|
||||
}
|
||||
escaped = 0;
|
||||
string++;
|
||||
}
|
||||
}
|
||||
|
||||
/* JSON Object */
|
||||
static JSON_Object * json_object_init(void) {
|
||||
JSON_Object *new_obj = (JSON_Object*)parson_malloc(sizeof(JSON_Object));
|
||||
if (!new_obj)
|
||||
return NULL;
|
||||
new_obj->names = (const char**)NULL;
|
||||
new_obj->values = (JSON_Value**)NULL;
|
||||
new_obj->capacity = 0;
|
||||
new_obj->count = 0;
|
||||
return new_obj;
|
||||
}
|
||||
|
||||
static int json_object_add(JSON_Object *object, const char *name, JSON_Value *value) {
|
||||
size_t index;
|
||||
if (object->count >= object->capacity) {
|
||||
size_t new_capacity = MAX(object->capacity * 2, STARTING_CAPACITY);
|
||||
if (new_capacity > OBJECT_MAX_CAPACITY)
|
||||
return ERROR;
|
||||
if (json_object_resize(object, new_capacity) == ERROR)
|
||||
return ERROR;
|
||||
}
|
||||
if (json_object_get_value(object, name) != NULL)
|
||||
return ERROR;
|
||||
index = object->count;
|
||||
object->names[index] = parson_strndup(name, strlen(name));
|
||||
if (!object->names[index])
|
||||
return ERROR;
|
||||
object->values[index] = value;
|
||||
object->count++;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
static int json_object_resize(JSON_Object *object, size_t capacity) {
|
||||
if (try_realloc((void**)&object->names, capacity * sizeof(char*)) == ERROR)
|
||||
return ERROR;
|
||||
if (try_realloc((void**)&object->values, capacity * sizeof(JSON_Value*)) == ERROR)
|
||||
return ERROR;
|
||||
object->capacity = capacity;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
static JSON_Value * json_object_nget_value(const JSON_Object *object, const char *name, size_t n) {
|
||||
size_t i, name_length;
|
||||
for (i = 0; i < json_object_get_count(object); i++) {
|
||||
name_length = strlen(object->names[i]);
|
||||
if (name_length != n)
|
||||
continue;
|
||||
if (strncmp(object->names[i], name, n) == 0)
|
||||
return object->values[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void json_object_free(JSON_Object *object) {
|
||||
while(object->count--) {
|
||||
parson_free(object->names[object->count]);
|
||||
json_value_free(object->values[object->count]);
|
||||
}
|
||||
parson_free(object->names);
|
||||
parson_free(object->values);
|
||||
parson_free(object);
|
||||
}
|
||||
|
||||
/* JSON Array */
|
||||
static JSON_Array * json_array_init(void) {
|
||||
JSON_Array *new_array = (JSON_Array*)parson_malloc(sizeof(JSON_Array));
|
||||
if (!new_array)
|
||||
return NULL;
|
||||
new_array->items = (JSON_Value**)NULL;
|
||||
new_array->capacity = 0;
|
||||
new_array->count = 0;
|
||||
return new_array;
|
||||
}
|
||||
|
||||
static int json_array_add(JSON_Array *array, JSON_Value *value) {
|
||||
if (array->count >= array->capacity) {
|
||||
size_t new_capacity = MAX(array->capacity * 2, STARTING_CAPACITY);
|
||||
if (new_capacity > ARRAY_MAX_CAPACITY)
|
||||
return ERROR;
|
||||
if (!json_array_resize(array, new_capacity))
|
||||
return ERROR;
|
||||
}
|
||||
array->items[array->count] = value;
|
||||
array->count++;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
static int json_array_resize(JSON_Array *array, size_t capacity) {
|
||||
if (try_realloc((void**)&array->items, capacity * sizeof(JSON_Value*)) == ERROR)
|
||||
return ERROR;
|
||||
array->capacity = capacity;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
static void json_array_free(JSON_Array *array) {
|
||||
while (array->count--)
|
||||
json_value_free(array->items[array->count]);
|
||||
parson_free(array->items);
|
||||
parson_free(array);
|
||||
}
|
||||
|
||||
/* JSON Value */
|
||||
static JSON_Value * json_value_init_object(void) {
|
||||
JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
|
||||
if (!new_value)
|
||||
return NULL;
|
||||
new_value->type = JSONObject;
|
||||
new_value->value.object = json_object_init();
|
||||
if (!new_value->value.object) {
|
||||
parson_free(new_value);
|
||||
return NULL;
|
||||
}
|
||||
return new_value;
|
||||
}
|
||||
|
||||
static JSON_Value * json_value_init_array(void) {
|
||||
JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
|
||||
if (!new_value)
|
||||
return NULL;
|
||||
new_value->type = JSONArray;
|
||||
new_value->value.array = json_array_init();
|
||||
if (!new_value->value.array) {
|
||||
parson_free(new_value);
|
||||
return NULL;
|
||||
}
|
||||
return new_value;
|
||||
}
|
||||
|
||||
static JSON_Value * json_value_init_string(const char *string) {
|
||||
JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
|
||||
if (!new_value)
|
||||
return NULL;
|
||||
new_value->type = JSONString;
|
||||
new_value->value.string = string;
|
||||
return new_value;
|
||||
}
|
||||
|
||||
static JSON_Value * json_value_init_number(double number) {
|
||||
JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
|
||||
if (!new_value)
|
||||
return NULL;
|
||||
new_value->type = JSONNumber;
|
||||
new_value->value.number = number;
|
||||
return new_value;
|
||||
}
|
||||
|
||||
static JSON_Value * json_value_init_boolean(int boolean) {
|
||||
JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
|
||||
if (!new_value)
|
||||
return NULL;
|
||||
new_value->type = JSONBoolean;
|
||||
new_value->value.boolean = boolean;
|
||||
return new_value;
|
||||
}
|
||||
|
||||
static JSON_Value * json_value_init_null(void) {
|
||||
JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
|
||||
if (!new_value)
|
||||
return NULL;
|
||||
new_value->type = JSONNull;
|
||||
return new_value;
|
||||
}
|
||||
|
||||
/* Parser */
|
||||
static void skip_quotes(const char **string) {
|
||||
skip_char(string);
|
||||
while (**string != '\"') {
|
||||
if (**string == '\0')
|
||||
return;
|
||||
if (**string == '\\') {
|
||||
skip_char(string);
|
||||
if (**string == '\0')
|
||||
return;
|
||||
}
|
||||
skip_char(string);
|
||||
}
|
||||
skip_char(string);
|
||||
}
|
||||
|
||||
/* Returns contents of a string inside double quotes and parses escaped
|
||||
characters inside.
|
||||
Example: "\u006Corem ipsum" -> lorem ipsum */
|
||||
static const char * get_processed_string(const char **string) {
|
||||
const char *string_start = *string;
|
||||
char *output, *processed_ptr, *unprocessed_ptr, current_char;
|
||||
unsigned int utf_val;
|
||||
skip_quotes(string);
|
||||
if (**string == '\0')
|
||||
return NULL;
|
||||
output = parson_strndup(string_start + 1, *string - string_start - 2);
|
||||
if (!output)
|
||||
return NULL;
|
||||
processed_ptr = unprocessed_ptr = output;
|
||||
while (*unprocessed_ptr) {
|
||||
current_char = *unprocessed_ptr;
|
||||
if (current_char == '\\') {
|
||||
unprocessed_ptr++;
|
||||
current_char = *unprocessed_ptr;
|
||||
switch (current_char) {
|
||||
case '\"': case '\\': case '/': break;
|
||||
case 'b': current_char = '\b'; break;
|
||||
case 'f': current_char = '\f'; break;
|
||||
case 'n': current_char = '\n'; break;
|
||||
case 'r': current_char = '\r'; break;
|
||||
case 't': current_char = '\t'; break;
|
||||
case 'u':
|
||||
unprocessed_ptr++;
|
||||
if (!is_utf((const unsigned char*)unprocessed_ptr) ||
|
||||
sscanf(unprocessed_ptr, "%4x", &utf_val) == EOF) {
|
||||
parson_free(output);
|
||||
return NULL;
|
||||
}
|
||||
if (utf_val < 0x80) {
|
||||
current_char = utf_val;
|
||||
} else if (utf_val < 0x800) {
|
||||
*processed_ptr++ = (utf_val >> 6) | 0xC0;
|
||||
current_char = ((utf_val | 0x80) & 0xBF);
|
||||
} else {
|
||||
*processed_ptr++ = (utf_val >> 12) | 0xE0;
|
||||
*processed_ptr++ = (((utf_val >> 6) | 0x80) & 0xBF);
|
||||
current_char = ((utf_val | 0x80) & 0xBF);
|
||||
}
|
||||
unprocessed_ptr += 3;
|
||||
break;
|
||||
default:
|
||||
parson_free(output);
|
||||
return NULL;
|
||||
break;
|
||||
}
|
||||
} else if ((unsigned char)current_char < 0x20) { /* 0x00-0x19 are invalid characters for json string (http://www.ietf.org/rfc/rfc4627.txt) */
|
||||
parson_free(output);
|
||||
return NULL;
|
||||
}
|
||||
*processed_ptr = current_char;
|
||||
processed_ptr++;
|
||||
unprocessed_ptr++;
|
||||
}
|
||||
*processed_ptr = '\0';
|
||||
if (try_realloc((void**)&output, strlen(output) + 1) == ERROR)
|
||||
return NULL;
|
||||
return output;
|
||||
}
|
||||
|
||||
static JSON_Value * parse_value(const char **string, size_t nesting) {
|
||||
if (nesting > MAX_NESTING)
|
||||
return NULL;
|
||||
skip_whitespaces(string);
|
||||
switch (**string) {
|
||||
case '{':
|
||||
return parse_object_value(string, nesting + 1);
|
||||
case '[':
|
||||
return parse_array_value(string, nesting + 1);
|
||||
case '\"':
|
||||
return parse_string_value(string);
|
||||
case 'f': case 't':
|
||||
return parse_boolean_value(string);
|
||||
case '-':
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9':
|
||||
return parse_number_value(string);
|
||||
case 'n':
|
||||
return parse_null_value(string);
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static JSON_Value * parse_object_value(const char **string, size_t nesting) {
|
||||
JSON_Value *output_value = json_value_init_object(), *new_value = NULL;
|
||||
JSON_Object *output_object = json_value_get_object(output_value);
|
||||
const char *new_key = NULL;
|
||||
if (!output_value)
|
||||
return NULL;
|
||||
skip_char(string);
|
||||
skip_whitespaces(string);
|
||||
if (**string == '}') { /* empty object */
|
||||
skip_char(string);
|
||||
return output_value;
|
||||
}
|
||||
while (**string != '\0') {
|
||||
new_key = get_processed_string(string);
|
||||
skip_whitespaces(string);
|
||||
if (!new_key || **string != ':') {
|
||||
json_value_free(output_value);
|
||||
return NULL;
|
||||
}
|
||||
skip_char(string);
|
||||
new_value = parse_value(string, nesting);
|
||||
if (!new_value) {
|
||||
parson_free(new_key);
|
||||
json_value_free(output_value);
|
||||
return NULL;
|
||||
}
|
||||
if(!json_object_add(output_object, new_key, new_value)) {
|
||||
parson_free(new_key);
|
||||
parson_free(new_value);
|
||||
json_value_free(output_value);
|
||||
return NULL;
|
||||
}
|
||||
parson_free(new_key);
|
||||
skip_whitespaces(string);
|
||||
if (**string != ',')
|
||||
break;
|
||||
skip_char(string);
|
||||
skip_whitespaces(string);
|
||||
}
|
||||
skip_whitespaces(string);
|
||||
if (**string != '}' || /* Trim object after parsing is over */
|
||||
json_object_resize(output_object, json_object_get_count(output_object)) == ERROR) {
|
||||
json_value_free(output_value);
|
||||
return NULL;
|
||||
}
|
||||
skip_char(string);
|
||||
return output_value;
|
||||
}
|
||||
|
||||
static JSON_Value * parse_array_value(const char **string, size_t nesting) {
|
||||
JSON_Value *output_value = json_value_init_array(), *new_array_value = NULL;
|
||||
JSON_Array *output_array = json_value_get_array(output_value);
|
||||
if (!output_value)
|
||||
return NULL;
|
||||
skip_char(string);
|
||||
skip_whitespaces(string);
|
||||
if (**string == ']') { /* empty array */
|
||||
skip_char(string);
|
||||
return output_value;
|
||||
}
|
||||
while (**string != '\0') {
|
||||
new_array_value = parse_value(string, nesting);
|
||||
if (!new_array_value) {
|
||||
json_value_free(output_value);
|
||||
return NULL;
|
||||
}
|
||||
if(json_array_add(output_array, new_array_value) == ERROR) {
|
||||
parson_free(new_array_value);
|
||||
json_value_free(output_value);
|
||||
return NULL;
|
||||
}
|
||||
skip_whitespaces(string);
|
||||
if (**string != ',')
|
||||
break;
|
||||
skip_char(string);
|
||||
skip_whitespaces(string);
|
||||
}
|
||||
skip_whitespaces(string);
|
||||
if (**string != ']' || /* Trim array after parsing is over */
|
||||
json_array_resize(output_array, json_array_get_count(output_array)) == ERROR) {
|
||||
json_value_free(output_value);
|
||||
return NULL;
|
||||
}
|
||||
skip_char(string);
|
||||
return output_value;
|
||||
}
|
||||
|
||||
static JSON_Value * parse_string_value(const char **string) {
|
||||
const char *new_string = get_processed_string(string);
|
||||
if (!new_string)
|
||||
return NULL;
|
||||
return json_value_init_string(new_string);
|
||||
}
|
||||
|
||||
static JSON_Value * parse_boolean_value(const char **string) {
|
||||
size_t true_token_size = sizeof_token("true");
|
||||
size_t false_token_size = sizeof_token("false");
|
||||
if (strncmp("true", *string, true_token_size) == 0) {
|
||||
*string += true_token_size;
|
||||
return json_value_init_boolean(1);
|
||||
} else if (strncmp("false", *string, false_token_size) == 0) {
|
||||
*string += false_token_size;
|
||||
return json_value_init_boolean(0);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static JSON_Value * parse_number_value(const char **string) {
|
||||
char *end;
|
||||
double number = strtod(*string, &end);
|
||||
JSON_Value *output_value;
|
||||
if (is_decimal(*string, end - *string)) {
|
||||
*string = end;
|
||||
output_value = json_value_init_number(number);
|
||||
} else {
|
||||
output_value = NULL;
|
||||
}
|
||||
return output_value;
|
||||
}
|
||||
|
||||
static JSON_Value * parse_null_value(const char **string) {
|
||||
size_t token_size = sizeof_token("null");
|
||||
if (strncmp("null", *string, token_size) == 0) {
|
||||
*string += token_size;
|
||||
return json_value_init_null();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Parser API */
|
||||
JSON_Value * json_parse_file(const char *filename) {
|
||||
char *file_contents = read_file(filename);
|
||||
JSON_Value *output_value = NULL;
|
||||
if (!file_contents)
|
||||
return NULL;
|
||||
output_value = json_parse_string(file_contents);
|
||||
parson_free(file_contents);
|
||||
return output_value;
|
||||
}
|
||||
|
||||
JSON_Value * json_parse_file_with_comments(const char *filename) {
|
||||
char *file_contents = read_file(filename);
|
||||
JSON_Value *output_value = NULL;
|
||||
if (!file_contents)
|
||||
return NULL;
|
||||
output_value = json_parse_string_with_comments(file_contents);
|
||||
parson_free(file_contents);
|
||||
return output_value;
|
||||
}
|
||||
|
||||
JSON_Value * json_parse_string(const char *string) {
|
||||
if (!string || (*string != '{' && *string != '['))
|
||||
return NULL;
|
||||
return parse_value((const char**)&string, 0);
|
||||
}
|
||||
|
||||
JSON_Value * json_parse_string_with_comments(const char *string) {
|
||||
JSON_Value *result = NULL;
|
||||
char *string_mutable_copy = NULL, *string_mutable_copy_ptr = NULL;
|
||||
string_mutable_copy = parson_strndup(string, strlen(string));
|
||||
if (!string_mutable_copy)
|
||||
return NULL;
|
||||
remove_comments(string_mutable_copy, "/*", "*/");
|
||||
remove_comments(string_mutable_copy, "//", "\n");
|
||||
string_mutable_copy_ptr = string_mutable_copy;
|
||||
skip_whitespaces(&string_mutable_copy_ptr);
|
||||
if (*string_mutable_copy_ptr != '{' && *string_mutable_copy_ptr != '[') {
|
||||
parson_free(string_mutable_copy);
|
||||
return NULL;
|
||||
}
|
||||
result = parse_value((const char**)&string_mutable_copy_ptr, 0);
|
||||
parson_free(string_mutable_copy);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/* JSON Object API */
|
||||
|
||||
JSON_Value * json_object_get_value(const JSON_Object *object, const char *name) {
|
||||
return json_object_nget_value(object, name, strlen(name));
|
||||
}
|
||||
|
||||
const char * json_object_get_string(const JSON_Object *object, const char *name) {
|
||||
return json_value_get_string(json_object_get_value(object, name));
|
||||
}
|
||||
|
||||
double json_object_get_number(const JSON_Object *object, const char *name) {
|
||||
return json_value_get_number(json_object_get_value(object, name));
|
||||
}
|
||||
|
||||
JSON_Object * json_object_get_object(const JSON_Object *object, const char *name) {
|
||||
return json_value_get_object(json_object_get_value(object, name));
|
||||
}
|
||||
|
||||
JSON_Array * json_object_get_array(const JSON_Object *object, const char *name) {
|
||||
return json_value_get_array(json_object_get_value(object, name));
|
||||
}
|
||||
|
||||
int json_object_get_boolean(const JSON_Object *object, const char *name) {
|
||||
return json_value_get_boolean(json_object_get_value(object, name));
|
||||
}
|
||||
|
||||
JSON_Value * json_object_dotget_value(const JSON_Object *object, const char *name) {
|
||||
const char *dot_position = strchr(name, '.');
|
||||
if (!dot_position)
|
||||
return json_object_get_value(object, name);
|
||||
object = json_value_get_object(json_object_nget_value(object, name, dot_position - name));
|
||||
return json_object_dotget_value(object, dot_position + 1);
|
||||
}
|
||||
|
||||
const char * json_object_dotget_string(const JSON_Object *object, const char *name) {
|
||||
return json_value_get_string(json_object_dotget_value(object, name));
|
||||
}
|
||||
|
||||
double json_object_dotget_number(const JSON_Object *object, const char *name) {
|
||||
return json_value_get_number(json_object_dotget_value(object, name));
|
||||
}
|
||||
|
||||
JSON_Object * json_object_dotget_object(const JSON_Object *object, const char *name) {
|
||||
return json_value_get_object(json_object_dotget_value(object, name));
|
||||
}
|
||||
|
||||
JSON_Array * json_object_dotget_array(const JSON_Object *object, const char *name) {
|
||||
return json_value_get_array(json_object_dotget_value(object, name));
|
||||
}
|
||||
|
||||
int json_object_dotget_boolean(const JSON_Object *object, const char *name) {
|
||||
return json_value_get_boolean(json_object_dotget_value(object, name));
|
||||
}
|
||||
|
||||
size_t json_object_get_count(const JSON_Object *object) {
|
||||
return object ? object->count : 0;
|
||||
}
|
||||
|
||||
const char * json_object_get_name(const JSON_Object *object, size_t index) {
|
||||
if (index >= json_object_get_count(object))
|
||||
return NULL;
|
||||
return object->names[index];
|
||||
}
|
||||
|
||||
/* JSON Array API */
|
||||
JSON_Value * json_array_get_value(const JSON_Array *array, size_t index) {
|
||||
if (index >= json_array_get_count(array))
|
||||
return NULL;
|
||||
return array->items[index];
|
||||
}
|
||||
|
||||
const char * json_array_get_string(const JSON_Array *array, size_t index) {
|
||||
return json_value_get_string(json_array_get_value(array, index));
|
||||
}
|
||||
|
||||
double json_array_get_number(const JSON_Array *array, size_t index) {
|
||||
return json_value_get_number(json_array_get_value(array, index));
|
||||
}
|
||||
|
||||
JSON_Object * json_array_get_object(const JSON_Array *array, size_t index) {
|
||||
return json_value_get_object(json_array_get_value(array, index));
|
||||
}
|
||||
|
||||
JSON_Array * json_array_get_array(const JSON_Array *array, size_t index) {
|
||||
return json_value_get_array(json_array_get_value(array, index));
|
||||
}
|
||||
|
||||
int json_array_get_boolean(const JSON_Array *array, size_t index) {
|
||||
return json_value_get_boolean(json_array_get_value(array, index));
|
||||
}
|
||||
|
||||
size_t json_array_get_count(const JSON_Array *array) {
|
||||
return array ? array->count : 0;
|
||||
}
|
||||
|
||||
/* JSON Value API */
|
||||
JSON_Value_Type json_value_get_type(const JSON_Value *value) {
|
||||
return value ? value->type : JSONError;
|
||||
}
|
||||
|
||||
JSON_Object * json_value_get_object(const JSON_Value *value) {
|
||||
return json_value_get_type(value) == JSONObject ? value->value.object : NULL;
|
||||
}
|
||||
|
||||
JSON_Array * json_value_get_array(const JSON_Value *value) {
|
||||
return json_value_get_type(value) == JSONArray ? value->value.array : NULL;
|
||||
}
|
||||
|
||||
const char * json_value_get_string(const JSON_Value *value) {
|
||||
return json_value_get_type(value) == JSONString ? value->value.string : NULL;
|
||||
}
|
||||
|
||||
double json_value_get_number(const JSON_Value *value) {
|
||||
return json_value_get_type(value) == JSONNumber ? value->value.number : 0;
|
||||
}
|
||||
|
||||
int json_value_get_boolean(const JSON_Value *value) {
|
||||
return json_value_get_type(value) == JSONBoolean ? value->value.boolean : -1;
|
||||
}
|
||||
|
||||
void json_value_free(JSON_Value *value) {
|
||||
switch (json_value_get_type(value)) {
|
||||
case JSONObject:
|
||||
json_object_free(value->value.object);
|
||||
break;
|
||||
case JSONString:
|
||||
if (value->value.string) { parson_free(value->value.string); }
|
||||
break;
|
||||
case JSONArray:
|
||||
json_array_free(value->value.array);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
parson_free(value);
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
/ _____) _ | |
|
||||
( (____ _____ ____ _| |_ _____ ____| |__
|
||||
\____ \| ___ | (_ _) ___ |/ ___) _ \
|
||||
_____) ) ____| | | || |_| ____( (___| | | |
|
||||
(______/|_____)_|_|_| \__)_____)\____)_| |_|
|
||||
(C)2013 Semtech-Cycleo
|
||||
|
||||
Description:
|
||||
LoRa concentrator : Timer synchronization
|
||||
Provides synchronization between unix, concentrator and gps clocks
|
||||
|
||||
License: Revised BSD License, see LICENSE.TXT file include in the project
|
||||
Maintainer: Michael Coracin
|
||||
*/
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- DEPENDANCIES --------------------------------------------------------- */
|
||||
|
||||
#include <stdio.h> /* printf, fprintf, snprintf, fopen, fputs */
|
||||
#include <stdint.h> /* C99 types */
|
||||
#include <pthread.h>
|
||||
|
||||
#include "trace.h"
|
||||
#include "timersync.h"
|
||||
#include "loragw_hal.h"
|
||||
#include "loragw_reg.h"
|
||||
#include "loragw_aux.h"
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE CONSTANTS & TYPES -------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE MACROS ------------------------------------------------------- */
|
||||
|
||||
#define timersub(a, b, result) \
|
||||
do { \
|
||||
(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
|
||||
(result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
|
||||
if ((result)->tv_usec < 0) { \
|
||||
--(result)->tv_sec; \
|
||||
(result)->tv_usec += 1000000; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */
|
||||
|
||||
static pthread_mutex_t mx_timersync = PTHREAD_MUTEX_INITIALIZER; /* control access to timer sync offsets */
|
||||
static struct timeval offset_unix_concent = {0,0}; /* timer offset between unix host and concentrator */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE SHARED VARIABLES (GLOBAL) ------------------------------------ */
|
||||
extern bool exit_sig;
|
||||
extern bool quit_sig;
|
||||
extern pthread_mutex_t mx_concent;
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */
|
||||
|
||||
int get_concentrator_time(struct timeval *concent_time, struct timeval unix_time) {
|
||||
struct timeval local_timeval;
|
||||
|
||||
if (concent_time == NULL) {
|
||||
MSG("ERROR: %s invalid parameter\n", __FUNCTION__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&mx_timersync); /* protect global variable access */
|
||||
timersub(&unix_time, &offset_unix_concent, &local_timeval);
|
||||
pthread_mutex_unlock(&mx_timersync);
|
||||
|
||||
/* TODO: handle sx1301 coutner wrap-up !! */
|
||||
concent_time->tv_sec = local_timeval.tv_sec;
|
||||
concent_time->tv_usec = local_timeval.tv_usec;
|
||||
|
||||
MSG_DEBUG(DEBUG_TIMERSYNC, " --> TIME: unix current time is %ld,%ld\n", unix_time.tv_sec, unix_time.tv_usec);
|
||||
MSG_DEBUG(DEBUG_TIMERSYNC, " offset is %ld,%ld\n", offset_unix_concent.tv_sec, offset_unix_concent.tv_usec);
|
||||
MSG_DEBUG(DEBUG_TIMERSYNC, " sx1301 current time is %ld,%ld\n", local_timeval.tv_sec, local_timeval.tv_usec);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------- */
|
||||
/* --- THREAD 6: REGULARLAY MONITOR THE OFFSET BETWEEN UNIX CLOCK AND CONCENTRATOR CLOCK -------- */
|
||||
|
||||
void thread_timersync(void) {
|
||||
struct timeval unix_timeval;
|
||||
struct timeval concentrator_timeval;
|
||||
uint32_t sx1301_timecount = 0;
|
||||
struct timeval offset_previous = {0,0};
|
||||
struct timeval offset_drift = {0,0}; /* delta between current and previous offset */
|
||||
|
||||
while (!exit_sig && !quit_sig) {
|
||||
/* Regularly disable GPS mode of concentrator's counter, in order to get
|
||||
real timer value for synchronizing with host's unix timer */
|
||||
MSG("\nINFO: Disabling GPS mode for concentrator's counter...\n");
|
||||
pthread_mutex_lock(&mx_concent); /* TODO: Is it necessary to protect here? */
|
||||
lgw_reg_w(LGW_GPS_EN, 0);
|
||||
pthread_mutex_unlock(&mx_concent);
|
||||
|
||||
/* Get current unix time */
|
||||
gettimeofday(&unix_timeval, NULL);
|
||||
|
||||
/* Get current concentrator counter value (1MHz) */
|
||||
lgw_get_trigcnt(&sx1301_timecount);
|
||||
concentrator_timeval.tv_sec = sx1301_timecount / 1000000UL;
|
||||
concentrator_timeval.tv_usec = sx1301_timecount - (concentrator_timeval.tv_sec * 1000000UL);
|
||||
|
||||
/* Compute offset between unix and concentrator timers, with microsecond precision */
|
||||
offset_previous.tv_sec = offset_unix_concent.tv_sec;
|
||||
offset_previous.tv_usec = offset_unix_concent.tv_usec;
|
||||
|
||||
/* TODO: handle sx1301 coutner wrap-up */
|
||||
pthread_mutex_lock(&mx_timersync); /* protect global variable access */
|
||||
timersub(&unix_timeval, &concentrator_timeval, &offset_unix_concent);
|
||||
pthread_mutex_unlock(&mx_timersync);
|
||||
|
||||
timersub(&offset_unix_concent, &offset_previous, &offset_drift);
|
||||
|
||||
MSG_DEBUG(DEBUG_TIMERSYNC, " sx1301 = %u (µs) - timeval (%ld,%ld)\n",
|
||||
sx1301_timecount,
|
||||
concentrator_timeval.tv_sec,
|
||||
concentrator_timeval.tv_usec);
|
||||
MSG_DEBUG(DEBUG_TIMERSYNC, " unix_timeval = %ld,%ld\n", unix_timeval.tv_sec, unix_timeval.tv_usec);
|
||||
|
||||
MSG("INFO: host/sx1301 time offset=(%lds:%ldµs) - drift=%ldµs\n",
|
||||
offset_unix_concent.tv_sec,
|
||||
offset_unix_concent.tv_usec,
|
||||
offset_drift.tv_sec * 1000000UL + offset_drift.tv_usec);
|
||||
MSG("INFO: Enabling GPS mode for concentrator's counter.\n\n");
|
||||
pthread_mutex_lock(&mx_concent); /* TODO: Is it necessary to protect here? */
|
||||
lgw_reg_w(LGW_GPS_EN, 1);
|
||||
pthread_mutex_unlock(&mx_concent);
|
||||
|
||||
/* delay next sync */
|
||||
/* If we consider a crystal oscillator precision of about 20ppm worst case, and a clock
|
||||
running at 1MHz, this would mean 1µs drift every 50000µs (10000000/20).
|
||||
As here the time precision is not critical, we should be able to cope with at least 1ms drift,
|
||||
which should occur after 50s (50000µs * 1000).
|
||||
Let's set the thread sleep to 1 minute for now */
|
||||
wait_ms(60000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script is a helper to update the Gateway_ID field of given
|
||||
# JSON configuration file, as a EUI-64 address generated from the 48-bits MAC
|
||||
# address of the device it is run from.
|
||||
#
|
||||
# Usage examples:
|
||||
# ./update_gwid.sh ./local_conf.json
|
||||
|
||||
iot_sk_update_gwid() {
|
||||
# get gateway ID from its MAC address to generate an EUI-64 address
|
||||
GWID_MIDFIX="FFFE"
|
||||
GWID_BEGIN=$(ip link show eth0 | awk '/ether/ {print $2}' | awk -F\: '{print $1$2$3}')
|
||||
GWID_END=$(ip link show eth0 | awk '/ether/ {print $2}' | awk -F\: '{print $4$5$6}')
|
||||
|
||||
# replace last 8 digits of default gateway ID by actual GWID, in given JSON configuration file
|
||||
sed -i 's/\(^\s*"gateway_ID":\s*"\).\{16\}"\s*\(,\?\).*$/\1'${GWID_BEGIN}${GWID_MIDFIX}${GWID_END}'"\2/' $1
|
||||
|
||||
echo "Gateway_ID set to "$GWID_BEGIN$GWID_MIDFIX$GWID_END" in file "$1
|
||||
}
|
||||
|
||||
if [ $# -ne 1 ]
|
||||
then
|
||||
echo "Usage: $0 [filename]"
|
||||
echo " filename: Path to JSON file containing Gateway_ID for packet forwarder"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
iot_sk_update_gwid $1
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,231 @@
|
||||
#Attention
|
||||
This is a fork with the needed modifications to be able to use lora_gateway
|
||||
with a Multitech MTAC-LORA (MultiConnect mCard) board instead of a
|
||||
IoT Starter Kit platform.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/ _____) _ | |
|
||||
( (____ _____ ____ _| |_ _____ ____| |__
|
||||
\____ \| ___ | (_ _) ___ |/ ___) _ \
|
||||
_____) ) ____| | | || |_| ____( (___| | | |
|
||||
(______/|_____)_|_|_| \__)_____)\____)_| |_|
|
||||
(C)2013 Semtech-Cycleo
|
||||
|
||||
Lora network packet forwarder project
|
||||
======================================
|
||||
|
||||
1. Core program: lora_pkt_fwd
|
||||
-------------------------------
|
||||
|
||||
The packet forwarder is a program running on the host of a Lora gateway that
|
||||
forwards RF packets receive by the concentrator to a server through a IP/UDP
|
||||
link, and emits RF packets that are sent by the server. It can also emit a
|
||||
network-wide GPS-synchronous beacon signal used for coordinating all nodes of
|
||||
the network.
|
||||
|
||||
((( Y )))
|
||||
|
|
||||
|
|
||||
+- -|- - - - - - - - - - - - -+ xxxxxxxxxxxx +--------+
|
||||
|+--+-----------+ +------+| xx x x xxx | |
|
||||
|| | | || xx Internet xx | |
|
||||
|| Concentrator |<----+ Host |<------xx or xx-------->| |
|
||||
|| | SPI | || xx Intranet xx | Server |
|
||||
|+--------------+ +------+| xxxx x xxxx | |
|
||||
| ^ ^ | xxxxxxxx | |
|
||||
| | PPS +-----+ NMEA | | | |
|
||||
| +------| GPS |-------+ | +--------+
|
||||
| +-----+ |
|
||||
| |
|
||||
| Gateway |
|
||||
+- - - - - - - - - - - - - - -+
|
||||
|
||||
Uplink: radio packets received by the gateway, with metadata added by the
|
||||
gateway, forwarded to the server. Might also include gateway status.
|
||||
|
||||
Downlink: packets generated by the server, with additional metadata, to be
|
||||
transmitted by the gateway on the radio channel. Might also include
|
||||
configuration data for the gateway.
|
||||
|
||||
2. Helper programs
|
||||
-------------------
|
||||
|
||||
Those programs are included in the project to provide examples on how to
|
||||
communicate with the packet forwarder, and to help the system builder use it
|
||||
without having to implement a full Lora network server.
|
||||
|
||||
### 3.1. util_sink ###
|
||||
|
||||
The packet sink is a simple helper program listening on a single port for UDP
|
||||
datagrams, and displaying a message each time one is received. The content of
|
||||
the datagram itself is ignored.
|
||||
|
||||
### 3.2. util_ack ###
|
||||
|
||||
The packet acknowledger is a simple helper program listening on a single UDP
|
||||
port and responding to PUSH_DATA datagrams with PUSH_ACK, and to PULL_DATA
|
||||
datagrams with PULL_ACK.
|
||||
|
||||
### 3.3. util_tx_test ###
|
||||
|
||||
The network packet sender is a simple helper program used to send packets
|
||||
through the gateway-to-server downlink route.
|
||||
|
||||
4. Helper scripts
|
||||
-----------------
|
||||
|
||||
### 4.1. lora_gateway/reset_lgw.sh
|
||||
|
||||
This script, provided with the HAL (lora_gateway), must be launched on IoT Start
|
||||
Kit platform to reset concentrator chip through GPIO, before starting any
|
||||
application using the concentrator, like the packet forwarder.
|
||||
|
||||
### 4.2. packet_forwarder/lora_pkt_fwd/update_gwid.sh
|
||||
|
||||
This script allows automatic update of Gateway_ID with unique MAC address, in
|
||||
packet forwarder JSON configuration file.
|
||||
Please refer to the script header for more details.
|
||||
|
||||
5. Changelog
|
||||
-------------
|
||||
|
||||
### v3.0.0 - 2016-05-19 ###
|
||||
|
||||
* Merged all different flavours of packet forwarder into one unique lora_pkt_fwd
|
||||
Note: Various flavours can still be achieved using the corresponding
|
||||
global_conf.json.XXX file provided in lora_pkt_fwd/cfg.
|
||||
* Added downlink "just-in-time" scheduling to optimize downlink capacity.
|
||||
* Updated Gateway <-> NetworkServer protocol to describe the new format of
|
||||
"tx_ack" message.
|
||||
* Added "Listen-Before-Talk" configuration.
|
||||
* Splitted reset_pkt_fwd.sh script in 2 different scripts:
|
||||
- reset_lgw.sh, provided with the HAL (lora_gateway)
|
||||
- update_gwid.sh, provided with lora_pkt_fwd
|
||||
|
||||
WARNING: Gateway <-> Network Server protocol version has changed. Please refer
|
||||
to PROTOCOL.txt file.
|
||||
|
||||
### v2.2.1 - 2016-04-12 ###
|
||||
|
||||
* util_tx_test: added FSK support and specific payload for easier PER testing.
|
||||
* base64: fixed padding check.
|
||||
* Updated all makefiles to handle the creation of obj directory when necessary.
|
||||
* [gps/beacon]_pkt_fwd: fixed crash on exit when GPS not enabled.
|
||||
* [*]_pkt_fwd: added a cfg/ directory containing different flavours or the
|
||||
global_conf.json file for different boards: Ref Design PCB_E336 (GW1.5-27dBm),
|
||||
Ref Design PCB_E286 (GW1.0), Ref Design with US902 frequency plan.
|
||||
|
||||
### v2.2.0 - 2015-10-08 ###
|
||||
|
||||
* Removed FTDI support in makefiles to align with HAL v3.2.0.
|
||||
* Force IPv4 mode usage on UDP socket, instead of auto. The auto mode was
|
||||
causing an issue to properly resolve LoRa server hostname given in JSON
|
||||
configuration file (MariaDB issue: https://mariadb.atlassian.net/browse/MDEV-4356,
|
||||
https://mariadb.atlassian.net/browse/MDEV-4379).
|
||||
|
||||
### v2.1.0 - 2015-06-29 ###
|
||||
|
||||
* Added helper script for concentrator reset through GPIO, needed on IoT
|
||||
Starter Kit (reset_pkt_fwd.sh).
|
||||
* The same reset_pkt_fwd.sh script also allows to automatically update the
|
||||
Gateway_ID field in JSON configuration file, with board MAC address.
|
||||
* Updated JSON configuration file with proper default value for IoT Starter
|
||||
Kit: server address set to local server, GPS device path set to proper value
|
||||
(/dev/ttyAMA0).
|
||||
|
||||
### v2.0.0 - 2015-04-30 ###
|
||||
|
||||
* Changed: Several configuration parameters are now set dynamically from the
|
||||
JSON configuration file: RSSI offset, concentrator clock source, radio type,
|
||||
TX gain table, network type. The HAL does not need to be recompiled any more to
|
||||
update those parameters. An example for IoT Starter Kit platform is provided in
|
||||
global_conf.json for basic, gps and beacon packet_forwarder.
|
||||
* Removed: Band frequency JSON configuration file has been removed. An example
|
||||
for EU 868MHz is provided in global_conf.json for basic, gps and beacon packet
|
||||
forwarder.
|
||||
* Changed: Updated makefiles to allow cross compilation from environment
|
||||
variable (ARCH, CROSS_COMPILE).
|
||||
|
||||
** WARNING: **
|
||||
** Update your JSON configuration file with new dynamic parameters. **
|
||||
|
||||
### v1.4.1 - 2015-01-23 ###
|
||||
|
||||
* Bugfix: fixed LP-116, fdev parameter parsed incorrectly, making FSK TX fail.
|
||||
* Bugfix: fixed a platform-dependant minor rounding issue.
|
||||
* Beta: updated beacon format, partially aligned with latest class B proposal.
|
||||
|
||||
### v1.4.0 - 2014-10-16 ###
|
||||
|
||||
* Feature: Adding TX FSK support.
|
||||
* Feature: optional auto-quit if a certain number of PULL_ACK is missed.
|
||||
* Feature: upstream and downstream ping time is displayed on console.
|
||||
* Bugfix: some beacons were missed at high beaconing frequency.
|
||||
* Bugfix: critical snprintf error caused a crash for long payloads.
|
||||
* FSK bitrate now appears in the upstream JSON.
|
||||
|
||||
### v1.3.0 - 2014-03-28 ###
|
||||
|
||||
* Feature: adding preliminary beacon support for class B development.
|
||||
* Solved warnings with 64b integer printf when compiling on x86_64.
|
||||
* Updated build system for easier deployment on various hardware.
|
||||
* Changed threads organization in the forwarder programs.
|
||||
* Removed duplicate protocol documentation.
|
||||
|
||||
### v1.2.0 - 2014-02-03 ###
|
||||
|
||||
* Feature: added a GPS-enabled packet forwarder, used to timestamp received
|
||||
packet with a globally-synchronous microsecond-accurate timestamp.
|
||||
* Feature: GPS packet forwarder sends status report on the uplink, protocol
|
||||
specification updated accordingly (report include gateway geolocation).
|
||||
* Feature: packets can be sent without CRC at radio layer.
|
||||
* Bugfix: no more crash with base64 padded input.
|
||||
* Bugfix: no more rounding errors on the 'freq' value sent to server.
|
||||
* A minimum preamble of 6 Lora symbol is enforced for optimum sensitivity.
|
||||
* Padded Base64 is sent on uplink, downlink accepts padded and unpadded Base64.
|
||||
* Updated the Parson JSON library to a version that supports comments.
|
||||
* Added .md (Markdown) extension to readme files for better Github viewing.
|
||||
|
||||
### v1.1.0 - 2013-12-09 ###
|
||||
|
||||
* Feature: added packet filtering parameters to the JSON configuration files.
|
||||
* Bugfix: will not send a datagram if all the packets returned by the receive()
|
||||
function have been filtered out.
|
||||
* Bugfix: removed leading zeros for the timestamp in the upstream JSON because
|
||||
it is not compliant with JSON standard (might be interpreted as octal number).
|
||||
* Removed TXT extension for README files for better Github integration.
|
||||
* Cleaned-up documentation, moving change log to top README.
|
||||
* Modified Makefiles to ease cross-compilation.
|
||||
|
||||
### v1.0.0 - 2013-11-22 ###
|
||||
|
||||
* Initial release of the packet forwarder, protocol specifications and helper
|
||||
programs.
|
||||
|
||||
6. Legal notice
|
||||
----------------
|
||||
|
||||
The information presented in this project documentation does not form part of
|
||||
any quotation or contract, is believed to be accurate and reliable and may be
|
||||
changed without notice. No liability will be accepted by the publisher for any
|
||||
consequence of its use. Publication thereof does not convey nor imply any
|
||||
license under patent or other industrial or intellectual property rights.
|
||||
Semtech assumes no responsibility or liability whatsoever for any failure or
|
||||
unexpected operation resulting from misuse, neglect improper installation,
|
||||
repair or improper handling or unusual physical or electrical stress
|
||||
including, but not limited to, exposure to parameters beyond the specified
|
||||
maximum ratings or operation outside the specified range.
|
||||
|
||||
SEMTECH PRODUCTS ARE NOT DESIGNED, INTENDED, AUTHORIZED OR WARRANTED TO BE
|
||||
SUITABLE FOR USE IN LIFE-SUPPORT APPLICATIONS, DEVICES OR SYSTEMS OR OTHER
|
||||
CRITICAL APPLICATIONS. INCLUSION OF SEMTECH PRODUCTS IN SUCH APPLICATIONS IS
|
||||
UNDERSTOOD TO BE UNDERTAKEN SOLELY AT THE CUSTOMER’S OWN RISK. Should a
|
||||
customer purchase or use Semtech products for any such unauthorized
|
||||
application, the customer shall indemnify and hold Semtech and its officers,
|
||||
employees, subsidiaries, affiliates, and distributors harmless against all
|
||||
claims, costs damages and attorney fees which could arise.
|
||||
|
||||
*EOF*
|
||||
@@ -0,0 +1,33 @@
|
||||
### Application-specific constants
|
||||
|
||||
APP_NAME := util_ack
|
||||
|
||||
### Constant symbols
|
||||
|
||||
CC := $(CROSS_COMPILE)gcc
|
||||
AR := $(CROSS_COMPILE)ar
|
||||
|
||||
CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I.
|
||||
|
||||
OBJDIR = obj
|
||||
|
||||
### General build targets
|
||||
|
||||
all: $(APP_NAME)
|
||||
|
||||
clean:
|
||||
rm -f $(OBJDIR)/*.o
|
||||
rm -f $(APP_NAME)
|
||||
|
||||
### Main program compilation and assembly
|
||||
|
||||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
$(OBJDIR)/%.o: src/%.c | $(OBJDIR)
|
||||
$(CC) -c $(CFLAGS) $< -o $@
|
||||
|
||||
$(APP_NAME): $(OBJDIR)/$(APP_NAME).o
|
||||
$(CC) $< -o $@
|
||||
|
||||
### EOF
|
||||
Binary file not shown.
@@ -0,0 +1,65 @@
|
||||
/ _____) _ | |
|
||||
( (____ _____ ____ _| |_ _____ ____| |__
|
||||
\____ \| ___ | (_ _) ___ |/ ___) _ \
|
||||
_____) ) ____| | | || |_| ____( (___| | | |
|
||||
(______/|_____)_|_|_| \__)_____)\____)_| |_|
|
||||
(C)2013 Semtech-Cycleo
|
||||
|
||||
Utility: packet acknowledger
|
||||
=============================
|
||||
|
||||
1. Introduction
|
||||
----------------
|
||||
|
||||
The packet acknowledger is a simple helper program listening on a single UDP
|
||||
port and responding to PUSH_DATA datagrams with PUSH_ACK, and to PULL_DATA
|
||||
datagrams with PULL_ACK.
|
||||
|
||||
Informations about the datagrams received and the answers send are display on
|
||||
screen to help communication debugging.
|
||||
|
||||
Packets not following the protocol detailed in the PROTOCOL.TXT document in the
|
||||
basic_pkt_fwt directory are ignored.
|
||||
|
||||
2. Dependencies
|
||||
----------------
|
||||
|
||||
This program follows the v1.1 version of the gateway-to-server protocol.
|
||||
|
||||
3. Usage
|
||||
---------
|
||||
|
||||
Start the program with the port number as first and only argument.
|
||||
|
||||
To stop the application, press Ctrl+C.
|
||||
|
||||
4. License
|
||||
-----------
|
||||
|
||||
Copyright (C) 2013, SEMTECH S.A.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Semtech corporation nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL SEMTECH S.A. BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*EOF*
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
/ _____) _ | |
|
||||
( (____ _____ ____ _| |_ _____ ____| |__
|
||||
\____ \| ___ | (_ _) ___ |/ ___) _ \
|
||||
_____) ) ____| | | || |_| ____( (___| | | |
|
||||
(______/|_____)_|_|_| \__)_____)\____)_| |_|
|
||||
(C)2013 Semtech-Cycleo
|
||||
|
||||
Description:
|
||||
Network sink, receives UDP packets and sends an acknowledge
|
||||
|
||||
License: Revised BSD License, see LICENSE.TXT file include in the project
|
||||
Maintainer: Sylvain Miermont
|
||||
*/
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- DEPENDANCIES --------------------------------------------------------- */
|
||||
|
||||
/* fix an issue between POSIX and C99 */
|
||||
#if __STDC_VERSION__ >= 199901L
|
||||
#define _XOPEN_SOURCE 600
|
||||
#else
|
||||
#define _XOPEN_SOURCE 500
|
||||
#endif
|
||||
|
||||
#include <stdint.h> /* C99 types */
|
||||
#include <stdio.h> /* printf, fprintf, sprintf, fopen, fputs */
|
||||
#include <unistd.h> /* usleep */
|
||||
|
||||
#include <string.h> /* memset */
|
||||
#include <time.h> /* time, clock_gettime, strftime, gmtime, clock_nanosleep*/
|
||||
#include <stdlib.h> /* atoi, exit */
|
||||
#include <errno.h> /* error messages */
|
||||
|
||||
#include <sys/socket.h> /* socket specific definitions */
|
||||
#include <netinet/in.h> /* INET constants and stuff */
|
||||
#include <arpa/inet.h> /* IP address conversion stuff */
|
||||
#include <netdb.h> /* gai_strerror */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE MACROS ------------------------------------------------------- */
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
|
||||
#define STRINGIFY(x) #x
|
||||
#define STR(x) STRINGIFY(x)
|
||||
#define MSG(args...) fprintf(stderr, args) /* message that is destined to the user */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
|
||||
|
||||
#define PROTOCOL_VERSION 2
|
||||
|
||||
#define PKT_PUSH_DATA 0
|
||||
#define PKT_PUSH_ACK 1
|
||||
#define PKT_PULL_DATA 2
|
||||
#define PKT_PULL_RESP 3
|
||||
#define PKT_PULL_ACK 4
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- MAIN FUNCTION -------------------------------------------------------- */
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int i; /* loop variable and temporary variable for return value */
|
||||
|
||||
/* server socket creation */
|
||||
int sock; /* socket file descriptor */
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *result; /* store result of getaddrinfo */
|
||||
struct addrinfo *q; /* pointer to move into *result data */
|
||||
char host_name[64];
|
||||
char port_name[64];
|
||||
|
||||
/* variables for receiving and sending packets */
|
||||
struct sockaddr_storage dist_addr;
|
||||
socklen_t addr_len = sizeof dist_addr;
|
||||
uint8_t databuf[4096];
|
||||
int byte_nb;
|
||||
|
||||
/* variables for protocol management */
|
||||
uint32_t raw_mac_h; /* Most Significant Nibble, network order */
|
||||
uint32_t raw_mac_l; /* Least Significant Nibble, network order */
|
||||
uint64_t gw_mac; /* MAC address of the client (gateway) */
|
||||
uint8_t ack_command;
|
||||
|
||||
/* check if port number was passed as parameter */
|
||||
if (argc != 2) {
|
||||
MSG("Usage: util_ack <port number>\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* prepare hints to open network sockets */
|
||||
memset(&hints, 0, sizeof hints);
|
||||
hints.ai_family = AF_UNSPEC; /* should handle IP v4 or v6 automatically */
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_flags = AI_PASSIVE; /* will assign local IP automatically */
|
||||
|
||||
/* look for address */
|
||||
i = getaddrinfo(NULL, argv[1], &hints, &result);
|
||||
if (i != 0) {
|
||||
MSG("ERROR: getaddrinfo returned %s\n", gai_strerror(i));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* try to open socket and bind it */
|
||||
for (q=result; q!=NULL; q=q->ai_next) {
|
||||
sock = socket(q->ai_family, q->ai_socktype,q->ai_protocol);
|
||||
if (sock == -1) {
|
||||
continue; /* socket failed, try next field */
|
||||
} else {
|
||||
i = bind(sock, q->ai_addr, q->ai_addrlen);
|
||||
if (i == -1) {
|
||||
shutdown(sock, SHUT_RDWR);
|
||||
continue; /* bind failed, try next field */
|
||||
} else {
|
||||
break; /* success, get out of loop */
|
||||
}
|
||||
}
|
||||
}
|
||||
if (q == NULL) {
|
||||
MSG("ERROR: failed to open socket or to bind to it\n");
|
||||
i = 1;
|
||||
for (q=result; q!=NULL; q=q->ai_next) {
|
||||
getnameinfo(q->ai_addr, q->ai_addrlen, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST);
|
||||
MSG("INFO: result %i host:%s service:%s\n", i, host_name, port_name);
|
||||
++i;
|
||||
}
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
MSG("INFO: util_ack listening on port %s\n", argv[1]);
|
||||
freeaddrinfo(result);
|
||||
|
||||
while (1) {
|
||||
/* wait to receive a packet */
|
||||
byte_nb = recvfrom(sock, databuf, sizeof databuf, 0, (struct sockaddr *)&dist_addr, &addr_len);
|
||||
if (byte_nb == -1) {
|
||||
MSG("ERROR: recvfrom returned %s \n", strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* display info about the sender */
|
||||
i = getnameinfo((struct sockaddr *)&dist_addr, addr_len, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST);
|
||||
if (i == -1) {
|
||||
MSG("ERROR: getnameinfo returned %s \n", gai_strerror(i));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
printf(" -> pkt in , host %s (port %s), %i bytes", host_name, port_name, byte_nb);
|
||||
|
||||
/* check and parse the payload */
|
||||
if (byte_nb < 12) { /* not enough bytes for packet from gateway */
|
||||
printf(" (too short for GW <-> MAC protocol)\n");
|
||||
continue;
|
||||
}
|
||||
/* don't touch the token in position 1-2, it will be sent back "as is" for acknowledgement */
|
||||
if (databuf[0] != PROTOCOL_VERSION) { /* check protocol version number */
|
||||
printf(", invalid version %u\n", databuf[0]);
|
||||
continue;
|
||||
}
|
||||
raw_mac_h = *((uint32_t *)(databuf+4));
|
||||
raw_mac_l = *((uint32_t *)(databuf+8));
|
||||
gw_mac = ((uint64_t)ntohl(raw_mac_h) << 32) + (uint64_t)ntohl(raw_mac_l);
|
||||
|
||||
/* interpret gateway command */
|
||||
switch (databuf[3]) {
|
||||
case PKT_PUSH_DATA:
|
||||
printf(", PUSH_DATA from gateway 0x%08X%08X\n", (uint32_t)(gw_mac >> 32), (uint32_t)(gw_mac & 0xFFFFFFFF));
|
||||
ack_command = PKT_PUSH_ACK;
|
||||
printf("<- pkt out, PUSH_ACK for host %s (port %s)", host_name, port_name);
|
||||
break;
|
||||
case PKT_PULL_DATA:
|
||||
printf(", PULL_DATA from gateway 0x%08X%08X\n", (uint32_t)(gw_mac >> 32), (uint32_t)(gw_mac & 0xFFFFFFFF));
|
||||
ack_command = PKT_PULL_ACK;
|
||||
printf("<- pkt out, PULL_ACK for host %s (port %s)", host_name, port_name);
|
||||
break;
|
||||
default:
|
||||
printf(", unexpected command %u\n", databuf[3]);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* add some artificial latency */
|
||||
usleep(30000); /* 30 ms */
|
||||
|
||||
/* send acknowledge and check return value */
|
||||
databuf[3] = ack_command;
|
||||
byte_nb = sendto(sock, (void *)databuf, 4, 0, (struct sockaddr *)&dist_addr, addr_len);
|
||||
if (byte_nb == -1) {
|
||||
printf(", send error:%s\n", strerror(errno));
|
||||
} else {
|
||||
printf(", %i bytes sent\n", byte_nb);
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
### Application-specific constants
|
||||
|
||||
APP_NAME := util_sink
|
||||
|
||||
### Constant symbols
|
||||
|
||||
CC := $(CROSS_COMPILE)gcc
|
||||
AR := $(CROSS_COMPILE)ar
|
||||
|
||||
CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I.
|
||||
|
||||
OBJDIR = obj
|
||||
|
||||
### General build targets
|
||||
|
||||
all: $(APP_NAME)
|
||||
|
||||
clean:
|
||||
rm -f $(OBJDIR)/*.o
|
||||
rm -f $(APP_NAME)
|
||||
|
||||
### Main program compilation and assembly
|
||||
|
||||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
$(OBJDIR)/%.o: src/%.c | $(OBJDIR)
|
||||
$(CC) -c $(CFLAGS) $< -o $@
|
||||
|
||||
$(APP_NAME): $(OBJDIR)/$(APP_NAME).o
|
||||
$(CC) $< -o $@
|
||||
|
||||
### EOF
|
||||
Binary file not shown.
@@ -0,0 +1,62 @@
|
||||
/ _____) _ | |
|
||||
( (____ _____ ____ _| |_ _____ ____| |__
|
||||
\____ \| ___ | (_ _) ___ |/ ___) _ \
|
||||
_____) ) ____| | | || |_| ____( (___| | | |
|
||||
(______/|_____)_|_|_| \__)_____)\____)_| |_|
|
||||
(C)2013 Semtech-Cycleo
|
||||
|
||||
Utility: packet sink
|
||||
=====================
|
||||
|
||||
1. Introduction
|
||||
----------------
|
||||
|
||||
The packet sink is a simple helper program listening on a single port for UDP
|
||||
datagrams, and displaying a message each time one is received. The content of
|
||||
the datagram itself is ignored.
|
||||
|
||||
This allow to test another software (locally or on another computer) that
|
||||
sends UDP datagrams without having ICMP 'port closed' errors each time.
|
||||
|
||||
2. Dependencies
|
||||
----------------
|
||||
|
||||
None.
|
||||
|
||||
3. Usage
|
||||
---------
|
||||
|
||||
Start the program with the port number as first and only argument.
|
||||
|
||||
To stop the application, press Ctrl+C.
|
||||
|
||||
4. License
|
||||
-----------
|
||||
|
||||
Copyright (C) 2013, SEMTECH S.A.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Semtech corporation nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL SEMTECH S.A. BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*EOF*
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
/ _____) _ | |
|
||||
( (____ _____ ____ _| |_ _____ ____| |__
|
||||
\____ \| ___ | (_ _) ___ |/ ___) _ \
|
||||
_____) ) ____| | | || |_| ____( (___| | | |
|
||||
(______/|_____)_|_|_| \__)_____)\____)_| |_|
|
||||
(C)2013 Semtech-Cycleo
|
||||
|
||||
Description:
|
||||
Network sink, receives UDP packets on certain ports and discards them
|
||||
|
||||
License: Revised BSD License, see LICENSE.TXT file include in the project
|
||||
Maintainer: Sylvain Miermont
|
||||
*/
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- DEPENDANCIES --------------------------------------------------------- */
|
||||
|
||||
/* fix an issue between POSIX and C99 */
|
||||
#if __STDC_VERSION__ >= 199901L
|
||||
#define _XOPEN_SOURCE 600
|
||||
#else
|
||||
#define _XOPEN_SOURCE 500
|
||||
#endif
|
||||
|
||||
#include <stdint.h> /* C99 types */
|
||||
#include <stdio.h> /* printf, fprintf, sprintf, fopen, fputs */
|
||||
|
||||
#include <string.h> /* memset */
|
||||
#include <time.h> /* time, clock_gettime, strftime, gmtime, clock_nanosleep*/
|
||||
#include <stdlib.h> /* atoi, exit */
|
||||
#include <errno.h> /* error messages */
|
||||
|
||||
#include <sys/socket.h> /* socket specific definitions */
|
||||
#include <netinet/in.h> /* INET constants and stuff */
|
||||
#include <arpa/inet.h> /* IP address conversion stuff */
|
||||
#include <netdb.h> /* gai_strerror */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE MACROS ------------------------------------------------------- */
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
|
||||
#define STRINGIFY(x) #x
|
||||
#define STR(x) STRINGIFY(x)
|
||||
#define MSG(args...) fprintf(stderr, args) /* message that is destined to the user */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- MAIN FUNCTION -------------------------------------------------------- */
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int i; /* loop variable and temporary variable for return value */
|
||||
|
||||
/* server socket creation */
|
||||
int sock; /* socket file descriptor */
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *result; /* store result of getaddrinfo */
|
||||
struct addrinfo *q; /* pointer to move into *result data */
|
||||
char host_name[64];
|
||||
char port_name[64];
|
||||
|
||||
/* variables for receiving packets */
|
||||
struct sockaddr_storage dist_addr;
|
||||
socklen_t addr_len = sizeof dist_addr;
|
||||
uint8_t databuf[4096];
|
||||
int byte_nb;
|
||||
|
||||
/* check if port number was passed as parameter */
|
||||
if (argc != 2) {
|
||||
MSG("Usage: util_sink <port number>\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* prepare hints to open network sockets */
|
||||
memset(&hints, 0, sizeof hints);
|
||||
hints.ai_family = AF_UNSPEC; /* should handle IP v4 or v6 automatically */
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_flags = AI_PASSIVE; /* will assign local IP automatically */
|
||||
|
||||
/* look for address */
|
||||
i = getaddrinfo(NULL, argv[1], &hints, &result);
|
||||
if (i != 0) {
|
||||
MSG("ERROR: getaddrinfo returned %s\n", gai_strerror(i));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* try to open socket and bind it */
|
||||
for (q=result; q!=NULL; q=q->ai_next) {
|
||||
sock = socket(q->ai_family, q->ai_socktype,q->ai_protocol);
|
||||
if (sock == -1) {
|
||||
continue; /* socket failed, try next field */
|
||||
} else {
|
||||
i = bind(sock, q->ai_addr, q->ai_addrlen);
|
||||
if (i == -1) {
|
||||
shutdown(sock, SHUT_RDWR);
|
||||
continue; /* bind failed, try next field */
|
||||
} else {
|
||||
break; /* success, get out of loop */
|
||||
}
|
||||
}
|
||||
}
|
||||
if (q == NULL) {
|
||||
MSG("ERROR: failed to open socket or to bind to it\n");
|
||||
i = 1;
|
||||
for (q=result; q!=NULL; q=q->ai_next) {
|
||||
getnameinfo(q->ai_addr, q->ai_addrlen, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST);
|
||||
MSG("result %i host:%s service:%s\n", i, host_name, port_name);
|
||||
++i;
|
||||
}
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
MSG("INFO: util_sink listening on port %s\n", argv[1]);
|
||||
freeaddrinfo(result);
|
||||
|
||||
while (1) {
|
||||
byte_nb = recvfrom(sock, databuf, sizeof databuf, 0, (struct sockaddr *)&dist_addr, &addr_len);
|
||||
if (byte_nb == -1) {
|
||||
MSG("ERROR: recvfrom returned %s \n", strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
getnameinfo((struct sockaddr *)&dist_addr, addr_len, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST);
|
||||
printf("Got packet from host %s port %s, %i bytes long\n", host_name, port_name, byte_nb);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,36 @@
|
||||
### Application-specific constants
|
||||
|
||||
APP_NAME := util_tx_test
|
||||
|
||||
### Constant symbols
|
||||
|
||||
CC := $(CROSS_COMPILE)gcc
|
||||
AR := $(CROSS_COMPILE)ar
|
||||
|
||||
CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I.
|
||||
|
||||
OBJDIR = obj
|
||||
INCLUDES = $(wildcard inc/*.h)
|
||||
|
||||
### General build targets
|
||||
|
||||
all: $(APP_NAME)
|
||||
|
||||
clean:
|
||||
rm -f $(OBJDIR)/*.o
|
||||
rm -f $(APP_NAME)
|
||||
|
||||
### Sub-modules compilation
|
||||
|
||||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
$(OBJDIR)/%.o: src/%.c $(INCLUDES) | $(OBJDIR)
|
||||
$(CC) -c $(CFLAGS) $< -o $@
|
||||
|
||||
### Main program assembly
|
||||
|
||||
$(APP_NAME): $(OBJDIR)/$(APP_NAME).o $(OBJDIR)/base64.o
|
||||
$(CC) $< $(OBJDIR)/base64.o -o $@
|
||||
|
||||
### EOF
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
/ _____) _ | |
|
||||
( (____ _____ ____ _| |_ _____ ____| |__
|
||||
\____ \| ___ | (_ _) ___ |/ ___) _ \
|
||||
_____) ) ____| | | || |_| ____( (___| | | |
|
||||
(______/|_____)_|_|_| \__)_____)\____)_| |_|
|
||||
(C)2013 Semtech-Cycleo
|
||||
|
||||
Description:
|
||||
Base64 encoding & decoding library
|
||||
|
||||
License: Revised BSD License, see LICENSE.TXT file include in the project
|
||||
Maintainer: Sylvain Miermont
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _BASE64_H
|
||||
#define _BASE64_H
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- DEPENDANCIES --------------------------------------------------------- */
|
||||
|
||||
#include <stdint.h> /* C99 types */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */
|
||||
|
||||
/**
|
||||
@brief Encode binary data in Base64 string (no padding)
|
||||
@param in pointer to a table of binary data
|
||||
@param size number of bytes to be encoded to base64
|
||||
@param out pointer to a string where the function will output encoded data
|
||||
@param max_len max length of the out string (including null char)
|
||||
@return >=0 length of the resulting string (w/o null char), -1 for error
|
||||
*/
|
||||
int bin_to_b64_nopad(const uint8_t * in, int size, char * out, int max_len);
|
||||
|
||||
/**
|
||||
@brief Decode Base64 string to binary data (no padding)
|
||||
@param in string containing only base64 valid characters
|
||||
@param size number of characters to be decoded from base64 (w/o null char)
|
||||
@param out pointer to a data buffer where the function will output decoded data
|
||||
@param out_max_len usable size of the output data buffer
|
||||
@return >=0 number of bytes written to the data buffer, -1 for error
|
||||
*/
|
||||
int b64_to_bin_nopad(const char * in, int size, uint8_t * out, int max_len);
|
||||
|
||||
/* === derivative functions === */
|
||||
|
||||
/**
|
||||
@brief Encode binary data in Base64 string (with added padding)
|
||||
*/
|
||||
int bin_to_b64(const uint8_t * in, int size, char * out, int max_len);
|
||||
|
||||
/**
|
||||
@brief Decode Base64 string to binary data (remove padding if necessary)
|
||||
*/
|
||||
int b64_to_bin(const char * in, int size, uint8_t * out, int max_len);
|
||||
|
||||
#endif
|
||||
|
||||
/* --- EOF ------------------------------------------------------------------ */
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,75 @@
|
||||
/ _____) _ | |
|
||||
( (____ _____ ____ _| |_ _____ ____| |__
|
||||
\____ \| ___ | (_ _) ___ |/ ___) _ \
|
||||
_____) ) ____| | | || |_| ____( (___| | | |
|
||||
(______/|_____)_|_|_| \__)_____)\____)_| |_|
|
||||
(C)2013 Semtech-Cycleo
|
||||
|
||||
Utility: network packet sender
|
||||
===============================
|
||||
|
||||
1. Introduction
|
||||
----------------
|
||||
|
||||
The network packet sender is a simple helper program used to send packets
|
||||
through the gateway-to-server downlink route.
|
||||
|
||||
The program start by waiting for a gateway to send it a PULL_DATA datagram.
|
||||
After that, it will send back to the gateway a specified amount of PULL_RESP
|
||||
datagrams, each containing a packet to be sent immediately and a variable
|
||||
payload.
|
||||
|
||||
2. Dependencies
|
||||
----------------
|
||||
|
||||
This program follows the v1.1 version of the gateway-to-server protocol.
|
||||
|
||||
3. Usage
|
||||
---------
|
||||
|
||||
The application runs until the specified number of packets have been sent.
|
||||
Press Ctrl+C to stop the application before that.
|
||||
|
||||
Use the -h option to get help and details about available options.
|
||||
|
||||
The packets are [9-n] bytes long, and have following payload content:
|
||||
+----------+---------------+---------------+---------------+---------------+---+---+---+---+---+---+---+---+
|
||||
| Id | PktCnt[31:24] | PktCnt[23:16] | PktCnt[15:8] | PktCnt[7:0] | P | E | R |FCS| 0 | 1 |...| n |
|
||||
+----------+---------------+---------------+---------------+---------------+---+---+---+---+---+---+---+---+
|
||||
|
||||
Id : User defined ID to differentiate sender at receiver side. (8 bits)
|
||||
PktCnt : Packet counter incremented at each transmission. (32 bits)
|
||||
‘P’, ‘E’, ‘R’ : ASCII values for characters 'P', 'E' and 'R'.
|
||||
FCS : Checksum: 8-bits sum of Id, PktCnt[31 :24] , PktCnt[23 :16] , PktCnt[15 :8] , PktCnt[7:0], ‘P’,’E’,’R’
|
||||
0,1, ..., n : Padding bytes up until user specified payload length.
|
||||
|
||||
4. License
|
||||
-----------
|
||||
|
||||
Copyright (C) 2013, SEMTECH S.A.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Semtech corporation nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL SEMTECH S.A. BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*EOF*
|
||||
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
/ _____) _ | |
|
||||
( (____ _____ ____ _| |_ _____ ____| |__
|
||||
\____ \| ___ | (_ _) ___ |/ ___) _ \
|
||||
_____) ) ____| | | || |_| ____( (___| | | |
|
||||
(______/|_____)_|_|_| \__)_____)\____)_| |_|
|
||||
(C)2013 Semtech-Cycleo
|
||||
|
||||
Description:
|
||||
Base64 encoding & decoding library
|
||||
|
||||
License: Revised BSD License, see LICENSE.TXT file include in the project
|
||||
Maintainer: Sylvain Miermont
|
||||
*/
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- DEPENDANCIES --------------------------------------------------------- */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE MACROS ------------------------------------------------------- */
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
|
||||
#define CRIT(a) fprintf(stderr, "\nCRITICAL file:%s line:%u msg:%s\n", __FILE__, __LINE__,a);exit(EXIT_FAILURE)
|
||||
|
||||
//#define DEBUG(args...) fprintf(stderr,"debug: " args) /* diagnostic message that is destined to the user */
|
||||
#define DEBUG(args...)
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE MODULE-WIDE VARIABLES ---------------------------------------- */
|
||||
|
||||
static char code_62 = '+'; /* RFC 1421 standard character for code 62 */
|
||||
static char code_63 = '/'; /* RFC 1421 standard character for code 63 */
|
||||
static char code_pad = '='; /* RFC 1421 padding character if padding */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */
|
||||
|
||||
/**
|
||||
@brief Convert a code in the range 0-63 to an ASCII character
|
||||
*/
|
||||
char code_to_char(uint8_t x);
|
||||
|
||||
/**
|
||||
@brief Convert an ASCII character to a code in the range 0-63
|
||||
*/
|
||||
uint8_t char_to_code(char x);
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
|
||||
|
||||
char code_to_char(uint8_t x) {
|
||||
if (x <= 25) {
|
||||
return 'A' + x;
|
||||
} else if ((x >= 26) && (x <= 51)) {
|
||||
return 'a' + (x-26);
|
||||
} else if ((x >= 52) && (x <= 61)) {
|
||||
return '0' + (x-52);
|
||||
} else if (x == 62) {
|
||||
return code_62;
|
||||
} else if (x == 63) {
|
||||
return code_63;
|
||||
} else {
|
||||
DEBUG("ERROR: %i IS OUT OF RANGE 0-63 FOR BASE64 ENCODING\n", x);
|
||||
exit(EXIT_FAILURE);
|
||||
} //TODO: improve error management
|
||||
}
|
||||
|
||||
uint8_t char_to_code(char x) {
|
||||
if ((x >= 'A') && (x <= 'Z')) {
|
||||
return (uint8_t)x - (uint8_t)'A';
|
||||
} else if ((x >= 'a') && (x <= 'z')) {
|
||||
return (uint8_t)x - (uint8_t)'a' + 26;
|
||||
} else if ((x >= '0') && (x <= '9')) {
|
||||
return (uint8_t)x - (uint8_t)'0' + 52;
|
||||
} else if (x == code_62) {
|
||||
return 62;
|
||||
} else if (x == code_63) {
|
||||
return 63;
|
||||
} else {
|
||||
DEBUG("ERROR: %c (0x%x) IS INVALID CHARACTER FOR BASE64 DECODING\n", x, x);
|
||||
exit(EXIT_FAILURE);
|
||||
} //TODO: improve error management
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */
|
||||
|
||||
int bin_to_b64_nopad(const uint8_t * in, int size, char * out, int max_len) {
|
||||
int i;
|
||||
int result_len; /* size of the result */
|
||||
int full_blocks; /* number of 3 unsigned chars / 4 characters blocks */
|
||||
int last_bytes; /* number of unsigned chars <3 in the last block */
|
||||
int last_chars; /* number of characters <4 in the last block */
|
||||
uint32_t b;
|
||||
|
||||
/* check input values */
|
||||
if ((out == NULL) || (in == NULL)) {
|
||||
DEBUG("ERROR: NULL POINTER AS OUTPUT IN BIN_TO_B64\n");
|
||||
return -1;
|
||||
}
|
||||
if (size == 0) {
|
||||
*out = 0; /* null string */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* calculate the number of base64 'blocks' */
|
||||
full_blocks = size / 3;
|
||||
last_bytes = size % 3;
|
||||
switch (last_bytes) {
|
||||
case 0: /* no byte left to encode */
|
||||
last_chars = 0;
|
||||
break;
|
||||
case 1: /* 1 byte left to encode -> +2 chars */
|
||||
last_chars = 2;
|
||||
break;
|
||||
case 2: /* 2 bytes left to encode -> +3 chars */
|
||||
last_chars = 3;
|
||||
break;
|
||||
default:
|
||||
CRIT("switch default that should not be possible");
|
||||
}
|
||||
|
||||
/* check if output buffer is big enough */
|
||||
result_len = (4*full_blocks) + last_chars;
|
||||
if (max_len < (result_len + 1)) { /* 1 char added for string terminator */
|
||||
DEBUG("ERROR: OUTPUT BUFFER TOO SMALL IN BIN_TO_B64\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* process all the full blocks */
|
||||
for (i=0; i < full_blocks; ++i) {
|
||||
b = (0xFF & in[3*i] ) << 16;
|
||||
b |= (0xFF & in[3*i + 1]) << 8;
|
||||
b |= 0xFF & in[3*i + 2];
|
||||
out[4*i + 0] = code_to_char((b >> 18) & 0x3F);
|
||||
out[4*i + 1] = code_to_char((b >> 12) & 0x3F);
|
||||
out[4*i + 2] = code_to_char((b >> 6 ) & 0x3F);
|
||||
out[4*i + 3] = code_to_char( b & 0x3F);
|
||||
}
|
||||
|
||||
/* process the last 'partial' block and terminate string */
|
||||
i = full_blocks;
|
||||
if (last_chars == 0) {
|
||||
out[4*i] = 0; /* null character to terminate string */
|
||||
} else if (last_chars == 2) {
|
||||
b = (0xFF & in[3*i] ) << 16;
|
||||
out[4*i + 0] = code_to_char((b >> 18) & 0x3F);
|
||||
out[4*i + 1] = code_to_char((b >> 12) & 0x3F);
|
||||
out[4*i + 2] = 0; /* null character to terminate string */
|
||||
} else if (last_chars == 3) {
|
||||
b = (0xFF & in[3*i] ) << 16;
|
||||
b |= (0xFF & in[3*i + 1]) << 8;
|
||||
out[4*i + 0] = code_to_char((b >> 18) & 0x3F);
|
||||
out[4*i + 1] = code_to_char((b >> 12) & 0x3F);
|
||||
out[4*i + 2] = code_to_char((b >> 6 ) & 0x3F);
|
||||
out[4*i + 3] = 0; /* null character to terminate string */
|
||||
}
|
||||
|
||||
return result_len;
|
||||
}
|
||||
|
||||
int b64_to_bin_nopad(const char * in, int size, uint8_t * out, int max_len) {
|
||||
int i;
|
||||
int result_len; /* size of the result */
|
||||
int full_blocks; /* number of 3 unsigned chars / 4 characters blocks */
|
||||
int last_chars; /* number of characters <4 in the last block */
|
||||
int last_bytes; /* number of unsigned chars <3 in the last block */
|
||||
uint32_t b;
|
||||
;
|
||||
|
||||
/* check input values */
|
||||
if ((out == NULL) || (in == NULL)) {
|
||||
DEBUG("ERROR: NULL POINTER AS OUTPUT OR INPUT IN B64_TO_BIN\n");
|
||||
return -1;
|
||||
}
|
||||
if (size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* calculate the number of base64 'blocks' */
|
||||
full_blocks = size / 4;
|
||||
last_chars = size % 4;
|
||||
switch (last_chars) {
|
||||
case 0: /* no char left to decode */
|
||||
last_bytes = 0;
|
||||
break;
|
||||
case 1: /* only 1 char left is an error */
|
||||
DEBUG("ERROR: ONLY ONE CHAR LEFT IN B64_TO_BIN\n");
|
||||
return -1;
|
||||
case 2: /* 2 chars left to decode -> +1 byte */
|
||||
last_bytes = 1;
|
||||
break;
|
||||
case 3: /* 3 chars left to decode -> +2 bytes */
|
||||
last_bytes = 2;
|
||||
break;
|
||||
default:
|
||||
CRIT("switch default that should not be possible");
|
||||
}
|
||||
|
||||
/* check if output buffer is big enough */
|
||||
result_len = (3*full_blocks) + last_bytes;
|
||||
if (max_len < result_len) {
|
||||
DEBUG("ERROR: OUTPUT BUFFER TOO SMALL IN B64_TO_BIN\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* process all the full blocks */
|
||||
for (i=0; i < full_blocks; ++i) {
|
||||
b = (0x3F & char_to_code(in[4*i] )) << 18;
|
||||
b |= (0x3F & char_to_code(in[4*i + 1])) << 12;
|
||||
b |= (0x3F & char_to_code(in[4*i + 2])) << 6;
|
||||
b |= 0x3F & char_to_code(in[4*i + 3]);
|
||||
out[3*i + 0] = (b >> 16) & 0xFF;
|
||||
out[3*i + 1] = (b >> 8 ) & 0xFF;
|
||||
out[3*i + 2] = b & 0xFF;
|
||||
}
|
||||
|
||||
/* process the last 'partial' block */
|
||||
i = full_blocks;
|
||||
if (last_bytes == 1) {
|
||||
b = (0x3F & char_to_code(in[4*i] )) << 18;
|
||||
b |= (0x3F & char_to_code(in[4*i + 1])) << 12;
|
||||
out[3*i + 0] = (b >> 16) & 0xFF;
|
||||
if (((b >> 12) & 0x0F) != 0) {
|
||||
DEBUG("WARNING: last character contains unusable bits\n");
|
||||
}
|
||||
} else if (last_bytes == 2) {
|
||||
b = (0x3F & char_to_code(in[4*i] )) << 18;
|
||||
b |= (0x3F & char_to_code(in[4*i + 1])) << 12;
|
||||
b |= (0x3F & char_to_code(in[4*i + 2])) << 6;
|
||||
out[3*i + 0] = (b >> 16) & 0xFF;
|
||||
out[3*i + 1] = (b >> 8 ) & 0xFF;
|
||||
if (((b >> 6) & 0x03) != 0) {
|
||||
DEBUG("WARNING: last character contains unusable bits\n");
|
||||
}
|
||||
}
|
||||
|
||||
return result_len;
|
||||
}
|
||||
|
||||
int bin_to_b64(const uint8_t * in, int size, char * out, int max_len) {
|
||||
int ret;
|
||||
|
||||
ret = bin_to_b64_nopad(in, size, out, max_len);
|
||||
|
||||
if (ret == -1) {
|
||||
return -1;
|
||||
}
|
||||
switch (ret%4) {
|
||||
case 0: /* nothing to do */
|
||||
return ret;
|
||||
case 1:
|
||||
DEBUG("ERROR: INVALID UNPADDED BASE64 STRING\n");
|
||||
return -1;
|
||||
case 2: /* 2 chars in last block, must add 2 padding char */
|
||||
if (max_len >= (ret + 2 + 1)) {
|
||||
out[ret] = code_pad;
|
||||
out[ret+1] = code_pad;
|
||||
out[ret+2] = 0;
|
||||
return ret+2;
|
||||
} else {
|
||||
DEBUG("ERROR: not enough room to add padding in bin_to_b64\n");
|
||||
return -1;
|
||||
}
|
||||
case 3: /* 3 chars in last block, must add 1 padding char */
|
||||
if (max_len >= (ret + 1 + 1)) {
|
||||
out[ret] = code_pad;
|
||||
out[ret+1] = 0;
|
||||
return ret+1;
|
||||
} else {
|
||||
DEBUG("ERROR: not enough room to add padding in bin_to_b64\n");
|
||||
return -1;
|
||||
}
|
||||
default:
|
||||
CRIT("switch default that should not be possible");
|
||||
}
|
||||
}
|
||||
|
||||
int b64_to_bin(const char * in, int size, uint8_t * out, int max_len) {
|
||||
if (in == NULL) {
|
||||
DEBUG("ERROR: NULL POINTER AS OUTPUT OR INPUT IN B64_TO_BIN\n");
|
||||
return -1;
|
||||
}
|
||||
if ((size%4 == 0) && (size >= 4)) { /* potentially padded Base64 */
|
||||
if (in[size-2] == code_pad) { /* 2 padding char to ignore */
|
||||
return b64_to_bin_nopad(in, size-2, out, max_len);
|
||||
} else if (in[size-1] == code_pad) { /* 1 padding char to ignore */
|
||||
return b64_to_bin_nopad(in, size-1, out, max_len);
|
||||
} else { /* no padding to ignore */
|
||||
return b64_to_bin_nopad(in, size, out, max_len);
|
||||
}
|
||||
} else { /* treat as unpadded Base64 */
|
||||
return b64_to_bin_nopad(in, size, out, max_len);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* --- EOF ------------------------------------------------------------------ */
|
||||
@@ -0,0 +1,500 @@
|
||||
/*
|
||||
/ _____) _ | |
|
||||
( (____ _____ ____ _| |_ _____ ____| |__
|
||||
\____ \| ___ | (_ _) ___ |/ ___) _ \
|
||||
_____) ) ____| | | || |_| ____( (___| | | |
|
||||
(______/|_____)_|_|_| \__)_____)\____)_| |_|
|
||||
(C)2013 Semtech-Cycleo
|
||||
|
||||
Description:
|
||||
Ask a gateway to emit packets using GW <-> server protocol
|
||||
|
||||
License: Revised BSD License, see LICENSE.TXT file include in the project
|
||||
Maintainer: Sylvain Miermont
|
||||
*/
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- DEPENDANCIES --------------------------------------------------------- */
|
||||
|
||||
/* fix an issue between POSIX and C99 */
|
||||
#if __STDC_VERSION__ >= 199901L
|
||||
#define _XOPEN_SOURCE 600
|
||||
#else
|
||||
#define _XOPEN_SOURCE 500
|
||||
#endif
|
||||
|
||||
#include <stdint.h> /* C99 types */
|
||||
#include <stdbool.h> /* bool type */
|
||||
#include <stdio.h> /* printf fprintf sprintf fopen fputs */
|
||||
#include <unistd.h> /* getopt access usleep */
|
||||
|
||||
#include <string.h> /* memset */
|
||||
#include <signal.h> /* sigaction */
|
||||
#include <stdlib.h> /* exit codes */
|
||||
#include <errno.h> /* error messages */
|
||||
|
||||
#include <sys/socket.h> /* socket specific definitions */
|
||||
#include <netinet/in.h> /* INET constants and stuff */
|
||||
#include <arpa/inet.h> /* IP address conversion stuff */
|
||||
#include <netdb.h> /* gai_strerror */
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE MACROS ------------------------------------------------------- */
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
|
||||
#define MSG(args...) fprintf(stderr, args) /* message that is destined to the user */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
|
||||
|
||||
#define PROTOCOL_VERSION 2
|
||||
|
||||
#define PKT_PUSH_DATA 0
|
||||
#define PKT_PUSH_ACK 1
|
||||
#define PKT_PULL_DATA 2
|
||||
#define PKT_PULL_RESP 3
|
||||
#define PKT_PULL_ACK 4
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */
|
||||
|
||||
/* signal handling variables */
|
||||
struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */
|
||||
static int exit_sig = 0; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */
|
||||
static int quit_sig = 0; /* 1 -> application terminates without shutting down the hardware */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */
|
||||
|
||||
static void sig_handler(int sigio);
|
||||
|
||||
void usage (void);
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
|
||||
|
||||
static void sig_handler(int sigio) {
|
||||
if (sigio == SIGQUIT) {
|
||||
quit_sig = 1;;
|
||||
} else if ((sigio == SIGINT) || (sigio == SIGTERM)) {
|
||||
exit_sig = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* describe command line options */
|
||||
void usage(void) {
|
||||
MSG("Usage: util_tx_test {options}\n");
|
||||
MSG("Available options:\n");
|
||||
MSG(" -h print this help\n");
|
||||
MSG(" -n <int or service> port number for gateway link\n");
|
||||
MSG(" -f <float> target frequency in MHz\n");
|
||||
MSG(" -m <str> Modulation type ['LORA, 'FSK']\n");
|
||||
MSG(" -s <int> Spreading Factor [7:12]\n");
|
||||
MSG(" -b <int> Modulation bandwidth in kHz [125,250,500]\n");
|
||||
MSG(" -d <uint> FSK frequency deviation in kHz [1:250]\n");
|
||||
MSG(" -r <float> FSK bitrate in kbps [0.5:250]\n");
|
||||
MSG(" -p <int> RF power (dBm)\n");
|
||||
MSG(" -z <uint> Payload size in bytes [9:255]\n");
|
||||
MSG(" -t <int> pause between packets (ms)\n");
|
||||
MSG(" -x <int> numbers of times the sequence is repeated\n");
|
||||
MSG(" -v <uint> test ID, inserted in payload for PER test [0:255]\n");
|
||||
MSG(" -i send packet using inverted modulation polarity \n");
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* --- MAIN FUNCTION -------------------------------------------------------- */
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int i, j, x;
|
||||
unsigned int xu;
|
||||
char arg_s[64];
|
||||
|
||||
/* application parameters */
|
||||
char mod[64] = "LORA"; /* LoRa modulation by default */
|
||||
float f_target = 866.0; /* target frequency */
|
||||
int sf = 10; /* SF10 by default */
|
||||
int bw = 125; /* 125kHz bandwidth by default */
|
||||
int pow = 14; /* 14 dBm by default */
|
||||
int delay = 1000; /* 1 second between packets by default */
|
||||
int repeat = 1; /* sweep only once by default */
|
||||
bool invert = false;
|
||||
float br_kbps = 50; /* 50 kbps by default */
|
||||
uint8_t fdev_khz = 25; /* 25 khz by default */
|
||||
|
||||
/* packet payload variables */
|
||||
int payload_size = 9; /* minimum size for PER frame */
|
||||
uint8_t payload_bin[255];
|
||||
char payload_b64[341];
|
||||
int payload_index;
|
||||
|
||||
/* PER payload variables */
|
||||
uint8_t id = 0;
|
||||
|
||||
/* server socket creation */
|
||||
int sock; /* socket file descriptor */
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *result; /* store result of getaddrinfo */
|
||||
struct addrinfo *q; /* pointer to move into *result data */
|
||||
char serv_port[8] = "1680";
|
||||
char host_name[64];
|
||||
char port_name[64];
|
||||
|
||||
/* variables for receiving and sending packets */
|
||||
struct sockaddr_storage dist_addr;
|
||||
socklen_t addr_len = sizeof dist_addr;
|
||||
uint8_t databuf[500];
|
||||
int buff_index;
|
||||
int byte_nb;
|
||||
|
||||
/* variables for gateway identification */
|
||||
uint32_t raw_mac_h; /* Most Significant Nibble, network order */
|
||||
uint32_t raw_mac_l; /* Least Significant Nibble, network order */
|
||||
uint64_t gw_mac; /* MAC address of the client (gateway) */
|
||||
|
||||
/* prepare hints to open network sockets */
|
||||
memset(&hints, 0, sizeof hints);
|
||||
hints.ai_family = AF_UNSPEC; /* should handle IP v4 or v6 automatically */
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_flags = AI_PASSIVE; /* will assign local IP automatically */
|
||||
|
||||
/* parse command line options */
|
||||
while ((i = getopt (argc, argv, "hn:f:m:s:b:d:r:p:z:t:x:v:i")) != -1) {
|
||||
switch (i) {
|
||||
case 'h':
|
||||
usage();
|
||||
return EXIT_FAILURE;
|
||||
break;
|
||||
|
||||
case 'n': /* -n <int or service> port number for gateway link */
|
||||
strncpy(serv_port, optarg, sizeof serv_port);
|
||||
break;
|
||||
|
||||
case 'f': /* -f <float> target frequency in MHz */
|
||||
i = sscanf(optarg, "%f", &f_target);
|
||||
if ((i != 1) || (f_target < 30.0) || (f_target > 3000.0)) {
|
||||
MSG("ERROR: invalid TX frequency\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'm': /* -m <str> Modulation type */
|
||||
i = sscanf(optarg, "%s", arg_s);
|
||||
if ((i != 1) || ((strcmp(arg_s, "LORA") != 0) && (strcmp(arg_s, "FSK")))) {
|
||||
MSG("ERROR: invalid modulation type\n");
|
||||
usage();
|
||||
return EXIT_FAILURE;
|
||||
} else {
|
||||
sprintf(mod, "%s", arg_s);
|
||||
}
|
||||
break;
|
||||
|
||||
case 's': /* -s <int> Spreading Factor */
|
||||
i = sscanf(optarg, "%i", &sf);
|
||||
if ((i != 1) || (sf < 7) || (sf > 12)) {
|
||||
MSG("ERROR: invalid spreading factor\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'b': /* -b <int> Modulation bandwidth in kHz */
|
||||
i = sscanf(optarg, "%i", &bw);
|
||||
if ((i != 1) || ((bw != 125) && (bw != 250) && (bw != 500))) {
|
||||
MSG("ERROR: invalid LORA bandwidth\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'd': /* -d <uint> FSK frequency deviation */
|
||||
i = sscanf(optarg, "%u", &xu);
|
||||
if ((i != 1) || (xu < 1) || (xu > 250)) {
|
||||
MSG("ERROR: invalid FSK frequency deviation\n");
|
||||
usage();
|
||||
return EXIT_FAILURE;
|
||||
} else {
|
||||
fdev_khz = (uint8_t)xu;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'r': /* -q <float> FSK bitrate */
|
||||
i = sscanf(optarg, "%f", &br_kbps);
|
||||
if ((i != 1) || (br_kbps < 0.5) || (br_kbps > 250)) {
|
||||
MSG("ERROR: invalid FSK bitrate\n");
|
||||
usage();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'p': /* -p <int> RF power */
|
||||
i = sscanf(optarg, "%i", &pow);
|
||||
if ((i != 1) || (pow < 0) || (pow > 30)) {
|
||||
MSG("ERROR: invalid RF power\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'z': /* -z <uint> Payload size */
|
||||
i = sscanf(optarg, "%i", &payload_size);
|
||||
if ((i != 1) || (payload_size < 9) || (payload_size > 255)) {
|
||||
MSG("ERROR: invalid payload size\n");
|
||||
usage();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
break;
|
||||
|
||||
case 't': /* -t <int> pause between RF packets (ms) */
|
||||
i = sscanf(optarg, "%i", &delay);
|
||||
if ((i != 1) || (delay < 0)) {
|
||||
MSG("ERROR: invalid time between RF packets\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'x': /* -x <int> numbers of times the sequence is repeated */
|
||||
i = sscanf(optarg, "%u", &repeat);
|
||||
if ((i != 1) || (repeat < 1)) {
|
||||
MSG("ERROR: invalid number of repeats\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'v': /* -v <uint> test Id */
|
||||
i = sscanf(optarg, "%u", &xu);
|
||||
if ((i != 1) || ((xu < 1) && (xu > 255))) {
|
||||
MSG("ERROR: invalid Id\n");
|
||||
return EXIT_FAILURE;
|
||||
} else {
|
||||
id = (uint8_t)xu;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'i': /* -i send packet using inverted modulation polarity */
|
||||
invert = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
MSG("ERROR: argument parsing failure, use -h option for help\n");
|
||||
usage();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
/* compose local address (auto-complete a structure for socket) */
|
||||
i = getaddrinfo(NULL, serv_port, &hints, &result);
|
||||
if (i != 0) {
|
||||
MSG("ERROR: getaddrinfo returned %s\n", gai_strerror(i));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* try to open socket and bind to it */
|
||||
for (q=result; q!=NULL; q=q->ai_next) {
|
||||
sock = socket(q->ai_family, q->ai_socktype,q->ai_protocol);
|
||||
if (sock == -1) {
|
||||
continue; /* socket failed, try next field */
|
||||
} else {
|
||||
i = bind(sock, q->ai_addr, q->ai_addrlen);
|
||||
if (i == -1) {
|
||||
shutdown(sock, SHUT_RDWR);
|
||||
continue; /* bind failed, try next field */
|
||||
} else {
|
||||
break; /* success, get out of loop */
|
||||
}
|
||||
}
|
||||
}
|
||||
if (q == NULL) {
|
||||
MSG("ERROR: failed to open socket or to bind to it\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
freeaddrinfo(result);
|
||||
|
||||
/* configure signal handling */
|
||||
sigemptyset(&sigact.sa_mask);
|
||||
sigact.sa_flags = 0;
|
||||
sigact.sa_handler = sig_handler;
|
||||
sigaction(SIGQUIT, &sigact, NULL);
|
||||
sigaction(SIGINT, &sigact, NULL);
|
||||
sigaction(SIGTERM, &sigact, NULL);
|
||||
|
||||
/* display setup summary */
|
||||
if (strcmp(mod, "FSK") == 0) {
|
||||
MSG("INFO: %i FSK pkts @%f MHz (FDev %u kHz, Bitrate %.2f kbps, %uB payload) %i dBm, %i ms between each\n", repeat, f_target, fdev_khz, br_kbps, payload_size, pow, delay);
|
||||
} else {
|
||||
MSG("INFO: %i LoRa pkts @%f MHz (BW %u kHz, SF%i, %uB payload) %i dBm, %i ms between each\n", repeat, f_target, bw, sf, payload_size, pow, delay);
|
||||
}
|
||||
|
||||
/* wait to receive a PULL_DATA request packet */
|
||||
MSG("INFO: waiting to receive a PULL_DATA request on port %s\n", serv_port);
|
||||
while (1) {
|
||||
byte_nb = recvfrom(sock, databuf, sizeof databuf, 0, (struct sockaddr *)&dist_addr, &addr_len);
|
||||
if ((quit_sig == 1) || (exit_sig == 1)) {
|
||||
exit(EXIT_SUCCESS);
|
||||
} else if (byte_nb < 0) {
|
||||
MSG("WARNING: recvfrom returned an error\n");
|
||||
} else if ((byte_nb < 12) || (databuf[0] != PROTOCOL_VERSION) || (databuf[3] != PKT_PULL_DATA)) {
|
||||
MSG("INFO: packet received, not PULL_DATA request\n");
|
||||
} else {
|
||||
break; /* success! */
|
||||
}
|
||||
}
|
||||
|
||||
/* retrieve gateway MAC from the request */
|
||||
raw_mac_h = *((uint32_t *)(databuf+4));
|
||||
raw_mac_l = *((uint32_t *)(databuf+8));
|
||||
gw_mac = ((uint64_t)ntohl(raw_mac_h) << 32) + (uint64_t)ntohl(raw_mac_l);
|
||||
|
||||
/* display info about the sender */
|
||||
i = getnameinfo((struct sockaddr *)&dist_addr, addr_len, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST);
|
||||
if (i == -1) {
|
||||
MSG("ERROR: getnameinfo returned %s \n", gai_strerror(i));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
MSG("INFO: PULL_DATA request received from gateway 0x%08X%08X (host %s, port %s)\n", (uint32_t)(gw_mac >> 32), (uint32_t)(gw_mac & 0xFFFFFFFF), host_name, port_name);
|
||||
|
||||
/* PKT_PULL_RESP datagrams header */
|
||||
databuf[0] = PROTOCOL_VERSION;
|
||||
databuf[1] = 0; /* no token */
|
||||
databuf[2] = 0; /* no token */
|
||||
databuf[3] = PKT_PULL_RESP;
|
||||
buff_index = 4;
|
||||
|
||||
/* start of JSON structure */
|
||||
memcpy((void *)(databuf + buff_index), (void *)"{\"txpk\":{\"imme\":true", 20);
|
||||
buff_index += 20;
|
||||
|
||||
/* TX frequency */
|
||||
i = snprintf((char *)(databuf + buff_index), 20, ",\"freq\":%.6f", f_target);
|
||||
if ((i>=0) && (i < 20)) {
|
||||
buff_index += i;
|
||||
} else {
|
||||
MSG("ERROR: snprintf failed line %u\n", (__LINE__ - 4));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* RF channel */
|
||||
memcpy((void *)(databuf + buff_index), (void *)",\"rfch\":0", 9);
|
||||
buff_index += 9;
|
||||
|
||||
/* TX power */
|
||||
i = snprintf((char *)(databuf + buff_index), 12, ",\"powe\":%i", pow);
|
||||
if ((i>=0) && (i < 12)) {
|
||||
buff_index += i;
|
||||
} else {
|
||||
MSG("ERROR: snprintf failed line %u\n", (__LINE__ - 4));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* modulation type and parameters */
|
||||
if (strcmp(mod, "FSK") == 0) {
|
||||
i = snprintf((char *)(databuf + buff_index), 50, ",\"modu\":\"FSK\",\"datr\":%u,\"fdev\":%u", (unsigned int)(br_kbps*1e3), (unsigned int)(fdev_khz*1e3));
|
||||
if ((i>=0) && (i < 50)) {
|
||||
buff_index += i;
|
||||
} else {
|
||||
MSG("ERROR: snprintf failed line %u\n", (__LINE__ - 4));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
i = snprintf((char *)(databuf + buff_index), 50, ",\"modu\":\"LORA\",\"datr\":\"SF%iBW%i\",\"codr\":\"4/6\"", sf, bw);
|
||||
if ((i>=0) && (i < 50)) {
|
||||
buff_index += i;
|
||||
} else {
|
||||
MSG("ERROR: snprintf failed line %u\n", (__LINE__ - 4));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
/* signal polarity */
|
||||
if (invert) {
|
||||
memcpy((void *)(databuf + buff_index), (void *)",\"ipol\":true", 12);
|
||||
buff_index += 12;
|
||||
} else {
|
||||
memcpy((void *)(databuf + buff_index), (void *)",\"ipol\":false", 13);
|
||||
buff_index += 13;
|
||||
}
|
||||
|
||||
/* Preamble size */
|
||||
memcpy((void *)(databuf + buff_index), (void *)",\"prea\":8", 9);
|
||||
buff_index += 9;
|
||||
|
||||
/* payload size */
|
||||
i = snprintf((char *)(databuf + buff_index), 12, ",\"size\":%i", payload_size);
|
||||
if ((i>=0) && (i < 12)) {
|
||||
buff_index += i;
|
||||
} else {
|
||||
MSG("ERROR: snprintf failed line %u\n", (__LINE__ - 4));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* payload JSON object */
|
||||
memcpy((void *)(databuf + buff_index), (void *)",\"data\":\"", 9);
|
||||
buff_index += 9;
|
||||
payload_index = buff_index; /* keep the value where the payload content start */
|
||||
|
||||
/* payload place-holder & end of JSON structure */
|
||||
x = bin_to_b64(payload_bin, payload_size, payload_b64, sizeof payload_b64); /* dummy conversion to get exact size */
|
||||
if (x >= 0) {
|
||||
buff_index += x;
|
||||
} else {
|
||||
MSG("ERROR: bin_to_b64 failed line %u\n", (__LINE__ - 4));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Close JSON structure */
|
||||
memcpy((void *)(databuf + buff_index), (void *)"\"}}", 3);
|
||||
buff_index += 3; /* ends up being the total length of payload */
|
||||
|
||||
/* main loop */
|
||||
for (i = 0; i < repeat; ++i) {
|
||||
/* fill payload */
|
||||
payload_bin[0] = id;
|
||||
payload_bin[1] = (uint8_t)(i >> 24);
|
||||
payload_bin[2] = (uint8_t)(i >> 16);
|
||||
payload_bin[3] = (uint8_t)(i >> 8);
|
||||
payload_bin[4] = (uint8_t)(i);
|
||||
payload_bin[5] = 'P';
|
||||
payload_bin[6] = 'E';
|
||||
payload_bin[7] = 'R';
|
||||
payload_bin[8] = (uint8_t)(payload_bin[0] + payload_bin[1] + payload_bin[2] + payload_bin[3] + payload_bin[4] + payload_bin[5] + payload_bin[6] + payload_bin[7]);
|
||||
for (j = 0; j < (payload_size - 9); j++) {
|
||||
payload_bin[9+j] = j;
|
||||
}
|
||||
|
||||
#if 0
|
||||
for (j = 0; j < payload_size; j++ ) {
|
||||
printf("0x%02X ", payload_bin[j]);
|
||||
}
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
/* encode the payload in Base64 */
|
||||
x = bin_to_b64(payload_bin, payload_size, payload_b64, sizeof payload_b64);
|
||||
if (x >= 0) {
|
||||
memcpy((void *)(databuf + payload_index), (void *)payload_b64, x);
|
||||
} else {
|
||||
MSG("ERROR: bin_to_b64 failed line %u\n", (__LINE__ - 4));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* send packet to the gateway */
|
||||
byte_nb = sendto(sock, (void *)databuf, buff_index, 0, (struct sockaddr *)&dist_addr, addr_len);
|
||||
if (byte_nb == -1) {
|
||||
MSG("WARNING: sendto returned an error %s\n", strerror(errno));
|
||||
} else {
|
||||
MSG("INFO: packet #%i sent successfully\n", i);
|
||||
}
|
||||
|
||||
/* wait inter-packet delay */
|
||||
usleep(delay * 1000);
|
||||
|
||||
/* exit loop on user signals */
|
||||
if ((quit_sig == 1) || (exit_sig == 1)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/* --- EOF ------------------------------------------------------------------ */
|
||||
Binary file not shown.
Reference in New Issue
Block a user