Noah H
Published

Airplane Cancellation Struggle

Shows the struggle of airplane cancellations.

IntermediateShowcase (no instructions)6 hours40
Airplane Cancellation Struggle

Things used in this project

Hardware components

Photon 2
Particle Photon 2
×1
Stepper Motor
Digilent Stepper Motor
×1
Adafruit NeoPixel Digital RGB LED Strip 144 LED, 1m White
Adafruit NeoPixel Digital RGB LED Strip 144 LED, 1m White
×1
5v DC Power Supply
×1

Software apps and online services

Aviationstack API
Particle Build Web IDE
Particle Build Web IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Hot glue gun (generic)
Hot glue gun (generic)
Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free
Wire Stripper & Cutter, 18-10 AWG / 0.75-4mm² Capacity Wires
Wire Stripper & Cutter, 18-10 AWG / 0.75-4mm² Capacity Wires
Premium Female/Male Extension Jumper Wires, 40 x 6" (150mm)
Premium Female/Male Extension Jumper Wires, 40 x 6" (150mm)
A lot.
cardboard

Story

Read more

Custom parts and enclosures

Platform

Attached to the stepper motor.

Sketchfab still processing.

Schematics

Schematic

A bad schematic drawn in MS Paint

Code

Proton 2 Code

C/C++
Many libraries used, ensure all are installed before using.
#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);
}

Credits

Noah H
2 projects • 0 followers

Comments