Display real-time NYC subway arrival times on an LED matrix using a Raspberry Pi. Works with all NYC subway lines - just configure your station and go!
- Real-time subway arrival data from all MTA GTFS-RT feeds
- Station-based configuration — show every train stopping at your station, including rerouted trains (e.g., F trains running on C line tracks during service changes)
- Supports all NYC subway routes with official MTA colors
- Programmatic route bullets rendered in each line's official color
- Direction-based display showing both directions simultaneously
- Efficient, lightweight code optimized for Raspberry Pi
- Easy configuration via
.envfile - no code editing required - Stale cache fallback keeps the display running during brief network outages
- Graceful shutdown on SIGTERM (systemd-friendly) with display cleanup
- Comprehensive logging with rotation for 24/7 operation
- Automatic retry logic with exponential backoff
- Simulation mode for development without LED hardware
All NYC subway lines are supported with their official MTA colors:
| Color | Routes |
|---|---|
| Blue | A, C, E |
| Orange | B, D, F, M |
| Red | 1, 2, 3 |
| Green | 4, 5, 6 |
| Purple | 7 |
| Yellow | N, Q, R, W |
| Light Green | G |
| Brown | J, Z |
| Gray | L, S (shuttles) |
The display shows upcoming trains separated by direction:
- Line 1: Northbound trains (e.g., "Ma" + colored C bullet + "3m")
- Line 2: Southbound trains (e.g., "Bk" + colored F bullet + "2m")
Each route bullet is drawn as a filled circle in the route's official MTA color with a white letter inside. When trains from multiple lines are running (e.g., C and F during a service change), each shows its own colored bullet automatically.
Direction labels are customizable using 2-letter borough codes (Ma=Manhattan, Bk=Brooklyn, Qn=Queens, Bx=Bronx). Use lowercase for the second letter. You can also use Nb=Northbound/Sb=Southbound or Up=Uptown/Dt=Downtown if you want.
- Raspberry Pi (tested on Pi 4B)
- RGB LED Matrix (this project uses 2 chained 32x64 matrices = 128x32 display)
- Adafruit RGB Matrix HAT or Bonnet
- 5V Power Supply for the LED matrices
nycsubwayclock/
├── main.py # Main application entry point
├── config.py # Configuration management with validation
├── .env.example # Configuration template (copy to .env)
├── train_times/ # Subway data fetching module
│ ├── __init__.py
│ └── fetch.py # GTFS feed processing with caching
├── display/ # LED matrix display module
│ ├── __init__.py
│ └── update.py # Display rendering (DisplayManager class)
├── utils/ # Utility functions
│ ├── __init__.py
│ └── helpers.py # Helper functions
├── MTA.ttf # Custom MTA font
├── nyct-gtfs/ # NYC Transit GTFS library (submodule)
└── rpi-rgb-led-matrix/ # RGB LED matrix library (submodule)
git clone --recursive https://github.com/dustinniles/nycsubwayclock.git
cd nycsubwayclockNote: The --recursive flag is important to pull the required submodules (nyct-gtfs and rpi-rgb-led-matrix).
# Install Python dependencies
pip install -r requirements.txt
# Install RGB matrix library (follow their instructions)
cd rpi-rgb-led-matrix
make build-python PYTHON=$(which python3)
sudo make install-python PYTHON=$(which python3)
cd ..Copy the example configuration and customize it:
cp .env.example .envEdit .env to set your station. You only need to change the stop IDs and direction labels:
# Your station's stop IDs (find in nyct-gtfs/nyct_gtfs/gtfs_static/stops.txt)
STOP_IDS=A44N,A44S
# Direction labels for your station
DIRECTION_NORTH_LABEL=Ma
DIRECTION_SOUTH_LABEL=BkThat's it — no route configuration needed. The clock queries all MTA feeds and shows every train stopping at your station automatically.
Stop IDs are in the GTFS static data at nyct-gtfs/nyct_gtfs/gtfs_static/stops.txt. Each station has IDs ending in N (northbound) and S (southbound).
Common examples:
A44N,A44S= Clinton-Washington Avs (A/C line, Brooklyn)A42N,A42S= Hoyt-Schermerhorn (A/C/G lines)635N,635S= 14 St-Union Sq (4/5/6/N/Q/R/W lines)725N,725S= Times Sq-42 St (1/2/3 lines)R20N,R20S= DeKalb Av (B/D/F/M/N/Q/R/W lines)
You can search the file for your station name:
grep -i "your station name" nyct-gtfs/nyct_gtfs/gtfs_static/stops.txtpython main.pyLogs will be written to logs/subway_clock.log.
For 24/7 operation: See RUNNING_CONTINUOUSLY.md for setting up auto-start on boot, monitoring, and maintenance recommendations.
You can develop and test without an LED matrix by enabling simulation mode:
SIMULATE_DISPLAY=trueThis logs the display output instead of rendering to hardware.
The clock queries all 8 MTA GTFS-RT feeds on every refresh cycle and filters results to trains stopping at your configured stop IDs. This means:
- Normal days: Only trains that regularly serve your station appear
- Service changes: Rerouted trains automatically appear with the correct colored bullet (e.g., F trains running on C line tracks show an orange F bullet)
- No manual reconfiguration: You never need to update the clock when service changes occur
The feeds are cached for 15 seconds, so the 8-feed sweep happens at most once per cache interval — typically taking ~2 seconds on a Pi 4.
All configuration is done through the .env file. See .env.example for full documentation.
| Setting | Description | Default |
|---|---|---|
STOP_IDS |
Comma-separated stop IDs (northbound, southbound) | A44N,A44S |
MAX_TRAINS_PER_DIRECTION |
Maximum trains to show per direction | 3 |
MAX_MINUTES_AWAY |
Maximum minutes out to show (1-120) | 30 |
DIRECTION_NORTH_LABEL |
Label for northbound direction (2 letters) | Ma |
DIRECTION_SOUTH_LABEL |
Label for southbound direction (2 letters) | Bk |
DISPLAY_REFRESH_CYCLE |
Seconds between display refreshes | 5 |
CACHE_TTL_SECONDS |
How long to cache MTA feed data | 15 |
STALE_CACHE_MAX_SECONDS |
Max age of stale data to show during outages | 300 |
MATRIX_ROWS |
LED matrix rows | 32 |
MATRIX_COLS |
LED matrix columns | 64 |
MATRIX_CHAIN_LENGTH |
Number of chained panels | 2 |
MATRIX_GPIO_SLOWDOWN |
GPIO slowdown (4 for Pi 4, 3 for Pi 3) | 4 |
MATRIX_BRIGHTNESS |
Display brightness (0-100) | 50 |
SIMULATE_DISPLAY |
Run without hardware (true/false) | false |
FONT_PATH |
Path to font file | MTA.ttf |
FONT_SIZE |
Font size in pixels | 16 |
LOG_LEVEL |
Logging level (DEBUG, INFO, WARNING, ERROR) | INFO |
# Times Square (1/2/3)
STOP_IDS=725N,725S
DIRECTION_NORTH_LABEL=Up
DIRECTION_SOUTH_LABEL=Dt
# Union Square (N/Q/R/W and 4/5/6)
STOP_IDS=635N,635S
DIRECTION_NORTH_LABEL=Up
DIRECTION_SOUTH_LABEL=Dt
# Nassau Av (G train)
STOP_IDS=G26N,G26S
DIRECTION_NORTH_LABEL=Qn
DIRECTION_SOUTH_LABEL=BkIf you're using just one 64x32 matrix:
MATRIX_CHAIN_LENGTH=1# Pi 3 or older
MATRIX_GPIO_SLOWDOWN=3
# Pi 4
MATRIX_GPIO_SLOWDOWN=4- Verify your stop IDs are correct in
stops.txt - Check logs at
logs/subway_clock.logfor feed errors - Configuration is validated at startup — check for error messages
- Try adjusting
MATRIX_GPIO_SLOWDOWN(increase the value) - Increase
MATRIX_PWM_LSB_NANOSECONDS(e.g., 130) - Ensure adequate power supply to LED matrices
- Make sure you cloned with
--recursiveflag - Reinstall dependencies:
pip install -r requirements.txt - Rebuild the rgbmatrix library:
make build-python PYTHON=$(which python3) && sudo make install-python PYTHON=$(which python3)in therpi-rgb-led-matrix/directory
- Run with sudo if needed:
sudo python main.py - Check file permissions
- Set
SIMULATE_DISPLAY=truein.envto log display output instead
This project builds on the excellent work of others:
- nyct-gtfs by Andrew Dickinson - NYC Transit GTFS feed library
- rpi-rgb-led-matrix by Henner Zeller - RGB LED matrix driver
- MTA Countdown Clock Font by Trammell Hudson - Custom MTA font
Originally built with GitHub Copilot as a Christmas gift for my girlfriend. Refactored with Claude to make it more maintainable and accessible for others.
Feel free to open issues or submit pull requests! This project started as a learning experience, and improvements are always welcome.
See LICENSE file for details.
Built for New Yorkers, by a New Yorker. Every apartment should have one of these!