Dawn DuPriest
Published © CC BY

Tweet-able Holiday Hoodie of Community

Share your emotions about 2020 on Twitter, and this hoodie responds with a light and music show.

IntermediateFull instructions provided6 hours220

Things used in this project

Hardware components

Adafruit Feather HUZZAH with ESP8266 WiFi
Adafruit Feather HUZZAH with ESP8266 WiFi
×1
Adafruit Music Maker FeatherWing
×1
Adafruit Soft Flexible Wire Neopixel Strand
×1
SparkFun Hamburger Mini Speaker
×1
Li-Ion Battery 1000mAh
Li-Ion Battery 1000mAh
×1
Adafruit Female Headers for Feather
×1
Adafruit Inline Toggle Switch
×1

Software apps and online services

Twitter service
IFTTT Twitter service
Arduino IDE
Arduino IDE
Adafruit IO

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
needle and thread

Story

Read more

Schematics

Wiring Diagram

Neopixel DIN is wired to GPIO4
Pushbutton wired to the A pin on Huzzah
Voltage divider needed for pushbutton since A takes 0-1v and Huzzah has 3.3v output

Code

Arduino Code sweater2020_flexible

Arduino
Arduino code that receives values from Adafruit IO, parses the text, and based on key words, plays music and runs a light animation. There's another file config.h which contains wifi password and adafruit io feed name and key.
// Adafruit IO IFTTT Example - Gmailbox
// Tutorial Link: https://learn.adafruit.com/gmailbox
//
// Adafruit invests time and resources providing this open source code.
// Please support Adafruit and open source hardware by purchasing
// products from Adafruit!
//
// Written by Brent Rubell for Adafruit Industries
// Copyright (c) 2018 Adafruit Industries
// Licensed under the MIT license.
//
// All text above must be included in any redistribution.

/************************** Configuration ***********************************/


// include SPI, MP3 and SD libraries
#include <SPI.h>
#include <SD.h>
#include <Adafruit_VS1053.h>

// These are the pins used
#define VS1053_RESET   -1     // VS1053 reset pin (not used!)

// a bunch of ifs for different feather boards
// Feather ESP8266
#if defined(ESP8266)
  #define VS1053_CS      16     // VS1053 chip select pin (output)
  #define VS1053_DCS     15     // VS1053 Data/command select pin (output)
  #define CARDCS          2     // Card chip select pin
  #define VS1053_DREQ     0     // VS1053 Data request, ideally an Interrupt pin
// Feather ESP32
#elif defined(ESP32)
  #define VS1053_CS      32     // VS1053 chip select pin (output)
  #define VS1053_DCS     33     // VS1053 Data/command select pin (output)
  #define CARDCS         14     // Card chip select pin
  #define VS1053_DREQ    15     // VS1053 Data request, ideally an Interrupt pin

// Feather Teensy3
#elif defined(TEENSYDUINO)
  #define VS1053_CS       3     // VS1053 chip select pin (output)
  #define VS1053_DCS     10     // VS1053 Data/command select pin (output)
  #define CARDCS          8     // Card chip select pin
  #define VS1053_DREQ     4     // VS1053 Data request, ideally an Interrupt pin

// WICED feather
#elif defined(ARDUINO_STM32_FEATHER)
  #define VS1053_CS       PC7     // VS1053 chip select pin (output)
  #define VS1053_DCS      PB4     // VS1053 Data/command select pin (output)
  #define CARDCS          PC5     // Card chip select pin
  #define VS1053_DREQ     PA15    // VS1053 Data request, ideally an Interrupt pin

#elif defined(ARDUINO_NRF52832_FEATHER )
  #define VS1053_CS       30     // VS1053 chip select pin (output)
  #define VS1053_DCS      11     // VS1053 Data/command select pin (output)
  #define CARDCS          27     // Card chip select pin
  #define VS1053_DREQ     31     // VS1053 Data request, ideally an Interrupt pin

// Feather M4, M0, 328, nRF52840 or 32u4
#else
  #define VS1053_CS       6     // VS1053 chip select pin (output)
  #define VS1053_DCS     10     // VS1053 Data/command select pin (output)
  #define CARDCS          5     // Card chip select pin
  // DREQ should be an Int pin *if possible* (not possible on 32u4)
  #define VS1053_DREQ     9     // VS1053 Data request, ideally an Interrupt pin

#endif






// edit the config.h tab and enter your Adafruit IO credentials
// and any additional configuration needed for WiFi, cellular,
// or ethernet clients.
#include "config.h"

// Import Servo Libraries
#if defined(ARDUINO_ARCH_ESP32)
  // ESP32Servo Library (https://github.com/madhephaestus/ESP32Servo)
  // installation: library manager -> search -> "ESP32Servo"
  #include <ESP32Servo.h>
#else
  //#include <Servo.h>
#endif

#include <Adafruit_NeoPixel.h>

#define LED_PIN 4
#define LED_COUNT 50



Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB+NEO_KHZ800);

Adafruit_VS1053_FilePlayer musicPlayer = 
  Adafruit_VS1053_FilePlayer(VS1053_RESET, VS1053_CS, VS1053_DCS, VS1053_DREQ, CARDCS);


class Pixel
{
  // Class Member Variables
  // These are initialized at startup
  public:
   int bright;      
   int red;
   int green;
   int blue;    
 
  
  public:
  Pixel()
  {
    bright = 0;
    red = 0;
    green = 0;
    blue = 0;
  }
  
  Pixel(int pbright, int pred, int pgreen, int pblue)
  {
  bright = pbright;
  red = pred;
  green = pgreen;
  blue = pblue;
  }
};

Pixel plist[LED_COUNT];

/************************ Example Starts Here *******************************/

// pin used to control the servo
#define SERVO_PIN 14

// Flag's up position, in degrees
#define FLAG_UP 0

// Flag's down position, in degrees
#define FLAG_DOWN 180

// How long to hold the flag up, in seconds
#define FLAG_DELAY 2

// create an instance of the servo class
//Servo servo;

// set up the feeds
AdafruitIO_Feed *tw_feed = io.feed("Sweater2020_feed");
//AdafruitIO_Feed *tw_feed2 = io.feed("Twitter_feed");

bool received_tweet = false;
bool musicIsWorking = true;
bool wifiIsWorking = true;
String tweettext;  // tweets should only be 280 characters


void setup() {

  // start the serial connection
  Serial.begin(115200);

  // wait for serial monitor to open
  while(! Serial);

  Serial.print("IFTTT Twitter");

  // tell the servo class which pin we are using
  //servo.attach(SERVO_PIN);

  // connect to io.adafruit.com
  Serial.print("Connecting to Adafruit IO");
  io.connect();

  // set up a message handler for the feed.
  // the handleMessage function (defined below)
  // will be called whenever a message is
  // received from adafruit io.
  tw_feed->onMessage(handleMessage);

  // wait for a connection
  int starttime = millis();
  int curtime = millis();
  
  
   while((io.status() < AIO_CONNECTED) &&(curtime <= starttime + 10000))  
   {
     Serial.print(".");
     delay(500);
     curtime = millis();
   }
   if(curtime >= starttime + 10000)
   {
      Serial.println("");
      Serial.println("Couldn't connect to wifi.");
      wifiIsWorking = false;
      pinMode(1, OUTPUT);
      digitalWrite(1, HIGH);
   }

  // we are connected
  if(wifiIsWorking)
  {
    Serial.println();
    Serial.println(io.statusText());
    tw_feed->get();

  }

  strip.begin();
  strip.clear();
  strip.show();
  strip.setBrightness(40);

  if (! musicPlayer.begin()) { // initialise the music player
     Serial.println(F("Couldn't find VS1053, do you have the right pins defined?"));
     musicIsWorking = false;
  }

    if (!SD.begin(CARDCS)) {
    Serial.println(F("SD failed, or not present"));
    musicIsWorking = false;  // don't do anything more
  }
  else
  {
    Serial.println("SD OK!");
  }
  
  
  // list files
  printDirectory(SD.open("/"), 0);
  
  // Set volume for left, right channels. lower numbers == louder volume!
  musicPlayer.setVolume(0,0);
  
#if defined(__AVR_ATmega32U4__) 
  // Timer interrupts are not suggested, better to use DREQ interrupt!
  // but we don't have them on the 32u4 feather...
  musicPlayer.useInterrupt(VS1053_FILEPLAYER_TIMER0_INT); // timer int
#else
  // If DREQ is on an interrupt pin we can do background
  // audio playing
  musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT);  // DREQ int
#endif



  

}

void loop() {

  // io.run(); is required for all sketches.
  // it should always be present at the top of your loop
  // function. it keeps the client connected to
  // io.adafruit.com, and processes any incoming data.
  int btn;
  btn = analogRead(A0);
  // if button is pressed, cue a random animation
  if(btn > 800)
  {
    Serial.println("Button pressed");
    int r = random(125);
    if(r < 25)
    {
      if(musicIsWorking)
      {
        Serial.println("Playing Mariah Carey!");
        musicPlayer.startPlayingFile("/mariah.mp3");
      }
      xmas();
    }
    else if(r >= 25 && r < 50)
    {
      if(musicIsWorking)
      {
        Serial.println("Playing Cher!");
        musicPlayer.startPlayingFile("/forthelonely.mp3");
      }
      lonely();
    }
    else if(r >= 50 && r < 75)
    {
      if(musicIsWorking)
      {
        Serial.println("Playing The Greatest Showman!");
        musicPlayer.startPlayingFile("/thisisme.mp3");
      }
      rainbow(3);
      strip.clear();
      strip.show();
    delayms(600);
    pulseRainbow(100,3);

    }
    else if(r >= 75 && r < 100)
    {
      if(musicIsWorking)
      {
        Serial.println("Playing Andy Grammer!");
        musicPlayer.startPlayingFile("/dontgiveup2.mp3");
      }
     
      theaterChase(strip.Color(255,0,0),80);
      theaterChase(strip.Color(255,0,255),110);
      theaterChase(strip.Color(0,0,255),110);
      delayms(30);
      strip.clear();
      strip.show();
    }
    else
    {
      if(musicIsWorking)
      {
        Serial.println("Playing Phantom!");
        musicPlayer.startPlayingFile("/masquerade.mp3");
      }
      masks();
      strip.clear();
      strip.show();
        
    }
  }

  // need to include this to poll for tweets
  if(wifiIsWorking)
  {
    io.run();
  }
// manage key words in tweets
  if(received_tweet && (tweettext.indexOf("lonely") >= 0))
  {
    if(musicIsWorking)
    {
      musicPlayer.startPlayingFile("/forthelonely.mp3");
    }
    
    lonely();
    received_tweet = false;
  }
  else if(received_tweet && ((tweettext.indexOf("christmas") >= 0) || (tweettext.indexOf("festive") >= 0)))
  {
    if(musicIsWorking)
    {
      musicPlayer.startPlayingFile("/mariah.mp3");
    }
   
    xmas();
    received_tweet = false;
  }
  else if(received_tweet && (tweettext.indexOf("pride") >= 0))
  {
    if(musicIsWorking)
    {
      musicPlayer.startPlayingFile("/thisisme.mp3");
    }
   
    rainbow(3);
    strip.clear();
    strip.show();
    delayms(600);
    pulseRainbow(100,3);
    received_tweet = false;
  }
  else if(received_tweet && (tweettext.indexOf("mask") >= 0))
  {
    if(musicIsWorking)
    {
      musicPlayer.startPlayingFile("/masquerade.mp3");
    }
    masks();
    strip.clear();
    strip.show();
    received_tweet = false;
  }
  else if(received_tweet && ((tweettext.indexOf("solidarity") >= 0) || (tweettext.indexOf("together") >= 0)))
  {
    if(musicIsWorking)
    {
      musicPlayer.startPlayingFile("/dontgiveup2.mp3");
    }
   
    theaterChase(strip.Color(255,0,0),100);
    theaterChase(strip.Color(255,0,255),100);
    theaterChase(strip.Color(0,0,255),100);
    delayms(30);
    strip.clear();
    strip.show();
    received_tweet = false;
  }
  else if(received_tweet && tweettext.indexOf("red") >= 0)
  {
    colorWipe(strip.Color(255,0,0),100);
    received_tweet = false;
  }
  else if(received_tweet && tweettext.indexOf("green") >= 0)
  {
    colorWipe(strip.Color(0,255,0),100);
    received_tweet = false;
  }
  else if(received_tweet && tweettext.indexOf("blue") >= 0)
  {
    colorWipe(strip.Color(0,0,255),100);
    received_tweet = false;
  }
  else if(received_tweet && tweettext.indexOf("cyan") >= 0)
  {
    colorWipe(strip.Color(0,255,255),100);
    received_tweet = false;
  }
  else if(received_tweet && tweettext.indexOf("yellow") >= 0)
  {
    colorWipe(strip.Color(255,255,0),100);
    received_tweet = false;
  }
  else if(received_tweet && tweettext.indexOf("magenta") >= 0)
  {
    colorWipe(strip.Color(255,0,255),100);
    received_tweet = false;
  }
}

// this function is called whenever a 'tweet' message
// is received from Adafruit IO. it was attached to
// the twitter feed in the setup() function above.
void handleMessage(AdafruitIO_Data *data) {

  Serial.println("A tweet came in!");
  
  Serial.println(data->value());
  tweettext = String(data->value());
  tweettext.toLowerCase();
  received_tweet = true;
   
}


void colorWipe(uint32_t color, int wait) {
  for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
    strip.setPixelColor(i, color);         //  Set pixel's color (in RAM)
    strip.show();                          //  Update strip to match
    delay(wait);                           //  Pause for a moment
  }
}


/// File listing helper
void printDirectory(File dir, int numTabs) {
   while(true) {
     
     File entry =  dir.openNextFile();
     if (! entry) {
       // no more files
       //Serial.println("**nomorefiles**");
       break;
     }
     for (uint8_t i=0; i<numTabs; i++) {
       Serial.print('\t');
     }
     Serial.print(entry.name());
     if (entry.isDirectory()) {
       Serial.println("/");
       printDirectory(entry, numTabs+1);
     } else {
       // files have sizes, directories do not
       Serial.print("\t\t");
       Serial.println(entry.size(), DEC);
     }
     entry.close();
   }
}


void xmas()
{

  int r;
  
  for(int i = 0; i < 340;i++)
  {
    r = random(LED_COUNT);
    plist[r].bright = 255;
    plist[r].red = 255;
    r = random(LED_COUNT);
    plist[r].green = 255;
    for(int j = 0; j < LED_COUNT; j++)
    {
      if(plist[j].red < 0)
        plist[j].red = 0;
       if(plist[j].green < 0)
        plist[j].green = 0;
      strip.setPixelColor(j,strip.Color(plist[j].red,plist[j].green,0));
      if(plist[j].red > 0)
       plist[j].red = plist[j].red - 20;
      if(plist[j].green > 0)
       plist[j].green = plist[j].green - 20;
    }
    
    strip.show();
    delayms(40);
    
    
  }
  strip.clear();
  strip.show();
    delayms(20);
}


void lonely()
{
  int r;
  int r2;
  int g;
  int b;
  int counter = 0;

  for(int c = 0; c < LED_COUNT; c++)
  {
    plist[c].red = 0;
    plist[c].blue = 0;
    plist[c].green = 0;
  }
  
  for(int i = 0; i < 1200;i++)
  {
    counter++;
    if(counter > 35)
    {
      for(int q = 0; q < 7; q++)
      {
        r = random(LED_COUNT);
        r2 = random(100);
        g = random(100);
        plist[r].bright = 255;
        plist[r].red = r2;
        plist[r].blue = 255;
        plist[r].green = g;
        counter = 0;
      }
    }
    
    for(int j = 0; j < LED_COUNT; j++)
    {
      if(plist[j].red < 0)
        plist[j].red = 0;
      if(plist[j].green < 0)
        plist[j].green = 0;
      if(plist[j].blue < 0)
        plist[j].blue = 0;
      strip.setPixelColor(j,strip.Color(plist[j].red,plist[j].green,plist[j].blue));
      if(plist[j].red > 0)
       plist[j].red = plist[j].red - 1;
      if(plist[j].green > 0)
       plist[j].green = plist[j].green - 1;
      if(plist[j].blue > 0)
       plist[j].blue = plist[j].blue - 3;
    } 
    strip.show();
    delay(10);
  }
  strip.clear();
  strip.show();
    delay(20);
}


// taken from strandtest example program
void rainbow(int wait) {
  for(int q = 0; q < 2; q++)
  {
    for(long firstPixelHue = 0; firstPixelHue < 5*65536; firstPixelHue += 256) {
      for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
        
        int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
        
        strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
      }
      strip.show(); // Update strip with new contents
      delay(wait);  // Pause for a moment
    }
  }
  strip.clear();
  strip.show();
  delay(1);
}



void pulseRainbow(int wait, int numtimes)
{
  
  int i;
  for (i = 0; i < LED_COUNT; i++)
  {
    switch(i%6){
     case 0:
      plist[i].red = 255;
      plist[i].green = 0;
      plist[i].blue = 0;
      break;
    case 1:
      plist[i].red = 255;
      plist[i].green= 100;
      plist[i].blue = 0;
      break;
    case 2:
      plist[i].red = 255;
      plist[i].green= 255;
      plist[i].blue = 0;
      break;
    case 3:
       plist[i].red = 0;
      plist[i].green= 255;
      plist[i].blue = 0;
      break;
    case 4:
       plist[i].red = 0;
      plist[i].green= 0;
      plist[i].blue = 255;
      break;
    case 5:
      plist[i].red = 255;
      plist[i].green= 0;
      plist[i].blue = 255;
      break;
    }
  }
  
  int b;
  for(int x = 0; x < numtimes; x++)
  {
    for(b = 255; b > 1 ; b-=16)
    {  
      for(i = 0; i < LED_COUNT; i++)
      {
        strip.setPixelColor(i,strip.Color((plist[i].red > b ? plist[i].red-b : 0),
                    (plist[i].green > b ? plist[i].green-b : 0),
                    (plist[i].blue > b ? plist[i].blue-b : 0)));      
      }
      strip.show();
      delay(1);
    }


    for(i = 0; i < LED_COUNT; i++)
      {
        strip.setPixelColor(i,strip.Color(plist[i].red,plist[i].green,plist[i].blue));
      }
      strip.show();
      delay(20);
      
    
    for(b = 1; b < 255; b+=4)
    {
      for(i = 0; i < LED_COUNT; i++)
      {
        strip.setPixelColor(i,strip.Color((plist[i].red > b ? plist[i].red-b : 0),
                    (plist[i].green > b ? plist[i].green-b : 0),
                    (plist[i].blue > b ? plist[i].blue-b : 0)));
      }
      strip.show();
      delay(1);
    }
    delayms(wait);
  }

    strip.clear();
    strip.show();
    delay(1);
}



void delayms(int m)
{
  int starttime = millis();
  int curtime = millis();
  while(starttime + m > curtime)
  {
    yield();
    curtime = millis();
  }
}


void masks()
{
  Pixel mlist[] = {Pixel(0,100,100,0),Pixel(0,120,120,0),Pixel(0,160,160,0),Pixel(0,200,200,0),
      Pixel(0,255,255,0),Pixel(0,255,255,50),Pixel(0,255,255,100),Pixel(0,255,255,150),Pixel(0,255,255,200),
      Pixel(0,255,255,255)};  
  int startpixel = 0;
  int curpixel = 0;

  for(int x = 0; x < 1500; x++)
  {
    strip.clear();
    for(int i = 0; i < 10; i++)
    {
      strip.setPixelColor(curpixel, strip.Color(mlist[i].red,mlist[i].green,mlist[i].blue));
      curpixel++;
      if(curpixel > LED_COUNT)
      {
        curpixel = 0;
      }
    }
    strip.show();
    startpixel++;
    if(startpixel > LED_COUNT)
      startpixel = 0;
  
    curpixel = startpixel;
    delayms(5);
  }  
  
}


void theaterChase(uint32_t color, int wait) {
  for(int a=0; a<19; a++) {  // Repeat 40 times...
    for(int b=0; b<3; b++) { //  'b' counts from 0 to 2...
      strip.clear();         //   Set all pixels in RAM to 0 (off)
      // 'c' counts up from 'b' to end of strip in steps of 3...
      for(int c=b; c<strip.numPixels(); c += 3) {
        strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
      }
      strip.show(); // Update strip with new contents
      delayms(wait);  // Pause for a moment
    }
  }
}

Credits

Dawn DuPriest

Dawn DuPriest

7 projects • 17 followers
I teach math and technology to junior high and high school students at a small project-based school... and I make stuff.
Thanks to Jo Franchetti.

Comments