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:
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:
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;
}
}