Skip to content

Instantly share code, notes, and snippets.

@abachman
Created March 1, 2018 19:43
Show Gist options
  • Save abachman/d34cc70107eeb838c2106d8a7cff8159 to your computer and use it in GitHub Desktop.
Save abachman/d34cc70107eeb838c2106d8a7cff8159 to your computer and use it in GitHub Desktop.
ESP8266 Superbowl Scoreboard
// IO Setup
#define IO_USERNAME "xxx"
#define IO_KEY "xxx"
#define WIFI_SSID "xxx"
#define WIFI_PASS "xxx"
#include "AdafruitIO_WiFi.h"
AdafruitIO_WiFi io(IO_USERNAME, IO_KEY, WIFI_SSID, WIFI_PASS);
#include <math.h> /* floor */
//// LED setup, I'm using a 1m NeoPixel strip.
#include <Adafruit_NeoPixel.h>
#define NEO_DATA_PIN 4
#define LED_PIN 0
// first actual pixel seems to be 1 with the strip
#define NUM_PIXELS 31
// test mode - 7 LED NeoPixel jewel
// #define NUM_PIXELS 7
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_PIXELS, NEO_DATA_PIN, NEO_GRB + NEO_KHZ800);
// Define the Adafruit IO feeds that will provide data.
AdafruitIO_Feed *team_a = io.feed("scoreboard.team-alpha");
AdafruitIO_Feed *team_b = io.feed("scoreboard.team-beta");
AdafruitIO_Feed *effect = io.feed("scoreboard.effect");
// Variables to hold current score and color values for the two "teams"
int a_score, b_score;
uint32_t color_a, color_b;
// Mode can only be one state at a time
enum Mode {
SCORE,
SPARKLE,
FADE,
CYCLE,
RAINBOW,
RGB,
OFF
};
Mode sb_mode = SCORE;
Mode prev_mode;
// Cycle mode
int curCycle = 0;
#define CYCLE_MAX 16
#define CYCLE_STEPS (256 / CYCLE_MAX)
/****
*
* Setup function
*
* - startup NeoPixels
* - connect to Adafruit IO
* - setup IO message handlers
* - initialize display, start in FADE mode
*
****/
void setup() {
// start the serial connection
Serial.begin(115200);
// indicator LED
pinMode(LED_PIN, OUTPUT);
// setup neopixels
strip.begin();
strip.setBrightness(255);
strip.show();
a_score = 0;
b_score = 0;
color_a = strip.Color(0, 255, 0);
color_b = strip.Color(0, 0, 255);
// connect to io.adafruit.com
Serial.print("Connecting to Adafruit IO");
io.connect();
team_a->onMessage(handleAScore);
team_b->onMessage(handleBScore);
effect->onMessage(handleEffect);
// wait for a connection
while (io.status() < AIO_CONNECTED) {
Serial.print(".");
strip.setPixelColor(1, strip.Color(255, 0, 0));
strip.show();
delay(200);
strip.setPixelColor(1, strip.Color(0, 0, 0));
strip.show();
delay(200);
}
digitalWrite(LED_PIN, LOW);
// we are connected!
Serial.println();
Serial.println(io.statusText());
colorWipe(strip.Color(50, 50, 50), 60);
sb_mode = FADE;
}
/****
*
* Main Program Loop
*
* Listen for incoming messages, handle mode changes
*
****/
void loop() {
io.run();
// blink a pixel if IO disconnects
if (io.status() < AIO_CONNECTED) {
strip.setPixelColor(1, strip.Color(255, 0, 0));
strip.show();
delay(200);
strip.setPixelColor(1, strip.Color(0, 0, 0));
strip.show();
delay(200);
}
// each time through loop() function, make sure the correct mode is running
switch (sb_mode) {
case SCORE:
if (prev_mode != SCORE) {
drawScoreboard();
}
break;
case SPARKLE:
sparkle(30);
break;
case FADE:
fader();
break;
case CYCLE:
colorWipe(Wheel(curCycle * CYCLE_STEPS), 20);
curCycle = (curCycle + 1) % CYCLE_MAX;
break;
case RAINBOW:
rainbowCycle(10);
break;
case OFF:
if (prev_mode != OFF) {
blank();
}
break;
case RGB:
break;
default:
if (prev_mode != SCORE) {
drawScoreboard();
}
}
prev_mode = sb_mode;
}
// receive scoreboard.team-beta message
void handleAScore(AdafruitIO_Data *data) {
Serial.print("TEAM A RECEIVED <- ");
Serial.println(data->toInt());
a_score = data->toInt();
drawScoreboard();
}
// receive scoreboard.team-beta message
void handleBScore(AdafruitIO_Data *data) {
Serial.print("TEAM B RECEIVED <- ");
Serial.println(data->toInt());
b_score = data->toInt();
drawScoreboard();
}
// sending a new effect locks the scoreboard into a new mode
void handleEffect(AdafruitIO_Data *data) {
String val = data->toString();
if (val == "sparkle") {
sb_mode = SPARKLE;
} else if (val == "fade") {
sb_mode = FADE;
} else if (val == "cycle") {
sb_mode = CYCLE;
} else if (val == "rainbow") {
sb_mode = RAINBOW;
} else if (val == "rgb") {
sb_mode = RGB;
} else if (val == "off") {
sb_mode = OFF;
} else {
sb_mode = SCORE;
}
}
// Given the current score values, set pixels to the appropriate colors. For
// example, 20 - 20 should mean half the pixels are color_a and half are
// color_b.
void drawScoreboard() {
float total = (float)a_score + (float)b_score;
float a_perc;
if (total != 0) {
a_perc = (float)a_score / total;
} else {
a_perc = 0.5;
}
int a_pixels = (int)floor(a_perc * (float)NUM_PIXELS);
for (int l=0; l < NUM_PIXELS; l++) {
strip.setPixelColor(l, (l < a_pixels) ? color_a : color_b);
}
strip.show();
}
// blank out pixels. This just clears the screen.
void blank() {
for (int n=0; n < NUM_PIXELS; n++) {
strip.setPixelColor(n, strip.Color(0, 0, 0));
}
strip.show();
}
// Cycle effect: pick a random color, fade up to it then back down
void fader() {
blank();
delay(500);
uint32_t init = Wheel(random(255));
uint8_t ir = (init & (255 << 16)) >> 16;
uint8_t ig = (init & (255 << 8)) >> 8;
uint8_t ib = init & 255;
int nr, ng, nb;
int fmax = 255;
// fade up
for (int f=0; f < 255; f++) {
nr = map(f, 0, fmax, 0, ir);
ng = map(f, 0, fmax, 0, ig);
nb = map(f, 0, fmax, 0, ib);
for (int l=0; l < strip.numPixels(); l++) {
strip.setPixelColor(l, strip.Color(nr, ng, ng));
}
strip.show();
delay(10);
}
delay(10);
// fade down
for (int f=255; f > 0; f--) {
nr = map(f, 0, fmax, 0, ir);
ng = map(f, 0, fmax, 0, ig);
nb = map(f, 0, fmax, 0, ib);
for (int l=0; l < strip.numPixels(); l++) {
strip.setPixelColor(l, strip.Color(nr, ng, ng));
}
strip.show();
delay(5);
}
}
// ooooooooooOOOOooo SHINY!
void sparkle(uint8_t wait) {
blank();
int n;
int sparks = floor(1000.0 / (float)wait) - 8;
uint32_t w = strip.Color(255, 255, 255);
// ~ a second of sparkles
for (int i=0; i < sparks; i++) {
n = random(strip.numPixels());
strip.setPixelColor(n, w);
strip.show();
delay(wait);
strip.setPixelColor(n, 0);
strip.show();
}
}
/////
/// Effects and color helpers
/// via https://learn.adafruit.com/florabrella/test-the-neopixel-strip
/////
// Fill the dots one after the other with a color
void colorWipe(uint32_t c, uint8_t wait) {
uint8_t dir = random(10) > 5 ? 1 : -1;
if (random(10) > 5) {
// l to r
for(uint16_t i=0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, c);
strip.show();
delay(wait);
}
} else {
// r to l
for(uint16_t i=strip.numPixels(); i > 0; i--) {
strip.setPixelColor(i, c);
strip.show();
delay(wait);
}
}
}
// Slightly different, this makes the rainbow equally distributed throughout
void rainbowCycle(uint8_t wait) {
uint16_t i, j;
for(j=0; j < 256; j++) { // one whole loop of colors
for(i=0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
}
strip.show();
delay(wait);
}
}
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
if(WheelPos < 85) {
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
} else if(WheelPos < 170) {
WheelPos -= 85;
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
} else {
WheelPos -= 170;
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment