Spot Check pt. I
node.js api github
embedded github
The end goal for this project is to have a small panel that will scroll the swell sizes and tide highs/lows across a grid of LEDs, much like a stock market ticker. The idea was borne from the annoyance of having to hop back and forth between different surf spots and apps for checking upcoming conditions, plus a chance to use one of the ESP8266s I have lying around. This write-up, pt. I, is for the majority of the software and firmware work and hardware prototyping. I’ve also tweaked the behavior and focus as the project has progressed.
The stack
- node.js + Typescript express API for data layer translation and packing
- ESP8266 for all of the networking and data processing
- Arduino solely for the use of a pre-written LED strip text display library
- A 5m, 300 LED WS2812B strip cut into six equal lengths and soldered in a zigzag format to form a 6x50 matrix
- Resistors, pushbuttons, LEDs, breadboard, jumper wires
The API
I pull all of the swell/weather/tide data from Surfline’s public-ish API. They have no explicit developer documentation, but it’s dead simple to inspect the requests made when loading pages on their site, and they require no authentication.
When first inspecting the requests for what parameters they support and their response schema, I found out fairly quickly that Surfline’s normal response bodies were anything but optimized for size. A basic request for tides data over six days results in a ~9.6kB response size, while swell or spot overview requests peaked around 60-80kB each. With a usable space in RAM of roughly 40kB in the ESP minus all of the space my code was already using, it wasn’t even close to feasible for me to use their API as-is.
My custom API is fairly simple for now. It has two endpoints, one for swell and one for tides. It supports the main query parameters that the Surfline API does, which vary between their endpoints but are used to filter down the days and timing frequency of the info fetched. Its main selling factor is that it takes the verbose Surfline response (in terms of JSON depth and extra ‘fluff’ info), parses out the barebones data I need, and formats that in the correct string that I want to display on the sign. Unfortunately, this makes the API a lot less useful to others since the format is baked in. For now, I took that tradeoff to avoid having to do a bunch of string manipulation on the ESP which can be space-costly. Going forward I could see that being an easy fix, with maybe an extra formatted
query param or header accepted by the API that would switch between raw minimized JSON and the strings themselves. The formatted strings are packed in a simple JSON structure and sent as the response.
The hardware
I would very much have preferred to do everything on the ESP, especially since I ordered one of the NodeMCU development boards that breaks out all the GPIO, UART, and more pins you could ever want. However, I did not want to waste time writing a library for translating a char array to scrolling text using the LED strips. I’ve also had a terrible time trying to port Arduino libraries out of that ecosystem in the past since many are firmly entrenched in that mesh of library dependencies. Hence, the Arduino in this build. It exists solely to receive data over UART from the ESP and call the LED text library functions to display that text on the LEDs. In the future, I really should take the time to do it all on the ESP, but for multiple reasons (alluded to below), I did not do that for this iteration.
Within the ESP, I was using the Espressif SDK and toolchain for everything, not their Arduino library which it seems like every other person uses. I would way rather work in C and with a CLI toolchain using a GCC-based compiler and makefiles than the Arduino IDE and its restrictions/baggage. I didn’t mind the Espressif SDK in terms of writing the code at all, and thought the documentation was fairly straightforward and simple. Holy moly though, the development/debug experience was the worst I’ve ever had. Even worse than an Arduino. I won’t repeat myself so if you want you can read my full rant here, but in retrospect, I almost wish I chose an entirely different microcontroller family.
The Arduino code is adapted from Allen Huffman’s LEDText library. I cleaned up some of the defines and split up the logic for text display to be outside of the main loop function. I’m using the UART1 pin on the ESP (D4) for transfer from ESP -> Arduino, and it fluctuates wildly at boot. To make sure the Arduino didn’t attempt to display the ASCII gibberish, I implemented a simple system for signaling the beginning, transfer, and end of a block of data from the ESP. I receive the data using a simple SoftwareSerial class attached to a GPIO pin to leave the main serial connection open for debug printing.
In my initial design, I had the ESP continuously sending data at a configured time interval, with the Arduino parsing and then displaying in a loop. I moved away from this for a couple of reasons. One was that the text display function blocks the running code with delay
s the entire time it’s scrolling, and so I had no way to process new serial data. The second was that no matter what tricks I used I could not fit both the LEDText library and a JSON parsing library on the Arduino and have everything work. It was crazy frustrating, since the code upload size was small and there seemed to be plenty of stack and heap space when I was debugging it. Declaring the JSON parsing buffer with different sizes and both locally and globally caused different types of really weird behavior where heap memory of other variables was being corrupted or the text library silently failed, so I scrapped it all. Now, the ESP does all of the JSON parsing and logic for sending start/stop commands, and the Arduino only displays what it receives. I also switched to triggering the request/transfer/display from a button press rather than constantly from a timer interrupt. Now instead of having an always-on set of moving LEDs, I can trigger it when I want, which is much more user- and power-friendly.
The LED strip setup is simple. With each adjacent end of the cut strips soldered together, I use the LED text library in the ZIGZAG
config (end of the first strip in the top right corner was soldered to the end of the second strip directly below it, and so on). If you’d prefer to wire yours end-to-end (end of the first strip is wired back to the beginning of the second) there’s a setting for that too. I did a few back of the envelope calculations for power needed, since I would never be driving the entire set of 300 LEDs. It seemed to be relatively low and, in testing, the 5V pin of the Arduino seemed to handle it just fine. Regardless, that’s not very good nor safe, so I switched to power it through the separate Vin
on the Arduino from a 12V/2A DC power block.
I also found that only grounding the end of the LED strips that data and power enter on led to a few LEDs randomly lighting up with different colors throughout the strip. Tying the other ground wire on the other end down as well fixed that right up.
Why pt. I?
I originally intended this to be another project where I bundled the dev board and small circuit together behind whatever was externally visible and called it done. For better or for worse, this became untenable when I was forced to bring in the Arduino as a second board. It became way too unwieldy of a connection chain from ESP, to Arduino, to LED strip, to be able to tape everything together and hope it stuck.
I’m now in the process of dusting off my old EE brain cells from university and planning out a schematic for a PCB print. I’ve gone a little back and forth on whether I want to make it big and with axial through-hole components so I can hand-solder it easier, or go the SMD route and make it look nice. Regardless, it’s going to take me a bit of time to work through it and get it ordered, so I figured I should get an initial update up.
There’s also plenty more work to do in terms of configuration for what’s displayed. I’ve been testing with a specific surf break hardcoded into it, and while it works fine for me, ideally I’d extend it to allow a user to configure the spot to check from a web interface or in some other way.
Always more work to be done!