If I had more time, I would have made a shorter video.


New York, New York, it’s a wonderful town. The Bronx is up, and the Battery’s down! The people ride in a hole in the ground… and sometimes, on the water. (Lyrics from New York, New York, On The Town, 1949)
In this project, I iterated on the concept of a subway clock and created a version that displays the current schedule of the NYC ferry— for those who ride in style!
This product was created for my boyfriend who takes the ferry from the same stop every day to get to work. The design incorporates a perpetual scroll display that shares the next 3 departure times from his stop (North Williamsburg). He can glance over at the ferry times in the morning and determine which ferry to catch, and whether to walk, bike, or skateboard to catch his ferry. Using this clock reduces the likelihood that he will be distracted when looking up the schedule on the NYC Ferry’s mobile app. It’s a great way to start the day for anyone who relies on the ferry schedule with regularity 🙂
My Instructables Project
https://www.instructables.com/NYC-Ferry-Schedule-Ticker-Clock
The Idea

Image source.
This project was inspired by a subway clock I saw at a friend’s house– and I have since discovered a few Arduino projects that recreate this product.
Prototype circuit and form
I sketched a few ideas for the encasement– some using corrugated wavy plastic (it’s giving waves!), others using acrylic sheets. I designed the case to fit in a very specific space on my boyfriend’s desk, and ultimately mis-measured 😀 when I cut the final. I used a 1/8″ pearlescent blue acrylic from Canal Plastics, and landed on a tab and slot pattern which I cut with a laser cutter in the VFL.



Materials

The materials that I used for this project are as follows:
- Arduino ESP32
- Arduino UNO and a solderless breadboard
- 1 16×32 LED RGB matrix
- A 5V / 2A power cable
- jumper wires
- soldering equipment
- Acrylic sheets for the encasement (I used this!)
- A laser cutter
Setting Up The Display

Initially, I had planned to use an ESP32 to run my RGB matrix display and make my API call. However, because of the power requirements of the display, I switched to an Arduino UNO and a breadboard to provide the extra grounding required.
I used the Adafru.it RGB Matrix tutorial to get my display set up and working. I followed the pinout guides and wiring in the Connecting with Jumper Wires tutorial for the RGB matrix in order to create my circuit, making sure to adjust as specified in the tutorial for the UNO. Then I ran the test code examples to make sure my circuit was working and to get familiar with how to program the display.
After testing that my circuit was working, I began to customize the text on the display.
Display Circuit Construction & Code

👉 In general, staring at an LED display for hours on end at all times of day/night can be hard on the eyes, so the first thing I’d recommend doing while you’re tweaking your code is changing your text to red which is less abrasive. 👈
The following libraries are required for this project. When you install them, make sure to install their dependencies too 🙂
- RGB Matrix Panel x Adafruit (all the functions I needed to control my display)
- Adafruit GFX. Adafruit GFX library has a bunch of fonts that you can import and use to customize your display. You can futz with the cursor’s starting position depending on the height of the font.
- The Adafruit Protomatter library is designed to make the LED matrix display work with the ESP32. As I mentioned, I had some issues with voltage and also connecting the ESP32 and the display, so ultimately I didn’t use this library. But maybe you’ll have better luck 🙂
For my code, I used the scroll_text_16x32 sketch from the RGB matrix panel example, and customized from there:
- Thinking ahead to the information I wanted my ferry clock to display, I hardcoded in an example message so that I could make aesthetic adjustments to accommodate the desired text.
- I tried out some different fonts for the text using the GFX library mentioned above, ultimately landing on FreeSans 9pt 7b.
- Out of the box, text on the display zooms by very quickly, so incorporating a scroll delay was important for legibility. I introduced the delay as a variable upfront and then called it directly in my loop(), working from the code in this forum post.
- I adjusted the position of the cursor for optimal legibility of the text on the display.
- I changed the color of my text to blue, to go with the case that I designed for it.
This sketch allowed me to finish a “looks like” prototype, which served as a guide for me when it came to incorporating real data from the NYC ferry schedule.
// scrolltext demo for Adafruit RGBmatrixPanel library.
// Demonstrates double-buffered animation on our 16x32 RGB LED matrix:
// http://www.adafruit.com/products/420
// DOUBLE-BUFFERED ANIMATION DOES NOT WORK WITH ARDUINO UNO or METRO 328.
// Written by Limor Fried/Ladyada & Phil Burgess/PaintYourDragon
// for Adafruit Industries.
// BSD license, all text above must be included in any redistribution.
#include <Arduino.h>
#include <RGBmatrixPanel.h>
#include <Fonts/FreeSans9pt7b.h> // best: matrix.setCursor(textX, 12) cuts of fragmented pxls at top
#define CLK 8
#define OE 9
#define LAT 10
#define A A0
#define B A1
#define C A2
// Last parameter = 'true' enables double-buffering, for flicker-free,
// buttery smooth animation. Note that NOTHING WILL SHOW ON THE DISPLAY
// until the first call to swapBuffers(). This is normal.
RGBmatrixPanel matrix(A, B, C, CLK, LAT, OE, false);
// Similar to F(), but for PROGMEM string pointers rather than literals
#define F2(progmem_ptr) (const __FlashStringHelper *)progmem_ptr
const char str[] PROGMEM = "ER S at N WBURG in 9min...17 min...29 min..."; //hardcode in example text
int16_t textX = matrix.width();
int16_t textMin = (int16_t)sizeof(str) * -12;
uint16_t hue = 0;
const uint8_t CHAR_SPACING = 1;
const uint8_t SCROLL_DELAY = 5; // Adjust for speed control
void setup() {
matrix.begin();
matrix.setTextWrap(false); // Allow text to run off edges
matrix.setFont(&FreeSans9pt7b); // Set the custom font
// matrix.setTextColor(matrix.Color333(7, 0, 0)); // Set text color to red while debugging
matrix.setTextColor(matrix.Color333(0, 0, 7)); // Set text color to blue
}
// void loop() with slow down
void loop() {
// Clear background
matrix.fillScreen(0);
// Set the text cursor + print the string
matrix.setCursor(textX, 12); // Position text (for use with custom font size 9pt)
matrix.print(F2(str));
// Move text left (w/wrap)
if ((--textX) < textMin) textX = matrix.width();
// Update display
matrix.swapBuffers(false);
// Add delay to control scroll speed
delay(SCROLL_DELAY); // Adjust this value to control the speed. Larger value = slower scroll
}
GETting the Background on GET Requests
The New York City Ferry Service uses GTFS (General Transit Feed Specification), which is a standard format for sharing transit system data. You can read more about GTFS here. This makes it possible for us to get an up-to-date ferry schedule by making an API call.
In order to work with GTFS directly, you need to spin up a server to manage the rate/volume of data when you make your query. (Follow Robert Boscacci’s subway clock tutorial for steps.)
However, after digging around on the internet, I found Transit.Land— an open data platform that collects GTFS and other open data feeds from transit providers around the world. Transitland makes all of this data queryable via REST APIs. There are feeds from over 2,500 operators in over 55 countries, including the NYC ferry!
I read through the Transit.Land documentation, and started to decode the the key/value pair data for ferry stops and ferry routes from the source feeds. I created an account on Interline.co to get a secure API key. Armed with my API key, I started to get to work on the program for my ESP32.
ESP32 API Call Code

Writing the API call and parsing the JSON data was the most complicated part of this project, and required a lot of research. I watched this video, read through ArduinoJSON’s deserialization documentation, and read this tutorial on GET requests, and got a lot of help from @Beckathwia.
We looked at Transitland’s REST API documentation for departures. This outlined how to specify which date we were looking for, and all of the other parameters necessary to include in the call.
I discovered that the returned payload only includes the next 20 departure times from any given stop. If you don’t specify a time, it gives the first 20 departure times for that day. So, I needed to pass in the current time in order to get the upcoming departure times. We did this (thanks @Beckathwia) via concatenation. Here’s a helpful guide.
I used Network Time Protocol to get the current time (more info here), and we defined variables for the base URL, and the current time above the setup(), and then concatenated them together in the loop():
String serverName = "https://transit.land/api/v2/rest/stops/s-dr5rsvh2hf-northwilliamsburg/departures?apikey=X77sYbAjffC4kXqNHkavDFY0mY2hUPP4&relative_date=today&start_time=";
WiFiClientSecure client;
char currentTime[9]; // Buffer to hold HH:MM:SS
// later, in void loop():
String serverPath = serverName + currentTime;
The final code attached here calls the API, parses the returned JSON file, and then prints to the Serial monitor the departure time of the next ferry departing from the station that is specified in the API call parameters.
What this code does not do yet is use Software Serial to connect the ESP32 to the UNO to pass the data along to be displayed on the matrix.
Make the Box




For this design, I went with a tab and slot box because I always find it beneficial to be able to revisit my circuits if I want to improve them in the future, and a tab and slot design avoids the need for glue.
I created a simple vector design for my box in Adobe Illustrator before I learned about MakerCase, which is a web tool that instantly creates box templates for laser cutters from a set of input dimensions. I recommend using MakerCase instead of DIYing it unless you’re hoping to make something really custom 🙂
The file for the encasement I created is attached if you’d like to use it! I had initially planned to include 2 rotary switches in my design, to allow the user to rotate through routes and stops, so there are some holes in the top panel to accommodate those. There is an exit hole in the back panel for the power cord. Everything else (my UNO and all of my cables) fits snugly inside behind the display which serves as the front of the box.
I cut the pieces out of a pearlescent blue acrylic which gives off the feeling of waves. It might not be an accurate depiction of what the East River water looks like, but hey, it’s aspirational 😉

^ the pattern I created in Adobe Illustrator for my tab and slot encasement.
Retrospective & Next Steps:
Software Serial & Soldering (still)

What I learned:
This project was a fantastic refresher on coding in general. It was great to be able to dig into documentation and work at putting the pieces together.
Unfortunately, I wasn’t able to figure out how to pass the results of my API call to my UNO for display. I ran into issues handling some of the time elements, passing messages into program memory, and passing multiple strings to be displayed in sequence on my display. The summary of the steps that remain are as follows:
- I need to link up my ESP32 and my UNO, so that the ESP32 can call the API, get the next 3 departure times, and pass them as a message via Software Serial to the UNO to display on the matrix.
- Solder my circuits! Right now my display circuit is sitting nicely inside my acrylic box, but it’s not permanent, and I can change that by soldering my ESP32 to a perf board, and soldering my UNO — but this has to wait for the previous step.




































































































































