/ Track_Your_Treats / Track_Your_Treats_FONA808 / Track_Your_Treats_FONA808.ino
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  }