├── library.properties ├── .gitignore ├── .travis.yml ├── README.md ├── examples ├── SodaqAutonomo-basic │ └── SodaqAutonomo-basic.ino ├── TheThingsUno-basic │ └── TheThingsUno-basic.ino ├── ESP8266-RN2483-basic │ └── ESP8266-RN2483-basic.ino ├── LoRaGoMOTE-basic │ └── LoRaGoMOTE-basic.ino ├── SodaqOne-TTN-Mapper-ascii │ ├── Sodaq_UBlox_GPS.h │ ├── SodaqOne-TTN-Mapper-ascii.ino │ └── Sodaq_UBlox_GPS.cpp ├── SodaqOne-TTN-Mapper-binary │ ├── Sodaq_UBlox_GPS.h │ ├── SodaqOne-TTN-Mapper-binary.ino │ └── Sodaq_UBlox_GPS.cpp ├── ArduinoUnoNano-basic │ └── ArduinoUnoNano-basic.ino ├── ArduinoUnoNano-downlink │ └── ArduinoUnoNano-downlink.ino └── TheThingsUno-GPSshield-TTN-Mapper-binary │ ├── TheThingsUno-GPSshield-TTN-Mapper-binary.ino │ ├── TinyGPS++.h │ └── TinyGPS++.cpp ├── src ├── rn2xx3.h └── rn2xx3.cpp └── LICENSE /library.properties: -------------------------------------------------------------------------------- 1 | name=RN2xx3 Arduino Library 2 | version=1.0.1 3 | author=JP Meijers 4 | maintainer=JP Meijers 5 | sentence=A library to communicate using the RN2483 and RN2903 module. 6 | paragraph=Supports the RN2483 and RN2903 7 | category=Communication 8 | url=https://github.com/jpmeijers/RN2483-Arduino-Library 9 | architectures=* 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | *.smod 19 | 20 | # Compiled Static libraries 21 | *.lai 22 | *.la 23 | *.a 24 | *.lib 25 | 26 | # Executables 27 | *.exe 28 | *.out 29 | *.app 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | 5 | # Cache PlatformIO packages using Travis CI container-based infrastructure 6 | sudo: false 7 | cache: 8 | directories: 9 | - "~/.platformio" 10 | 11 | env: 12 | # global: 13 | # - PLATFORMIO_BUILD_FLAGS=-Werror 14 | matrix: 15 | - PLATFORMIO_CI_SRC=examples/ArduinoUnoNano-basic PLATFORMIO_CI_EXTRA_ARGS="--board=uno" 16 | - PLATFORMIO_CI_SRC=examples/ArduinoUnoNano-downlink PLATFORMIO_CI_EXTRA_ARGS="--board=uno" 17 | - PLATFORMIO_CI_SRC=examples/ESP8266-RN2483-basic PLATFORMIO_CI_EXTRA_ARGS="--board=d1_mini" 18 | - PLATFORMIO_CI_SRC=examples/SodaqAutonomo-basic PLATFORMIO_CI_EXTRA_ARGS="--board=sodaq_autonomo" 19 | - PLATFORMIO_CI_SRC=examples/SodaqOne-TTN-Mapper-ascii PLATFORMIO_CI_EXTRA_ARGS="--board=sodaq_one" 20 | - PLATFORMIO_CI_SRC=examples/SodaqOne-TTN-Mapper-binary PLATFORMIO_CI_EXTRA_ARGS="--board=sodaq_one" 21 | - PLATFORMIO_CI_SRC=examples/TheThingsUno-basic PLATFORMIO_CI_EXTRA_ARGS="--board=leonardo" 22 | - PLATFORMIO_CI_SRC=examples/TheThingsUno-GPSshield-TTN-Mapper-binary PLATFORMIO_CI_EXTRA_ARGS="--board=leonardo" 23 | 24 | 25 | install: 26 | - pip install -U platformio 27 | - pio update 28 | 29 | script: 30 | - platformio ci --lib="./src" $PLATFORMIO_CI_EXTRA_ARGS 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/jpmeijers/RN2483-Arduino-Library.svg?branch=master)](https://travis-ci.org/jpmeijers/RN2483-Arduino-Library) 2 | 3 | # RN2483-Arduino-Library 4 | Arduino C++ code to communicate with a Microchip RN2483 and RN2903 module 5 | 6 | # Installation 7 | At the top of this website (https://github.com/jpmeijers/RN2483-Arduino-Library), choose Clone or download -> Download ZIP. 8 | 9 | Follow the instructions on https://www.arduino.cc/en/Guide/Libraries to import this library into your Arduino IDE. 10 | 11 | # Examples 12 | Examples with this library are meant to be used to contribute to TTN Mapper (http://ttnmapper.org). 13 | 14 | # License 15 | All code in this repository falls under the Apache v2.0 license, unless otherwise stated in the header of the respective file. 16 | 17 | Copyright 2016 JP Meijers 18 | 19 | Licensed under the Apache License, Version 2.0 (the "License"); 20 | you may not use this file except in compliance with the License. 21 | You may obtain a copy of the License at 22 | 23 | http://www.apache.org/licenses/LICENSE-2.0 24 | 25 | Unless required by applicable law or agreed to in writing, software 26 | distributed under the License is distributed on an "AS IS" BASIS, 27 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28 | See the License for the specific language governing permissions and 29 | limitations under the License. 30 | -------------------------------------------------------------------------------- /examples/SodaqAutonomo-basic/SodaqAutonomo-basic.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Basic sketch for connecting 3 | * a Sodaq Autonomo equiped with a 4 | * LoRaBee to The Things Network 5 | * 6 | * Author: Richard Verbruggen - http://vannut.nl 7 | */ 8 | 9 | #include 10 | 11 | // Autonomo Serial port definitions. 12 | #define debugSerial SerialUSB 13 | #define loraSerial Serial1 14 | 15 | 16 | // create an instance of the Library. 17 | rn2xx3 myLora(loraSerial); 18 | 19 | 20 | void setup() 21 | { 22 | // Put power on the BeeSocket. 23 | digitalWrite(BEE_VCC, HIGH); 24 | 25 | // built_in led 26 | pinMode(LED_BUILTIN, OUTPUT); 27 | led_on(); 28 | 29 | // make sure usb serial connection is available, 30 | // or after 10s go on anyway for 'headless' use of the 31 | // node. 32 | while ((!debugSerial) && (millis() < 10000)); 33 | 34 | // beginning serial connections. 35 | debugSerial.begin(57600); 36 | loraSerial.begin(9600); 37 | 38 | // 39 | debugSerial.println(F("--------------------------------")); 40 | debugSerial.println(F("Basic sketch for connecting ")); 41 | debugSerial.println(F("to The ThingsNetwork")); 42 | debugSerial.println(F("--------------------------------")); 43 | led_off(); 44 | 45 | initialize_radio(); 46 | 47 | } 48 | 49 | void initialize_radio() 50 | { 51 | 52 | myLora.autobaud(); 53 | 54 | debugSerial.println("DevEUI? ");debugSerial.print(F("> ")); 55 | debugSerial.println(myLora.hweui()); 56 | debugSerial.println("Version?");debugSerial.print(F("> ")); 57 | debugSerial.println(myLora.sysver()); 58 | debugSerial.println(F("--------------------------------")); 59 | 60 | debugSerial.println(F("Trying to join TTN")); 61 | bool join_result = false; 62 | 63 | 64 | //ABP: initABP(String addr, String AppSKey, String NwkSKey); 65 | join_result = myLora.initABP("02017201", "8D7FFEF938589D95AAD928C2E2E7E48F", "AE17E567AECC8787F749A62F5541D522"); 66 | 67 | //OTAA: initOTAA(String AppEUI, String AppKey); 68 | //join_result = myLora.initOTAA("70B3D57ED00001A6", "A23C96EE13804963F8C2BD6285448198"); 69 | 70 | while(!join_result) 71 | { 72 | debugSerial.println("\u2A2F Unable to join. Are your keys correct, and do you have TTN coverage?"); 73 | delay(30000); //delay 30s before retry 74 | join_result = myLora.init(); 75 | } 76 | debugSerial.println("\u2713 Successfully joined TTN"); 77 | 78 | 79 | } 80 | 81 | 82 | 83 | 84 | void loop() 85 | { 86 | led_on(); 87 | debugSerial.println(F("> TXing")); 88 | myLora.tx("!"); //one byte, blocking function 89 | led_off(); 90 | 91 | // delay it a little bit 92 | // but the library manages the real dutycycle check. 93 | delay(200); 94 | } 95 | 96 | 97 | void led_on() 98 | { 99 | digitalWrite(LED_BUILTIN, 1); 100 | } 101 | 102 | void led_off() 103 | { 104 | digitalWrite(LED_BUILTIN, 0); 105 | } 106 | -------------------------------------------------------------------------------- /examples/TheThingsUno-basic/TheThingsUno-basic.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: JP Meijers 3 | * Date: 2016-09-07 4 | * 5 | * This program is meant to be used on a The Things Uno board. 6 | * 7 | * Transmit a one byte packet via TTN. This happens as fast as possible, 8 | * while still keeping to the 1% duty cycle rules enforced by the RN2483's built 9 | * in LoRaWAN stack. Even though this is allowed by the radio regulations of the 10 | * 868MHz band, the fair use policy of TTN may prohibit this. 11 | * 12 | * CHECK THE RULES BEFORE USING THIS PROGRAM! 13 | * 14 | * CHANGE ADDRESS! 15 | * Change the device address, network (session) key, and app (session) key to 16 | * the values that are registered via the TTN dashboard. 17 | * The appropriate line is "myLora.initABP(XXX);" or "myLora.initOTAA(XXX);" 18 | * When using ABP, it is advised to enable "relax frame count" on the dashboard. 19 | * 20 | */ 21 | #include 22 | 23 | //create an instance of the rn2483 library, using the given Serial port 24 | rn2xx3 myLora(Serial1); 25 | 26 | // the setup routine runs once when you press reset: 27 | void setup() 28 | { 29 | //output LED pin 30 | pinMode(13, OUTPUT); 31 | led_on(); 32 | 33 | // Open serial communications and wait for port to open: 34 | Serial.begin(57600); //serial port to computer 35 | Serial1.begin(57600); //serial port to radio 36 | 37 | // make sure usb serial connection is available, 38 | // or after 10s go on anyway for 'headless' use of the 39 | // node. 40 | while ((!Serial) && (millis() < 10000)); 41 | 42 | Serial.println("Startup"); 43 | 44 | initialize_radio(); 45 | 46 | //transmit a startup message 47 | myLora.tx("TTN Mapper on TTN Uno node"); 48 | 49 | led_off(); 50 | delay(2000); 51 | } 52 | 53 | void initialize_radio() 54 | { 55 | delay(100); //wait for the RN2xx3's startup message 56 | Serial1.flush(); 57 | 58 | //print out the HWEUI so that we can register it via ttnctl 59 | String hweui = myLora.hweui(); 60 | while(hweui.length() != 16) 61 | { 62 | Serial.println("Communication with RN2xx3 unsuccessful. Power cycle the TTN UNO board."); 63 | delay(10000); 64 | hweui = myLora.hweui(); 65 | } 66 | Serial.println("When using OTAA, register this DevEUI: "); 67 | Serial.println(hweui); 68 | Serial.println("RN2xx3 firmware version:"); 69 | Serial.println(myLora.sysver()); 70 | 71 | //configure your keys and join the network 72 | Serial.println("Trying to join TTN"); 73 | bool join_result = false; 74 | 75 | //ABP: initABP(String addr, String AppSKey, String NwkSKey); 76 | join_result = myLora.initABP("02017201", "8D7FFEF938589D95AAD928C2E2E7E48F", "AE17E567AECC8787F749A62F5541D522"); 77 | 78 | //OTAA: initOTAA(String AppEUI, String AppKey); 79 | //join_result = myLora.initOTAA("70B3D57ED00001A6", "A23C96EE13804963F8C2BD6285448198"); 80 | 81 | while(!join_result) 82 | { 83 | Serial.println("Unable to join. Are your keys correct, and do you have TTN coverage?"); 84 | delay(60000); //delay a minute before retry 85 | join_result = myLora.init(); 86 | } 87 | Serial.println("Successfully joined TTN"); 88 | 89 | } 90 | 91 | // the loop routine runs over and over again forever: 92 | void loop() 93 | { 94 | led_on(); 95 | 96 | Serial.println("TXing"); 97 | myLora.tx("!"); //one byte, blocking function 98 | 99 | led_off(); 100 | delay(200); 101 | } 102 | 103 | void led_on() 104 | { 105 | digitalWrite(13, 1); 106 | } 107 | 108 | void led_off() 109 | { 110 | digitalWrite(13, 0); 111 | } 112 | 113 | -------------------------------------------------------------------------------- /examples/ESP8266-RN2483-basic/ESP8266-RN2483-basic.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: JP Meijers 3 | * Date: 2016-10-20 4 | * 5 | * Transmit a one byte packet via TTN. This happens as fast as possible, while still keeping to 6 | * the 1% duty cycle rules enforced by the RN2483's built in LoRaWAN stack. Even though this is 7 | * allowed by the radio regulations of the 868MHz band, the fair use policy of TTN may prohibit this. 8 | * 9 | * CHECK THE RULES BEFORE USING THIS PROGRAM! 10 | * 11 | * CHANGE ADDRESS! 12 | * Change the device address, network (session) key, and app (session) key to the values 13 | * that are registered via the TTN dashboard. 14 | * The appropriate line is "myLora.initABP(XXX);" or "myLora.initOTAA(XXX);" 15 | * When using ABP, it is advised to enable "relax frame count". 16 | * 17 | * Connect the RN2xx3 as follows: 18 | * RN2xx3 -- ESP8266 19 | * Uart TX -- GPIO4 20 | * Uart RX -- GPIO5 21 | * Reset -- GPIO15 22 | * Vcc -- 3.3V 23 | * Gnd -- Gnd 24 | * 25 | */ 26 | #include 27 | #include 28 | 29 | #define RESET 15 30 | SoftwareSerial mySerial(4, 5); // RX, TX !! labels on relay board is swapped !! 31 | 32 | //create an instance of the rn2xx3 library, 33 | //giving the software UART as stream to use, 34 | //and using LoRa WAN 35 | rn2xx3 myLora(mySerial); 36 | 37 | // the setup routine runs once when you press reset: 38 | void setup() { 39 | // LED pin is GPIO2 which is the ESP8266's built in LED 40 | pinMode(2, OUTPUT); 41 | led_on(); 42 | 43 | // Open serial communications and wait for port to open: 44 | Serial.begin(57600); 45 | mySerial.begin(57600); 46 | 47 | delay(1000); //wait for the arduino ide's serial console to open 48 | 49 | Serial.println("Startup"); 50 | 51 | initialize_radio(); 52 | 53 | //transmit a startup message 54 | myLora.tx("TTN Mapper on ESP8266 node"); 55 | 56 | led_off(); 57 | delay(2000); 58 | } 59 | 60 | void initialize_radio() 61 | { 62 | //reset RN2xx3 63 | pinMode(RESET, OUTPUT); 64 | digitalWrite(RESET, LOW); 65 | delay(100); 66 | digitalWrite(RESET, HIGH); 67 | 68 | delay(100); //wait for the RN2xx3's startup message 69 | mySerial.flush(); 70 | 71 | //check communication with radio 72 | String hweui = myLora.hweui(); 73 | while(hweui.length() != 16) 74 | { 75 | Serial.println("Communication with RN2xx3 unsuccessful. Power cycle the board."); 76 | Serial.println(hweui); 77 | delay(10000); 78 | hweui = myLora.hweui(); 79 | } 80 | 81 | //print out the HWEUI so that we can register it via ttnctl 82 | Serial.println("When using OTAA, register this DevEUI: "); 83 | Serial.println(hweui); 84 | Serial.println("RN2xx3 firmware version:"); 85 | Serial.println(myLora.sysver()); 86 | 87 | //configure your keys and join the network 88 | Serial.println("Trying to join TTN"); 89 | bool join_result = false; 90 | 91 | //ABP: initABP(String addr, String AppSKey, String NwkSKey); 92 | join_result = myLora.initABP("02017201", "8D7FFEF938589D95AAD928C2E2E7E48F", "AE17E567AECC8787F749A62F5541D522"); 93 | 94 | //OTAA: initOTAA(String AppEUI, String AppKey); 95 | //join_result = myLora.initOTAA("70B3D57ED00001A6", "A23C96EE13804963F8C2BD6285448198"); 96 | 97 | while(!join_result) 98 | { 99 | Serial.println("Unable to join. Are your keys correct, and do you have TTN coverage?"); 100 | delay(60000); //delay a minute before retry 101 | join_result = myLora.init(); 102 | } 103 | Serial.println("Successfully joined TTN"); 104 | 105 | } 106 | 107 | // the loop routine runs over and over again forever: 108 | void loop() { 109 | led_on(); 110 | 111 | Serial.println("TXing"); 112 | myLora.tx("!"); //one byte, blocking function 113 | 114 | led_off(); 115 | delay(200); 116 | } 117 | 118 | void led_on() 119 | { 120 | digitalWrite(2, 1); 121 | } 122 | 123 | void led_off() 124 | { 125 | digitalWrite(2, 0); 126 | } 127 | -------------------------------------------------------------------------------- /examples/LoRaGoMOTE-basic/LoRaGoMOTE-basic.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Peng Wei 3 | * Date: 2018-01-21 4 | * 5 | * Transmit a one byte packet via TTN. This happens as fast as possible, while still keeping to 6 | * the 1% duty cycle rules enforced by the RN2483's built in LoRaWAN stack. Even though this is 7 | * allowed by the radio regulations of the 868MHz band, the fair use policy of TTN may prohibit this. 8 | * 9 | * CHECK THE RULES BEFORE USING THIS PROGRAM! 10 | * 11 | * CHANGE ADDRESS! 12 | * Change the device address, network (session) key, and app (session) key to the values 13 | * that are registered via the TTN dashboard. 14 | * The appropriate line is "myLora.initABP(XXX);" or "myLora.initOTAA(XXX);" 15 | * When using ABP, it is advised to enable "relax frame count". 16 | * 17 | * Remember that the serial port should be initialised before calling initTTN(). 18 | * If you use 57600 baud, you can remove the line "myLora.autobaud();". 19 | * 20 | */ 21 | #include 22 | #include 23 | 24 | SoftwareSerial mySerial(2, 3); // RX, TX 25 | 26 | //create an instance of the rn2xx3 library, 27 | //giving the software serial as port to use 28 | rn2xx3 myLora(mySerial); 29 | 30 | // the setup routine runs once when you press reset: 31 | void setup() 32 | { 33 | //output LED pin 34 | pinMode(13, OUTPUT); 35 | led_on(); 36 | 37 | // Open serial communications and wait for port to open: 38 | Serial.begin(57600); //serial port to computer 39 | mySerial.begin(9600); //serial port to radio 40 | Serial.println("Startup"); 41 | 42 | initialize_radio(); 43 | 44 | //transmit a startup message 45 | myLora.tx("TTN Mapper on TTN Enschede node"); 46 | 47 | led_off(); 48 | delay(2000); 49 | } 50 | 51 | void initialize_radio() 52 | { 53 | delay(100); //wait for the RN2xx3's startup message 54 | mySerial.flush(); 55 | 56 | //Autobaud the rn2483 module to 9600. The default would otherwise be 57600. 57 | myLora.autobaud(); 58 | 59 | //check communication with radio 60 | String hweui = myLora.hweui(); 61 | while(hweui.length() != 16) 62 | { 63 | Serial.println("Communication with RN2xx3 unsuccessful. Power cycle the board."); 64 | Serial.println(hweui); 65 | delay(10000); 66 | hweui = myLora.hweui(); 67 | } 68 | 69 | //print out the HWEUI so that we can register it via ttnctl 70 | Serial.println("When using OTAA, register this DevEUI: "); 71 | Serial.println(myLora.hweui()); 72 | Serial.println("RN2xx3 firmware version:"); 73 | Serial.println(myLora.sysver()); 74 | 75 | //configure your keys and join the network 76 | Serial.println("Trying to join TTN"); 77 | bool join_result = false; 78 | 79 | 80 | /* 81 | * ABP: initABP(String addr, String AppSKey, String NwkSKey); 82 | * Paste the example code from the TTN console here: 83 | */ 84 | const char *devAddr = "02017201"; 85 | const char *nwkSKey = "AE17E567AECC8787F749A62F5541D522"; 86 | const char *appSKey = "8D7FFEF938589D95AAD928C2E2E7E48F"; 87 | 88 | join_result = myLora.initABP(devAddr, appSKey, nwkSKey); 89 | 90 | /* 91 | * OTAA: initOTAA(String AppEUI, String AppKey); 92 | * If you are using OTAA, paste the example code from the TTN console here: 93 | */ 94 | //const char *appEui = "70B3D57ED00001A6"; 95 | //const char *appKey = "A23C96EE13804963F8C2BD6285448198"; 96 | 97 | //join_result = myLora.initOTAA(appEui, appKey); 98 | 99 | 100 | while(!join_result) 101 | { 102 | Serial.println("Unable to join. Are your keys correct, and do you have TTN coverage?"); 103 | delay(60000); //delay a minute before retry 104 | join_result = myLora.init(); 105 | } 106 | Serial.println("Successfully joined TTN"); 107 | 108 | } 109 | 110 | // the loop routine runs over and over again forever: 111 | void loop() 112 | { 113 | led_on(); 114 | 115 | Serial.println("TXing"); 116 | myLora.tx("!"); //one byte, blocking function 117 | 118 | led_off(); 119 | delay(200); 120 | } 121 | 122 | void led_on() 123 | { 124 | digitalWrite(13, 1); 125 | } 126 | 127 | void led_off() 128 | { 129 | digitalWrite(13, 0); 130 | } 131 | -------------------------------------------------------------------------------- /examples/SodaqOne-TTN-Mapper-ascii/Sodaq_UBlox_GPS.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 SODAQ. All rights reserved. 3 | * 4 | * This file is part of Sodaq_UBlox_GPS. 5 | * 6 | * Sodaq_UBlox_GPS is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published 8 | * by the Free Software Foundation, either version 3 of the License, or (at 9 | * your option) any later version. 10 | * 11 | * Sodaq_UBlox_GPS is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 14 | * License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with Sodaq_UBlox_GPS. If not, see . 18 | */ 19 | 20 | #ifndef _SODAQ_UBLOX_GPS_H 21 | #define _SODAQ_UBLOX_GPS_H 22 | 23 | #include 24 | #include 25 | 26 | class Sodaq_UBlox_GPS 27 | { 28 | public: 29 | Sodaq_UBlox_GPS(); 30 | 31 | void init(int8_t enable_pin); 32 | bool scan(bool leave_on=false, uint32_t timeout=20000); 33 | String getDateTimeString(); 34 | double getLat() { return _lat; } 35 | double getLon() { return _lon; } 36 | double getAlt() { return _alt; } 37 | double getHDOP() { return _hdop; } 38 | uint8_t getNumberOfSatellites() { return _numSatellites; } 39 | uint16_t getYear() { return (uint16_t)_yy + 2000; } // 2016.. 40 | uint8_t getMonth() { return _MM; } // 1.. 41 | uint8_t getDay() { return _dd; } // 1.. 42 | uint8_t getHour() { return _hh; } // 0.. 43 | uint8_t getMinute() { return _mm; } // 0.. 44 | uint8_t getSecond() { return _ss; } // 0.. 45 | 46 | // How many extra lines must scan see before it stops? This is merely for debugging 47 | void setMinNumOfLines(size_t num) { _minNumOfLines = num; } 48 | 49 | // The minimum number of satellites to satisfy scan 50 | void setMinNumSatellites(size_t num) { _minNumSatellites = num; } 51 | 52 | // Sets the optional "Diagnostics and Debug" stream. 53 | void setDiag(Stream &stream) { _diagStream = &stream; } 54 | void setDiag(Stream *stream) { _diagStream = stream; } 55 | 56 | private: 57 | void on(); 58 | void off(); 59 | 60 | // Read one byte 61 | uint8_t read(); 62 | bool readLine(uint32_t timeout = 10000); 63 | bool parseLine(const char * line); 64 | bool parseGPGGA(const String & line); 65 | bool parseGPGSA(const String & line); 66 | bool parseGPRMC(const String & line); 67 | bool parseGPGSV(const String & line); 68 | bool parseGPGLL(const String & line); 69 | bool parseGPVTG(const String & line); 70 | bool parseGPTXT(const String & line); 71 | bool computeCrc(const char * line, bool do_logging=false); 72 | uint8_t getHex2(const char * s, size_t index); 73 | String num2String(int num, size_t width); 74 | String getField(const String & data, int index); 75 | double convertDegMinToDecDeg(const String & data); 76 | 77 | void setDateTime(const String & date, const String & time); 78 | 79 | void beginTransmission(); 80 | void endTransmission(); 81 | 82 | void resetValues(); 83 | 84 | int8_t _enablePin; 85 | 86 | // The (optional) stream to show debug information. 87 | Stream * _diagStream; 88 | 89 | uint8_t _addr; 90 | 91 | size_t _minNumOfLines; 92 | 93 | // Minimum number of satellites to satisfy scan(). Zero means any number is OK. 94 | size_t _minNumSatellites; 95 | 96 | bool _seenLatLon; 97 | bool _seenAlt; 98 | uint8_t _numSatellites; 99 | double _lat; 100 | double _lon; 101 | double _alt; 102 | double _hdop; 103 | 104 | bool _seenTime; 105 | uint8_t _yy; 106 | uint8_t _MM; 107 | uint8_t _dd; 108 | uint8_t _hh; 109 | uint8_t _mm; 110 | uint8_t _ss; 111 | 112 | bool _trans_active; 113 | 114 | static const char _fieldSep; 115 | char * _inputBuffer; 116 | size_t _inputBufferSize; 117 | }; 118 | 119 | extern Sodaq_UBlox_GPS sodaq_gps; 120 | 121 | #endif // _SODAQ_UBLOX_GPS_H 122 | -------------------------------------------------------------------------------- /examples/SodaqOne-TTN-Mapper-binary/Sodaq_UBlox_GPS.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 SODAQ. All rights reserved. 3 | * 4 | * This file is part of Sodaq_UBlox_GPS. 5 | * 6 | * Sodaq_UBlox_GPS is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published 8 | * by the Free Software Foundation, either version 3 of the License, or (at 9 | * your option) any later version. 10 | * 11 | * Sodaq_UBlox_GPS is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 14 | * License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with Sodaq_UBlox_GPS. If not, see . 18 | */ 19 | 20 | #ifndef _SODAQ_UBLOX_GPS_H 21 | #define _SODAQ_UBLOX_GPS_H 22 | 23 | #include 24 | #include 25 | 26 | class Sodaq_UBlox_GPS 27 | { 28 | public: 29 | Sodaq_UBlox_GPS(); 30 | 31 | void init(int8_t enable_pin); 32 | bool scan(bool leave_on=false, uint32_t timeout=20000); 33 | String getDateTimeString(); 34 | double getLat() { return _lat; } 35 | double getLon() { return _lon; } 36 | double getAlt() { return _alt; } 37 | double getHDOP() { return _hdop; } 38 | uint8_t getNumberOfSatellites() { return _numSatellites; } 39 | uint16_t getYear() { return (uint16_t)_yy + 2000; } // 2016.. 40 | uint8_t getMonth() { return _MM; } // 1.. 41 | uint8_t getDay() { return _dd; } // 1.. 42 | uint8_t getHour() { return _hh; } // 0.. 43 | uint8_t getMinute() { return _mm; } // 0.. 44 | uint8_t getSecond() { return _ss; } // 0.. 45 | 46 | // How many extra lines must scan see before it stops? This is merely for debugging 47 | void setMinNumOfLines(size_t num) { _minNumOfLines = num; } 48 | 49 | // The minimum number of satellites to satisfy scan 50 | void setMinNumSatellites(size_t num) { _minNumSatellites = num; } 51 | 52 | // Sets the optional "Diagnostics and Debug" stream. 53 | void setDiag(Stream &stream) { _diagStream = &stream; } 54 | void setDiag(Stream *stream) { _diagStream = stream; } 55 | 56 | private: 57 | void on(); 58 | void off(); 59 | 60 | // Read one byte 61 | uint8_t read(); 62 | bool readLine(uint32_t timeout = 10000); 63 | bool parseLine(const char * line); 64 | bool parseGPGGA(const String & line); 65 | bool parseGPGSA(const String & line); 66 | bool parseGPRMC(const String & line); 67 | bool parseGPGSV(const String & line); 68 | bool parseGPGLL(const String & line); 69 | bool parseGPVTG(const String & line); 70 | bool parseGPTXT(const String & line); 71 | bool computeCrc(const char * line, bool do_logging=false); 72 | uint8_t getHex2(const char * s, size_t index); 73 | String num2String(int num, size_t width); 74 | String getField(const String & data, int index); 75 | double convertDegMinToDecDeg(const String & data); 76 | 77 | void setDateTime(const String & date, const String & time); 78 | 79 | void beginTransmission(); 80 | void endTransmission(); 81 | 82 | void resetValues(); 83 | 84 | int8_t _enablePin; 85 | 86 | // The (optional) stream to show debug information. 87 | Stream * _diagStream; 88 | 89 | uint8_t _addr; 90 | 91 | size_t _minNumOfLines; 92 | 93 | // Minimum number of satellites to satisfy scan(). Zero means any number is OK. 94 | size_t _minNumSatellites; 95 | 96 | bool _seenLatLon; 97 | bool _seenAlt; 98 | uint8_t _numSatellites; 99 | double _lat; 100 | double _lon; 101 | double _alt; 102 | double _hdop; 103 | 104 | bool _seenTime; 105 | uint8_t _yy; 106 | uint8_t _MM; 107 | uint8_t _dd; 108 | uint8_t _hh; 109 | uint8_t _mm; 110 | uint8_t _ss; 111 | 112 | bool _trans_active; 113 | 114 | static const char _fieldSep; 115 | char * _inputBuffer; 116 | size_t _inputBufferSize; 117 | }; 118 | 119 | extern Sodaq_UBlox_GPS sodaq_gps; 120 | 121 | #endif // _SODAQ_UBLOX_GPS_H 122 | -------------------------------------------------------------------------------- /examples/SodaqOne-TTN-Mapper-ascii/SodaqOne-TTN-Mapper-ascii.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: JP Meijers 3 | * Date: 2016-09-02 4 | * 5 | * Transmit GPS coordinates via TTN. This happens as fast as possible, while still keeping to 6 | * the 1% duty cycle rules enforced by the RN2483's built in LoRaWAN stack. Even though this is 7 | * allowed by the radio regulations of the 868MHz band, the fair use policy of TTN may prohibit this. 8 | * 9 | * CHECK THE RULES BEFORE USING THIS PROGRAM! 10 | * 11 | * CHANGE ADDRESS! 12 | * Change the device address, network (session) key, and app (session) key to the values 13 | * that are registered via the TTN dashboard. 14 | * The appropriate line is "myLora.initABP(XXX);" or "myLora.initOTAA(XXX);" 15 | * When using ABP, it is advised to enable "relax frame count". 16 | * 17 | * This sketch assumes you are using the Sodaq One V4 node in its original configuration. 18 | * 19 | * This program makes use of the Sodaq UBlox library, but with minor changes to include altitude and HDOP. 20 | * 21 | * LED indicators: 22 | * Blue: Busy transmitting a packet 23 | * Green waiting for a new GPS fix 24 | * Red: GPS fix taking a long time. Try to go outdoors. 25 | * 26 | */ 27 | #include 28 | #include "Sodaq_UBlox_GPS.h" 29 | #include 30 | 31 | //create an instance of the rn2xx3 library, 32 | //giving the software serial as port to use 33 | rn2xx3 myLora(Serial1); 34 | 35 | String toLog; 36 | 37 | void setup() 38 | { 39 | delay(3000); 40 | 41 | SerialUSB.begin(57600); 42 | Serial1.begin(57600); 43 | 44 | // make sure usb serial connection is available, 45 | // or after 10s go on anyway for 'headless' use of the 46 | // node. 47 | while ((!SerialUSB) && (millis() < 10000)); 48 | 49 | SerialUSB.println("SODAQ LoRaONE TTN Mapper starting"); 50 | 51 | initialize_radio(); 52 | 53 | //transmit a startup message 54 | myLora.tx("TTN Mapper on Sodaq One"); 55 | 56 | // initialize GPS 57 | sodaq_gps.init(GPS_ENABLE); 58 | 59 | // myLora.setDR(0); //set the datarate at which we measure. DR7 is the best. 60 | pinMode(LED_BLUE, OUTPUT); 61 | digitalWrite(LED_BLUE, HIGH); 62 | pinMode(LED_RED, OUTPUT); 63 | digitalWrite(LED_RED, HIGH); 64 | pinMode(LED_GREEN, OUTPUT); 65 | digitalWrite(LED_GREEN, HIGH); 66 | } 67 | 68 | void initialize_radio() 69 | { 70 | delay(100); //wait for the RN2xx3's startup message 71 | while(Serial1.available()){ 72 | Serial1.read(); 73 | } 74 | 75 | //print out the HWEUI so that we can register it via ttnctl 76 | String hweui = myLora.hweui(); 77 | while(hweui.length() != 16) 78 | { 79 | SerialUSB.println("Communication with RN2xx3 unsuccessful. Power cycle the Sodaq One board."); 80 | delay(10000); 81 | hweui = myLora.hweui(); 82 | } 83 | SerialUSB.println("When using OTAA, register this DevEUI: "); 84 | SerialUSB.println(hweui); 85 | SerialUSB.println("RN2xx3 firmware version:"); 86 | SerialUSB.println(myLora.sysver()); 87 | 88 | //configure your keys and join the network 89 | SerialUSB.println("Trying to join TTN"); 90 | bool join_result = false; 91 | 92 | //ABP: initABP(String addr, String AppSKey, String NwkSKey); 93 | join_result = myLora.initABP("02017201", "8D7FFEF938589D95AAD928C2E2E7E48F", "AE17E567AECC8787F749A62F5541D522"); 94 | 95 | //OTAA: initOTAA(String AppEUI, String AppKey); 96 | //join_result = myLora.initOTAA("70B3D57ED00001A6", "A23C96EE13804963F8C2BD6285448198"); 97 | 98 | while(!join_result) 99 | { 100 | SerialUSB.println("Unable to join. Are your keys correct, and do you have TTN coverage?"); 101 | delay(60000); //delay a minute before retry 102 | digitalWrite(LED_BLUE, LOW); 103 | join_result = myLora.init(); 104 | digitalWrite(LED_BLUE, HIGH); 105 | } 106 | SerialUSB.println("Successfully joined TTN"); 107 | 108 | } 109 | 110 | void loop() 111 | { 112 | SerialUSB.println("Waiting for GPS fix"); 113 | 114 | digitalWrite(LED_GREEN, LOW); 115 | // Keep the GPS enabled after we do a scan, increases accuracy 116 | sodaq_gps.scan(true); 117 | digitalWrite(LED_GREEN, HIGH); 118 | 119 | while(sodaq_gps.getLat()==0.0) 120 | { 121 | digitalWrite(LED_RED, LOW); 122 | // Keep the GPS enabled after we do a scan, increases accuracy 123 | sodaq_gps.scan(true); 124 | digitalWrite(LED_RED, HIGH); 125 | } 126 | 127 | toLog = String(long(sodaq_gps.getLat()*1000000)); 128 | toLog +=" "; 129 | toLog += String(long(sodaq_gps.getLon()*1000000)); 130 | toLog+=" "; 131 | toLog+=String(int(sodaq_gps.getAlt())); 132 | toLog+=" "; 133 | toLog+=String(int(sodaq_gps.getHDOP()*100)); 134 | 135 | SerialUSB.println(toLog); 136 | digitalWrite(LED_BLUE, LOW); 137 | myLora.tx(toLog); 138 | digitalWrite(LED_BLUE, HIGH); 139 | SerialUSB.println("TX done"); 140 | } 141 | -------------------------------------------------------------------------------- /examples/ArduinoUnoNano-basic/ArduinoUnoNano-basic.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: JP Meijers 3 | * Date: 2016-02-07 4 | * Previous filename: TTN-Mapper-TTNEnschede-V1 5 | * 6 | * This program is meant to be used with an Arduino UNO or NANO, conencted to an RNxx3 radio module. 7 | * It will most likely also work on other compatible Arduino or Arduino compatible boards, like The Things Uno, but might need some slight modifications. 8 | * 9 | * Transmit a one byte packet via TTN. This happens as fast as possible, while still keeping to 10 | * the 1% duty cycle rules enforced by the RN2483's built in LoRaWAN stack. Even though this is 11 | * allowed by the radio regulations of the 868MHz band, the fair use policy of TTN may prohibit this. 12 | * 13 | * CHECK THE RULES BEFORE USING THIS PROGRAM! 14 | * 15 | * CHANGE ADDRESS! 16 | * Change the device address, network (session) key, and app (session) key to the values 17 | * that are registered via the TTN dashboard. 18 | * The appropriate line is "myLora.initABP(XXX);" or "myLora.initOTAA(XXX);" 19 | * When using ABP, it is advised to enable "relax frame count". 20 | * 21 | * Connect the RN2xx3 as follows: 22 | * RN2xx3 -- Arduino 23 | * Uart TX -- 10 24 | * Uart RX -- 11 25 | * Reset -- 12 26 | * Vcc -- 3.3V 27 | * Gnd -- Gnd 28 | * 29 | * If you use an Arduino with a free hardware serial port, you can replace 30 | * the line "rn2xx3 myLora(mySerial);" 31 | * with "rn2xx3 myLora(SerialX);" 32 | * where the parameter is the serial port the RN2xx3 is connected to. 33 | * Remember that the serial port should be initialised before calling initTTN(). 34 | * For best performance the serial port should be set to 57600 baud, which is impossible with a software serial port. 35 | * If you use 57600 baud, you can remove the line "myLora.autobaud();". 36 | * 37 | */ 38 | #include 39 | #include 40 | 41 | SoftwareSerial mySerial(10, 11); // RX, TX 42 | 43 | //create an instance of the rn2xx3 library, 44 | //giving the software serial as port to use 45 | rn2xx3 myLora(mySerial); 46 | 47 | // the setup routine runs once when you press reset: 48 | void setup() 49 | { 50 | //output LED pin 51 | pinMode(13, OUTPUT); 52 | led_on(); 53 | 54 | // Open serial communications and wait for port to open: 55 | Serial.begin(57600); //serial port to computer 56 | mySerial.begin(9600); //serial port to radio 57 | Serial.println("Startup"); 58 | 59 | initialize_radio(); 60 | 61 | //transmit a startup message 62 | myLora.tx("TTN Mapper on TTN Enschede node"); 63 | 64 | led_off(); 65 | delay(2000); 66 | } 67 | 68 | void initialize_radio() 69 | { 70 | //reset rn2483 71 | pinMode(12, OUTPUT); 72 | digitalWrite(12, LOW); 73 | delay(500); 74 | digitalWrite(12, HIGH); 75 | 76 | delay(100); //wait for the RN2xx3's startup message 77 | mySerial.flush(); 78 | 79 | //Autobaud the rn2483 module to 9600. The default would otherwise be 57600. 80 | myLora.autobaud(); 81 | 82 | //check communication with radio 83 | String hweui = myLora.hweui(); 84 | while(hweui.length() != 16) 85 | { 86 | Serial.println("Communication with RN2xx3 unsuccessful. Power cycle the board."); 87 | Serial.println(hweui); 88 | delay(10000); 89 | hweui = myLora.hweui(); 90 | } 91 | 92 | //print out the HWEUI so that we can register it via ttnctl 93 | Serial.println("When using OTAA, register this DevEUI: "); 94 | Serial.println(myLora.hweui()); 95 | Serial.println("RN2xx3 firmware version:"); 96 | Serial.println(myLora.sysver()); 97 | 98 | //configure your keys and join the network 99 | Serial.println("Trying to join TTN"); 100 | bool join_result = false; 101 | 102 | 103 | /* 104 | * ABP: initABP(String addr, String AppSKey, String NwkSKey); 105 | * Paste the example code from the TTN console here: 106 | */ 107 | const char *devAddr = "02017201"; 108 | const char *nwkSKey = "AE17E567AECC8787F749A62F5541D522"; 109 | const char *appSKey = "8D7FFEF938589D95AAD928C2E2E7E48F"; 110 | 111 | join_result = myLora.initABP(devAddr, appSKey, nwkSKey); 112 | 113 | /* 114 | * OTAA: initOTAA(String AppEUI, String AppKey); 115 | * If you are using OTAA, paste the example code from the TTN console here: 116 | */ 117 | //const char *appEui = "70B3D57ED00001A6"; 118 | //const char *appKey = "A23C96EE13804963F8C2BD6285448198"; 119 | 120 | //join_result = myLora.initOTAA(appEui, appKey); 121 | 122 | 123 | while(!join_result) 124 | { 125 | Serial.println("Unable to join. Are your keys correct, and do you have TTN coverage?"); 126 | delay(60000); //delay a minute before retry 127 | join_result = myLora.init(); 128 | } 129 | Serial.println("Successfully joined TTN"); 130 | 131 | } 132 | 133 | // the loop routine runs over and over again forever: 134 | void loop() 135 | { 136 | led_on(); 137 | 138 | Serial.println("TXing"); 139 | myLora.tx("!"); //one byte, blocking function 140 | 141 | led_off(); 142 | delay(200); 143 | } 144 | 145 | void led_on() 146 | { 147 | digitalWrite(13, 1); 148 | } 149 | 150 | void led_off() 151 | { 152 | digitalWrite(13, 0); 153 | } 154 | 155 | -------------------------------------------------------------------------------- /examples/ArduinoUnoNano-downlink/ArduinoUnoNano-downlink.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Dennis Ruigrok and JP Meijers 3 | * Date: 2017-01-16 4 | * 5 | * This program is meant to be used with an Arduino UNO or NANO, conencted to an RNxx3 radio module. 6 | * It will most likely also work on other compatible Arduino or Arduino compatible boards, 7 | * like The Things Uno, but might need some slight modifications. 8 | * 9 | * Transmit a one byte packet via TTN, using confirmed messages, 10 | * waiting for an acknowledgement or a downlink message. 11 | * 12 | * CHECK THE RULES BEFORE USING THIS PROGRAM! 13 | * 14 | * CHANGE ADDRESS! 15 | * Change the device address, network (session) key, and app (session) key to the values 16 | * that are registered via the TTN dashboard. 17 | * The appropriate line is "myLora.initABP(XXX);" or "myLora.initOTAA(XXX);" 18 | * When using ABP, it is advised to enable "relax frame count". 19 | * 20 | * Connect the RN2xx3 as follows: 21 | * RN2xx3 -- Arduino 22 | * Uart TX -- 10 23 | * Uart RX -- 11 24 | * Reset -- 12 25 | * Vcc -- 3.3V 26 | * Gnd -- Gnd 27 | * 28 | * If you use an Arduino with a free hardware serial port, you can replace 29 | * the line "rn2xx3 myLora(mySerial);" 30 | * with "rn2xx3 myLora(SerialX);" 31 | * where the parameter is the serial port the RN2xx3 is connected to. 32 | * Remember that the serial port should be initialised before calling initTTN(). 33 | * For best performance the serial port should be set to 57600 baud, which is impossible with a software serial port. 34 | * If you use 57600 baud, you can remove the line "myLora.autobaud();". 35 | * 36 | */ 37 | #include 38 | #include 39 | 40 | SoftwareSerial mySerial(10, 11); // RX, TX 41 | 42 | //create an instance of the rn2xx3 library, 43 | //giving the software serial as port to use 44 | rn2xx3 myLora(mySerial); 45 | 46 | // the setup routine runs once when you press reset: 47 | void setup() 48 | { 49 | //output LED pin 50 | pinMode(13, OUTPUT); 51 | led_on(); 52 | 53 | // Open serial communications and wait for port to open: 54 | Serial.begin(57600); //serial port to computer 55 | mySerial.begin(9600); //serial port to radio 56 | Serial.println("Startup"); 57 | 58 | initialize_radio(); 59 | 60 | //transmit a startup message 61 | myLora.tx("TTN Mapper on TTN Enschede node"); 62 | 63 | led_off(); 64 | delay(2000); 65 | } 66 | 67 | void initialize_radio() 68 | { 69 | //reset rn2483 70 | pinMode(12, OUTPUT); 71 | digitalWrite(12, LOW); 72 | delay(500); 73 | digitalWrite(12, HIGH); 74 | 75 | delay(100); //wait for the RN2xx3's startup message 76 | mySerial.flush(); 77 | 78 | //Autobaud the rn2483 module to 9600. The default would otherwise be 57600. 79 | myLora.autobaud(); 80 | 81 | //check communication with radio 82 | String hweui = myLora.hweui(); 83 | while(hweui.length() != 16) 84 | { 85 | Serial.println("Communication with RN2xx3 unsuccesful. Power cycle the board."); 86 | Serial.println(hweui); 87 | delay(10000); 88 | hweui = myLora.hweui(); 89 | } 90 | 91 | //print out the HWEUI so that we can register it via ttnctl 92 | Serial.println("When using OTAA, register this DevEUI: "); 93 | Serial.println(myLora.hweui()); 94 | Serial.println("RN2xx3 firmware version:"); 95 | Serial.println(myLora.sysver()); 96 | 97 | //configure your keys and join the network 98 | Serial.println("Trying to join TTN"); 99 | bool join_result = false; 100 | 101 | //ABP: initABP(String addr, String AppSKey, String NwkSKey); 102 | join_result = myLora.initABP("02017201", "8D7FFEF938589D95AAD928C2E2E7E48F", "AE17E567AECC8787F749A62F5541D522"); 103 | 104 | //OTAA: initOTAA(String AppEUI, String AppKey); 105 | //join_result = myLora.initOTAA("70B3D57ED00001A6", "A23C96EE13804963F8C2BD6285448198"); 106 | 107 | while(!join_result) 108 | { 109 | Serial.println("Unable to join. Are your keys correct, and do you have TTN coverage?"); 110 | delay(60000); //delay a minute before retry 111 | join_result = myLora.init(); 112 | } 113 | Serial.println("Successfully joined TTN"); 114 | 115 | } 116 | 117 | // the loop routine runs over and over again forever: 118 | void loop() 119 | { 120 | led_on(); 121 | 122 | Serial.print("TXing"); 123 | myLora.txCnf("!"); //one byte, blocking function 124 | 125 | switch(myLora.txCnf("!")) //one byte, blocking function 126 | { 127 | case TX_FAIL: 128 | { 129 | Serial.println("TX unsuccessful or not acknowledged"); 130 | break; 131 | } 132 | case TX_SUCCESS: 133 | { 134 | Serial.println("TX successful and acknowledged"); 135 | break; 136 | } 137 | case TX_WITH_RX: 138 | { 139 | String received = myLora.getRx(); 140 | received = myLora.base16decode(received); 141 | Serial.print("Received downlink: " + received); 142 | break; 143 | } 144 | default: 145 | { 146 | Serial.println("Unknown response from TX function"); 147 | } 148 | } 149 | 150 | led_off(); 151 | delay(10000); 152 | } 153 | 154 | void led_on() 155 | { 156 | digitalWrite(13, 1); 157 | } 158 | 159 | void led_off() 160 | { 161 | digitalWrite(13, 0); 162 | } 163 | 164 | -------------------------------------------------------------------------------- /examples/TheThingsUno-GPSshield-TTN-Mapper-binary/TheThingsUno-GPSshield-TTN-Mapper-binary.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: JP Meijers 3 | * Date: 2016-09-07 4 | * 5 | * This example program is meant for a The Things UNO board with an 6 | * "Adafruit Ultimate GPS+Logging Shield" on top of it. Make sure the switch on 7 | * the GPS shield is switched to the "Soft. Serial" position. 8 | * 9 | * Coordinates from the GPS is packed into a LoRaWan packet using binary 10 | * encoding, and then sent out on the air using the TTN UNO's RN2xx3 module. 11 | * This happens as fast as possible, while still keeping to the 1% duty cycle 12 | * rules enforced by the RN2483's built in LoRaWAN stack. Even though this is 13 | * allowed by the radio regulations of the 868MHz band, the fair use policy of 14 | * TTN may prohibit this. 15 | * 16 | * CHECK THE RULES BEFORE USING THIS PROGRAM! 17 | * 18 | * CHANGE ADDRESS! 19 | * Change the device address, network (session) key, and app (session) key to 20 | * the values that are registered via the TTN dashboard. 21 | * The appropriate line is "myLora.initABP(XXX);" or "myLora.initOTAA(XXX);" 22 | * When using ABP, it is advised to enable "relax frame count" on the dashboard. 23 | * 24 | * TO CONTRIBUTE TO TTN Mapper: 25 | * 1. Register a new Application and/or new device on the 26 | * TTN Dashboard (https://console.thethingsnetwork.org). 27 | * 2. Copy the correct keys into the line "myLora.initABP(XXX);" 28 | * or "myLora.initOTAA(XXX);" in this program. 29 | * 3. Comment out or remove the line: "while(!Serial); //wait for Serial to 30 | be available - remove this line after successful test run" 31 | * 4. Make sure packets are arriving on the TTN console when your node is 32 | * powered and in reach of a gateway. 33 | * 5. Share your Application ID, Access Key and Device ID with 34 | * contribute@ttnmapper.org so that your measurements can be automatically 35 | * imported into TTN Mapper. 36 | * 37 | * 38 | * To decode the binary payload, you can use the following 39 | * javascript decoder function. It should work with the TTN console. 40 | * 41 | function Decoder(bytes, port) { 42 | // Decode an uplink message from a buffer 43 | // (array) of bytes to an object of fields. 44 | var decoded = {}; 45 | 46 | // if (port === 1) decoded.led = bytes[0]; 47 | decoded.lat = ((bytes[0]<<16)>>>0) + ((bytes[1]<<8)>>>0) + bytes[2]; 48 | decoded.lat = (decoded.lat / 16777215.0 * 180) - 90; 49 | 50 | decoded.lon = ((bytes[3]<<16)>>>0) + ((bytes[4]<<8)>>>0) + bytes[5]; 51 | decoded.lon = (decoded.lon / 16777215.0 * 360) - 180; 52 | 53 | var altValue = ((bytes[6]<<8)>>>0) + bytes[7]; 54 | var sign = bytes[6] & (1 << 7); 55 | if(sign) 56 | { 57 | decoded.alt = 0xFFFF0000 | altValue; 58 | } 59 | else 60 | { 61 | decoded.alt = altValue; 62 | } 63 | 64 | decoded.hdop = bytes[8] / 10.0; 65 | 66 | return decoded; 67 | } 68 | * 69 | */ 70 | #include "TinyGPS++.h" 71 | #include 72 | #include 73 | 74 | SoftwareSerial gpsSerial(8, 9); // RX, TX 75 | TinyGPSPlus gps; 76 | rn2xx3 myLora(Serial1); 77 | 78 | unsigned long last_update = 0; 79 | String toLog; 80 | uint8_t txBuffer[9]; 81 | uint32_t LatitudeBinary, LongitudeBinary; 82 | uint16_t altitudeGps; 83 | uint8_t hdopGps; 84 | 85 | #define PMTK_SET_NMEA_UPDATE_05HZ "$PMTK220,2000*1C" 86 | #define PMTK_SET_NMEA_UPDATE_1HZ "$PMTK220,1000*1F" 87 | #define PMTK_SET_NMEA_OUTPUT_RMCGGA "$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28" 88 | 89 | void setup() { 90 | //output LED pin 91 | pinMode(13, OUTPUT); 92 | led_on(); 93 | 94 | Serial.begin(57600); //serial to computer 95 | gpsSerial.begin(9600); //serial to gps 96 | Serial1.begin(57600); //serial to RN2xx3 97 | 98 | // make sure usb serial connection is available, 99 | // or after 10s go on anyway for 'headless' use of the 100 | // node. 101 | while ((!Serial) && (millis() < 10000)); 102 | 103 | Serial.println("TTN UNO + GPS shield startup"); 104 | 105 | //set up RN2xx3 106 | initialize_radio(); 107 | //transmit a startup message 108 | myLora.tx("TTN Mapper on TTN Uno with GPS"); 109 | 110 | gpsSerial.println(F(PMTK_SET_NMEA_OUTPUT_RMCGGA)); 111 | gpsSerial.println(F(PMTK_SET_NMEA_UPDATE_1HZ)); // 1 Hz update rate 112 | 113 | led_off(); 114 | delay(1000); 115 | } 116 | 117 | void initialize_radio() 118 | { 119 | delay(100); //wait for the RN2xx3's startup message 120 | Serial1.flush(); 121 | 122 | //print out the HWEUI so that we can register it via ttnctl 123 | String hweui = myLora.hweui(); 124 | while(hweui.length() != 16) 125 | { 126 | Serial.println("Communication with RN2xx3 unsuccessful. Power cycle the TTN UNO board."); 127 | delay(10000); 128 | hweui = myLora.hweui(); 129 | } 130 | Serial.println("When using OTAA, register this DevEUI: "); 131 | Serial.println(hweui); 132 | Serial.println("RN2xx3 firmware version:"); 133 | Serial.println(myLora.sysver()); 134 | 135 | //configure your keys and join the network 136 | Serial.println("Trying to join TTN"); 137 | bool join_result = false; 138 | 139 | //ABP: initABP(String addr, String AppSKey, String NwkSKey); 140 | join_result = myLora.initABP("02017201", "8D7FFEF938589D95AAD928C2E2E7E48F", "AE17E567AECC8787F749A62F5541D522"); 141 | 142 | //OTAA: initOTAA(String AppEUI, String AppKey); 143 | //join_result = myLora.initOTAA("70B3D57ED00001A6", "A23C96EE13804963F8C2BD6285448198"); 144 | 145 | while(!join_result) 146 | { 147 | Serial.println("Unable to join. Are your keys correct, and do you have TTN coverage?"); 148 | delay(60000); //delay a minute before retry 149 | join_result = myLora.init(); 150 | } 151 | Serial.println("Successfully joined TTN"); 152 | 153 | } 154 | 155 | void loop() { 156 | while (gpsSerial.available()){ 157 | gps.encode(gpsSerial.read()); 158 | } 159 | 160 | if (gps.location.age() < 1000 && (millis() - last_update) >= 1000) { 161 | led_on(); 162 | Serial.print("Interval: "); 163 | Serial.println(millis()-last_update); 164 | 165 | build_packet(); 166 | 167 | Serial.println(toLog); 168 | myLora.txBytes(txBuffer, sizeof(txBuffer)); 169 | Serial.println("TX done"); 170 | 171 | led_off(); 172 | last_update = millis(); 173 | } 174 | 175 | 176 | } 177 | 178 | void build_packet() 179 | { 180 | LatitudeBinary = ((gps.location.lat() + 90) / 180.0) * 16777215; 181 | LongitudeBinary = ((gps.location.lng() + 180) / 360.0) * 16777215; 182 | 183 | txBuffer[0] = ( LatitudeBinary >> 16 ) & 0xFF; 184 | txBuffer[1] = ( LatitudeBinary >> 8 ) & 0xFF; 185 | txBuffer[2] = LatitudeBinary & 0xFF; 186 | 187 | txBuffer[3] = ( LongitudeBinary >> 16 ) & 0xFF; 188 | txBuffer[4] = ( LongitudeBinary >> 8 ) & 0xFF; 189 | txBuffer[5] = LongitudeBinary & 0xFF; 190 | 191 | altitudeGps = gps.altitude.meters(); 192 | txBuffer[6] = ( altitudeGps >> 8 ) & 0xFF; 193 | txBuffer[7] = altitudeGps & 0xFF; 194 | 195 | hdopGps = gps.hdop.value()/10; 196 | txBuffer[8] = hdopGps & 0xFF; 197 | 198 | toLog = ""; 199 | for(size_t i = 0; i>>0) + ((bytes[1]<<8)>>>0) + bytes[2]; 36 | decoded.lat = (decoded.lat / 16777215.0 * 180) - 90; 37 | 38 | decoded.lon = ((bytes[3]<<16)>>>0) + ((bytes[4]<<8)>>>0) + bytes[5]; 39 | decoded.lon = (decoded.lon / 16777215.0 * 360) - 180; 40 | 41 | var altValue = ((bytes[6]<<8)>>>0) + bytes[7]; 42 | var sign = bytes[6] & (1 << 7); 43 | if(sign) 44 | { 45 | decoded.alt = 0xFFFF0000 | altValue; 46 | } 47 | else 48 | { 49 | decoded.alt = altValue; 50 | } 51 | 52 | decoded.hdop = bytes[8] / 10.0; 53 | 54 | return decoded; 55 | } 56 | * 57 | */ 58 | #include 59 | #include "Sodaq_UBlox_GPS.h" 60 | #include 61 | 62 | //create an instance of the rn2xx3 library, 63 | //giving Serial1 as stream to use for communication with the radio 64 | rn2xx3 myLora(Serial1); 65 | 66 | String toLog; 67 | uint8_t txBuffer[9]; 68 | uint32_t LatitudeBinary, LongitudeBinary; 69 | uint16_t altitudeGps; 70 | uint8_t hdopGps; 71 | int dr = 0; 72 | 73 | void setup() 74 | { 75 | delay(3000); 76 | 77 | SerialUSB.begin(57600); 78 | Serial1.begin(57600); 79 | 80 | // make sure usb serial connection is available, 81 | // or after 10s go on anyway for 'headless' use of the node. 82 | while ((!SerialUSB) && (millis() < 10000)); 83 | 84 | SerialUSB.println("SODAQ LoRaONE TTN Mapper starting"); 85 | 86 | initialize_radio(); 87 | 88 | //transmit a startup message 89 | myLora.tx("TTN Mapper on Sodaq One"); 90 | 91 | // Enable next line to enable debug information of the sodaq_gps 92 | //sodaq_gps.setDiag(SerialUSB); 93 | 94 | // initialize GPS 95 | sodaq_gps.init(GPS_ENABLE); 96 | 97 | // Set the datarate/spreading factor at which we communicate. 98 | // DR5 is the fastest and best to use. DR0 is the slowest. 99 | myLora.setDR(dr); 100 | 101 | // LED pins as outputs. HIGH=Off, LOW=On 102 | pinMode(LED_BLUE, OUTPUT); 103 | digitalWrite(LED_BLUE, HIGH); 104 | pinMode(LED_RED, OUTPUT); 105 | digitalWrite(LED_RED, HIGH); 106 | pinMode(LED_GREEN, OUTPUT); 107 | digitalWrite(LED_GREEN, HIGH); 108 | } 109 | 110 | void initialize_radio() 111 | { 112 | delay(100); //wait for the RN2xx3's startup message 113 | while(Serial1.available()){ 114 | Serial1.read(); 115 | } 116 | 117 | //print out the HWEUI so that we can register it via ttnctl 118 | String hweui = myLora.hweui(); 119 | while(hweui.length() != 16) 120 | { 121 | SerialUSB.println("Communication with RN2xx3 unsuccessful. Power cycle the Sodaq One board."); 122 | delay(10000); 123 | hweui = myLora.hweui(); 124 | } 125 | SerialUSB.println("When using OTAA, register this DevEUI: "); 126 | SerialUSB.println(hweui); 127 | SerialUSB.println("RN2xx3 firmware version:"); 128 | SerialUSB.println(myLora.sysver()); 129 | 130 | //configure your keys and join the network 131 | SerialUSB.println("Trying to join TTN"); 132 | bool join_result = false; 133 | 134 | //ABP: initABP(String addr, String AppSKey, String NwkSKey); 135 | join_result = myLora.initABP("02017201", "8D7FFEF938589D95AAD928C2E2E7E48F", "AE17E567AECC8787F749A62F5541D522"); 136 | 137 | //OTAA: initOTAA(String AppEUI, String AppKey); 138 | //join_result = myLora.initOTAA("70B3D57ED00001A6", "A23C96EE13804963F8C2BD6285448198"); 139 | 140 | while(!join_result) 141 | { 142 | SerialUSB.println("Unable to join. Are your keys correct, and do you have TTN coverage?"); 143 | delay(60000); //delay a minute before retry 144 | digitalWrite(LED_BLUE, LOW); 145 | join_result = myLora.init(); 146 | digitalWrite(LED_BLUE, HIGH); 147 | } 148 | 149 | SerialUSB.println("Successfully joined TTN"); 150 | 151 | } 152 | 153 | void loop() 154 | { 155 | SerialUSB.println("Waiting for GPS fix"); 156 | 157 | digitalWrite(LED_GREEN, LOW); 158 | // Keep the GPS enabled after we do a scan, increases accuracy 159 | sodaq_gps.scan(true); 160 | digitalWrite(LED_GREEN, HIGH); 161 | 162 | // if the latitude is 0, we likely do not have a GPS fix yet, so wait longer 163 | while(sodaq_gps.getLat()==0.0) 164 | { 165 | SerialUSB.println("Latitude still 0.0, doing another scan"); 166 | 167 | digitalWrite(LED_RED, LOW); 168 | // Keep the GPS enabled after we do a scan, increases accuracy 169 | sodaq_gps.scan(true); 170 | digitalWrite(LED_RED, HIGH); 171 | } 172 | 173 | LatitudeBinary = ((sodaq_gps.getLat() + 90) / 180) * 16777215; 174 | LongitudeBinary = ((sodaq_gps.getLon() + 180) / 360) * 16777215; 175 | 176 | txBuffer[0] = ( LatitudeBinary >> 16 ) & 0xFF; 177 | txBuffer[1] = ( LatitudeBinary >> 8 ) & 0xFF; 178 | txBuffer[2] = LatitudeBinary & 0xFF; 179 | 180 | txBuffer[3] = ( LongitudeBinary >> 16 ) & 0xFF; 181 | txBuffer[4] = ( LongitudeBinary >> 8 ) & 0xFF; 182 | txBuffer[5] = LongitudeBinary & 0xFF; 183 | 184 | altitudeGps = sodaq_gps.getAlt(); 185 | txBuffer[6] = ( altitudeGps >> 8 ) & 0xFF; 186 | txBuffer[7] = altitudeGps & 0xFF; 187 | 188 | hdopGps = sodaq_gps.getHDOP()*10; 189 | txBuffer[8] = hdopGps & 0xFF; 190 | 191 | toLog = ""; 192 | for(size_t i = 0; i= 100 28 | #include "Arduino.h" 29 | #else 30 | #include "WProgram.h" 31 | #endif 32 | #include 33 | 34 | #define _GPS_VERSION "0.92" // software version of this library 35 | #define _GPS_MPH_PER_KNOT 1.15077945 36 | #define _GPS_MPS_PER_KNOT 0.51444444 37 | #define _GPS_KMPH_PER_KNOT 1.852 38 | #define _GPS_MILES_PER_METER 0.00062137112 39 | #define _GPS_KM_PER_METER 0.001 40 | #define _GPS_FEET_PER_METER 3.2808399 41 | #define _GPS_MAX_FIELD_SIZE 15 42 | 43 | struct RawDegrees 44 | { 45 | uint16_t deg; 46 | uint32_t billionths; 47 | bool negative; 48 | public: 49 | RawDegrees() : deg(0), billionths(0), negative(false) 50 | {} 51 | }; 52 | 53 | struct TinyGPSLocation 54 | { 55 | friend class TinyGPSPlus; 56 | public: 57 | bool isValid() const { return valid; } 58 | bool isUpdated() const { return updated; } 59 | uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } 60 | const RawDegrees &rawLat() { updated = false; return rawLatData; } 61 | const RawDegrees &rawLng() { updated = false; return rawLngData; } 62 | double lat(); 63 | double lng(); 64 | 65 | TinyGPSLocation() : valid(false), updated(false) 66 | {} 67 | 68 | private: 69 | bool valid, updated; 70 | RawDegrees rawLatData, rawLngData, rawNewLatData, rawNewLngData; 71 | uint32_t lastCommitTime; 72 | void commit(); 73 | void setLatitude(const char *term); 74 | void setLongitude(const char *term); 75 | }; 76 | 77 | struct TinyGPSDate 78 | { 79 | friend class TinyGPSPlus; 80 | public: 81 | bool isValid() const { return valid; } 82 | bool isUpdated() const { return updated; } 83 | uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } 84 | 85 | uint32_t value() { updated = false; return date; } 86 | uint16_t year(); 87 | uint8_t month(); 88 | uint8_t day(); 89 | 90 | TinyGPSDate() : valid(false), updated(false), date(0) 91 | {} 92 | 93 | private: 94 | bool valid, updated; 95 | uint32_t date, newDate; 96 | uint32_t lastCommitTime; 97 | void commit(); 98 | void setDate(const char *term); 99 | }; 100 | 101 | struct TinyGPSTime 102 | { 103 | friend class TinyGPSPlus; 104 | public: 105 | bool isValid() const { return valid; } 106 | bool isUpdated() const { return updated; } 107 | uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } 108 | 109 | uint32_t value() { updated = false; return time; } 110 | uint8_t hour(); 111 | uint8_t minute(); 112 | uint8_t second(); 113 | uint8_t centisecond(); 114 | 115 | TinyGPSTime() : valid(false), updated(false), time(0) 116 | {} 117 | 118 | private: 119 | bool valid, updated; 120 | uint32_t time, newTime; 121 | uint32_t lastCommitTime; 122 | void commit(); 123 | void setTime(const char *term); 124 | }; 125 | 126 | struct TinyGPSDecimal 127 | { 128 | friend class TinyGPSPlus; 129 | public: 130 | bool isValid() const { return valid; } 131 | bool isUpdated() const { return updated; } 132 | uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } 133 | int32_t value() { updated = false; return val; } 134 | 135 | TinyGPSDecimal() : valid(false), updated(false), val(0) 136 | {} 137 | 138 | private: 139 | bool valid, updated; 140 | uint32_t lastCommitTime; 141 | int32_t val, newval; 142 | void commit(); 143 | void set(const char *term); 144 | }; 145 | 146 | struct TinyGPSInteger 147 | { 148 | friend class TinyGPSPlus; 149 | public: 150 | bool isValid() const { return valid; } 151 | bool isUpdated() const { return updated; } 152 | uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } 153 | uint32_t value() { updated = false; return val; } 154 | 155 | TinyGPSInteger() : valid(false), updated(false), val(0) 156 | {} 157 | 158 | private: 159 | bool valid, updated; 160 | uint32_t lastCommitTime; 161 | uint32_t val, newval; 162 | void commit(); 163 | void set(const char *term); 164 | }; 165 | 166 | struct TinyGPSSpeed : TinyGPSDecimal 167 | { 168 | double knots() { return value() / 100.0; } 169 | double mph() { return _GPS_MPH_PER_KNOT * value() / 100.0; } 170 | double mps() { return _GPS_MPS_PER_KNOT * value() / 100.0; } 171 | double kmph() { return _GPS_KMPH_PER_KNOT * value() / 100.0; } 172 | }; 173 | 174 | struct TinyGPSCourse : public TinyGPSDecimal 175 | { 176 | double deg() { return value() / 100.0; } 177 | }; 178 | 179 | struct TinyGPSAltitude : TinyGPSDecimal 180 | { 181 | double meters() { return value() / 100.0; } 182 | double miles() { return _GPS_MILES_PER_METER * value() / 100.0; } 183 | double kilometers() { return _GPS_KM_PER_METER * value() / 100.0; } 184 | double feet() { return _GPS_FEET_PER_METER * value() / 100.0; } 185 | }; 186 | 187 | class TinyGPSPlus; 188 | class TinyGPSCustom 189 | { 190 | public: 191 | TinyGPSCustom() {}; 192 | TinyGPSCustom(TinyGPSPlus &gps, const char *sentenceName, int termNumber); 193 | void begin(TinyGPSPlus &gps, const char *_sentenceName, int _termNumber); 194 | 195 | bool isUpdated() const { return updated; } 196 | bool isValid() const { return valid; } 197 | uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } 198 | const char *value() { updated = false; return buffer; } 199 | 200 | private: 201 | void commit(); 202 | void set(const char *term); 203 | 204 | char stagingBuffer[_GPS_MAX_FIELD_SIZE + 1]; 205 | char buffer[_GPS_MAX_FIELD_SIZE + 1]; 206 | unsigned long lastCommitTime; 207 | bool valid, updated; 208 | const char *sentenceName; 209 | int termNumber; 210 | friend class TinyGPSPlus; 211 | TinyGPSCustom *next; 212 | }; 213 | 214 | class TinyGPSPlus 215 | { 216 | public: 217 | TinyGPSPlus(); 218 | bool encode(char c); // process one character received from GPS 219 | TinyGPSPlus &operator << (char c) {encode(c); return *this;} 220 | 221 | TinyGPSLocation location; 222 | TinyGPSDate date; 223 | TinyGPSTime time; 224 | TinyGPSSpeed speed; 225 | TinyGPSCourse course; 226 | TinyGPSAltitude altitude; 227 | TinyGPSInteger satellites; 228 | TinyGPSDecimal hdop; 229 | 230 | static const char *libraryVersion() { return _GPS_VERSION; } 231 | 232 | static double distanceBetween(double lat1, double long1, double lat2, double long2); 233 | static double courseTo(double lat1, double long1, double lat2, double long2); 234 | static const char *cardinal(double course); 235 | 236 | static int32_t parseDecimal(const char *term); 237 | static void parseDegrees(const char *term, RawDegrees °); 238 | 239 | uint32_t charsProcessed() const { return encodedCharCount; } 240 | uint32_t sentencesWithFix() const { return sentencesWithFixCount; } 241 | uint32_t failedChecksum() const { return failedChecksumCount; } 242 | uint32_t passedChecksum() const { return passedChecksumCount; } 243 | 244 | private: 245 | enum {GPS_SENTENCE_GPGGA, GPS_SENTENCE_GPRMC, GPS_SENTENCE_OTHER}; 246 | 247 | // parsing state variables 248 | uint8_t parity; 249 | bool isChecksumTerm; 250 | char term[_GPS_MAX_FIELD_SIZE]; 251 | uint8_t curSentenceType; 252 | uint8_t curTermNumber; 253 | uint8_t curTermOffset; 254 | bool sentenceHasFix; 255 | 256 | // custom element support 257 | friend class TinyGPSCustom; 258 | TinyGPSCustom *customElts; 259 | TinyGPSCustom *customCandidates; 260 | void insertCustom(TinyGPSCustom *pElt, const char *sentenceName, int index); 261 | 262 | // statistics 263 | uint32_t encodedCharCount; 264 | uint32_t sentencesWithFixCount; 265 | uint32_t failedChecksumCount; 266 | uint32_t passedChecksumCount; 267 | 268 | // internal utilities 269 | int fromHex(char a); 270 | bool endOfTermHandler(); 271 | }; 272 | 273 | #endif // def(__TinyGPSPlus_h) 274 | -------------------------------------------------------------------------------- /src/rn2xx3.h: -------------------------------------------------------------------------------- 1 | /* 2 | * A library for controlling a Microchip RN2xx3 LoRa radio. 3 | * 4 | * @Author JP Meijers 5 | * @Author Nicolas Schteinschraber 6 | * @Date 18/12/2015 7 | * 8 | */ 9 | 10 | #ifndef rn2xx3_h 11 | #define rn2xx3_h 12 | 13 | #include "Arduino.h" 14 | 15 | enum RN2xx3_t { 16 | RN_NA = 0, // Not set 17 | RN2903 = 2903, 18 | RN2483 = 2483 19 | }; 20 | 21 | enum FREQ_PLAN { 22 | SINGLE_CHANNEL_EU, 23 | TTN_EU, 24 | TTN_US, 25 | DEFAULT_EU 26 | }; 27 | 28 | enum TX_RETURN_TYPE { 29 | TX_FAIL = 0, // The transmission failed. 30 | // If you sent a confirmed message and it is not acked, 31 | // this will be the returned value. 32 | 33 | TX_SUCCESS = 1, // The transmission was successful. 34 | // Also the case when a confirmed message was acked. 35 | 36 | TX_WITH_RX = 2 // A downlink message was received after the transmission. 37 | // This also implies that a confirmed message is acked. 38 | }; 39 | 40 | class rn2xx3 41 | { 42 | public: 43 | 44 | /* 45 | * A simplified constructor taking only a Stream ({Software/Hardware}Serial) object. 46 | * The serial port should already be initialised when initialising this library. 47 | */ 48 | rn2xx3(Stream& serial); 49 | 50 | /* 51 | * Transmit the correct sequence to the rn2xx3 to trigger its autobauding feature. 52 | * After this operation the rn2xx3 should communicate at the same baud rate than us. 53 | */ 54 | void autobaud(); 55 | 56 | /* 57 | * Get the hardware EUI of the radio, so that we can register it on The Things Network 58 | * and obtain the correct AppKey. 59 | * You have to have a working serial connection to the radio before calling this function. 60 | * In other words you have to at least call autobaud() some time before this function. 61 | */ 62 | String hweui(); 63 | 64 | /* 65 | * Returns the AppSKey or AppKey used when initializing the radio. 66 | * In the case of ABP this function will return the App Session Key. 67 | * In the case of OTAA this function will return the App Key. 68 | */ 69 | String appkey(); 70 | 71 | /* 72 | * In the case of OTAA this function will return the Application EUI used 73 | * to initialize the radio. 74 | */ 75 | String appeui(); 76 | 77 | /* 78 | * In the case of OTAA this function will return the Device EUI used to 79 | * initialize the radio. This is not necessarily the same as the Hardware EUI. 80 | * To obtain the Hardware EUI, use the hweui() function. 81 | */ 82 | String deveui(); 83 | 84 | /* 85 | * Get the RN2xx3's hardware and firmware version number. This is also used 86 | * to detect if the module is either an RN2483 or an RN2903. 87 | */ 88 | String sysver(); 89 | 90 | /* 91 | * Initialise the RN2xx3 and join the LoRa network (if applicable). 92 | * This function can only be called after calling initABP() or initOTAA(). 93 | * The sole purpose of this function is to re-initialise the radio if it 94 | * is in an unknown state. 95 | */ 96 | bool init(); 97 | 98 | /* 99 | * Initialise the RN2xx3 and join a network using personalization. 100 | * 101 | * addr: The device address as a HEX string. 102 | * Example "0203FFEE" 103 | * AppSKey: Application Session Key as a HEX string. 104 | * Example "8D7FFEF938589D95AAD928C2E2E7E48F" 105 | * NwkSKey: Network Session Key as a HEX string. 106 | * Example "AE17E567AECC8787F749A62F5541D522" 107 | */ 108 | bool initABP(String addr, String AppSKey, String NwkSKey); 109 | 110 | //TODO: initABP(uint8_t * addr, uint8_t * AppSKey, uint8_t * NwkSKey) 111 | 112 | /* 113 | * Initialise the RN2xx3 and join a network using over the air activation. 114 | * 115 | * AppEUI: Application EUI as a HEX string. 116 | * Example "70B3D57ED00001A6" 117 | * AppKey: Application key as a HEX string. 118 | * Example "A23C96EE13804963F8C2BD6285448198" 119 | * DevEUI: Device EUI as a HEX string. 120 | * Example "0011223344556677" 121 | * If the DevEUI parameter is omitted, the Hardware EUI from module will be used 122 | * If no keys, or invalid length keys, are provided, no keys 123 | * will be configured. If the module is already configured with some keys 124 | * they will be used. Otherwise the join will fail and this function 125 | * will return false. 126 | */ 127 | bool initOTAA(String AppEUI="", String AppKey="", String DevEUI=""); 128 | 129 | /* 130 | * Initialise the RN2xx3 and join a network using over the air activation, 131 | * using byte arrays. This is useful when storing the keys in eeprom or flash 132 | * and reading them out in runtime. 133 | * 134 | * AppEUI: Application EUI as a uint8_t buffer 135 | * AppKey: Application key as a uint8_t buffer 136 | * DevEui: Device EUI as a uint8_t buffer (optional - set to 0 to use Hardware EUI) 137 | */ 138 | bool initOTAA(uint8_t * AppEUI, uint8_t * AppKey, uint8_t * DevEui); 139 | 140 | /* 141 | * Transmit the provided data. The data is hex-encoded by this library, 142 | * so plain text can be provided. 143 | * This function is an alias for txUncnf(). 144 | * 145 | * Parameter is an ascii text string. 146 | */ 147 | TX_RETURN_TYPE tx(String); 148 | 149 | /* 150 | * Transmit raw byte encoded data via LoRa WAN. 151 | * This method expects a raw byte array as first parameter. 152 | * The second parameter is the count of the bytes to send. 153 | */ 154 | TX_RETURN_TYPE txBytes(const byte*, uint8_t); 155 | 156 | /* 157 | * Do a confirmed transmission via LoRa WAN. 158 | * 159 | * Parameter is an ascii text string. 160 | */ 161 | TX_RETURN_TYPE txCnf(String); 162 | 163 | /* 164 | * Do an unconfirmed transmission via LoRa WAN. 165 | * 166 | * Parameter is an ascii text string. 167 | */ 168 | TX_RETURN_TYPE txUncnf(String); 169 | 170 | /* 171 | * Transmit the provided data using the provided command. 172 | * 173 | * String - the tx command to send 174 | can only be one of "mac tx cnf 1 " or "mac tx uncnf 1 " 175 | * String - an ascii text string if bool is true. A HEX string if bool is false. 176 | * bool - should the data string be hex encoded or not 177 | */ 178 | TX_RETURN_TYPE txCommand(String, String, bool); 179 | 180 | /* 181 | * Change the datarate at which the RN2xx3 transmits. 182 | * A value of between 0 and 5 can be specified, 183 | * as is defined in the LoRaWan specs. 184 | * This can be overwritten by the network when using OTAA. 185 | * So to force a datarate, call this function after initOTAA(). 186 | */ 187 | void setDR(int dr); 188 | 189 | /* 190 | * Put the RN2xx3 to sleep for a specified timeframe. 191 | * The RN2xx3 accepts values from 100 to 4294967296. 192 | * Rumour has it that you need to do a autobaud() after the module wakes up again. 193 | */ 194 | void sleep(long msec); 195 | 196 | /* 197 | * Send a raw command to the RN2xx3 module. 198 | * Returns the raw string as received back from the RN2xx3. 199 | * If the RN2xx3 replies with multiple line, only the first line will be returned. 200 | */ 201 | String sendRawCommand(String command); 202 | 203 | /* 204 | * Returns the module type either RN2903 or RN2483, or NA. 205 | */ 206 | RN2xx3_t moduleType(); 207 | 208 | /* 209 | * Set the active channels to use. 210 | * Returns true if setting the channels is possible. 211 | * Returns false if you are trying to use the wrong channels on the wrong module type. 212 | */ 213 | bool setFrequencyPlan(FREQ_PLAN); 214 | 215 | /* 216 | * Returns the last downlink message HEX string. 217 | */ 218 | String getRx(); 219 | 220 | /* 221 | * Get the RN2xx3's SNR of the last received packet. Helpful to debug link quality. 222 | */ 223 | int getSNR(); 224 | 225 | /* 226 | * Encode an ASCII string to a HEX string as needed when passed 227 | * to the RN2xx3 module. 228 | */ 229 | String base16encode(String); 230 | 231 | /* 232 | * Decode a HEX string to an ASCII string. Useful to decode a 233 | * string received from the RN2xx3. 234 | */ 235 | String base16decode(String); 236 | 237 | private: 238 | Stream& _serial; 239 | 240 | RN2xx3_t _moduleType = RN_NA; 241 | 242 | //Flags to switch code paths. Default is to use OTAA. 243 | bool _otaa = true; 244 | 245 | //The default address to use on TTN if no address is defined. 246 | //This one falls in the "testing" address space. 247 | String _devAddr = "03FFBEEF"; 248 | 249 | // if you want to use another DevEUI than the hardware one 250 | // use this deveui for LoRa WAN 251 | String _deveui = "0011223344556677"; 252 | 253 | //the appeui to use for LoRa WAN 254 | String _appeui = "0"; 255 | 256 | //the nwkskey to use for LoRa WAN 257 | String _nwkskey = "0"; 258 | 259 | //the appskey/appkey to use for LoRa WAN 260 | String _appskey = "0"; 261 | 262 | // The downlink messenge 263 | String _rxMessenge = ""; 264 | 265 | /* 266 | * Auto configure for either RN2903 or RN2483 module 267 | */ 268 | RN2xx3_t configureModuleType(); 269 | 270 | void sendEncoded(String); 271 | }; 272 | 273 | #endif 274 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /examples/TheThingsUno-GPSshield-TTN-Mapper-binary/TinyGPS++.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | TinyGPS++ - a small GPS library for Arduino providing universal NMEA parsing 3 | Based on work by and "distanceBetween" and "courseTo" courtesy of Maarten Lamers. 4 | Suggestion to add satellites, courseTo(), and cardinal() by Matt Monson. 5 | Location precision improvements suggested by Wayne Holder. 6 | Copyright (C) 2008-2013 Mikal Hart 7 | All rights reserved. 8 | 9 | This library is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | 14 | This library is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | Lesser General Public License for more details. 18 | 19 | You should have received a copy of the GNU Lesser General Public 20 | License along with this library; if not, write to the Free Software 21 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 22 | */ 23 | 24 | #include "TinyGPS++.h" 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #define _GPRMCterm "GPRMC" 31 | #define _GPGGAterm "GPGGA" 32 | 33 | TinyGPSPlus::TinyGPSPlus() 34 | : parity(0) 35 | , isChecksumTerm(false) 36 | , curSentenceType(GPS_SENTENCE_OTHER) 37 | , curTermNumber(0) 38 | , curTermOffset(0) 39 | , sentenceHasFix(false) 40 | , customElts(0) 41 | , customCandidates(0) 42 | , encodedCharCount(0) 43 | , sentencesWithFixCount(0) 44 | , failedChecksumCount(0) 45 | , passedChecksumCount(0) 46 | { 47 | term[0] = '\0'; 48 | } 49 | 50 | // 51 | // public methods 52 | // 53 | 54 | bool TinyGPSPlus::encode(char c) 55 | { 56 | ++encodedCharCount; 57 | 58 | switch(c) 59 | { 60 | case ',': // term terminators 61 | parity ^= (uint8_t)c; 62 | case '\r': 63 | case '\n': 64 | case '*': 65 | { 66 | bool isValidSentence = false; 67 | if (curTermOffset < sizeof(term)) 68 | { 69 | term[curTermOffset] = 0; 70 | isValidSentence = endOfTermHandler(); 71 | } 72 | ++curTermNumber; 73 | curTermOffset = 0; 74 | isChecksumTerm = c == '*'; 75 | return isValidSentence; 76 | } 77 | break; 78 | 79 | case '$': // sentence begin 80 | curTermNumber = curTermOffset = 0; 81 | parity = 0; 82 | curSentenceType = GPS_SENTENCE_OTHER; 83 | isChecksumTerm = false; 84 | sentenceHasFix = false; 85 | return false; 86 | 87 | default: // ordinary characters 88 | if (curTermOffset < sizeof(term) - 1) 89 | term[curTermOffset++] = c; 90 | if (!isChecksumTerm) 91 | parity ^= c; 92 | return false; 93 | } 94 | 95 | return false; 96 | } 97 | 98 | // 99 | // internal utilities 100 | // 101 | int TinyGPSPlus::fromHex(char a) 102 | { 103 | if (a >= 'A' && a <= 'F') 104 | return a - 'A' + 10; 105 | else if (a >= 'a' && a <= 'f') 106 | return a - 'a' + 10; 107 | else 108 | return a - '0'; 109 | } 110 | 111 | // static 112 | // Parse a (potentially negative) number with up to 2 decimal digits -xxxx.yy 113 | int32_t TinyGPSPlus::parseDecimal(const char *term) 114 | { 115 | bool negative = *term == '-'; 116 | if (negative) ++term; 117 | int32_t ret = 100 * (int32_t)atol(term); 118 | while (isdigit(*term)) ++term; 119 | if (*term == '.' && isdigit(term[1])) 120 | { 121 | ret += 10 * (term[1] - '0'); 122 | if (isdigit(term[2])) 123 | ret += term[2] - '0'; 124 | } 125 | return negative ? -ret : ret; 126 | } 127 | 128 | // static 129 | // Parse degrees in that funny NMEA format DDMM.MMMM 130 | void TinyGPSPlus::parseDegrees(const char *term, RawDegrees °) 131 | { 132 | uint32_t leftOfDecimal = (uint32_t)atol(term); 133 | uint16_t minutes = (uint16_t)(leftOfDecimal % 100); 134 | uint32_t multiplier = 10000000UL; 135 | uint32_t tenMillionthsOfMinutes = minutes * multiplier; 136 | 137 | deg.deg = (int16_t)(leftOfDecimal / 100); 138 | 139 | while (isdigit(*term)) 140 | ++term; 141 | 142 | if (*term == '.') 143 | while (isdigit(*++term)) 144 | { 145 | multiplier /= 10; 146 | tenMillionthsOfMinutes += (*term - '0') * multiplier; 147 | } 148 | 149 | deg.billionths = (5 * tenMillionthsOfMinutes + 1) / 3; 150 | deg.negative = false; 151 | } 152 | 153 | #define COMBINE(sentence_type, term_number) (((unsigned)(sentence_type) << 5) | term_number) 154 | 155 | // Processes a just-completed term 156 | // Returns true if new sentence has just passed checksum test and is validated 157 | bool TinyGPSPlus::endOfTermHandler() 158 | { 159 | // If it's the checksum term, and the checksum checks out, commit 160 | if (isChecksumTerm) 161 | { 162 | byte checksum = 16 * fromHex(term[0]) + fromHex(term[1]); 163 | if (checksum == parity) 164 | { 165 | passedChecksumCount++; 166 | if (sentenceHasFix) 167 | ++sentencesWithFixCount; 168 | 169 | switch(curSentenceType) 170 | { 171 | case GPS_SENTENCE_GPRMC: 172 | date.commit(); 173 | time.commit(); 174 | if (sentenceHasFix) 175 | { 176 | location.commit(); 177 | speed.commit(); 178 | course.commit(); 179 | } 180 | break; 181 | case GPS_SENTENCE_GPGGA: 182 | time.commit(); 183 | if (sentenceHasFix) 184 | { 185 | location.commit(); 186 | altitude.commit(); 187 | } 188 | satellites.commit(); 189 | hdop.commit(); 190 | break; 191 | } 192 | 193 | // Commit all custom listeners of this sentence type 194 | for (TinyGPSCustom *p = customCandidates; p != NULL && strcmp(p->sentenceName, customCandidates->sentenceName) == 0; p = p->next) 195 | p->commit(); 196 | return true; 197 | } 198 | 199 | else 200 | { 201 | ++failedChecksumCount; 202 | } 203 | 204 | return false; 205 | } 206 | 207 | // the first term determines the sentence type 208 | if (curTermNumber == 0) 209 | { 210 | if (!strcmp(term, _GPRMCterm)) 211 | curSentenceType = GPS_SENTENCE_GPRMC; 212 | else if (!strcmp(term, _GPGGAterm)) 213 | curSentenceType = GPS_SENTENCE_GPGGA; 214 | else 215 | curSentenceType = GPS_SENTENCE_OTHER; 216 | 217 | // Any custom candidates of this sentence type? 218 | for (customCandidates = customElts; customCandidates != NULL && strcmp(customCandidates->sentenceName, term) < 0; customCandidates = customCandidates->next); 219 | if (customCandidates != NULL && strcmp(customCandidates->sentenceName, term) > 0) 220 | customCandidates = NULL; 221 | 222 | return false; 223 | } 224 | 225 | if (curSentenceType != GPS_SENTENCE_OTHER && term[0]) 226 | switch(COMBINE(curSentenceType, curTermNumber)) 227 | { 228 | case COMBINE(GPS_SENTENCE_GPRMC, 1): // Time in both sentences 229 | case COMBINE(GPS_SENTENCE_GPGGA, 1): 230 | time.setTime(term); 231 | break; 232 | case COMBINE(GPS_SENTENCE_GPRMC, 2): // GPRMC validity 233 | sentenceHasFix = term[0] == 'A'; 234 | break; 235 | case COMBINE(GPS_SENTENCE_GPRMC, 3): // Latitude 236 | case COMBINE(GPS_SENTENCE_GPGGA, 2): 237 | location.setLatitude(term); 238 | break; 239 | case COMBINE(GPS_SENTENCE_GPRMC, 4): // N/S 240 | case COMBINE(GPS_SENTENCE_GPGGA, 3): 241 | location.rawNewLatData.negative = term[0] == 'S'; 242 | break; 243 | case COMBINE(GPS_SENTENCE_GPRMC, 5): // Longitude 244 | case COMBINE(GPS_SENTENCE_GPGGA, 4): 245 | location.setLongitude(term); 246 | break; 247 | case COMBINE(GPS_SENTENCE_GPRMC, 6): // E/W 248 | case COMBINE(GPS_SENTENCE_GPGGA, 5): 249 | location.rawNewLngData.negative = term[0] == 'W'; 250 | break; 251 | case COMBINE(GPS_SENTENCE_GPRMC, 7): // Speed (GPRMC) 252 | speed.set(term); 253 | break; 254 | case COMBINE(GPS_SENTENCE_GPRMC, 8): // Course (GPRMC) 255 | course.set(term); 256 | break; 257 | case COMBINE(GPS_SENTENCE_GPRMC, 9): // Date (GPRMC) 258 | date.setDate(term); 259 | break; 260 | case COMBINE(GPS_SENTENCE_GPGGA, 6): // Fix data (GPGGA) 261 | sentenceHasFix = term[0] > '0'; 262 | break; 263 | case COMBINE(GPS_SENTENCE_GPGGA, 7): // Satellites used (GPGGA) 264 | satellites.set(term); 265 | break; 266 | case COMBINE(GPS_SENTENCE_GPGGA, 8): // HDOP 267 | hdop.set(term); 268 | break; 269 | case COMBINE(GPS_SENTENCE_GPGGA, 9): // Altitude (GPGGA) 270 | altitude.set(term); 271 | break; 272 | } 273 | 274 | // Set custom values as needed 275 | for (TinyGPSCustom *p = customCandidates; p != NULL && strcmp(p->sentenceName, customCandidates->sentenceName) == 0 && p->termNumber <= curTermNumber; p = p->next) 276 | if (p->termNumber == curTermNumber) 277 | p->set(term); 278 | 279 | return false; 280 | } 281 | 282 | /* static */ 283 | double TinyGPSPlus::distanceBetween(double lat1, double long1, double lat2, double long2) 284 | { 285 | // returns distance in meters between two positions, both specified 286 | // as signed decimal-degrees latitude and longitude. Uses great-circle 287 | // distance computation for hypothetical sphere of radius 6372795 meters. 288 | // Because Earth is no exact sphere, rounding errors may be up to 0.5%. 289 | // Courtesy of Maarten Lamers 290 | double delta = radians(long1-long2); 291 | double sdlong = sin(delta); 292 | double cdlong = cos(delta); 293 | lat1 = radians(lat1); 294 | lat2 = radians(lat2); 295 | double slat1 = sin(lat1); 296 | double clat1 = cos(lat1); 297 | double slat2 = sin(lat2); 298 | double clat2 = cos(lat2); 299 | delta = (clat1 * slat2) - (slat1 * clat2 * cdlong); 300 | delta = sq(delta); 301 | delta += sq(clat2 * sdlong); 302 | delta = sqrt(delta); 303 | double denom = (slat1 * slat2) + (clat1 * clat2 * cdlong); 304 | delta = atan2(delta, denom); 305 | return delta * 6372795; 306 | } 307 | 308 | double TinyGPSPlus::courseTo(double lat1, double long1, double lat2, double long2) 309 | { 310 | // returns course in degrees (North=0, West=270) from position 1 to position 2, 311 | // both specified as signed decimal-degrees latitude and longitude. 312 | // Because Earth is no exact sphere, calculated course may be off by a tiny fraction. 313 | // Courtesy of Maarten Lamers 314 | double dlon = radians(long2-long1); 315 | lat1 = radians(lat1); 316 | lat2 = radians(lat2); 317 | double a1 = sin(dlon) * cos(lat2); 318 | double a2 = sin(lat1) * cos(lat2) * cos(dlon); 319 | a2 = cos(lat1) * sin(lat2) - a2; 320 | a2 = atan2(a1, a2); 321 | if (a2 < 0.0) 322 | { 323 | a2 += TWO_PI; 324 | } 325 | return degrees(a2); 326 | } 327 | 328 | const char *TinyGPSPlus::cardinal(double course) 329 | { 330 | static const char* directions[] = {"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"}; 331 | int direction = (int)((course + 11.25f) / 22.5f); 332 | return directions[direction % 16]; 333 | } 334 | 335 | void TinyGPSLocation::commit() 336 | { 337 | rawLatData = rawNewLatData; 338 | rawLngData = rawNewLngData; 339 | lastCommitTime = millis(); 340 | valid = updated = true; 341 | } 342 | 343 | void TinyGPSLocation::setLatitude(const char *term) 344 | { 345 | TinyGPSPlus::parseDegrees(term, rawNewLatData); 346 | } 347 | 348 | void TinyGPSLocation::setLongitude(const char *term) 349 | { 350 | TinyGPSPlus::parseDegrees(term, rawNewLngData); 351 | } 352 | 353 | double TinyGPSLocation::lat() 354 | { 355 | updated = false; 356 | double ret = rawLatData.deg + rawLatData.billionths / 1000000000.0; 357 | return rawLatData.negative ? -ret : ret; 358 | } 359 | 360 | double TinyGPSLocation::lng() 361 | { 362 | updated = false; 363 | double ret = rawLngData.deg + rawLngData.billionths / 1000000000.0; 364 | return rawLngData.negative ? -ret : ret; 365 | } 366 | 367 | void TinyGPSDate::commit() 368 | { 369 | date = newDate; 370 | lastCommitTime = millis(); 371 | valid = updated = true; 372 | } 373 | 374 | void TinyGPSTime::commit() 375 | { 376 | time = newTime; 377 | lastCommitTime = millis(); 378 | valid = updated = true; 379 | } 380 | 381 | void TinyGPSTime::setTime(const char *term) 382 | { 383 | newTime = (uint32_t)TinyGPSPlus::parseDecimal(term); 384 | } 385 | 386 | void TinyGPSDate::setDate(const char *term) 387 | { 388 | newDate = atol(term); 389 | } 390 | 391 | uint16_t TinyGPSDate::year() 392 | { 393 | updated = false; 394 | uint16_t year = date % 100; 395 | return year + 2000; 396 | } 397 | 398 | uint8_t TinyGPSDate::month() 399 | { 400 | updated = false; 401 | return (date / 100) % 100; 402 | } 403 | 404 | uint8_t TinyGPSDate::day() 405 | { 406 | updated = false; 407 | return date / 10000; 408 | } 409 | 410 | uint8_t TinyGPSTime::hour() 411 | { 412 | updated = false; 413 | return time / 1000000; 414 | } 415 | 416 | uint8_t TinyGPSTime::minute() 417 | { 418 | updated = false; 419 | return (time / 10000) % 100; 420 | } 421 | 422 | uint8_t TinyGPSTime::second() 423 | { 424 | updated = false; 425 | return (time / 100) % 100; 426 | } 427 | 428 | uint8_t TinyGPSTime::centisecond() 429 | { 430 | updated = false; 431 | return time % 100; 432 | } 433 | 434 | void TinyGPSDecimal::commit() 435 | { 436 | val = newval; 437 | lastCommitTime = millis(); 438 | valid = updated = true; 439 | } 440 | 441 | void TinyGPSDecimal::set(const char *term) 442 | { 443 | newval = TinyGPSPlus::parseDecimal(term); 444 | } 445 | 446 | void TinyGPSInteger::commit() 447 | { 448 | val = newval; 449 | lastCommitTime = millis(); 450 | valid = updated = true; 451 | } 452 | 453 | void TinyGPSInteger::set(const char *term) 454 | { 455 | newval = atol(term); 456 | } 457 | 458 | TinyGPSCustom::TinyGPSCustom(TinyGPSPlus &gps, const char *_sentenceName, int _termNumber) 459 | { 460 | begin(gps, _sentenceName, _termNumber); 461 | } 462 | 463 | void TinyGPSCustom::begin(TinyGPSPlus &gps, const char *_sentenceName, int _termNumber) 464 | { 465 | lastCommitTime = 0; 466 | updated = valid = false; 467 | sentenceName = _sentenceName; 468 | termNumber = _termNumber; 469 | memset(stagingBuffer, '\0', sizeof(stagingBuffer)); 470 | memset(buffer, '\0', sizeof(buffer)); 471 | 472 | // Insert this item into the GPS tree 473 | gps.insertCustom(this, _sentenceName, _termNumber); 474 | } 475 | 476 | void TinyGPSCustom::commit() 477 | { 478 | strcpy(this->buffer, this->stagingBuffer); 479 | lastCommitTime = millis(); 480 | valid = updated = true; 481 | } 482 | 483 | void TinyGPSCustom::set(const char *term) 484 | { 485 | strncpy(this->stagingBuffer, term, sizeof(this->stagingBuffer)); 486 | } 487 | 488 | void TinyGPSPlus::insertCustom(TinyGPSCustom *pElt, const char *sentenceName, int termNumber) 489 | { 490 | TinyGPSCustom **ppelt; 491 | 492 | for (ppelt = &this->customElts; *ppelt != NULL; ppelt = &(*ppelt)->next) 493 | { 494 | int cmp = strcmp(sentenceName, (*ppelt)->sentenceName); 495 | if (cmp < 0 || (cmp == 0 && termNumber < (*ppelt)->termNumber)) 496 | break; 497 | } 498 | 499 | pElt->next = *ppelt; 500 | *ppelt = pElt; 501 | } 502 | -------------------------------------------------------------------------------- /examples/SodaqOne-TTN-Mapper-ascii/Sodaq_UBlox_GPS.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 SODAQ. All rights reserved. 3 | * 4 | * This file is part of Sodaq_UBlox_GPS. 5 | * 6 | * Sodaq_UBlox_GPS is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published 8 | * by the Free Software Foundation, either version 3 of the License, or (at 9 | * your option) any later version. 10 | * 11 | * Sodaq_UBlox_GPS is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 14 | * License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with Sodaq_UBlox_GPS. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include "Sodaq_UBlox_GPS.h" 23 | 24 | #define DEBUG 1 25 | #ifdef DEBUG 26 | #define debugPrintLn(...) { if (this->_diagStream) this->_diagStream->println(__VA_ARGS__); } 27 | #define debugPrint(...) { if (this->_diagStream) this->_diagStream->print(__VA_ARGS__); } 28 | #else 29 | #define debugPrintLn(...) 30 | #define debugPrint(...) 31 | #endif 32 | 33 | static const size_t INPUT_BUFFER_SIZE = 128; // TODO Check UBlox manual ReceiverDescProtSpec 34 | static const uint8_t UBlox_I2C_addr = 0x42; 35 | 36 | #define GPS_ENABLE_ON HIGH 37 | #define GPS_ENABLE_OFF LOW 38 | 39 | Sodaq_UBlox_GPS sodaq_gps; 40 | const char Sodaq_UBlox_GPS::_fieldSep = ','; 41 | 42 | static inline bool is_timedout(uint32_t from, uint32_t nr_ms) __attribute__((always_inline)); 43 | static inline bool is_timedout(uint32_t from, uint32_t nr_ms) 44 | { 45 | return (millis() - from) > nr_ms; 46 | } 47 | 48 | Sodaq_UBlox_GPS::Sodaq_UBlox_GPS() 49 | { 50 | _enablePin = -1; 51 | 52 | _diagStream = 0; 53 | _addr = UBlox_I2C_addr; 54 | 55 | _minNumOfLines = 0; 56 | _minNumSatellites = 0; 57 | 58 | resetValues(); 59 | 60 | _trans_active = false; 61 | _inputBuffer = static_cast(malloc(INPUT_BUFFER_SIZE)); 62 | _inputBufferSize = INPUT_BUFFER_SIZE; 63 | } 64 | 65 | void Sodaq_UBlox_GPS::resetValues() 66 | { 67 | _seenLatLon = false; 68 | _seenAlt = false; 69 | _numSatellites = 0; 70 | _lat = 0; 71 | _lon = 0; 72 | 73 | _seenTime = false; 74 | _hh = 0; 75 | _mm = 0; 76 | _ss = 0; 77 | _yy = 0; 78 | _MM = 0; 79 | _dd = 0; 80 | } 81 | 82 | void Sodaq_UBlox_GPS::init(int8_t enable_pin) 83 | { 84 | _enablePin = enable_pin; 85 | Wire.begin(); 86 | digitalWrite(_enablePin, GPS_ENABLE_OFF); 87 | pinMode(_enablePin, OUTPUT); 88 | } 89 | 90 | /*! 91 | * Read the UBlox device until a fix is seen, or until 92 | * a timeout has been reached. 93 | */ 94 | bool Sodaq_UBlox_GPS::scan(bool leave_on, uint32_t timeout) 95 | { 96 | bool retval = false; 97 | uint32_t start = millis(); 98 | resetValues(); 99 | 100 | on(); 101 | delay(500); // TODO Is this needed? 102 | 103 | size_t fix_count = 0; 104 | while (!is_timedout(start, timeout)) { 105 | if (!readLine()) { 106 | // TODO Maybe quit? 107 | continue; 108 | } 109 | parseLine(_inputBuffer); 110 | 111 | // Which conditions are required to quit the scan? 112 | if (_seenLatLon 113 | && _seenTime 114 | && _seenAlt 115 | && (_minNumSatellites == 0 || _numSatellites >= _minNumSatellites)) { 116 | ++fix_count; 117 | if (fix_count >= _minNumOfLines) { 118 | retval = true; 119 | break; 120 | } 121 | } 122 | } 123 | 124 | if (_numSatellites > 0) { 125 | debugPrintLn(String("[scan] num sats = ") + _numSatellites); 126 | } 127 | if (_seenTime) { 128 | debugPrintLn(String("[scan] datetime = ") + getDateTimeString()); 129 | } 130 | if (_seenLatLon) { 131 | debugPrintLn(String("[scan] lat = ") + String(_lat, 7)); 132 | debugPrintLn(String("[scan] lon = ") + String(_lon, 7)); 133 | } 134 | 135 | if (!leave_on) { 136 | off(); 137 | } 138 | return retval; 139 | } 140 | 141 | String Sodaq_UBlox_GPS::getDateTimeString() 142 | { 143 | return num2String(getYear(), 4) 144 | + num2String(getMonth(), 2) 145 | + num2String(getDay(), 2) 146 | + num2String(getHour(), 2) 147 | + num2String(getMinute(), 2) 148 | + num2String(getSecond(), 2); 149 | } 150 | 151 | bool Sodaq_UBlox_GPS::parseLine(const char * line) 152 | { 153 | //debugPrintLn(String("= ") + line); 154 | if (!computeCrc(line, false)) { 155 | // Redo the check, with logging 156 | computeCrc(line, true); 157 | return false; 158 | } 159 | String data = line + 1; 160 | data.remove(data.length() - 3, 3); // Strip checksum * 161 | 162 | if (data.startsWith("GPGGA")) { 163 | return parseGPGGA(data); 164 | } 165 | 166 | if (data.startsWith("GPGSA")) { 167 | return parseGPGSA(data); 168 | } 169 | 170 | if (data.startsWith("GPRMC")) { 171 | return parseGPRMC(data); 172 | } 173 | 174 | if (data.startsWith("GPGSV")) { 175 | return parseGPGSV(data); 176 | } 177 | 178 | if (data.startsWith("GPGLL")) { 179 | return parseGPGLL(data); 180 | } 181 | 182 | if (data.startsWith("GPVTG")) { 183 | return parseGPVTG(data); 184 | } 185 | 186 | if (data.startsWith("GPTXT")) { 187 | return parseGPTXT(data); 188 | } 189 | 190 | debugPrintLn(String("?? >> ") + line); 191 | return false; 192 | } 193 | 194 | /*! 195 | * Read the coordinates using $GPGGA 196 | * See also section 24.3 of u-blox 7, Receiver Description. Document number: GPS.G7-SW-12001-B 197 | * 198 | * See section "Decode of selected position sentences" at 199 | * http://www.gpsinformation.org/dale/nmea.htm 200 | * The most important NMEA sentences include the GGA which provides the 201 | * current Fix data, the RMC which provides the minimum gps sentences 202 | * information, and the GSA which provides the Satellite status data. 203 | * 204 | * 0 $GPGGA 205 | * 1 time hhmmss.ss UTC time 206 | * 2 lat ddmm.mmmmm Latitude (degrees & minutes) 207 | * 3 NS char North/South indicator 208 | * 4 long dddmm.mmmmm Longitude (degrees & minutes) 209 | * 5 EW char East/West indicator 210 | * 6 quality digit Quality indicator for position fix: 0 No Fix, 6 Estimated, 1 Auto GNSS, 2 Diff GNSS 211 | * 7 numSV num Number of satellites used 212 | * 8 HDOP num Horizontal Dilution of Precision 213 | * 9 alt num Altitude above mean sea level 214 | * 10 uAlt char Altitude units: meters 215 | * 11 sep num Geoid separation: difference between geoid and mean sea level 216 | * 12 uSep char Separation units: meters 217 | * 13 diffAge num Age of differential corrections 218 | * 14 diffStation num ID of station providing differential corrections 219 | * 15 checksum 2 hex digits 220 | */ 221 | bool Sodaq_UBlox_GPS::parseGPGGA(const String & line) 222 | { 223 | debugPrintLn("parseGPGGA"); 224 | debugPrintLn(String(">> ") + line); 225 | if (getField(line, 6) != "0") { 226 | _lat = convertDegMinToDecDeg(getField(line, 2)); 227 | if (getField(line, 3) == "S") { 228 | _lat = -_lat; 229 | } 230 | _lon = convertDegMinToDecDeg(getField(line, 4)); 231 | if (getField(line, 5) == "W") { 232 | _lon = -_lon; 233 | } 234 | _seenLatLon = true; 235 | 236 | _hdop = getField(line, 8).toFloat(); 237 | if(getField(line, 10) == "M") { 238 | _alt = getField(line, 9).toFloat(); 239 | _seenAlt = true; 240 | } 241 | } 242 | 243 | _numSatellites = getField(line, 7).toInt(); 244 | return true; 245 | } 246 | 247 | /*! 248 | * Parse GPGSA line 249 | * GNSS DOP and Active Satellites 250 | */ 251 | bool Sodaq_UBlox_GPS::parseGPGSA(const String & line) 252 | { 253 | // Not (yet) used 254 | debugPrintLn("parseGPGSA"); 255 | debugPrintLn(String(">> ") + line); 256 | return false; 257 | } 258 | 259 | /*! 260 | * Read the coordinates using $GPRMC 261 | * See also section 24.13 of u-blox 7, Receiver Description. Document number: GPS.G7-SW-12001-B 262 | * 263 | * 0 $GPRMC 264 | * 1 time hhmmss.ss UTC time 265 | * 2 status char Status, V = Navigation receiver warning, A = Data valid 266 | * 3 lat ddmm.mmmmm Latitude (degrees & minutes) 267 | * 4 NS char North/South 268 | * 5 long dddmm.mmmmm Longitude (degrees & minutes) 269 | * 6 EW char East/West 270 | * 7 spd num Speed over ground 271 | * 8 cog num Course over ground 272 | * 9 date ddmmyy Date in day, month, year format 273 | * 10 mv num Magnetic variation value 274 | * 11 mvEW char Magnetic variation E/W indicator 275 | * 12 posMode char Mode Indicator: 'N' No Fix, 'E' Estimate, 'A' Auto GNSS, 'D' Diff GNSS 276 | * 13 checksum 2 hex digits Checksum 277 | */ 278 | bool Sodaq_UBlox_GPS::parseGPRMC(const String & line) 279 | { 280 | debugPrintLn("parseGPRMC"); 281 | debugPrintLn(String(">> ") + line); 282 | 283 | if (getField(line, 2) == "A" && getField(line, 12) != "N") { 284 | _lat = convertDegMinToDecDeg(getField(line, 3)); 285 | if (getField(line, 4) == "S") { 286 | _lat = -_lat; 287 | } 288 | _lon = convertDegMinToDecDeg(getField(line, 5)); 289 | if (getField(line, 6) == "W") { 290 | _lon = -_lon; 291 | } 292 | _seenLatLon = true; 293 | } 294 | 295 | String time = getField(line, 1); 296 | String date = getField(line, 9); 297 | setDateTime(date, time); 298 | 299 | return true; 300 | } 301 | 302 | /*! 303 | * Parse GPGSV line 304 | * See also section 24.12 of u-blox 7, Receiver Description. Document number: GPS.G7-SW-12001-B 305 | * 306 | * 0 $GPGSV 307 | * 1 numMsg digit Number of messages, total number of GSV messages being output 308 | * 2 msgNum digit Number of this message 309 | * 3 numSV num Number of satellites in view 310 | * 311 | * 4 sv num Satellite ID 312 | * 5 elv num Elevation (range 0-90) 313 | * 6 az num Azimuth, (range 0-359) 314 | * 7 cno num Signal strength (C/N0, range 0-99), blank when not tracking 315 | * 316 | * fields 4..7 are repeated for each satellite in this message 317 | */ 318 | bool Sodaq_UBlox_GPS::parseGPGSV(const String & line) 319 | { 320 | debugPrintLn("parseGPGSV"); 321 | debugPrintLn(String(">> ") + line); 322 | 323 | // We could/should only use msgNum == 1. However, all messages should have 324 | // the same numSV. 325 | _numSatellites = getField(line, 3).toInt(); 326 | 327 | return true; 328 | } 329 | 330 | /*! 331 | * Parse GPGLL line 332 | * Latitude and longitude, with time of position fix and status 333 | */ 334 | bool Sodaq_UBlox_GPS::parseGPGLL(const String & line) 335 | { 336 | // Not (yet) used 337 | debugPrintLn("parseGPGLL"); 338 | debugPrintLn(String(">> ") + line); 339 | return false; 340 | } 341 | 342 | /*! 343 | * Parse GPVTG line 344 | * Course over ground and Ground speed 345 | */ 346 | bool Sodaq_UBlox_GPS::parseGPVTG(const String & line) 347 | { 348 | // Not (yet) used 349 | debugPrintLn("parseGPVTG"); 350 | debugPrintLn(String(">> ") + line); 351 | return false; 352 | } 353 | 354 | /*! 355 | * Parse GPTXT line 356 | * See also section 24.13 of u-blox 7, Receiver Description. Document number: GPS.G7-SW-12001-B 357 | * $GPTXT,01,01,02,ANTSTATUS=INIT*25 358 | * ... 359 | * $GPTXT,01,01,02,ANTSTATUS=OK*3B 360 | * 361 | * 0 $GPTXT 362 | * 1 numMsg num Total number of messages in this transmission 363 | * 2 msgNum num Message number in this transmission 364 | * 3 msgType num Text identifier, 00: Error, 01: Warning, 02: Notice, 07: User 365 | * 4 text string Any ASCII text 366 | * 13 checksum 2 hex digits Checksum 367 | */ 368 | bool Sodaq_UBlox_GPS::parseGPTXT(const String & line) 369 | { 370 | //debugPrintLn("parseGPTXT"); 371 | //debugPrintLn(String(">> ") + line); 372 | debugPrintLn(String("TXT: \"") + getField(line, 4) + "\""); 373 | return true; 374 | } 375 | 376 | /*! 377 | * Compute and verify the checksum 378 | * 379 | * Each line must start with '$' 380 | * Each line must end with '*' 381 | */ 382 | bool Sodaq_UBlox_GPS::computeCrc(const char * line, bool do_logging) 383 | { 384 | if (do_logging) { 385 | debugPrint(line); 386 | } 387 | size_t len = strlen(line); 388 | if (len < 4) { 389 | if (do_logging) { 390 | debugPrint(" Invalid short: "); 391 | debugPrintLn(len); 392 | } 393 | return false; 394 | } 395 | if (line[0] != '$') { 396 | if (do_logging) { 397 | debugPrintLn(" Invalid$"); 398 | } 399 | return false; 400 | } 401 | if (line[len - 3] != '*') { 402 | if (do_logging) { 403 | debugPrintLn(" Invalid*"); 404 | } 405 | return false; 406 | } 407 | 408 | uint8_t crc = 0; 409 | for (size_t i = 1; i < len - 3; ++i) { 410 | crc ^= line[i]; 411 | } 412 | 413 | uint8_t crc1 = getHex2(line, len - 2); 414 | if (crc != crc1) { 415 | if (do_logging) { 416 | debugPrint(" INVALID CRC "); 417 | debugPrint(crc1, HEX); 418 | debugPrint(" EXPECTED "); 419 | debugPrintLn(crc, HEX); 420 | } 421 | return false; 422 | } 423 | 424 | //debugSerial.println(" OK"); 425 | return true; 426 | } 427 | 428 | uint8_t Sodaq_UBlox_GPS::getHex2(const char * s, size_t index) 429 | { 430 | uint8_t val = 0; 431 | char c; 432 | c = s[index]; 433 | if (c >= '0' && c <= '9') { 434 | val += c - '0'; 435 | } else if (c >= 'a' && c <= 'f') { 436 | val += c - 'a' + 10; 437 | } else if (c >= 'A' && c <= 'F') { 438 | val += c - 'A' + 10; 439 | } 440 | val <<= 4; 441 | c = s[++index]; 442 | if (c >= '0' && c <= '9') { 443 | val += c - '0'; 444 | } else if (c >= 'a' && c <= 'f') { 445 | val += c - 'a' + 10; 446 | } else if (c >= 'A' && c <= 'F') { 447 | val += c - 'A' + 10; 448 | } 449 | return val; 450 | } 451 | 452 | String Sodaq_UBlox_GPS::num2String(int num, size_t width) 453 | { 454 | String out; 455 | out = num; 456 | while (out.length() < width) { 457 | out = String("0") + out; 458 | } 459 | return out; 460 | } 461 | 462 | String Sodaq_UBlox_GPS::getField(const String & data, int index) 463 | { 464 | int found = 0; 465 | int strIndex[] = { 0, -1 }; 466 | int maxIndex = data.length() - 1; 467 | 468 | for (int i = 0; i <= maxIndex && found <= index; i++) { 469 | if (data.charAt(i) == _fieldSep || i == maxIndex) { 470 | found++; 471 | strIndex[0] = strIndex[1] + 1; 472 | strIndex[1] = (i == maxIndex) ? i + 1 : i; 473 | } 474 | } 475 | 476 | return found > index ? data.substring(strIndex[0], strIndex[1]) : ""; 477 | } 478 | 479 | /* 480 | * Convert lat/long degree-minute format to decimal-degrees 481 | * 482 | * This code is from 483 | * http://arduinodev.woofex.net/2013/02/06/adafruit_gps_forma/ 484 | * 485 | * According to the NMEA Standard, Latitude and Longitude are output in the format Degrees, Minutes and 486 | * (Decimal) Fractions of Minutes. To convert to Degrees and Fractions of Degrees, or Degrees, Minutes, Seconds 487 | * and Fractions of seconds, the 'Minutes' and 'Fractional Minutes' parts need to be converted. In other words: If 488 | * the GPS Receiver reports a Latitude of 4717.112671 North and Longitude of 00833.914843 East, this is 489 | * Latitude 47 Degrees, 17.112671 Minutes 490 | * Longitude 8 Degrees, 33.914843 Minutes 491 | * or 492 | * Latitude 47 Degrees, 17 Minutes, 6.76026 Seconds 493 | * Longitude 8 Degrees, 33 Minutes, 54.89058 Seconds 494 | * or 495 | * Latitude 47.28521118 Degrees 496 | * Longitude 8.56524738 Degrees 497 | */ 498 | double Sodaq_UBlox_GPS::convertDegMinToDecDeg(const String & data) 499 | { 500 | double degMin = data.toFloat(); 501 | double min = 0.0; 502 | double decDeg = 0.0; 503 | 504 | //get the minutes, fmod() requires double 505 | min = fmod((double) degMin, 100.0); 506 | 507 | //rebuild coordinates in decimal degrees 508 | degMin = (int) (degMin / 100); 509 | decDeg = degMin + (min / 60); 510 | 511 | return decDeg; 512 | } 513 | 514 | void Sodaq_UBlox_GPS::setDateTime(const String & date, const String & time) 515 | { 516 | if (time.length() == 9 && date.length() == 6) { 517 | _hh = time.substring(0, 2).toInt(); 518 | _mm = time.substring(2, 4).toInt(); 519 | _ss = time.substring(4, 6).toInt(); 520 | _dd = date.substring(0, 2).toInt(); 521 | _MM = date.substring(2, 4).toInt(); 522 | _yy = date.substring(4, 6).toInt(); 523 | _seenTime = true; 524 | } 525 | } 526 | 527 | /*! 528 | * Read one NMEA frame 529 | */ 530 | bool Sodaq_UBlox_GPS::readLine(uint32_t timeout) 531 | { 532 | if (!_inputBuffer) { 533 | return false; 534 | } 535 | 536 | uint32_t start = millis(); 537 | char c; 538 | char *ptr = _inputBuffer; 539 | size_t cnt = 0; 540 | *ptr = '\0'; 541 | 542 | c = 0; 543 | while (!is_timedout(start, timeout)) { 544 | c = (char)read(); 545 | if (c == '$') { 546 | break; 547 | } 548 | } 549 | if (c != '$') { 550 | return false; 551 | } 552 | *ptr++ = c; 553 | ++cnt; 554 | 555 | c = 0; 556 | while (!is_timedout(start, timeout)) { 557 | c = (char)read(); 558 | if (c == '\r') { 559 | continue; 560 | } 561 | if (c == '\n') { 562 | break; 563 | } 564 | if (cnt < _inputBufferSize - 1) { 565 | *ptr++ = c; 566 | ++cnt; 567 | } 568 | } 569 | *ptr = '\0'; 570 | if (c != '\n') { 571 | return false; 572 | } 573 | endTransmission(); 574 | return true; 575 | } 576 | 577 | uint8_t Sodaq_UBlox_GPS::read() 578 | { 579 | beginTransmission(); 580 | 581 | uint8_t b = 0xFF; 582 | uint8_t nr_bytes; 583 | nr_bytes = Wire.requestFrom(_addr, 1, false); 584 | if (nr_bytes == 1) { 585 | b = Wire.read(); 586 | } 587 | return b; 588 | } 589 | 590 | void Sodaq_UBlox_GPS::beginTransmission() 591 | { 592 | if (_trans_active) { 593 | return; 594 | } 595 | Wire.beginTransmission(_addr); 596 | _trans_active = true; 597 | } 598 | 599 | void Sodaq_UBlox_GPS::endTransmission() 600 | { 601 | if (!_trans_active) { 602 | return; 603 | } 604 | Wire.endTransmission(); 605 | _trans_active = false; 606 | } 607 | 608 | void Sodaq_UBlox_GPS::on() 609 | { 610 | digitalWrite(_enablePin, GPS_ENABLE_ON); 611 | } 612 | 613 | void Sodaq_UBlox_GPS::off() 614 | { 615 | digitalWrite(_enablePin, GPS_ENABLE_OFF); 616 | } 617 | -------------------------------------------------------------------------------- /examples/SodaqOne-TTN-Mapper-binary/Sodaq_UBlox_GPS.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 SODAQ. All rights reserved. 3 | * 4 | * This file is part of Sodaq_UBlox_GPS. 5 | * 6 | * Sodaq_UBlox_GPS is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published 8 | * by the Free Software Foundation, either version 3 of the License, or (at 9 | * your option) any later version. 10 | * 11 | * Sodaq_UBlox_GPS is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 14 | * License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with Sodaq_UBlox_GPS. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include "Sodaq_UBlox_GPS.h" 23 | 24 | #define DEBUG 1 25 | #ifdef DEBUG 26 | #define debugPrintLn(...) { if (this->_diagStream) this->_diagStream->println(__VA_ARGS__); } 27 | #define debugPrint(...) { if (this->_diagStream) this->_diagStream->print(__VA_ARGS__); } 28 | #else 29 | #define debugPrintLn(...) 30 | #define debugPrint(...) 31 | #endif 32 | 33 | static const size_t INPUT_BUFFER_SIZE = 128; // TODO Check UBlox manual ReceiverDescProtSpec 34 | static const uint8_t UBlox_I2C_addr = 0x42; 35 | 36 | #define GPS_ENABLE_ON HIGH 37 | #define GPS_ENABLE_OFF LOW 38 | 39 | Sodaq_UBlox_GPS sodaq_gps; 40 | const char Sodaq_UBlox_GPS::_fieldSep = ','; 41 | 42 | static inline bool is_timedout(uint32_t from, uint32_t nr_ms) __attribute__((always_inline)); 43 | static inline bool is_timedout(uint32_t from, uint32_t nr_ms) 44 | { 45 | return (millis() - from) > nr_ms; 46 | } 47 | 48 | Sodaq_UBlox_GPS::Sodaq_UBlox_GPS() 49 | { 50 | _enablePin = -1; 51 | 52 | _diagStream = 0; 53 | _addr = UBlox_I2C_addr; 54 | 55 | _minNumOfLines = 0; 56 | _minNumSatellites = 0; 57 | 58 | resetValues(); 59 | 60 | _trans_active = false; 61 | _inputBuffer = static_cast(malloc(INPUT_BUFFER_SIZE)); 62 | _inputBufferSize = INPUT_BUFFER_SIZE; 63 | } 64 | 65 | void Sodaq_UBlox_GPS::resetValues() 66 | { 67 | _seenLatLon = false; 68 | _seenAlt = false; 69 | _numSatellites = 0; 70 | _lat = 0; 71 | _lon = 0; 72 | 73 | _seenTime = false; 74 | _hh = 0; 75 | _mm = 0; 76 | _ss = 0; 77 | _yy = 0; 78 | _MM = 0; 79 | _dd = 0; 80 | } 81 | 82 | void Sodaq_UBlox_GPS::init(int8_t enable_pin) 83 | { 84 | _enablePin = enable_pin; 85 | Wire.begin(); 86 | digitalWrite(_enablePin, GPS_ENABLE_OFF); 87 | pinMode(_enablePin, OUTPUT); 88 | } 89 | 90 | /*! 91 | * Read the UBlox device until a fix is seen, or until 92 | * a timeout has been reached. 93 | */ 94 | bool Sodaq_UBlox_GPS::scan(bool leave_on, uint32_t timeout) 95 | { 96 | bool retval = false; 97 | uint32_t start = millis(); 98 | resetValues(); 99 | 100 | on(); 101 | delay(500); // TODO Is this needed? 102 | 103 | size_t fix_count = 0; 104 | while (!is_timedout(start, timeout)) { 105 | if (!readLine()) { 106 | // TODO Maybe quit? 107 | continue; 108 | } 109 | parseLine(_inputBuffer); 110 | 111 | // Which conditions are required to quit the scan? 112 | if (_seenLatLon 113 | && _seenTime 114 | && _seenAlt 115 | && (_minNumSatellites == 0 || _numSatellites >= _minNumSatellites)) { 116 | ++fix_count; 117 | if (fix_count >= _minNumOfLines) { 118 | retval = true; 119 | break; 120 | } 121 | } 122 | } 123 | 124 | if (_numSatellites > 0) { 125 | debugPrintLn(String("[scan] num sats = ") + _numSatellites); 126 | } 127 | if (_seenTime) { 128 | debugPrintLn(String("[scan] datetime = ") + getDateTimeString()); 129 | } 130 | if (_seenLatLon) { 131 | debugPrintLn(String("[scan] lat = ") + String(_lat, 7)); 132 | debugPrintLn(String("[scan] lon = ") + String(_lon, 7)); 133 | } 134 | 135 | if (!leave_on) { 136 | off(); 137 | } 138 | return retval; 139 | } 140 | 141 | String Sodaq_UBlox_GPS::getDateTimeString() 142 | { 143 | return num2String(getYear(), 4) 144 | + num2String(getMonth(), 2) 145 | + num2String(getDay(), 2) 146 | + num2String(getHour(), 2) 147 | + num2String(getMinute(), 2) 148 | + num2String(getSecond(), 2); 149 | } 150 | 151 | bool Sodaq_UBlox_GPS::parseLine(const char * line) 152 | { 153 | //debugPrintLn(String("= ") + line); 154 | if (!computeCrc(line, false)) { 155 | // Redo the check, with logging 156 | computeCrc(line, true); 157 | return false; 158 | } 159 | String data = line + 1; 160 | data.remove(data.length() - 3, 3); // Strip checksum * 161 | 162 | if (data.startsWith("GPGGA")) { 163 | return parseGPGGA(data); 164 | } 165 | 166 | if (data.startsWith("GPGSA")) { 167 | return parseGPGSA(data); 168 | } 169 | 170 | if (data.startsWith("GPRMC")) { 171 | return parseGPRMC(data); 172 | } 173 | 174 | if (data.startsWith("GPGSV")) { 175 | return parseGPGSV(data); 176 | } 177 | 178 | if (data.startsWith("GPGLL")) { 179 | return parseGPGLL(data); 180 | } 181 | 182 | if (data.startsWith("GPVTG")) { 183 | return parseGPVTG(data); 184 | } 185 | 186 | if (data.startsWith("GPTXT")) { 187 | return parseGPTXT(data); 188 | } 189 | 190 | debugPrintLn(String("?? >> ") + line); 191 | return false; 192 | } 193 | 194 | /*! 195 | * Read the coordinates using $GPGGA 196 | * See also section 24.3 of u-blox 7, Receiver Description. Document number: GPS.G7-SW-12001-B 197 | * 198 | * See section "Decode of selected position sentences" at 199 | * http://www.gpsinformation.org/dale/nmea.htm 200 | * The most important NMEA sentences include the GGA which provides the 201 | * current Fix data, the RMC which provides the minimum gps sentences 202 | * information, and the GSA which provides the Satellite status data. 203 | * 204 | * 0 $GPGGA 205 | * 1 time hhmmss.ss UTC time 206 | * 2 lat ddmm.mmmmm Latitude (degrees & minutes) 207 | * 3 NS char North/South indicator 208 | * 4 long dddmm.mmmmm Longitude (degrees & minutes) 209 | * 5 EW char East/West indicator 210 | * 6 quality digit Quality indicator for position fix: 0 No Fix, 6 Estimated, 1 Auto GNSS, 2 Diff GNSS 211 | * 7 numSV num Number of satellites used 212 | * 8 HDOP num Horizontal Dilution of Precision 213 | * 9 alt num Altitude above mean sea level 214 | * 10 uAlt char Altitude units: meters 215 | * 11 sep num Geoid separation: difference between geoid and mean sea level 216 | * 12 uSep char Separation units: meters 217 | * 13 diffAge num Age of differential corrections 218 | * 14 diffStation num ID of station providing differential corrections 219 | * 15 checksum 2 hex digits 220 | */ 221 | bool Sodaq_UBlox_GPS::parseGPGGA(const String & line) 222 | { 223 | debugPrintLn("parseGPGGA"); 224 | debugPrintLn(String(">> ") + line); 225 | if (getField(line, 6) != "0") { 226 | _lat = convertDegMinToDecDeg(getField(line, 2)); 227 | if (getField(line, 3) == "S") { 228 | _lat = -_lat; 229 | } 230 | _lon = convertDegMinToDecDeg(getField(line, 4)); 231 | if (getField(line, 5) == "W") { 232 | _lon = -_lon; 233 | } 234 | _seenLatLon = true; 235 | 236 | _hdop = getField(line, 8).toFloat(); 237 | if(getField(line, 10) == "M") { 238 | _alt = getField(line, 9).toFloat(); 239 | _seenAlt = true; 240 | } 241 | } 242 | 243 | _numSatellites = getField(line, 7).toInt(); 244 | return true; 245 | } 246 | 247 | /*! 248 | * Parse GPGSA line 249 | * GNSS DOP and Active Satellites 250 | */ 251 | bool Sodaq_UBlox_GPS::parseGPGSA(const String & line) 252 | { 253 | // Not (yet) used 254 | debugPrintLn("parseGPGSA"); 255 | debugPrintLn(String(">> ") + line); 256 | return false; 257 | } 258 | 259 | /*! 260 | * Read the coordinates using $GPRMC 261 | * See also section 24.13 of u-blox 7, Receiver Description. Document number: GPS.G7-SW-12001-B 262 | * 263 | * 0 $GPRMC 264 | * 1 time hhmmss.ss UTC time 265 | * 2 status char Status, V = Navigation receiver warning, A = Data valid 266 | * 3 lat ddmm.mmmmm Latitude (degrees & minutes) 267 | * 4 NS char North/South 268 | * 5 long dddmm.mmmmm Longitude (degrees & minutes) 269 | * 6 EW char East/West 270 | * 7 spd num Speed over ground 271 | * 8 cog num Course over ground 272 | * 9 date ddmmyy Date in day, month, year format 273 | * 10 mv num Magnetic variation value 274 | * 11 mvEW char Magnetic variation E/W indicator 275 | * 12 posMode char Mode Indicator: 'N' No Fix, 'E' Estimate, 'A' Auto GNSS, 'D' Diff GNSS 276 | * 13 checksum 2 hex digits Checksum 277 | */ 278 | bool Sodaq_UBlox_GPS::parseGPRMC(const String & line) 279 | { 280 | debugPrintLn("parseGPRMC"); 281 | debugPrintLn(String(">> ") + line); 282 | 283 | if (getField(line, 2) == "A" && getField(line, 12) != "N") { 284 | _lat = convertDegMinToDecDeg(getField(line, 3)); 285 | if (getField(line, 4) == "S") { 286 | _lat = -_lat; 287 | } 288 | _lon = convertDegMinToDecDeg(getField(line, 5)); 289 | if (getField(line, 6) == "W") { 290 | _lon = -_lon; 291 | } 292 | _seenLatLon = true; 293 | } 294 | 295 | String time = getField(line, 1); 296 | String date = getField(line, 9); 297 | setDateTime(date, time); 298 | 299 | return true; 300 | } 301 | 302 | /*! 303 | * Parse GPGSV line 304 | * See also section 24.12 of u-blox 7, Receiver Description. Document number: GPS.G7-SW-12001-B 305 | * 306 | * 0 $GPGSV 307 | * 1 numMsg digit Number of messages, total number of GSV messages being output 308 | * 2 msgNum digit Number of this message 309 | * 3 numSV num Number of satellites in view 310 | * 311 | * 4 sv num Satellite ID 312 | * 5 elv num Elevation (range 0-90) 313 | * 6 az num Azimuth, (range 0-359) 314 | * 7 cno num Signal strength (C/N0, range 0-99), blank when not tracking 315 | * 316 | * fields 4..7 are repeated for each satellite in this message 317 | */ 318 | bool Sodaq_UBlox_GPS::parseGPGSV(const String & line) 319 | { 320 | debugPrintLn("parseGPGSV"); 321 | debugPrintLn(String(">> ") + line); 322 | 323 | // We could/should only use msgNum == 1. However, all messages should have 324 | // the same numSV. 325 | _numSatellites = getField(line, 3).toInt(); 326 | 327 | return true; 328 | } 329 | 330 | /*! 331 | * Parse GPGLL line 332 | * Latitude and longitude, with time of position fix and status 333 | */ 334 | bool Sodaq_UBlox_GPS::parseGPGLL(const String & line) 335 | { 336 | // Not (yet) used 337 | debugPrintLn("parseGPGLL"); 338 | debugPrintLn(String(">> ") + line); 339 | return false; 340 | } 341 | 342 | /*! 343 | * Parse GPVTG line 344 | * Course over ground and Ground speed 345 | */ 346 | bool Sodaq_UBlox_GPS::parseGPVTG(const String & line) 347 | { 348 | // Not (yet) used 349 | debugPrintLn("parseGPVTG"); 350 | debugPrintLn(String(">> ") + line); 351 | return false; 352 | } 353 | 354 | /*! 355 | * Parse GPTXT line 356 | * See also section 24.13 of u-blox 7, Receiver Description. Document number: GPS.G7-SW-12001-B 357 | * $GPTXT,01,01,02,ANTSTATUS=INIT*25 358 | * ... 359 | * $GPTXT,01,01,02,ANTSTATUS=OK*3B 360 | * 361 | * 0 $GPTXT 362 | * 1 numMsg num Total number of messages in this transmission 363 | * 2 msgNum num Message number in this transmission 364 | * 3 msgType num Text identifier, 00: Error, 01: Warning, 02: Notice, 07: User 365 | * 4 text string Any ASCII text 366 | * 13 checksum 2 hex digits Checksum 367 | */ 368 | bool Sodaq_UBlox_GPS::parseGPTXT(const String & line) 369 | { 370 | //debugPrintLn("parseGPTXT"); 371 | //debugPrintLn(String(">> ") + line); 372 | debugPrintLn(String("TXT: \"") + getField(line, 4) + "\""); 373 | return true; 374 | } 375 | 376 | /*! 377 | * Compute and verify the checksum 378 | * 379 | * Each line must start with '$' 380 | * Each line must end with '*' 381 | */ 382 | bool Sodaq_UBlox_GPS::computeCrc(const char * line, bool do_logging) 383 | { 384 | if (do_logging) { 385 | debugPrint(line); 386 | } 387 | size_t len = strlen(line); 388 | if (len < 4) { 389 | if (do_logging) { 390 | debugPrint(" Invalid short: "); 391 | debugPrintLn(len); 392 | } 393 | return false; 394 | } 395 | if (line[0] != '$') { 396 | if (do_logging) { 397 | debugPrintLn(" Invalid$"); 398 | } 399 | return false; 400 | } 401 | if (line[len - 3] != '*') { 402 | if (do_logging) { 403 | debugPrintLn(" Invalid*"); 404 | } 405 | return false; 406 | } 407 | 408 | uint8_t crc = 0; 409 | for (size_t i = 1; i < len - 3; ++i) { 410 | crc ^= line[i]; 411 | } 412 | 413 | uint8_t crc1 = getHex2(line, len - 2); 414 | if (crc != crc1) { 415 | if (do_logging) { 416 | debugPrint(" INVALID CRC "); 417 | debugPrint(crc1, HEX); 418 | debugPrint(" EXPECTED "); 419 | debugPrintLn(crc, HEX); 420 | } 421 | return false; 422 | } 423 | 424 | //debugSerial.println(" OK"); 425 | return true; 426 | } 427 | 428 | uint8_t Sodaq_UBlox_GPS::getHex2(const char * s, size_t index) 429 | { 430 | uint8_t val = 0; 431 | char c; 432 | c = s[index]; 433 | if (c >= '0' && c <= '9') { 434 | val += c - '0'; 435 | } else if (c >= 'a' && c <= 'f') { 436 | val += c - 'a' + 10; 437 | } else if (c >= 'A' && c <= 'F') { 438 | val += c - 'A' + 10; 439 | } 440 | val <<= 4; 441 | c = s[++index]; 442 | if (c >= '0' && c <= '9') { 443 | val += c - '0'; 444 | } else if (c >= 'a' && c <= 'f') { 445 | val += c - 'a' + 10; 446 | } else if (c >= 'A' && c <= 'F') { 447 | val += c - 'A' + 10; 448 | } 449 | return val; 450 | } 451 | 452 | String Sodaq_UBlox_GPS::num2String(int num, size_t width) 453 | { 454 | String out; 455 | out = num; 456 | while (out.length() < width) { 457 | out = String("0") + out; 458 | } 459 | return out; 460 | } 461 | 462 | String Sodaq_UBlox_GPS::getField(const String & data, int index) 463 | { 464 | int found = 0; 465 | int strIndex[] = { 0, -1 }; 466 | int maxIndex = data.length() - 1; 467 | 468 | for (int i = 0; i <= maxIndex && found <= index; i++) { 469 | if (data.charAt(i) == _fieldSep || i == maxIndex) { 470 | found++; 471 | strIndex[0] = strIndex[1] + 1; 472 | strIndex[1] = (i == maxIndex) ? i + 1 : i; 473 | } 474 | } 475 | 476 | return found > index ? data.substring(strIndex[0], strIndex[1]) : ""; 477 | } 478 | 479 | /* 480 | * Convert lat/long degree-minute format to decimal-degrees 481 | * 482 | * This code is from 483 | * http://arduinodev.woofex.net/2013/02/06/adafruit_gps_forma/ 484 | * 485 | * According to the NMEA Standard, Latitude and Longitude are output in the format Degrees, Minutes and 486 | * (Decimal) Fractions of Minutes. To convert to Degrees and Fractions of Degrees, or Degrees, Minutes, Seconds 487 | * and Fractions of seconds, the 'Minutes' and 'Fractional Minutes' parts need to be converted. In other words: If 488 | * the GPS Receiver reports a Latitude of 4717.112671 North and Longitude of 00833.914843 East, this is 489 | * Latitude 47 Degrees, 17.112671 Minutes 490 | * Longitude 8 Degrees, 33.914843 Minutes 491 | * or 492 | * Latitude 47 Degrees, 17 Minutes, 6.76026 Seconds 493 | * Longitude 8 Degrees, 33 Minutes, 54.89058 Seconds 494 | * or 495 | * Latitude 47.28521118 Degrees 496 | * Longitude 8.56524738 Degrees 497 | */ 498 | double Sodaq_UBlox_GPS::convertDegMinToDecDeg(const String & data) 499 | { 500 | double degMin = data.toFloat(); 501 | double min = 0.0; 502 | double decDeg = 0.0; 503 | 504 | //get the minutes, fmod() requires double 505 | min = fmod((double) degMin, 100.0); 506 | 507 | //rebuild coordinates in decimal degrees 508 | degMin = (int) (degMin / 100); 509 | decDeg = degMin + (min / 60); 510 | 511 | return decDeg; 512 | } 513 | 514 | void Sodaq_UBlox_GPS::setDateTime(const String & date, const String & time) 515 | { 516 | if (time.length() == 9 && date.length() == 6) { 517 | _hh = time.substring(0, 2).toInt(); 518 | _mm = time.substring(2, 4).toInt(); 519 | _ss = time.substring(4, 6).toInt(); 520 | _dd = date.substring(0, 2).toInt(); 521 | _MM = date.substring(2, 4).toInt(); 522 | _yy = date.substring(4, 6).toInt(); 523 | _seenTime = true; 524 | } 525 | } 526 | 527 | /*! 528 | * Read one NMEA frame 529 | */ 530 | bool Sodaq_UBlox_GPS::readLine(uint32_t timeout) 531 | { 532 | if (!_inputBuffer) { 533 | return false; 534 | } 535 | 536 | uint32_t start = millis(); 537 | char c; 538 | char *ptr = _inputBuffer; 539 | size_t cnt = 0; 540 | *ptr = '\0'; 541 | 542 | c = 0; 543 | while (!is_timedout(start, timeout)) { 544 | c = (char)read(); 545 | if (c == '$') { 546 | break; 547 | } 548 | } 549 | if (c != '$') { 550 | return false; 551 | } 552 | *ptr++ = c; 553 | ++cnt; 554 | 555 | c = 0; 556 | while (!is_timedout(start, timeout)) { 557 | c = (char)read(); 558 | if (c == '\r') { 559 | continue; 560 | } 561 | if (c == '\n') { 562 | break; 563 | } 564 | if (cnt < _inputBufferSize - 1) { 565 | *ptr++ = c; 566 | ++cnt; 567 | } 568 | } 569 | *ptr = '\0'; 570 | if (c != '\n') { 571 | return false; 572 | } 573 | endTransmission(); 574 | return true; 575 | } 576 | 577 | uint8_t Sodaq_UBlox_GPS::read() 578 | { 579 | beginTransmission(); 580 | 581 | uint8_t b = 0xFF; 582 | uint8_t nr_bytes; 583 | nr_bytes = Wire.requestFrom(_addr, 1, false); 584 | if (nr_bytes == 1) { 585 | b = Wire.read(); 586 | } 587 | return b; 588 | } 589 | 590 | void Sodaq_UBlox_GPS::beginTransmission() 591 | { 592 | if (_trans_active) { 593 | return; 594 | } 595 | Wire.beginTransmission(_addr); 596 | _trans_active = true; 597 | } 598 | 599 | void Sodaq_UBlox_GPS::endTransmission() 600 | { 601 | if (!_trans_active) { 602 | return; 603 | } 604 | Wire.endTransmission(); 605 | _trans_active = false; 606 | } 607 | 608 | void Sodaq_UBlox_GPS::on() 609 | { 610 | digitalWrite(_enablePin, GPS_ENABLE_ON); 611 | } 612 | 613 | void Sodaq_UBlox_GPS::off() 614 | { 615 | digitalWrite(_enablePin, GPS_ENABLE_OFF); 616 | } 617 | -------------------------------------------------------------------------------- /src/rn2xx3.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * A library for controlling a Microchip rn2xx3 LoRa radio. 3 | * 4 | * @Author JP Meijers 5 | * @Author Nicolas Schteinschraber 6 | * @Date 18/12/2015 7 | * 8 | */ 9 | 10 | #include "Arduino.h" 11 | #include "rn2xx3.h" 12 | 13 | extern "C" { 14 | #include 15 | #include 16 | } 17 | 18 | /* 19 | @param serial Needs to be an already opened Stream ({Software/Hardware}Serial) to write to and read from. 20 | */ 21 | rn2xx3::rn2xx3(Stream& serial): 22 | _serial(serial) 23 | { 24 | _serial.setTimeout(2000); 25 | } 26 | 27 | //TODO: change to a boolean 28 | void rn2xx3::autobaud() 29 | { 30 | String response = ""; 31 | 32 | // Try a maximum of 10 times with a 1 second delay 33 | for (uint8_t i=0; i<10 && response==""; i++) 34 | { 35 | delay(1000); 36 | _serial.write((byte)0x00); 37 | _serial.write(0x55); 38 | _serial.println(); 39 | // we could use sendRawCommand(F("sys get ver")); here 40 | _serial.println("sys get ver"); 41 | response = _serial.readStringUntil('\n'); 42 | } 43 | } 44 | 45 | 46 | String rn2xx3::sysver() 47 | { 48 | String ver = sendRawCommand(F("sys get ver")); 49 | ver.trim(); 50 | return ver; 51 | } 52 | 53 | RN2xx3_t rn2xx3::configureModuleType() 54 | { 55 | String version = sysver(); 56 | String model = version.substring(2,6); 57 | switch (model.toInt()) { 58 | case 2903: 59 | _moduleType = RN2903; 60 | break; 61 | case 2483: 62 | _moduleType = RN2483; 63 | break; 64 | default: 65 | _moduleType = RN_NA; 66 | break; 67 | } 68 | return _moduleType; 69 | } 70 | 71 | String rn2xx3::hweui() 72 | { 73 | return (sendRawCommand(F("sys get hweui"))); 74 | } 75 | 76 | String rn2xx3::appeui() 77 | { 78 | return ( sendRawCommand(F("mac get appeui") )); 79 | } 80 | 81 | String rn2xx3::appkey() 82 | { 83 | // We can't read back from module, we send the one 84 | // we have memorized if it has been set 85 | return _appskey; 86 | } 87 | 88 | String rn2xx3::deveui() 89 | { 90 | return (sendRawCommand(F("mac get deveui"))); 91 | } 92 | 93 | bool rn2xx3::init() 94 | { 95 | if(_appskey=="0") //appskey variable is set by both OTAA and ABP 96 | { 97 | return false; 98 | } 99 | else if(_otaa==true) 100 | { 101 | return initOTAA(_appeui, _appskey); 102 | } 103 | else 104 | { 105 | return initABP(_devAddr, _appskey, _nwkskey); 106 | } 107 | } 108 | 109 | 110 | bool rn2xx3::initOTAA(String AppEUI, String AppKey, String DevEUI) 111 | { 112 | _otaa = true; 113 | _nwkskey = "0"; 114 | String receivedData; 115 | 116 | //clear serial buffer 117 | while(_serial.available()) 118 | _serial.read(); 119 | 120 | // detect which model radio we are using 121 | configureModuleType(); 122 | 123 | // reset the module - this will clear all keys set previously 124 | switch (_moduleType) 125 | { 126 | case RN2903: 127 | sendRawCommand(F("mac reset")); 128 | break; 129 | case RN2483: 130 | sendRawCommand(F("mac reset 868")); 131 | break; 132 | default: 133 | // we shouldn't go forward with the init 134 | return false; 135 | } 136 | 137 | // If the Device EUI was given as a parameter, use it 138 | // otherwise use the Hardware EUI. 139 | if (DevEUI.length() == 16) 140 | { 141 | _deveui = DevEUI; 142 | } 143 | else 144 | { 145 | String addr = sendRawCommand(F("sys get hweui")); 146 | if( addr.length() == 16 ) 147 | { 148 | _deveui = addr; 149 | } 150 | // else fall back to the hard coded value in the header file 151 | } 152 | 153 | sendRawCommand("mac set deveui "+_deveui); 154 | 155 | // A valid length App EUI was given. Use it. 156 | if ( AppEUI.length() == 16 ) 157 | { 158 | _appeui = AppEUI; 159 | sendRawCommand("mac set appeui "+_appeui); 160 | } 161 | 162 | // A valid length App Key was give. Use it. 163 | if ( AppKey.length() == 32 ) 164 | { 165 | _appskey = AppKey; //reuse the same variable as for ABP 166 | sendRawCommand("mac set appkey "+_appskey); 167 | } 168 | 169 | if (_moduleType == RN2903) 170 | { 171 | sendRawCommand(F("mac set pwridx 5")); 172 | } 173 | else 174 | { 175 | sendRawCommand(F("mac set pwridx 1")); 176 | } 177 | 178 | // TTN does not yet support Adaptive Data Rate. 179 | // Using it is also only necessary in limited situations. 180 | // Therefore disable it by default. 181 | sendRawCommand(F("mac set adr off")); 182 | 183 | // Switch off automatic replies, because this library can not 184 | // handle more than one mac_rx per tx. See RN2483 datasheet, 185 | // 2.4.8.14, page 27 and the scenario on page 19. 186 | sendRawCommand(F("mac set ar off")); 187 | 188 | // Semtech and TTN both use a non default RX2 window freq and SF. 189 | // Maybe we should not specify this for other networks. 190 | // if (_moduleType == RN2483) 191 | // { 192 | // sendRawCommand(F("mac set rx2 3 869525000")); 193 | // } 194 | // Disabled for now because an OTAA join seems to work fine without. 195 | 196 | _serial.setTimeout(30000); 197 | sendRawCommand(F("mac save")); 198 | 199 | bool joined = false; 200 | 201 | // Only try twice to join, then return and let the user handle it. 202 | for(int i=0; i<2 && !joined; i++) 203 | { 204 | sendRawCommand(F("mac join otaa")); 205 | // Parse 2nd response 206 | receivedData = _serial.readStringUntil('\n'); 207 | 208 | if(receivedData.startsWith("accepted")) 209 | { 210 | joined=true; 211 | delay(1000); 212 | } 213 | else 214 | { 215 | delay(1000); 216 | } 217 | } 218 | _serial.setTimeout(2000); 219 | return joined; 220 | } 221 | 222 | 223 | bool rn2xx3::initOTAA(uint8_t * AppEUI, uint8_t * AppKey, uint8_t * DevEUI) 224 | { 225 | String app_eui; 226 | String dev_eui; 227 | String app_key; 228 | char buff[3]; 229 | 230 | app_eui=""; 231 | for (uint8_t i=0; i<8; i++) 232 | { 233 | sprintf(buff, "%02X", AppEUI[i]); 234 | app_eui += String (buff); 235 | } 236 | 237 | dev_eui = "0"; 238 | if (DevEUI) //==0 239 | { 240 | dev_eui = ""; 241 | for (uint8_t i=0; i<8; i++) 242 | { 243 | sprintf(buff, "%02X", DevEUI[i]); 244 | dev_eui += String (buff); 245 | } 246 | } 247 | 248 | app_key=""; 249 | for (uint8_t i=0; i<16; i++) 250 | { 251 | sprintf(buff, "%02X", AppKey[i]); 252 | app_key += String (buff); 253 | } 254 | 255 | return initOTAA(app_eui, app_key, dev_eui); 256 | } 257 | 258 | bool rn2xx3::initABP(String devAddr, String AppSKey, String NwkSKey) 259 | { 260 | _otaa = false; 261 | _devAddr = devAddr; 262 | _appskey = AppSKey; 263 | _nwkskey = NwkSKey; 264 | String receivedData; 265 | 266 | //clear serial buffer 267 | while(_serial.available()) 268 | _serial.read(); 269 | 270 | configureModuleType(); 271 | 272 | switch (_moduleType) { 273 | case RN2903: 274 | sendRawCommand(F("mac reset")); 275 | break; 276 | case RN2483: 277 | sendRawCommand(F("mac reset 868")); 278 | // sendRawCommand(F("mac set rx2 3 869525000")); 279 | // In the past we set the downlink channel here, 280 | // but setFrequencyPlan is a better place to do it. 281 | break; 282 | default: 283 | // we shouldn't go forward with the init 284 | return false; 285 | } 286 | 287 | sendRawCommand("mac set nwkskey "+_nwkskey); 288 | sendRawCommand("mac set appskey "+_appskey); 289 | sendRawCommand("mac set devaddr "+_devAddr); 290 | sendRawCommand(F("mac set adr off")); 291 | 292 | // Switch off automatic replies, because this library can not 293 | // handle more than one mac_rx per tx. See RN2483 datasheet, 294 | // 2.4.8.14, page 27 and the scenario on page 19. 295 | sendRawCommand(F("mac set ar off")); 296 | 297 | if (_moduleType == RN2903) 298 | { 299 | sendRawCommand("mac set pwridx 5"); 300 | } 301 | else 302 | { 303 | sendRawCommand(F("mac set pwridx 1")); 304 | } 305 | sendRawCommand(F("mac set dr 5")); //0= min, 7=max 306 | 307 | _serial.setTimeout(60000); 308 | sendRawCommand(F("mac save")); 309 | sendRawCommand(F("mac join abp")); 310 | receivedData = _serial.readStringUntil('\n'); 311 | 312 | _serial.setTimeout(2000); 313 | delay(1000); 314 | 315 | if(receivedData.startsWith("accepted")) 316 | { 317 | return true; 318 | //with abp we can always join successfully as long as the keys are valid 319 | } 320 | else 321 | { 322 | return false; 323 | } 324 | } 325 | 326 | TX_RETURN_TYPE rn2xx3::tx(String data) 327 | { 328 | return txUncnf(data); //we are unsure which mode we're in. Better not to wait for acks. 329 | } 330 | 331 | TX_RETURN_TYPE rn2xx3::txBytes(const byte* data, uint8_t size) 332 | { 333 | char msgBuffer[size*2 + 1]; 334 | 335 | char buffer[3]; 336 | for (unsigned i=0; i10) 370 | { 371 | return TX_FAIL; 372 | } 373 | 374 | _serial.print(command); 375 | if(shouldEncode) 376 | { 377 | sendEncoded(data); 378 | } 379 | else 380 | { 381 | _serial.print(data); 382 | } 383 | _serial.println(); 384 | 385 | String receivedData = _serial.readStringUntil('\n'); 386 | //TODO: Debug print on receivedData 387 | 388 | if(receivedData.startsWith("ok")) 389 | { 390 | _serial.setTimeout(30000); 391 | receivedData = _serial.readStringUntil('\n'); 392 | _serial.setTimeout(2000); 393 | 394 | //TODO: Debug print on receivedData 395 | 396 | if(receivedData.startsWith("mac_tx_ok")) 397 | { 398 | //SUCCESS!! 399 | send_success = true; 400 | return TX_SUCCESS; 401 | } 402 | 403 | else if(receivedData.startsWith("mac_rx")) 404 | { 405 | //example: mac_rx 1 54657374696E6720313233 406 | _rxMessenge = receivedData.substring(receivedData.indexOf(' ', 7)+1); 407 | send_success = true; 408 | return TX_WITH_RX; 409 | } 410 | 411 | else if(receivedData.startsWith("mac_err")) 412 | { 413 | init(); 414 | } 415 | 416 | else if(receivedData.startsWith("invalid_data_len")) 417 | { 418 | //this should never happen if the prototype worked 419 | send_success = true; 420 | return TX_FAIL; 421 | } 422 | 423 | else if(receivedData.startsWith("radio_tx_ok")) 424 | { 425 | //SUCCESS!! 426 | send_success = true; 427 | return TX_SUCCESS; 428 | } 429 | 430 | else if(receivedData.startsWith("radio_err")) 431 | { 432 | //This should never happen. If it does, something major is wrong. 433 | init(); 434 | } 435 | 436 | else 437 | { 438 | //unknown response 439 | //init(); 440 | } 441 | } 442 | 443 | else if(receivedData.startsWith("invalid_param")) 444 | { 445 | //should not happen if we typed the commands correctly 446 | send_success = true; 447 | return TX_FAIL; 448 | } 449 | 450 | else if(receivedData.startsWith("not_joined")) 451 | { 452 | init(); 453 | } 454 | 455 | else if(receivedData.startsWith("no_free_ch")) 456 | { 457 | //retry 458 | delay(1000); 459 | } 460 | 461 | else if(receivedData.startsWith("silent")) 462 | { 463 | init(); 464 | } 465 | 466 | else if(receivedData.startsWith("frame_counter_err_rejoin_needed")) 467 | { 468 | init(); 469 | } 470 | 471 | else if(receivedData.startsWith("busy")) 472 | { 473 | busy_count++; 474 | 475 | // Not sure if this is wise. At low data rates with large packets 476 | // this can perhaps cause transmissions at more than 1% duty cycle. 477 | // Need to calculate the correct constant value. 478 | // But it is wise to have this check and re-init in case the 479 | // lorawan stack in the RN2xx3 hangs. 480 | if(busy_count>=10) 481 | { 482 | init(); 483 | } 484 | else 485 | { 486 | delay(1000); 487 | } 488 | } 489 | 490 | else if(receivedData.startsWith("mac_paused")) 491 | { 492 | init(); 493 | } 494 | 495 | else if(receivedData.startsWith("invalid_data_len")) 496 | { 497 | //should not happen if the prototype worked 498 | send_success = true; 499 | return TX_FAIL; 500 | } 501 | 502 | else 503 | { 504 | //unknown response after mac tx command 505 | init(); 506 | } 507 | } 508 | 509 | return TX_FAIL; //should never reach this 510 | } 511 | 512 | void rn2xx3::sendEncoded(String input) 513 | { 514 | char working; 515 | char buffer[3]; 516 | for (unsigned i=0; i=0 && dr<=5) 589 | { 590 | delay(100); 591 | while(_serial.available()) 592 | _serial.read(); 593 | _serial.print("mac set dr "); 594 | _serial.println(dr); 595 | _serial.readStringUntil('\n'); 596 | } 597 | } 598 | 599 | void rn2xx3::sleep(long msec) 600 | { 601 | _serial.print("sys sleep "); 602 | _serial.println(msec); 603 | } 604 | 605 | 606 | String rn2xx3::sendRawCommand(String command) 607 | { 608 | delay(100); 609 | while(_serial.available()) 610 | _serial.read(); 611 | _serial.println(command); 612 | String ret = _serial.readStringUntil('\n'); 613 | ret.trim(); 614 | 615 | //TODO: Add debug print 616 | 617 | return ret; 618 | } 619 | 620 | RN2xx3_t rn2xx3::moduleType() 621 | { 622 | return _moduleType; 623 | } 624 | 625 | bool rn2xx3::setFrequencyPlan(FREQ_PLAN fp) 626 | { 627 | bool returnValue; 628 | 629 | switch (fp) 630 | { 631 | case SINGLE_CHANNEL_EU: 632 | { 633 | if(_moduleType == RN2483) 634 | { 635 | //mac set rx2 636 | //sendRawCommand(F("mac set rx2 5 868100000")); //use this for "strict" one channel gateways 637 | sendRawCommand(F("mac set rx2 3 869525000")); //use for "non-strict" one channel gateways 638 | sendRawCommand(F("mac set ch dcycle 0 99")); //1% duty cycle for this channel 639 | sendRawCommand(F("mac set ch dcycle 1 65535")); //almost never use this channel 640 | sendRawCommand(F("mac set ch dcycle 2 65535")); //almost never use this channel 641 | 642 | returnValue = true; 643 | } 644 | else 645 | { 646 | returnValue = false; 647 | } 648 | break; 649 | } 650 | 651 | case TTN_EU: 652 | { 653 | if(_moduleType == RN2483) 654 | { 655 | /* 656 | * The value that needs to be configured can be 657 | * obtained from the actual duty cycle X (in percentage) 658 | * using the following formula: = (100/X) – 1 659 | * 660 | * 10% -> 9 661 | * 1% -> 99 662 | * 0.33% -> 299 663 | * 8 channels, total of 1% duty cycle: 664 | * 0.125% per channel -> 799 665 | * 666 | * Most of the TTN_EU frequency plan was copied from: 667 | * https://github.com/TheThingsNetwork/arduino-device-lib 668 | */ 669 | 670 | //RX window 2 671 | sendRawCommand(F("mac set rx2 3 869525000")); 672 | 673 | //channel 0 674 | sendRawCommand(F("mac set ch dcycle 0 799")); 675 | 676 | //channel 1 677 | sendRawCommand(F("mac set ch drrange 1 0 6")); 678 | sendRawCommand(F("mac set ch dcycle 1 799")); 679 | 680 | //channel 2 681 | sendRawCommand(F("mac set ch dcycle 2 799")); 682 | 683 | //channel 3 684 | sendRawCommand(F("mac set ch freq 3 867100000")); 685 | sendRawCommand(F("mac set ch drrange 3 0 5")); 686 | sendRawCommand(F("mac set ch dcycle 3 799")); 687 | sendRawCommand(F("mac set ch status 3 on")); 688 | 689 | //channel 4 690 | sendRawCommand(F("mac set ch freq 4 867300000")); 691 | sendRawCommand(F("mac set ch drrange 4 0 5")); 692 | sendRawCommand(F("mac set ch dcycle 4 799")); 693 | sendRawCommand(F("mac set ch status 4 on")); 694 | 695 | //channel 5 696 | sendRawCommand(F("mac set ch freq 5 867500000")); 697 | sendRawCommand(F("mac set ch drrange 5 0 5")); 698 | sendRawCommand(F("mac set ch dcycle 5 799")); 699 | sendRawCommand(F("mac set ch status 5 on")); 700 | 701 | //channel 6 702 | sendRawCommand(F("mac set ch freq 6 867700000")); 703 | sendRawCommand(F("mac set ch drrange 6 0 5")); 704 | sendRawCommand(F("mac set ch dcycle 6 799")); 705 | sendRawCommand(F("mac set ch status 6 on")); 706 | 707 | //channel 7 708 | sendRawCommand(F("mac set ch freq 7 867900000")); 709 | sendRawCommand(F("mac set ch drrange 7 0 5")); 710 | sendRawCommand(F("mac set ch dcycle 7 799")); 711 | sendRawCommand(F("mac set ch status 7 on")); 712 | 713 | returnValue = true; 714 | } 715 | else 716 | { 717 | returnValue = false; 718 | } 719 | 720 | break; 721 | } 722 | 723 | case TTN_US: 724 | { 725 | /* 726 | * Most of the TTN_US frequency plan was copied from: 727 | * https://github.com/TheThingsNetwork/arduino-device-lib 728 | */ 729 | if(_moduleType == RN2903) 730 | { 731 | for(int channel=0; channel<72; channel++) 732 | { 733 | // Build command string. First init, then add int. 734 | String command = F("mac set ch status "); 735 | command += channel; 736 | 737 | if(channel>=8 && channel<16) 738 | { 739 | sendRawCommand(command+F(" on")); 740 | } 741 | else 742 | { 743 | sendRawCommand(command+F(" off")); 744 | } 745 | } 746 | returnValue = true; 747 | } 748 | else 749 | { 750 | returnValue = false; 751 | } 752 | break; 753 | } 754 | 755 | case DEFAULT_EU: 756 | { 757 | if(_moduleType == RN2483) 758 | { 759 | //fix duty cycle - 1% = 0.33% per channel 760 | sendRawCommand(F("mac set ch dcycle 0 799")); 761 | sendRawCommand(F("mac set ch dcycle 1 799")); 762 | sendRawCommand(F("mac set ch dcycle 2 799")); 763 | 764 | //disable non-default channels 765 | sendRawCommand(F("mac set ch status 3 on")); 766 | sendRawCommand(F("mac set ch status 4 on")); 767 | sendRawCommand(F("mac set ch status 5 on")); 768 | sendRawCommand(F("mac set ch status 6 on")); 769 | sendRawCommand(F("mac set ch status 7 on")); 770 | 771 | returnValue = true; 772 | } 773 | else 774 | { 775 | returnValue = false; 776 | } 777 | 778 | break; 779 | } 780 | default: 781 | { 782 | //set default channels 868.1, 868.3 and 868.5? 783 | returnValue = false; //well we didn't do anything, so yes, false 784 | break; 785 | } 786 | } 787 | 788 | return returnValue; 789 | } 790 | --------------------------------------------------------------------------------