├── .gitignore ├── .vscode └── extensions.json ├── README.md ├── img └── ttgo-t-beam-v1.1.jpg ├── include └── README ├── lib ├── OnBoardGPS │ ├── README.md │ ├── examples │ │ ├── PeriodicLowPowerMode │ │ │ └── PeriodicLowPowerMode.ino │ │ ├── configOnBoardGPS │ │ │ └── configOnBoardGPS.ino │ │ ├── oledDisplayGPSInfo │ │ │ └── oledDisplayGPSInfo.ino │ │ └── printGPSInfo │ │ │ └── printGPSInfo.ino │ ├── keywords.txt │ ├── library.properties │ └── src │ │ ├── CubeCell_TinyGPS++.cpp │ │ ├── CubeCell_TinyGPS++.h │ │ ├── GPS_Air530.cpp │ │ ├── GPS_Air530.h │ │ ├── GPS_Air530Z.cpp │ │ ├── GPS_Air530Z.h │ │ ├── GPS_Trans.cpp │ │ ├── GPS_Trans.h │ │ ├── GPS_Ublox_M8M.cpp │ │ └── GPS_Ublox_M8M.h └── README ├── payload_formatter_v3.js ├── platformio.ini ├── scripts ├── config.ini.example ├── converter.py ├── log_gps_status.py └── visualizer.py ├── src ├── config.hpp.example ├── cubecell.cpp ├── display.cpp ├── display.hpp ├── esp32.cpp ├── gps.cpp ├── gps.hpp ├── lorawan.cpp ├── lorawan.hpp ├── power.cpp └── power.hpp └── test └── README /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/settings.json 5 | .vscode/launch.json 6 | .vscode/ipch 7 | src/config.hpp 8 | scripts/config.ini 9 | scripts/*.log 10 | pio.map 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TTN Mapper T-beam 2 | 3 | Energy efficient mapping software for [ttnmapper](http://ttnmapper.org/) using a T-beam. 4 | 5 | Send your GPS-Location over [TTS](https://www.thethingsnetwork.org/). The Location can be viewed on the [advanced maps](https://ttnmapper.org/advanced-maps/) when the [integration](https://docs.ttnmapper.org/integration/tts-integration-v3.html) is activated. 6 | 7 | ## Features 8 | 9 | * deep sleep ESP32 to save energy 10 | * geofence for location upload 11 | * detect no movement 12 | * sleep-mode while not in use 13 | 14 | ## Usage 15 | 16 | LED IO4(red): 17 | * off: device in permanent sleep or off 18 | * gliming: device is in deep sleep waiting for the next transmission time 19 | * on: device is active either joining or has woken up to send the next message 20 | 21 | LED CHG(blue): 22 | 23 | * off: everything ok 24 | * on: battery is charging 25 | 26 | LED GPS(red,next to the GPS-Module): 27 | * off: GPS off or no fix 28 | * blinking: GPS has fix 29 | 30 | Button Power(PWR): 31 | 32 | * short press(1 sec) to turn the device on or if already powered wake up, send the current location and go back to sleep again 33 | * long press(2 sec/wait until IO4 LED goes off) put the device into permanent sleep mode turning off the GPS and radio while preserving LoraWAN credentials 34 | * very long press(10 sec) completely turn off the ESP loosing all credentials 35 | 36 | ## Hardware 37 | 38 | I'm using the TTGO-T-beam v1.1(with the axp power management chip onboard) 39 | 40 | ![T-Beam v1.1](img/ttgo-t-beam-v1.1.jpg "T-Beam v1.1") 41 | 42 | But the software can easily be modified to run on any ESP32-Board with a GPS and a LoRa module attached. 43 | 44 | ## Compiling 45 | 46 | ### Software 47 | 48 | Clone this repository and open it with [Visual Studio Code(vscode)](https://code.visualstudio.com/). This code editor automaticly downloads the extension [platformio](https://platformio.org/) to compile and upload the program. 49 | 50 | ### Modification 51 | 52 | | :warning: Modify the config file with your own data! | 53 | |------------------------------------------------------| 54 | 55 | You have to copy [config.hpp.example](src/config.hpp.example). Insert your own TTS-keys and use or disable the geofencing option 56 | 57 | ### Upload 58 | 59 | For uploading just hit the upload-button at the bottom of the vscode window 60 | -------------------------------------------------------------------------------- /img/ttgo-t-beam-v1.1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitconnector/ttn_mapper_t-beam/d26e5dd866c7408de6e04b0f913378d7d69ec38f/img/ttgo-t-beam-v1.1.jpg -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /lib/OnBoardGPS/README.md: -------------------------------------------------------------------------------- 1 | # TinyGPSPlus 2 | A new, customizable Arduino NMEA parsing library 3 | A *NEW* Full-featured GPS/NMEA Parser for Arduino 4 | TinyGPS++ is a new Arduino library for parsing NMEA data streams provided by GPS modules. 5 | 6 | Like its predecessor, TinyGPS, this library provides compact and easy-to-use methods for extracting position, date, time, altitude, speed, and course from consumer GPS devices. 7 | 8 | However, TinyGPS++’s programmer interface is considerably simpler to use than TinyGPS, and the new library can extract arbitrary data from any of the myriad NMEA sentences out there, even proprietary ones. 9 | 10 | See [Arduiniana - TinyGPS++](http://arduiniana.org/libraries/tinygpsplus/) for more detailed information on how to use TinyGPSPlus 11 | -------------------------------------------------------------------------------- /lib/OnBoardGPS/examples/PeriodicLowPowerMode/PeriodicLowPowerMode.ino: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include "GPS_Air530.h" 3 | #include "GPS_Air530Z.h" 4 | #include "HT_SSD1306Wire.h" 5 | 6 | SSD1306Wire display(0x3c, 500000, I2C_NUM_0,GEOMETRY_128_64,GPIO10 ); // addr , freq , i2c group , ratio , rst 7 | 8 | //if GPS module is Air530, use this 9 | //Air530Class GPS; 10 | 11 | //if GPS module is Air530Z, use this 12 | Air530ZClass GPS; 13 | 14 | typedef enum 15 | { 16 | STATUS_LPM = 0, 17 | STATUS_UPDATE_GPS 18 | }dev_status; 19 | 20 | //fisrt fix timeout 21 | #define GPS_INIT_TIMEOUT 60000 22 | 23 | //sleep time until next GPS update 24 | #define GPS_SLEEPTIME 60000 25 | 26 | //when gps waked, if in GPS_UPDATE_TIMEOUT, gps not fixed then into low power mode 27 | #define GPS_UPDATE_TIMEOUT 10000 28 | 29 | //once fixed, GPS_CONTINUE_TIME later into low power mode 30 | #define GPS_CONTINUE_TIME 5000 31 | 32 | static TimerEvent_t autoGPS; 33 | dev_status mcu_status; 34 | uint32_t starttime; 35 | 36 | int32_t fracPart(double val, int n) 37 | { 38 | return (int32_t)((val - (int32_t)(val))*pow(10,n)); 39 | } 40 | 41 | void VextON(void) 42 | { 43 | pinMode(Vext,OUTPUT); 44 | digitalWrite(Vext, LOW); 45 | } 46 | 47 | void VextOFF(void) //Vext default OFF 48 | { 49 | pinMode(Vext,OUTPUT); 50 | digitalWrite(Vext, HIGH); 51 | } 52 | void displayGPSInof() 53 | { 54 | char str[30]; 55 | display.clear(); 56 | display.setFont(ArialMT_Plain_10); 57 | int index = sprintf(str,"%02d-%02d-%02d",GPS.date.year(),GPS.date.day(),GPS.date.month()); 58 | str[index] = 0; 59 | display.setTextAlignment(TEXT_ALIGN_LEFT); 60 | display.drawString(0, 0, str); 61 | 62 | index = sprintf(str,"%02d:%02d:%02d",GPS.time.hour(),GPS.time.minute(),GPS.time.second(),GPS.time.centisecond()); 63 | str[index] = 0; 64 | display.drawString(60, 0, str); 65 | 66 | if( GPS.location.age() < 1000 ) 67 | { 68 | display.drawString(120, 0, "A"); 69 | } 70 | else 71 | { 72 | display.drawString(120, 0, "V"); 73 | } 74 | 75 | index = sprintf(str,"alt: %d.%d",(int)GPS.altitude.meters(),fracPart(GPS.altitude.meters(),2)); 76 | str[index] = 0; 77 | display.drawString(0, 16, str); 78 | 79 | index = sprintf(str,"hdop: %d.%d",(int)GPS.hdop.hdop(),fracPart(GPS.hdop.hdop(),2)); 80 | str[index] = 0; 81 | display.drawString(0, 32, str); 82 | 83 | index = sprintf(str,"lat : %d.%d",(int)GPS.location.lat(),fracPart(GPS.location.lat(),4)); 84 | str[index] = 0; 85 | display.drawString(60, 16, str); 86 | 87 | index = sprintf(str,"lon:%d.%d",(int)GPS.location.lng(),fracPart(GPS.location.lng(),4)); 88 | str[index] = 0; 89 | display.drawString(60, 32, str); 90 | 91 | index = sprintf(str,"speed: %d.%d km/h",(int)GPS.speed.kmph(),fracPart(GPS.speed.kmph(),3)); 92 | str[index] = 0; 93 | display.drawString(0, 48, str); 94 | display.display(); 95 | } 96 | 97 | void printGPSInof() 98 | { 99 | Serial.print("Date/Time: "); 100 | if (GPS.date.isValid()) 101 | { 102 | Serial.printf("%d/%02d/%02d",GPS.date.year(),GPS.date.day(),GPS.date.month()); 103 | } 104 | else 105 | { 106 | Serial.print("INVALID"); 107 | } 108 | 109 | if (GPS.time.isValid()) 110 | { 111 | Serial.printf(" %02d:%02d:%02d.%02d",GPS.time.hour(),GPS.time.minute(),GPS.time.second(),GPS.time.centisecond()); 112 | } 113 | else 114 | { 115 | Serial.print(" INVALID"); 116 | } 117 | Serial.println(); 118 | 119 | Serial.print("LAT: "); 120 | Serial.print(GPS.location.lat(),6); 121 | Serial.print(", LON: "); 122 | Serial.print(GPS.location.lng(),6); 123 | Serial.print(", ALT: "); 124 | Serial.print(GPS.altitude.meters()); 125 | 126 | Serial.println(); 127 | 128 | Serial.print("SATS: "); 129 | Serial.print(GPS.satellites.value()); 130 | Serial.print(", HDOP: "); 131 | Serial.print(GPS.hdop.hdop()); 132 | Serial.print(", AGE: "); 133 | Serial.print(GPS.location.age()); 134 | Serial.print(", COURSE: "); 135 | Serial.print(GPS.course.deg()); 136 | Serial.print(", SPEED: "); 137 | Serial.println(GPS.speed.kmph()); 138 | Serial.println(); 139 | } 140 | 141 | void onAutoGPS() 142 | { 143 | TimerStop(&autoGPS); 144 | mcu_status = STATUS_UPDATE_GPS; 145 | } 146 | 147 | void gpsUpdate(uint32_t timeout, uint32_t continuetime) 148 | { 149 | VextON();// oled power on; 150 | delay(10); 151 | display.init(); 152 | display.clear(); 153 | display.display(); 154 | 155 | display.setTextAlignment(TEXT_ALIGN_CENTER); 156 | display.setFont(ArialMT_Plain_16); 157 | display.drawString(64, 32-16/2, "GPS Searching..."); 158 | Serial.println("GPS Searching..."); 159 | display.display(); 160 | 161 | GPS.begin(); 162 | starttime = millis(); 163 | while( (millis()-starttime) < timeout ) 164 | { 165 | while (GPS.available() > 0) 166 | { 167 | GPS.encode(GPS.read()); 168 | } 169 | 170 | // gps fixed in a second 171 | if( GPS.location.age() < 1000 ) 172 | { 173 | break; 174 | } 175 | } 176 | 177 | //if gps fixed update gps and print gps info every 1 second 178 | if(GPS.location.age() < 1000) 179 | { 180 | starttime = millis(); 181 | uint32_t printinfo = 0; 182 | while( (millis()-starttime) < continuetime ) 183 | { 184 | while (GPS.available() > 0) 185 | { 186 | GPS.encode(GPS.read()); 187 | } 188 | 189 | if( (millis()-starttime) > printinfo ) 190 | { 191 | printinfo += 1000; 192 | printGPSInof(); 193 | displayGPSInof(); 194 | } 195 | } 196 | } 197 | else //if gps no fixed waited for 2s in to lowpowermode 198 | { 199 | display.clear(); 200 | display.drawString(64, 32-16/2, "GPS TIMEOUT"); 201 | display.display(); 202 | Serial.println("GPS search timeout."); 203 | delay(2000); 204 | } 205 | GPS.end(); 206 | display.clear(); 207 | display.setTextAlignment(TEXT_ALIGN_CENTER); 208 | display.setFont(ArialMT_Plain_16); 209 | display.drawString(64, 32-16/2, "into lowpower mode..."); 210 | display.display(); 211 | Serial.println("2 seconds later into lowpower mode...."); 212 | delay(2000); 213 | display.clear(); 214 | display.display(); 215 | display.stop(); 216 | VextOFF(); //oled power off 217 | 218 | TimerSetValue( &autoGPS, GPS_SLEEPTIME ); 219 | TimerStart( &autoGPS ); 220 | } 221 | void setup() { 222 | // put your setup code here, to run once: 223 | Serial.begin(115200); 224 | 225 | TimerInit( &autoGPS, onAutoGPS ); 226 | 227 | gpsUpdate(GPS_INIT_TIMEOUT,GPS_CONTINUE_TIME); 228 | mcu_status = STATUS_LPM; 229 | } 230 | 231 | void loop() { 232 | switch(mcu_status) 233 | { 234 | case STATUS_LPM: 235 | lowPowerHandler(); 236 | break; 237 | case STATUS_UPDATE_GPS: 238 | gpsUpdate(GPS_UPDATE_TIMEOUT,GPS_CONTINUE_TIME); 239 | mcu_status = STATUS_LPM; 240 | break; 241 | default: 242 | mcu_status = STATUS_LPM; 243 | break; 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /lib/OnBoardGPS/examples/configOnBoardGPS/configOnBoardGPS.ino: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include "GPS_Air530.h" 3 | #include "GPS_Air530Z.h" 4 | 5 | //if GPS module is Air530, use this 6 | Air530Class GPS; 7 | 8 | //if GPS module is Air530Z, use this 9 | //Air530ZClass GPS; 10 | 11 | void setup() { 12 | Serial.begin(115200); 13 | 14 | /*GPS uart baud: 15 | * Air530Z support : 9600,19200,38400,57600,115200. 16 | * Air530 support : 9600,19200,38400,57600,115200,230400,460800 17 | * default is 9600 18 | */ 19 | GPS.begin(9600); 20 | 21 | /*GPS mode set: 22 | * GPS : MODE_GPS 23 | * BEIDOU : MODE_BEIDOU 24 | * GLONASS : MODE_GLONASS //only Air530Z supported. 25 | * GPS+BEIDOU : MODE_GPS_BEIDOU 26 | * GSP+GLONASS: MODE_GPS_GLONASS 27 | * GPS+BEIDOU+GLONASS: MODE_GPS_BEIDOU_GLONASS //only Air530Z supported. 28 | * For Air530, default mode is GPS+BEIDOU. 29 | * For Air530Z, default mode is GPS+BEIDOU+GLONASS. 30 | */ 31 | // GPS.setmode(MODE_GPS_GLONASS); 32 | 33 | /*supported nmea sentence : 34 | * GLL, RMC, VTG, GGA, GSA, GSV 35 | */ 36 | // GPS.setNMEA(NMEA_GSV); 37 | } 38 | 39 | void loop() 40 | { 41 | /*get nmea sentence 42 | * GPS.getAll() to get all nmea sentence; 43 | * GPS.getNMEA() to get an any kind of nmea sentence; 44 | * GPS.getRMC() to get RMC sentence; 45 | * GPS.getGGA() to get GGA sentence; 46 | * GPS.getGSA() to get GSA sentence; 47 | * GPS.getGSV() to get GSV sentence; 48 | * GPS.getGLL() to get GLL sentence; 49 | * GPS.getVTG() to get VTG sentence; 50 | */ 51 | String NMEA = GPS.getAll(); 52 | if(NMEA != "0") 53 | { 54 | Serial.println(NMEA); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/OnBoardGPS/examples/oledDisplayGPSInfo/oledDisplayGPSInfo.ino: -------------------------------------------------------------------------------- 1 | //the GPS module used is GPS. 2 | #include "Arduino.h" 3 | #include "GPS_Air530.h" 4 | #include "GPS_Air530Z.h" 5 | #include 6 | #include "HT_SSD1306Wire.h" 7 | 8 | SSD1306Wire display(0x3c, 500000, I2C_NUM_0, GEOMETRY_128_64, GPIO10 ); // addr , freq , i2c group , resolution , rst 9 | 10 | //if GPS module is Air530, use this 11 | //Air530Class GPS; 12 | 13 | //if GPS module is Air530Z, use this 14 | Air530ZClass GPS; 15 | 16 | int fracPart(double val, int n) 17 | { 18 | return (int)((val - (int)(val))*pow(10,n)); 19 | } 20 | 21 | void VextON(void) 22 | { 23 | pinMode(Vext,OUTPUT); 24 | digitalWrite(Vext, LOW); 25 | } 26 | 27 | void VextOFF(void) //Vext default OFF 28 | { 29 | pinMode(Vext,OUTPUT); 30 | digitalWrite(Vext, HIGH); 31 | } 32 | 33 | void setup() { 34 | VextON(); 35 | delay(10); 36 | 37 | display.init(); 38 | display.clear(); 39 | display.display(); 40 | 41 | display.setTextAlignment(TEXT_ALIGN_CENTER); 42 | display.setFont(ArialMT_Plain_16); 43 | display.drawString(64, 32-16/2, "GPS initing..."); 44 | display.display(); 45 | 46 | Serial.begin(115200); 47 | GPS.begin(); 48 | } 49 | 50 | void loop() 51 | { 52 | uint32_t starttime = millis(); 53 | while( (millis()-starttime) < 1000 ) 54 | { 55 | while (GPS.available() > 0) 56 | { 57 | GPS.encode(GPS.read()); 58 | } 59 | } 60 | 61 | char str[30]; 62 | display.clear(); 63 | display.setFont(ArialMT_Plain_10); 64 | int index = sprintf(str,"%02d-%02d-%02d",GPS.date.year(),GPS.date.day(),GPS.date.month()); 65 | str[index] = 0; 66 | display.setTextAlignment(TEXT_ALIGN_LEFT); 67 | display.drawString(0, 0, str); 68 | 69 | index = sprintf(str,"%02d:%02d:%02d",GPS.time.hour(),GPS.time.minute(),GPS.time.second(),GPS.time.centisecond()); 70 | str[index] = 0; 71 | display.drawString(60, 0, str); 72 | 73 | if( GPS.location.age() < 1000 ) 74 | { 75 | display.drawString(120, 0, "A"); 76 | } 77 | else 78 | { 79 | display.drawString(120, 0, "V"); 80 | } 81 | 82 | index = sprintf(str,"alt: %d.%d",(int)GPS.altitude.meters(),fracPart(GPS.altitude.meters(),2)); 83 | str[index] = 0; 84 | display.drawString(0, 16, str); 85 | 86 | index = sprintf(str,"hdop: %d.%d",(int)GPS.hdop.hdop(),fracPart(GPS.hdop.hdop(),2)); 87 | str[index] = 0; 88 | display.drawString(0, 32, str); 89 | 90 | index = sprintf(str,"lat : %d.%d",(int)GPS.location.lat(),fracPart(GPS.location.lat(),4)); 91 | str[index] = 0; 92 | display.drawString(60, 16, str); 93 | 94 | index = sprintf(str,"lon:%d.%d",(int)GPS.location.lng(),fracPart(GPS.location.lng(),4)); 95 | str[index] = 0; 96 | display.drawString(60, 32, str); 97 | 98 | index = sprintf(str,"speed: %d.%d km/h",(int)GPS.speed.kmph(),fracPart(GPS.speed.kmph(),3)); 99 | str[index] = 0; 100 | display.drawString(0, 48, str); 101 | display.display(); 102 | } 103 | -------------------------------------------------------------------------------- /lib/OnBoardGPS/examples/printGPSInfo/printGPSInfo.ino: -------------------------------------------------------------------------------- 1 | #include "GPS_Air530.h" 2 | #include "GPS_Air530Z.h" 3 | 4 | //if GPS module is Air530, use this 5 | //Air530Class GPS; 6 | 7 | //if GPS module is Air530Z, use this 8 | Air530ZClass GPS; 9 | 10 | void setup() 11 | { 12 | Serial.begin(115200); 13 | GPS.begin(); 14 | 15 | Serial.println(F("A simple demonstration of Air530 module")); 16 | Serial.println(); 17 | 18 | displayInfo(); 19 | } 20 | 21 | void loop(){ 22 | uint32_t starttime = millis(); 23 | while( (millis()-starttime) < 1000 ) 24 | { 25 | while (GPS.available() > 0) 26 | { 27 | GPS.encode(GPS.read()); 28 | } 29 | } 30 | displayInfo(); 31 | if (millis() > 5000 && GPS.charsProcessed() < 10) 32 | { 33 | Serial.println("No GPS detected: check wiring."); 34 | while(true); 35 | } 36 | } 37 | 38 | void displayInfo() 39 | { 40 | Serial.print("Date/Time: "); 41 | if (GPS.date.isValid()) 42 | { 43 | Serial.printf("%d/%02d/%02d",GPS.date.year(),GPS.date.day(),GPS.date.month()); 44 | } 45 | else 46 | { 47 | Serial.print("INVALID"); 48 | } 49 | 50 | if (GPS.time.isValid()) 51 | { 52 | Serial.printf(" %02d:%02d:%02d.%02d",GPS.time.hour(),GPS.time.minute(),GPS.time.second(),GPS.time.centisecond()); 53 | } 54 | else 55 | { 56 | Serial.print(" INVALID"); 57 | } 58 | Serial.println(); 59 | 60 | Serial.print("LAT: "); 61 | Serial.print(GPS.location.lat(),6); 62 | Serial.print(", LON: "); 63 | Serial.print(GPS.location.lng(),6); 64 | Serial.print(", ALT: "); 65 | Serial.print(GPS.altitude.meters()); 66 | 67 | Serial.println(); 68 | 69 | Serial.print("SATS: "); 70 | Serial.print(GPS.satellites.value()); 71 | Serial.print(", HDOP: "); 72 | Serial.print(GPS.hdop.hdop()); 73 | Serial.print(", AGE: "); 74 | Serial.print(GPS.location.age()); 75 | Serial.print(", COURSE: "); 76 | Serial.print(GPS.course.deg()); 77 | Serial.print(", SPEED: "); 78 | Serial.println(GPS.speed.kmph()); 79 | Serial.println(); 80 | } 81 | -------------------------------------------------------------------------------- /lib/OnBoardGPS/keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map for TinyGPS++ 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | TinyGPSPlus KEYWORD1 10 | TinyGPSLocation KEYWORD1 11 | TinyGPSDate KEYWORD1 12 | TinyGPSTime KEYWORD1 13 | TinyGPSSpeed KEYWORD1 14 | TinyGPSCourse KEYWORD1 15 | TinyGPSAltitude KEYWORD1 16 | TinyGPSInteger KEYWORD1 17 | TinyGPSDecimal KEYWORD1 18 | TinyGPSCustom KEYWORD1 19 | 20 | ####################################### 21 | # Methods and Functions (KEYWORD2) 22 | ####################################### 23 | 24 | encode KEYWORD2 25 | location KEYWORD2 26 | date KEYWORD2 27 | time KEYWORD2 28 | speed KEYWORD2 29 | course KEYWORD2 30 | altitude KEYWORD2 31 | satellites KEYWORD2 32 | hdop KEYWORD2 33 | libraryVersion KEYWORD2 34 | distanceBetween KEYWORD2 35 | courseTo KEYWORD2 36 | cardinal KEYWORD2 37 | charsProcessed KEYWORD2 38 | sentencesWithFix KEYWORD2 39 | failedChecksum KEYWORD2 40 | passedChecksum KEYWORD2 41 | isValid KEYWORD2 42 | isUpdated KEYWORD2 43 | age KEYWORD2 44 | lat KEYWORD2 45 | lng KEYWORD2 46 | isUpdatedDate KEYWORD2 47 | isUpdatedTime KEYWORD2 48 | year KEYWORD2 49 | month KEYWORD2 50 | day KEYWORD2 51 | hour KEYWORD2 52 | minute KEYWORD2 53 | second KEYWORD2 54 | centisecond KEYWORD2 55 | value KEYWORD2 56 | knots KEYWORD2 57 | mph KEYWORD2 58 | mps KEYWORD2 59 | kmph KEYWORD2 60 | deg KEYWORD2 61 | billionths KEYWORD2 62 | negative KEYWORD2 63 | meters KEYWORD2 64 | miles KEYWORD2 65 | kilometers KEYWORD2 66 | feet KEYWORD2 67 | 68 | ####################################### 69 | # Constants (LITERAL1) 70 | ####################################### 71 | 72 | -------------------------------------------------------------------------------- /lib/OnBoardGPS/library.properties: -------------------------------------------------------------------------------- 1 | name=OnBoardGPS 2 | version=1.0.0 3 | author=Mikal Hart, Heltec 4 | maintainer= 5 | sentence=T 6 | paragraph= 7 | category=GPS 8 | url= 9 | architectures= 10 | -------------------------------------------------------------------------------- /lib/OnBoardGPS/src/CubeCell_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 | This library is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU Lesser General Public 10 | License as published by the Free Software Foundation; either 11 | version 2.1 of the License, or (at your option) any later version. 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | You should have received a copy of the GNU Lesser General Public 17 | License along with this library; if not, write to the Free Software 18 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | #include "CubeCell_TinyGPS++.h" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #define _GPRMCterm "GPRMC" 28 | #define _GPGGAterm "GPGGA" 29 | #define _GNRMCterm "GNRMC" 30 | #define _GNGGAterm "GNGGA" 31 | 32 | TinyGPSPlus::TinyGPSPlus() 33 | : parity(0) 34 | , isChecksumTerm(false) 35 | , curSentenceType(GPS_SENTENCE_OTHER) 36 | , curTermNumber(0) 37 | , curTermOffset(0) 38 | , sentenceHasFix(false) 39 | , customElts(0) 40 | , customCandidates(0) 41 | , encodedCharCount(0) 42 | , sentencesWithFixCount(0) 43 | , failedChecksumCount(0) 44 | , passedChecksumCount(0) 45 | { 46 | term[0] = '\0'; 47 | } 48 | 49 | // 50 | // public methods 51 | // 52 | 53 | bool TinyGPSPlus::encode(char c) 54 | { 55 | ++encodedCharCount; 56 | 57 | switch(c) 58 | { 59 | case ',': // term terminators 60 | parity ^= (uint8_t)c; 61 | case '\r': 62 | case '\n': 63 | case '*': 64 | { 65 | bool isValidSentence = false; 66 | if (curTermOffset < sizeof(term)) 67 | { 68 | term[curTermOffset] = 0; 69 | isValidSentence = endOfTermHandler(); 70 | } 71 | ++curTermNumber; 72 | curTermOffset = 0; 73 | isChecksumTerm = c == '*'; 74 | return isValidSentence; 75 | } 76 | break; 77 | 78 | case '$': // sentence begin 79 | curTermNumber = curTermOffset = 0; 80 | parity = 0; 81 | curSentenceType = GPS_SENTENCE_OTHER; 82 | isChecksumTerm = false; 83 | sentenceHasFix = false; 84 | return false; 85 | 86 | default: // ordinary characters 87 | if (curTermOffset < sizeof(term) - 1) 88 | term[curTermOffset++] = c; 89 | if (!isChecksumTerm) 90 | parity ^= c; 91 | return false; 92 | } 93 | 94 | return false; 95 | } 96 | 97 | // 98 | // internal utilities 99 | // 100 | int TinyGPSPlus::fromHex(char a) 101 | { 102 | if (a >= 'A' && a <= 'F') 103 | return a - 'A' + 10; 104 | else if (a >= 'a' && a <= 'f') 105 | return a - 'a' + 10; 106 | else 107 | return a - '0'; 108 | } 109 | 110 | // static 111 | // Parse a (potentially negative) number with up to 2 decimal digits -xxxx.yy 112 | int32_t TinyGPSPlus::parseDecimal(const char *term) 113 | { 114 | bool negative = *term == '-'; 115 | if (negative) ++term; 116 | int32_t ret = 100 * (int32_t)atol(term); 117 | while (isdigit(*term)) ++term; 118 | if (*term == '.' && isdigit(term[1])) 119 | { 120 | ret += 10 * (term[1] - '0'); 121 | if (isdigit(term[2])) 122 | ret += term[2] - '0'; 123 | } 124 | return negative ? -ret : ret; 125 | } 126 | 127 | // static 128 | // Parse degrees in that funny NMEA format DDMM.MMMM 129 | void TinyGPSPlus::parseDegrees(const char *term, RawDegrees °) 130 | { 131 | uint32_t leftOfDecimal = (uint32_t)atol(term); 132 | uint16_t minutes = (uint16_t)(leftOfDecimal % 100); 133 | uint32_t multiplier = 10000000UL; 134 | uint32_t tenMillionthsOfMinutes = minutes * multiplier; 135 | 136 | deg.deg = (int16_t)(leftOfDecimal / 100); 137 | 138 | while (isdigit(*term)) 139 | ++term; 140 | 141 | if (*term == '.') 142 | while (isdigit(*++term)) 143 | { 144 | multiplier /= 10; 145 | tenMillionthsOfMinutes += (*term - '0') * multiplier; 146 | } 147 | 148 | deg.billionths = (5 * tenMillionthsOfMinutes + 1) / 3; 149 | deg.negative = false; 150 | } 151 | 152 | #define COMBINE(sentence_type, term_number) (((unsigned)(sentence_type) << 5) | term_number) 153 | 154 | // Processes a just-completed term 155 | // Returns true if new sentence has just passed checksum test and is validated 156 | bool TinyGPSPlus::endOfTermHandler() 157 | { 158 | // If it's the checksum term, and the checksum checks out, commit 159 | if (isChecksumTerm) 160 | { 161 | byte checksum = 16 * fromHex(term[0]) + fromHex(term[1]); 162 | if (checksum == parity) 163 | { 164 | passedChecksumCount++; 165 | if (sentenceHasFix) 166 | ++sentencesWithFixCount; 167 | 168 | switch(curSentenceType) 169 | { 170 | case GPS_SENTENCE_GPRMC: 171 | date.commit(); 172 | time.commit(); 173 | if (sentenceHasFix) 174 | { 175 | location.commit(); 176 | speed.commit(); 177 | course.commit(); 178 | } 179 | break; 180 | case GPS_SENTENCE_GPGGA: 181 | time.commit(); 182 | if (sentenceHasFix) 183 | { 184 | location.commit(); 185 | altitude.commit(); 186 | } 187 | satellites.commit(); 188 | hdop.commit(); 189 | break; 190 | } 191 | 192 | // Commit all custom listeners of this sentence type 193 | for (TinyGPSCustom *p = customCandidates; p != NULL && strcmp(p->sentenceName, customCandidates->sentenceName) == 0; p = p->next) 194 | p->commit(); 195 | return true; 196 | } 197 | 198 | else 199 | { 200 | ++failedChecksumCount; 201 | } 202 | 203 | return false; 204 | } 205 | 206 | // the first term determines the sentence type 207 | if (curTermNumber == 0) 208 | { 209 | if (!strcmp(term, _GPRMCterm) || !strcmp(term, _GNRMCterm)) 210 | curSentenceType = GPS_SENTENCE_GPRMC; 211 | else if (!strcmp(term, _GPGGAterm) || !strcmp(term, _GNGGAterm)) 212 | curSentenceType = GPS_SENTENCE_GPGGA; 213 | else 214 | curSentenceType = GPS_SENTENCE_OTHER; 215 | 216 | // Any custom candidates of this sentence type? 217 | for (customCandidates = customElts; customCandidates != NULL && strcmp(customCandidates->sentenceName, term) < 0; customCandidates = 218 | 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 <= 276 | curTermNumber; p = p->next) 277 | if (p->termNumber == curTermNumber) 278 | p->set(term); 279 | 280 | return false; 281 | } 282 | 283 | /* static */ 284 | double TinyGPSPlus::distanceBetween(double lat1, double long1, double lat2, double long2) 285 | { 286 | // returns distance in meters between two positions, both specified 287 | // as signed decimal-degrees latitude and longitude. Uses great-circle 288 | // distance computation for hypothetical sphere of radius 6372795 meters. 289 | // Because Earth is no exact sphere, rounding errors may be up to 0.5%. 290 | // Courtesy of Maarten Lamers 291 | double delta = radians(long1-long2); 292 | double sdlong = sin(delta); 293 | double cdlong = cos(delta); 294 | lat1 = radians(lat1); 295 | lat2 = radians(lat2); 296 | double slat1 = sin(lat1); 297 | double clat1 = cos(lat1); 298 | double slat2 = sin(lat2); 299 | double clat2 = cos(lat2); 300 | delta = (clat1 * slat2) - (slat1 * clat2 * cdlong); 301 | delta = sq(delta); 302 | delta += sq(clat2 * sdlong); 303 | delta = sqrt(delta); 304 | double denom = (slat1 * slat2) + (clat1 * clat2 * cdlong); 305 | delta = atan2(delta, denom); 306 | return delta * 6372795; 307 | } 308 | 309 | double TinyGPSPlus::courseTo(double lat1, double long1, double lat2, double long2) 310 | { 311 | // returns course in degrees (North=0, West=270) from position 1 to position 2, 312 | // both specified as signed decimal-degrees latitude and longitude. 313 | // Because Earth is no exact sphere, calculated course may be off by a tiny fraction. 314 | // Courtesy of Maarten Lamers 315 | double dlon = radians(long2-long1); 316 | lat1 = radians(lat1); 317 | lat2 = radians(lat2); 318 | double a1 = sin(dlon) * cos(lat2); 319 | double a2 = sin(lat1) * cos(lat2) * cos(dlon); 320 | a2 = cos(lat1) * sin(lat2) - a2; 321 | a2 = atan2(a1, a2); 322 | if (a2 < 0.0) 323 | { 324 | a2 += TWO_PI; 325 | } 326 | return degrees(a2); 327 | } 328 | 329 | const char *TinyGPSPlus::cardinal(double course) 330 | { 331 | static const char* directions[] = {"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"}; 332 | int direction = (int)((course + 11.25f) / 22.5f); 333 | return directions[direction % 16]; 334 | } 335 | 336 | void TinyGPSLocation::commit() 337 | { 338 | rawLatData = rawNewLatData; 339 | rawLngData = rawNewLngData; 340 | lastCommitTime = millis(); 341 | valid = updated = true; 342 | } 343 | 344 | void TinyGPSLocation::setLatitude(const char *term) 345 | { 346 | TinyGPSPlus::parseDegrees(term, rawNewLatData); 347 | } 348 | 349 | void TinyGPSLocation::setLongitude(const char *term) 350 | { 351 | TinyGPSPlus::parseDegrees(term, rawNewLngData); 352 | } 353 | 354 | double TinyGPSLocation::lat() 355 | { 356 | updated = false; 357 | double ret = rawLatData.deg + rawLatData.billionths / 1000000000.0; 358 | return rawLatData.negative ? -ret : ret; 359 | } 360 | 361 | double TinyGPSLocation::lng() 362 | { 363 | updated = false; 364 | double ret = rawLngData.deg + rawLngData.billionths / 1000000000.0; 365 | return rawLngData.negative ? -ret : ret; 366 | } 367 | 368 | void TinyGPSDate::commit() 369 | { 370 | date = newDate; 371 | lastCommitTime = millis(); 372 | valid = updated = true; 373 | } 374 | 375 | void TinyGPSTime::commit() 376 | { 377 | time = newTime; 378 | lastCommitTime = millis(); 379 | valid = updated = true; 380 | } 381 | 382 | void TinyGPSTime::setTime(const char *term) 383 | { 384 | newTime = (uint32_t)TinyGPSPlus::parseDecimal(term); 385 | } 386 | 387 | void TinyGPSDate::setDate(const char *term) 388 | { 389 | newDate = atol(term); 390 | } 391 | 392 | uint16_t TinyGPSDate::year() 393 | { 394 | updated = false; 395 | uint16_t year = date % 100; 396 | return year + 2000; 397 | } 398 | 399 | uint8_t TinyGPSDate::month() 400 | { 401 | updated = false; 402 | return (date / 100) % 100; 403 | } 404 | 405 | uint8_t TinyGPSDate::day() 406 | { 407 | updated = false; 408 | return date / 10000; 409 | } 410 | 411 | uint8_t TinyGPSTime::hour() 412 | { 413 | updated = false; 414 | return time / 1000000; 415 | } 416 | 417 | uint8_t TinyGPSTime::minute() 418 | { 419 | updated = false; 420 | return (time / 10000) % 100; 421 | } 422 | 423 | uint8_t TinyGPSTime::second() 424 | { 425 | updated = false; 426 | return (time / 100) % 100; 427 | } 428 | 429 | uint8_t TinyGPSTime::centisecond() 430 | { 431 | updated = false; 432 | return time % 100; 433 | } 434 | 435 | void TinyGPSDecimal::commit() 436 | { 437 | val = newval; 438 | lastCommitTime = millis(); 439 | valid = updated = true; 440 | } 441 | 442 | void TinyGPSDecimal::set(const char *term) 443 | { 444 | newval = TinyGPSPlus::parseDecimal(term); 445 | } 446 | 447 | void TinyGPSInteger::commit() 448 | { 449 | val = newval; 450 | lastCommitTime = millis(); 451 | valid = updated = true; 452 | } 453 | 454 | void TinyGPSInteger::set(const char *term) 455 | { 456 | newval = atol(term); 457 | } 458 | 459 | TinyGPSCustom::TinyGPSCustom(TinyGPSPlus &gps, const char *_sentenceName, int _termNumber) 460 | { 461 | begin(gps, _sentenceName, _termNumber); 462 | } 463 | 464 | void TinyGPSCustom::begin(TinyGPSPlus &gps, const char *_sentenceName, int _termNumber) 465 | { 466 | lastCommitTime = 0; 467 | updated = valid = false; 468 | sentenceName = _sentenceName; 469 | termNumber = _termNumber; 470 | memset(stagingBuffer, '\0', sizeof(stagingBuffer)); 471 | memset(buffer, '\0', sizeof(buffer)); 472 | 473 | // Insert this item into the GPS tree 474 | gps.insertCustom(this, _sentenceName, _termNumber); 475 | } 476 | 477 | void TinyGPSCustom::commit() 478 | { 479 | strcpy(this->buffer, this->stagingBuffer); 480 | lastCommitTime = millis(); 481 | valid = updated = true; 482 | } 483 | 484 | void TinyGPSCustom::set(const char *term) 485 | { 486 | strncpy(this->stagingBuffer, term, sizeof(this->stagingBuffer)); 487 | } 488 | 489 | void TinyGPSPlus::insertCustom(TinyGPSCustom *pElt, const char *sentenceName, int termNumber) 490 | { 491 | TinyGPSCustom **ppelt; 492 | 493 | for (ppelt = &this->customElts; *ppelt != NULL; ppelt = &(*ppelt)->next) 494 | { 495 | int cmp = strcmp(sentenceName, (*ppelt)->sentenceName); 496 | if (cmp < 0 || (cmp == 0 && termNumber < (*ppelt)->termNumber)) 497 | break; 498 | } 499 | 500 | pElt->next = *ppelt; 501 | *ppelt = pElt; 502 | } 503 | -------------------------------------------------------------------------------- /lib/OnBoardGPS/src/CubeCell_TinyGPS++.h: -------------------------------------------------------------------------------- 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 | #ifndef __TinyGPSPlus_h 25 | #define __TinyGPSPlus_h 26 | 27 | 28 | #include "Arduino.h" 29 | #include 30 | 31 | #define _GPS_VERSION "1.0.2" // software version of this library 32 | #define _GPS_MPH_PER_KNOT 1.15077945 33 | #define _GPS_MPS_PER_KNOT 0.51444444 34 | #define _GPS_KMPH_PER_KNOT 1.852 35 | #define _GPS_MILES_PER_METER 0.00062137112 36 | #define _GPS_KM_PER_METER 0.001 37 | #define _GPS_FEET_PER_METER 3.2808399 38 | #define _GPS_MAX_FIELD_SIZE 15 39 | 40 | struct RawDegrees 41 | { 42 | uint16_t deg; 43 | uint32_t billionths; 44 | bool negative; 45 | public: 46 | RawDegrees() : deg(0), billionths(0), negative(false) 47 | {} 48 | }; 49 | 50 | struct TinyGPSLocation 51 | { 52 | friend class TinyGPSPlus; 53 | public: 54 | bool isValid() const { return valid; } 55 | bool isUpdated() const { return updated; } 56 | uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } 57 | const RawDegrees &rawLat() { updated = false; return rawLatData; } 58 | const RawDegrees &rawLng() { updated = false; return rawLngData; } 59 | double lat(); 60 | double lng(); 61 | 62 | TinyGPSLocation() : valid(false), updated(false) 63 | {} 64 | 65 | private: 66 | bool valid, updated; 67 | RawDegrees rawLatData, rawLngData, rawNewLatData, rawNewLngData; 68 | uint32_t lastCommitTime; 69 | void commit(); 70 | void setLatitude(const char *term); 71 | void setLongitude(const char *term); 72 | }; 73 | 74 | struct TinyGPSDate 75 | { 76 | friend class TinyGPSPlus; 77 | public: 78 | bool isValid() const { return valid; } 79 | bool isUpdated() const { return updated; } 80 | uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } 81 | 82 | uint32_t value() { updated = false; return date; } 83 | uint16_t year(); 84 | uint8_t month(); 85 | uint8_t day(); 86 | 87 | TinyGPSDate() : valid(false), updated(false), date(0) 88 | {} 89 | 90 | private: 91 | bool valid, updated; 92 | uint32_t date, newDate; 93 | uint32_t lastCommitTime; 94 | void commit(); 95 | void setDate(const char *term); 96 | }; 97 | 98 | struct TinyGPSTime 99 | { 100 | friend class TinyGPSPlus; 101 | public: 102 | bool isValid() const { return valid; } 103 | bool isUpdated() const { return updated; } 104 | uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } 105 | 106 | uint32_t value() { updated = false; return time; } 107 | uint8_t hour(); 108 | uint8_t minute(); 109 | uint8_t second(); 110 | uint8_t centisecond(); 111 | 112 | TinyGPSTime() : valid(false), updated(false), time(0) 113 | {} 114 | 115 | private: 116 | bool valid, updated; 117 | uint32_t time, newTime; 118 | uint32_t lastCommitTime; 119 | void commit(); 120 | void setTime(const char *term); 121 | }; 122 | 123 | struct TinyGPSDecimal 124 | { 125 | friend class TinyGPSPlus; 126 | public: 127 | bool isValid() const { return valid; } 128 | bool isUpdated() const { return updated; } 129 | uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } 130 | int32_t value() { updated = false; return val; } 131 | 132 | TinyGPSDecimal() : valid(false), updated(false), val(0) 133 | {} 134 | 135 | private: 136 | bool valid, updated; 137 | uint32_t lastCommitTime; 138 | int32_t val, newval; 139 | void commit(); 140 | void set(const char *term); 141 | }; 142 | 143 | struct TinyGPSInteger 144 | { 145 | friend class TinyGPSPlus; 146 | public: 147 | bool isValid() const { return valid; } 148 | bool isUpdated() const { return updated; } 149 | uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } 150 | uint32_t value() { updated = false; return val; } 151 | 152 | TinyGPSInteger() : valid(false), updated(false), val(0) 153 | {} 154 | 155 | private: 156 | bool valid, updated; 157 | uint32_t lastCommitTime; 158 | uint32_t val, newval; 159 | void commit(); 160 | void set(const char *term); 161 | }; 162 | 163 | struct TinyGPSSpeed : TinyGPSDecimal 164 | { 165 | double knots() { return value() / 100.0; } 166 | double mph() { return _GPS_MPH_PER_KNOT * value() / 100.0; } 167 | double mps() { return _GPS_MPS_PER_KNOT * value() / 100.0; } 168 | double kmph() { return _GPS_KMPH_PER_KNOT * value() / 100.0; } 169 | }; 170 | 171 | struct TinyGPSCourse : public TinyGPSDecimal 172 | { 173 | double deg() { return value() / 100.0; } 174 | }; 175 | 176 | struct TinyGPSAltitude : TinyGPSDecimal 177 | { 178 | double meters() { return value() / 100.0; } 179 | double miles() { return _GPS_MILES_PER_METER * value() / 100.0; } 180 | double kilometers() { return _GPS_KM_PER_METER * value() / 100.0; } 181 | double feet() { return _GPS_FEET_PER_METER * value() / 100.0; } 182 | }; 183 | 184 | struct TinyGPSHDOP : TinyGPSDecimal 185 | { 186 | double hdop() { return value() / 100.0; } 187 | }; 188 | 189 | class TinyGPSPlus; 190 | class TinyGPSCustom 191 | { 192 | public: 193 | TinyGPSCustom() {}; 194 | TinyGPSCustom(TinyGPSPlus &gps, const char *sentenceName, int termNumber); 195 | void begin(TinyGPSPlus &gps, const char *_sentenceName, int _termNumber); 196 | 197 | bool isUpdated() const { return updated; } 198 | bool isValid() const { return valid; } 199 | uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } 200 | const char *value() { updated = false; return buffer; } 201 | 202 | private: 203 | void commit(); 204 | void set(const char *term); 205 | 206 | char stagingBuffer[_GPS_MAX_FIELD_SIZE + 1]; 207 | char buffer[_GPS_MAX_FIELD_SIZE + 1]; 208 | unsigned long lastCommitTime; 209 | bool valid, updated; 210 | const char *sentenceName; 211 | int termNumber; 212 | friend class TinyGPSPlus; 213 | TinyGPSCustom *next; 214 | }; 215 | 216 | class TinyGPSPlus 217 | { 218 | public: 219 | TinyGPSPlus(); 220 | bool encode(char c); // process one character received from GPS 221 | TinyGPSPlus &operator << (char c) {encode(c); return *this;} 222 | 223 | TinyGPSLocation location; 224 | TinyGPSDate date; 225 | TinyGPSTime time; 226 | TinyGPSSpeed speed; 227 | TinyGPSCourse course; 228 | TinyGPSAltitude altitude; 229 | TinyGPSInteger satellites; 230 | TinyGPSHDOP hdop; 231 | 232 | static const char *libraryVersion() { return _GPS_VERSION; } 233 | 234 | static double distanceBetween(double lat1, double long1, double lat2, double long2); 235 | static double courseTo(double lat1, double long1, double lat2, double long2); 236 | static const char *cardinal(double course); 237 | 238 | static int32_t parseDecimal(const char *term); 239 | static void parseDegrees(const char *term, RawDegrees °); 240 | 241 | uint32_t charsProcessed() const { return encodedCharCount; } 242 | uint32_t sentencesWithFix() const { return sentencesWithFixCount; } 243 | uint32_t failedChecksum() const { return failedChecksumCount; } 244 | uint32_t passedChecksum() const { return passedChecksumCount; } 245 | 246 | private: 247 | enum {GPS_SENTENCE_GPGGA, GPS_SENTENCE_GPRMC, GPS_SENTENCE_OTHER}; 248 | 249 | // parsing state variables 250 | uint8_t parity; 251 | bool isChecksumTerm; 252 | char term[_GPS_MAX_FIELD_SIZE]; 253 | uint8_t curSentenceType; 254 | uint8_t curTermNumber; 255 | uint8_t curTermOffset; 256 | bool sentenceHasFix; 257 | 258 | // custom element support 259 | friend class TinyGPSCustom; 260 | TinyGPSCustom *customElts; 261 | TinyGPSCustom *customCandidates; 262 | void insertCustom(TinyGPSCustom *pElt, const char *sentenceName, int index); 263 | 264 | // statistics 265 | uint32_t encodedCharCount; 266 | uint32_t sentencesWithFixCount; 267 | uint32_t failedChecksumCount; 268 | uint32_t passedChecksumCount; 269 | 270 | // internal utilities 271 | int fromHex(char a); 272 | bool endOfTermHandler(); 273 | }; 274 | 275 | #endif // def(__TinyGPSPlus_h) 276 | -------------------------------------------------------------------------------- /lib/OnBoardGPS/src/GPS_Air530.cpp: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include "GPS_Air530.h" 3 | 4 | static String calchecksum(String cmd) 5 | { 6 | uint8_t checksum=cmd[1]; 7 | char temp[5]; 8 | for(int i=2;i=999) 173 | { 174 | pulse_width = 998; 175 | } 176 | memset(temp,0,15); 177 | sprintf(temp, "%d,%d,%d", mode,pulse_width,1000); 178 | cmd += temp; 179 | 180 | cmd = calchecksum(cmd); 181 | sendcmd(cmd); 182 | } 183 | 184 | void Air530Class::end() 185 | { 186 | digitalWrite(_powerCtl, HIGH); 187 | GPSSerial.end(); 188 | } 189 | 190 | void Air530Class::sendcmd(String cmd) 191 | { 192 | 193 | while(GPSSerial.available())//wait for gps serial idel 194 | { 195 | GPSSerial.readStringUntil('\r'); 196 | } 197 | GPSSerial.print(cmd); 198 | } 199 | 200 | String Air530Class::getAll() 201 | { 202 | String nmea = ""; 203 | uint32_t starttime = millis(); 204 | while(millis() - starttime <1000) 205 | { 206 | if ( GPSSerial.available()) 207 | { 208 | while(GPSSerial.available()) 209 | { 210 | nmea+=(char)GPSSerial.read(); 211 | int n = 0; 212 | while(n<=1000) 213 | { 214 | if(GPSSerial.available()) 215 | break; 216 | delayMicroseconds(1); 217 | n++; 218 | } 219 | } 220 | return nmea; 221 | } 222 | } 223 | return "0"; 224 | } 225 | 226 | String Air530Class::getNMEA() 227 | { 228 | uint32_t starttime = millis(); 229 | String nmea = ""; 230 | char buff[128]; 231 | int index = 0; 232 | while(millis() - starttime <1000) 233 | { 234 | if(GPSSerial.available()) 235 | { 236 | char c = GPSSerial.read(); 237 | if(c=='$') 238 | { 239 | nmea += c; 240 | index = GPSSerial.readBytesUntil('\r',buff,127); 241 | buff[index]=0; 242 | if(buff[index-3]!='*') 243 | return "0"; 244 | 245 | nmea += (String)buff; 246 | return nmea; 247 | } 248 | } 249 | } 250 | return "0"; 251 | } 252 | 253 | String Air530Class::getRMC() 254 | { 255 | String nmea = ""; 256 | String res=""; 257 | uint32_t starttime = millis(); 258 | while(millis() - starttime <1000) 259 | { 260 | if ( GPSSerial.available()) 261 | { 262 | char c = GPSSerial.read(); 263 | if(c=='$') 264 | { 265 | nmea = GPSSerial.readStringUntil('\r'); 266 | if(nmea[2] == 'R' && nmea[3] == 'M' && nmea[4] == 'C') 267 | { 268 | res = res + c + nmea + "\r\n"; 269 | } 270 | } 271 | } 272 | } 273 | if(res!="") 274 | return res; 275 | return "0"; 276 | } 277 | 278 | 279 | String Air530Class::getGGA() 280 | { 281 | String nmea = ""; 282 | String res=""; 283 | uint32_t starttime = millis(); 284 | while(millis() - starttime <1000) 285 | { 286 | if ( GPSSerial.available()) 287 | { 288 | char c = GPSSerial.read(); 289 | if(c=='$') 290 | { 291 | nmea = GPSSerial.readStringUntil('\r'); 292 | if(nmea[2] == 'G' && nmea[3] == 'G' && nmea[4] == 'A') 293 | { 294 | res = res + c + nmea + "\r\n"; 295 | } 296 | } 297 | } 298 | } 299 | if(res!="") 300 | return res; 301 | return "0"; 302 | } 303 | 304 | String Air530Class::getVTG() 305 | { 306 | String nmea = ""; 307 | String res=""; 308 | uint32_t starttime = millis(); 309 | while(millis() - starttime <1000) 310 | { 311 | if ( GPSSerial.available()) 312 | { 313 | char c = GPSSerial.read(); 314 | if(c=='$') 315 | { 316 | nmea = GPSSerial.readStringUntil('\r'); 317 | if(nmea[2] == 'V' && nmea[3] == 'T' && nmea[4] == 'G') 318 | { 319 | res = res + c + nmea + "\r\n"; 320 | } 321 | } 322 | } 323 | } 324 | if(res!="") 325 | return res; 326 | return "0"; 327 | } 328 | 329 | String Air530Class::getGSA() 330 | { 331 | String nmea = ""; 332 | String res=""; 333 | uint32_t starttime = millis(); 334 | while(millis() - starttime <1000) 335 | { 336 | if ( GPSSerial.available()) 337 | { 338 | char c = GPSSerial.read(); 339 | if(c=='$') 340 | { 341 | nmea = GPSSerial.readStringUntil('\r'); 342 | if(nmea[2] == 'G' && nmea[3] == 'S' && nmea[4] == 'A') 343 | { 344 | res = res + c + nmea + "\r\n"; 345 | } 346 | } 347 | } 348 | } 349 | if(res!="") 350 | return res; 351 | return "0"; 352 | } 353 | 354 | 355 | String Air530Class::getGSV() 356 | { 357 | String nmea = ""; 358 | String res=""; 359 | uint32_t starttime = millis(); 360 | while(millis() - starttime <1000) 361 | { 362 | if ( GPSSerial.available()) 363 | { 364 | char c = GPSSerial.read(); 365 | if(c=='$') 366 | { 367 | nmea = GPSSerial.readStringUntil('\r'); 368 | if(nmea[2] == 'G' && nmea[3] == 'S' && nmea[4] == 'V') 369 | { 370 | res = res + c + nmea + "\r\n"; 371 | } 372 | } 373 | } 374 | } 375 | if(res!="") 376 | return res; 377 | return "0"; 378 | } 379 | 380 | String Air530Class::getGLL() 381 | { 382 | String nmea = ""; 383 | String res=""; 384 | uint32_t starttime = millis(); 385 | while(millis() - starttime <1000) 386 | { 387 | if ( GPSSerial.available()) 388 | { 389 | char c = GPSSerial.read(); 390 | if(c=='$') 391 | { 392 | nmea = GPSSerial.readStringUntil('\r'); 393 | if(nmea[2] == 'G' && nmea[3] == 'L' && nmea[4] == 'L') 394 | { 395 | res = res + c + nmea + "\r\n"; 396 | } 397 | } 398 | } 399 | } 400 | if(res!="") 401 | return res; 402 | return "0"; 403 | } 404 | 405 | 406 | /* 407 | * WGS-84: international standard GPS coordinate��Google Earth�� GPS module��Tian Map�� 408 | * GCJ-02: China coordinate migration standard, Google Map��Gaode Map��Tencent map 409 | * BD-09: Baidu coordinate offset standard, Baidu Map 410 | */ 411 | 412 | //WGS-84 to GCJ-02 413 | /* 414 | gps_status_t Air530Class::WGSToGCJ(gps_status_t status) { 415 | if (outOfChina(status.latitude, status.longitude)) { 416 | return status; 417 | } 418 | double dLat = transformLat(status.longitude - 105.0, status.latitude - 35.0); 419 | double dLon = transformLon(status.longitude - 105.0, status.latitude - 35.0); 420 | double radLat = status.latitude / 180.0 * gps_pi; 421 | double magic = sin(radLat); 422 | magic = 1 - gps_ee * magic * magic; 423 | double sqrtMagic = sqrt(magic); 424 | dLat = (dLat * 180.0) / ((gps_a * (1 - gps_ee)) / (magic * sqrtMagic) * gps_pi); 425 | dLon = (dLon * 180.0) / (gps_a / sqrtMagic * cos(radLat) * gps_pi); 426 | double lat = status.latitude + dLat; 427 | double lon = status.longitude + dLon; 428 | status.latitude = lat; 429 | status.longitude = lon; 430 | return status; 431 | }; 432 | 433 | gps_status_t Air530Class::GCJToBD(gps_status_t status){ 434 | double x = status.longitude; 435 | double y = status.latitude; 436 | double z = sqrt(x * x + y * y) + 0.00002 * sin(y * gps_x_pi); 437 | double theta = atan2(y, x) + 0.000003 * cos(x * gps_x_pi); 438 | double bd_lon = z * cos(theta) + 0.0065; 439 | double bd_lat = z * sin(theta) + 0.006; 440 | status.latitude=bd_lat; 441 | status.longitude=bd_lon; 442 | return status; 443 | }; 444 | 445 | gps_status_t Air530Class::WGSToBD(gps_status_t status) 446 | { 447 | status = WGSToGCJ(status); 448 | status = GCJToBD(status); 449 | return status; 450 | } 451 | */ 452 | 453 | //Air530Class Air530(GPIO14); 454 | 455 | 456 | -------------------------------------------------------------------------------- /lib/OnBoardGPS/src/GPS_Air530.h: -------------------------------------------------------------------------------- 1 | #ifndef GPS_Air530_H 2 | #define GPS_Air530_H 3 | 4 | #include "Arduino.h" 5 | #include 6 | #include "GPS_Trans.h" 7 | 8 | 9 | class Air530Class:public TinyGPSPlus{ 10 | public: 11 | Air530Class(uint8_t powerCtl=GPIO14); 12 | void begin(uint32_t baud = 9600); 13 | void reset(); 14 | void setmode(GPSMODE mode); 15 | void setPPS(uint8_t mode, uint16_t pulse_width = 500); 16 | void setNMEA(uint8_t nmeamode); 17 | void clear(); 18 | int available(void); 19 | int read(void); 20 | String getNMEA(); 21 | String getRMC(); 22 | String getGGA(); 23 | String getVTG(); 24 | String getGSV(); 25 | String getGSA(); 26 | String getGLL(); 27 | String getAll(); 28 | /* gps_status_t WGSToGCJ(gps_status_t status); 29 | gps_status_t GCJToBD(gps_status_t status); 30 | gps_status_t WGSToBD(gps_status_t status);*/ 31 | void sendcmd(String cmd); 32 | void end(); 33 | 34 | private: 35 | uint8_t _powerCtl; 36 | uint32_t _baud; 37 | }; 38 | 39 | //extern Air530Class Air530; 40 | 41 | #endif 42 | 43 | -------------------------------------------------------------------------------- /lib/OnBoardGPS/src/GPS_Air530Z.cpp: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include "GPS_Air530Z.h" 3 | 4 | static String calchecksum(String cmd) 5 | { 6 | uint8_t checksum=cmd[1]; 7 | char temp[5]; 8 | for(int i=2;i5000) 75 | { 76 | GPSSerial.updateBaudRate(bauds[i]); 77 | _baud = bauds[i]; 78 | Serial.printf("GPS baudrate updated failed, use GPS baudrate %d\r\n",_baud); 79 | } 80 | else 81 | { 82 | _baud = baud; 83 | Serial.print("GPS baudrate updated to "); 84 | Serial.println(baud); 85 | } 86 | 87 | delay(10); 88 | setmode(MODE_GPS_BEIDOU_GLONASS); 89 | delay(10); 90 | setNMEA(NMEA_GSV|NMEA_GGA|NMEA_GSA|NMEA_RMC|NMEA_VTG|NMEA_GLL); 91 | delay(10); 92 | } 93 | 94 | 95 | void Air530ZClass::setmode(GPSMODE mode) 96 | { 97 | String cmd="$PCAS04,7"; 98 | switch(mode) 99 | { 100 | case MODE_GPS_BEIDOU: 101 | cmd[8] = '3'; 102 | break; 103 | case MODE_GPS_GLONASS: 104 | cmd[8] = '5'; 105 | break; 106 | case MODE_GPS_BEIDOU_GLONASS: 107 | cmd[8] = '7'; 108 | break; 109 | case MODE_GPS: 110 | cmd[8] = '1'; 111 | break; 112 | case MODE_BEIDOU: 113 | cmd[8] = '2'; 114 | break; 115 | case MODE_GLONASS: 116 | cmd[8] = '4'; 117 | break; 118 | default: 119 | cmd[8] = '7'; 120 | break; 121 | } 122 | cmd = calchecksum(cmd); 123 | sendcmd(cmd); 124 | } 125 | 126 | void Air530ZClass::setNMEA(uint8_t nmeamode) 127 | { 128 | String cmd = "$PCAS03,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"; 129 | if(nmeamode & NMEA_GGA) 130 | { 131 | cmd[8] = '1'; 132 | } 133 | if(nmeamode & NMEA_GLL) 134 | { 135 | cmd[10] = '1'; 136 | } 137 | if(nmeamode & NMEA_GSA) 138 | { 139 | cmd[12] = '1'; 140 | } 141 | if(nmeamode & NMEA_GSV) 142 | { 143 | cmd[14] = '1'; 144 | } 145 | if(nmeamode & NMEA_RMC) 146 | { 147 | cmd[16] = '1'; 148 | } 149 | if(nmeamode & NMEA_VTG) 150 | { 151 | cmd[18] = '1'; 152 | } 153 | if(nmeamode & NMEA_GST) 154 | { 155 | cmd[36] = '1'; 156 | } 157 | 158 | cmd = calchecksum(cmd); 159 | 160 | sendcmd(cmd); 161 | } 162 | 163 | void Air530ZClass::setBaud(uint32_t baud) 164 | { 165 | String cmd = "$PCAS01,1"; 166 | switch(baud) 167 | { 168 | case 9600: 169 | cmd[8] = '1'; 170 | break; 171 | case 19200: 172 | cmd[8] = '2'; 173 | break; 174 | case 38400: 175 | cmd[8] = '3'; 176 | break; 177 | case 57600: 178 | cmd[8] = '4'; 179 | break; 180 | case 115200: 181 | cmd[8] = '5'; 182 | break; 183 | default: 184 | cmd[8] = '1'; 185 | break; 186 | } 187 | cmd = calchecksum(cmd); 188 | sendcmd(cmd); 189 | } 190 | 191 | int Air530ZClass::available(void) 192 | { 193 | return GPSSerial.available(); 194 | } 195 | 196 | int Air530ZClass::read(void) 197 | { 198 | return GPSSerial.read(); 199 | } 200 | 201 | 202 | void Air530ZClass::end() 203 | { 204 | digitalWrite(_powerCtl, HIGH); 205 | GPSSerial.end(); 206 | } 207 | 208 | void Air530ZClass::sendcmd(String cmd) 209 | { 210 | 211 | while(GPSSerial.available())//wait for gps serial idel 212 | { 213 | GPSSerial.readStringUntil('\r'); 214 | } 215 | GPSSerial.print(cmd); 216 | } 217 | 218 | String Air530ZClass::getAll() 219 | { 220 | String nmea = ""; 221 | uint32_t starttime = millis(); 222 | while(millis() - starttime <1000) 223 | { 224 | if ( GPSSerial.available()) 225 | { 226 | while(GPSSerial.available()) 227 | { 228 | nmea+=(char)GPSSerial.read(); 229 | int n = 0; 230 | while(n<=1000) 231 | { 232 | if(GPSSerial.available()) 233 | break; 234 | delayMicroseconds(1); 235 | n++; 236 | } 237 | } 238 | return nmea; 239 | } 240 | } 241 | return "0"; 242 | } 243 | 244 | String Air530ZClass::getNMEA() 245 | { 246 | uint32_t starttime = millis(); 247 | String nmea = ""; 248 | char buff[128]; 249 | int index = 0; 250 | while(millis() - starttime <1000) 251 | { 252 | if(GPSSerial.available()) 253 | { 254 | char c = GPSSerial.read(); 255 | if(c=='$') 256 | { 257 | nmea += c; 258 | index = GPSSerial.readBytesUntil('\r',buff,127); 259 | buff[index]=0; 260 | if(buff[index-3]!='*') 261 | return "0"; 262 | 263 | nmea += (String)buff; 264 | return nmea; 265 | } 266 | } 267 | } 268 | return "0"; 269 | } 270 | 271 | String Air530ZClass::getRMC() 272 | { 273 | String nmea = ""; 274 | String res=""; 275 | uint32_t starttime = millis(); 276 | while(millis() - starttime <1000) 277 | { 278 | if ( GPSSerial.available()) 279 | { 280 | char c = GPSSerial.read(); 281 | if(c=='$') 282 | { 283 | nmea = GPSSerial.readStringUntil('\r'); 284 | if(nmea[2] == 'R' && nmea[3] == 'M' && nmea[4] == 'C') 285 | { 286 | res = res + c + nmea + "\r\n"; 287 | } 288 | } 289 | } 290 | } 291 | if(res!="") 292 | return res; 293 | return "0"; 294 | } 295 | 296 | 297 | String Air530ZClass::getGGA() 298 | { 299 | String nmea = ""; 300 | String res=""; 301 | uint32_t starttime = millis(); 302 | while(millis() - starttime <1000) 303 | { 304 | if ( GPSSerial.available()) 305 | { 306 | char c = GPSSerial.read(); 307 | if(c=='$') 308 | { 309 | nmea = GPSSerial.readStringUntil('\r'); 310 | if(nmea[2] == 'G' && nmea[3] == 'G' && nmea[4] == 'A') 311 | { 312 | res = res + c + nmea + "\r\n"; 313 | } 314 | } 315 | } 316 | } 317 | if(res!="") 318 | return res; 319 | return "0"; 320 | } 321 | 322 | String Air530ZClass::getVTG() 323 | { 324 | String nmea = ""; 325 | String res=""; 326 | uint32_t starttime = millis(); 327 | while(millis() - starttime <1000) 328 | { 329 | if ( GPSSerial.available()) 330 | { 331 | char c = GPSSerial.read(); 332 | if(c=='$') 333 | { 334 | nmea = GPSSerial.readStringUntil('\r'); 335 | if(nmea[2] == 'V' && nmea[3] == 'T' && nmea[4] == 'G') 336 | { 337 | res = res + c + nmea + "\r\n"; 338 | } 339 | } 340 | } 341 | } 342 | if(res!="") 343 | return res; 344 | return "0"; 345 | } 346 | 347 | String Air530ZClass::getGSA() 348 | { 349 | String nmea = ""; 350 | String res=""; 351 | uint32_t starttime = millis(); 352 | while(millis() - starttime <1000) 353 | { 354 | if ( GPSSerial.available()) 355 | { 356 | char c = GPSSerial.read(); 357 | if(c=='$') 358 | { 359 | nmea = GPSSerial.readStringUntil('\r'); 360 | if(nmea[2] == 'G' && nmea[3] == 'S' && nmea[4] == 'A') 361 | { 362 | res = res + c + nmea + "\r\n"; 363 | } 364 | } 365 | } 366 | } 367 | if(res!="") 368 | return res; 369 | return "0"; 370 | } 371 | 372 | 373 | String Air530ZClass::getGSV() 374 | { 375 | String nmea = ""; 376 | String res=""; 377 | uint32_t starttime = millis(); 378 | while(millis() - starttime <1000) 379 | { 380 | if ( GPSSerial.available()) 381 | { 382 | char c = GPSSerial.read(); 383 | if(c=='$') 384 | { 385 | nmea = GPSSerial.readStringUntil('\r'); 386 | if(nmea[2] == 'G' && nmea[3] == 'S' && nmea[4] == 'V') 387 | { 388 | res = res + c + nmea + "\r\n"; 389 | } 390 | } 391 | } 392 | } 393 | if(res!="") 394 | return res; 395 | return "0"; 396 | } 397 | 398 | String Air530ZClass::getGLL() 399 | { 400 | String nmea = ""; 401 | String res=""; 402 | uint32_t starttime = millis(); 403 | while(millis() - starttime <1000) 404 | { 405 | if ( GPSSerial.available()) 406 | { 407 | char c = GPSSerial.read(); 408 | if(c=='$') 409 | { 410 | nmea = GPSSerial.readStringUntil('\r'); 411 | if(nmea[2] == 'G' && nmea[3] == 'L' && nmea[4] == 'L') 412 | { 413 | res = res + c + nmea + "\r\n"; 414 | } 415 | } 416 | } 417 | } 418 | if(res!="") 419 | return res; 420 | return "0"; 421 | } 422 | 423 | -------------------------------------------------------------------------------- /lib/OnBoardGPS/src/GPS_Air530Z.h: -------------------------------------------------------------------------------- 1 | #ifndef GPS_Air530Z_H 2 | #define GPS_Air530Z_H 3 | 4 | #include "Arduino.h" 5 | #include 6 | #include "GPS_Trans.h" 7 | 8 | class Air530ZClass:public TinyGPSPlus{ 9 | public: 10 | Air530ZClass(uint8_t powerCtl=GPIO14,uint8_t modePin=GPIO11); 11 | void begin(uint32_t baud = 9600); 12 | void setmode(GPSMODE mode); 13 | void setNMEA(uint8_t nmeamode); 14 | int available(void); 15 | void setBaud(uint32_t baud); 16 | int read(void); 17 | String getNMEA(); 18 | String getRMC(); 19 | String getGGA(); 20 | String getVTG(); 21 | String getGSV(); 22 | String getGSA(); 23 | String getGLL(); 24 | String getAll(); 25 | /* gps_status_t WGSToGCJ(gps_status_t status); 26 | gps_status_t GCJToBD(gps_status_t status); 27 | gps_status_t WGSToBD(gps_status_t status);*/ 28 | void sendcmd(String cmd); 29 | void end(); 30 | 31 | private: 32 | uint8_t _powerCtl; 33 | uint32_t _baud; 34 | uint8_t _modePin; 35 | }; 36 | 37 | //extern Air530ZClass Air530; 38 | 39 | #endif 40 | 41 | -------------------------------------------------------------------------------- /lib/OnBoardGPS/src/GPS_Trans.cpp: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include "GPS_Trans.h" 3 | HardwareSerial GPSSerial(UART_NUM_1); 4 | 5 | double gps_x_pi = 3.14159265358979324 * 3000.0 / 180.0; 6 | double gps_pi = 3.14159265358979324; 7 | double gps_a = 6378245.0; 8 | double gps_ee = 0.00669342162296594323; 9 | 10 | uint32_t bauds[]={9600,19200,38400,57600,115200,230400,460800,921600}; 11 | uint32_t bauds_array = sizeof(bauds) / sizeof(bauds[0]); 12 | 13 | double transformLon(double x, double y) { 14 | double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(abs(x)); 15 | ret += (20.0 * sin(6.0 * x * gps_pi) + 20.0 * sin(2.0 * x * gps_pi)) * 2.0 / 3.0; 16 | ret += (20.0 * sin(x * gps_pi) + 40.0 * sin(x / 3.0 * gps_pi)) * 2.0 / 3.0; 17 | ret += (150.0 * sin(x / 12.0 * gps_pi) + 300.0 * sin(x / 30.0 * gps_pi)) * 2.0 / 3.0; 18 | return ret; 19 | }; 20 | double transformLat(double x, double y) { 21 | double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(abs(x)); 22 | ret += (20.0 * sin(6.0 * x * gps_pi) + 20.0 * sin(2.0 * x * gps_pi)) * 2.0 / 3.0; 23 | ret += (20.0 * sin(y * gps_pi) + 40.0 * sin(y / 3.0 * gps_pi)) * 2.0 / 3.0; 24 | ret += (160.0 * sin(y / 12.0 * gps_pi) + 320 * sin(y * gps_pi / 30.0)) * 2.0 / 3.0; 25 | return ret; 26 | } 27 | 28 | double outOfChina(double lat, double lon) { 29 | if (lon < 72.004 || lon > 137.8347) 30 | return true; 31 | if (lat < 0.8293 || lat > 55.8271) 32 | return true; 33 | return false; 34 | } 35 | 36 | int str_chop(char *s, int buff_size, char separator, uint8_t *idx_ary, int max_idx) { 37 | int i = 0; /* index in the string */ 38 | int j = 0; /* index in the result array */ 39 | 40 | if ((s == NULL) || (buff_size < 0) || (separator == 0) || (idx_ary == NULL) || (max_idx < 0)) { 41 | /* unsafe to do anything */ 42 | return -1; 43 | } 44 | if ((buff_size == 0) || (max_idx == 0)) { 45 | /* nothing to do */ 46 | return 0; 47 | } 48 | s[buff_size - 1] = 0; /* add string terminator at the end of the buffer, just to be sure */ 49 | idx_ary[j] = 0; 50 | j += 1; 51 | /* loop until string terminator is reached */ 52 | while (s[i] != 0) { 53 | if (s[i] == separator) { 54 | s[i] = 0; /* replace separator by string terminator */ 55 | if (j >= max_idx) { /* no more room in the index array */ 56 | return j; 57 | } 58 | idx_ary[j] = i+1; /* next token start after replaced separator */ 59 | ++j; 60 | } 61 | ++i; 62 | } 63 | return j; 64 | } 65 | 66 | 67 | -------------------------------------------------------------------------------- /lib/OnBoardGPS/src/GPS_Trans.h: -------------------------------------------------------------------------------- 1 | #ifndef GPS_TRANS_H 2 | #define GPS_TRANS_H 3 | 4 | 5 | #include "Arduino.h" 6 | #include 7 | 8 | typedef enum 9 | { 10 | MODE_GPS_BEIDOU=0, 11 | MODE_GPS_GLONASS, 12 | MODE_GPS_GALILEO_BEIDOU, 13 | MODE_GPS_GALILEO_GLONASS, 14 | MODE_GPS_BEIDOU_GLONASS, 15 | MODE_GPS, 16 | MODE_BEIDOU, 17 | MODE_GLONASS, 18 | }GPSMODE; 19 | 20 | #define NMEA_GGA 0x01 21 | #define NMEA_GLL 0x02 22 | #define NMEA_GSA 0x04 23 | #define NMEA_GSV 0x08 24 | #define NMEA_RMC 0x10 25 | #define NMEA_VTG 0x20 26 | #define NMEA_GRS 0x40 27 | #define NMEA_GST 0x80 28 | 29 | extern double gps_x_pi; 30 | extern double gps_pi; 31 | extern double gps_a; 32 | extern double gps_ee; 33 | extern uint32_t bauds[]; 34 | extern uint32_t bauds_array; 35 | 36 | int str_chop(char *s, int buff_size, char separator, uint8_t *idx_ary, int max_idx); 37 | double transformLon(double x, double y); 38 | double transformLat(double x, double y); 39 | double outOfChina(double lat, double lon); 40 | extern HardwareSerial GPSSerial; 41 | 42 | #endif 43 | 44 | -------------------------------------------------------------------------------- /lib/OnBoardGPS/src/GPS_Ublox_M8M.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitconnector/ttn_mapper_t-beam/d26e5dd866c7408de6e04b0f913378d7d69ec38f/lib/OnBoardGPS/src/GPS_Ublox_M8M.cpp -------------------------------------------------------------------------------- /lib/OnBoardGPS/src/GPS_Ublox_M8M.h: -------------------------------------------------------------------------------- 1 | #ifndef GPS_M8M_H 2 | #define GPS_M8M_H 3 | 4 | #include "Arduino.h" 5 | #include 6 | #include "GPS_Trans.h" 7 | 8 | 9 | #define NMEA_GGA 0x01 10 | #define NMEA_GLL 0x02 11 | #define NMEA_GSA 0x04 12 | #define NMEA_GSV 0x08 13 | #define NMEA_RMC 0x10 14 | #define NMEA_VTG 0x20 15 | 16 | 17 | class M8MClass:public TinyGPSPlus{ 18 | public: 19 | M8MClass(int8_t powerCtl); 20 | void begin(uint32_t baud = 9600); 21 | void reset(); 22 | void setmode(GPSMODE mode); 23 | void setPPS(uint8_t mode, uint16_t pulse_width = 500); 24 | void setNMEA(uint8_t nmeamode); 25 | void setVersion(); 26 | void clear(); 27 | int available(void); 28 | int read(void); 29 | String getNMEA(); 30 | String getRMC(); 31 | String getGGA(); 32 | String getVTG(); 33 | String getGSV(); 34 | String getGSA(); 35 | String getGLL(); 36 | String getAll(); 37 | /* gps_status_t WGSToGCJ(gps_status_t status); 38 | gps_status_t GCJToBD(gps_status_t status); 39 | gps_status_t WGSToBD(gps_status_t status);*/ 40 | void sendcmd(uint8_t * cmd,int size); 41 | void end(); 42 | 43 | private: 44 | uint8_t _powerCtl; 45 | uint32_t _baud; 46 | }; 47 | 48 | extern M8MClass M8M; 49 | 50 | #endif 51 | 52 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /payload_formatter_v3.js: -------------------------------------------------------------------------------- 1 | function decodeUplink(input) { 2 | bat_min = 3.4 3 | bat_max = 4.16 4 | 5 | var ptr = 0; 6 | var data = {}; 7 | 8 | if (input.bytes[ptr] === 255) { 9 | data.bat = bat_max; 10 | data.percentage = 100 11 | batState = "charging" 12 | } 13 | else { 14 | data.bat = input.bytes[ptr] 15 | data.bat += 250 16 | data.bat /= 100 17 | data.percentage = ((data.bat - bat_min) / (bat_max - bat_min)) * 100 | 0 18 | if (data.percentage > 100) data.percentage = 100; 19 | batState = "bat: " + data.percentage + "% " + data.bat + "V"; 20 | } 21 | ptr = ptr + 1; 22 | 23 | if (input.fPort === 1) { 24 | data.msg = "status: "; 25 | data.status = input.bytes[ptr] 26 | if (data.status === 1) data.msg += "button pressed" 27 | if (data.status === 2) data.msg += "startup" 28 | if (data.status === 3) data.msg += "enter sleep" 29 | if (data.status === 4) data.msg += "running" 30 | ptr = ptr + 1; 31 | 32 | data.msg += "\nGPS: " 33 | data.gps = input.bytes[ptr] 34 | if (data.gps === 0) data.msg += "no fix" 35 | if (data.gps === 1) data.msg += "ok" 36 | if (data.gps === 2) data.msg += "no movement" 37 | if (data.gps === 3) data.msg += "geofence" 38 | ptr = ptr + 1; 39 | } 40 | else if (input.fPort === 21) { 41 | data.msg = "location: "; 42 | data.lat = ((input.bytes[ptr] << 16) >>> 0) + ((input.bytes[ptr + 1] << 8) >>> 0) + input.bytes[ptr + 2]; 43 | data.lat = (data.lat / 16777215.0 * 180) - 90; 44 | data.lon = ((input.bytes[ptr + 3] << 16) >>> 0) + ((input.bytes[ptr + 4] << 8) >>> 0) + input.bytes[ptr + 5]; 45 | data.lon = (data.lon / 16777215.0 * 360) - 180; 46 | 47 | var altValue = ((input.bytes[ptr + 6] << 8) >>> 0) + input.bytes[ptr + 7]; 48 | var sign = input.bytes[ptr + 6] & (1 << 7); 49 | if (sign) { 50 | data.alt = 0xFFFF0000 | altValue; 51 | } 52 | else { 53 | data.alt = altValue; 54 | } 55 | data.hdop = input.bytes[ptr + 8] / 10.0; 56 | ptr = ptr + 9; 57 | 58 | data.msg += data.lat + " " + data.lon; 59 | } 60 | 61 | data.msg += "\n" + batState; 62 | 63 | return { data }; 64 | } 65 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | default_envs = esp32 13 | 14 | [env] 15 | monitor_speed = 115200 16 | lib_ldf_mode = chain+ 17 | 18 | 19 | [env:esp32] 20 | platform = espressif32 21 | board = ttgo-t-beam 22 | framework = arduino 23 | build_flags = 24 | -D ESP32 25 | -D SLEEP_VAR=RTC_DATA_ATTR 26 | -D AXP 27 | build_src_filter = 28 | + 29 | + 30 | + 31 | + 32 | + 33 | #+<*> -<.git/> -<.svn/> 34 | #- 35 | lib_deps = 36 | https://github.com/bitconnector/LittleWan#0.1.2 37 | sandeepmistry/LoRa@^0.8.0 38 | mikalhart/TinyGPSPlus @ ^1.0.2 39 | lewisxhe/XPowersLib @ ^0.2.0 40 | SPI 41 | Wire 42 | olikraus/U8g2 @ ^2.35.7 43 | 44 | 45 | [env:cubecell_gps] 46 | platform = asrmicro650x 47 | board = cubecell_gps 48 | framework = arduino 49 | ;board_build.f_cpu = 48000000L 50 | 51 | board_build.arduino.lorawan.region = EU868 52 | board_build.arduino.lorawan.adr = ON 53 | board_build.arduino.lorawan.debug_level = NONE 54 | ;board_build.arduino.lorawan.debug_level = FREQ_AND_DIO 55 | board_build.arduino.lorawan.rgb = OFF 56 | 57 | build_flags = 58 | -D CUBECELL 59 | -D SLEEP_VAR="" 60 | build_src_filter = 61 | + 62 | + 63 | + 64 | lib_deps = 65 | https://github.com/bitconnector/LittleWan#0.1.2 66 | mikalhart/TinyGPSPlus @ ^1.0.2 67 | SPI 68 | 69 | -------------------------------------------------------------------------------- /scripts/config.ini.example: -------------------------------------------------------------------------------- 1 | [MQTT] 2 | server = eu1.cloud.thethings.network 3 | port = 1883 4 | user = application@ttn 5 | pass = NNSXS.II75FDDP6SGYPEKQ6PRM4Y.JZKGBPDGGJZJXLBV2GVEVFSWAIUVL 6 | topic = # 7 | 8 | -------------------------------------------------------------------------------- /scripts/converter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import sys 4 | import json 5 | from datetime import datetime 6 | 7 | 8 | data = {} 9 | data["ver"] = 1 10 | 11 | 12 | def xls_to_touple(file): 13 | with open(file) as f: 14 | data["time"] = int(datetime.strptime( 15 | f.readline().split(",")[0], "%Y.%m.%d %H:%M:%S").timestamp()) 16 | tou = [] 17 | for line in f: 18 | d = line.replace(" ", "").replace("\n", "").split(",")[1:] 19 | print(tuple(d)) 20 | tou.append(tuple(d)) 21 | data["values"] = tou 22 | 23 | 24 | def list_to_touple(file): 25 | with open(file) as f: 26 | obj = json.loads(f.read()) 27 | data['time'] = obj['start'] 28 | tou = [] 29 | i = 0 30 | while i < len(obj["time"]): 31 | t = obj['time'][i] 32 | v = obj['bat'][i] 33 | p = obj['percentage'][i] 34 | s = obj['status'][i] 35 | g = obj['gps'][i] 36 | tou.append((t, v, p, s, g)) 37 | i = i+1 38 | data["values"] = tou 39 | 40 | 41 | xls_to_touple(sys.argv[1]) 42 | #list_to_touple(sys.argv[1]) 43 | 44 | 45 | print(data) 46 | 47 | 48 | try: 49 | new_file = input("save to: ") 50 | logfile = open(new_file, "w") 51 | json.dump(data, logfile) 52 | except: 53 | pass 54 | -------------------------------------------------------------------------------- /scripts/log_gps_status.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import paho.mqtt.client as mqtt 4 | import json 5 | from datetime import datetime 6 | import random 7 | import signal 8 | import sys 9 | import os 10 | import configparser 11 | 12 | CONFIGFILE = "config.ini" 13 | 14 | MQTT = None 15 | 16 | counter = 0 17 | initial_timestamp = 0 18 | a_time = [] 19 | a_bat = [] 20 | a_percentage = [] 21 | a_status = [] 22 | a_gps = [] 23 | 24 | 25 | def on_message(client, userdata, message): 26 | msg = str(message.payload.decode("utf-8")) 27 | obj = json.loads(msg) 28 | try: 29 | log_status(obj) 30 | except: 31 | pass 32 | 33 | 34 | def log_status(obj): 35 | global counter 36 | counter = counter+1 37 | 38 | message = obj["uplink_message"] 39 | payload = message["decoded_payload"] 40 | 41 | f_port = message["f_port"] 42 | if(f_port != 1): 43 | return 44 | 45 | device_id = obj["end_device_ids"]["device_id"] 46 | spreading_factor = message["settings"]["data_rate"]["lora"]["spreading_factor"] 47 | frequency = message["settings"]["frequency"] 48 | gw_cnt = len(message["rx_metadata"]) 49 | time = message["received_at"] 50 | timestamp = float(datetime.strptime( 51 | time[:26], '%Y-%m-%dT%H:%M:%S.%f').timestamp()) 52 | timenorm = datetime.fromtimestamp(timestamp).strftime("%Y.%m.%d %H:%M:%S") 53 | 54 | try: 55 | f_cnt = message["f_cnt"] 56 | except: 57 | f_cnt = 0 58 | 59 | bat = payload["bat"] 60 | percentage = payload["percentage"] 61 | status = payload["status"] 62 | gps = payload["gps"] 63 | 64 | global initial_timestamp 65 | if(initial_timestamp == 0): 66 | initial_timestamp = int(timestamp) 67 | 68 | timestamp_relative = int(timestamp)-initial_timestamp 69 | print(f'{timenorm},\t{timestamp_relative},\t{bat},\t{percentage},\t{status},\t{gps}') 70 | 71 | global a_time, a_bat, a_percentage, a_status, a_gps 72 | a_time.append(timestamp_relative) 73 | a_bat.append(bat) 74 | a_percentage.append(percentage) 75 | a_status.append(status) 76 | a_gps.append(gps) 77 | 78 | 79 | def signal_handler(sig, frame): 80 | print('You pressed Ctrl+C!') 81 | client.disconnect() 82 | print("disconnected MQTT") 83 | 84 | global initial_timestamp 85 | if(initial_timestamp == 0): 86 | sys.exit(0) 87 | 88 | date = datetime.fromtimestamp( 89 | initial_timestamp).strftime("%Y.%m.%d_%H:%M:%S.log") 90 | 91 | global a_time, a_bat, a_percentage 92 | data = {"start": initial_timestamp, "time": a_time, "bat": a_bat, 93 | "percentage": a_percentage, "status": a_status, "gps": a_gps} 94 | print(data) 95 | 96 | logfile = open(date, "w") 97 | json.dump(data, logfile) 98 | print("saved logfile: ", date) 99 | 100 | sys.exit(0) 101 | 102 | 103 | def on_connect(client, userdata, flags, rc): 104 | client.subscribe(MQTT["Topic"]) 105 | 106 | 107 | def config_gen(path): 108 | config = configparser.ConfigParser() 109 | config['MQTT'] = {'Server': 'eu1.cloud.thethings.network', 110 | 'Port': '1883', 111 | 'User': 'application@ttn', 112 | 'Pass': 'NNSXS.II75FDDP6SGYPEKQ6PRM4Y.JZKGBPDGGJZJXLBV2GVEVFSWAIUVL', 113 | 'Topic': '#' 114 | } 115 | with open(path, 'w') as configfile: 116 | config.write(configfile) 117 | 118 | 119 | def config_read(path): 120 | config = configparser.ConfigParser() 121 | config.read(path) 122 | global MQTT 123 | MQTT = config['MQTT'] 124 | 125 | 126 | if __name__ == "__main__": 127 | if not os.path.exists(CONFIGFILE): 128 | config_gen(CONFIGFILE) 129 | print("Config file generated. Please modify", CONFIGFILE) 130 | sys.exit(0) 131 | 132 | config_read(CONFIGFILE) 133 | 134 | client = mqtt.Client(f'python-mqtt-{random.randint(0, 1000)}') 135 | client.username_pw_set(MQTT["User"], MQTT["Pass"]) 136 | client.on_connect = on_connect 137 | client.on_message = on_message 138 | client.connect(MQTT["Server"], int(MQTT["Port"])) 139 | 140 | signal.signal(signal.SIGINT, signal_handler) 141 | 142 | print('timestamp,\t\ttime,\tbat,\tperc,\tstatus,\tgps') 143 | client.loop_forever() 144 | -------------------------------------------------------------------------------- /scripts/visualizer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import sys 4 | import json 5 | import matplotlib.pyplot as plt 6 | 7 | 8 | STARTINGVOLTAGE = 4.1 9 | COLOURS = ['blue', 'red', 'green', 'yellow'] 10 | 11 | logs = [] 12 | if(len(sys.argv) == 1): 13 | logs.append(input("path to log: ")) 14 | else: 15 | for path in sys.argv[1:]: 16 | logs.append(path) 17 | 18 | 19 | def plotData(data, color): 20 | a_time = data['time'] 21 | a_bat = data['bat'] 22 | a_perc = data['percentage'] 23 | a_status = data['status'] 24 | a_gps = data['gps'] 25 | 26 | idx_start = 0 27 | while idx_start < len(a_bat): 28 | if a_bat[idx_start] <= STARTINGVOLTAGE: 29 | break 30 | idx_start = idx_start+1 31 | 32 | print("start index:", idx_start) 33 | 34 | a_time_h = [(time-a_time[idx_start]) / 3600 for time in a_time] 35 | 36 | plt.plot(a_time_h[idx_start:], a_bat[idx_start:], 37 | label='Path', linewidth=2, color=color) 38 | 39 | 40 | def plotData2(data, color): 41 | idx_start = 0 42 | while idx_start < len(data["values"]): 43 | if float(data["values"][idx_start][1]) <= STARTINGVOLTAGE: 44 | break 45 | idx_start = idx_start+1 46 | 47 | print("start index:", idx_start) 48 | tou = [None, *data["values"][idx_start:], None] 49 | for prev, curr, nxt in zip(tou, tou[1:], tou[2:]): 50 | #print(prev, curr, nxt) 51 | if(prev): 52 | linex = [int(prev[0]) / 3600, int(curr[0]) / 3600] 53 | liney = [float(prev[1]), float(curr[1])] 54 | plt.plot(linex, liney, 55 | label='Path', linewidth=2, color=color) 56 | 57 | 58 | def openLogs(logs): 59 | for i in range(len(logs)): 60 | print(logs[i]) 61 | with open(logs[i]) as fp: 62 | data = json.loads(fp.read()) 63 | if not "ver" in data: 64 | plotData(data, COLOURS[i]) 65 | elif data["ver"] == 1: 66 | plotData2(data, COLOURS[i]) 67 | 68 | 69 | openLogs(logs) 70 | 71 | plt.title(str(logs)+" bat") 72 | plt.xlabel('time in h') 73 | plt.ylabel('bat in v') 74 | plt.ylim(3.2, 4.2) 75 | # plt.axis('equal') 76 | 77 | plt.show() 78 | -------------------------------------------------------------------------------- /src/config.hpp.example: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Hardware 4 | #ifdef ESP32 5 | #define LED 4 6 | #define ButtonPin 38 7 | 8 | #define LoRa_RST 23 9 | #define LoRa_CS 18 10 | #define LoRa_DIO0 26 11 | #define LoRa_DIO1 33 12 | #define LoRa_DIO2 32 13 | 14 | #define GPS_RX 34 15 | #define GPS_TX 12 16 | 17 | #define AXP_SDA 21 18 | #define AXP_SCL 22 19 | #define AXP_IRQ 35 20 | #endif 21 | #ifdef CUBECELL 22 | #define LED 13 23 | #define ButtonPin USER_KEY 24 | 25 | #define GPS_RX 34 26 | #define GPS_TX 12 27 | #endif 28 | 29 | #define LORAWAN_DEFAULT_SF 7 30 | #define GPS_INTERVAL 30 31 | #define GPS_MOVE_DIST 25 32 | #define STATUS_INTERVAL 10 33 | #define STATUS_SF 10 34 | #define JOIN_SF 10 35 | 36 | // TTS credentials(tts_v3) 37 | // https://console.cloud.thethings.network/ 38 | 39 | #define USE_OTAA 40 | 41 | #ifndef USE_OTAA 42 | // ABP 43 | static const char DEVADDR[] = "260B4F2E"; 44 | static const char NWKSKEY[] = "C8C2CE3ADE475B282995A5937B5CA437"; 45 | static const char APPSKEY[] = "4E6D2ADA888BF79B8C0CC6E14EF9A5B6"; 46 | #else 47 | 48 | // OTAA 49 | const char APPEUI[] = "0000000000000000"; 50 | static const char DEVEUI[] = "70B3D57ED0045DDA"; 51 | static const char APPKEY[] = "5340769C09F900173852BEB4DBAA8B59"; 52 | #endif 53 | 54 | // locations excludet for ttnmapper-upload 55 | struct Geofence 56 | { 57 | double lat; 58 | double lng; 59 | u_int radius; 60 | }; 61 | 62 | const struct Geofence geofence[] = { 63 | {52.01910, 8.53100, 10000}, // Bielefeld 64 | {52.51704, 13.38886, 15000}, // Berlin 65 | {48.77845, 9.18001, 5000} // Stuttgart 66 | }; 67 | -------------------------------------------------------------------------------- /src/cubecell.cpp: -------------------------------------------------------------------------------- 1 | // https://github.com/HelTecAutomation/CubeCell-Arduino/blob/master/libraries/Basics/examples/LowPower/LowPower_WakeUpByGPIO/LowPower_WakeUpByGPIO.ino 2 | #include "LoRaWan_APP.h" 3 | #include "Arduino.h" 4 | 5 | #define CUBECELL 6 | 7 | #include "gps.hpp" 8 | #include "lorawan.hpp" 9 | #include "config.hpp" 10 | 11 | #include "CubeCell_NeoPixel.h" 12 | CubeCell_NeoPixel pixels(1, RGB, NEO_GRB + NEO_KHZ800); 13 | 14 | unsigned int TX_INTERVAL = GPS_INTERVAL; 15 | int wakeup_count = 0; 16 | 17 | void sendLocation(); 18 | void sendStatus(int state, int gps); 19 | 20 | static TimerEvent_t sleep; 21 | uint8_t lowpower = 0; 22 | void onWakeUp() { lowpower = 0; } 23 | 24 | static TimerEvent_t deepSleepTimer; 25 | bool deepSleepEnabled = 0; 26 | void onDeepSleepTimer() { deepSleepEnabled = 0; } 27 | void deepSleep(uint32_t duration) 28 | { 29 | TimerInit(&deepSleepTimer, onDeepSleepTimer); 30 | TimerSetValue(&deepSleepTimer, duration); 31 | TimerStart(&deepSleepTimer); 32 | deepSleepEnabled = 1; 33 | while (deepSleepEnabled) 34 | lowPowerHandler(); 35 | } 36 | 37 | void setup() 38 | { 39 | pinMode(Vext, OUTPUT); 40 | pinMode(GPIO14, OUTPUT); // VGPS 41 | pinMode(ButtonPin, INPUT); 42 | pixels.begin(); 43 | pixels.clear(); 44 | Serial.begin(115200); 45 | digitalWrite(Vext, LOW); // OLED and Neo ON 46 | 47 | lorawan_sleep(); 48 | attachInterrupt(ButtonPin, onWakeUp, FALLING); 49 | TimerInit(&sleep, onWakeUp); 50 | 51 | Serial.printf("Startup\n"); 52 | deepSleep(3000); 53 | Serial.flush(); 54 | 55 | setup_gps(); 56 | while (0) 57 | { 58 | Serial.printf("detecting GPS\n"); 59 | int gpsStatus = getGPS(); 60 | Serial.printf("sleep\n"); 61 | Serial.flush(); 62 | deepSleep(5000); 63 | Serial.flush(); 64 | if (!digitalRead(ButtonPin)) 65 | { 66 | digitalWrite(GPIO14, !digitalRead(GPIO14)); 67 | Serial.printf("\nswitch %s\n", digitalRead(GPIO14) ? "off" : "on"); 68 | } 69 | } 70 | 71 | pixels.setPixelColor(0, pixels.Color(0, 15, 0)); 72 | pixels.show(); 73 | startup_lorawan(); 74 | sendStatus(2, 0); 75 | } 76 | 77 | void loop() 78 | { 79 | pixels.setPixelColor(0, pixels.Color(15, 0, 0)); 80 | pixels.show(); 81 | TimerStop(&sleep); 82 | if (digitalRead(ButtonPin) == 0) // Interrupt wakeup 83 | { 84 | Serial.printf("button "); 85 | unsigned long buttonHold = millis(); 86 | while (digitalRead(ButtonPin) == 0 && millis() - buttonHold < 1000) 87 | ; 88 | buttonHold = millis() - buttonHold; 89 | if (buttonHold < 1000) // short press 90 | { 91 | Serial.printf("buttonHoldshort\n"); 92 | Serial.println(F("send status and location")); 93 | int gpsStatus = getGPS(); 94 | sendStatus(1, gpsStatus); 95 | if (gpsStatus == 1 || gpsStatus == 2) 96 | sendLocation(); 97 | TimerSetValue(&sleep, TX_INTERVAL * 1000); 98 | TimerStart(&sleep); 99 | } 100 | else // long press 101 | { 102 | Serial.printf("long\n"); 103 | Serial.print(F("entering deep sleep for infinity\n")); 104 | sendStatus(3, 0); 105 | end_gps(); 106 | pixels.clear(); 107 | pixels.show(); 108 | digitalWrite(Vext, HIGH); // OLED and Neo OFF 109 | 110 | Serial.flush(); 111 | delay(300); 112 | lowpower = 1; 113 | while (lowpower) 114 | lowPowerHandler(); 115 | 116 | digitalWrite(Vext, LOW); // OLED and Neo ON 117 | delay(10); 118 | pixels.setPixelColor(0, pixels.Color(15, 0, 0)); 119 | pixels.show(); 120 | setup_gps(); 121 | sendStatus(1, 0); 122 | TimerSetValue(&sleep, TX_INTERVAL * 1000); 123 | TimerStart(&sleep); 124 | } 125 | } 126 | else // Timer wakeup 127 | { 128 | Serial.printf("timer\n"); 129 | Serial.println(F("Wakeup caused by timer")); 130 | int gpsStatus = getGPS(); 131 | if (wakeup_count % STATUS_INTERVAL == 0) 132 | { 133 | Serial.println(F("periodically send state")); 134 | sendStatus(4, gpsStatus); 135 | } 136 | if (gpsStatus == 1) 137 | sendLocation(); 138 | TimerSetValue(&sleep, TX_INTERVAL * 1000); 139 | TimerStart(&sleep); 140 | } 141 | pixels.setPixelColor(0, pixels.Color(1, 0, 0)); 142 | pixels.show(); 143 | lowpower = 1; 144 | while (lowpower) 145 | lowPowerHandler(); 146 | Serial.flush(); 147 | wakeup_count++; 148 | } 149 | 150 | uint8_t vbatt_bin(uint8_t *txBuffer, uint8_t offset) 151 | { 152 | txBuffer[offset] = (getBatteryVoltage() / 10) - 250; 153 | return offset + 1; 154 | } 155 | 156 | void sendLocation() 157 | { 158 | startup_lorawan(); 159 | uint8_t txBuffer[14]; 160 | uint8_t bufferSize = 0; 161 | uint8_t port = 21; 162 | bufferSize = vbatt_bin(txBuffer, bufferSize); // get battery level 163 | bufferSize = location_bin(txBuffer, bufferSize); 164 | lorawan_send(port, txBuffer, bufferSize, 0, LORAWAN_DEFAULT_SF); 165 | } 166 | 167 | void sendStatus(int state, int gps) 168 | { 169 | startup_lorawan(); 170 | uint8_t txBuffer[14]; 171 | uint8_t bufferSize = 0; 172 | uint8_t port = 1; 173 | bufferSize = vbatt_bin(txBuffer, bufferSize); // get battery level 174 | txBuffer[bufferSize] = state; 175 | bufferSize++; 176 | txBuffer[bufferSize] = gps; 177 | bufferSize++; 178 | lorawan_send(port, txBuffer, bufferSize, 0, STATUS_SF); 179 | } 180 | -------------------------------------------------------------------------------- /src/display.cpp: -------------------------------------------------------------------------------- 1 | #include "display.hpp" 2 | 3 | U8G2_SSD1306_128X64_NONAME_F_HW_I2C *u8g2 = nullptr; 4 | 5 | void setupDisplay() 6 | { 7 | Wire.begin(I2C_SDA, I2C_SCL); 8 | 9 | Wire.beginTransmission(0x3C); 10 | if (Wire.endTransmission() == 0) 11 | { 12 | Serial.println("Started OLED"); 13 | u8g2 = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(U8G2_R0, U8X8_PIN_NONE); 14 | u8g2->begin(); 15 | u8g2->clearBuffer(); 16 | u8g2->setFlipMode(0); 17 | u8g2->setFontMode(1); // Transparent 18 | u8g2->setDrawColor(1); 19 | u8g2->setFontDirection(0); 20 | u8g2->firstPage(); 21 | do 22 | { 23 | u8g2->setFont(u8g2_font_inb19_mr); 24 | u8g2->drawStr(0, 30, "LilyGo"); 25 | u8g2->drawHLine(2, 35, 47); 26 | u8g2->drawHLine(3, 36, 47); 27 | u8g2->drawVLine(45, 32, 12); 28 | u8g2->drawVLine(46, 33, 12); 29 | u8g2->setFont(u8g2_font_inb19_mf); 30 | u8g2->drawStr(58, 60, "LoRa"); 31 | } while (u8g2->nextPage()); 32 | u8g2->sendBuffer(); 33 | u8g2->setFont(u8g2_font_fur11_tf); 34 | delay(3000); 35 | 36 | u8g2->clearDisplay(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/display.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "config.hpp" 5 | #include 6 | 7 | #include 8 | 9 | extern U8G2_SSD1306_128X64_NONAME_F_HW_I2C *u8g2; 10 | 11 | #ifndef OLED_WIRE_PORT 12 | #define OLED_WIRE_PORT Wire 13 | #endif 14 | 15 | #define I2C_SDA 21 16 | #define I2C_SCL 22 17 | 18 | void setupDisplay(); 19 | -------------------------------------------------------------------------------- /src/esp32.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | good sources: 3 | https://github.com/JackGruber/ESP32-LMIC-DeepSleep-example 4 | https://randomnerdtutorials.com/esp32-deep-sleep-arduino-ide-wake-up-sources/ 5 | */ 6 | #include 7 | 8 | #include "gps.hpp" 9 | #include "lorawan.hpp" 10 | #include "power.hpp" 11 | #include "display.hpp" 12 | #include "config.hpp" 13 | 14 | unsigned int TX_INTERVAL = GPS_INTERVAL; 15 | SLEEP_VAR int wakeup_count = 0; 16 | 17 | void sendLocation(); 18 | void sendStatus(int state, int gps); 19 | 20 | void setSleepTimer(int seconds); 21 | void enterSleep(); 22 | 23 | void setup() 24 | { 25 | wakeup_count++; 26 | pinMode(LED, OUTPUT); 27 | pinMode(ButtonPin, INPUT); 28 | Serial.begin(115200); 29 | digitalWrite(LED, LOW); // LED on 30 | startup_axp(); 31 | 32 | Serial.println(PMU->getBattVoltage()); 33 | 34 | esp_sleep_wakeup_cause_t wakeup_reason = 35 | esp_sleep_get_wakeup_cause(); 36 | if (wakeup_reason == ESP_SLEEP_WAKEUP_TIMER) // <-------------- timer 37 | { 38 | Serial.println(F("Wakeup caused by timer")); 39 | setup_gps(); 40 | int gpsStatus = getGPS(); 41 | 42 | if (gpsStatus == 0) 43 | axp_gps(1); // give GPS more volt to get a fix 44 | else 45 | axp_gps(2); // turn down voltage for GPS to save energy 46 | 47 | if (wakeup_count % STATUS_INTERVAL == 0) 48 | { 49 | Serial.println(F("periodically send state")); 50 | sendStatus(4, gpsStatus); 51 | } 52 | if (gpsStatus == 1) 53 | sendLocation(); 54 | setSleepTimer(TX_INTERVAL); 55 | } 56 | else if (wakeup_reason == ESP_SLEEP_WAKEUP_EXT0) 57 | { 58 | Serial.println(F("Wakeup caused by axp")); 59 | axp_gps(1); // turn GPS on 60 | uint8_t cause = axp_cause(); 61 | if (cause == 1) // <----------------------------- short press power 62 | { 63 | Serial.println(F("send status and location")); 64 | setup_gps(); 65 | int gpsStatus = getGPS(); 66 | sendStatus(1, gpsStatus); 67 | if (gpsStatus == 1 || gpsStatus == 2) 68 | sendLocation(); 69 | setSleepTimer(TX_INTERVAL); 70 | } 71 | else if (cause == 2) // <------------------------- long press power 72 | { 73 | Serial.print(F("entering deep sleep for infinity\n")); 74 | axp_gps(0); // turn GPS off 75 | sendStatus(3, 0); 76 | digitalWrite(LED, HIGH); // turn the LED off 77 | enterSleep(); // enter sleep without timer 78 | } 79 | else if (cause == 3) // <------------------------- battery critical 80 | { 81 | Serial.println(F("Battery low shutting down")); 82 | } 83 | } 84 | else // <------------------------------------------------------ reset 85 | { 86 | Serial.println(F("Wakeup caused by reset")); 87 | delay(3000); 88 | setup_axp(); 89 | end_gps(); 90 | startup_lorawan(); 91 | sendStatus(2, 0); 92 | setSleepTimer(TX_INTERVAL); 93 | } 94 | 95 | pinMode(LED, INPUT_PULLDOWN); // let the LED glim 96 | enterSleep(); 97 | } 98 | 99 | void loop() 100 | { 101 | } 102 | 103 | void setSleepTimer(int seconds) 104 | { 105 | esp_sleep_enable_timer_wakeup(seconds * 1000000); 106 | } 107 | 108 | void enterSleep() 109 | { 110 | lorawan_sleep(); 111 | axp_sleep(); 112 | Serial.flush(); 113 | esp_deep_sleep_start(); 114 | } 115 | 116 | void sendLocation() 117 | { 118 | startup_lorawan(); 119 | uint8_t txBuffer[14]; 120 | uint8_t bufferSize = 0; 121 | uint8_t port = 21; 122 | bufferSize = vbatt_bin(txBuffer, bufferSize); // get battery level 123 | bufferSize = location_bin(txBuffer, bufferSize); 124 | lorawan_send(port, txBuffer, bufferSize, 0, LORAWAN_DEFAULT_SF); 125 | } 126 | 127 | void sendStatus(int state, int gps) 128 | { 129 | startup_lorawan(); 130 | uint8_t txBuffer[14]; 131 | uint8_t bufferSize = 0; 132 | uint8_t port = 1; 133 | bufferSize = vbatt_bin(txBuffer, bufferSize); // get battery level 134 | txBuffer[bufferSize] = state; 135 | bufferSize++; 136 | txBuffer[bufferSize] = gps; 137 | bufferSize++; 138 | lorawan_send(port, txBuffer, bufferSize, 0, STATUS_SF); 139 | } 140 | -------------------------------------------------------------------------------- /src/gps.cpp: -------------------------------------------------------------------------------- 1 | #include "gps.hpp" 2 | 3 | TinyGPSPlus gps; 4 | #ifndef CUBECELL 5 | HardwareSerial serialGPS(1); 6 | #else 7 | Air530Class serialGPS; 8 | #endif 9 | 10 | void setup_gps() 11 | { 12 | #ifndef CUBECELL 13 | serialGPS.begin(9600, SERIAL_8N1, GPS_RX, GPS_TX); 14 | #else 15 | digitalWrite(GPIO14, LOW); // VGPS ON 16 | serialGPS.begin(); 17 | #endif 18 | } 19 | 20 | void end_gps() 21 | { 22 | serialGPS.end(); 23 | #ifdef CUBECELL 24 | digitalWrite(GPIO14, HIGH); // VGPS OFF 25 | #endif 26 | #ifdef ESP32 27 | digitalWrite(GPS_TX, HIGH); 28 | gpio_hold_en((gpio_num_t)GPS_TX); 29 | #endif 30 | } 31 | 32 | void gps_loop() 33 | { 34 | unsigned long lock = millis() + 2; 35 | while (serialGPS.available() > 0 && millis() < lock) 36 | { 37 | char c = serialGPS.read(); 38 | gps.encode(c); 39 | Serial.print(c); 40 | } 41 | } 42 | 43 | int getGPS() 44 | { 45 | Serial.println("GPS-loop"); 46 | unsigned long time = millis() + 1200; 47 | while (!gps_valid() && time > millis()) 48 | gps_loop(); 49 | 50 | Serial.flush(); 51 | Serial.print("GPS-end: "); 52 | 53 | if (!gps_valid()) // no GPS 54 | { 55 | Serial.println("no fix"); 56 | return 0; 57 | } 58 | if (gps_geo()) // Geofence 59 | { 60 | Serial.println("geofence"); 61 | return 3; 62 | } 63 | if (!gps_moved(GPS_MOVE_DIST)) // no movement 64 | { 65 | Serial.println("no movement"); 66 | return 2; 67 | } 68 | 69 | Serial.println("ok"); 70 | return 1; // GPS ok 71 | } 72 | 73 | bool gps_valid() 74 | { 75 | if (gps.location.isValid() && gps.hdop.isValid() && gps.altitude.isValid() && gps.location.age() <= 1200 && gps.hdop.age() <= 1200 && gps.altitude.age() <= 1200) 76 | return true; 77 | else 78 | return false; 79 | } 80 | 81 | SLEEP_VAR double last_lat; 82 | SLEEP_VAR double last_lng; 83 | bool gps_moved(int meter) 84 | { 85 | if (gps.distanceBetween(gps.location.lat(), gps.location.lng(), last_lat, last_lng) < meter) 86 | return false; 87 | else 88 | { 89 | last_lat = gps.location.lat(); 90 | last_lng = gps.location.lng(); 91 | return true; 92 | } 93 | } 94 | 95 | uint8_t gps_geo() 96 | { 97 | for (int i = 0; i < (sizeof(geofence) / sizeof(Geofence)); i++) 98 | { 99 | if (gps.distanceBetween(gps.location.lat(), gps.location.lng(), geofence[i].lat, geofence[i].lng) < geofence[i].radius) 100 | { 101 | return i + 1; 102 | } 103 | } 104 | return 0; 105 | } 106 | 107 | uint8_t location_bin(uint8_t *txBuffer, uint8_t offset) 108 | { 109 | uint32_t LatitudeBinary, LongitudeBinary; 110 | uint16_t altitudeGps; 111 | uint8_t hdopGps; 112 | 113 | LatitudeBinary = ((gps.location.lat() + 90) / 180) * 16777215; 114 | LongitudeBinary = ((gps.location.lng() + 180) / 360) * 16777215; 115 | 116 | txBuffer[offset + 0] = (LatitudeBinary >> 16) & 0xFF; 117 | txBuffer[offset + 1] = (LatitudeBinary >> 8) & 0xFF; 118 | txBuffer[offset + 2] = LatitudeBinary & 0xFF; 119 | 120 | txBuffer[offset + 3] = (LongitudeBinary >> 16) & 0xFF; 121 | txBuffer[offset + 4] = (LongitudeBinary >> 8) & 0xFF; 122 | txBuffer[offset + 5] = LongitudeBinary & 0xFF; 123 | 124 | altitudeGps = gps.altitude.meters(); 125 | txBuffer[offset + 6] = (altitudeGps >> 8) & 0xFF; 126 | txBuffer[offset + 7] = altitudeGps & 0xFF; 127 | 128 | if (gps.hdop.isValid()) 129 | hdopGps = gps.hdop.hdop() * 10; 130 | else 131 | hdopGps = 144; 132 | txBuffer[offset + 8] = hdopGps & 0xFF; 133 | 134 | return offset + 9; 135 | } 136 | -------------------------------------------------------------------------------- /src/gps.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #ifdef ESP32 6 | #include "soc/rtc_cntl_reg.h" 7 | #include "soc/rtc.h" 8 | #include "driver/rtc_io.h" 9 | #endif 10 | 11 | #ifdef CUBECELL 12 | #include "LoRaWan_APP.h" 13 | #include "GPS_Air530.h" 14 | #include "GPS_Air530Z.h" 15 | 16 | extern Air530Class serialGPS; 17 | #else 18 | extern HardwareSerial serialGPS; 19 | #endif 20 | 21 | #include "config.hpp" 22 | 23 | extern TinyGPSPlus gps; 24 | 25 | extern SLEEP_VAR double last_lat; 26 | extern SLEEP_VAR double last_lng; 27 | 28 | void setup_gps(); 29 | void end_gps(); 30 | void gps_loop(); 31 | int getGPS(); 32 | bool gps_valid(); 33 | bool gps_moved(int meter); 34 | uint8_t gps_geo(); 35 | uint8_t location_bin(uint8_t *txBuffer, uint8_t offset); 36 | -------------------------------------------------------------------------------- /src/lorawan.cpp: -------------------------------------------------------------------------------- 1 | #include "lorawan.hpp" 2 | 3 | unsigned char Buffer[235]; 4 | SLEEP_VAR LoraWANmessage message; 5 | SLEEP_VAR bool joined = 0; 6 | 7 | #ifdef CUBECELL 8 | int16_t Rssi; 9 | bool radioBusy = 0; 10 | RadioEvents_t RadioEvents; 11 | #endif 12 | 13 | void startup_lorawan() 14 | { 15 | #ifdef CUBECELL 16 | RadioEvents.TxDone = OnTxDone; 17 | RadioEvents.TxTimeout = OnTxTimeout; 18 | RadioEvents.RxDone = OnRxDone; 19 | Radio.Init(&RadioEvents); 20 | #else 21 | LoRa.setPins(LoRa_CS, LoRa_RST, LoRa_DIO0); 22 | #endif 23 | #ifdef ESP32 24 | esp_random(); 25 | #endif 26 | 27 | #ifdef USE_OTAA 28 | if (joined == 0) 29 | { 30 | message = LoraWANmessage(Buffer); 31 | LoraWANactivation otaa = LoraWANactivation(&message); 32 | otaa.setDevNonce((uint16_t)random(70225)); 33 | otaa.setDevEUI(DEVEUI); 34 | otaa.setJoinEUI(APPEUI); 35 | otaa.setAppKey(APPKEY); 36 | 37 | Serial.print("Joining"); 38 | while (!joined) 39 | { 40 | otaa.setDevNonce((uint16_t)random(256 * 256)); 41 | otaa.joinmsg(); 42 | 43 | long frequency = getFrequency(); 44 | lora_tx(frequency, JOIN_SF, message.data, message.dataLen); 45 | unsigned long txTime = millis(); 46 | 47 | // RX1 48 | lora_rx(frequency, JOIN_SF); 49 | while (txTime + 20000 > millis() && !joined) 50 | { 51 | if (!lora_busy()) 52 | { 53 | joined = otaa.checkJoin((char *)message.data, message.dataLen); 54 | printPackage((char *)message.data, message.dataLen, 0); 55 | lora_rx(frequency, JOIN_SF); 56 | } 57 | } 58 | Serial.print("."); 59 | } 60 | lorawan_sleep(); 61 | Serial.println("success!"); 62 | } 63 | #else 64 | if (joined == 0) 65 | { 66 | message = LoraWANmessage(Buffer); 67 | message.setDevAddr(DEVADDR); 68 | message.setNwkSKey(NWKSKEY); 69 | message.setAppSKey(APPSKEY); 70 | joined = 1; 71 | } 72 | #endif 73 | } 74 | 75 | bool lorawan_send(uint8_t _port, uint8_t *_data, uint8_t _size, bool _confirm, int _sf) 76 | { 77 | long frequency = getFrequency(); 78 | 79 | message.uplink((char *)_data, _size, _port, _confirm); 80 | printPackage((char *)message.data, message.dataLen, 1); 81 | 82 | lora_tx(frequency, _sf, message.data, message.dataLen); 83 | 84 | if (_confirm) 85 | { 86 | return 1; 87 | } 88 | return 0; 89 | } 90 | 91 | void printHex2(unsigned v) 92 | { 93 | v &= 0xff; 94 | if (v < 16) 95 | Serial.print('0'); 96 | Serial.print(v, HEX); 97 | } 98 | 99 | long getFrequency() 100 | { 101 | uint16_t frequencylist[] = {8681, 8683, 8685, 8671, 8673, 8675, 8677, 8679}; 102 | uint8_t idx = message.frameCounterUp % (sizeof(frequencylist) / sizeof(uint16_t)); 103 | long frequency = frequencylist[idx] * 100000; 104 | return frequency; 105 | } 106 | 107 | #ifdef CUBECELL 108 | void lorawan_sleep() 109 | { 110 | Radio.Sleep(); 111 | } 112 | 113 | void lora_tx(long frequency, int sf, uint8_t *data, uint8_t size) 114 | { 115 | Serial.printf("sending packet\n"); 116 | Radio.SetChannel(frequency); 117 | Radio.SetTxConfig(MODEM_LORA, 20, 0, 0, 118 | sf, 1, 119 | 8, false, 120 | true, 0, 0, false, 3000); 121 | Radio.SetSyncWord(0x34); 122 | radioBusy = 1; 123 | Radio.Send(data, size); 124 | while (radioBusy) 125 | Radio.IrqProcess(); 126 | } 127 | 128 | void OnRxDone(uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr) 129 | { 130 | Serial.println("RX callback"); 131 | Rssi = rssi; 132 | message.dataLen = size; 133 | memcpy(message.data, payload, size); 134 | Radio.Sleep(); 135 | Serial.printf("\r\nreceived packet \"%s\" with Rssi %d , length %d\r\n", payload, Rssi, size); 136 | Serial.println("wait to send next packet"); 137 | 138 | radioBusy = 0; 139 | } 140 | 141 | void OnTxDone(void) 142 | { 143 | Serial.print("TX done......"); 144 | radioBusy = 0; 145 | } 146 | 147 | void OnTxTimeout(void) 148 | { 149 | Radio.Sleep(); 150 | Serial.print("TX Timeout......"); 151 | radioBusy = 0; 152 | } 153 | 154 | void lora_rx(long frequency, int sf) 155 | { 156 | Serial.println("into RX mode"); 157 | Radio.SetChannel(frequency); 158 | Radio.SetRxConfig(MODEM_LORA, 0, sf, 159 | 1, 0, 8, 160 | 0, false, 161 | 0, false, 0, 0, true, true); 162 | Radio.SetSyncWord(0x34); 163 | radioBusy = 1; 164 | Radio.Rx(0); 165 | } 166 | 167 | bool lora_busy() 168 | { 169 | Radio.IrqProcess(); 170 | return radioBusy; 171 | } 172 | #else 173 | void lorawan_sleep() 174 | { 175 | LoRa.sleep(); 176 | } 177 | 178 | void lora_tx(long frequency, int sf, uint8_t *data, uint8_t size) 179 | { 180 | LoRa.begin(frequency); 181 | LoRa.setPreambleLength(8); 182 | LoRa.setSyncWord(0x34); 183 | LoRa.enableCrc(); 184 | LoRa.disableInvertIQ(); 185 | LoRa.setCodingRate4(5); 186 | LoRa.setSpreadingFactor(sf); 187 | LoRa.setSignalBandwidth(125E3); 188 | 189 | LoRa.beginPacket(); // start packet 190 | LoRa.write(data, size); 191 | LoRa.endPacket(); // finish packet and send it 192 | } 193 | 194 | void lora_rx(long frequency, int sf) 195 | { 196 | LoRa.begin(frequency); 197 | LoRa.setPreambleLength(8); 198 | LoRa.setSyncWord(0x34); 199 | LoRa.disableCrc(); 200 | LoRa.enableInvertIQ(); 201 | LoRa.setCodingRate4(5); 202 | LoRa.setSpreadingFactor(sf); 203 | LoRa.setSignalBandwidth(125E3); 204 | } 205 | 206 | bool lora_busy() 207 | { 208 | uint8_t size = 0; 209 | if (LoRa.parsePacket()) 210 | { 211 | while (LoRa.available()) 212 | message.data[size++] = LoRa.read(); 213 | message.dataLen = size; 214 | return 0; 215 | } 216 | return 1; 217 | } 218 | #endif 219 | 220 | void printPackage(char *data, uint16_t size, bool structure) 221 | { 222 | Serial.print("+ Package:"); 223 | for (int i = 0; i < size; i++) 224 | Serial.printf(" %02x", data[i]); 225 | if (structure) 226 | { 227 | Serial.print("\n |hd| addr |Ct| FCnt|"); 228 | if (size < 13) 229 | { 230 | Serial.println(" MIC |"); 231 | } 232 | else if (size == 13) 233 | { 234 | Serial.println("Pt| MIC |"); 235 | } 236 | else 237 | { 238 | Serial.print("Pt|"); 239 | for (int i = 14; i < size; i++) 240 | Serial.print(" "); 241 | Serial.println(" | MIC |"); 242 | } 243 | } 244 | else 245 | Serial.println(); 246 | } 247 | -------------------------------------------------------------------------------- /src/lorawan.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #include "config.hpp" 6 | 7 | #ifdef CUBECELL 8 | #include "LoRaWan_APP.h" 9 | extern int16_t Rssi; 10 | extern bool radioBusy; 11 | extern RadioEvents_t RadioEvents; 12 | #else 13 | #include 14 | #endif 15 | 16 | extern unsigned char Buffer[235]; 17 | extern SLEEP_VAR LoraWANmessage message; 18 | extern SLEEP_VAR bool joined; 19 | 20 | void startup_lorawan(); 21 | bool lorawan_send(uint8_t _port, uint8_t *_data, uint8_t _size, bool _confirm, int _sf); 22 | void lorawan_sleep(); 23 | void printPackage(char *data, uint16_t size, bool structure = 0); 24 | long getFrequency(); 25 | void lora_tx(long frequency, int sf, uint8_t *data, uint8_t size); 26 | void lora_rx(long frequency, int sf); 27 | bool lora_busy(); 28 | 29 | #ifdef CUBECELL 30 | void OnTxDone(); 31 | void OnTxTimeout(); 32 | void OnRxDone(uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr); 33 | #endif 34 | -------------------------------------------------------------------------------- /src/power.cpp: -------------------------------------------------------------------------------- 1 | #ifdef AXP 2 | #include "power.hpp" 3 | 4 | XPowersLibInterface *PMU = NULL; 5 | bool axpIrq = 0; 6 | 7 | void startup_axp() 8 | { 9 | Wire.begin(AXP_SDA, AXP_SCL); 10 | if (!PMU) 11 | { 12 | PMU = new XPowersAXP2101(Wire); 13 | if (!PMU->init()) 14 | { 15 | Serial.println("Warning: Failed to find AXP2101 power management"); 16 | delete PMU; 17 | PMU = NULL; 18 | } 19 | else 20 | { 21 | Serial.println("AXP2101 PMU init succeeded"); 22 | } 23 | } 24 | 25 | if (!PMU) 26 | { 27 | PMU = new XPowersAXP192(Wire); 28 | if (!PMU->init()) 29 | { 30 | Serial.println("Warning: Failed to find AXP192 power management"); 31 | delete PMU; 32 | PMU = NULL; 33 | } 34 | else 35 | { 36 | Serial.println("AXP192 PMU init succeeded"); 37 | } 38 | } 39 | 40 | // pinMode(AXP_IRQ, INPUT_PULLUP); 41 | pinMode(AXP_IRQ, INPUT); 42 | // attachInterrupt(digitalPinToInterrupt(AXP_IRQ), axp_interrupt, FALLING); 43 | } 44 | 45 | void setup_axp() 46 | { 47 | startup_axp(); 48 | 49 | if (PMU->getChipModel() == XPOWERS_AXP192) 50 | { 51 | // protected esp32 power source 52 | PMU->setProtectedChannel(XPOWERS_DCDC3); 53 | 54 | // disable not use channel 55 | PMU->disablePowerOutput(XPOWERS_DCDC2); 56 | 57 | // Set constant current charging current 58 | PMU->setChargerConstantCurr(XPOWERS_AXP192_CHG_CUR_450MA); 59 | 60 | // Set up the charging voltage 61 | PMU->setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2); 62 | } 63 | else if (PMU->getChipModel() == XPOWERS_AXP2101) 64 | { 65 | // ESP32 VDD 3300mV 66 | PMU->setProtectedChannel(XPOWERS_DCDC1); 67 | 68 | // GNSS RTC PowerVDD 3300mV 69 | PMU->setPowerChannelVoltage(XPOWERS_VBACKUP, 3300); 70 | PMU->enablePowerOutput(XPOWERS_VBACKUP); 71 | 72 | // Unuse power channel 73 | PMU->disablePowerOutput(XPOWERS_DCDC2); 74 | PMU->disablePowerOutput(XPOWERS_DCDC3); 75 | PMU->disablePowerOutput(XPOWERS_DCDC4); 76 | PMU->disablePowerOutput(XPOWERS_DCDC5); 77 | PMU->disablePowerOutput(XPOWERS_ALDO1); 78 | PMU->disablePowerOutput(XPOWERS_ALDO4); 79 | PMU->disablePowerOutput(XPOWERS_BLDO1); 80 | PMU->disablePowerOutput(XPOWERS_BLDO2); 81 | PMU->disablePowerOutput(XPOWERS_DLDO1); 82 | PMU->disablePowerOutput(XPOWERS_DLDO2); 83 | } 84 | PMU->enableSystemVoltageMeasure(); 85 | PMU->enableVbusVoltageMeasure(); 86 | PMU->enableBattVoltageMeasure(); 87 | // It is necessary to disable the detection function of the TS pin on the board 88 | // without the battery temperature detection function, otherwise it will cause abnormal charging 89 | PMU->disableTSPinMeasure(); 90 | 91 | // Set the time of pressing the button to turn off 92 | PMU->setPowerKeyPressOffTime(XPOWERS_POWEROFF_10S); 93 | 94 | uint64_t pmuIrqMask = 0; 95 | uint64_t pmuIrqDis = 0; 96 | 97 | if (PMU->getChipModel() == XPOWERS_AXP192) 98 | { 99 | pmuIrqDis = XPOWERS_AXP192_ALL_IRQ; 100 | // pmuIrqMask = XPOWERS_AXP192_VBUS_INSERT_IRQ | XPOWERS_AXP192_BAT_INSERT_IRQ | XPOWERS_AXP192_PKEY_SHORT_IRQ; 101 | pmuIrqMask = XPOWERS_AXP192_PKEY_SHORT_IRQ | XPOWERS_AXP192_PKEY_LONG_IRQ; 102 | } 103 | else if (PMU->getChipModel() == XPOWERS_AXP2101) 104 | { 105 | pmuIrqDis = XPOWERS_AXP2101_ALL_IRQ; 106 | // pmuIrqMask |= XPOWERS_AXP2101_BAT_INSERT_IRQ | XPOWERS_AXP2101_BAT_REMOVE_IRQ; // BATTERY 107 | // pmuIrqMask |= XPOWERS_AXP2101_VBUS_INSERT_IRQ | XPOWERS_AXP2101_VBUS_REMOVE_IRQ; // VBUS 108 | pmuIrqMask |= XPOWERS_AXP2101_PKEY_SHORT_IRQ | XPOWERS_AXP2101_PKEY_LONG_IRQ; // POWER KEY 109 | // pmuIrqMask |= XPOWERS_AXP2101_BAT_CHG_DONE_IRQ | XPOWERS_AXP2101_BAT_CHG_START_IRQ; // CHARGE 110 | } 111 | PMU->disableIRQ(pmuIrqDis); 112 | PMU->clearIrqStatus(); 113 | PMU->enableIRQ(pmuIrqMask); 114 | 115 | axp_lora(1); 116 | axp_gps(1); 117 | axp_display(1); 118 | } 119 | 120 | void axp_gps(uint8_t state) 121 | { 122 | if (state == 0) // disable GPS 123 | { 124 | if (PMU->getChipModel() == XPOWERS_AXP192) 125 | { 126 | PMU->disablePowerOutput(XPOWERS_LDO3); 127 | } 128 | else if (PMU->getChipModel() == XPOWERS_AXP2101) 129 | { 130 | PMU->disablePowerOutput(XPOWERS_ALDO3); 131 | } 132 | return; 133 | } 134 | 135 | uint16_t voltage = 3000; 136 | if (state == 2) 137 | voltage = 2500; 138 | 139 | if (PMU->getChipModel() == XPOWERS_AXP192) 140 | { 141 | PMU->setPowerChannelVoltage(XPOWERS_LDO3, voltage); 142 | PMU->enablePowerOutput(XPOWERS_LDO3); 143 | } 144 | else if (PMU->getChipModel() == XPOWERS_AXP2101) 145 | { 146 | PMU->setPowerChannelVoltage(XPOWERS_ALDO3, voltage); 147 | PMU->enablePowerOutput(XPOWERS_ALDO3); 148 | } 149 | } 150 | 151 | void axp_lora(bool state) 152 | { 153 | if (state == 0) // disable LoRa 154 | { 155 | if (PMU->getChipModel() == XPOWERS_AXP192) 156 | { 157 | PMU->disablePowerOutput(XPOWERS_LDO2); 158 | } 159 | else if (PMU->getChipModel() == XPOWERS_AXP2101) 160 | { 161 | PMU->disablePowerOutput(XPOWERS_ALDO2); 162 | } 163 | return; 164 | } 165 | 166 | if (PMU->getChipModel() == XPOWERS_AXP192) 167 | { 168 | PMU->setPowerChannelVoltage(XPOWERS_LDO2, 3300); 169 | PMU->enablePowerOutput(XPOWERS_LDO2); 170 | } 171 | else if (PMU->getChipModel() == XPOWERS_AXP2101) 172 | { 173 | PMU->setPowerChannelVoltage(XPOWERS_ALDO2, 3300); 174 | PMU->enablePowerOutput(XPOWERS_ALDO2); 175 | } 176 | } 177 | 178 | void axp_display(bool state) 179 | { 180 | if (state == 0) // disable LoRa 181 | { 182 | if (PMU->getChipModel() == XPOWERS_AXP192) 183 | { 184 | PMU->disablePowerOutput(XPOWERS_DCDC1); 185 | } 186 | else if (PMU->getChipModel() == XPOWERS_AXP2101) 187 | { 188 | // PMU->disablePowerOutput(XPOWERS_ALDO3); 189 | } 190 | return; 191 | } 192 | 193 | if (PMU->getChipModel() == XPOWERS_AXP192) 194 | { 195 | PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300); 196 | PMU->enablePowerOutput(XPOWERS_DCDC1); 197 | } 198 | else if (PMU->getChipModel() == XPOWERS_AXP2101) 199 | { 200 | // PMU->setPowerChannelVoltage(XPOWERS_ALDO3, 3300); 201 | // PMU->enablePowerOutput(XPOWERS_ALDO3); 202 | } 203 | } 204 | 205 | void axp_print() 206 | { 207 | Serial.print("isCharging:"); 208 | Serial.println(PMU->isCharging() ? "YES" : "NO"); 209 | Serial.print("isDischarge:"); 210 | Serial.println(PMU->isDischarge() ? "YES" : "NO"); 211 | Serial.print("isVbusIn:"); 212 | Serial.println(PMU->isVbusIn() ? "YES" : "NO"); 213 | Serial.print("getBattVoltage:"); 214 | Serial.print(PMU->getBattVoltage()); 215 | Serial.println("mV"); 216 | Serial.print("getVbusVoltage:"); 217 | Serial.print(PMU->getVbusVoltage()); 218 | Serial.println("mV"); 219 | Serial.print("getSystemVoltage:"); 220 | Serial.print(PMU->getSystemVoltage()); 221 | Serial.println("mV"); 222 | // The battery percentage may be inaccurate at first use, the PMU will automatically 223 | // learn the battery curve and will automatically calibrate the battery percentage 224 | // after a charge and discharge cycle 225 | if (PMU->isBatteryConnect()) 226 | { 227 | Serial.print("getBatteryPercent:"); 228 | Serial.print(PMU->getBatteryPercent()); 229 | Serial.println("%"); 230 | } 231 | Serial.println(); 232 | 233 | Serial.printf("=========================================\n"); 234 | if (PMU->isChannelAvailable(XPOWERS_DCDC1)) 235 | { 236 | Serial.printf("DC1 : %s Voltage: %04u mV \n", PMU->isPowerChannelEnable(XPOWERS_DCDC1) ? "+" : "-", PMU->getPowerChannelVoltage(XPOWERS_DCDC1)); 237 | } 238 | if (PMU->isChannelAvailable(XPOWERS_DCDC2)) 239 | { 240 | Serial.printf("DC2 : %s Voltage: %04u mV \n", PMU->isPowerChannelEnable(XPOWERS_DCDC2) ? "+" : "-", PMU->getPowerChannelVoltage(XPOWERS_DCDC2)); 241 | } 242 | if (PMU->isChannelAvailable(XPOWERS_DCDC3)) 243 | { 244 | Serial.printf("DC3 : %s Voltage: %04u mV \n", PMU->isPowerChannelEnable(XPOWERS_DCDC3) ? "+" : "-", PMU->getPowerChannelVoltage(XPOWERS_DCDC3)); 245 | } 246 | if (PMU->isChannelAvailable(XPOWERS_DCDC4)) 247 | { 248 | Serial.printf("DC4 : %s Voltage: %04u mV \n", PMU->isPowerChannelEnable(XPOWERS_DCDC4) ? "+" : "-", PMU->getPowerChannelVoltage(XPOWERS_DCDC4)); 249 | } 250 | if (PMU->isChannelAvailable(XPOWERS_DCDC5)) 251 | { 252 | Serial.printf("DC5 : %s Voltage: %04u mV \n", PMU->isPowerChannelEnable(XPOWERS_DCDC5) ? "+" : "-", PMU->getPowerChannelVoltage(XPOWERS_DCDC5)); 253 | } 254 | if (PMU->isChannelAvailable(XPOWERS_LDO2)) 255 | { 256 | Serial.printf("LDO2 : %s Voltage: %04u mV \n", PMU->isPowerChannelEnable(XPOWERS_LDO2) ? "+" : "-", PMU->getPowerChannelVoltage(XPOWERS_LDO2)); 257 | } 258 | if (PMU->isChannelAvailable(XPOWERS_LDO3)) 259 | { 260 | Serial.printf("LDO3 : %s Voltage: %04u mV \n", PMU->isPowerChannelEnable(XPOWERS_LDO3) ? "+" : "-", PMU->getPowerChannelVoltage(XPOWERS_LDO3)); 261 | } 262 | if (PMU->isChannelAvailable(XPOWERS_ALDO1)) 263 | { 264 | Serial.printf("ALDO1: %s Voltage: %04u mV \n", PMU->isPowerChannelEnable(XPOWERS_ALDO1) ? "+" : "-", PMU->getPowerChannelVoltage(XPOWERS_ALDO1)); 265 | } 266 | if (PMU->isChannelAvailable(XPOWERS_ALDO2)) 267 | { 268 | Serial.printf("ALDO2: %s Voltage: %04u mV \n", PMU->isPowerChannelEnable(XPOWERS_ALDO2) ? "+" : "-", PMU->getPowerChannelVoltage(XPOWERS_ALDO2)); 269 | } 270 | if (PMU->isChannelAvailable(XPOWERS_ALDO3)) 271 | { 272 | Serial.printf("ALDO3: %s Voltage: %04u mV \n", PMU->isPowerChannelEnable(XPOWERS_ALDO3) ? "+" : "-", PMU->getPowerChannelVoltage(XPOWERS_ALDO3)); 273 | } 274 | if (PMU->isChannelAvailable(XPOWERS_ALDO4)) 275 | { 276 | Serial.printf("ALDO4: %s Voltage: %04u mV \n", PMU->isPowerChannelEnable(XPOWERS_ALDO4) ? "+" : "-", PMU->getPowerChannelVoltage(XPOWERS_ALDO4)); 277 | } 278 | if (PMU->isChannelAvailable(XPOWERS_BLDO1)) 279 | { 280 | Serial.printf("BLDO1: %s Voltage: %04u mV \n", PMU->isPowerChannelEnable(XPOWERS_BLDO1) ? "+" : "-", PMU->getPowerChannelVoltage(XPOWERS_BLDO1)); 281 | } 282 | if (PMU->isChannelAvailable(XPOWERS_BLDO2)) 283 | { 284 | Serial.printf("BLDO2: %s Voltage: %04u mV \n", PMU->isPowerChannelEnable(XPOWERS_BLDO2) ? "+" : "-", PMU->getPowerChannelVoltage(XPOWERS_BLDO2)); 285 | } 286 | Serial.printf("=========================================\n"); 287 | } 288 | 289 | uint8_t axp_cause() 290 | { 291 | uint8_t ret = 0; 292 | uint32_t status = PMU->getIrqStatus(); 293 | Serial.print("STATUS => HEX:"); 294 | Serial.print(status, HEX); 295 | Serial.print(" BIN:"); 296 | Serial.println(status, BIN); 297 | 298 | if (PMU->isPekeyShortPressIrq()) 299 | { 300 | Serial.println("isPekeyShortPress"); 301 | ret = 1; 302 | } 303 | if (PMU->isPekeyLongPressIrq()) 304 | { 305 | Serial.println("isPekeyLongPress"); 306 | ret = 2; 307 | } 308 | 309 | PMU->clearIrqStatus(); 310 | 311 | return ret; 312 | } 313 | 314 | void axp_sleep() 315 | { 316 | // detachInterrupt(digitalPinToInterrupt(AXP_IRQ)); 317 | esp_sleep_enable_ext0_wakeup((gpio_num_t)AXP_IRQ, 0); 318 | } 319 | 320 | uint8_t vbatt_bin(uint8_t *txBuffer, uint8_t offset) 321 | { 322 | if (PMU->isBatteryConnect()) 323 | txBuffer[offset] = (PMU->getBattVoltage() / 10) - 250; 324 | else 325 | txBuffer[offset] = 0xff; 326 | return offset + 1; 327 | } 328 | #endif 329 | -------------------------------------------------------------------------------- /src/power.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifdef AXP 3 | #include 4 | #include 5 | 6 | #include "config.hpp" 7 | 8 | extern XPowersLibInterface *PMU; 9 | extern bool axpIrq; 10 | 11 | void setup_axp(); 12 | void startup_axp(); 13 | void axp_gps(uint8_t state); 14 | void axp_lora(bool state); 15 | void axp_display(bool state); 16 | void axp_print(); 17 | uint8_t axp_cause(); 18 | void axp_sleep(); 19 | uint8_t vbatt_bin(uint8_t *txBuffer, uint8_t offset); 20 | #endif 21 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | --------------------------------------------------------------------------------