├── LICENSE ├── hardware-tests ├── aglora-gps-test │ └── aglora-gps-test.ino └── aglora-lora-test │ └── aglora-lora-test.ino ├── README.md └── AGLoRa-tracker.ino /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Eugeny Shlyagin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /hardware-tests/aglora-gps-test/aglora-gps-test.ino: -------------------------------------------------------------------------------- 1 | /* 2 | GPS TEST for AGLoRa device 3 | https://github.com/Udj13/AGLoRa/ 4 | */ 5 | #include 6 | 7 | // Please setup library "TinyGPSPlus by Mikal Hart" from Arduino IDE library manager 8 | #include 9 | 10 | // ============ GPS PIN SETTINGS ============ 11 | static const int GPS_PIN_RX = 7, GPS_PIN_TX = 8; 12 | static const uint32_t GPSBaud = 9600; 13 | 14 | // The TinyGPSPlus object 15 | TinyGPSPlus gps; 16 | 17 | // The serial connection to the GPS device 18 | SoftwareSerial gpsSerial(GPS_PIN_RX, GPS_PIN_TX); 19 | 20 | void setup() { 21 | Serial.begin(9600); 22 | gpsSerial.begin(GPSBaud); 23 | 24 | Serial.println(); 25 | Serial.println(); 26 | Serial.println(F("AGLoRa fast GPS connection test")); 27 | Serial.println(F("TinyGPSPlus library")); 28 | Serial.println(); 29 | } 30 | 31 | unsigned long lastGPSPrintTime; 32 | static const unsigned int PERIODIC_GPS_INTERVAL = 2000; // milliseconds 33 | 34 | 35 | void loop() { 36 | // This sketch displays information every time a new sentence is correctly encoded. 37 | while (gpsSerial.available() > 0) { 38 | char _nmea = gpsSerial.read(); 39 | Serial.print(_nmea); 40 | if (gps.encode(_nmea)) { 41 | if (millis() > lastGPSPrintTime + PERIODIC_GPS_INTERVAL) { 42 | displayInfo(); 43 | lastGPSPrintTime = millis(); 44 | } 45 | } 46 | 47 | 48 | if (millis() > 5000 && gps.charsProcessed() < 10) { 49 | Serial.println(F("No GPS detected: check wiring.")); 50 | while (true) 51 | ; 52 | } 53 | } 54 | } 55 | 56 | void displayInfo() { 57 | Serial.println(); 58 | Serial.println(); 59 | Serial.print(F("Tiny GPS Plus parser | ")); 60 | Serial.print(F("Location: ")); 61 | if (gps.location.isValid()) { 62 | Serial.print(gps.location.lat(), 6); 63 | Serial.print(F(",")); 64 | Serial.print(gps.location.lng(), 6); 65 | } else { 66 | Serial.print(F("INVALID")); 67 | } 68 | 69 | Serial.print(F(" Date/Time: ")); 70 | if (gps.date.isValid()) { 71 | Serial.print(gps.date.month()); 72 | Serial.print(F("/")); 73 | Serial.print(gps.date.day()); 74 | Serial.print(F("/")); 75 | Serial.print(gps.date.year()); 76 | } else { 77 | Serial.print(F("INVALID")); 78 | } 79 | 80 | Serial.print(F(" ")); 81 | if (gps.time.isValid()) { 82 | if (gps.time.hour() < 10) Serial.print(F("0")); 83 | Serial.print(gps.time.hour()); 84 | Serial.print(F(":")); 85 | if (gps.time.minute() < 10) Serial.print(F("0")); 86 | Serial.print(gps.time.minute()); 87 | Serial.print(F(":")); 88 | if (gps.time.second() < 10) Serial.print(F("0")); 89 | Serial.print(gps.time.second()); 90 | Serial.print(F(".")); 91 | if (gps.time.centisecond() < 10) Serial.print(F("0")); 92 | Serial.print(gps.time.centisecond()); 93 | } else { 94 | Serial.print(F("INVALID")); 95 | } 96 | 97 | Serial.println(); 98 | } 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AGLoRa 2 | 3 | Project AGLoRa - easy opensource LoRa GPS tracker. 4 | Created by Eugeny Shlyagin (shlyagin@gmail.com) 5 | 6 | [![AGLoRa video](http://img.youtube.com/vi/bU68tQFBxwA/0.jpg)](http://www.youtube.com/watch?v=bU68tQFBxwA) 7 | 8 | Click to open video. 9 | 10 | ![AGLoRa](https://bitlite.ru/wp-content/uploads/2021/11/aglora-prototype.jpg) 11 | 12 | AGLoRa is an acronym for "Arduino GPS LoRa". 13 | LoRa (from "long range") is a proprietary low-power wide-area network modulation technique. https://en.wikipedia.org/wiki/LoRa 14 | 15 | AGLoRa is a simple open-source satellite tracking system for hiking, sailing, pet finding, and other outdoor activities. 16 | All components are widely available to buy online (aliexpress etc.). 17 | 18 | ![Program block diagram](https://bitlite.ru/wp-content/uploads/2021/11/lora-tracker.drawio.png) 19 | 20 | 21 | AGLoRa receives the GPS coordinates from other trackers (via LoRa) and immediately transmits them to the phone app. 22 | By default the tracker sends its coordinates via LoRa every 10 seconds, when its GPS data is valid. 23 | 24 | ## ABOUT THE PROJECT 25 | 26 | We are going to test E32-E433T30D. 27 | It is a wireless transceiver module, operating at 433 MHz based on original RFIC SX1278 from SEMTECH. 28 | Aglora broadcasts coordinates to other trackers. 29 | 30 | ![AGLoRa working diagram](https://bitlite.ru/wp-content/uploads/2021/11/Project-proposal-1.jpg) 31 | 32 | 33 | ### COMPONENTS AND SUPPLIES 34 | - Arduino Nano or Arduino UNO (ATMEGA328P, not ATmega168) 35 | - LoRa Module (EBYTE E220-900T22D (868 MHz), E433T30D (433 MHz)) 36 | - GPS Module (Generic) 37 | - AT-09 (HM-10) Bluetooth Low Energy Module 38 | - iOS or Android device 39 | 40 | ### APPS 41 | - Arduino IDE 42 | 43 | # Wiring. 44 | 45 | Let’s Start Building. The circuit is so simple, there are a few connections to be made. 46 | 47 | ![AGLoRa device diagram](https://bitlite.ru/wp-content/uploads/2021/11/Project-proposal.jpg) 48 | 49 | Also you can read [step-by-step instructions](https://github.com/Udj13/AGLoRa/wiki/Step%E2%80%90by%E2%80%90step-instructions) in AGLoRa Wiki 50 | 51 | 52 | ### Сonnecting the LoRa module 53 | ``` 54 | Arduino Pins LoRa Pins 55 | Pin 2 ——> TX 56 | Pin 3 ——> RX 57 | Pin 4 ——> M0 58 | Pin 5 ——> M1 59 | Pin 6 ——> AX 60 | 5V ——> VCC 61 | GND ——> GND 62 | ``` 63 | 64 | ### Connecting the GPS module 65 | 66 | ``` 67 | Arduino Pins GPS Pins 68 | Pin 7 ——> TX 69 | Pin 8 ——> RX 70 | 5V ——> VCC 71 | GND ——> GND 72 | ``` 73 | 74 | ### Connecting the BLE module (optional) 75 | ``` 76 | Arduino Pins Bluetooth Pins 77 | RX (Pin 0) ——> TX 78 | TX (Pin 1) ——> RX 79 | 5V ——> VCC 80 | GND ——> GND 81 | ``` 82 | 83 | ![AGLoRA wiring](https://bitlite.ru/wp-content/uploads/2021/11/aglora-on-green.jpg) 84 | 85 | Add a 5V stabilizer for battery power (like LM7805). 86 | 87 | # Uploading Sketch 88 | 89 | Three steps: 90 | 91 | 1. Download or copy the Sketch from here: https://github.com/Udj13/AGLoRa/blob/main/AGLoRa-tracker.ino Just one file! Easy! 92 | 93 | 2. Change the “MY_NAME” setting. Сheck the debug mode is off. 94 | 95 | ``` 96 | char MY_NAME[NAME_LENGTH] = "Morty"; 97 | #define DEBUG_MODE false 98 | ``` 99 | 100 | 3. Install "EByte LoRa E220 by Renzo Mischianty" library and "TinyGPSPlus by Mikal Hart" library from Arduino IDE library manager ("Tools" -> "Manage Libraries") 101 | 102 | Ready! Upload a Sketch to an Arduino. 103 | 104 | **NOTES:** 105 | 106 | **Value “NAME_LENGTH” must be same for all your devices** 107 | 108 | **Remove Bluetooth module Tx-Rx connection before uploading the program!** 109 | 110 | **I strongly recommend using the PlatformIO and the full project (https://github.com/Udj13/AGLoRa-full)! This way you will have the latest version of the code!** 111 | 112 | 113 | # Install the app on your phone. 114 | 115 | ![AGLoRa client icon](https://bitlite.ru/wp-content/uploads/2021/12/80.png) 116 | 117 | - Source code is available for free on github: https://github.com/Udj13/AGLoRa-client-flutter 118 | - You can download the iOs app from Apple App Store: https://apps.apple.com/ru/app/aglora/id1600250635 119 | - You can download the Android app APK from here: https://github.com/Udj13/AGLoRa-client-flutter/releases/tag/AGLoRa2.0 120 | 121 | Permissions: 122 | - Bluetooth permission required to connect to your AGLoRa device 123 | - Location permission is required to calculate distance between AGLoRa devices. Without this permission the app will only show the coordinates. 124 | 125 | ![image](https://github.com/Udj13/AGLoRa/assets/54446451/db3090a5-7945-4770-8903-61eff84b1b90) 126 | 127 | 128 | 129 | ## How to use the AGLoRa Client App? 130 | 131 | ![AGLoRa client](https://bitlite.ru/wp-content/uploads/2021/11/aglora-test-3km.jpg) 132 | 133 | - Install application on your device 134 | - Turn on the AGLoRa trackers. When the GPS data is correct, the built-in LED will turn on. 135 | - Scan for available devices 136 | - Select your Bluetooth module from the List (“AGLoRa”) 137 | - Wait to receive data from other trackers 138 | 139 | # Hardware tests 140 | 141 | If the modules do not work, then you can run test sketch from "hardware-tests" folder. 142 | 143 | # Script customization 144 | 145 | Just follow the ~~white rabbit~~ the instructions in the code. 146 | 147 | GPS_PACKET_INTERVAL - how often the tracker will be send data 148 | 149 | USE_EEPROM_MEMORY - set "false" to use SRAM memory, "true" to use EEPROM 150 | 151 | SRAM_STORAGE_SIZE - if you are using SRAM you can set the size of memory. In this case check the free memory in Arduino IDE. 152 | 153 | ![image](https://github.com/Udj13/AGLoRa/assets/54446451/092a52a8-19a8-4996-be58-3ef0c38b8e5d) 154 | 155 | # Custom sensors 156 | 157 | If you need you can add any sensors and then see its values in the application. To do this, just edit the section with the main data structure and the section with the protocol. 158 | Note that all software versions must be the same for all trackers. 159 | Example: 160 | 161 | ``` 162 | struct DATA { 163 | char id[NAME_LENGTH]; // name 164 | 165 | float lat; // coordinates 166 | float lon; 167 | unsigned char sat; 168 | 169 | unsigned char year; // the Year minus 2000 170 | unsigned char month; 171 | unsigned char day; 172 | 173 | unsigned char hour; 174 | unsigned char minute; 175 | unsigned char second; 176 | 177 | // Add more data fields here if you need 178 | // ... 179 | // unsigned char speed; 180 | // unsigned char battery; 181 | // unsigned char sensor1; 182 | // ... 183 | }; 184 | 185 | ``` 186 | 187 | ``` 188 | void sendPackageToBluetooth(DATA *package){ 189 | .... 190 | // Sensors and additional data 191 | Serial.print(F("&sat=")); //record separator 192 | Serial.print(package->sat); // satellites 1 byte 193 | 194 | // Add more data here if you need ... 195 | // Serial.print("&speed="); // data's name in app 196 | // Serial.print(package->speed); // value 197 | 198 | // Serial.print("&batt="); 199 | // Serial.print(package->battery); 200 | 201 | // Serial.print("&alienSensor="); 202 | // Serial.print(package->sensor1); 203 | 204 | 205 | ``` 206 | 207 | # Links 208 | 209 | AGLoRa Wiki: https://github.com/Udj13/AGLoRa/wiki 210 | 211 | Full project (C++, PlatformIO): https://github.com/Udj13/AGLoRa-full/ 212 | 213 | Mobile client (Dart, Flutter): https://github.com/Udj13/AGLoRa-client-flutter 214 | 215 | 216 | Description in Russian: https://bitlite.ru/aglora-lora-gps-tracker/ 217 | 218 | 219 | 220 | # Contributions of other authors 221 | 222 | BLE to COM convertor for tests - https://github.com/magdel/ble2com 223 | 224 | Aglora Emulator - https://github.com/magdel/agloraemulator 225 | 226 | 3d model - https://makerworld.com.cn/zh/models/1363168-aglora-lora-gpszhui-zong-he 227 | 228 | 229 | -------------------------------------------------------------------------------- /hardware-tests/aglora-lora-test/aglora-lora-test.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Ebyte LoRa TEST for AGLoRa device 3 | https://github.com/Udj13/AGLoRa/ 4 | */ 5 | #include "Arduino.h" 6 | 7 | // Please setup library "EByte LoRa E220 by Renzo Mischianty" from Arduino IDE library manager 8 | #include "LoRa_E220.h" 9 | 10 | // Then, set pins 11 | 12 | static const byte LORA_PIN_TX = 2, LORA_PIN_RX = 3; 13 | static const byte LORA_PIN_M0 = 4, LORA_PIN_M1 = 5; 14 | static const byte LORA_PIN_AUX = 6; 15 | 16 | // Ready, you can start sketch! 17 | 18 | // Your device will be send periodic packets 19 | // also you can enter any text and send it 20 | 21 | static const unsigned int PERIODIC_PACKET_INTERVAL = 10000; // milliseconds 22 | 23 | #include 24 | SoftwareSerial mySerial(LORA_PIN_TX, LORA_PIN_RX); 25 | LoRa_E220 e220ttl(&mySerial, LORA_PIN_AUX, LORA_PIN_M1, LORA_PIN_M0); 26 | //LoRa_E220 e220ttl(&mySerial); // Config without connect AUX and M0 M1 27 | 28 | void setLoRaParameters(struct Configuration configuration); 29 | void printParameters(struct Configuration configuration); 30 | void printModuleInformation(struct ModuleInformation moduleInformation); 31 | unsigned long lastLoraPacketTime; 32 | 33 | void setup() { 34 | // Serial init 35 | Serial.begin(9600); 36 | while (!Serial) {}; 37 | delay(500); 38 | Serial.println(); 39 | 40 | // LoRa init 41 | e220ttl.begin(); 42 | ResponseStructContainer c; 43 | 44 | // Printing current LoRa parameters 45 | Serial.println("---------------------------------------------"); 46 | Serial.println("------ Get current LoRa configuration -------"); 47 | c = e220ttl.getConfiguration(); 48 | Configuration configuration = *(Configuration *)c.data; 49 | Serial.println(c.status.getResponseDescription()); 50 | Serial.println(c.status.code); 51 | printParameters(configuration); 52 | 53 | // Apply new parameters 54 | Serial.println("---------------------------------------------"); 55 | Serial.println("--------- Set new LoRa configuration --------"); 56 | setLoRaParameters(&configuration); // <- check parameters in function 57 | ResponseStatus rs = e220ttl.setConfiguration(configuration, WRITE_CFG_PWR_DWN_SAVE); 58 | Serial.println(rs.getResponseDescription()); 59 | Serial.println(rs.code); 60 | 61 | // Check of changes 62 | c = e220ttl.getConfiguration(); 63 | configuration = *(Configuration *)c.data; 64 | Serial.println(c.status.getResponseDescription()); 65 | Serial.println(c.status.code); 66 | printParameters(configuration); 67 | 68 | c.close(); 69 | } 70 | 71 | void loop() { 72 | // if something available 73 | if (e220ttl.available() > 1) { 74 | Serial.println("New data received"); 75 | ResponseContainer rc = e220ttl.receiveMessage(); 76 | // is something goes wrong print error 77 | if (rc.status.code != 1) { 78 | Serial.println(rc.status.getResponseDescription()); 79 | } else { 80 | // print the data received 81 | Serial.print("<");Serial.print(rc.data);Serial.println(">"); 82 | Serial.println(rc.status.getResponseDescription()); 83 | Serial.println(); 84 | } 85 | } 86 | 87 | // data for transmitting is available 88 | if (Serial.available()) { 89 | Serial.println(); 90 | 91 | String input = Serial.readString(); 92 | Serial.print("Sending ->: "); 93 | Serial.print(input); 94 | 95 | Serial.print("sendMessage - "); 96 | ResponseStatus rs = e220ttl.sendMessage(input); 97 | Serial.println(rs.getResponseDescription()); 98 | } 99 | 100 | // periodic transmitting 101 | if ((millis() - lastLoraPacketTime) > PERIODIC_PACKET_INTERVAL) { 102 | Serial.println("Sending ->: Test data"); 103 | ResponseStatus rs = e220ttl.sendMessage("Test data"); 104 | Serial.println(rs.getResponseDescription()); 105 | lastLoraPacketTime = millis(); 106 | } 107 | } 108 | 109 | 110 | void setLoRaParameters(struct Configuration *configuration) { 111 | configuration->ADDL = 0x00; 112 | configuration->ADDH = 0x00; 113 | 114 | configuration->CHAN = 0x17; 115 | 116 | configuration->SPED.uartBaudRate = UART_BPS_9600; 117 | configuration->SPED.airDataRate = AIR_DATA_RATE_010_24; 118 | configuration->SPED.uartParity = MODE_00_8N1; 119 | 120 | configuration->OPTION.subPacketSetting = SPS_200_00; 121 | configuration->OPTION.RSSIAmbientNoise = RSSI_AMBIENT_NOISE_DISABLED; 122 | configuration->OPTION.transmissionPower = POWER_22; 123 | 124 | 125 | configuration->TRANSMISSION_MODE.enableRSSI = RSSI_DISABLED; 126 | configuration->TRANSMISSION_MODE.fixedTransmission = FT_TRANSPARENT_TRANSMISSION; 127 | configuration->TRANSMISSION_MODE.enableLBT = LBT_DISABLED; // monitoring before data transmitted 128 | configuration->TRANSMISSION_MODE.WORPeriod = WOR_2000_011; 129 | } 130 | 131 | 132 | void printParameters(struct Configuration configuration) { 133 | Serial.println("----------------------------------------"); 134 | 135 | Serial.print(F("HEAD : ")); 136 | Serial.print(configuration.COMMAND, HEX); 137 | Serial.print(" "); 138 | Serial.print(configuration.STARTING_ADDRESS, HEX); 139 | Serial.print(" "); 140 | Serial.println(configuration.LENGHT, HEX); 141 | Serial.println(F(" ")); 142 | Serial.print(F("AddH : ")); 143 | Serial.println(configuration.ADDH, HEX); 144 | Serial.print(F("AddL : ")); 145 | Serial.println(configuration.ADDL, HEX); 146 | Serial.println(F(" ")); 147 | Serial.print(F("Chan : ")); 148 | Serial.print(configuration.CHAN, DEC); 149 | Serial.print(" -> "); 150 | Serial.println(configuration.getChannelDescription()); 151 | Serial.println(F(" ")); 152 | Serial.print(F("SpeedParityBit : ")); 153 | Serial.print(configuration.SPED.uartParity, BIN); 154 | Serial.print(" -> "); 155 | Serial.println(configuration.SPED.getUARTParityDescription()); 156 | Serial.print(F("SpeedUARTDatte : ")); 157 | Serial.print(configuration.SPED.uartBaudRate, BIN); 158 | Serial.print(" -> "); 159 | Serial.println(configuration.SPED.getUARTBaudRateDescription()); 160 | Serial.print(F("SpeedAirDataRate : ")); 161 | Serial.print(configuration.SPED.airDataRate, BIN); 162 | Serial.print(" -> "); 163 | Serial.println(configuration.SPED.getAirDataRateDescription()); 164 | Serial.println(F(" ")); 165 | Serial.print(F("OptionSubPacketSett: ")); 166 | Serial.print(configuration.OPTION.subPacketSetting, BIN); 167 | Serial.print(" -> "); 168 | Serial.println(configuration.OPTION.getSubPacketSetting()); 169 | Serial.print(F("OptionTranPower : ")); 170 | Serial.print(configuration.OPTION.transmissionPower, BIN); 171 | Serial.print(" -> "); 172 | Serial.println(configuration.OPTION.getTransmissionPowerDescription()); 173 | Serial.print(F("OptionRSSIAmbientNo: ")); 174 | Serial.print(configuration.OPTION.RSSIAmbientNoise, BIN); 175 | Serial.print(" -> "); 176 | Serial.println(configuration.OPTION.getRSSIAmbientNoiseEnable()); 177 | Serial.println(F(" ")); 178 | Serial.print(F("TransModeWORPeriod : ")); 179 | Serial.print(configuration.TRANSMISSION_MODE.WORPeriod, BIN); 180 | Serial.print(" -> "); 181 | Serial.println(configuration.TRANSMISSION_MODE.getWORPeriodByParamsDescription()); 182 | Serial.print(F("TransModeEnableLBT : ")); 183 | Serial.print(configuration.TRANSMISSION_MODE.enableLBT, BIN); 184 | Serial.print(" -> "); 185 | Serial.println(configuration.TRANSMISSION_MODE.getLBTEnableByteDescription()); 186 | Serial.print(F("TransModeEnableRSSI: ")); 187 | Serial.print(configuration.TRANSMISSION_MODE.enableRSSI, BIN); 188 | Serial.print(" -> "); 189 | Serial.println(configuration.TRANSMISSION_MODE.getRSSIEnableByteDescription()); 190 | Serial.print(F("TransModeFixedTrans: ")); 191 | Serial.print(configuration.TRANSMISSION_MODE.fixedTransmission, BIN); 192 | Serial.print(" -> "); 193 | Serial.println(configuration.TRANSMISSION_MODE.getFixedTransmissionDescription()); 194 | 195 | 196 | Serial.println("----------------------------------------"); 197 | } 198 | 199 | void printModuleInformation(struct ModuleInformation moduleInformation) { 200 | Serial.println("----------------------------------------"); 201 | Serial.print(F("HEAD: ")); 202 | Serial.print(moduleInformation.COMMAND, HEX); 203 | Serial.print(" "); 204 | Serial.print(moduleInformation.STARTING_ADDRESS, HEX); 205 | Serial.print(" "); 206 | Serial.println(moduleInformation.LENGHT, DEC); 207 | 208 | Serial.print(F("Model no.: ")); 209 | Serial.println(moduleInformation.model, HEX); 210 | Serial.print(F("Version : ")); 211 | Serial.println(moduleInformation.version, HEX); 212 | Serial.print(F("Features : ")); 213 | Serial.println(moduleInformation.features, HEX); 214 | Serial.println("----------------------------------------"); 215 | } 216 | -------------------------------------------------------------------------------- /AGLoRa-tracker.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Project AGLoRa (abbreviation of Arduino + GPS + LoRa) 3 | Tiny and chip LoRa GPS tracker 4 | 5 | https://github.com/Udj13/AGLoRa/ 6 | 7 | Copyright © 2021-2024 Eugeny Shlyagin. Contacts: 8 | License: http://opensource.org/licenses/MIT 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty. 12 | 13 | Modules used: 14 | - Arduino UNO/Nano (ATMEGA328P, not ATmega168) 15 | - GPS NMEA Module (Generic) 16 | - LoRa EBYTE E220-900T22D (868 MHz) or EBYTE E32-E433T30D (433 MHz) 17 | - Bluetooth BLE AT-09 or HC-05 18 | */ 19 | 20 | /* 21 | HOW THIS SKETCH WORKS: 22 | 23 | Most of the time the tracker works in listening mode. 24 | 25 | After receiving the LoRa package from another tracker - 26 | this data is transferred to the bluetooth serial port. 27 | 28 | Once every three minutes, the tracker switches to 29 | the GPS receiver for a few seconds to get coordinates. 30 | Then sends a LoRa data packet. 31 | 32 | NOTE: GPS is valid, if LED_BUILTIN is HIGH 33 | */ 34 | 35 | // HOW TO SETUP: 36 | // ======================================== 37 | // ==== Settings LEVEL 1 (required) ======= 38 | // ======================================== 39 | 40 | #include 41 | #include 42 | 43 | // First of all, select EBYTE module: 44 | #define EBYTE_E220 // EBYTE_E220 or EBYTE_E32 45 | //#define EBYTE_E32 // EBYTE_E220 or EBYTE_E32 46 | 47 | // Here are the libraries that I used in this project, thanks to their author Renzo Mischianti! 48 | // Please, give him a donation if you also think he did a great job! 49 | // https://github.com/xreef 50 | // https://mischianti.org/ 51 | 52 | #ifdef EBYTE_E32 53 | #include "LoRa_E32.h" 54 | // Docs: https://github.com/xreef/LoRa_E32_Series_Library 55 | #endif 56 | //or 57 | #ifdef EBYTE_E220 58 | #include "LoRa_E220.h" 59 | // Docs: https://github.com/xreef/EByte_LoRa_E220_Series_Library 60 | #endif 61 | 62 | // After selecting a module, you need to set up a tracker name and module connections: 63 | 64 | // ========== NAME ======================= 65 | #define NAME_LENGTH 12 // The same value for all devices 66 | const char NAME[NAME_LENGTH] = "Morty"; // Name of current tracker, NAME_LENGTH characters 67 | // Example: 68 | // #define NAME = "Rick"; // All names length should be no longer than NAME_LENGTH 69 | // ========== WIRING ===================== 70 | //UART LORA 71 | #define LORA_PIN_RX 2 72 | #define LORA_PIN_TX 3 73 | #define LORA_PIN_M0 4 74 | #define LORA_PIN_M1 5 75 | #define LORA_PIN_AX 6 76 | 77 | //UART GPS 78 | #define GPS_PIN_RX 7 79 | #define GPS_PIN_TX 8 80 | 81 | // Leds 82 | #define LORA_LED LED_BUILTIN 83 | #define GPS_LED LED_BUILTIN 84 | #define BLE_LED LED_BUILTIN 85 | #define MEMORY_LED LED_BUILTIN 86 | // ========== MODULES SETTING============= 87 | #define GPS_SPEED 9600 88 | #define LORA_START_SPEED 9600 89 | 90 | // Then install "EByte LoRa E220 by Renzo Mischianty" library 91 | // or "EByte LoRa E32 by Renzo Mischianty" 92 | // from Arduino IDE library manager ("Tools" -> "Manage Libraries") 93 | 94 | // Now you can upload this sketch to Arduino 95 | // and turn on the app "AGLoRa" on your phone 96 | 97 | // If something went wrong you can enable 98 | // "debug mode" through the serial port. 99 | // Don't forget to disconnect the bluetooth module. 100 | // Then open "Tools" -> "Serial monitor" in Arduino IDE. 101 | #define DEBUG_MODE false // change "false" to "true" to enable 102 | // Next, logs levels for comfortable deallbugging, 103 | // if DEBUG_MODE == false, logs level are not important 104 | #define DEBUG_BLE false // bluetooth low energy 105 | #define DEBUG_GPS true // print GPS logs 106 | #define DEBUG_LORA true // print GPS logs 107 | #define DEBUG_MEMORY true // print GPS logs 108 | #define DEBUG_AGLORA true // print GPS logs 109 | 110 | // ======================================== 111 | // ==== Settings LEVEL 2 (optional) ======= 112 | // ======================================== 113 | 114 | /* 115 | This is the structure of the LoRa data package. 116 | If you want to send additional data between AGLoRa trackers 117 | you should add it to this section. 118 | Note that all software version must be the same for all trackers. 119 | */ 120 | struct DATA { 121 | char name[NAME_LENGTH]; // name 122 | 123 | float lat; // coordinates 124 | float lon; 125 | 126 | unsigned char year; // the Year minus 2000 127 | unsigned char month; 128 | unsigned char day; 129 | 130 | unsigned char hour; 131 | unsigned char minute; 132 | unsigned char second; 133 | 134 | bool gpsValid; 135 | 136 | // Add more data fields here if you need 137 | // ... 138 | //unsigned char sat; 139 | //unsigned char hdop; 140 | unsigned char battery; 141 | // unsigned char speed; 142 | // unsigned char sensor1; 143 | // unsigned char sensor2; 144 | // ... 145 | }; 146 | 147 | // Forward declarations for functions that are defined later 148 | String sendToPhone(DATA *package); 149 | String sendBatteryToPhone(); 150 | 151 | // ======================================== 152 | // ==== Settings LEVEL 3 (nightmare) ====== 153 | // ======================================== 154 | #define USE_EEPROM_MEMORY false // "false" by default 155 | // set "false" to use SRAM memory, "true" to use EEPROM 156 | // EEPROM is permanent memory, data is not lost even 157 | // if the system is turned off. 158 | // But the write operation is finite and usually capped at 100,000 cycles. 159 | // Please read: https://docs.arduino.cc/learn/programming/memory-guide 160 | // ============ LORA NETWORK SETTINGS ============ 161 | #define I_WANT_TO_SEND_MY_LOCATION true // "true" by default 162 | #define DATA_SENDING_INTERVAL 30000 // milliseconds (seconds * 1000) 163 | 164 | #define MESH_MODE true // "true" by default 165 | #define TTL 3 // Data packet lifetime (for transfer between devices) 166 | // ============ OTHER SETTINGS ========== 167 | #define USE_BLE true // use BLE output 168 | #define BLE_UPDATE_INTERVAL 50000 // milliseconds (seconds * 1000) 169 | // ============ SRAM STORAGE ============== 170 | // Maximum number of track points (struct DATA) in memory 171 | // Change and check free memory in "Output" after pressing "Verify". 172 | #define SRAM_STORAGE_SIZE 15 // DATA array size 173 | // not used if USE_EEPROM_MEMORY true, may be zero in this case 174 | // NOTE: Don't use all free memory! It's may broke BLE output. 175 | // You should hold free memory for return String from "sendToPhone" 176 | // ============ EEPROM STORAGE ============== 177 | // EEPROM (non-volatile) memory 178 | // not used if define USE_EEPROM_MEMORY false 179 | #define EEPROM_BEGIN_ADDRESS 0 //bytes 180 | // reserve for storing settings 181 | // not used if USE_EEPROM_MEMORY false 182 | // ================ TESTS ================== 183 | #define TEST_LORA_DATA false 184 | #define OTHER_NAME "Summer" // virtual tracker's name, don't forget about NAME_LENGTH 185 | // ========================================= 186 | // ========== END OF SETTINGS ============== 187 | // ========================================= 188 | 189 | // ====================== INDICATION SECTION ======================= 190 | /* 191 | ___ _ _ ____ ___ ____ _ _____ ___ ___ _ _ 192 | |_ _| | \ | | | _ \ |_ _| / ___| / \ |_ _| |_ _| / _ \ | \ | | 193 | | | | \| | | | | | | | | | / _ \ | | | | | | | | | \| | 194 | | | | |\ | | |_| | | | | |___ / ___ \ | | | | | |_| | | |\ | 195 | |___| |_| \_| |____/ |___| \____| /_/ \_\ |_| |___| \___/ |_| \_| 196 | */ 197 | 198 | enum class GPSStatuses 199 | { 200 | correct, 201 | invalid, 202 | connectionError 203 | }; 204 | 205 | enum class LoRaStatuses 206 | { 207 | dataReceived, 208 | dataTransmitted, 209 | error 210 | }; 211 | 212 | enum class BLEStatuses 213 | { 214 | output, 215 | input, 216 | error 217 | }; 218 | 219 | enum class MemoryStatuses 220 | { 221 | read, 222 | write, 223 | crcError 224 | }; 225 | 226 | class INDICATION 227 | { 228 | public: 229 | INDICATION(uint8_t gpsLedPin, 230 | uint8_t loraLedPin, 231 | uint8_t bleLedPin, 232 | uint8_t memoryLedPin); 233 | void gps(GPSStatuses status); 234 | void lora(LoRaStatuses status); 235 | void ble(BLEStatuses status); 236 | void memory(MemoryStatuses status); 237 | void loop(); 238 | 239 | private: 240 | uint8_t _gpsLedPin; 241 | uint8_t _loraLedPin; 242 | uint8_t _bleLedPin; 243 | uint8_t _memoryLedPin; 244 | 245 | bool loraLedStatus = false; 246 | const byte loraDelaySec = 1; 247 | unsigned long lastLoraUpdateTime; 248 | }; 249 | 250 | INDICATION::INDICATION(uint8_t gpsLedPin, uint8_t loraLedPin, uint8_t bleLedPin, uint8_t memoryLedPin) 251 | { 252 | _gpsLedPin = gpsLedPin; 253 | _loraLedPin = loraLedPin; 254 | _bleLedPin = bleLedPin; 255 | _memoryLedPin = memoryLedPin; 256 | 257 | pinMode(_gpsLedPin, OUTPUT); // GPS indicator 258 | pinMode(_loraLedPin, OUTPUT); // LORA indicator 259 | } 260 | 261 | void INDICATION::gps(GPSStatuses status) 262 | { 263 | switch (status) 264 | { 265 | case GPSStatuses::correct: 266 | digitalWrite(_gpsLedPin, HIGH); 267 | break; 268 | case GPSStatuses::invalid: 269 | digitalWrite(_gpsLedPin, LOW); 270 | break; 271 | case GPSStatuses::connectionError: 272 | digitalWrite(_gpsLedPin, LOW); 273 | break; 274 | default: 275 | digitalWrite(_gpsLedPin, LOW); 276 | } 277 | } 278 | 279 | void INDICATION::lora(LoRaStatuses status) 280 | { 281 | switch (status) 282 | { 283 | case LoRaStatuses::dataReceived: 284 | digitalWrite(_loraLedPin, HIGH); 285 | loraLedStatus = true; 286 | break; 287 | case LoRaStatuses::dataTransmitted: 288 | digitalWrite(_loraLedPin, HIGH); 289 | loraLedStatus = true; 290 | break; 291 | case LoRaStatuses::error: 292 | digitalWrite(_loraLedPin, LOW); 293 | loraLedStatus = false; 294 | break; 295 | default: 296 | digitalWrite(_loraLedPin, LOW); 297 | loraLedStatus = false; 298 | } 299 | lastLoraUpdateTime = millis(); 300 | } 301 | 302 | void INDICATION::ble(BLEStatuses status) 303 | { 304 | } 305 | 306 | void INDICATION::memory(MemoryStatuses status) 307 | { 308 | } 309 | 310 | void INDICATION::loop() 311 | { 312 | if (loraLedStatus) 313 | { 314 | if ((lastLoraUpdateTime + (loraDelaySec * 1000)) < millis()) 315 | { 316 | digitalWrite(_loraLedPin, LOW); 317 | 318 | loraLedStatus = false; 319 | } 320 | } 321 | } 322 | 323 | // ======================== UTILITES =============================== 324 | /* 325 | _ _ _ _ _ 326 | _ _ | |_ (_) | | (_) | |_ ___ ___ 327 | | | | | | __| | | | | | | | __| / _ \ / __| 328 | | |_| | | |_ | | | | | | | |_ | __/ \__ \ 329 | \__,_| \__| |_| |_| |_| \__| \___| |___/ 330 | */ 331 | 332 | // CRC 333 | unsigned char calculateCRC(unsigned char *buffer, unsigned char size) { 334 | byte crc = 0; 335 | for (byte i = 0; i < size; i++) { 336 | byte data = buffer[i]; 337 | for (int j = 8; j > 0; j--) { 338 | crc = ((crc ^ data) & 1) ? (crc >> 1) ^ 0x8C : (crc >> 1); 339 | data >>= 1; 340 | } 341 | } 342 | return crc; 343 | } 344 | 345 | // ========================== BLE SECTION ============================== 346 | /* 347 | ____ _ _ _ _____ _____ ___ ___ _____ _ _ 348 | | __ ) | | | | | | | ____| |_ _| / _ \ / _ \ |_ _| | | | | 349 | | _ \ | | | | | | | _| | | | | | | | | | | | | | |_| | 350 | | |_) | | |___ | |_| | | |___ | | | |_| | | |_| | | | | _ | 351 | |____/ |_____| \___/ |_____| |_| \___/ \___/ |_| |_| |_| 352 | */ 353 | 354 | class BLE_HM10 355 | { 356 | public: 357 | BLE_HM10(); 358 | void setup(); 359 | String read(); 360 | void send(String * package); 361 | 362 | private: 363 | const byte MTU = 22; 364 | void sendCommand(const String command); 365 | }; 366 | 367 | BLE_HM10::BLE_HM10() 368 | { 369 | } 370 | 371 | void BLE_HM10::setup() 372 | { 373 | #if DEBUG_MODE && DEBUG_BLE 374 | Serial.print(F("📲[BLE: ready for work ✅. Maximum Transmission Unit (MTU) = ")); 375 | Serial.print(MTU); 376 | Serial.println(F("]")); 377 | #endif 378 | #if !DEBUG_MODE 379 | sendCommand(F("AT")); 380 | sendCommand(F("AT+NAMEAGLoRa")); 381 | sendCommand(F("AT+ROLE0")); 382 | #endif 383 | } 384 | 385 | String BLE_HM10::read() 386 | { 387 | String result = ""; 388 | while (Serial.available()) 389 | { 390 | result += Serial.readString(); // read until timeout 391 | } 392 | result.trim(); // remove any \r \n whitespace at the end of the String 393 | 394 | return result; 395 | } 396 | 397 | void BLE_HM10::send(String *package) 398 | { 399 | #if DEBUG_MODE && DEBUG_BLE 400 | Serial.print(F("📲[BLE: 📫 Sending: ")); 401 | Serial.print(*package); 402 | 403 | Serial.print(F("\t")); 404 | for (byte i = 1; i <= MTU; ++i) 405 | { 406 | Serial.print(i % 10); 407 | } 408 | Serial.print(F(" (MTU = ")); 409 | Serial.print(MTU); 410 | Serial.println(F(")")); 411 | #endif 412 | 413 | bool isStringNotEmpty = true; 414 | while (isStringNotEmpty) 415 | { 416 | #if DEBUG_MODE && DEBUG_BLE 417 | Serial.print(F("\t")); 418 | #endif 419 | String nextSendMTU = package->substring(0, MTU); 420 | package->remove(0, MTU); 421 | isStringNotEmpty = package->length() != 0; 422 | 423 | #if !DEBUG_MODE && !DEBUG_BLE 424 | // important part 425 | Serial.print(nextSendMTU); // here we send data to BLE 426 | delay(10); 427 | #endif 428 | 429 | #if DEBUG_MODE && DEBUG_BLE 430 | if (isStringNotEmpty) 431 | Serial.println(F(" ⮐")); 432 | #endif 433 | } 434 | } 435 | 436 | void BLE_HM10::sendCommand(const String command) 437 | { 438 | Serial.println(command); 439 | delay(200); // wait some time 440 | while (Serial.available()) 441 | { 442 | Serial.read(); 443 | } 444 | } 445 | 446 | // ======================= GPS SECTION ===================== 447 | /* 448 | ____ ____ ____ 449 | / ___| | _ \ / ___| 450 | | | _ | |_) | \___ \ 451 | | |_| | | __/ ___) | 452 | \____| |_| |____/ 453 | */ 454 | 455 | class GPS 456 | { 457 | SoftwareSerial gpsPort; 458 | public: 459 | GPS(uint8_t pinRx, uint8_t pinTx, long speed, INDICATION * indication); 460 | void setup(); 461 | void updateLocation(DATA *dataPackage); 462 | 463 | private: 464 | bool _debugMode; 465 | INDICATION * _indication; 466 | void printReadingIndication(unsigned long start, unsigned int delay); 467 | char _readingIndicator = 0; 468 | bool processRMCSentence(String sentence, DATA *dataPackage); 469 | bool processGGASentence(String sentence, DATA *dataPackage); 470 | float convertDegMinToDecDeg(float degMin); 471 | }; 472 | 473 | GPS::GPS(uint8_t pinRx, uint8_t pinTx, long speed, INDICATION *indication) : gpsPort(pinRx, pinTx) 474 | { 475 | gpsPort.begin(speed); 476 | _indication = indication; 477 | } 478 | 479 | void GPS::setup() 480 | { 481 | #if DEBUG_MODE && DEBUG_GPS 482 | Serial.println(F("📡[GPS: Start GPS configuration.]")); 483 | #endif 484 | } 485 | 486 | void GPS::printReadingIndication(unsigned long start, unsigned int delay) 487 | { 488 | #if DEBUG_MODE && DEBUG_GPS 489 | byte progress = (10 * (millis() - start)) / delay; 490 | if (progress != _readingIndicator) 491 | { 492 | _readingIndicator = progress; 493 | Serial.print(F("⏳")); 494 | } 495 | #endif 496 | } 497 | 498 | void GPS::updateLocation(DATA *dataPackage) 499 | { 500 | #if DEBUG_MODE && DEBUG_GPS 501 | Serial.print(F("📡[GPS reading: ")); 502 | #endif 503 | 504 | char c; 505 | String sentence = ""; 506 | 507 | // For three seconds we parse GPS data and report some key values 508 | const unsigned int readingDelay = 3000; 509 | bool gpsReadingComplited = false; 510 | bool ggaCatched = false; 511 | bool rmcCatched = false; 512 | 513 | gpsPort.listen(); 514 | 515 | for (unsigned long start = millis(); millis() - start < readingDelay;) 516 | { 517 | printReadingIndication(start, readingDelay); 518 | while (gpsPort.available() > 0) 519 | { 520 | c = gpsPort.read(); 521 | if (c == '$') 522 | { 523 | // Start of a new sentence 524 | sentence = ""; 525 | } 526 | else if (c == '\n') 527 | { 528 | // End of a sentence process it 529 | if (processRMCSentence(sentence, dataPackage)) 530 | rmcCatched = true; 531 | if (processGGASentence(sentence, dataPackage)) 532 | ggaCatched = true; 533 | 534 | if (ggaCatched && rmcCatched) 535 | { 536 | gpsReadingComplited = true; 537 | break; 538 | } 539 | } 540 | else 541 | { 542 | // Add character to the sentence 543 | sentence += c; 544 | } 545 | } 546 | if (gpsReadingComplited) 547 | break; 548 | } 549 | 550 | if (dataPackage->gpsValid) 551 | { 552 | #if DEBUG_MODE && DEBUG_GPS 553 | Serial.println(" ✅ GPS data is correct.]"); 554 | #endif 555 | _indication->gps(GPSStatuses::correct); // GPS is valid 556 | } 557 | else 558 | { 559 | #if DEBUG_MODE && DEBUG_GPS 560 | Serial.println(F("❌ No valid data.]")); 561 | #endif 562 | _indication->gps(GPSStatuses::invalid); // GPS is invalid 563 | } 564 | } 565 | 566 | bool GPS::processRMCSentence(String sentence, DATA *dataPackage) 567 | { 568 | if (sentence.startsWith("GPRMC")) 569 | { 570 | #if DEBUG_MODE && DEBUG_GPS 571 | Serial.print("✨"); 572 | #endif 573 | 574 | // Process RMC sentence 575 | int commaIndex = sentence.indexOf(','); 576 | sentence = sentence.substring(commaIndex + 1); 577 | 578 | commaIndex = sentence.indexOf(','); 579 | sentence = sentence.substring(commaIndex + 1); 580 | 581 | commaIndex = sentence.indexOf(','); 582 | String status = sentence.substring(0, commaIndex); 583 | sentence = sentence.substring(commaIndex + 1); 584 | 585 | commaIndex = sentence.indexOf(','); 586 | // String latStr = sentence.substring(0, commaIndex); 587 | sentence = sentence.substring(commaIndex + 1); 588 | 589 | commaIndex = sentence.indexOf(','); 590 | // String latDir = sentence.substring(0, commaIndex); 591 | sentence = sentence.substring(commaIndex + 1); 592 | 593 | commaIndex = sentence.indexOf(','); 594 | // String lonStr = sentence.substring(0, commaIndex); 595 | sentence = sentence.substring(commaIndex + 1); 596 | 597 | commaIndex = sentence.indexOf(','); 598 | // String lonDir = sentence.substring(0, commaIndex); 599 | sentence = sentence.substring(commaIndex + 1); 600 | 601 | commaIndex = sentence.indexOf(','); 602 | // String speedKnots = sentence.substring(0, commaIndex); 603 | sentence = sentence.substring(commaIndex + 1); 604 | 605 | commaIndex = sentence.indexOf(','); 606 | // String course = sentence.substring(0, commaIndex); 607 | sentence = sentence.substring(commaIndex + 1); 608 | 609 | commaIndex = sentence.indexOf(','); 610 | String dateStr = sentence.substring(0, commaIndex); 611 | 612 | if (status == "A") 613 | { 614 | // Convert date to year, month, and day 615 | dataPackage->day = dateStr.substring(0, 2).toInt(); 616 | ; 617 | dataPackage->month = dateStr.substring(2, 4).toInt(); 618 | dataPackage->year = dateStr.substring(4, 6).toInt(); 619 | 620 | return true; 621 | } 622 | } 623 | 624 | return false; 625 | } 626 | 627 | bool GPS::processGGASentence(String sentence, DATA *dataPackage) 628 | { 629 | if (sentence.startsWith("GPGGA")) 630 | { 631 | #if DEBUG_MODE && DEBUG_GPS 632 | Serial.print("✨"); 633 | #endif 634 | 635 | // Process GGA sentence 636 | int commaIndex = sentence.indexOf(','); 637 | String timeStr = sentence.substring(commaIndex + 1, commaIndex + 7); 638 | sentence = sentence.substring(commaIndex + 1); 639 | 640 | commaIndex = sentence.indexOf(','); 641 | sentence = sentence.substring(commaIndex + 1); 642 | 643 | commaIndex = sentence.indexOf(','); 644 | String latStr = sentence.substring(0, commaIndex); 645 | sentence = sentence.substring(commaIndex + 1); 646 | 647 | commaIndex = sentence.indexOf(','); 648 | String latDir = sentence.substring(0, commaIndex); 649 | sentence = sentence.substring(commaIndex + 1); 650 | 651 | commaIndex = sentence.indexOf(','); 652 | String lonStr = sentence.substring(0, commaIndex); 653 | sentence = sentence.substring(commaIndex + 1); 654 | 655 | commaIndex = sentence.indexOf(','); 656 | String lonDir = sentence.substring(0, commaIndex); 657 | sentence = sentence.substring(commaIndex + 1); 658 | 659 | commaIndex = sentence.indexOf(','); 660 | String fixQuality = sentence.substring(0, commaIndex); 661 | sentence = sentence.substring(commaIndex + 1); 662 | 663 | commaIndex = sentence.indexOf(','); 664 | String satStr = sentence.substring(0, commaIndex); 665 | sentence = sentence.substring(commaIndex + 1); 666 | 667 | commaIndex = sentence.indexOf(','); 668 | String hdopStr = sentence.substring(0, commaIndex); 669 | 670 | if (fixQuality.toInt() > 0) 671 | { 672 | dataPackage->gpsValid = true; 673 | dataPackage->lat = latStr.toFloat(); 674 | dataPackage->lon = lonStr.toFloat(); 675 | //dataPackage->sat = satStr.toInt(); // optional 676 | //dataPackage->hdop = hdopStr.toFloat(); // optional 677 | 678 | // Convert latitude and longitude to decimal degrees 679 | dataPackage->lat = convertDegMinToDecDeg(dataPackage->lat); 680 | dataPackage->lon = convertDegMinToDecDeg(dataPackage->lon); 681 | if (latDir == "S") 682 | dataPackage->lat = -dataPackage->lat; 683 | if (lonDir == "W") 684 | dataPackage->lon = -dataPackage->lon; 685 | 686 | // Convert time to hours, minutes, and seconds 687 | int hour = timeStr.substring(0, 2).toInt(); 688 | int minute = timeStr.substring(2, 4).toInt(); 689 | int second = timeStr.substring(4, 6).toInt(); 690 | dataPackage->hour = hour; 691 | dataPackage->minute = minute; 692 | dataPackage->second = second; 693 | 694 | return true; 695 | } 696 | } 697 | 698 | return false; 699 | } 700 | 701 | float GPS::convertDegMinToDecDeg(float degMin) 702 | { 703 | float min = fmod(degMin, 100.0); 704 | float deg = (degMin - min) / 100.0; 705 | return deg + (min / 60.0); 706 | } 707 | 708 | // ======================= LORA SECTION ===================== 709 | /* 710 | _ _ _ ____ _____ _ ___ ____ _ 711 | | | | | / \ | _ \ |_ _| | | / _ \ | _ \ / \ 712 | | | | | / _ \ | |_) | | | | | | | | | | |_) | / _ \ 713 | | |_| | / ___ \ | _ < | | | |___ | |_| | | _ < / ___ \ 714 | \___/ /_/ \_\ |_| \_\ |_| |_____| \___/ |_| \_\ /_/ \_\ 715 | */ 716 | 717 | struct LORADATA 718 | { 719 | DATA data; 720 | unsigned char ttl; // time to live (for mesh network) 721 | }; 722 | 723 | #ifdef EBYTE_E220 724 | class LORA 725 | { 726 | SoftwareSerial loraPort; 727 | LoRa_E220 e220ttl; 728 | public: 729 | LORA(uint8_t pinRx, uint8_t pinTx, uint8_t aux, uint8_t m0, uint8_t m1, INDICATION * indication); 730 | void setup(); 731 | void send(LORADATA *loraDataPackage); 732 | bool hasNewData(LORADATA *loraDataPackage); 733 | 734 | private: 735 | bool _debugMode; 736 | uint8_t _ledPin; 737 | INDICATION * _indication; 738 | ResponseStructContainer rsc; 739 | }; 740 | 741 | LORA::LORA(uint8_t pinRx, uint8_t pinTx, uint8_t aux, uint8_t m0, uint8_t m1, INDICATION *indication) : loraPort(pinRx, pinTx), 742 | e220ttl(&loraPort, aux, m0, m1) 743 | { 744 | loraPort.begin(LORA_START_SPEED); 745 | _indication = indication; 746 | } 747 | 748 | void LORA::setup() 749 | { 750 | #if DEBUG_MODE && DEBUG_LORA 751 | Serial.println(F("🛜 [LORA: Start configuration]")); 752 | #endif 753 | 754 | e220ttl.begin(); 755 | e220ttl.resetModule(); 756 | 757 | ResponseStructContainer c; 758 | c = e220ttl.getConfiguration(); 759 | Configuration configuration = *(Configuration *)c.data; 760 | delay(100); 761 | 762 | configuration.ADDL = 0x00; 763 | configuration.ADDH = 0x00; 764 | configuration.CHAN = 0x17; 765 | configuration.SPED.uartBaudRate = UART_BPS_57600; 766 | configuration.SPED.airDataRate = AIR_DATA_RATE_010_24; 767 | configuration.SPED.uartParity = MODE_00_8N1; 768 | 769 | configuration.OPTION.subPacketSetting = SPS_200_00; 770 | configuration.OPTION.RSSIAmbientNoise = RSSI_AMBIENT_NOISE_DISABLED; 771 | configuration.OPTION.transmissionPower = POWER_22; 772 | 773 | configuration.TRANSMISSION_MODE.enableRSSI = RSSI_DISABLED; 774 | configuration.TRANSMISSION_MODE.fixedTransmission = FT_TRANSPARENT_TRANSMISSION; 775 | configuration.TRANSMISSION_MODE.enableLBT = LBT_DISABLED; // monitoring before data transmitted 776 | configuration.TRANSMISSION_MODE.WORPeriod = WOR_2000_011; 777 | 778 | e220ttl.setConfiguration(configuration, WRITE_CFG_PWR_DWN_SAVE); 779 | delay(100); 780 | #if DEBUG_MODE && DEBUG_LORA 781 | Serial.print(F("\t🛜 [LORA current config: channel = ")); 782 | Serial.print(configuration.getChannelDescription()); 783 | Serial.print(F(" , airDataRate = ")); 784 | Serial.print(configuration.SPED.getAirDataRateDescription()); 785 | Serial.print(F(" , transmissionPower = ")); 786 | Serial.print(configuration.OPTION.getTransmissionPowerDescription()); 787 | Serial.println(F("]")); 788 | #endif 789 | 790 | loraPort.end(); 791 | loraPort.begin(57600); 792 | } 793 | 794 | void LORA::send(LORADATA *loraDataPacket) 795 | { 796 | loraPort.listen(); 797 | const byte LORADATASIZE = sizeof(LORADATA); 798 | 799 | #if DEBUG_MODE && DEBUG_LORA 800 | Serial.print(F("🛜 [LoRa: Sending 📫, ")); 801 | Serial.print(LORADATASIZE); 802 | Serial.print(F(" bytes are ready to send")); 803 | Serial.print(F(" ➜ ")); 804 | Serial.print(loraDataPacket->data.name); 805 | Serial.print(F(" / ")); 806 | Serial.print(loraDataPacket->data.lat, 6); 807 | Serial.print(F(" ")); 808 | Serial.print(loraDataPacket->data.lon, 6); 809 | Serial.print(F(" / ")); 810 | Serial.print(loraDataPacket->data.year); 811 | Serial.print(F("-")); 812 | if (loraDataPacket->data.month < 10) 813 | Serial.print(F("0")); 814 | Serial.print(loraDataPacket->data.month); 815 | Serial.print(F("-")); 816 | if (loraDataPacket->data.day < 10) 817 | Serial.print(F("0")); 818 | Serial.print(loraDataPacket->data.day); 819 | Serial.print(F(" ")); 820 | Serial.print(loraDataPacket->data.hour); 821 | Serial.print(F(":")); 822 | if (loraDataPacket->data.minute < 10) 823 | Serial.print(F("0")); 824 | Serial.print(loraDataPacket->data.minute); 825 | Serial.print(F(" (UTC)")); 826 | Serial.print(F(" TTL=")); 827 | Serial.print(loraDataPacket->ttl); 828 | Serial.print(F("] ➜ ")); 829 | 830 | #endif 831 | 832 | ResponseStatus rs = e220ttl.sendMessage(loraDataPacket, LORADATASIZE); 833 | 834 | #if DEBUG_MODE && DEBUG_LORA 835 | Serial.print(F("[Status: ")); 836 | Serial.print(rs.getResponseDescription()); 837 | #endif 838 | 839 | if (rs.code == 1) 840 | { 841 | #if DEBUG_MODE && DEBUG_LORA 842 | Serial.print(F(" 🆗")); 843 | #endif 844 | _indication->lora(LoRaStatuses::dataTransmitted); 845 | } 846 | else 847 | { 848 | #if DEBUG_MODE && DEBUG_LORA 849 | Serial.print(F(" 🚨")); 850 | #endif 851 | _indication->lora(LoRaStatuses::error); 852 | } 853 | 854 | #if DEBUG_MODE && DEBUG_LORA 855 | Serial.println(F("]")); 856 | Serial.println(); 857 | #endif 858 | } 859 | 860 | bool LORA::hasNewData(LORADATA *loraDataPacket) 861 | { 862 | if (e220ttl.available() > 1) 863 | { 864 | #if DEBUG_MODE && DEBUG_LORA 865 | Serial.println(F("🛜 [LORA: we have new data 🥳]")); 866 | #endif 867 | 868 | rsc = e220ttl.receiveMessage(sizeof(LORADATA)); 869 | if (rsc.status.code != 1) 870 | { 871 | #if DEBUG_MODE && DEBUG_LORA 872 | Serial.println(F("🛜 [LORA error: ❌ status - ")); 873 | Serial.println(rsc.status.getResponseDescription()); 874 | Serial.println(F("]")); 875 | #endif 876 | _indication->lora(LoRaStatuses::error); 877 | return false; 878 | } 879 | else 880 | { 881 | memcpy(loraDataPacket, (LORADATA *)rsc.data, sizeof(LORADATA)); 882 | rsc.close(); 883 | } 884 | _indication->lora(LoRaStatuses::dataReceived); 885 | return true; 886 | } 887 | return false; 888 | } 889 | #endif 890 | 891 | #ifdef EBYTE_E32 892 | class LORA 893 | { 894 | SoftwareSerial loraPort; 895 | LoRa_E32 e32ttl; 896 | public: 897 | LORA(uint8_t pinRx, uint8_t pinTx, uint8_t aux, uint8_t m0, uint8_t m1, INDICATION * indication); 898 | void setup(); 899 | void send(LORADATA *loraDataPackage); 900 | bool hasNewData(LORADATA *loraDataPackage); 901 | 902 | private: 903 | bool _debugMode; 904 | uint8_t _ledPin; 905 | INDICATION * _indication; 906 | ResponseStructContainer rsc; 907 | }; 908 | 909 | LORA::LORA(uint8_t pinRx, uint8_t pinTx, uint8_t aux, uint8_t m0, uint8_t m1, INDICATION *indication) : loraPort(pinRx, pinTx), 910 | e32ttl(&loraPort, aux, m0, m1) 911 | { 912 | loraPort.begin(LORA_START_SPEED); 913 | _indication = indication; 914 | } 915 | 916 | void LORA::setup() 917 | { 918 | #if DEBUG_MODE && DEBUG_LORA 919 | Serial.println(F("🛜 [LORA: Start configuration]")); 920 | #endif 921 | 922 | e32ttl.begin(); 923 | e32ttl.resetModule(); 924 | 925 | ResponseStructContainer c; 926 | c = e32ttl.getConfiguration(); 927 | Configuration configuration = *(Configuration *)c.data; 928 | delay(100); 929 | 930 | configuration.ADDL = 0x0; 931 | configuration.ADDH = 0x1; 932 | configuration.CHAN = 0x17; // Channel. (410 + CHAN*1MHz) MHz. Default 17H (433MHz) 933 | configuration.OPTION.fec = FEC_1_ON; // FEC_0_OFF / FEC_1_ON (default) - Forward Error Correction Switch 934 | configuration.OPTION.fixedTransmission = FT_TRANSPARENT_TRANSMISSION; // FT_TRANSPARENT_TRANSMISSION (default) / FT_FIXED_TRANSMISSION 935 | configuration.OPTION.ioDriveMode = IO_D_MODE_PUSH_PULLS_PULL_UPS; // IO_D_MODE_OPEN_COLLECTOR / IO_D_MODE_PUSH_PULLS_PULL_UPS 936 | configuration.OPTION.transmissionPower = POWER_20; // 21/24/27/30 dBm if define E32_TTL_1W 937 | configuration.OPTION.wirelessWakeupTime = WAKE_UP_250; // 250 (default)/500/750/1000/1250/1500/1750/2000 938 | configuration.SPED.airDataRate = AIR_DATA_RATE_010_24; // AIR_DATA_RATE_000_03 - 0.3kbps 939 | // AIR_DATA_RATE_001_12 - 1.2kbps 940 | // AIR_DATA_RATE_010_24 - 2.4kbps (default) 941 | // AIR_DATA_RATE_011_48 - 4.8kbps 942 | // AIR_DATA_RATE_100_96 - 9.6kbps 943 | // AIR_DATA_RATE_101_192 - 19.2kbps 944 | // AIR_DATA_RATE_110_192 - 19.2kbps (same 101) 945 | // AIR_DATA_RATE_111_192 - 19.2kbps (same 101) 946 | configuration.SPED.uartBaudRate = UART_BPS_9600; 947 | configuration.SPED.uartParity = MODE_00_8N1; 948 | 949 | e32ttl.setConfiguration(configuration, WRITE_CFG_PWR_DWN_SAVE); 950 | delay(100); 951 | #if DEBUG_MODE && DEBUG_LORA 952 | Serial.print(F("\t🛜 [LORA current config: channel = ")); 953 | Serial.print(configuration.getChannelDescription()); 954 | 955 | Serial.print(F(" , airDataRate = ")); 956 | Serial.print(configuration.SPED.getAirDataRate()); 957 | 958 | Serial.print(F(" , transmissionPower = ")); 959 | Serial.print(configuration.OPTION.getTransmissionPowerDescription()); 960 | Serial.println(F("]")); 961 | #endif 962 | 963 | loraPort.end(); 964 | loraPort.begin(57600); 965 | } 966 | 967 | void LORA::send(LORADATA *loraDataPacket) 968 | { 969 | loraPort.listen(); 970 | const byte LORADATASIZE = sizeof(LORADATA); 971 | 972 | #if DEBUG_MODE && DEBUG_LORA 973 | Serial.print(F("🛜 [LoRa: Sending 📫, ")); 974 | Serial.print(LORADATASIZE); 975 | Serial.print(F(" bytes are ready to send")); 976 | Serial.print(F(" ➜ ")); 977 | Serial.print(loraDataPacket->data.name); 978 | Serial.print(F(" / ")); 979 | Serial.print(loraDataPacket->data.lat, 6); 980 | Serial.print(F(" ")); 981 | Serial.print(loraDataPacket->data.lon, 6); 982 | Serial.print(F(" / ")); 983 | Serial.print(loraDataPacket->data.year); 984 | Serial.print(F("-")); 985 | if (loraDataPacket->data.month < 10) 986 | Serial.print(F("0")); 987 | Serial.print(loraDataPacket->data.month); 988 | Serial.print(F("-")); 989 | if (loraDataPacket->data.day < 10) 990 | Serial.print(F("0")); 991 | Serial.print(loraDataPacket->data.day); 992 | Serial.print(F(" ")); 993 | Serial.print(loraDataPacket->data.hour); 994 | Serial.print(F(":")); 995 | if (loraDataPacket->data.minute < 10) 996 | Serial.print(F("0")); 997 | Serial.print(loraDataPacket->data.minute); 998 | Serial.print(F(" (UTC)")); 999 | Serial.print(F(" TTL=")); 1000 | Serial.print(loraDataPacket->ttl); 1001 | Serial.print(F("] ➜ ")); 1002 | #endif 1003 | 1004 | ResponseStatus rs = e32ttl.sendMessage(loraDataPacket, LORADATASIZE); 1005 | 1006 | #if DEBUG_MODE && DEBUG_LORA 1007 | Serial.print(F("[Status: ")); 1008 | Serial.print(rs.getResponseDescription()); 1009 | #endif 1010 | 1011 | if (rs.code == 1) 1012 | { 1013 | #if DEBUG_MODE && DEBUG_LORA 1014 | Serial.print(F(" 🆗")); 1015 | #endif 1016 | _indication->lora(LoRaStatuses::dataTransmitted); 1017 | } 1018 | else 1019 | { 1020 | #if DEBUG_MODE && DEBUG_LORA 1021 | Serial.print(F(" 🚨")); 1022 | #endif 1023 | _indication->lora(LoRaStatuses::error); 1024 | } 1025 | 1026 | #if DEBUG_MODE && DEBUG_LORA 1027 | Serial.println(F("]")); 1028 | Serial.println(); 1029 | #endif 1030 | } 1031 | 1032 | bool LORA::hasNewData(LORADATA *loraDataPacket) 1033 | { 1034 | if (e32ttl.available() > 1) 1035 | { 1036 | #if DEBUG_MODE && DEBUG_LORA 1037 | Serial.println(F("🛜 [LORA: we have new data 🥳]")); 1038 | #endif 1039 | 1040 | rsc = e32ttl.receiveMessage(sizeof(LORADATA)); 1041 | if (rsc.status.code != 1) 1042 | { 1043 | #if DEBUG_MODE && DEBUG_LORA 1044 | Serial.println(F("🛜 [LORA error: ❌ status - ")); 1045 | Serial.println(rsc.status.getResponseDescription()); 1046 | Serial.println(F("]")); 1047 | #endif 1048 | _indication->lora(LoRaStatuses::error); 1049 | return false; 1050 | } 1051 | else 1052 | { 1053 | memcpy(loraDataPacket, (LORADATA *)rsc.data, sizeof(LORADATA)); 1054 | rsc.close(); 1055 | } 1056 | _indication->lora(LoRaStatuses::dataReceived); 1057 | return true; 1058 | } 1059 | return false; 1060 | } 1061 | #endif 1062 | 1063 | // ================= TESTS SECTION ================= 1064 | /* 1065 | _____ _____ ____ _____ ____ 1066 | |_ _| | ____| / ___| |_ _| / ___| 1067 | | | | _| \___ \ | | \___ \ 1068 | | | | |___ ___) | | | ___) | 1069 | |_| |_____| |____/ |_| |____/ 1070 | */ 1071 | 1072 | class TESTS 1073 | { 1074 | public: 1075 | bool hasNewDataEveryXSec(LORADATA *loraDataPacket, GPS *gps, byte interval); 1076 | 1077 | private: 1078 | unsigned long _timeOfLastSendedPacket = 0; 1079 | }; 1080 | 1081 | bool TESTS::hasNewDataEveryXSec(LORADATA *loraDataPacket, GPS *gps, byte interval) 1082 | { 1083 | int _intervarSec = interval * 1000; 1084 | if (_timeOfLastSendedPacket + _intervarSec < millis()) 1085 | { 1086 | #if DEBUG_MODE 1087 | Serial.println(); 1088 | Serial.println(F("💛💛💛 [TEST: virtual tracker transmitted the data] 💛💛💛")); 1089 | #endif 1090 | 1091 | gps->updateLocation(&loraDataPacket->data); // we need an actual time 1092 | strcpy(loraDataPacket->data.name, OTHER_NAME); 1093 | loraDataPacket->data.lat = 45.455631; 1094 | loraDataPacket->data.lon = 54.084960; 1095 | loraDataPacket->ttl = TTL; 1096 | 1097 | #if DEBUG_MODE 1098 | Serial.println(); 1099 | #endif 1100 | 1101 | _timeOfLastSendedPacket = millis(); 1102 | return true; 1103 | } 1104 | 1105 | return false; 1106 | } 1107 | 1108 | /* 1109 | 1110 | _ __ ___ ___ _ __ ___ ___ _ __ _ _ 1111 | | '_ ` _ \ / _ \ | '_ ` _ \ / _ \ | '__| | | | | 1112 | | | | | | | | __/ | | | | | | | (_) | | | | |_| | 1113 | |_| |_| |_| \___| |_| |_| |_| \___/ |_| \__, | 1114 | |___/ 1115 | */ 1116 | 1117 | class IMemory { // interface 1118 | public: 1119 | virtual void setup() = 0; 1120 | 1121 | virtual void clearAllPositions() = 0; 1122 | virtual bool checkUnique(DATA *newPoint) = 0; 1123 | virtual unsigned int save(DATA *newData) = 0; 1124 | virtual DATA * load(unsigned int index) = 0; 1125 | 1126 | virtual bool checkCRC() = 0; // all memory 1127 | virtual bool checkCRC(unsigned int index) = 0; 1128 | 1129 | virtual unsigned int getSize() = 0; 1130 | virtual unsigned int getIndex() = 0; 1131 | virtual bool getStorageOverwrite() = 0; 1132 | }; 1133 | 1134 | struct EEPROMDATA { 1135 | unsigned char counter; 1136 | DATA data; 1137 | unsigned char crc; 1138 | }; 1139 | 1140 | class EEPROMAglora : public IMemory 1141 | { 1142 | public: 1143 | EEPROMAglora(); 1144 | void setup(); 1145 | bool checkUnique(DATA *newPoint); 1146 | unsigned int save(DATA *newData); 1147 | DATA *load(unsigned int index); 1148 | void clearAllPositions(); 1149 | bool checkCRC(); 1150 | bool checkCRC(unsigned int index); 1151 | unsigned int getSize(); 1152 | unsigned int getIndex(); 1153 | bool getStorageOverwrite(); 1154 | 1155 | private: 1156 | EEPROMDATA eepromdata; 1157 | unsigned int EEPROMStorageIndex = 0; // index in memory (address = EEPROMStorageIndex * EEPROMDataSize) 1158 | unsigned int incrementCounter = 0; // min 0, max 254 (because default EEPROM is 255) 1159 | 1160 | unsigned int EEPROMStorageSize; 1161 | byte dataSize; 1162 | bool storageOverwrite = false; 1163 | 1164 | const unsigned char EEPROMDataSize = sizeof(EEPROMDATA); 1165 | }; 1166 | 1167 | EEPROMAglora::EEPROMAglora() 1168 | { 1169 | dataSize = sizeof(DATA); 1170 | EEPROMStorageSize = (EEPROM.length() - EEPROM_BEGIN_ADDRESS) / EEPROMDataSize; 1171 | } 1172 | 1173 | void EEPROMAglora::setup() 1174 | { 1175 | EEPROMStorageIndex = 0; 1176 | incrementCounter = 0; 1177 | storageOverwrite = false; 1178 | 1179 | #if DEBUG_MODE && DEBUG_MEMORY 1180 | Serial.print(F("📀[EEPROM storage: Start EEPROM initialization. Size of memory: ")); 1181 | Serial.print(EEPROM.length()); 1182 | Serial.println(F(" bytes]")); 1183 | Serial.print(F("📀[EEPROM storage: ")); 1184 | Serial.print(EEPROMStorageSize); 1185 | Serial.println(F(" track points can be saved.]")); 1186 | Serial.println(F("\tCRC check symbols: ✅ CRC OK, ⛔️ CRC ERROR, ⬜ empty memory cell, 👉 - current cell")); 1187 | Serial.println(F("\tFormat: {COUNTER}➜ {CRC} {CRC check symbol}, 255 - default EEPROM value ")); 1188 | Serial.print(F("\tFinding the index of the last record. Read memory... ")); 1189 | #endif 1190 | 1191 | unsigned int prevIncCounter = 0; 1192 | 1193 | for (unsigned int i = 0; i < EEPROMStorageSize; i++) 1194 | { 1195 | EEPROM.get(i * EEPROMDataSize + EEPROM_BEGIN_ADDRESS, eepromdata); 1196 | 1197 | #if DEBUG_MODE && DEBUG_MEMORY 1198 | Serial.print(F(".")); 1199 | if ((i + 1) % 50 == 0) 1200 | Serial.println(); 1201 | #endif 1202 | 1203 | if (eepromdata.counter == 255) 1204 | { // check empty EEPROM cell 1205 | if (i == 0) 1206 | { 1207 | break; 1208 | } 1209 | EEPROMStorageIndex = i; 1210 | incrementCounter = prevIncCounter + 1; 1211 | if (incrementCounter >= 255) 1212 | { 1213 | incrementCounter = 0; 1214 | } 1215 | break; 1216 | } 1217 | 1218 | if (abs(eepromdata.counter - prevIncCounter) > 1) 1219 | { // not in sequence 1220 | if (i != 0) 1221 | { 1222 | if (prevIncCounter != 254) 1223 | { // exclude the option ...252,253,254,0,1,2... 1224 | EEPROMStorageIndex = i; 1225 | incrementCounter = prevIncCounter + 1; 1226 | if (incrementCounter >= 255) 1227 | { 1228 | incrementCounter = 0; 1229 | } 1230 | if (eepromdata.counter != 255) 1231 | { 1232 | storageOverwrite = true; 1233 | } 1234 | break; 1235 | } 1236 | } 1237 | } 1238 | prevIncCounter = eepromdata.counter; 1239 | } 1240 | 1241 | #if DEBUG_MODE && DEBUG_MEMORY 1242 | Serial.println(); 1243 | Serial.print(F("\tNext record will be ")); 1244 | Serial.print(EEPROMStorageIndex + 1); 1245 | Serial.print(F(",\tstorageOverwrite = ")); 1246 | Serial.println(storageOverwrite); 1247 | #endif 1248 | } 1249 | 1250 | bool EEPROMAglora::checkUnique(DATA *newPoint) 1251 | { 1252 | if (strcmp(newPoint->name, NAME) == 0) 1253 | { 1254 | #if DEBUG_MODE && DEBUG_MEMORY 1255 | Serial.println(F("📀[EEPROM storage: returned package 🔄 ]")); 1256 | #endif 1257 | return false; 1258 | } 1259 | 1260 | for (unsigned int i = 0; i < EEPROMStorageSize; i++) 1261 | { 1262 | DATA *eepromPoint = load(i); 1263 | if ((newPoint->name == eepromPoint->name) && 1264 | (newPoint->year == eepromPoint->year) && 1265 | (newPoint->month == eepromPoint->month) && 1266 | (newPoint->day == eepromPoint->day) && 1267 | (newPoint->hour == eepromPoint->hour) && 1268 | (newPoint->minute == eepromPoint->minute) && 1269 | (newPoint->second == eepromPoint->second)) 1270 | { 1271 | #if DEBUG_MODE && DEBUG_MEMORY 1272 | Serial.println(F("📀[EEPROM storage: data already exist‼️⛔️]")); 1273 | #endif 1274 | return false; 1275 | } 1276 | } 1277 | #if DEBUG_MODE && DEBUG_MEMORY 1278 | Serial.println(F("📀[EEPROM storage: new data checked ✅]")); 1279 | #endif 1280 | return true; 1281 | } 1282 | 1283 | /// @brief Save data to storage 1284 | /// @param newData that needs to be added to the storage 1285 | /// @return the index of added data 1286 | unsigned int EEPROMAglora::save(DATA *newData) 1287 | { 1288 | eepromdata.counter = incrementCounter; 1289 | eepromdata.data = *newData; 1290 | eepromdata.crc = calculateCRC((unsigned char *)newData, sizeof(DATA)); 1291 | EEPROM.put(EEPROMStorageIndex * EEPROMDataSize + EEPROM_BEGIN_ADDRESS, eepromdata); 1292 | 1293 | #if DEBUG_MODE && DEBUG_MEMORY 1294 | Serial.print(F("📀[EEPROM storage: New data added. Address: ")); 1295 | Serial.print(EEPROMStorageIndex * EEPROMDataSize + EEPROM_BEGIN_ADDRESS); 1296 | Serial.print(F(", index: ")); 1297 | Serial.print(EEPROMStorageIndex); 1298 | Serial.print(F(", counter: ")); 1299 | Serial.print(eepromdata.counter); 1300 | Serial.print(F(", CRC: ")); 1301 | Serial.print(eepromdata.crc); 1302 | Serial.println(F("]")); 1303 | #endif 1304 | 1305 | EEPROMStorageIndex++; 1306 | if (EEPROMStorageIndex >= EEPROMStorageSize) 1307 | { 1308 | EEPROMStorageIndex = 0; 1309 | } 1310 | 1311 | incrementCounter++; 1312 | if (incrementCounter >= 255) 1313 | { 1314 | incrementCounter = 0; 1315 | } 1316 | 1317 | return incrementCounter; 1318 | } 1319 | 1320 | DATA *EEPROMAglora::load(unsigned int index) 1321 | { 1322 | EEPROM.get(index * EEPROMDataSize + EEPROM_BEGIN_ADDRESS, eepromdata); 1323 | return &eepromdata.data; 1324 | } 1325 | 1326 | void EEPROMAglora::clearAllPositions() 1327 | { 1328 | #if DEBUG_MODE && DEBUG_MEMORY 1329 | const byte rowLength = 120; 1330 | int cellsCounter = 0; 1331 | Serial.println(F("📀[EEPROM storage: clearing memory.]")); 1332 | Serial.print(F("\t")); 1333 | #endif 1334 | 1335 | unsigned char memoryCell = 0; 1336 | for (unsigned int i = 0; i < EEPROM.length(); i++) 1337 | { 1338 | EEPROM.get(i + EEPROM_BEGIN_ADDRESS, memoryCell); 1339 | if (memoryCell != 255) 1340 | { // 255 - default value in EEPROM 1341 | memoryCell = 255; 1342 | EEPROM.put(i + EEPROM_BEGIN_ADDRESS, memoryCell); 1343 | #if DEBUG_MODE && DEBUG_MEMORY 1344 | Serial.print(F("#")); 1345 | if (++cellsCounter % rowLength == 0) 1346 | { 1347 | Serial.println(); 1348 | Serial.print(F("\t")); 1349 | } 1350 | #endif 1351 | } 1352 | } 1353 | EEPROMStorageIndex = 0; 1354 | incrementCounter = 0; 1355 | storageOverwrite = false; 1356 | 1357 | #if DEBUG_MODE && DEBUG_MEMORY 1358 | Serial.println(); 1359 | #endif 1360 | } 1361 | 1362 | bool EEPROMAglora::checkCRC() 1363 | { 1364 | const byte rowLength = 12; // how many characters will be printed in a row 1365 | const byte rowDivider = 4; // split string for better view 1366 | bool result = true; 1367 | 1368 | #if DEBUG_MODE && DEBUG_MEMORY 1369 | Serial.print(F("📀[EEPROM storage: checking CRC, ")); 1370 | Serial.print(EEPROMStorageIndex); 1371 | Serial.print(F("/")); 1372 | Serial.print(EEPROMStorageSize); 1373 | Serial.print(F(" cells are used, storageOverwrite is ")); 1374 | Serial.print(storageOverwrite); 1375 | Serial.println(F("]")); 1376 | #endif 1377 | 1378 | #if DEBUG_MODE && DEBUG_MEMORY // Memory visualisation 1379 | Serial.print(F("\t ")); 1380 | for (unsigned int i = 0; i < rowLength; ++i) 1381 | { 1382 | Serial.print(F("🔻")); 1383 | if (i < 10) 1384 | Serial.print(F("0")); 1385 | Serial.print(i + 1); 1386 | Serial.print(F("⎻⎻⎻⎻⎻⎻⎻")); 1387 | 1388 | if ((i + 1) % rowDivider == 0) 1389 | Serial.print(F(" ")); 1390 | } 1391 | Serial.println(); 1392 | Serial.print(F("\t")); 1393 | #endif 1394 | 1395 | for (unsigned int i = 0; i < EEPROMStorageSize; i++) 1396 | { 1397 | EEPROM.get(i * EEPROMDataSize + EEPROM_BEGIN_ADDRESS, eepromdata); 1398 | 1399 | unsigned char crc = calculateCRC((unsigned char *)&eepromdata.data, sizeof(eepromdata.data)); 1400 | 1401 | #if DEBUG_MODE && DEBUG_MEMORY 1402 | if (eepromdata.counter < 100) 1403 | Serial.print(F(" ")); 1404 | if (eepromdata.counter < 10) 1405 | Serial.print(F(" ")); 1406 | 1407 | Serial.print(F(" ")); 1408 | Serial.print(eepromdata.counter); 1409 | 1410 | if (i == EEPROMStorageIndex - 1) 1411 | { 1412 | Serial.print(F("👉")); 1413 | } 1414 | else 1415 | { 1416 | Serial.print(F("➜ ")); 1417 | } 1418 | 1419 | if (eepromdata.crc < 100) 1420 | Serial.print(F("0")); 1421 | if (eepromdata.crc < 10) 1422 | Serial.print(F("0")); 1423 | 1424 | Serial.print(eepromdata.crc); 1425 | #endif 1426 | 1427 | if (eepromdata.counter != 255) 1428 | { 1429 | if (crc == eepromdata.crc) 1430 | { 1431 | #if DEBUG_MODE && DEBUG_MEMORY 1432 | Serial.print(F("✅")); 1433 | #endif 1434 | } 1435 | else 1436 | { 1437 | #if DEBUG_MODE && DEBUG_MEMORY 1438 | Serial.print(F("⛔️")); 1439 | #endif 1440 | result = false; 1441 | } 1442 | } 1443 | else 1444 | { 1445 | #if DEBUG_MODE && DEBUG_MEMORY 1446 | Serial.print(F("⬜")); 1447 | #endif 1448 | } 1449 | 1450 | #if DEBUG_MODE && DEBUG_MEMORY // Memory visualisation 1451 | if ((i + 1) % rowLength == 0) 1452 | { 1453 | Serial.println(); 1454 | Serial.println(); 1455 | Serial.print(F("\t")); 1456 | } 1457 | else 1458 | { 1459 | if ((i + 1) % rowDivider == 0) 1460 | Serial.print(F(" · ")); 1461 | } 1462 | #endif 1463 | } 1464 | 1465 | #if DEBUG_MODE && DEBUG_MEMORY 1466 | Serial.println(); 1467 | #endif 1468 | 1469 | return result; 1470 | } 1471 | 1472 | bool EEPROMAglora::checkCRC(unsigned int index) 1473 | { 1474 | EEPROM.get(index * EEPROMDataSize + EEPROM_BEGIN_ADDRESS, eepromdata); 1475 | const byte crc = calculateCRC((unsigned char *)&eepromdata.data, dataSize); 1476 | if (eepromdata.crc == crc) 1477 | return true; 1478 | return false; 1479 | } 1480 | 1481 | unsigned int EEPROMAglora::getSize() 1482 | { 1483 | return EEPROMStorageSize; 1484 | } 1485 | 1486 | unsigned int EEPROMAglora::getIndex() 1487 | { 1488 | return EEPROMStorageIndex; 1489 | } 1490 | 1491 | bool EEPROMAglora::getStorageOverwrite() 1492 | { 1493 | return storageOverwrite; 1494 | } 1495 | 1496 | struct SRAMDATA 1497 | { 1498 | DATA data; 1499 | unsigned char crc; 1500 | }; 1501 | 1502 | class SRAM: public IMemory 1503 | { 1504 | public: 1505 | SRAM(); 1506 | void setup(); 1507 | bool checkUnique(DATA *newPoint); 1508 | unsigned int save(DATA *newData); //function returns the index 1509 | DATA * load(unsigned int index); 1510 | void clearAllPositions(); 1511 | bool checkCRC(); 1512 | bool checkCRC(unsigned int index); 1513 | unsigned int getSize(); 1514 | unsigned int getIndex(); 1515 | bool getStorageOverwrite(); 1516 | 1517 | private: 1518 | SRAMDATA storage[SRAM_STORAGE_SIZE]; 1519 | unsigned int storageIndex = 0; 1520 | bool storageOverwrite = false; 1521 | byte dataSize; 1522 | bool checkCRC(SRAMDATA *point); 1523 | }; 1524 | 1525 | SRAM::SRAM() 1526 | { 1527 | dataSize = sizeof(DATA); 1528 | } 1529 | 1530 | void SRAM::setup() 1531 | { 1532 | #if DEBUG_MODE && DEBUG_MEMORY 1533 | Serial.print(F("💾[SRAM storage: memory is ready. SRAM_STORAGE_SIZE=")); 1534 | Serial.print(SRAM_STORAGE_SIZE + 1); 1535 | Serial.print(F(" (")); 1536 | Serial.print((SRAM_STORAGE_SIZE + 1) * sizeof(DATA)); 1537 | Serial.print(F(" bytes)")); 1538 | Serial.println(F("]")); 1539 | Serial.println(F("\t CRC check symbols: ✅ CRC OK, ⛔️ CRC ERROR, ⬜ empty memory cell, underlined CRC\u0332 - current cell")); 1540 | #endif 1541 | } 1542 | 1543 | /// @brief Checking new data for uniqueness 1544 | /// @param loraDataPacket 1545 | /// @return true if the same data is not exist 1546 | bool SRAM::checkUnique(DATA *newPoint) 1547 | { 1548 | #if DEBUG_MODE && DEBUG_MEMORY 1549 | Serial.print(F("💾[SRAM storage: checking data for uniqueness 📦 ↔ 📦 ↔ 📦. First check, is <")); 1550 | Serial.print(newPoint->name); 1551 | Serial.print(F("> equal to <")); 1552 | Serial.print(NAME); 1553 | Serial.println(F(">]")); 1554 | #endif 1555 | 1556 | if (strcmp(newPoint->name, NAME) == 0) 1557 | { 1558 | #if DEBUG_MODE && DEBUG_MEMORY 1559 | Serial.print(F("💾[SRAM storage: returned package 🔄, because ")); 1560 | Serial.print(newPoint->name); 1561 | Serial.println(F(" it's me!]")); 1562 | #endif 1563 | return false; 1564 | } 1565 | 1566 | #if DEBUG_MODE && DEBUG_MEMORY 1567 | Serial.println(F("💾[SRAM storage: checking data for uniqueness 📦 ↔ 📦 ↔ 📦. Second check, find the coordinates and time duplication ")); 1568 | Serial.print(F("\t")); 1569 | #endif 1570 | 1571 | const unsigned int maxIndex = storageOverwrite ? SRAM_STORAGE_SIZE : storageIndex; 1572 | for (unsigned int i = 0; i < maxIndex; ++i) 1573 | { 1574 | #if DEBUG_MODE && DEBUG_MEMORY 1575 | Serial.print(F("#")); 1576 | #endif 1577 | 1578 | if ((strcmp(newPoint->name, storage[i].data.name) == 0) && 1579 | (newPoint->year == storage[i].data.year) && 1580 | (newPoint->month == storage[i].data.month) && 1581 | (newPoint->day == storage[i].data.day) && 1582 | (newPoint->hour == storage[i].data.hour) && 1583 | (newPoint->minute == storage[i].data.minute) && 1584 | (newPoint->second == storage[i].data.second)) 1585 | { 1586 | #if DEBUG_MODE && DEBUG_MEMORY 1587 | Serial.println(); 1588 | Serial.println(F("💾[SRAM storage: data already exist‼️⛔️]")); 1589 | #endif 1590 | return false; 1591 | } 1592 | } 1593 | #if DEBUG_MODE && DEBUG_MEMORY 1594 | Serial.println(); 1595 | Serial.println(F("💾[SRAM storage: new data ✅]")); 1596 | #endif 1597 | return true; 1598 | } 1599 | 1600 | /// @brief Save data to storage 1601 | /// @param newData data that needs to be added to the storage 1602 | /// @return the index of added data 1603 | unsigned int SRAM::save(DATA *newData) 1604 | { 1605 | storage[storageIndex].data = *newData; 1606 | storage[storageIndex].crc = calculateCRC((unsigned char *)newData, dataSize); 1607 | 1608 | #if DEBUG_MODE && DEBUG_MEMORY 1609 | Serial.print(F("💾[SRAM storage saving: data from ")); 1610 | Serial.print(storage[storageIndex].data.name); 1611 | Serial.print(F(" was added. Memory: ")); 1612 | Serial.print(storageIndex + 1); 1613 | Serial.print(F("/")); 1614 | Serial.print(SRAM_STORAGE_SIZE + 1); 1615 | Serial.print(F(", CRC ")); 1616 | Serial.print(storage[storageIndex].crc); 1617 | Serial.println(F(" ✅]")); 1618 | #endif 1619 | 1620 | unsigned int addedIindex = storageIndex; 1621 | storageIndex++; 1622 | if (storageIndex >= SRAM_STORAGE_SIZE) 1623 | { 1624 | storageIndex = 0; 1625 | storageOverwrite = true; 1626 | } 1627 | 1628 | return addedIindex; 1629 | } 1630 | 1631 | /// @brief Loading data from memory to loraDataPacket by index 1632 | /// @param loraDataPacket pointer 1633 | /// @param index index of data in memory 1634 | /// @return true if success 1635 | DATA *SRAM::load(unsigned int index) 1636 | { 1637 | return &storage[index].data; 1638 | } 1639 | 1640 | void SRAM::clearAllPositions() 1641 | { 1642 | #if DEBUG_MODE && DEBUG_MEMORY 1643 | Serial.println(F("💾[SRAM storage: clearing memory 🫙]")); 1644 | #endif 1645 | storageIndex = 0; 1646 | storageOverwrite = false; 1647 | } 1648 | 1649 | bool SRAM::checkCRC() 1650 | { 1651 | #if DEBUG_MODE && DEBUG_MEMORY 1652 | Serial.print(F("💾[SRAM storage: checking CRC, ")); 1653 | Serial.print(storageIndex); 1654 | Serial.print(F("/")); 1655 | Serial.print(SRAM_STORAGE_SIZE); 1656 | Serial.print(F(" cells are used, storageOverwrite is ")); 1657 | Serial.print(storageOverwrite); 1658 | Serial.println(F("]")); 1659 | #endif 1660 | 1661 | const byte rowLength = 12; // how many characters will be printed in a row 1662 | const byte rowDivider = 4; // split string for better view 1663 | bool result = true; 1664 | byte crc = 0; 1665 | 1666 | if ((storageIndex == 0) && (!storageOverwrite)) 1667 | { 1668 | #if DEBUG_MODE && DEBUG_MEMORY 1669 | Serial.println(F("💾[SRAM storage: memory is empty]")); 1670 | #endif 1671 | return result; 1672 | } 1673 | 1674 | const unsigned int maxIndex = storageOverwrite ? (SRAM_STORAGE_SIZE - 1) : (storageIndex - 1); 1675 | 1676 | #if DEBUG_MODE && DEBUG_MEMORY 1677 | Serial.print(F("\t")); 1678 | #endif 1679 | 1680 | for (unsigned int i = 0; i < SRAM_STORAGE_SIZE; ++i) 1681 | { 1682 | if (i <= maxIndex) 1683 | { 1684 | crc = calculateCRC((unsigned char *)&storage[i], dataSize); 1685 | if (storage[i].crc == crc) 1686 | { 1687 | #if DEBUG_MODE && DEBUG_MEMORY 1688 | Serial.print(F(" ✅")); 1689 | #endif 1690 | } 1691 | else 1692 | { 1693 | #if DEBUG_MODE && DEBUG_MEMORY 1694 | Serial.print(F(" ⛔️")); 1695 | #endif 1696 | result = false; 1697 | } 1698 | #if DEBUG_MODE && DEBUG_MEMORY 1699 | if (crc < 100) 1700 | Serial.print(F("0")); 1701 | if (crc < 10) 1702 | Serial.print(F("0")); 1703 | Serial.print(crc); 1704 | if ((i == storageIndex - 1) || 1705 | ((i == 0) && (storageOverwrite))) 1706 | { 1707 | Serial.print(F("\u0332")); // underline active memory cell 1708 | } 1709 | #endif 1710 | } 1711 | else 1712 | { 1713 | #if DEBUG_MODE && DEBUG_MEMORY 1714 | Serial.print(F(" ⬜")); 1715 | Serial.print(F(" ")); 1716 | #endif 1717 | } 1718 | 1719 | #if DEBUG_MODE && DEBUG_MEMORY // Memory visualisation 1720 | if ((i + 1) % rowLength == 0) 1721 | { 1722 | Serial.println(); 1723 | Serial.println(); 1724 | Serial.print(F("\t")); 1725 | } 1726 | else 1727 | { 1728 | if ((i + 1) % rowDivider == 0) 1729 | Serial.print(F(" · ")); 1730 | } 1731 | #endif 1732 | } 1733 | 1734 | #if DEBUG_MODE && DEBUG_MEMORY 1735 | Serial.println(); 1736 | #endif 1737 | 1738 | return result; 1739 | } 1740 | 1741 | bool SRAM::checkCRC(SRAMDATA *point) 1742 | { 1743 | const byte crc = calculateCRC((unsigned char *)point, dataSize); 1744 | if (point->crc == crc) 1745 | return true; 1746 | return false; 1747 | } 1748 | 1749 | bool SRAM::checkCRC(unsigned int index) 1750 | { 1751 | const byte crc = calculateCRC((unsigned char *)&storage[index], dataSize); 1752 | if (storage[index].crc == crc) 1753 | return true; 1754 | return false; 1755 | } 1756 | 1757 | unsigned int SRAM::getSize() 1758 | { 1759 | return SRAM_STORAGE_SIZE; 1760 | } 1761 | 1762 | unsigned int SRAM::getIndex() 1763 | { 1764 | return storageIndex; 1765 | } 1766 | 1767 | bool SRAM::getStorageOverwrite() 1768 | { 1769 | return storageOverwrite; 1770 | } 1771 | 1772 | /***************************************************** 1773 | _ ____ _ ___ ____ _ 1774 | / \ / ___| | | / _ \ | _ \ / \ 1775 | / _ \ | | _ | | | | | | | |_) | / _ \ 1776 | / ___ \ | |_| | | |___ | |_| | | _ < / ___ \ 1777 | /_/ \_\ \____| |_____| \___/ |_| \_\ /_/ \_\ 1778 | ******************************************************/ 1779 | 1780 | class AGLORA 1781 | { 1782 | public: 1783 | AGLORA(IMemory * memory, BLE_HM10 * ble); 1784 | void hello(); 1785 | void checkMemoryToBLE(); 1786 | void clearDataPacket(DATA * trackerData); 1787 | void updateSensors(DATA * trackerData); 1788 | void printPackage(LORADATA * loraDataPacket); 1789 | void getRequest(String request); 1790 | void sendPackageToBLE(DATA * trackerData, int index); 1791 | 1792 | private: 1793 | IMemory * _memory; 1794 | BLE_HM10 * _ble; 1795 | void sendAllPackagesToBLE(); 1796 | void sendLastPackagesToBLE(); 1797 | void sendPackageToBLEFromStorage(unsigned int index); 1798 | bool isDataMoreRecent(DATA * newData, DATA * oldData); 1799 | }; 1800 | 1801 | void AGLORA::updateSensors(DATA *loraDataPacket) 1802 | { 1803 | loraDataPacket->battery = 100; // just for example 1804 | 1805 | #if DEBUG_MODE && DEBUG_AGLORA 1806 | Serial.print(F("🟢[AGLoRa sensors: ")); 1807 | Serial.print(F("🔋 - ")); 1808 | Serial.print(loraDataPacket->battery); 1809 | Serial.println(F("]")); 1810 | #endif 1811 | } 1812 | 1813 | const String bleProtocolPrefix = "AGLoRa-"; 1814 | const String bleProtocolTypePoint = "point"; 1815 | const String bleProtocolTypeMemory = "memory"; 1816 | const String bleProtocolVersion = "&ver=2.2"; 1817 | const String bleProtocolParamCRC = "&crc="; 1818 | const String bleProtocolOK = "ok"; 1819 | const String bleProtocolError = "error"; 1820 | 1821 | const String bleProtocolParamMemorySize = "&memsize="; 1822 | const String bleProtocolParamMemoryOverwrite = "&overwrite="; 1823 | const String bleProtocolParamMemoryIndex = "&index="; 1824 | 1825 | const String bleProtocolDeviceName = "&dev_name=" + String(NAME); 1826 | 1827 | const String bleProtocolDivider = "\r\n"; 1828 | 1829 | AGLORA::AGLORA(IMemory *memory, BLE_HM10 *ble) 1830 | { 1831 | _ble = ble; 1832 | _memory = memory; 1833 | } 1834 | 1835 | void AGLORA::hello() 1836 | { 1837 | #if DEBUG_MODE && DEBUG_AGLORA 1838 | Serial.println(F("[power on]")); 1839 | 1840 | Serial.print(F("Waiting | ")); 1841 | for (int i = 0; i < 50; i++) 1842 | { 1843 | Serial.print(F("#")); 1844 | delay(50); 1845 | } 1846 | Serial.println(); 1847 | Serial.println(F("AGLORA tracker started...")); 1848 | #endif 1849 | } 1850 | 1851 | /// @brief 1852 | /// 1. clear 1853 | /// 2. set name 1854 | /// 3. set default ttl 1855 | /// @param loraDataPacket 1856 | void AGLORA::clearDataPacket(DATA *trackerData) 1857 | { 1858 | memset(trackerData, 0, sizeof(&trackerData)); 1859 | strcpy(trackerData->name, NAME); 1860 | #if DEBUG_MODE && DEBUG_AGLORA 1861 | Serial.println(F("🟢[AGLoRa: time to send your location📍, new loraDataPacket prepared 📦]")); 1862 | #endif 1863 | } 1864 | 1865 | void AGLORA::printPackage(LORADATA *loraDataPacket) 1866 | { 1867 | // DEBUG_MODE 1868 | #if DEBUG_MODE && DEBUG_AGLORA // dump out what was just received 1869 | Serial.println(F("🟢[AGLoRa: loraDataPacket now contains ↴]")); 1870 | Serial.print(F(" Name: ")); 1871 | Serial.print(loraDataPacket->data.name); 1872 | Serial.print(F(", 🌎 lat: ")); 1873 | Serial.print(loraDataPacket->data.lat, 6); 1874 | Serial.print(F(", lon: ")); 1875 | Serial.print(loraDataPacket->data.lon, 6); 1876 | 1877 | if (loraDataPacket->data.gpsValid) 1878 | Serial.print(F(", GPS 🆗")); 1879 | else 1880 | Serial.print(F(", GPS ❌")); 1881 | 1882 | // Serial.print(F(", sat: ")); 1883 | // Serial.print(loraDataPacket->data.sat); // example 1884 | // Serial.print(F(", hdop: ")); 1885 | // Serial.print(loraDataPacket->data.hdop); // example 1886 | 1887 | Serial.print(F(", 📆 date: ")); 1888 | Serial.print(loraDataPacket->data.year); 1889 | Serial.print(F("/")); 1890 | if (loraDataPacket->data.month < 10) 1891 | Serial.print(F("0")); 1892 | Serial.print(loraDataPacket->data.month); 1893 | Serial.print(F("/")); 1894 | if (loraDataPacket->data.day < 10) 1895 | Serial.print(F("0")); 1896 | Serial.print(loraDataPacket->data.day); 1897 | 1898 | Serial.print(F(", 🕰️ time: ")); 1899 | Serial.print(loraDataPacket->data.hour); 1900 | Serial.print(F(":")); 1901 | if (loraDataPacket->data.minute < 10) 1902 | Serial.print(F("0")); 1903 | Serial.print(loraDataPacket->data.minute); 1904 | Serial.print(F(":")); 1905 | if (loraDataPacket->data.second < 10) 1906 | Serial.print(F("0")); 1907 | Serial.print(loraDataPacket->data.second); 1908 | Serial.print(F(" (UTC)")); 1909 | 1910 | Serial.print(F(" 📦 TTL=")); 1911 | Serial.print(loraDataPacket->ttl); 1912 | 1913 | Serial.println(); 1914 | #endif 1915 | } 1916 | 1917 | void AGLORA::getRequest(String request) 1918 | { 1919 | if (request.length() == 0) 1920 | { 1921 | return; 1922 | } 1923 | 1924 | #if DEBUG_MODE && DEBUG_AGLORA 1925 | Serial.println(); 1926 | Serial.print(F("🟢[AGLoRa: 📭 BLE request received <<")); 1927 | Serial.print(request); 1928 | Serial.println(F(">> received]")); 1929 | Serial.println(); 1930 | #endif 1931 | 1932 | if (request.startsWith(F("crc"))) 1933 | { 1934 | checkMemoryToBLE(); 1935 | return; 1936 | } 1937 | 1938 | if (request.startsWith(F("clear"))) 1939 | { 1940 | _memory->clearAllPositions(); 1941 | checkMemoryToBLE(); 1942 | return; 1943 | } 1944 | 1945 | if (request.startsWith(F("all"))) 1946 | { 1947 | checkMemoryToBLE(); 1948 | sendAllPackagesToBLE(); 1949 | return; 1950 | } 1951 | 1952 | if (request.startsWith(F("id"))) 1953 | { 1954 | request.remove(0, 2); 1955 | unsigned int index = request.toInt(); 1956 | sendPackageToBLEFromStorage(index); 1957 | 1958 | return; 1959 | } 1960 | } 1961 | 1962 | void AGLORA::checkMemoryToBLE() 1963 | { 1964 | String response = bleProtocolPrefix + 1965 | bleProtocolTypeMemory + 1966 | bleProtocolVersion; 1967 | response += bleProtocolParamCRC; 1968 | response += _memory->checkCRC() ? bleProtocolOK : bleProtocolError; 1969 | response += bleProtocolDeviceName; 1970 | response += sendBatteryToPhone(); 1971 | response += bleProtocolParamMemorySize + _memory->getSize(); 1972 | response += bleProtocolParamMemoryIndex + _memory->getIndex(); 1973 | response += bleProtocolParamMemoryOverwrite + _memory->getStorageOverwrite(); 1974 | response += bleProtocolDivider; 1975 | _ble->send(&response); 1976 | } 1977 | 1978 | void AGLORA::sendPackageToBLE(DATA *trackerData, int index) 1979 | { 1980 | String response = bleProtocolPrefix + 1981 | bleProtocolTypePoint + 1982 | bleProtocolVersion; 1983 | 1984 | response += sendToPhone(trackerData); 1985 | response += sendBatteryToPhone(); 1986 | response += bleProtocolParamMemoryIndex + index; 1987 | response += bleProtocolParamCRC; 1988 | response += _memory->checkCRC(index) ? bleProtocolOK : bleProtocolError; 1989 | response += bleProtocolDivider; 1990 | 1991 | #if DEBUG_MODE && DEBUG_AGLORA 1992 | Serial.print(F("🟢AGLoRa: send point 📦 to BLE → ")); 1993 | Serial.print(response); 1994 | #endif 1995 | 1996 | _ble->send(&response); 1997 | } 1998 | 1999 | void AGLORA::sendAllPackagesToBLE() 2000 | { 2001 | unsigned int maxIndex = _memory->getStorageOverwrite() ? _memory->getSize() : _memory->getIndex(); 2002 | for (unsigned int i = 0; i < maxIndex; ++i) 2003 | { 2004 | #if DEBUG_MODE && DEBUG_AGLORA 2005 | Serial.print(F("🟢[AGLoRa: loading ")); 2006 | Serial.print(i + 1); 2007 | Serial.print(F("/")); 2008 | Serial.print(maxIndex); 2009 | Serial.print(F(" 📦 from memory ]")); 2010 | #endif 2011 | 2012 | sendPackageToBLE(_memory->load(i), i); 2013 | } 2014 | 2015 | #if DEBUG_MODE && DEBUG_AGLORA 2016 | Serial.println(); 2017 | #endif 2018 | } 2019 | 2020 | void AGLORA::sendPackageToBLEFromStorage(unsigned int index) 2021 | { 2022 | #if DEBUG_MODE && DEBUG_AGLORA 2023 | Serial.print(F("🟢[AGLoRa: loading 📦 from index ")); 2024 | Serial.print(index); 2025 | Serial.print(F("]")); 2026 | #endif 2027 | 2028 | if ((_memory->getStorageOverwrite() == false) && (_memory->getIndex() == 0)) 2029 | { 2030 | #if DEBUG_MODE && DEBUG_AGLORA 2031 | Serial.println(F("- error 🚨 empty memory 🚨")); 2032 | #endif 2033 | 2034 | String response = bleProtocolPrefix + 2035 | bleProtocolTypeMemory + 2036 | bleProtocolVersion; 2037 | response += bleProtocolParamMemorySize + _memory->getSize(); 2038 | response += bleProtocolParamMemoryIndex + _memory->getIndex(); 2039 | response += bleProtocolParamMemoryOverwrite + _memory->getStorageOverwrite(); 2040 | response += bleProtocolDivider; 2041 | _ble->send(&response); 2042 | 2043 | return; 2044 | } 2045 | 2046 | unsigned int maxIndex = _memory->getStorageOverwrite() ? _memory->getSize() : _memory->getIndex(); 2047 | if (index > maxIndex - 1) 2048 | { 2049 | #if DEBUG_MODE && DEBUG_AGLORA 2050 | Serial.println(F("- error 🚨 index out of range 🚨")); 2051 | #endif 2052 | return; 2053 | // TODO: send error 2054 | } 2055 | 2056 | sendPackageToBLE(_memory->load(index), index); 2057 | } 2058 | 2059 | void AGLORA::sendLastPackagesToBLE() 2060 | { 2061 | const unsigned int MAX_TRACKERS = 10; // Max trackers in memory 2062 | struct TrackerData { 2063 | char name[NAME_LENGTH]; 2064 | DATA* lastData; 2065 | }; 2066 | TrackerData lastDataArray[MAX_TRACKERS]; 2067 | unsigned int trackerCount = 0; 2068 | 2069 | unsigned int maxIndex = _memory->getStorageOverwrite() ? _memory->getSize() : _memory->getIndex(); 2070 | 2071 | for (unsigned int i = 0; i < maxIndex; ++i) 2072 | { 2073 | DATA* data = _memory->load(i); 2074 | if (data == nullptr) continue; 2075 | 2076 | bool found = false; 2077 | for (unsigned int j = 0; j < trackerCount; ++j) 2078 | { 2079 | if (strncmp(lastDataArray[j].name, data->name, NAME_LENGTH) == 0) 2080 | { 2081 | if (isDataMoreRecent(data, lastDataArray[j].lastData)) 2082 | { 2083 | lastDataArray[j].lastData = data; 2084 | } 2085 | found = true; 2086 | break; 2087 | } 2088 | } 2089 | 2090 | if (!found && trackerCount < MAX_TRACKERS) 2091 | { 2092 | strncpy(lastDataArray[trackerCount].name, data->name, NAME_LENGTH); 2093 | lastDataArray[trackerCount].lastData = data; 2094 | trackerCount++; 2095 | } 2096 | } 2097 | 2098 | for (unsigned int i = 0; i < trackerCount; ++i) 2099 | { 2100 | sendPackageToBLE(lastDataArray[i].lastData, 0); 2101 | } 2102 | } 2103 | 2104 | bool AGLORA::isDataMoreRecent(DATA * newData, DATA * oldData) 2105 | { 2106 | if (newData->year > oldData->year) return true; 2107 | if (newData->year < oldData->year) return false; 2108 | 2109 | if (newData->month > oldData->month) return true; 2110 | if (newData->month < oldData->month) return false; 2111 | 2112 | if (newData->day > oldData->day) return true; 2113 | if (newData->day < oldData->day) return false; 2114 | 2115 | if (newData->hour > oldData->hour) return true; 2116 | if (newData->hour < oldData->hour) return false; 2117 | 2118 | if (newData->minute > oldData->minute) return true; 2119 | if (newData->minute < oldData->minute) return false; 2120 | 2121 | if (newData->second > oldData->second) return true; 2122 | if (newData->second < oldData->second) return false; 2123 | 2124 | return false; 2125 | } 2126 | 2127 | /* 2128 | This is a function that sends data to the app. 2129 | Data packets are sent using OsmAnd-like protocol: 2130 | id=name&lat={0}&lon={1}×tamp={2}&speed={3}&altitude={4} 2131 | */ 2132 | String sendToPhone(DATA *package) { 2133 | String result; 2134 | result += F("&dev_batt="); //battery of your device 2135 | result += F("100"); //implement voltage acquisition 2136 | 2137 | result += F("&name="); //other tracker's name 2138 | result += package->name; //NAME_LENGTH bytes 2139 | 2140 | result += F("&lat="); // cordinates 2141 | result += String(package->lat, 6); // latitude 2142 | result += F("&lon="); // record separator 2143 | result += String(package->lon, 6); // longitute 2144 | 2145 | //Date and time format: 2023-06-07T15:21:00Z 2146 | result += F("×tamp="); // record separator 2147 | result += package->year + 2000; // year 2148 | result += F("-"); // data separator 2149 | if (package->month < 10) result += F("0"); 2150 | result += package->month; // month 2151 | result += F("-"); // data separator 2152 | if (package->day < 10) result += F("0"); 2153 | result += package->day; // day 2154 | result += F("T"); // record separator 2155 | if (package->hour < 10) result += F("0"); 2156 | result += package->hour; // hour 2157 | result += F(":"); // time separator 2158 | if (package->minute < 10) result += F("0"); 2159 | result += package->minute; // minute 2160 | result += F(":"); // time separator 2161 | if (package->second < 10) result += F("0"); 2162 | result += package->second; // second 2163 | result += F("Z"); // UTC 2164 | 2165 | // Sensors and additional data 2166 | result += F("&gpsValid="); 2167 | result += package->gpsValid; // validity of coordinates bool 2168 | 2169 | // Add more data here if you need ... 2170 | // result += "&speed="; 2171 | // result += package->speed; 2172 | 2173 | result += "&battery="; 2174 | result += package->battery; 2175 | 2176 | // result += "&C-137-level="; // data's name in app 2177 | // result += package->sensor2; // value 2178 | 2179 | return result; 2180 | } 2181 | 2182 | String sendBatteryToPhone() { 2183 | String result; 2184 | 2185 | result += "&dev_bat="; 2186 | result += 100; 2187 | 2188 | return result; 2189 | } 2190 | 2191 | // ========================================================================= 2192 | // ========================== MAIN PROGRAM ================================= 2193 | // ========================================================================= 2194 | 2195 | TESTS tests; 2196 | INDICATION indication(GPS_LED, LORA_LED, BLE_LED, MEMORY_LED); 2197 | GPS gps(GPS_PIN_RX, GPS_PIN_TX, GPS_SPEED, &indication); 2198 | LORA lora(LORA_PIN_RX, LORA_PIN_TX, LORA_PIN_AX, LORA_PIN_M0, LORA_PIN_M1, &indication); 2199 | BLE_HM10 ble; 2200 | 2201 | #if USE_EEPROM_MEMORY 2202 | EEPROMAglora memory; 2203 | #else 2204 | SRAM memory; 2205 | #endif 2206 | 2207 | LORADATA loraDataPackage; 2208 | AGLORA aglora(&memory, &ble); 2209 | 2210 | // ========== BEGIN ========== 2211 | void setup() 2212 | { 2213 | Serial.begin(9600); 2214 | aglora.hello(); // Beautifully print Hello from AGloRa :-) 2215 | // Start modules 2216 | gps.setup(); // GPS 2217 | lora.setup(); // LoRa 2218 | memory.setup(); // SRAM or EEPROM 2219 | ble.setup(); // Bluetooth Low Energy 2220 | } 2221 | 2222 | // ========== MAIN LOOP ========== 2223 | unsigned long _timeToSendMyLocation = millis() + DATA_SENDING_INTERVAL; 2224 | unsigned long _timeOfLastReceivedPacket; 2225 | unsigned int addedMemoryIndex; 2226 | byte ttl = 0; 2227 | 2228 | void processNewData(LORADATA *loraDataPackage); 2229 | 2230 | void loop() 2231 | { 2232 | if (_timeToSendMyLocation < millis()) 2233 | { 2234 | #if I_WANT_TO_SEND_MY_LOCATION 2235 | aglora.clearDataPacket(&loraDataPackage.data); // clear structure before reading new data 2236 | aglora.updateSensors(&loraDataPackage.data); // add sensors 2237 | gps.updateLocation(&loraDataPackage.data); // add locations 2238 | loraDataPackage.ttl = TTL; // time to live (for mesh network) 2239 | 2240 | aglora.printPackage(&loraDataPackage); 2241 | 2242 | lora.send(&loraDataPackage); // send location to other trackers 2243 | #endif 2244 | 2245 | _timeToSendMyLocation += DATA_SENDING_INTERVAL; 2246 | } 2247 | 2248 | // checking for new data 2249 | if (lora.hasNewData(&loraDataPackage)) 2250 | { 2251 | processNewData(&loraDataPackage); 2252 | } 2253 | 2254 | #if TEST_LORA_DATA 2255 | if (tests.hasNewDataEveryXSec(&loraDataPackage, &gps, 10)) 2256 | { 2257 | processNewData(&loraDataPackage); 2258 | } 2259 | #endif 2260 | 2261 | // if the time checker is over some prescribed amount 2262 | // let the user know there is no incoming data 2263 | if ((_timeOfLastReceivedPacket + BLE_UPDATE_INTERVAL) < millis()) 2264 | { 2265 | aglora.checkMemoryToBLE(); 2266 | _timeOfLastReceivedPacket = millis(); 2267 | } 2268 | 2269 | aglora.getRequest(ble.read()); // check requests from app 2270 | 2271 | indication.loop(); // make an indication 2272 | } 2273 | 2274 | /** 2275 | * Processes new data from a LORADATA package. 2276 | * 2277 | * @param loraDataPackage The LORADATA package containing the new data to be processed. 2278 | * 2279 | * @return void 2280 | * 2281 | * @throws None 2282 | */ 2283 | void processNewData(LORADATA *loraDataPackage) 2284 | { 2285 | if (memory.checkUnique(&loraDataPackage->data)) // Check the name and time of the point 2286 | { 2287 | ttl = loraDataPackage->ttl; 2288 | 2289 | addedMemoryIndex = memory.save(&loraDataPackage->data); 2290 | memory.checkCRC(); 2291 | 2292 | #if USE_BLE 2293 | aglora.sendPackageToBLE(&loraDataPackage->data, addedMemoryIndex); // upload data to app 2294 | #endif 2295 | 2296 | // resend data to other trackers 2297 | #if MESH_MODE 2298 | if (--ttl > 0) 2299 | { 2300 | loraDataPackage->ttl = ttl; 2301 | lora.send(loraDataPackage); 2302 | } 2303 | #endif 2304 | } 2305 | 2306 | _timeOfLastReceivedPacket = millis(); // if you got data, update the checker 2307 | } 2308 | --------------------------------------------------------------------------------