├── .dockerignore ├── images ├── side.jpg ├── top.jpg ├── overview.jpg ├── Arduino_pins.jpg ├── Pi-GPIO-header.png ├── inair_dimensions.gif ├── resin_io_env_vars.png └── Lora_Shield-v13_annotated.jpg ├── docker-compose.yml ├── packet_forwarder ├── Makefile ├── LICENSE ├── README.md ├── base64.h ├── base64.c └── main.cpp ├── Dockerfile.template ├── LICENSE └── README.md /.dockerignore: -------------------------------------------------------------------------------- 1 | images 2 | -------------------------------------------------------------------------------- /images/side.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thesolarnomad/resin-ttn-gateway/HEAD/images/side.jpg -------------------------------------------------------------------------------- /images/top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thesolarnomad/resin-ttn-gateway/HEAD/images/top.jpg -------------------------------------------------------------------------------- /images/overview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thesolarnomad/resin-ttn-gateway/HEAD/images/overview.jpg -------------------------------------------------------------------------------- /images/Arduino_pins.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thesolarnomad/resin-ttn-gateway/HEAD/images/Arduino_pins.jpg -------------------------------------------------------------------------------- /images/Pi-GPIO-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thesolarnomad/resin-ttn-gateway/HEAD/images/Pi-GPIO-header.png -------------------------------------------------------------------------------- /images/inair_dimensions.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thesolarnomad/resin-ttn-gateway/HEAD/images/inair_dimensions.gif -------------------------------------------------------------------------------- /images/resin_io_env_vars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thesolarnomad/resin-ttn-gateway/HEAD/images/resin_io_env_vars.png -------------------------------------------------------------------------------- /images/Lora_Shield-v13_annotated.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thesolarnomad/resin-ttn-gateway/HEAD/images/Lora_Shield-v13_annotated.jpg -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | build: 2 | build: ./ 3 | dockerfile: Dockerfile.template 4 | command: "./packet_forwarder/single_chan_pkt_fwd" 5 | environment: 6 | EMAIL_ADDRESS: 7 | FREQUENCY: 8 | HOSTS: 9 | EMAIL_ADDRESS: 10 | FREQUENCY: 11 | LATITUDE: 12 | LONGITUDE: 13 | ALTITUDE: 14 | -------------------------------------------------------------------------------- /packet_forwarder/Makefile: -------------------------------------------------------------------------------- 1 | # single_chan_pkt_fwd 2 | # Single Channel LoRaWAN Gateway 3 | 4 | CC=g++ 5 | CFLAGS=-c -Wall 6 | LIBS=-lwiringPi 7 | 8 | all: single_chan_pkt_fwd 9 | 10 | single_chan_pkt_fwd: base64.o main.o 11 | $(CC) main.o base64.o $(LIBS) -o single_chan_pkt_fwd 12 | 13 | main.o: main.cpp 14 | $(CC) $(CFLAGS) main.cpp 15 | 16 | base64.o: base64.c 17 | $(CC) $(CFLAGS) base64.c 18 | 19 | clean: 20 | rm *.o single_chan_pkt_fwd -------------------------------------------------------------------------------- /Dockerfile.template: -------------------------------------------------------------------------------- 1 | #FROM resin/%%RESIN_MACHINE_NAME%%-debian 2 | #For RPi1 3 | FROM resin/rpi-raspbian:jessie 4 | 5 | #switch on systemd init system in container 6 | ENV INITSYSTEM on 7 | 8 | RUN apt-get -q update && apt-get install -yq --no-install-recommends \ 9 | git-core \ 10 | build-essential \ 11 | gcc \ 12 | && apt-get clean && rm -rf /var/lib/apt/lists/* 13 | 14 | RUN git clone git://git.drogon.net/wiringPi \ 15 | && cd wiringPi \ 16 | && ./build 17 | 18 | COPY . /usr/src/app 19 | WORKDIR /usr/src/app 20 | 21 | RUN cd packet_forwarder && make 22 | 23 | CMD ./packet_forwarder/single_chan_pkt_fwd \ 24 | -u$HOSTS \ 25 | -e$EMAIL_ADDRESS \ 26 | -f$FREQUENCY \ 27 | -lat$LATITUDE \ 28 | -lon$LONGITUDE \ 29 | -alt$ALTITUDE \ 30 | -ss$SS_PIN \ 31 | -dio$DIO_PIN \ 32 | -rst$RST_PIN 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Wala 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packet_forwarder/LICENSE: -------------------------------------------------------------------------------- 1 | Applicable to base64.h and base64.c: 2 | 3 | Copyright (C) 2013, SEMTECH S.A. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | * Neither the name of the Semtech corporation nor the 14 | names of its contributors may be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL SEMTECH S.A. BE LIABLE FOR ANY 21 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /packet_forwarder/README.md: -------------------------------------------------------------------------------- 1 | Single Channel LoRaWAN Gateway 2 | ============================== 3 | This repository contains a proof-of-concept implementation of a single 4 | channel LoRaWAN gateway. 5 | 6 | It has been tested on the Raspberry Pi platform, using a Semtech SX1272 7 | transceiver (HopeRF RFM92W), and SX1276 (HopeRF RFM95W). 8 | 9 | The code is for testing and development purposes only, and is not meant 10 | for production usage. 11 | 12 | Part of the source has been copied from the Semtech Packet Forwarder 13 | (with permission). 14 | 15 | Maintainer: Thomas Telkamp 16 | 17 | Features 18 | -------- 19 | - listen on configurable frequency and spreading factor 20 | - SF7 to SF12 21 | - status updates 22 | - can forward to two servers 23 | 24 | Not (yet) supported: 25 | - PACKET_PUSH_ACK processing 26 | - SF7BW250 modulation 27 | - FSK modulation 28 | - downstream messages (tx) 29 | 30 | Dependencies 31 | ------------ 32 | - SPI needs to be enabled on the Raspberry Pi (use raspi-config) 33 | - WiringPi: a GPIO access library written in C for the BCM2835 34 | used in the Raspberry Pi. 35 | sudo apt-get install wiringpi 36 | see http://wiringpi.com 37 | - Run packet forwarder as root 38 | 39 | Connections 40 | ----------- 41 | SX1272 - Raspberry 42 | 43 | 3.3V - 3.3V (header pin #1) 44 | GND - GND (pin #6) 45 | MISO - MISO (pin #21) 46 | MOSI - MOSI (pin #19) 47 | SCK - CLK (pin #23) 48 | NSS - GPIO6 (pin #22) 49 | DIO0 - GPIO7 (pin #7) 50 | RST - GPIO0 (pin #11) 51 | 52 | Configuration 53 | ------------- 54 | 55 | Defaults: 56 | 57 | - LoRa: SF7 at 868.1 Mhz 58 | - Server: 54.229.214.112, port 1700 (The Things Network: croft.thethings.girovito.nl) 59 | 60 | Edit source node (main.cpp) to change configuration (look for: "Configure these values!"). 61 | 62 | Please set location, email and description. 63 | 64 | License 65 | ------- 66 | The source files in this repository are made available under the Eclipse 67 | Public License v1.0, except for the base64 implementation, that has been 68 | copied from the Semtech Packet Forwader. 69 | -------------------------------------------------------------------------------- /packet_forwarder/base64.h: -------------------------------------------------------------------------------- 1 | /* 2 | / _____) _ | | 3 | ( (____ _____ ____ _| |_ _____ ____| |__ 4 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 5 | _____) ) ____| | | || |_| ____( (___| | | | 6 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 7 | (C)2013 Semtech-Cycleo 8 | 9 | Description: 10 | Base64 encoding & decoding library 11 | 12 | License: Revised BSD License, see LICENSE.TXT file include in the project 13 | Maintainer: Sylvain Miermont 14 | */ 15 | 16 | 17 | #ifndef _BASE64_H 18 | #define _BASE64_H 19 | 20 | /* -------------------------------------------------------------------------- */ 21 | /* --- DEPENDANCIES --------------------------------------------------------- */ 22 | 23 | #include /* C99 types */ 24 | 25 | /* -------------------------------------------------------------------------- */ 26 | /* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ 27 | 28 | /** 29 | @brief Encode binary data in Base64 string (no padding) 30 | @param in pointer to a table of binary data 31 | @param size number of bytes to be encoded to base64 32 | @param out pointer to a string where the function will output encoded data 33 | @param max_len max length of the out string (including null char) 34 | @return >=0 length of the resulting string (w/o null char), -1 for error 35 | */ 36 | int bin_to_b64_nopad(const uint8_t * in, int size, char * out, int max_len); 37 | 38 | /** 39 | @brief Decode Base64 string to binary data (no padding) 40 | @param in string containing only base64 valid characters 41 | @param size number of characters to be decoded from base64 (w/o null char) 42 | @param out pointer to a data buffer where the function will output decoded data 43 | @param out_max_len usable size of the output data buffer 44 | @return >=0 number of bytes written to the data buffer, -1 for error 45 | */ 46 | int b64_to_bin_nopad(const char * in, int size, uint8_t * out, int max_len); 47 | 48 | /* === derivative functions === */ 49 | 50 | /** 51 | @brief Encode binary data in Base64 string (with added padding) 52 | */ 53 | int bin_to_b64(const uint8_t * in, int size, char * out, int max_len); 54 | 55 | /** 56 | @brief Decode Base64 string to binary data (remove padding if necessary) 57 | */ 58 | int b64_to_bin(const char * in, int size, uint8_t * out, int max_len); 59 | 60 | #endif 61 | 62 | /* --- EOF ------------------------------------------------------------------ */ 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Resin.io The Things Network Gateway based on Raspberry Pi 2 | 3 | This uses a fork of the [Single Channel LoRaWAN Gateway](https://github.com/tftelkamp/single_chan_pkt_fwd) on a Raspberry Pi [as described by Dragino](http://www.instructables.com/id/Use-Lora-Shield-and-RPi-to-Build-a-LoRaWAN-Gateway/) on [resin.io](http://resin.io). 4 | 5 | ![Overview](images/overview.jpg) 6 | 7 | With [resin.io](http://resin.io) you can easily manage multiple gateways on multiple devices remotely and use environment variables (see table at the end of the page) to control each of the device parameters. 8 | 9 | ## Hardware 10 | 11 | It uses a Raspberry Pi Model B+ V1.2 and 12 | 13 | * a [Modtronix inAir9](http://modtronix.com/inair9.html) SX1276 LoRa Module 14 | 15 | *OR* 16 | 17 | * a [Dragino Shield v.1.3](http://wiki.dragino.com/index.php?title=Lora_Shield). 18 | 19 | ## Default pin mapping: 20 | 21 | | Dragino LoRa v 1.3 | inAir9 | Raspberry Pi | WiringPi | 22 | |---------------------|--------|------------------------------------------------------|----------| 23 | | 3.3V | 3.3V | [pin #1](http://pinout.xyz/pinout/pin1_3v3_power) | | 24 | | ICSP GND | GND | [pin #6](http://pinout.xyz/pinout/ground) | | 25 | | ICSP MISO | MISO | [pin #21](http://pinout.xyz/pinout/pin21_gpio9) | | 26 | | ICSP MOSI | MOSI | [pin #19](http://pinout.xyz/pinout/pin19_gpio10) | | 27 | | ICSP SCK | SCK | [pin #23](http://pinout.xyz/pinout/pin23_gpio11) | | 28 | | Pin 10 | NSS | [pin #22](http://pinout.xyz/pinout/pin22_gpio25) | 6 | 29 | | DIO0 | DIO0 | [pin #7](http://pinout.xyz/pinout/pin7_gpio4) | 7 | 30 | | RESET | RST | [pin #11](http://pinout.xyz/pinout/pin11_gpio17) | 0 | 31 | 32 | ### Examples 33 | 34 | #### Rpi from top 35 | ![RPi top](images/top.jpg) 36 | #### Rpi from side 37 | ![both](images/side.jpg) 38 | #### RPi pin table 39 | ![RPi pins](images/Pi-GPIO-header.png) 40 | #### inAir9 layout 41 | ![inAir9 pins](images/inair_dimensions.gif) 42 | #### Arduino pin layout 43 | ![Arduino pins](images/Arduino_pins.jpg) 44 | #### Dragino 1.3 45 | ![Dragino 1.3 annotated](images/Lora_Shield-v13_annotated.jpg) 46 | 47 | ## Environment variables for resin.io 48 | 49 | | Variable name | Value | More info | 50 | |---------------|---------------------------------------------------------------------------------------|----------------------------------------| 51 | | DIO_PIN | 7 | Optional. Defaults to 7 (WiringPi pin).| 52 | | RST_PIN | 0 | Optional. Defaults to 0 (WiringPi pin).| 53 | | SS_PIN | 6 | Optional. Defaults to 6 (WiringPi pin).| 54 | | EMAIL_ADDRESS | you@domain.com | Optional, empty by default. | 55 | | FREQUENCY | [Depends on your location and chip](https://github.com/TheThingsNetwork/gateway-conf) | Optional. In Hz. Defaults to 868100000 | 56 | | HOSTS | [Depends on your location](https://github.com/TheThingsNetwork/gateway-conf) | Optional | 57 | | LATITUDE | [Your latitude](http://www.gps-coordinates.net/) | Optional. Float. Defaults to 0.0 | 58 | | LONGITUDE | [Your longitude](http://www.gps-coordinates.net/) | Optional. Float. Defaults to 0.0 | 59 | | ALTITUDE | [Your altitude](http://www.gps-coordinates.net/) | Optional. Integer. Defaults to 0 | 60 | 61 | ### Example 62 | ![resin.io interface](images/resin_io_env_vars.png) 63 | 64 | ## Client 65 | 66 | You should be able to get most of the code from the instructables page (see link in the intro), however I needed to add 67 | 68 | ```cpp 69 | #define CFG_eu868 0 70 | #define CFG_us915 1 71 | ``` 72 | and 73 | 74 | ``` 75 | LMIC_setupChannel(0, 916800000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); 76 | ``` 77 | 78 | to my sketch to connect to it. You can then read the results from the TTN website (`https://thethingsnetwork.org/api/v0/nodes//`). 79 | -------------------------------------------------------------------------------- /packet_forwarder/base64.c: -------------------------------------------------------------------------------- 1 | /* 2 | / _____) _ | | 3 | ( (____ _____ ____ _| |_ _____ ____| |__ 4 | \____ \| ___ | (_ _) ___ |/ ___) _ \ 5 | _____) ) ____| | | || |_| ____( (___| | | | 6 | (______/|_____)_|_|_| \__)_____)\____)_| |_| 7 | (C)2013 Semtech-Cycleo 8 | 9 | Description: 10 | Base64 encoding & decoding library 11 | 12 | License: Revised BSD License, see LICENSE.TXT file include in the project 13 | Maintainer: Sylvain Miermont 14 | */ 15 | 16 | 17 | /* -------------------------------------------------------------------------- */ 18 | /* --- DEPENDANCIES --------------------------------------------------------- */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "base64.h" 25 | 26 | /* -------------------------------------------------------------------------- */ 27 | /* --- PRIVATE MACROS ------------------------------------------------------- */ 28 | 29 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) 30 | #define CRIT(a) fprintf(stderr, "\nCRITICAL file:%s line:%u msg:%s\n", __FILE__, __LINE__,a);exit(EXIT_FAILURE) 31 | 32 | //#define DEBUG(args...) fprintf(stderr,"debug: " args) /* diagnostic message that is destined to the user */ 33 | #define DEBUG(args...) 34 | 35 | /* -------------------------------------------------------------------------- */ 36 | /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ 37 | 38 | /* -------------------------------------------------------------------------- */ 39 | /* --- PRIVATE MODULE-WIDE VARIABLES ---------------------------------------- */ 40 | 41 | static char code_62 = '+'; /* RFC 1421 standard character for code 62 */ 42 | static char code_63 = '/'; /* RFC 1421 standard character for code 63 */ 43 | static char code_pad = '='; /* RFC 1421 padding character if padding */ 44 | 45 | /* -------------------------------------------------------------------------- */ 46 | /* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ 47 | 48 | /** 49 | @brief Convert a code in the range 0-63 to an ASCII character 50 | */ 51 | char code_to_char(uint8_t x); 52 | 53 | /** 54 | @brief Convert an ASCII character to a code in the range 0-63 55 | */ 56 | uint8_t char_to_code(char x); 57 | 58 | /* -------------------------------------------------------------------------- */ 59 | /* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ 60 | 61 | char code_to_char(uint8_t x) { 62 | if (x <= 25) { 63 | return 'A' + x; 64 | } else if ((x >= 26) && (x <= 51)) { 65 | return 'a' + (x-26); 66 | } else if ((x >= 52) && (x <= 61)) { 67 | return '0' + (x-52); 68 | } else if (x == 62) { 69 | return code_62; 70 | } else if (x == 63) { 71 | return code_63; 72 | } else { 73 | DEBUG("ERROR: %i IS OUT OF RANGE 0-63 FOR BASE64 ENCODING\n", x); 74 | exit(EXIT_FAILURE); 75 | } //TODO: improve error management 76 | } 77 | 78 | uint8_t char_to_code(char x) { 79 | if ((x >= 'A') && (x <= 'Z')) { 80 | return (uint8_t)x - (uint8_t)'A'; 81 | } else if ((x >= 'a') && (x <= 'z')) { 82 | return (uint8_t)x - (uint8_t)'a' + 26; 83 | } else if ((x >= '0') && (x <= '9')) { 84 | return (uint8_t)x - (uint8_t)'0' + 52; 85 | } else if (x == code_62) { 86 | return 62; 87 | } else if (x == code_63) { 88 | return 63; 89 | } else { 90 | DEBUG("ERROR: %c (0x%x) IS INVALID CHARACTER FOR BASE64 DECODING\n", x, x); 91 | exit(EXIT_FAILURE); 92 | } //TODO: improve error management 93 | } 94 | 95 | /* -------------------------------------------------------------------------- */ 96 | /* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ 97 | 98 | int bin_to_b64_nopad(const uint8_t * in, int size, char * out, int max_len) { 99 | int i; 100 | int result_len; /* size of the result */ 101 | int full_blocks; /* number of 3 unsigned chars / 4 characters blocks */ 102 | int last_bytes; /* number of unsigned chars <3 in the last block */ 103 | int last_chars; /* number of characters <4 in the last block */ 104 | uint32_t b; 105 | 106 | /* check input values */ 107 | if ((out == NULL) || (in == NULL)) { 108 | DEBUG("ERROR: NULL POINTER AS OUTPUT IN BIN_TO_B64\n"); 109 | return -1; 110 | } 111 | if (size == 0) { 112 | *out = 0; /* null string */ 113 | return 0; 114 | } 115 | 116 | /* calculate the number of base64 'blocks' */ 117 | full_blocks = size / 3; 118 | last_bytes = size % 3; 119 | switch (last_bytes) { 120 | case 0: /* no byte left to encode */ 121 | last_chars = 0; 122 | break; 123 | case 1: /* 1 byte left to encode -> +2 chars */ 124 | last_chars = 2; 125 | break; 126 | case 2: /* 2 bytes left to encode -> +3 chars */ 127 | last_chars = 3; 128 | break; 129 | default: 130 | CRIT("switch default that should not be possible"); 131 | } 132 | 133 | /* check if output buffer is big enough */ 134 | result_len = (4*full_blocks) + last_chars; 135 | if (max_len < (result_len + 1)) { /* 1 char added for string terminator */ 136 | DEBUG("ERROR: OUTPUT BUFFER TOO SMALL IN BIN_TO_B64\n"); 137 | return -1; 138 | } 139 | 140 | /* process all the full blocks */ 141 | for (i=0; i < full_blocks; ++i) { 142 | b = (0xFF & in[3*i] ) << 16; 143 | b |= (0xFF & in[3*i + 1]) << 8; 144 | b |= 0xFF & in[3*i + 2]; 145 | out[4*i + 0] = code_to_char((b >> 18) & 0x3F); 146 | out[4*i + 1] = code_to_char((b >> 12) & 0x3F); 147 | out[4*i + 2] = code_to_char((b >> 6 ) & 0x3F); 148 | out[4*i + 3] = code_to_char( b & 0x3F); 149 | } 150 | 151 | /* process the last 'partial' block and terminate string */ 152 | i = full_blocks; 153 | if (last_chars == 0) { 154 | out[4*i] = 0; /* null character to terminate string */ 155 | } else if (last_chars == 2) { 156 | b = (0xFF & in[3*i] ) << 16; 157 | out[4*i + 0] = code_to_char((b >> 18) & 0x3F); 158 | out[4*i + 1] = code_to_char((b >> 12) & 0x3F); 159 | out[4*i + 2] = 0; /* null character to terminate string */ 160 | } else if (last_chars == 3) { 161 | b = (0xFF & in[3*i] ) << 16; 162 | b |= (0xFF & in[3*i + 1]) << 8; 163 | out[4*i + 0] = code_to_char((b >> 18) & 0x3F); 164 | out[4*i + 1] = code_to_char((b >> 12) & 0x3F); 165 | out[4*i + 2] = code_to_char((b >> 6 ) & 0x3F); 166 | out[4*i + 3] = 0; /* null character to terminate string */ 167 | } 168 | 169 | return result_len; 170 | } 171 | 172 | int b64_to_bin_nopad(const char * in, int size, uint8_t * out, int max_len) { 173 | int i; 174 | int result_len; /* size of the result */ 175 | int full_blocks; /* number of 3 unsigned chars / 4 characters blocks */ 176 | int last_chars; /* number of characters <4 in the last block */ 177 | int last_bytes; /* number of unsigned chars <3 in the last block */ 178 | uint32_t b; 179 | ; 180 | 181 | /* check input values */ 182 | if ((out == NULL) || (in == NULL)) { 183 | DEBUG("ERROR: NULL POINTER AS OUTPUT OR INPUT IN B64_TO_BIN\n"); 184 | return -1; 185 | } 186 | if (size == 0) { 187 | return 0; 188 | } 189 | 190 | /* calculate the number of base64 'blocks' */ 191 | full_blocks = size / 4; 192 | last_chars = size % 4; 193 | switch (last_chars) { 194 | case 0: /* no char left to decode */ 195 | last_bytes = 0; 196 | break; 197 | case 1: /* only 1 char left is an error */ 198 | DEBUG("ERROR: ONLY ONE CHAR LEFT IN B64_TO_BIN\n"); 199 | return -1; 200 | case 2: /* 2 chars left to decode -> +1 byte */ 201 | last_bytes = 1; 202 | break; 203 | case 3: /* 3 chars left to decode -> +2 bytes */ 204 | last_bytes = 2; 205 | break; 206 | default: 207 | CRIT("switch default that should not be possible"); 208 | } 209 | 210 | /* check if output buffer is big enough */ 211 | result_len = (3*full_blocks) + last_bytes; 212 | if (max_len < result_len) { 213 | DEBUG("ERROR: OUTPUT BUFFER TOO SMALL IN B64_TO_BIN\n"); 214 | return -1; 215 | } 216 | 217 | /* process all the full blocks */ 218 | for (i=0; i < full_blocks; ++i) { 219 | b = (0x3F & char_to_code(in[4*i] )) << 18; 220 | b |= (0x3F & char_to_code(in[4*i + 1])) << 12; 221 | b |= (0x3F & char_to_code(in[4*i + 2])) << 6; 222 | b |= 0x3F & char_to_code(in[4*i + 3]); 223 | out[3*i + 0] = (b >> 16) & 0xFF; 224 | out[3*i + 1] = (b >> 8 ) & 0xFF; 225 | out[3*i + 2] = b & 0xFF; 226 | } 227 | 228 | /* process the last 'partial' block */ 229 | i = full_blocks; 230 | if (last_bytes == 1) { 231 | b = (0x3F & char_to_code(in[4*i] )) << 18; 232 | b |= (0x3F & char_to_code(in[4*i + 1])) << 12; 233 | out[3*i + 0] = (b >> 16) & 0xFF; 234 | if (((b >> 12) & 0x0F) != 0) { 235 | DEBUG("WARNING: last character contains unusable bits\n"); 236 | } 237 | } else if (last_bytes == 2) { 238 | b = (0x3F & char_to_code(in[4*i] )) << 18; 239 | b |= (0x3F & char_to_code(in[4*i + 1])) << 12; 240 | b |= (0x3F & char_to_code(in[4*i + 2])) << 6; 241 | out[3*i + 0] = (b >> 16) & 0xFF; 242 | out[3*i + 1] = (b >> 8 ) & 0xFF; 243 | if (((b >> 6) & 0x03) != 0) { 244 | DEBUG("WARNING: last character contains unusable bits\n"); 245 | } 246 | } 247 | 248 | return result_len; 249 | } 250 | 251 | int bin_to_b64(const uint8_t * in, int size, char * out, int max_len) { 252 | int ret; 253 | 254 | ret = bin_to_b64_nopad(in, size, out, max_len); 255 | 256 | if (ret == -1) { 257 | return -1; 258 | } 259 | switch (ret%4) { 260 | case 0: /* nothing to do */ 261 | return ret; 262 | case 1: 263 | DEBUG("ERROR: INVALID UNPADDED BASE64 STRING\n"); 264 | return -1; 265 | case 2: /* 2 chars in last block, must add 2 padding char */ 266 | if (max_len > (ret + 2 + 1)) { 267 | out[ret] = code_pad; 268 | out[ret+1] = code_pad; 269 | out[ret+2] = 0; 270 | return ret+2; 271 | } else { 272 | DEBUG("ERROR: not enough room to add padding in bin_to_b64\n"); 273 | return -1; 274 | } 275 | case 3: /* 3 chars in last block, must add 1 padding char */ 276 | if (max_len > (ret + 1 + 1)) { 277 | out[ret] = code_pad; 278 | out[ret+1] = 0; 279 | return ret+1; 280 | } else { 281 | DEBUG("ERROR: not enough room to add padding in bin_to_b64\n"); 282 | return -1; 283 | } 284 | default: 285 | CRIT("switch default that should not be possible"); 286 | } 287 | } 288 | 289 | int b64_to_bin(const char * in, int size, uint8_t * out, int max_len) { 290 | if (in == NULL) { 291 | DEBUG("ERROR: NULL POINTER AS OUTPUT OR INPUT IN B64_TO_BIN\n"); 292 | return -1; 293 | } 294 | if ((size%4 == 0) && (size >= 4)) { /* potentially padded Base64 */ 295 | if (in[size-2] == code_pad) { /* 2 padding char to ignore */ 296 | return b64_to_bin_nopad(in, size-2, out, max_len); 297 | } else if (in[size-1] == code_pad) { /* 1 padding char to ignore */ 298 | return b64_to_bin_nopad(in, size-1, out, max_len); 299 | } else { /* no padding to ignore */ 300 | return b64_to_bin_nopad(in, size, out, max_len); 301 | } 302 | } else { /* treat as unpadded Base64 */ 303 | return b64_to_bin_nopad(in, size, out, max_len); 304 | } 305 | } 306 | 307 | 308 | /* --- EOF ------------------------------------------------------------------ */ 309 | -------------------------------------------------------------------------------- /packet_forwarder/main.cpp: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Copyright (c) 2015 Thomas Telkamp 4 | * 5 | * All rights reserved. This program and the accompanying materials 6 | * are made available under the terms of the Eclipse Public License v1.0 7 | * which accompanies this distribution, and is available at 8 | * http://www.eclipse.org/legal/epl-v10.html 9 | * 10 | *******************************************************************************/ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | using namespace std; 30 | 31 | #include "base64.h" 32 | 33 | #include 34 | #include 35 | 36 | typedef bool boolean; 37 | typedef unsigned char byte; 38 | 39 | static const int CHANNEL = 0; 40 | 41 | byte currentMode = 0x81; 42 | 43 | char message[256]; 44 | char b64[256]; 45 | 46 | bool sx1272 = true; 47 | 48 | byte receivedbytes; 49 | 50 | struct sockaddr_in si_other; 51 | int s, slen=sizeof(si_other); 52 | struct ifreq ifr; 53 | 54 | uint32_t cp_nb_rx_rcv; 55 | uint32_t cp_nb_rx_ok; 56 | uint32_t cp_nb_rx_bad; 57 | uint32_t cp_nb_rx_nocrc; 58 | uint32_t cp_up_pkt_fwd; 59 | 60 | enum sf_t { SF7=7, SF8, SF9, SF10, SF11, SF12 }; 61 | 62 | enum { 63 | EU868_F1 = 868100000, // g1 SF7-12 64 | EU868_F2 = 868300000, // g1 SF7-12 FSK SF7/250 65 | EU868_F3 = 868500000, // g1 SF7-12 66 | EU868_F4 = 868850000, // g2 SF7-12 67 | EU868_F5 = 869050000, // g2 SF7-12 68 | EU868_F6 = 869525000, // g3 SF7-12 69 | EU868_J4 = 864100000, // g2 SF7-12 used during join 70 | EU868_J5 = 864300000, // g2 SF7-12 ditto 71 | EU868_J6 = 864500000, // g2 SF7-12 ditto 72 | }; 73 | /******************************************************************************* 74 | * 75 | * Configure these values! 76 | * 77 | *******************************************************************************/ 78 | 79 | // SX1272 - Raspberry connections 80 | int ssPin = 6; 81 | int dio0 = 7; 82 | int RST = 0; 83 | 84 | #define DEFAULTSERVER "croft.thethings.girovito.nl" 85 | #define DEFAULTPORT 1700 //The port on which to send data 86 | 87 | // Set spreading factor (SF7 - SF12) 88 | sf_t sf = SF7; 89 | 90 | // Set center frequency 91 | uint32_t freq = 868100000; // in Mhz! (868.1) 92 | 93 | // Set location 94 | float lat=0.0; 95 | float lon=0.0; 96 | int alt=0; 97 | 98 | /* Informal status fields */ 99 | static char platform[24] = "Single Channel Gateway"; /* platform definition */ 100 | static char email[40] = ""; /* used for contact email */ 101 | 102 | // ############################################# 103 | // ############################################# 104 | std::map > serverList; 105 | 106 | static char description[64] = ""; /* Will be set automatically containing the frequency and spreading factor values */ 107 | 108 | 109 | #define REG_FIFO 0x00 110 | #define REG_FIFO_ADDR_PTR 0x0D 111 | #define REG_FIFO_TX_BASE_AD 0x0E 112 | #define REG_FIFO_RX_BASE_AD 0x0F 113 | #define REG_RX_NB_BYTES 0x13 114 | #define REG_OPMODE 0x01 115 | #define REG_FIFO_RX_CURRENT_ADDR 0x10 116 | #define REG_IRQ_FLAGS 0x12 117 | #define REG_DIO_MAPPING_1 0x40 118 | #define REG_DIO_MAPPING_2 0x41 119 | #define REG_MODEM_CONFIG 0x1D 120 | #define REG_MODEM_CONFIG2 0x1E 121 | #define REG_MODEM_CONFIG3 0x26 122 | #define REG_SYMB_TIMEOUT_LSB 0x1F 123 | #define REG_PKT_SNR_VALUE 0x19 124 | #define REG_PAYLOAD_LENGTH 0x22 125 | #define REG_IRQ_FLAGS_MASK 0x11 126 | #define REG_MAX_PAYLOAD_LENGTH 0x23 127 | #define REG_HOP_PERIOD 0x24 128 | #define REG_SYNC_WORD 0x39 129 | #define REG_VERSION 0x42 130 | 131 | #define SX72_MODE_RX_CONTINUOS 0x85 132 | #define SX72_MODE_TX 0x83 133 | #define SX72_MODE_SLEEP 0x80 134 | #define SX72_MODE_STANDBY 0x81 135 | 136 | #define PAYLOAD_LENGTH 0x40 137 | 138 | // LOW NOISE AMPLIFIER 139 | #define REG_LNA 0x0C 140 | #define LNA_MAX_GAIN 0x23 141 | #define LNA_OFF_GAIN 0x00 142 | #define LNA_LOW_GAIN 0x20 143 | 144 | // CONF REG 145 | #define REG1 0x0A 146 | #define REG2 0x84 147 | 148 | #define SX72_MC2_FSK 0x00 149 | #define SX72_MC2_SF7 0x70 150 | #define SX72_MC2_SF8 0x80 151 | #define SX72_MC2_SF9 0x90 152 | #define SX72_MC2_SF10 0xA0 153 | #define SX72_MC2_SF11 0xB0 154 | #define SX72_MC2_SF12 0xC0 155 | 156 | #define SX72_MC1_LOW_DATA_RATE_OPTIMIZE 0x01 // mandated for SF11 and SF12 157 | 158 | // FRF 159 | #define REG_FRF_MSB 0x06 160 | #define REG_FRF_MID 0x07 161 | #define REG_FRF_LSB 0x08 162 | 163 | #define FRF_MSB 0xD9 // 868.1 Mhz 164 | #define FRF_MID 0x06 165 | #define FRF_LSB 0x66 166 | 167 | #define BUFLEN 2048 //Max length of buffer 168 | 169 | #define PROTOCOL_VERSION 1 170 | #define PKT_PUSH_DATA 0 171 | #define PKT_PUSH_ACK 1 172 | #define PKT_PULL_DATA 2 173 | #define PKT_PULL_RESP 3 174 | #define PKT_PULL_ACK 4 175 | 176 | #define TX_BUFF_SIZE 2048 177 | #define STATUS_SIZE 1024 178 | 179 | void die(const char *s) 180 | { 181 | perror(s); 182 | exit(1); 183 | } 184 | 185 | void selectreceiver() 186 | { 187 | digitalWrite(ssPin, LOW); 188 | } 189 | 190 | void unselectreceiver() 191 | { 192 | digitalWrite(ssPin, HIGH); 193 | } 194 | 195 | int hostToIp(const char *host, char *ip, const int bufflen) 196 | { 197 | struct addrinfo hints, *servinfo, *p; 198 | struct sockaddr_in *h; 199 | int ret=-1; 200 | 201 | if(bufflen < INET6_ADDRSTRLEN) 202 | die("hostToIP, supplied buffer too short"); 203 | 204 | memset(&hints, 0, sizeof hints); 205 | hints.ai_family = AF_UNSPEC; 206 | hints.ai_socktype = SOCK_STREAM; 207 | 208 | if((ret = getaddrinfo(host, NULL, NULL, &servinfo)) != 0) 209 | { 210 | std::stringstream error; 211 | error << "Failed to look up server: "; 212 | error << host; 213 | error << ", "; 214 | error << gai_strerror(ret); 215 | die(error.str().c_str()); 216 | } 217 | 218 | for(p = servinfo; p != NULL; p = p->ai_next) 219 | { 220 | h = (struct sockaddr_in *) p->ai_addr; 221 | strncpy(ip , inet_ntoa( h->sin_addr ), bufflen); 222 | } 223 | 224 | freeaddrinfo(servinfo); 225 | return 0; 226 | } 227 | 228 | byte readRegister(byte addr) 229 | { 230 | unsigned char spibuf[2]; 231 | 232 | selectreceiver(); 233 | spibuf[0] = addr & 0x7F; 234 | spibuf[1] = 0x00; 235 | if(wiringPiSPIDataRW(CHANNEL, spibuf, 2) < 0) 236 | fprintf (stderr, "readRegister failed: %s\n", strerror (errno)); 237 | unselectreceiver(); 238 | 239 | return spibuf[1]; 240 | } 241 | 242 | void writeRegister(byte addr, byte value) 243 | { 244 | unsigned char spibuf[2]; 245 | 246 | spibuf[0] = addr | 0x80; 247 | spibuf[1] = value; 248 | selectreceiver(); 249 | wiringPiSPIDataRW(CHANNEL, spibuf, 2); 250 | 251 | unselectreceiver(); 252 | } 253 | 254 | 255 | boolean receivePkt(char *payload) 256 | { 257 | 258 | // clear rxDone 259 | writeRegister(REG_IRQ_FLAGS, 0x40); 260 | 261 | int irqflags = readRegister(REG_IRQ_FLAGS); 262 | 263 | cp_nb_rx_rcv++; 264 | 265 | // payload crc: 0x20 266 | if((irqflags & 0x20) == 0x20) 267 | { 268 | printf("CRC error\n"); 269 | writeRegister(REG_IRQ_FLAGS, 0x20); 270 | return false; 271 | } else { 272 | 273 | cp_nb_rx_ok++; 274 | 275 | byte currentAddr = readRegister(REG_FIFO_RX_CURRENT_ADDR); 276 | byte receivedCount = readRegister(REG_RX_NB_BYTES); 277 | receivedbytes = receivedCount; 278 | 279 | writeRegister(REG_FIFO_ADDR_PTR, currentAddr); 280 | 281 | for(int i = 0; i < receivedCount; i++) 282 | { 283 | payload[i] = (char)readRegister(REG_FIFO); 284 | } 285 | } 286 | return true; 287 | } 288 | 289 | void SetupLoRa() 290 | { 291 | 292 | digitalWrite(RST, HIGH); 293 | delay(100); 294 | digitalWrite(RST, LOW); 295 | delay(100); 296 | 297 | byte version = readRegister(REG_VERSION); 298 | 299 | if (version == 0x22) { 300 | // sx1272 301 | printf("SX1272 detected, starting.\n"); 302 | sx1272 = true; 303 | } else { 304 | // sx1276? 305 | digitalWrite(RST, LOW); 306 | delay(100); 307 | digitalWrite(RST, HIGH); 308 | delay(100); 309 | version = readRegister(REG_VERSION); 310 | if (version == 0x12) { 311 | // sx1276 312 | printf("SX1276 detected, starting.\n"); 313 | sx1272 = false; 314 | } else { 315 | printf("Unrecognized transceiver.: %x\n", version); 316 | //printf("Version: 0x%x\n",version); 317 | exit(1); 318 | } 319 | } 320 | 321 | writeRegister(REG_OPMODE, SX72_MODE_SLEEP); 322 | 323 | // set frequency 324 | uint64_t frf = ((uint64_t)freq << 19) / 32000000; 325 | writeRegister(REG_FRF_MSB, (uint8_t)(frf>>16) ); 326 | writeRegister(REG_FRF_MID, (uint8_t)(frf>> 8) ); 327 | writeRegister(REG_FRF_LSB, (uint8_t)(frf>> 0) ); 328 | 329 | writeRegister(REG_SYNC_WORD, 0x34); // LoRaWAN public sync word 330 | 331 | if (sx1272) { 332 | if (sf == SF11 || sf == SF12) { 333 | writeRegister(REG_MODEM_CONFIG,0x0B); 334 | } else { 335 | writeRegister(REG_MODEM_CONFIG,0x0A); 336 | } 337 | writeRegister(REG_MODEM_CONFIG2,(sf<<4) | 0x04); 338 | } else { 339 | if (sf == SF11 || sf == SF12) { 340 | writeRegister(REG_MODEM_CONFIG3,0x0C); 341 | } else { 342 | writeRegister(REG_MODEM_CONFIG3,0x04); 343 | } 344 | writeRegister(REG_MODEM_CONFIG,0x72); 345 | writeRegister(REG_MODEM_CONFIG2,(sf<<4) | 0x04); 346 | } 347 | 348 | if (sf == SF10 || sf == SF11 || sf == SF12) { 349 | writeRegister(REG_SYMB_TIMEOUT_LSB,0x05); 350 | } else { 351 | writeRegister(REG_SYMB_TIMEOUT_LSB,0x08); 352 | } 353 | writeRegister(REG_MAX_PAYLOAD_LENGTH,0x80); 354 | writeRegister(REG_PAYLOAD_LENGTH,PAYLOAD_LENGTH); 355 | writeRegister(REG_HOP_PERIOD,0xFF); 356 | writeRegister(REG_FIFO_ADDR_PTR, readRegister(REG_FIFO_RX_BASE_AD)); 357 | 358 | // Set Continous Receive Mode 359 | writeRegister(REG_LNA, LNA_MAX_GAIN); // max lna gain 360 | writeRegister(REG_OPMODE, SX72_MODE_RX_CONTINUOS); 361 | 362 | } 363 | 364 | void sendudp(char *msg, int length) { 365 | 366 | std::map >::iterator iter; 367 | for(iter = serverList.begin(); iter != serverList.end(); iter++) 368 | { 369 | si_other.sin_port = htons(iter->second.second); 370 | inet_aton(iter->second.first.c_str(), &si_other.sin_addr); 371 | if (sendto(s, (char *)msg, length, 0 , (struct sockaddr *) &si_other, slen)==-1) 372 | { 373 | die("sendto()"); 374 | } 375 | } 376 | } 377 | 378 | void sendstat() { 379 | 380 | static char status_report[STATUS_SIZE]; /* status report as a JSON object */ 381 | char stat_timestamp[24]; 382 | time_t t; 383 | 384 | int stat_index=0; 385 | 386 | /* pre-fill the data buffer with fixed fields */ 387 | status_report[0] = PROTOCOL_VERSION; 388 | status_report[3] = PKT_PUSH_DATA; 389 | 390 | status_report[4] = (unsigned char)ifr.ifr_hwaddr.sa_data[0]; 391 | status_report[5] = (unsigned char)ifr.ifr_hwaddr.sa_data[1]; 392 | status_report[6] = (unsigned char)ifr.ifr_hwaddr.sa_data[2]; 393 | status_report[7] = 0xFF; 394 | status_report[8] = 0xFF; 395 | status_report[9] = (unsigned char)ifr.ifr_hwaddr.sa_data[3]; 396 | status_report[10] = (unsigned char)ifr.ifr_hwaddr.sa_data[4]; 397 | status_report[11] = (unsigned char)ifr.ifr_hwaddr.sa_data[5]; 398 | 399 | /* start composing datagram with the header */ 400 | uint8_t token_h = (uint8_t)rand(); /* random token */ 401 | uint8_t token_l = (uint8_t)rand(); /* random token */ 402 | status_report[1] = token_h; 403 | status_report[2] = token_l; 404 | stat_index = 12; /* 12-byte header */ 405 | 406 | /* get timestamp for statistics */ 407 | t = time(NULL); 408 | strftime(stat_timestamp, sizeof stat_timestamp, "%F %T %Z", gmtime(&t)); 409 | 410 | int j = snprintf((char *)(status_report + stat_index), STATUS_SIZE-stat_index, "{\"stat\":{\"time\":\"%s\",\"lati\":%.5f,\"long\":%.5f,\"alti\":%i,\"rxnb\":%u,\"rxok\":%u,\"rxfw\":%u,\"ackr\":%.1f,\"dwnb\":%u,\"txnb\":%u,\"pfrm\":\"%s\",\"mail\":\"%s\",\"desc\":\"%s\"}}", stat_timestamp, lat, lon, (int)alt, cp_nb_rx_rcv, cp_nb_rx_ok, cp_up_pkt_fwd, (float)0, 0, 0,platform,email,description); 411 | stat_index += j; 412 | status_report[stat_index] = 0; /* add string terminator, for safety */ 413 | 414 | printf("stat update: %s\n", (char *)(status_report+12)); /* DEBUG: display JSON stat */ 415 | 416 | //send the update 417 | sendudp(status_report, stat_index); 418 | 419 | } 420 | 421 | void receivepacket() { 422 | 423 | long int SNR; 424 | int rssicorr; 425 | 426 | if(digitalRead(dio0) == 1) 427 | { 428 | if(receivePkt(message)) { 429 | byte value = readRegister(REG_PKT_SNR_VALUE); 430 | if( value & 0x80 ) // The SNR sign bit is 1 431 | { 432 | // Invert and divide by 4 433 | value = ( ( ~value + 1 ) & 0xFF ) >> 2; 434 | SNR = -value; 435 | } 436 | else 437 | { 438 | // Divide by 4 439 | SNR = ( value & 0xFF ) >> 2; 440 | } 441 | 442 | if (sx1272) { 443 | rssicorr = 139; 444 | } else { 445 | rssicorr = 157; 446 | } 447 | 448 | printf("Packet RSSI: %d, ",readRegister(0x1A)-rssicorr); 449 | printf("RSSI: %d, ",readRegister(0x1B)-rssicorr); 450 | printf("SNR: %li, ",SNR); 451 | printf("Length: %i",(int)receivedbytes); 452 | printf("\n"); 453 | 454 | int j; 455 | j = bin_to_b64((uint8_t *)message, receivedbytes, (char *)(b64), 341); 456 | //fwrite(b64, sizeof(char), j, stdout); 457 | 458 | char buff_up[TX_BUFF_SIZE]; /* buffer to compose the upstream packet */ 459 | int buff_index=0; 460 | 461 | /* gateway <-> MAC protocol variables */ 462 | //static uint32_t net_mac_h; /* Most Significant Nibble, network order */ 463 | //static uint32_t net_mac_l; /* Least Significant Nibble, network order */ 464 | 465 | /* pre-fill the data buffer with fixed fields */ 466 | buff_up[0] = PROTOCOL_VERSION; 467 | buff_up[3] = PKT_PUSH_DATA; 468 | 469 | /* process some of the configuration variables */ 470 | //net_mac_h = htonl((uint32_t)(0xFFFFFFFF & (lgwm>>32))); 471 | //net_mac_l = htonl((uint32_t)(0xFFFFFFFF & lgwm )); 472 | //*(uint32_t *)(buff_up + 4) = net_mac_h; 473 | //*(uint32_t *)(buff_up + 8) = net_mac_l; 474 | 475 | buff_up[4] = (unsigned char)ifr.ifr_hwaddr.sa_data[0]; 476 | buff_up[5] = (unsigned char)ifr.ifr_hwaddr.sa_data[1]; 477 | buff_up[6] = (unsigned char)ifr.ifr_hwaddr.sa_data[2]; 478 | buff_up[7] = 0xFF; 479 | buff_up[8] = 0xFF; 480 | buff_up[9] = (unsigned char)ifr.ifr_hwaddr.sa_data[3]; 481 | buff_up[10] = (unsigned char)ifr.ifr_hwaddr.sa_data[4]; 482 | buff_up[11] = (unsigned char)ifr.ifr_hwaddr.sa_data[5]; 483 | 484 | /* start composing datagram with the header */ 485 | uint8_t token_h = (uint8_t)rand(); /* random token */ 486 | uint8_t token_l = (uint8_t)rand(); /* random token */ 487 | buff_up[1] = token_h; 488 | buff_up[2] = token_l; 489 | buff_index = 12; /* 12-byte header */ 490 | 491 | // TODO: tmst can jump is time is (re)set, not good. 492 | struct timeval now; 493 | gettimeofday(&now, NULL); 494 | uint32_t tmst = (uint32_t)(now.tv_sec*1000000 + now.tv_usec); 495 | 496 | /* start of JSON structure */ 497 | memcpy((void *)(buff_up + buff_index), (void *)"{\"rxpk\":[", 9); 498 | buff_index += 9; 499 | buff_up[buff_index] = '{'; 500 | ++buff_index; 501 | j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, "\"tmst\":%u", tmst); 502 | buff_index += j; 503 | j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"chan\":%1u,\"rfch\":%1u,\"freq\":%.6lf", 0, 0, (double)freq/1000000); 504 | buff_index += j; 505 | memcpy((void *)(buff_up + buff_index), (void *)",\"stat\":1", 9); 506 | buff_index += 9; 507 | memcpy((void *)(buff_up + buff_index), (void *)",\"modu\":\"LORA\"", 14); 508 | buff_index += 14; 509 | /* Lora datarate & bandwidth, 16-19 useful chars */ 510 | switch (sf) { 511 | case SF7: 512 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF7", 12); 513 | buff_index += 12; 514 | break; 515 | case SF8: 516 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF8", 12); 517 | buff_index += 12; 518 | break; 519 | case SF9: 520 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF9", 12); 521 | buff_index += 12; 522 | break; 523 | case SF10: 524 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF10", 13); 525 | buff_index += 13; 526 | break; 527 | case SF11: 528 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF11", 13); 529 | buff_index += 13; 530 | break; 531 | case SF12: 532 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF12", 13); 533 | buff_index += 13; 534 | break; 535 | default: 536 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF?", 12); 537 | buff_index += 12; 538 | } 539 | memcpy((void *)(buff_up + buff_index), (void *)"BW125\"", 6); 540 | buff_index += 6; 541 | memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"4/5\"", 13); 542 | buff_index += 13; 543 | j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"lsnr\":%li", SNR); 544 | buff_index += j; 545 | j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"rssi\":%d,\"size\":%u", readRegister(0x1A)-rssicorr, receivedbytes); 546 | buff_index += j; 547 | memcpy((void *)(buff_up + buff_index), (void *)",\"data\":\"", 9); 548 | buff_index += 9; 549 | j = bin_to_b64((uint8_t *)message, receivedbytes, (char *)(buff_up + buff_index), 341); 550 | buff_index += j; 551 | buff_up[buff_index] = '"'; 552 | ++buff_index; 553 | 554 | /* End of packet serialization */ 555 | buff_up[buff_index] = '}'; 556 | ++buff_index; 557 | buff_up[buff_index] = ']'; 558 | ++buff_index; 559 | /* end of JSON datagram payload */ 560 | buff_up[buff_index] = '}'; 561 | ++buff_index; 562 | buff_up[buff_index] = 0; /* add string terminator, for safety */ 563 | 564 | printf("rxpk update: %s\n", (char *)(buff_up + 12)); /* DEBUG: display JSON payload */ 565 | 566 | //send the messages 567 | sendudp(buff_up, buff_index); 568 | 569 | fflush(stdout); 570 | 571 | } // received a message 572 | 573 | } // dio0=1 574 | } 575 | 576 | void parseCommandline(int argc, char *argv[]) 577 | { 578 | for(int i=1; i> port; 589 | server = server.substr(0, server.find(':')); 590 | if(port == 0) 591 | { 592 | std::stringstream error; 593 | error << "Invalid server string given, cannot parse given port number: "; 594 | error << parser.str(); 595 | die(error.str().c_str()); 596 | } 597 | else if(port < 1024) 598 | { 599 | die("Don't use priviledged ports < 1024 for sending"); 600 | } 601 | } 602 | hostToIp(server.c_str(), ip, INET6_ADDRSTRLEN); 603 | std::string address = ip; 604 | serverList.insert(std::make_pair(server, std::make_pair(address, port))); 605 | } 606 | else if(0 == strncasecmp(argv[i], "-sf", 3)) 607 | { 608 | int sFactor=0; 609 | std::string sfString=argv[i]+3; 610 | std::stringstream parser(sfString); 611 | parser >> sFactor; 612 | 613 | switch(sFactor) 614 | { 615 | case 7: 616 | sf = SF7; 617 | break; 618 | case 8: 619 | sf = SF8; 620 | break; 621 | case 9: 622 | sf = SF9; 623 | break; 624 | case 10: 625 | sf = SF10; 626 | break; 627 | case 11: 628 | sf = SF11; 629 | break; 630 | case 12: 631 | sf = SF12; 632 | break; 633 | default: 634 | die("Invalid spreading factor specified, valid range is 7-12"); 635 | } 636 | } 637 | else if(0 == strncasecmp(argv[i], "-f", 2)) 638 | { 639 | uint32_t frequency=0; 640 | std::string fString=argv[i]+2; 641 | std::stringstream parser(fString); 642 | parser >> frequency; 643 | 644 | if(frequency >= EU868_J4 && frequency <= EU868_F6) 645 | { 646 | //EUR Channels 647 | if(frequency == EU868_F1 648 | || frequency == EU868_F2 649 | || frequency == EU868_F3 650 | || frequency == EU868_F4 651 | || frequency == EU868_F5 652 | || frequency == EU868_F6 653 | || frequency == EU868_J4 654 | || frequency == EU868_J5 655 | || frequency == EU868_J6) 656 | { 657 | freq = frequency; 658 | } 659 | else 660 | { 661 | std::stringstream error; 662 | error << "Invalid EU channel specified: "; 663 | error << frequency; 664 | error << "Hz"; 665 | die(error.str().c_str()); 666 | } 667 | } 668 | else if(frequency >=902000000 && frequency <= 928000000) 669 | { 670 | //Todo valid channels in the US 671 | freq = frequency; 672 | } 673 | else 674 | { 675 | std::stringstream error; 676 | error << "The specified frequency "; 677 | error << frequency; 678 | error << "Hz is outside of any valid ISM band."; 679 | die(error.str().c_str()); 680 | } 681 | } 682 | else if( 0 == strncasecmp(argv[i], "-e", 2)) 683 | { 684 | std::string email_address = argv[i]+2; 685 | strncpy(email, email_address.c_str(), 40); 686 | } 687 | else if ( 0 == strncasecmp(argv[i], "-lat", 4)) 688 | { 689 | std::string latitude = argv[i]+4; 690 | lat = ::atof(latitude.c_str()); 691 | std::cout << "Latitude: " << lat << std::endl; 692 | } 693 | else if ( 0 == strncasecmp(argv[i], "-lon", 4)) 694 | { 695 | std::string longitude = argv[i]+4; 696 | lon = ::atof(longitude.c_str()); 697 | std::cout << "Longitude: " << lon << std::endl; 698 | } 699 | else if ( 0 == strncasecmp(argv[i], "-alt", 4)) 700 | { 701 | std::string altitude = argv[i]+4; 702 | alt = ::atoi(altitude.c_str()); 703 | std::cout << "Altitude: " << alt << std::endl; 704 | } 705 | else if ( 0 == strncasecmp(argv[i], "-ss", 3)) 706 | { 707 | std::string ss = argv[i]+3; 708 | ssPin = ::atoi(ss.c_str()); 709 | std::cout << "SS Pin: " << ssPin << std::endl; 710 | } 711 | else if ( 0 == strncasecmp(argv[i], "-dio", 4)) 712 | { 713 | std::string dio = argv[i]+4; 714 | dio0 = ::atoi(dio.c_str()); 715 | std::cout << "DIO0 Pin: " << dio0 << std::endl; 716 | } 717 | else if ( 0 == strncasecmp(argv[i], "-rst", 4)) 718 | { 719 | std::string rst = argv[i]+4; 720 | RST = ::atoi(rst.c_str()); 721 | std::cout << "RST Pin: " << RST << std::endl; 722 | } 723 | else 724 | { 725 | std::cout << "Usage: " << argv[0] << " [-uSERVERNAMEORIP[:PORT]] [-sf(7-12)] [-fFREQUENCYHZ] [-eEMAIL] [-latCOORD] [-lonCOORD] [-altALTITUDEMETERS] [-dioPIN] [-rstPIN] [-ssPIN]" << std::endl; 726 | std::cout << " Example: " << argv[0] << " -ucroft.thethings.girovito.nl" << std::endl; 727 | std::cout << " Example: " << argv[0] << " -ucroft.thethings.girovito.nl:1700" << std::endl; 728 | std::cout << " Example: " << argv[0] << " -u192.168.0.111 -sf8 -f868100000" << std::endl; 729 | std::cout << " Multiple servers can be supplied with multiple -u parameters." << std::endl; 730 | std::cout << " When no server is supplied, the default server is used (" << DEFAULTSERVER << ")." << std::endl; 731 | std::cout << " When no port is supplied, the default port is used (" << DEFAULTPORT << ")." << std::endl; 732 | std::cout << " Spreading factor can be specified using -sf7 to -sf12 (default value is SF7)" << std::endl; 733 | std::cout << " Listening frequency can be specified using -fFREQUENCY in Hz. (default 868100000)" << std::endl; 734 | std::cout << " European frequencies are checked for a valid Lora channel, valid Frequencies are: " << std::endl; 735 | std::cout << " 868100000Hz, 868300000Hz, 868500000Hz," << std::endl; 736 | std::cout << " 868850000Hz, 869050000Hz, 869525000Hz," << std::endl; 737 | std::cout << " 864100000Hz, 864300000Hz, 864500000Hz" << std::endl; 738 | std::cout << " The 900MHz Range (902-928MHz) is currently not validated." << std::endl; 739 | std::cout << " An email address can be supplied with -e (default is empty): " << std::endl; 740 | std::cout << " Example: -ebla@foo.com" << std::endl; 741 | std::cout << " Coordinates can be passed via -lat (default is 0.0), -lon (default is 0.0) and -alt (default is 0): " << std::endl; 742 | std::cout << " Example: -lat33.0 -lon-100.0 -alt10" << std::endl; 743 | std::cout << " Pin mapping can be passed via" << std::endl; 744 | std::cout << " -dio (default: " << dio0 <<")" << std::endl; 745 | std::cout << " -ss (default: " << ssPin <<")" << std::endl; 746 | std::cout << " -rst (default: " << RST <<")" << std::endl; 747 | std::cout << std::endl; 748 | 749 | std::stringstream error; 750 | error << "Unknown command line parameter: "; 751 | error << argv[i]; 752 | die(error.str().c_str()); 753 | } 754 | } 755 | 756 | if( 0 == serverList.size()) 757 | { 758 | //Add the default server, if no servers are given on the command line, to make it work as before 759 | //without any parameters supplied. 760 | char ip[INET6_ADDRSTRLEN]; 761 | hostToIp(DEFAULTSERVER, ip, INET6_ADDRSTRLEN); 762 | std::string server = DEFAULTSERVER; 763 | std::string address = ip; 764 | serverList.insert(std::make_pair(server, std::make_pair(address, DEFAULTPORT))); 765 | } 766 | 767 | } 768 | 769 | int main(int argc, char *argv[] ) { 770 | 771 | parseCommandline(argc, argv); 772 | std::stringstream desc; 773 | desc << "Single channel, "; 774 | desc << (double)freq/1000000 << "MHz, "; 775 | desc << "SF" << sf; 776 | strncpy(description, desc.str().c_str(), 64); 777 | struct timeval nowtime; 778 | uint32_t lasttime; 779 | 780 | wiringPiSetup () ; 781 | pinMode(ssPin, OUTPUT); 782 | pinMode(dio0, INPUT); 783 | pinMode(RST, OUTPUT); 784 | 785 | if(wiringPiSPISetup(CHANNEL, 500000) < 0) 786 | fprintf (stderr, "SPI Setup failed: %s\n", strerror (errno)); 787 | //cout << "Init result: " << fd << endl; 788 | 789 | SetupLoRa(); 790 | 791 | if ( (s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) 792 | { 793 | die("socket"); 794 | } 795 | memset((char *) &si_other, 0, sizeof(si_other)); 796 | si_other.sin_family = AF_INET; 797 | si_other.sin_port = htons(DEFAULTPORT); 798 | 799 | ifr.ifr_addr.sa_family = AF_INET; 800 | strncpy(ifr.ifr_name, "eth0", IFNAMSIZ-1); // can we rely on eth0? 801 | ioctl(s, SIOCGIFHWADDR, &ifr); 802 | 803 | /* display result */ 804 | printf("Gateway ID: %.2x:%.2x:%.2x:ff:ff:%.2x:%.2x:%.2x\n", 805 | (unsigned char)ifr.ifr_hwaddr.sa_data[0], 806 | (unsigned char)ifr.ifr_hwaddr.sa_data[1], 807 | (unsigned char)ifr.ifr_hwaddr.sa_data[2], 808 | (unsigned char)ifr.ifr_hwaddr.sa_data[3], 809 | (unsigned char)ifr.ifr_hwaddr.sa_data[4], 810 | (unsigned char)ifr.ifr_hwaddr.sa_data[5]); 811 | 812 | printf("Listening at SF%i on %.6lf Mhz.\n", sf,(double)freq/1000000); 813 | 814 | std::map >::iterator iter; 815 | for(iter = serverList.begin(); iter != serverList.end(); iter++) 816 | { 817 | printf("Forwarding packets to: %s (%s), port: %d\n", iter->first.c_str(), iter->second.first.c_str(), iter->second.second); 818 | } 819 | 820 | printf("------------------\n"); 821 | 822 | while(1) { 823 | 824 | receivepacket(); 825 | 826 | gettimeofday(&nowtime, NULL); 827 | uint32_t nowseconds = (uint32_t)(nowtime.tv_sec); 828 | if (nowseconds - lasttime >= 30) { 829 | lasttime = nowseconds; 830 | sendstat(); 831 | cp_nb_rx_rcv = 0; 832 | cp_nb_rx_ok = 0; 833 | cp_up_pkt_fwd = 0; 834 | } 835 | delay(1); 836 | } 837 | 838 | return (0); 839 | 840 | } 841 | --------------------------------------------------------------------------------