#include "HttpClient.h"
#include "SparkTime.h"
#include "neopixel.h"
#include <Stepper.h>
SYSTEM_MODE(AUTOMATIC);
#if (PLATFORM_ID == 32)
#define PIXEL_PIN SPI
#else
#define PIXEL_PIN D3
#endif
#define PIXEL_COUNT 13
#define PIXEL_TYPE WS2812B
Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);
const int stepsPerRevolution = 2048;
#define IN1 2
#define IN2 3
#define IN3 4
#define IN4 5
Stepper myStepper(stepsPerRevolution, IN1, IN3, IN2, IN4);
const char* API_KEY = "API-KEY-HERE";
const char* ORD_IATA = "ORD";
UDP UDPClient;
SparkTime rtc;
int currentCancellations = -1;
int previousCancellations = 0;
String currentTrackingDate = "";
bool firstRun = true;
int lastCheckMinute = -1;
//RANDOM DATE IF NEEDED
int currentYear = 2025;
int currentMonth = 3;
int currentDay = 14;
TCPClient client;
char server[] = "api.aviationstack.com";
int getCancellationCount() {
Serial.println("Getting cancellation count using direct TCP...");
Particle.publish("FlightMonitor", "Checking cancellations via API", PRIVATE);
char currentDate[11];
unsigned long currentTime = rtc.now();
int year = rtc.year(currentTime);
int month = rtc.month(currentTime);
int day = rtc.day(currentTime);
if (year >= 2020 && year <= 2030) {
sprintf(currentDate, "%04d-%02d-%02d", year, month, day);
} else {
sprintf(currentDate, "%04d-%02d-%02d", currentYear, currentMonth, currentDay);
}
Serial.print("Current date: ");
Serial.println(currentDate);
String dateMsg = String::format("Checking date: %s", currentDate);
Particle.publish("FlightMonitor", dateMsg, PRIVATE);
if(!client.connect(server, 80)) {
Serial.println("TCP connection failed");
Particle.publish("FlightMonitor", "ERROR: API connection failed", PRIVATE);
return -1;
}
Serial.println("Connected to server");
String request = "GET /v1/flights?access_key=";
request += API_KEY;
request += "&dep_iata=";
request += ORD_IATA;
request += "&flight_status=cancelled&flight_date=";
request += currentDate;
request += " HTTP/1.1\r\nHost: api.aviationstack.com\r\nConnection: close\r\n\r\n";
Serial.println("Sending request...");
client.print(request);
unsigned long timeout = millis();
while (client.available() == 0) {
if (millis() - timeout > 10000) {
Serial.println(">>> Client Timeout !");
Particle.publish("FlightMonitor", "ERROR: API request timeout", PRIVATE);
client.stop();
return -1;
}
}
Serial.println("Reading response...");
bool headerDone = false;
while(client.available() && !headerDone) {
String line = client.readStringUntil('\n');
if (line.length() <= 1) {
headerDone = true;
}
}
String partialJson = "";
bool foundCount = false;
int count = -1;
timeout = millis();
while (client.available() && !foundCount && (millis() - timeout < 5000)) {
char c = client.read();
partialJson += c;
if (partialJson.length() > 30) {
int countPos = partialJson.indexOf("\"count\":");
if (countPos != -1) {
int valueStart = countPos + 8;
int valueEnd = partialJson.indexOf(",", valueStart);
if (valueEnd != -1) {
String countStr = partialJson.substring(valueStart, valueEnd);
count = countStr.toInt();
foundCount = true;
Serial.print("Found count early: ");
Serial.println(count);
}
}
}
if (partialJson.length() > 200 && !foundCount) {
break;
}
}
client.stop();
if (foundCount) {
Serial.print("Found ");
Serial.print(count);
Serial.println(" cancelled flights (from pagination)");
String resultMsg = String::format("Found %d cancelled flights", count);
Particle.publish("FlightMonitor", resultMsg, PRIVATE);
return count;
}
Serial.println("Could not parse count from JSON response");
Particle.publish("FlightMonitor", "ERROR: Failed to parse API response", PRIVATE);
return -1;
}
void activateAlert() {
Serial.println("ALERT: Activating red lights!");
Particle.publish("FlightAlert", "ALERT: Cancellations increased! Activating alert sequence", PRIVATE);
Serial.println("Rotating stepper motor 180 degrees clockwise");
myStepper.step(stepsPerRevolution / 2);
for(int i=0; i<strip.numPixels(); i++) {
strip.setPixelColor(i, strip.Color(255, 0, 0));
}
strip.show();
Serial.println("Lights on - waiting 30 seconds");
Particle.publish("FlightAlert", "Red lights activated for 30 seconds", PRIVATE);
delay(30000);
strip.clear();
strip.show();
Serial.println("Lights turned off");
Serial.println("Rotating stepper motor 180 degrees counterclockwise");
myStepper.step(-stepsPerRevolution / 2);
Serial.println("Alert sequence complete");
Particle.publish("FlightAlert", "Alert sequence complete", PRIVATE);
}
void setup() {
Serial.begin(9600);
delay(5000);
Serial.println("\n\n===== Flight Cancellation Monitor Starting =====");
Particle.publish("FlightMonitor", "Device starting up", PRIVATE);
Serial.println("Initializing stepper motor...");
myStepper.setSpeed(17);
Serial.println("Initializing NeoPixels...");
strip.begin();
strip.show();
for(int i=0; i<strip.numPixels(); i++) {
strip.setPixelColor(i, strip.Color(0, 0, 50));
}
strip.show();
delay(1000);
strip.clear();
strip.show();
Serial.println("Syncing with NTP server...");
Particle.publish("FlightMonitor", "Syncing with NTP time server", PRIVATE);
rtc.begin(&UDPClient, "time.nist.gov");
rtc.setTimeZone(-5);
rtc.setUseDST(true);
delay(2000); // Give time to sync
unsigned long syncTime = rtc.now();
String timeStr = String::format("Time synced: %02d:%02d:%02d",
rtc.hour(syncTime),
rtc.minute(syncTime),
rtc.second(syncTime));
Serial.println(timeStr);
Particle.publish("FlightMonitor", timeStr, PRIVATE);
Serial.println("Running initial light test...");
for(int i=0; i<strip.numPixels(); i++) {
strip.setPixelColor(i, strip.Color(0, 50, 0));
}
strip.show();
delay(3000);
strip.clear();
strip.show();
Serial.println("Testing stepper motor...");
myStepper.step(stepsPerRevolution / 8);
delay(1000);
myStepper.step(-stepsPerRevolution / 8);
Serial.println("Stepper motor test complete");
Particle.publish("FlightMonitor", "Hardware initialization complete", PRIVATE);
unsigned long currentTime = rtc.now();
lastCheckMinute = rtc.minute(currentTime);
Serial.print("Initial minute set to: ");
Serial.println(lastCheckMinute);
}
void loop() {
unsigned long currentTime = rtc.now();
int currentMinute = rtc.minute(currentTime);
if ((currentMinute % 5 == 0) && (currentMinute != lastCheckMinute)) {
Serial.println("\n--- Starting check cycle (every 5 min) ---");
String timeStr = String::format("Check cycle at %02d:%02d:%02d",
rtc.hour(currentTime),
currentMinute,
rtc.second(currentTime));
Serial.println(timeStr);
Particle.publish("FlightMonitor", timeStr, PRIVATE);
lastCheckMinute = currentMinute;
int newCount = getCancellationCount();
if(newCount != -1) {
Serial.print("Previous count: ");
Serial.print(currentCancellations);
Serial.print(", New count: ");
Serial.println(newCount);
if(firstRun || newCount > currentCancellations) {
if(firstRun) {
Serial.println("Initial cancellation count established");
String initMsg = String::format("Initial cancellation count: %d", newCount);
Particle.publish("FlightMonitor", initMsg, PRIVATE);
firstRun = false;
} else {
Serial.println("NEW CANCELLATIONS DETECTED!");
String alertMsg = String::format("INCREASE: %d to %d cancellations",
currentCancellations, newCount);
Particle.publish("FlightAlert", alertMsg, PRIVATE);
}
activateAlert();
currentCancellations = newCount;
Particle.publish("Cancellations", String(currentCancellations), PRIVATE);
}
else if(newCount < currentCancellations) {
Serial.println("Cancellation count decreased");
String decreaseMsg = String::format("DECREASE: %d to %d cancellations",
currentCancellations, newCount);
Particle.publish("FlightMonitor", decreaseMsg, PRIVATE);
currentCancellations = newCount;
} else {
Serial.println("No change in cancellation count");
Particle.publish("FlightMonitor", "No change in cancellations", PRIVATE);
}
} else {
Serial.println("ERROR: Failed to get cancellation count");
Particle.publish("FlightMonitor", "ERROR: Failed to get cancellation count", PRIVATE);
}
}
delay(100);
}
Comments