HTMAA 2024
Sungmoon Lim

week 12


networking and communications

fetching data over WiFi

This week's assignment is to design, build, and connect wired or wireless node(s) with network or bus addresses and local input &/or output device(s). It's highly relevant to my final project, which made for an efficient week: I'll be using this week's progress towards my overall project.

I've been working on the schematic and PCB design for my final project (which is no easy task considering that there are over 400 LEDs that I have to account for and design around). Because I know I'll be using LEDs (WS2818 LEDs) for my project, I decided to choose one of the subway lines for this week's assignment. I will be using a modified, pared-down version of my final project, using the ESP32C3 and LED strip as the wireless and wired communication, and the LEDs in the strip acting as addressable stations as the networked nodes. I won't be soldering on the individual LEDs; I will keep it in its strip form. I'm hoping to re-use my board from week 8 since the OLED had fallen off anyways. I would need this just for the ESP32 for the data signal (connecting the LED strip to GND and LED strip to board header).

As always, I first consulted the pinout sheet for the XIAO to determine which connections need to be made:

vinyl

While I tried to re-use my PCB from week 8, I ended up having some trouble with the PCB (maybe something got affected as the OLED fell off? I'm not sure) so I used a breadboard to put something together. In terms of components, I just ended up using the ESP32C3, LED strip, breadboard, and jumper wires.

In terms of connections, I connected the GND wire to LED strip ground (white), data pin wire to LED strip data (green), LED strip power (red) to power supply.

I'll test this on the L train line, since it's a relatively short subway line with fewer stops than the others:

vinyl

In addition to the live time feed, I also had to access the MTA API endpoint for the L line (https://api-endpoint.mta.info/mta_esi.php?key=YOUR_KEY&feed_id=2). The MTA previously required an API key for access, but it looks like it's fully open now!

For MTA's GTFS (General Transit Feed Specification), we'll need to modify the code to handle real subway data. For my project, I'm using GTFS Realtime (also known as GTFS-rt), which is an extension to GTFS. GTFS-rt differs from GTFS because real-time vehicle locations, arrival time predictions, and alerts such as detours and cancellations via Protocol Buffers web server is shared.

The MTA provides its GTFS feed in binary GTFS-realtime protobuf format, so I had to adjust the code to handle this format. In order to read the GTFS data, we have to download MTA's GTFS Protobuf definitions (gtfs-realtime.pb.h) and (nyct-subway.pb.h).

I assigned each of the 24 stations on the L to a specific LED, so that the LED would light up in grey (the color of the train line) when a train was at that station, and remain off when there was no train present. The feed refreshes every 20 seconds, so unfortunately it's probably not best to rely on this to determine when you should actually leave for your train.


        #include <Adafruit_NeoPixel.h>
        #include <WiFi.h>
        #include <HTTPClient.h>
        #include <WiFiClientSecure.h>
        #include <ArduinoJson.h>
        
        #define PIN_L 2     // Using GPIO2
        #define NUM_LEDS 50
        
        // Train status definitions
        #define NO_TRAIN 0
        #define TRAIN_PRESENT 1
        #define TRAIN_APPROACHING 2
        
        Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, PIN_L, NEO_GRB + NEO_KHZ800);
        
        // WiFi credentials
        const char* ssid = "I USED MY HOTSPOT";
        const char* password = "PASSWORD";
        
        // MTA Feed URL
        const char* trainURL = "https://api.mta.info/GTFS/feeds/nyct%2Fgtfs-l";
        
        // Station definitions for L (westbound)
        struct Station {
          const char* name;
          int ledIndex;
          uint8_t validLines;
        };
        
        Station stations[] = {
           {"8 Av", 0, 0b1},                    // 8th Avenue
           {"6 Av", 1, 0b1},                    // 6th Avenue
           {"14 St-Union Sq", 2, 0b1},          // Union Square
           {"3 Av", 3, 0b1},                    // 3rd Avenue
           {"1 Av", 4, 0b1},                    // 1st Avenue
           {"Bedford Av", 5, 0b1},              // Bedford Avenue
           {"Lorimer St", 6, 0b1},              // Lorimer Street
           {"Graham Av", 7, 0b1},               // Graham Avenue
           {"Grand St", 8, 0b1},                // Grand Street
           {"Montrose Av", 9, 0b1},             // Montrose Avenue
           {"Morgan Av", 10, 0b1},              // Morgan Avenue
           {"Jefferson St", 11, 0b1},           // Jefferson Street
           {"DeKalb Av", 12, 0b1},             // DeKalb Avenue
           {"Myrtle-Wyckoff", 13, 0b1},        // Myrtle-Wyckoff
           {"Halsey St", 14, 0b1},             // Halsey Street
           {"Wilson Av", 15, 0b1},             // Wilson Avenue
           {"Bushwick Av", 16, 0b1},           // Bushwick Avenue
           {"Broadway Jct", 17, 0b1},          // Broadway Junction
           {"Atlantic Av", 18, 0b1},           // Atlantic Avenue
           {"Sutter Av", 19, 0b1},            // Sutter Avenue
           {"Livonia Av", 20, 0b1},           // Livonia Avenue
           {"New Lots Av", 21, 0b1},          // New Lots Avenue
           {"East 105 St", 22, 0b1},          // East 105th Street
           {"Canarsie", 23, 0b1}             // Canarsie-Rockaway Parkway
        };
        
        void setup() {
          Serial.begin(115200);
          delay(1000);
          
          // Initialize LED strip
          strip.begin();
          strip.setBrightness(255);  // Full brightness
          strip.clear();
          strip.show();
          Serial.println("Strip initialized at full brightness");
        
          // Connect to WiFi
          Serial.println("Connecting to WiFi...");
          WiFi.begin(ssid, password);
          
          while (WiFi.status() != WL_CONNECTED) {
              delay(500);
              Serial.print(".");
              // Blink first LED while connecting
              strip.setPixelColor(0, strip.Color(128, 128, 128));  // Grey for L
              strip.show();
              delay(250);
              strip.setPixelColor(0, 0);
              strip.show();
              delay(250);
          }
          Serial.println("\nConnected!");
        }
        
        void loop() {
          static unsigned long lastUpdate = 0;
          const unsigned long UPDATE_INTERVAL = 20000; // 20 seconds
        
          if(WiFi.status() == WL_CONNECTED) {
              unsigned long currentTime = millis();
              if(currentTime - lastUpdate >= UPDATE_INTERVAL) {
                  lastUpdate = currentTime;
                  getTrainData();
              }
              updateBlinkingLEDs();
          }
        }
        
        void updateBlinkingLEDs() {
          static unsigned long lastBlink = 0;
          const unsigned long BLINK_INTERVAL = 500;
          
          if(millis() - lastBlink >= BLINK_INTERVAL) {
              lastBlink = millis();
              strip.show();
          }
        }
        
        void getTrainData() {
          WiFiClientSecure client;
          client.setInsecure();
          HTTPClient https;
          
          Serial.println("Getting L train data...");
          if(https.begin(client, trainURL)) {
              int httpCode = https.GET();
              if(httpCode > 0) {
                  String payload = https.getString();
                  Serial.println("L train data received");
                  simulateTrains();  // For testing
              }
              https.end();
          }
        }
        
        void simulateTrains() {
          Serial.println("Starting simulation...");
          strip.clear();
          Serial.println("Strip cleared");
          
          // Simulate multiple trains (grey for L train)
          updateLED(strip, 0, TRAIN_PRESENT);      // Train at 8th Ave
          updateLED(strip, 2, TRAIN_APPROACHING);  // Train approaching Union Square
          updateLED(strip, 5, TRAIN_PRESENT);      // Train at Bedford
          updateLED(strip, 13, TRAIN_APPROACHING); // Train approaching Myrtle-Wyckoff
          updateLED(strip, 23, TRAIN_PRESENT);     // Train at Canarsie
          
          strip.show();
          Serial.println("Strip show called");
        }
        
        void updateLED(Adafruit_NeoPixel &strip, int ledIndex, int trainStatus) {
          Serial.print("Setting LED ");
          Serial.print(ledIndex);
          Serial.print(" to status ");
          Serial.println(trainStatus);
          
          uint32_t color = strip.Color(128, 128, 128);  // Grey for L train
          
          switch(trainStatus) {
              case TRAIN_PRESENT:
                  strip.setPixelColor(ledIndex, color);
                  Serial.println("Set to solid grey");
                  break;
              case TRAIN_APPROACHING:
                  if((millis() / 500) % 2) {
                      strip.setPixelColor(ledIndex, color);
                      Serial.println("Set to blinking grey (on)");
                  } else {
                      strip.setPixelColor(ledIndex, 0);
                      Serial.println("Set to blinking grey (off)");
                  }
                  break;
              default:
                  strip.setPixelColor(ledIndex, 0);
                  Serial.println("Set to off");
                  break;
          }
        }

I chose to only program it going one direction for simplicity's sake, but I wonder how to best represent trains going east and westbound at the same time; it would be a bit chaotic for a train system like New York's (too many flashing lights all at once) but it would probably be doable for Boston's T or a smaller scale transit system.

LEDs are working! It was fun to compare this against Google Maps' feed of the subway arrivals, though mine had a lag due to the fetching limits of the ESP32.

You can see that there are currently trains at 8th Avenue, Union Square, and Bedford Avenue Stations, which makes sense since they are the three busiest subway stations that the L goes to:

vinyl

Here's a video of it showing the live-time data fetching. I programmed it to update every 20 seconds; I cropped the video, and you'll see the train change stations and the "train" (light) move to the next "station (LED).