Track_Your_Treats_FONA808.ino
1 // SPDX-FileCopyrightText: 2019 Tony DiCola for Adafruit Industries 2 // 3 // SPDX-License-Identifier: MIT 4 5 // Track Your Treats - FONA808 Shield & Adafruit IO Halloween Candy Route Tracker 6 // Author: Tony DiCola 7 // 8 // See the guide at: 9 // https://learn.adafruit.com/track-your-treats-halloween-candy-gps-tracker/overview 10 // 11 // Released under a MIT license: 12 // https://opensource.org/licenses/MIT 13 #include <SoftwareSerial.h> 14 #include "Adafruit_SleepyDog.h" 15 #include "Adafruit_FONA.h" 16 #include "Adafruit_MQTT.h" 17 #include "Adafruit_MQTT_FONA.h" 18 19 20 // Configuration (you need to change at least the APN and AIO username & key values): 21 22 #define LED_PIN 6 // Pin connected to an LED that flashes the status of the project. 23 24 #define BUTTON_PIN 5 // Pin connected to the button. 25 26 #define LOGGING_PERIOD_SEC 15 // Seconds to wait between logging GPS locations. 27 28 #define FONA_RX 2 // FONA serial RX pin (pin 2 for shield). 29 30 #define FONA_TX 3 // FONA serial TX pin (pin 3 for shield). 31 32 #define FONA_RST 4 // FONA reset pin (pin 4 for shield) 33 34 #define FONA_APN "" // APN used by cell data service (leave blank if unused). 35 // Contact your cell service provider to get this value and 36 // any username or password required too (see below). 37 38 #define FONA_USERNAME "" // Username used by cell data service (leave blank if unused). 39 40 #define FONA_PASSWORD "" // Password used by cell data service (leave blank if unused). 41 42 #define AIO_SERVER "io.adafruit.com" // Adafruit IO server name. 43 44 #define AIO_SERVERPORT 1883 // Adafruit IO port. 45 46 #define AIO_USERNAME "" // Adafruit IO username (see http://accounts.adafruit.com/). 47 48 #define AIO_KEY "" // Adafruit IO key (see settings page at: https://io.adafruit.com/settings). 49 50 #define PATH_FEED_NAME "treat-path" // Name of the AIO feed to log regular location updates. 51 52 #define GOOD_CANDY_FEED_NAME "treat-good-candy" // Name of the AIO feed to log good candy locations. 53 54 #define MAX_TX_FAILURES 3 // Maximum number of publish failures in a row before resetting the whole sketch. 55 56 57 // Global state (you don't need to change this): 58 SoftwareSerial fonaSS = SoftwareSerial(FONA_TX, FONA_RX); // FONA software serial connection. 59 Adafruit_FONA fona = Adafruit_FONA(FONA_RST); // FONA library connection. 60 const char MQTT_SERVER[] PROGMEM = AIO_SERVER; // MQTT server, client, username, password. 61 const char MQTT_CLIENTID[] PROGMEM = __TIME__ AIO_USERNAME; // (stored in flash memory) 62 const char MQTT_USERNAME[] PROGMEM = AIO_USERNAME; 63 const char MQTT_PASSWORD[] PROGMEM = AIO_KEY; 64 Adafruit_MQTT_FONA mqtt(&fona, MQTT_SERVER, AIO_SERVERPORT, // MQTT connection. 65 MQTT_CLIENTID, MQTT_USERNAME, 66 MQTT_PASSWORD); 67 uint8_t txFailures = 0; // Count of how many publish failures have occured in a row. 68 uint32_t logCounter = 0; // Counter until next location log is recorded. 69 70 // Publishing feed setup (you don't need to change this): 71 // Note that the path ends in '/csv', this means a comma separated set of values 72 // can be pushed to the feed, including location data like lat, long, altitude. 73 Adafruit_MQTT_Publish path = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/treat-path/csv"); 74 Adafruit_MQTT_Publish goodCandy = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/treat-good-candy/csv"); 75 76 // Halt function called when an error occurs. Will print an error and stop execution while 77 // doing a fast blink of the LED. If the watchdog is enabled it will reset after 8 seconds. 78 void halt(const __FlashStringHelper *error) { 79 Serial.println(error); 80 while (1) { 81 digitalWrite(LED_PIN, LOW); 82 delay(100); 83 digitalWrite(LED_PIN, HIGH); 84 delay(100); 85 } 86 } 87 88 // Timer interrupt called every millisecond to keep track of when the location should be logged. 89 SIGNAL(TIMER0_COMPA_vect) { 90 // Decrease the count since last location log. 91 if (logCounter > 0) { 92 logCounter--; 93 } 94 } 95 96 // Serialize the lat, long, altitude to a CSV string that can be published to the specified feed. 97 void logLocation(float latitude, float longitude, float altitude, Adafruit_MQTT_Publish& publishFeed) { 98 // Initialize a string buffer to hold the data that will be published. 99 char sendBuffer[120]; 100 memset(sendBuffer, 0, sizeof(sendBuffer)); 101 int index = 0; 102 103 // Start with '0,' to set the feed value. The value isn't really used so 0 is used as a placeholder. 104 sendBuffer[index++] = '0'; 105 sendBuffer[index++] = ','; 106 107 // Now set latitude, longitude, altitude separated by commas. 108 dtostrf(latitude, 2, 6, &sendBuffer[index]); 109 index += strlen(&sendBuffer[index]); 110 sendBuffer[index++] = ','; 111 dtostrf(longitude, 3, 6, &sendBuffer[index]); 112 index += strlen(&sendBuffer[index]); 113 sendBuffer[index++] = ','; 114 dtostrf(altitude, 2, 6, &sendBuffer[index]); 115 116 // Finally publish the string to the feed. 117 Serial.print(F("Publishing: ")); 118 Serial.println(sendBuffer); 119 if (!publishFeed.publish(sendBuffer)) { 120 Serial.println(F("Publish failed!")); 121 txFailures++; 122 } 123 else { 124 Serial.println(F("Publish succeeded!")); 125 txFailures = 0; 126 } 127 } 128 129 void setup() { 130 // Initialize serial output. 131 Serial.begin(115200); 132 Serial.println(F("Track your Treats - FONA808 & Adafruit IO")); 133 134 // Initialize LED and button. 135 pinMode(LED_PIN, OUTPUT); 136 pinMode(BUTTON_PIN, INPUT_PULLUP); 137 digitalWrite(LED_PIN, LOW); 138 139 // Initialize the FONA module 140 Serial.println(F("Initializing FONA....(may take 10 seconds)")); 141 fonaSS.begin(4800); 142 if (!fona.begin(fonaSS)) { 143 halt(F("Couldn't find FONA")); 144 } 145 fonaSS.println("AT+CMEE=2"); 146 Serial.println(F("FONA is OK")); 147 148 // Use the watchdog to simplify retry logic and make things more robust. 149 // Enable this after FONA is intialized because FONA init takes about 8-9 seconds. 150 Watchdog.enable(8000); 151 Watchdog.reset(); 152 153 // Wait for FONA to connect to cell network (up to 8 seconds, then watchdog reset). 154 Serial.println(F("Checking for network...")); 155 while (fona.getNetworkStatus() != 1) { 156 delay(500); 157 } 158 159 // Enable GPS. 160 fona.enableGPS(true); 161 162 // Start the GPRS data connection. 163 Watchdog.reset(); 164 fona.setGPRSNetworkSettings(F(FONA_APN), F(FONA_USERNAME), F(FONA_PASSWORD)); 165 delay(2000); 166 Watchdog.reset(); 167 Serial.println(F("Disabling GPRS")); 168 fona.enableGPRS(false); 169 delay(2000); 170 Watchdog.reset(); 171 Serial.println(F("Enabling GPRS")); 172 if (!fona.enableGPRS(true)) { 173 halt(F("Failed to turn GPRS on, resetting...")); 174 } 175 Serial.println(F("Connected to Cellular!")); 176 177 // Wait a little bit to stabilize the connection. 178 Watchdog.reset(); 179 delay(3000); 180 181 // Now make the MQTT connection. 182 int8_t ret = mqtt.connect(); 183 if (ret != 0) { 184 Serial.println(mqtt.connectErrorString(ret)); 185 halt(F("MQTT connection failed, resetting...")); 186 } 187 Serial.println(F("MQTT Connected!")); 188 189 // Configure timer0 compare interrupt to run and decrease the log counter every millisecond. 190 OCR0A = 0xAF; 191 TIMSK0 |= _BV(OCIE0A); 192 } 193 194 void loop() { 195 // Watchdog reset at start of loop--make sure everything below takes less than 8 seconds in normal operation! 196 Watchdog.reset(); 197 198 // Reset everything if disconnected or too many transmit failures occured in a row. 199 if (!fona.TCPconnected() || (txFailures >= MAX_TX_FAILURES)) { 200 halt(F("Connection lost, resetting...")); 201 } 202 203 // Grab a GPS reading. 204 float latitude, longitude, speed_kph, heading, altitude; 205 bool gpsFix = fona.getGPS(&latitude, &longitude, &speed_kph, &heading, &altitude); 206 207 // Light the LED solid if there's a GPS fix, otherwise flash it on and off once a second. 208 if (gpsFix) { 209 digitalWrite(LED_PIN, HIGH); 210 } 211 else { 212 // No fix, blink the LED once a second and stop further processing. 213 digitalWrite(LED_PIN, (millis()/1000) % 2); 214 return; 215 } 216 217 // Check if the button is pressed. 218 if (digitalRead(BUTTON_PIN) == LOW) { 219 // Pause a bit to debounce. 220 delay(100); 221 if (digitalRead(BUTTON_PIN) == LOW) { 222 // Button pressed! Log the current location to the good candy feed. 223 logLocation(latitude, longitude, altitude, goodCandy); 224 // Then flash the light 5 times to signal the location was recorded. 225 for (int i=0; i<5; ++i) { 226 digitalWrite(LED_PIN, HIGH); 227 delay(250); 228 digitalWrite(LED_PIN, LOW); 229 delay(250); 230 } 231 } 232 } 233 234 // Periodically log the location. 235 if (logCounter == 0) { 236 // Log the current location to the path feed, then reset the counter. 237 logLocation(latitude, longitude, altitude, path); 238 logCounter = LOGGING_PERIOD_SEC*1000; 239 } 240 }