Eve Mawrey
Published © MIT

LED Tide Display

See the water level at a glance with an LED strip that displays the tide's actual height 1 to 1 on your wall.

IntermediateShowcase (no instructions)10 hours1,238
LED Tide Display

Things used in this project

Hardware components

Photon
Particle Photon
×1
NeoPixel strip
NeoPixel strip
×1

Software apps and online services

ThingSpeak API
ThingSpeak API

Story

Read more

Code

Display.ino

Arduino
The Particle source file that polls the ThingSpeak server for the tide measurements, relays data to the TideDisplay object, and handles the timers.
#include "TideDisplay.h"
#include <ThingSpeak.h> // from https://github.com/mathworks/thingspeak-particle

#define PIN 7

// ********** Device Parameters **********
const int displayHeight = 78; // tide display height in inches
const int numDisplayPixels = 278; // number of pixels on tide display

// ********** NON CONFIGURABLE Parameters **********
TCPClient client; // client for Thingspeak connection
char server[] = "api.thingspeak.com"; // url for Thingspeak connection
TideDisplay display = TideDisplay(numDisplayPixels, PIN, displayHeight);

Timer updateDisplay(5000, displayUpdate);
void displayUpdate()
{
    display.bubble();
}

Timer takeReadings(20000, readingUpdate);
void readingUpdate()
{
    connectToThingspeak();
}

void connectToThingspeak()
{
    int heightReading = ThingSpeak.readStringField(22641, 1).toInt();
    double nextTideReading = ThingSpeak.readStringField(131258, 1).toFloat();
    int dirReading = ThingSpeak.readStringField(47960, 1).toInt();
    
    if (heightReading != NULL && dirReading != NULL && nextTideReading != NULL) {
        double tideHeight = ((3449 - heightReading) / 24.5);
        double nextTide = (nextTideReading + 1) * 12;
        bool tideRising = (dirReading == -1 ? false : true);

        display.heightUpdate(tideHeight, nextTide, tideRising);
        
        //display.showStatus(true); // uncomment to show green LED with successful connection
    } else {
        display.showStatus(false);
    }
}

void setup()
{
    Serial.begin(9600);
    delay(500);
    
    ThingSpeak.begin(client);
    display.start();
    
    updateDisplay.start();
    takeReadings.start();
    
    readingUpdate();
}

void loop()
{
    
}

TideDisplay.h

C/C++
The header for the display runner that defines the Color struct used to store RGB colors and the TideDisplay class used to update the LED strip.
#ifndef TideDisplay_h
#define TideDisplay_h

#include <neopixel.h> // from https://github.com/technobly/Particle-NeoPixel
#include <vector>

struct Color
{
    uint8_t red;
    uint8_t green;
    uint8_t blue;
    
    Color (uint8_t red, uint8_t green,uint8_t blue) :
        red(red), green(green), blue(blue) { }
    
    Color () :
        red(0), green(0), blue(0) { }
    
    bool operator==(const Color &other) const;
    bool operator!=(const Color &other) const;
};

class TideDisplay
{
    public:
        // Constructor.
        TideDisplay(unsigned numPixels, unsigned pin, double physicalHeight);
        // Readies the display for input.
        void start();
        // Cause a bubble to run up/down the display, depending on value of rising.
        void bubble();
        // Update the tide height displayed, height in inches.
        void heightUpdate(double height, double nextHeight, bool rising);
        // Display the status of the last read at the top of the display.
        void showStatus(bool success);
        // Returns a Color that is the result of the linear gradient between the two colors.
        // Progress must be a double in the interval [0, 1].
        Color gradient(Color fromColor, Color toColor, double progress);
        double pixelsPerInch;
        
        double pixelToInch(unsigned pixel);
        unsigned inchToPixel(double inch);
        
        int lowThreshold;
        int warnThreshold;
    private:
        int _numPixels; // the number of pixels on the strip
        double _height; // the height of the display, inches
        
        int _pixelsInUse; // the number of pixels currently in use
        int _lastPredPixel; // the last height prediction in pixels
        
        os_thread_return_t _update();
        Thread* updateThread;
        
        std::vector<Color> _indicators;
        double _indicatorIntensity;
        bool _indicatorIntensityRising;
        
        void _bubbleAdvance();
        bool _bubbleRising;
        int _bubblePosition;
        
        void _setBrightness(unsigned led, double brightness);
        Color _colorAt(unsigned pixel);
        
        Adafruit_NeoPixel _pixelStrip;
        Timer _bubbleTimer;
};

#endif

TideDisplay.cpp

C/C++
The source for the TideDisplay class that receives readings from the INO and runs the LED strip.
#include "TideDisplay.h"

bool Color::operator==(const Color &other) const {
    return red == other.red && green == other.green && blue == other.blue;
}

bool Color::operator!=(const Color &other) const {
    return !(*this == other);
}

TideDisplay::TideDisplay(unsigned numPixels, unsigned pin, double physicalHeight) : 
    _pixelStrip(numPixels, pin),
    _bubbleTimer(25, &TideDisplay::_bubbleAdvance, *this),
    _height(physicalHeight),
    _numPixels(numPixels),
    _indicators(numPixels),
    pixelsPerInch(numPixels / physicalHeight),
    lowThreshold(inchToPixel(15)), warnThreshold(inchToPixel(20)) { }

void TideDisplay::start()
{
    _pixelStrip.begin();
    _pixelStrip.show();
    
    updateThread = new Thread("update", [this]() { _update(); });
}

void TideDisplay::bubble()
{
    if (!_bubbleTimer.isActive()) {
        _bubblePosition = _bubbleRising ? 0 : _pixelsInUse;
        _bubbleTimer.start();
    }
}

void TideDisplay::heightUpdate(double height, double nextHeight, bool rising)
{
    _pixelsInUse = inchToPixel(height);
    _bubbleRising = rising;
    
    // Clear old prediction
    _indicators[_lastPredPixel] = Color(0, 0, 0);
    
    /*
    // Bottom 'status' LED
    if (_pixelsInUse < lowThreshold) {
        _indicators[0] = Color(255, 0, 0);
    } else if (_pixelsInUse < warnThreshold) {
        _indicators[0] = Color(255, 255, 0);
    } else {
        _indicators[0] = Color(0, 255, 0);
    }*/
    
    // Section divider LED's
    if (_pixelsInUse > lowThreshold) {
        _indicators[lowThreshold] = Color(192, 192, 192);
    } else {
        _pixelStrip.setColor(lowThreshold, 0, 0, 0);
        _indicators[lowThreshold] = Color(0, 0, 0);
    }
    if (_pixelsInUse > warnThreshold) {
        _indicators[warnThreshold] = Color(192, 192, 192);
    } else {
        _pixelStrip.setColor(warnThreshold, 0, 0, 0);
        _indicators[warnThreshold] = Color(0, 0, 0);
    }
    
    // Prediction LED
    int nextPredPixel = inchToPixel(nextHeight);
    if (nextPredPixel < _numPixels) {
        _indicators[nextPredPixel] = gradient(Color(255, 0, 0), Color(0, 255, 0), 2 * ((double)nextPredPixel / _numPixels));
        _lastPredPixel = nextPredPixel;
    }
}

void TideDisplay::showStatus(bool success)
{
    if (success) {
        _indicators[_numPixels - 1] = Color(0, 255, 0);
    } else {
        _indicators[_numPixels - 1] = Color(255, 0, 0);
    }
    
    delay(500);
    
    _pixelStrip.setColor(_numPixels - 1, 0, 0, 0);
    _indicators[_numPixels - 1] = Color(0, 0, 0);
}

double TideDisplay::pixelToInch(unsigned pixel)
{
    return pixel / pixelsPerInch;
}

unsigned TideDisplay::inchToPixel(double inch)
{
    return inch * pixelsPerInch;
}

os_thread_return_t TideDisplay::_update()
{
    while (true) {
        for (int i = 0; i < _indicators.size(); i++) {
            Color indicatorColor = _indicators[i];
            
            if (indicatorColor != Color()) {
                Color gradientColor = gradient(indicatorColor, _colorAt(i), _indicatorIntensity);
                
                _pixelStrip.setColorDimmed(i, gradientColor.red, gradientColor.green, gradientColor.blue, (int)(0.75 * 255));
            }
        }
        
        _pixelStrip.show();
        
        _indicatorIntensity += 0.02 * (_indicatorIntensityRising ? 1 : -1);
        
        if (_indicatorIntensity < 0) {
            _indicatorIntensity = 0;
            _indicatorIntensityRising = true;
        } else if (_indicatorIntensity > 1) {
            _indicatorIntensity = 1;
            _indicatorIntensityRising = false;
        }
        
        delay(25);
    }
}

void TideDisplay::_bubbleAdvance()
{
    if (_bubblePosition - 2 > 0) {
        _setBrightness(_bubblePosition - 2, 0.55);
    }
    if (_bubblePosition - 1 > 0) {
        _setBrightness(_bubblePosition - 1, 0.60);
    }
    
    _setBrightness(_bubblePosition, 0.75);
    
    if (_bubblePosition + 1 < _pixelsInUse) {
        _setBrightness(_bubblePosition + 1, 0.60);
    }
    if (_bubblePosition + 2 < _pixelsInUse) {
        _setBrightness(_bubblePosition + 2, 0.55);
    }
    
    if (_bubbleRising)
        _bubblePosition++;
    else
        _bubblePosition--;
        
    if (_bubblePosition < -2 || _bubblePosition >= _pixelsInUse + 2) {
        _bubbleTimer.stop();
        
        for (int i = _pixelsInUse; i < _numPixels; i++) {
            _pixelStrip.setColor(i, 0, 0, 0);
        }
    }
}

void TideDisplay::_setBrightness(unsigned led, double brightness)
{
    Color color = _colorAt(led);
    
    _pixelStrip.setColorDimmed(led, color.red, color.green, color.blue, (int)(brightness * 255));
}

Color TideDisplay::_colorAt(unsigned pixel)
{
    if (pixel >= _pixelsInUse) {
        return Color(0, 0, 0);
    } else {
        return gradient(Color(0, 64, 128), Color(154, 154, 154), (double)pixel / _numPixels);
    }
}

Color TideDisplay::gradient(Color fromColor, Color toColor, double progress)
{
    if (progress > 1)
        progress = 1;
    if (progress < 0)
        progress = 0;
    
    Color result = Color(0, 0, 0);
    result.red = (fromColor.red * (1 - progress)) + (toColor.red * progress);
    result.green = (fromColor.green * (1 - progress)) + (toColor.green * progress);
    result.blue = (fromColor.blue * (1 - progress)) + (toColor.blue * progress);

    return result;
}

GitHub

The GitHub repository for the Tide Display project.

Credits

Eve Mawrey

Eve Mawrey

1 project • 10 followers
Software developer and graduate of Khoury College of Computer Science at Northeastern University
Thanks to Robert Mawrey.

Comments