Andrew Plencner
Published © CC BY-NC

Living Sculpture - Light Pollution

I partnered with a Sculpture student to create a moving, lit up Sculpture that displays live data about energy use in Chicago.

IntermediateShowcase (no instructions)23
Living Sculpture - Light Pollution

Things used in this project

Hardware components

SG90 Micro-servo motor
SG90 Micro-servo motor
×2
Stepper Motor
Digilent Stepper Motor
Went unused.
×1
WS2812 Addressable LED Strip
Digilent WS2812 Addressable LED Strip
×1
LED (generic)
LED (generic)
I actually used pre wired ones with resistors attached already.
×7
Photon 2
Particle Photon 2
×1

Story

Read more

Schematics

Circuit Diagram

Google Drawings is a premier CAD tool

Code

Source Code

C/C++
//particle bullshit
#include <neopixel.h>
#include <Stepper.h>
#include <ArduinoJson.h>
#include "Particle.h"
SYSTEM_MODE(AUTOMATIC);
SerialLogHandler logHandler(LOG_LEVEL_INFO);
//

//strings and things setup
JsonDocument doc;
String arr[10];
//

//neopixel setup
int pixels = 20;
int border = 8;
// IMPORTANT: Set pixel COUNT, PIN and TYPE
#if (PLATFORM_ID == 32)
// MOSI pin MO
#define PIXEL_PIN SPI
// MOSI pin D2
// #define PIXEL_PIN SPI1
#else // #if (PLATFORM_ID == 32)
#define PIXEL_PIN D4
#endif
#define PIXEL_COUNT pixels
#define PIXEL_TYPE WS2812B
Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);

//stepper setup
const int rev = 2048; 
#define IN1 D0
#define IN2 D5
#define IN3 D2
#define IN4 D3
Stepper myStepper(rev, IN1, IN3 , IN2, IN4);
//

//mine
int STARS = A2;
int SERVO = A5;
int SERVETTE = D1;
int vals = 10;
Servo servo;
Servo servette;
int lastRun = -1;
int interval = 5;
int trend = 0;
float turtlePotion = 400;
//

void setup() {
  //initial stuff
  while(Particle.disconnected());
  delay(1000);
  Particle.subscribe("hook-response/energypull", extractInfo);
  strip.begin();
  
  delay(1000);
  
  //settings
  Time.zone(-6);  //Central time
  Serial.begin(9600);
  myStepper.setSpeed(15);
  
  //pins
  pinMode(STARS, OUTPUT);
  servo.attach(SERVO);
  servette.attach(SERVETTE);
  
  //zeroing servos
  servo.write(10);
  servette.write(10);
  
  //pulling data
  puller();
  
  //turn the stars on
  analogWrite(STARS, 255, 50);
  
  //turn the building lights on
  for(int i = border; i < pixels; i++){
        strip.setPixelColor(i, 15, 15, 15);
    }
    strip.show();
  
  
  
  
}


void loop() {
    
    /*
    //testing/debug loop
    
    Serial.print("The trend is ");
    Serial.println(trend);
    
    Serial.println("Testing motors...");
    stepControl(10, true);
    servo.write(90);
    servette.write(90);
    

    Serial.println("Testing if data can be accessed and LEDs...");
    Serial.print("Setting LED brightness to ");
    int mapped = map(arr[2].toInt(), 8000, 12000, 0, 255);
    Serial.println(mapped);
    analogWrite(STARS, mapped, 50);
    
    delay(2000);
    
    Serial.print("Setting each color of each WS2812B to ");
    Serial.println(mapped);
    for(int i = 0; i < 8; i++){
        strip.setPixelColor(i, mapped, mapped, mapped);
        strip.show();
    }
    
    delay(2000);
    
    Serial.println("Testing motors in the other direction...");
    stepControl(10, false);
    servo.write(0);
    servette.write(0);
    
    Serial.println("Setting lights back to 0...");
    for(int i = 0; i < 8; i++){
        strip.setPixelColor(i, 0, 0, 0);
    }
    analogWrite(STARS, 0, 50);
    
    delay(2000);
    */
    
    
    
    //should run event() at start and every "interval" minutes past the hour (if interval == 5, it runs at start, 12:00, 12:05, 12:10, etc)
    //lastRun is initialized to -1
    int curRun = Time.minute();
    if((((curRun % interval) == 0) && (curRun != lastRun)) || (lastRun == -1)){
        lastRun = curRun;
        event();
    }
    
    //ambient twinkling of the building lights that occurs between events
    twinkler();
    
    for(int i = border; i < pixels; i++){
        strip.setPixelColor(i, 15, 15, 15);
    }
    strip.show();


}





//publishes to particle
void puller() {
    
    time_t startTime;
    startTime = Time.now() - (2 * 86400);
    
    //troubleshooting
    /*
    Serial.println("DATE INFO -----------------------------");
    Serial.print("Unformatted: ");
    Serial.println(startTime);
    */
    
    String strStartTime = Time.format(startTime, TIME_FORMAT_ISO8601_FULL);//formats time in 1so8601
    
    //troubleshooting
    /*
    Serial.print("Uncut: ");
    Serial.println(strStartTime);
    */
    
    strStartTime = strStartTime.substring(0, strStartTime.length()-12);//formats time for particle
    
    //troubleshooting
    /*
    Serial.print("Final: ");
    Serial.println(strStartTime);
    */
    
    String data = "{\"minDate\":\"" + strStartTime + "\"}";
    Serial.println(data);
    
    Particle.publish("energypull", data); 
    
    
}





//handler
void extractInfo(const char *event, const char *data)
{
    Serial.println("handling...");
    
    deserializeJson(doc, data);
    
    String dataR = String(data);
    
    //fills array arr with the values in descending order by date
    for(int i = 0; i < vals; i++){
        if(dataR.indexOf(",") == -1){
            Serial.println("Responses cut off");
            break;
        }
        
        String iDat = dataR.substring(0, dataR.indexOf(","));//1234,5678,91011, -> 1234,
        iDat = iDat.substring(0,iDat.indexOf(",") - 1);//1234, -> 1234
        
        arr[i] = iDat;
        
        dataR = dataR.substring(dataR.indexOf(",") + 1);//1234,5678,91011, -> 5678,91011,
    }

    
    //troubleshooting 
    //prints values of each index in arr to serial
    Serial.println("HERE'S THE DATA:");
    for(int i = 0; i < vals; i++){
      Serial.print("Value ");
      Serial.print(i);
      Serial.print(" is ");
      Serial.println(arr[i]);
    }
    Serial.println("THAT WAS THE DATA");
    //
    
    trend = linearRegressor(arr, vals);
    Serial.print("HERE'S THE TREND: ");
    Serial.println(trend);
    
    
}





/*
//executes steps in a row while printing to monitor.  
//polarity == true means positive steps, polarity == false means negative steps
void stepControl(int chunks,int size, bool polarity){
    for(int i = 0; i < chunks; i++){
        if(polarity){
            myStepper.step((rev/size)*chunks);
            //Serial.println("chunk");
        }
        else{
            myStepper.step((rev/size)*chunks*-1);
            //Serial.println("minus chunk");
        }
    }
    Serial.println("stepper complete!");
}
*/








//occurs every 5 minutes and at startup, shows data
void event(){
    int z = 15;
    //dim stars and brighten city lights (neopixel), roll wheels
    for(int i = 255; i >= 0; i--){
        
        analogWrite(STARS, i, 50);
        
        if(z <= 255){
            for(int j = border; j < pixels; j++)//or is it pixels - 1?
            {
            strip.setPixelColor(j, z , z, z);
            }
            strip.show();
            z++;
        }
        
        //myStepper.step(rev/255);
        delay(turtlePotion);
    }
    myStepper.step(rev * 2);
    
    int mapped = map(abs(trend), -500, 500, 0, 255);
    if(trend < 0){
        for(int k = 0; k < border; k++){
            strip.setPixelColor(k, 0, mapped, 0);
        }
    }
    else{
        for(int k = 0; k < border; k++){
            strip.setPixelColor(k, mapped, mapped, mapped);
        }
    }
    strip.show();
    
    delay(1000);
    
    servo.write(90);
    servette.write(90);
    
    delay(5555);
    
    servo.write(10);
    servette.write(10);
    
    delay(1000);
    
    for(int k = 0; k < border; k++){
            strip.setPixelColor(k, 0, 0, 0);
        }
    strip.show();
    
    myStepper.step(rev/255);
    
    for(int i = 0; i <= 255; i++){
        
        analogWrite(STARS, i, 50);
        
        if(z >= 15){
            for(int j = border; j < pixels; j++)//here too
            {
            strip.setPixelColor(j, z, z, z);
            }
            strip.show();
            z--;
        }
        
        //myStepper.step(rev/255);
        
        delay(turtlePotion/2);
    }
    
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, LOW);
}









//ambient twinkling on the neopixel
void twinkler(){
    //finds a random duration, chooses a random pixel from the city lights
    int duration = random(1000,5001);
    int pixel = random(border,(pixels-1));
    uint32_t start = strip.getPixelColor(pixel);
    
    float curR = Red(start);
    float curG = Green(start);
    float curB = Blue(start);
    
    //the step is the distance to some random value, divided into 'duration' steps
    float stepR = (random(0,256) - curR)/duration;
    float stepG = (random(0,256) - curG)/duration;
    float stepB = (random(0,256) - curB)/duration;
    
    //moves lights to the random colors
    for(int i = 0; i < duration; i++){
        
        curR += stepR;
        curG += stepG;
        curB += stepB;
        
        
        strip.setPixelColor(pixel, curR, curG, curB);
        strip.show();
        delay(1);
    }
    
    delay(duration/100);//hold that color for a little bit
    
    stepR = (curR - 15)/duration;
    stepG = (curG - 15)/duration;
    stepB = (curB - 15)/duration;
    
    //moves lights back to a dim white (15,15,15)
    for(int i = 0; i < duration; i++){
        
        curR -= stepR;
        curG -= stepG;
        curB -= stepB;
        
        
        strip.setPixelColor(pixel, curR, curG, curB);
        strip.show();
        delay(1);
    }
    
}









//shamelessly stolen from https://learn.adafruit.com/multi-tasking-the-arduino-part-3/utility-functions
// Returns the Red component of a 32-bit color
    uint8_t Red(uint32_t color)
    {
        return (color >> 16) & 0xFF;
    }

// Returns the Green component of a 32-bit color
    uint8_t Green(uint32_t color)
    {
        return (color >> 8) & 0xFF;
    }

// Returns the Blue component of a 32-bit color
    uint8_t Blue(uint32_t color)
    {
        return color & 0xFF;
    }














//calculates the trend from an array of datapoints
//many thanks to google ai
double linearRegressor(String data[], int n){
    double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
    for (int i = 0; i < n; i++) {
        // According to your prompt: x=1 is index 9, x=10 is index 0
        int x = 10 - i; 
        int y = data[i].toInt();
        
        sumX += x;
        sumY += y;
        sumXY += (double)x * y;
        sumX2 += (double)x * x;
    }
    double slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
    //double intercept = (sumY - slope * sumX) / n;
    return slope;
}

Credits

Andrew Plencner
2 projects • 0 followers
Thanks to Mia Rojas.

Comments