├── LICENSE ├── Makefile ├── README.md ├── base64.c ├── base64.h └── main.cpp /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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *** IMPORTANT *** 2 | 3 | Please note this repository is deprecated, and the code is not up-to-date to use on The Things Network. 4 | 5 | This repository will not be further maintained. Please find another repository if you want to deploy a single channel gateway. 6 | 7 | 8 | Single Channel LoRaWAN Gateway 9 | ============================== 10 | This repository contains a proof-of-concept implementation of a single 11 | channel LoRaWAN gateway. 12 | 13 | It has been tested on the Raspberry Pi platform, using a Semtech SX1272 14 | transceiver (HopeRF RFM92W), and SX1276 (HopeRF RFM95W). 15 | 16 | The code is for testing and development purposes only, and is not meant 17 | for production usage. 18 | 19 | Part of the source has been copied from the Semtech Packet Forwarder 20 | (with permission). 21 | 22 | Features 23 | -------- 24 | - listen on configurable frequency and spreading factor 25 | - SF7 to SF12 26 | - status updates 27 | - can forward to two servers 28 | 29 | Not (yet) supported: 30 | - PACKET_PUSH_ACK processing 31 | - SF7BW250 modulation 32 | - FSK modulation 33 | - downstream messages (tx) 34 | 35 | Dependencies 36 | ------------ 37 | - SPI needs to be enabled on the Raspberry Pi (use raspi-config) 38 | - WiringPi: a GPIO access library written in C for the BCM2835 39 | used in the Raspberry Pi. 40 | sudo apt-get install wiringpi 41 | see http://wiringpi.com 42 | - Run packet forwarder as root 43 | 44 | Connections 45 | ----------- 46 | SX1272 - Raspberry 47 | 48 | 3.3V - 3.3V (header pin #1) 49 | GND - GND (pin #6) 50 | MISO - MISO (pin #21) 51 | MOSI - MOSI (pin #19) 52 | SCK - CLK (pin #23) 53 | NSS - GPIO6 (pin #22) 54 | DIO0 - GPIO7 (pin #7) 55 | RST - GPIO0 (pin #11) 56 | 57 | Configuration 58 | ------------- 59 | 60 | Defaults: 61 | 62 | - LoRa: SF7 at 868.1 Mhz 63 | - Server: 54.229.214.112, port 1700 (The Things Network: croft.thethings.girovito.nl) 64 | 65 | Edit source node (main.cpp) to change configuration (look for: "Configure these values!"). 66 | 67 | Please set location, email and description. 68 | 69 | License 70 | ------- 71 | The source files in this repository are made available under the Eclipse 72 | Public License v1.0, except for the base64 implementation, that has been 73 | copied from the Semtech Packet Forwader. 74 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 23 | #include 24 | #include 25 | 26 | using namespace std; 27 | 28 | #include "base64.h" 29 | 30 | #include 31 | #include 32 | 33 | typedef bool boolean; 34 | typedef unsigned char byte; 35 | 36 | static const int CHANNEL = 0; 37 | 38 | byte currentMode = 0x81; 39 | 40 | char message[256]; 41 | char b64[256]; 42 | 43 | bool sx1272 = true; 44 | 45 | byte receivedbytes; 46 | 47 | struct sockaddr_in si_other; 48 | int s, slen=sizeof(si_other); 49 | struct ifreq ifr; 50 | 51 | uint32_t cp_nb_rx_rcv; 52 | uint32_t cp_nb_rx_ok; 53 | uint32_t cp_nb_rx_bad; 54 | uint32_t cp_nb_rx_nocrc; 55 | uint32_t cp_up_pkt_fwd; 56 | 57 | enum sf_t { SF7=7, SF8, SF9, SF10, SF11, SF12 }; 58 | 59 | /******************************************************************************* 60 | * 61 | * Configure these values! 62 | * 63 | *******************************************************************************/ 64 | 65 | // SX1272 - Raspberry connections 66 | int ssPin = 6; 67 | int dio0 = 7; 68 | int RST = 0; 69 | 70 | // Set spreading factor (SF7 - SF12) 71 | sf_t sf = SF7; 72 | 73 | // Set center frequency 74 | uint32_t freq = 868100000; // in Mhz! (868.1) 75 | 76 | // Set location 77 | float lat=0.0; 78 | float lon=0.0; 79 | int alt=0; 80 | 81 | /* Informal status fields */ 82 | static char platform[24] = "Single Channel Gateway"; /* platform definition */ 83 | static char email[40] = ""; /* used for contact email */ 84 | static char description[64] = ""; /* used for free form description */ 85 | 86 | // define servers 87 | // TODO: use host names and dns 88 | #define SERVER1 "54.72.145.119" // The Things Network: croft.thethings.girovito.nl 89 | //#define SERVER2 "192.168.1.10" // local 90 | #define PORT 1700 // The port on which to send data 91 | 92 | // ############################################# 93 | // ############################################# 94 | 95 | #define REG_FIFO 0x00 96 | #define REG_FIFO_ADDR_PTR 0x0D 97 | #define REG_FIFO_TX_BASE_AD 0x0E 98 | #define REG_FIFO_RX_BASE_AD 0x0F 99 | #define REG_RX_NB_BYTES 0x13 100 | #define REG_OPMODE 0x01 101 | #define REG_FIFO_RX_CURRENT_ADDR 0x10 102 | #define REG_IRQ_FLAGS 0x12 103 | #define REG_DIO_MAPPING_1 0x40 104 | #define REG_DIO_MAPPING_2 0x41 105 | #define REG_MODEM_CONFIG 0x1D 106 | #define REG_MODEM_CONFIG2 0x1E 107 | #define REG_MODEM_CONFIG3 0x26 108 | #define REG_SYMB_TIMEOUT_LSB 0x1F 109 | #define REG_PKT_SNR_VALUE 0x19 110 | #define REG_PAYLOAD_LENGTH 0x22 111 | #define REG_IRQ_FLAGS_MASK 0x11 112 | #define REG_MAX_PAYLOAD_LENGTH 0x23 113 | #define REG_HOP_PERIOD 0x24 114 | #define REG_SYNC_WORD 0x39 115 | #define REG_VERSION 0x42 116 | 117 | #define SX72_MODE_RX_CONTINUOS 0x85 118 | #define SX72_MODE_TX 0x83 119 | #define SX72_MODE_SLEEP 0x80 120 | #define SX72_MODE_STANDBY 0x81 121 | 122 | #define PAYLOAD_LENGTH 0x40 123 | 124 | // LOW NOISE AMPLIFIER 125 | #define REG_LNA 0x0C 126 | #define LNA_MAX_GAIN 0x23 127 | #define LNA_OFF_GAIN 0x00 128 | #define LNA_LOW_GAIN 0x20 129 | 130 | // CONF REG 131 | #define REG1 0x0A 132 | #define REG2 0x84 133 | 134 | #define SX72_MC2_FSK 0x00 135 | #define SX72_MC2_SF7 0x70 136 | #define SX72_MC2_SF8 0x80 137 | #define SX72_MC2_SF9 0x90 138 | #define SX72_MC2_SF10 0xA0 139 | #define SX72_MC2_SF11 0xB0 140 | #define SX72_MC2_SF12 0xC0 141 | 142 | #define SX72_MC1_LOW_DATA_RATE_OPTIMIZE 0x01 // mandated for SF11 and SF12 143 | 144 | // FRF 145 | #define REG_FRF_MSB 0x06 146 | #define REG_FRF_MID 0x07 147 | #define REG_FRF_LSB 0x08 148 | 149 | #define FRF_MSB 0xD9 // 868.1 Mhz 150 | #define FRF_MID 0x06 151 | #define FRF_LSB 0x66 152 | 153 | #define BUFLEN 2048 //Max length of buffer 154 | 155 | #define PROTOCOL_VERSION 1 156 | #define PKT_PUSH_DATA 0 157 | #define PKT_PUSH_ACK 1 158 | #define PKT_PULL_DATA 2 159 | #define PKT_PULL_RESP 3 160 | #define PKT_PULL_ACK 4 161 | 162 | #define TX_BUFF_SIZE 2048 163 | #define STATUS_SIZE 1024 164 | 165 | void die(const char *s) 166 | { 167 | perror(s); 168 | exit(1); 169 | } 170 | 171 | void selectreceiver() 172 | { 173 | digitalWrite(ssPin, LOW); 174 | } 175 | 176 | void unselectreceiver() 177 | { 178 | digitalWrite(ssPin, HIGH); 179 | } 180 | 181 | byte readRegister(byte addr) 182 | { 183 | unsigned char spibuf[2]; 184 | 185 | selectreceiver(); 186 | spibuf[0] = addr & 0x7F; 187 | spibuf[1] = 0x00; 188 | wiringPiSPIDataRW(CHANNEL, spibuf, 2); 189 | unselectreceiver(); 190 | 191 | return spibuf[1]; 192 | } 193 | 194 | void writeRegister(byte addr, byte value) 195 | { 196 | unsigned char spibuf[2]; 197 | 198 | spibuf[0] = addr | 0x80; 199 | spibuf[1] = value; 200 | selectreceiver(); 201 | wiringPiSPIDataRW(CHANNEL, spibuf, 2); 202 | 203 | unselectreceiver(); 204 | } 205 | 206 | 207 | boolean receivePkt(char *payload) 208 | { 209 | 210 | // clear rxDone 211 | writeRegister(REG_IRQ_FLAGS, 0x40); 212 | 213 | int irqflags = readRegister(REG_IRQ_FLAGS); 214 | 215 | cp_nb_rx_rcv++; 216 | 217 | // payload crc: 0x20 218 | if((irqflags & 0x20) == 0x20) 219 | { 220 | printf("CRC error\n"); 221 | writeRegister(REG_IRQ_FLAGS, 0x20); 222 | return false; 223 | } else { 224 | 225 | cp_nb_rx_ok++; 226 | 227 | byte currentAddr = readRegister(REG_FIFO_RX_CURRENT_ADDR); 228 | byte receivedCount = readRegister(REG_RX_NB_BYTES); 229 | receivedbytes = receivedCount; 230 | 231 | writeRegister(REG_FIFO_ADDR_PTR, currentAddr); 232 | 233 | for(int i = 0; i < receivedCount; i++) 234 | { 235 | payload[i] = (char)readRegister(REG_FIFO); 236 | } 237 | } 238 | return true; 239 | } 240 | 241 | void SetupLoRa() 242 | { 243 | 244 | digitalWrite(RST, HIGH); 245 | delay(100); 246 | digitalWrite(RST, LOW); 247 | delay(100); 248 | 249 | byte version = readRegister(REG_VERSION); 250 | 251 | if (version == 0x22) { 252 | // sx1272 253 | printf("SX1272 detected, starting.\n"); 254 | sx1272 = true; 255 | } else { 256 | // sx1276? 257 | digitalWrite(RST, LOW); 258 | delay(100); 259 | digitalWrite(RST, HIGH); 260 | delay(100); 261 | version = readRegister(REG_VERSION); 262 | if (version == 0x12) { 263 | // sx1276 264 | printf("SX1276 detected, starting.\n"); 265 | sx1272 = false; 266 | } else { 267 | printf("Unrecognized transceiver.\n"); 268 | //printf("Version: 0x%x\n",version); 269 | exit(1); 270 | } 271 | } 272 | 273 | writeRegister(REG_OPMODE, SX72_MODE_SLEEP); 274 | 275 | // set frequency 276 | uint64_t frf = ((uint64_t)freq << 19) / 32000000; 277 | writeRegister(REG_FRF_MSB, (uint8_t)(frf>>16) ); 278 | writeRegister(REG_FRF_MID, (uint8_t)(frf>> 8) ); 279 | writeRegister(REG_FRF_LSB, (uint8_t)(frf>> 0) ); 280 | 281 | writeRegister(REG_SYNC_WORD, 0x34); // LoRaWAN public sync word 282 | 283 | if (sx1272) { 284 | if (sf == SF11 || sf == SF12) { 285 | writeRegister(REG_MODEM_CONFIG,0x0B); 286 | } else { 287 | writeRegister(REG_MODEM_CONFIG,0x0A); 288 | } 289 | writeRegister(REG_MODEM_CONFIG2,(sf<<4) | 0x04); 290 | } else { 291 | if (sf == SF11 || sf == SF12) { 292 | writeRegister(REG_MODEM_CONFIG3,0x0C); 293 | } else { 294 | writeRegister(REG_MODEM_CONFIG3,0x04); 295 | } 296 | writeRegister(REG_MODEM_CONFIG,0x72); 297 | writeRegister(REG_MODEM_CONFIG2,(sf<<4) | 0x04); 298 | } 299 | 300 | if (sf == SF10 || sf == SF11 || sf == SF12) { 301 | writeRegister(REG_SYMB_TIMEOUT_LSB,0x05); 302 | } else { 303 | writeRegister(REG_SYMB_TIMEOUT_LSB,0x08); 304 | } 305 | writeRegister(REG_MAX_PAYLOAD_LENGTH,0x80); 306 | writeRegister(REG_PAYLOAD_LENGTH,PAYLOAD_LENGTH); 307 | writeRegister(REG_HOP_PERIOD,0xFF); 308 | writeRegister(REG_FIFO_ADDR_PTR, readRegister(REG_FIFO_RX_BASE_AD)); 309 | 310 | // Set Continous Receive Mode 311 | writeRegister(REG_LNA, LNA_MAX_GAIN); // max lna gain 312 | writeRegister(REG_OPMODE, SX72_MODE_RX_CONTINUOS); 313 | 314 | } 315 | 316 | void sendudp(char *msg, int length) { 317 | 318 | //send the update 319 | #ifdef SERVER1 320 | inet_aton(SERVER1 , &si_other.sin_addr); 321 | if (sendto(s, (char *)msg, length, 0 , (struct sockaddr *) &si_other, slen)==-1) 322 | { 323 | die("sendto()"); 324 | } 325 | #endif 326 | 327 | #ifdef SERVER2 328 | inet_aton(SERVER2 , &si_other.sin_addr); 329 | if (sendto(s, (char *)msg, length , 0 , (struct sockaddr *) &si_other, slen)==-1) 330 | { 331 | die("sendto()"); 332 | } 333 | #endif 334 | } 335 | 336 | void sendstat() { 337 | 338 | static char status_report[STATUS_SIZE]; /* status report as a JSON object */ 339 | char stat_timestamp[24]; 340 | time_t t; 341 | 342 | int stat_index=0; 343 | 344 | /* pre-fill the data buffer with fixed fields */ 345 | status_report[0] = PROTOCOL_VERSION; 346 | status_report[3] = PKT_PUSH_DATA; 347 | 348 | status_report[4] = (unsigned char)ifr.ifr_hwaddr.sa_data[0]; 349 | status_report[5] = (unsigned char)ifr.ifr_hwaddr.sa_data[1]; 350 | status_report[6] = (unsigned char)ifr.ifr_hwaddr.sa_data[2]; 351 | status_report[7] = 0xFF; 352 | status_report[8] = 0xFF; 353 | status_report[9] = (unsigned char)ifr.ifr_hwaddr.sa_data[3]; 354 | status_report[10] = (unsigned char)ifr.ifr_hwaddr.sa_data[4]; 355 | status_report[11] = (unsigned char)ifr.ifr_hwaddr.sa_data[5]; 356 | 357 | /* start composing datagram with the header */ 358 | uint8_t token_h = (uint8_t)rand(); /* random token */ 359 | uint8_t token_l = (uint8_t)rand(); /* random token */ 360 | status_report[1] = token_h; 361 | status_report[2] = token_l; 362 | stat_index = 12; /* 12-byte header */ 363 | 364 | /* get timestamp for statistics */ 365 | t = time(NULL); 366 | strftime(stat_timestamp, sizeof stat_timestamp, "%F %T %Z", gmtime(&t)); 367 | 368 | 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); 369 | stat_index += j; 370 | status_report[stat_index] = 0; /* add string terminator, for safety */ 371 | 372 | printf("stat update: %s\n", (char *)(status_report+12)); /* DEBUG: display JSON stat */ 373 | 374 | //send the update 375 | sendudp(status_report, stat_index); 376 | 377 | } 378 | 379 | void receivepacket() { 380 | 381 | long int SNR; 382 | int rssicorr; 383 | 384 | if(digitalRead(dio0) == 1) 385 | { 386 | if(receivePkt(message)) { 387 | byte value = readRegister(REG_PKT_SNR_VALUE); 388 | if( value & 0x80 ) // The SNR sign bit is 1 389 | { 390 | // Invert and divide by 4 391 | value = ( ( ~value + 1 ) & 0xFF ) >> 2; 392 | SNR = -value; 393 | } 394 | else 395 | { 396 | // Divide by 4 397 | SNR = ( value & 0xFF ) >> 2; 398 | } 399 | 400 | if (sx1272) { 401 | rssicorr = 139; 402 | } else { 403 | rssicorr = 157; 404 | } 405 | 406 | printf("Packet RSSI: %d, ",readRegister(0x1A)-rssicorr); 407 | printf("RSSI: %d, ",readRegister(0x1B)-rssicorr); 408 | printf("SNR: %li, ",SNR); 409 | printf("Length: %i",(int)receivedbytes); 410 | printf("\n"); 411 | 412 | int j; 413 | j = bin_to_b64((uint8_t *)message, receivedbytes, (char *)(b64), 341); 414 | //fwrite(b64, sizeof(char), j, stdout); 415 | 416 | char buff_up[TX_BUFF_SIZE]; /* buffer to compose the upstream packet */ 417 | int buff_index=0; 418 | 419 | /* gateway <-> MAC protocol variables */ 420 | //static uint32_t net_mac_h; /* Most Significant Nibble, network order */ 421 | //static uint32_t net_mac_l; /* Least Significant Nibble, network order */ 422 | 423 | /* pre-fill the data buffer with fixed fields */ 424 | buff_up[0] = PROTOCOL_VERSION; 425 | buff_up[3] = PKT_PUSH_DATA; 426 | 427 | /* process some of the configuration variables */ 428 | //net_mac_h = htonl((uint32_t)(0xFFFFFFFF & (lgwm>>32))); 429 | //net_mac_l = htonl((uint32_t)(0xFFFFFFFF & lgwm )); 430 | //*(uint32_t *)(buff_up + 4) = net_mac_h; 431 | //*(uint32_t *)(buff_up + 8) = net_mac_l; 432 | 433 | buff_up[4] = (unsigned char)ifr.ifr_hwaddr.sa_data[0]; 434 | buff_up[5] = (unsigned char)ifr.ifr_hwaddr.sa_data[1]; 435 | buff_up[6] = (unsigned char)ifr.ifr_hwaddr.sa_data[2]; 436 | buff_up[7] = 0xFF; 437 | buff_up[8] = 0xFF; 438 | buff_up[9] = (unsigned char)ifr.ifr_hwaddr.sa_data[3]; 439 | buff_up[10] = (unsigned char)ifr.ifr_hwaddr.sa_data[4]; 440 | buff_up[11] = (unsigned char)ifr.ifr_hwaddr.sa_data[5]; 441 | 442 | /* start composing datagram with the header */ 443 | uint8_t token_h = (uint8_t)rand(); /* random token */ 444 | uint8_t token_l = (uint8_t)rand(); /* random token */ 445 | buff_up[1] = token_h; 446 | buff_up[2] = token_l; 447 | buff_index = 12; /* 12-byte header */ 448 | 449 | // TODO: tmst can jump is time is (re)set, not good. 450 | struct timeval now; 451 | gettimeofday(&now, NULL); 452 | uint32_t tmst = (uint32_t)(now.tv_sec*1000000 + now.tv_usec); 453 | 454 | /* start of JSON structure */ 455 | memcpy((void *)(buff_up + buff_index), (void *)"{\"rxpk\":[", 9); 456 | buff_index += 9; 457 | buff_up[buff_index] = '{'; 458 | ++buff_index; 459 | j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, "\"tmst\":%u", tmst); 460 | buff_index += j; 461 | j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"chan\":%1u,\"rfch\":%1u,\"freq\":%.6lf", 0, 0, (double)freq/1000000); 462 | buff_index += j; 463 | memcpy((void *)(buff_up + buff_index), (void *)",\"stat\":1", 9); 464 | buff_index += 9; 465 | memcpy((void *)(buff_up + buff_index), (void *)",\"modu\":\"LORA\"", 14); 466 | buff_index += 14; 467 | /* Lora datarate & bandwidth, 16-19 useful chars */ 468 | switch (sf) { 469 | case SF7: 470 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF7", 12); 471 | buff_index += 12; 472 | break; 473 | case SF8: 474 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF8", 12); 475 | buff_index += 12; 476 | break; 477 | case SF9: 478 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF9", 12); 479 | buff_index += 12; 480 | break; 481 | case SF10: 482 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF10", 13); 483 | buff_index += 13; 484 | break; 485 | case SF11: 486 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF11", 13); 487 | buff_index += 13; 488 | break; 489 | case SF12: 490 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF12", 13); 491 | buff_index += 13; 492 | break; 493 | default: 494 | memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF?", 12); 495 | buff_index += 12; 496 | } 497 | memcpy((void *)(buff_up + buff_index), (void *)"BW125\"", 6); 498 | buff_index += 6; 499 | memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"4/5\"", 13); 500 | buff_index += 13; 501 | j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"lsnr\":%li", SNR); 502 | buff_index += j; 503 | j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"rssi\":%d,\"size\":%u", readRegister(0x1A)-rssicorr, receivedbytes); 504 | buff_index += j; 505 | memcpy((void *)(buff_up + buff_index), (void *)",\"data\":\"", 9); 506 | buff_index += 9; 507 | j = bin_to_b64((uint8_t *)message, receivedbytes, (char *)(buff_up + buff_index), 341); 508 | buff_index += j; 509 | buff_up[buff_index] = '"'; 510 | ++buff_index; 511 | 512 | /* End of packet serialization */ 513 | buff_up[buff_index] = '}'; 514 | ++buff_index; 515 | buff_up[buff_index] = ']'; 516 | ++buff_index; 517 | /* end of JSON datagram payload */ 518 | buff_up[buff_index] = '}'; 519 | ++buff_index; 520 | buff_up[buff_index] = 0; /* add string terminator, for safety */ 521 | 522 | printf("rxpk update: %s\n", (char *)(buff_up + 12)); /* DEBUG: display JSON payload */ 523 | 524 | //send the messages 525 | sendudp(buff_up, buff_index); 526 | 527 | fflush(stdout); 528 | 529 | } // received a message 530 | 531 | } // dio0=1 532 | } 533 | 534 | int main () { 535 | 536 | struct timeval nowtime; 537 | uint32_t lasttime; 538 | 539 | wiringPiSetup () ; 540 | pinMode(ssPin, OUTPUT); 541 | pinMode(dio0, INPUT); 542 | pinMode(RST, OUTPUT); 543 | 544 | //int fd = 545 | wiringPiSPISetup(CHANNEL, 500000); 546 | //cout << "Init result: " << fd << endl; 547 | 548 | SetupLoRa(); 549 | 550 | if ( (s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) 551 | { 552 | die("socket"); 553 | } 554 | memset((char *) &si_other, 0, sizeof(si_other)); 555 | si_other.sin_family = AF_INET; 556 | si_other.sin_port = htons(PORT); 557 | 558 | ifr.ifr_addr.sa_family = AF_INET; 559 | strncpy(ifr.ifr_name, "eth0", IFNAMSIZ-1); // can we rely on eth0? 560 | ioctl(s, SIOCGIFHWADDR, &ifr); 561 | 562 | /* display result */ 563 | printf("Gateway ID: %.2x:%.2x:%.2x:ff:ff:%.2x:%.2x:%.2x\n", 564 | (unsigned char)ifr.ifr_hwaddr.sa_data[0], 565 | (unsigned char)ifr.ifr_hwaddr.sa_data[1], 566 | (unsigned char)ifr.ifr_hwaddr.sa_data[2], 567 | (unsigned char)ifr.ifr_hwaddr.sa_data[3], 568 | (unsigned char)ifr.ifr_hwaddr.sa_data[4], 569 | (unsigned char)ifr.ifr_hwaddr.sa_data[5]); 570 | 571 | printf("Listening at SF%i on %.6lf Mhz.\n", sf,(double)freq/1000000); 572 | printf("------------------\n"); 573 | 574 | while(1) { 575 | 576 | receivepacket(); 577 | 578 | gettimeofday(&nowtime, NULL); 579 | uint32_t nowseconds = (uint32_t)(nowtime.tv_sec); 580 | if (nowseconds - lasttime >= 30) { 581 | lasttime = nowseconds; 582 | sendstat(); 583 | cp_nb_rx_rcv = 0; 584 | cp_nb_rx_ok = 0; 585 | cp_up_pkt_fwd = 0; 586 | } 587 | delay(1); 588 | } 589 | 590 | return (0); 591 | 592 | } 593 | 594 | --------------------------------------------------------------------------------