Caroline C. Blaker
Published © MIT

LiveLights

LiveLights is a capstone IOT project miniature of a concept for installable, life-scale, interactive neopixel art.

IntermediateFull instructions providedOver 5 days149
LiveLights

Things used in this project

Hardware components

Photon 2
Particle Photon 2
×1
Adafruit VL503X Time-of-Flight Sensor
×6
Temp&Humi&Barometer Sensor (BME280)
Seeed Studio Temp&Humi&Barometer Sensor (BME280)
Any BME will work
×1
Neopixel strip - 300
×1
Machine Screw, M3
Machine Screw, M3
×21
Hardware, Washer
Hardware, Washer
×13
Connector Accessory, Hex Nut
Connector Accessory, Hex Nut
m3
×21
Machine Screw, M2
Machine Screw, M2
×6
Connector Accessory, Hex Nut
Connector Accessory, Hex Nut
m2
×6

Software apps and online services

Particle Build Web IDE
Particle Build Web IDE
Adafruit Dashboard
VS Code
Microsoft VS Code

Hand tools and fabrication machines

Glowforge
Glowforge
Soldering iron (generic)
Soldering iron (generic)
Solder Wire, 0.022" Diameter
Solder Wire, 0.022" Diameter
Premium Female/Male Extension Jumper Wires, 40 x 6" (150mm)
Premium Female/Male Extension Jumper Wires, 40 x 6" (150mm)
3D Printer (generic)
3D Printer (generic)
Breadboard, 170 Pin
Breadboard, 170 Pin

Story

Read more

Custom parts and enclosures

Front cut

Use 3/16mm translucent material in a laser cutter to create the front and feet of LiveLights

Back cut

Use 3/16mm material in a laser cutter to create the back of LiveLights

3D Print Custom Bracket Mount for VL503X sensors

3D print number of needed sensor brackets to span the top of LiveLights. Demo uses 3.

Sketchfab still processing.

Schematics

Schematic for LiveLights Capstone

Fritzing Diagram for LiveLights Capstone

Author's Note: Bring-to-Front and Send-to-Back prompts were entirely unresponsive in Fritzing. Thank you for understanding.

Code

Controller Code

C/C++
Take a look at the code that creates a LiveLights panel.
/* 
 * Project Livelights Capstone Project
 * Author: Caroline C. Blaker
 * Date: 2024-12-02 - 2024-12-13
 * For comprehensive documentation and examples, please visit:
 * https://docs.particle.io/firmware/best-practices/firmware-template/
 */

// Include Particle Device OS APIs
#include "Particle.h"
#include "Adafruit_VL53L0X.h"
#include "Adafruit_BME280.h"
#include "neopixel.h"
#include <Adafruit_MQTT.h>
#include "Adafruit_MQTT/Adafruit_MQTT_SPARK.h"
#include "Adafruit_MQTT/Adafruit_MQTT.h"
#include "credentials.h"

bool status;
const byte BPEADDRESS=0x76;
int LOXL_ADDRESS = 0x30;
int LOXC_ADDRESS = 0x31;
int LOXR_ADDRESS = 0x32;
int bLOXL_ADDRESS = 0x33;
int bLOXC_ADDRESS = 0x34;
int bLOXR_ADDRESS = 0x35;
int LOXL_PIN = D5;
int LOXC_PIN = D6;
int LOXR_PIN = D7;
int bLOXL_PIN = D12;
int bLOXC_PIN = D13;
int bLOXR_PIN = D14;

// this naming convention diverges from the styleguide in a way
// that helps me keep track of what the variable means.
const int LOXL_MAX = 430; 
const int bLOXL_MAX= 390;
const int LOXC_MAX = 310; 
const int bLOXC_MAX= 390;
const int LOXR_MAX = 330; 
const int bLOXR_MAX= 390;

int i, j,n;

const int INTERVAL=500;
const int ONCEASOMETHING= 2000;
int currentTime;
int lastTime;

uint8_t wait;

const int red = 0;
const int orange = (30.0/360.0)*255;
const int yellow = (60.0/360.0)*255;
const int green = (120.0/360.0)*255;
const int blue = (240.0/360.0)*255;
const int indigo = (265.0/360.0)*255;
const int violet = (275.0/360.0)*255;
const int TOTAL_NEOPIXELS = 300;
const int rainbow2[] = {red, orange, yellow, green, blue,indigo,violet};

int pointX,colorband, pointY,brightVal,randomColor, start;
float tempF,humidRH;



//                                                             10th                                             20th
int myMatrix[20][15] = {{19, 20,  59,  60,  99,  100,  139,  140,  179,  180,  219,  220,  259,  260, 299},  
                   { 18, 21,  58,  61,  98,  101,  138,  141,  178,  181,  218,  221,  258,  261, 298},  
                   { 17, 22,  57,  62,  97,  102,  137,  142,  177,  182,  217,  222,  257,  262, 297},  
                   { 16, 23,  56,  63,  96,  103,  136,  143,  176,  183,  216,  223,  256,  263, 296},
                   { 15, 24,  55,  64,  95,  104,  135,  144,  175,  184,  215,  224,  255,  264, 295},
                   { 14, 25,  54,  65,  94,  105,  134,  145,  174,  185,  214,  225,  254,  265, 294},
                   { 13, 26,  53,  66,  93,  106,  133,  146,  173,  186,  213,  226,  253,  266, 293},
                   { 12, 27,  52,  67,  92,  107,  132,  147,  172,  187,  212,  227,  252,  267, 292},
                   { 11, 28,  51,  68,  91,  108,  131,  148,  171,  188,  211,  228,  251,  268, 291},
                   { 10, 29,  50,  69,  90,  109,  130,  149,  170,  189,  210,  229,  250,  269, 290},
                   {  9, 30,  49,  70,  89,  110,  129,  150,  169,  190,  209,  230,  249,  270, 289},
                   {  8, 31,  48,  71,  88,  111,  128,  151,  168,  191,  208,  231,  248,  271, 288},
                   {  7, 32,  47,  72,  87,  112,  127,  152,  167,  192,  207,  232,  247,  272, 287},
                   {  6, 33,  46,  73,  86,  113,  126,  153,  166,  193,  206,  233,  246,  273, 286},
                   {  5, 34,  45,  74,  85,  114,  125,  154,  165,  194,  205,  234,  245,  274, 285},
                   {  4, 35,  44,  75,  84,  115,  124,  155,  164,  195,  204,  235,  244,  275, 284},
                   {  3, 36,  43,  76,  83,  116,  123,  156,  163,  196,  203,  236,  243,  276, 283},
                   {  2, 37,  42,  77,  82,  117,  122,  157,  162,  197,  202,  237,  242,  277, 282},
                   {  1, 38,  41,  78,  81,  118,  121,  158,  161,  198,  201,  238,  241,  278, 281},                   
                   {  0, 39,  40,  79,  80,  119,  120,  159,  160,  199,  200,  239,  240,  279, 280}};

int myColors[20][15];

int c_val[]= {4, 5, 6, 7, 8, 9, 10};
int r_val[]= {8, 9, 10, 11, 12, 13, 14};
// Let Device OS manage the connection to the Particle Cloud
SYSTEM_MODE(AUTOMATIC);

// Run the application and system concurrently in separate threads
SYSTEM_THREAD(ENABLED);

Adafruit_NeoPixel panel(TOTAL_NEOPIXELS, SPI1, WS2812B);



Adafruit_VL53L0X loxL = Adafruit_VL53L0X();
Adafruit_VL53L0X loxC = Adafruit_VL53L0X();
Adafruit_VL53L0X loxR = Adafruit_VL53L0X();
Adafruit_VL53L0X bLoxL = Adafruit_VL53L0X();
Adafruit_VL53L0X bLoxC = Adafruit_VL53L0X();
Adafruit_VL53L0X bLoxR = Adafruit_VL53L0X();
//SerialLogHandler logHandler(LOG_LEVEL_INFO);
VL53L0X_RangingMeasurementData_t measureL;
VL53L0X_RangingMeasurementData_t measureC;
VL53L0X_RangingMeasurementData_t measureR;
VL53L0X_RangingMeasurementData_t bMeasureL;
VL53L0X_RangingMeasurementData_t bMeasureC;
VL53L0X_RangingMeasurementData_t bMeasureR;
Adafruit_BME280 bme;

TCPClient TheClient; 

// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details. 
Adafruit_MQTT_SPARK mqtt(&TheClient,AIO_SERVER,AIO_SERVERPORT,AIO_USERNAME,AIO_KEY); 

Adafruit_MQTT_Publish pubFeed = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/starting-pixel-number");

void setID();
int positioner();
void createDots();
void pixelFill(int start, int end, int color);
void matrixFill(int color);
void lineRunner(int posx, int posy,int color);
void rainbow(uint8_t wait);
uint32_t hueWheel(byte WheelPos);
byte reverseWheel(uint32_t color);
void setPixel( uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint16_t brightness);
void MQTT_connect();
bool MQTT_ping();

void setID() {  //sets new address for each TOF sensor
  // all reset
  digitalWrite(LOXL_PIN, LOW);    
  digitalWrite(LOXC_PIN, LOW);
  digitalWrite(LOXR_PIN, LOW);
  delay(10);
  // all unreset
  digitalWrite(LOXL_PIN, HIGH);
  digitalWrite(LOXC_PIN, HIGH);
  digitalWrite(LOXR_PIN, HIGH);
  delay(10);

  // activating LOX1 and reseting LOX2
  digitalWrite(LOXL_PIN, HIGH);
  digitalWrite(LOXC_PIN, LOW);
  digitalWrite(LOXR_PIN, LOW);

  delay(10);
  if (!loxL.begin(LOXL_ADDRESS)) {
    Serial.println(F("Failed to boot left VL53L0X"));
    while(1);
  }

  digitalWrite(LOXC_PIN, HIGH);
  // initing center
  if(!loxC.begin(LOXC_ADDRESS)) {
    Serial.println(F("Failed to boot center VL53L0X"));
    while(1);
  }
  delay(10);

  // activating Right
  digitalWrite(LOXR_PIN, HIGH);
  delay(10);

  //initing Right
  if(!loxR.begin(LOXR_ADDRESS)) {
    Serial.println(F("Failed to boot right VL53L0X"));
    while(1);
  }
  Serial.printf("going LOW\n");
  // all reset
  digitalWrite(bLOXL_PIN, LOW);    
  digitalWrite(bLOXC_PIN, LOW);
  digitalWrite(bLOXR_PIN, LOW);
  delay(10);
  // all unreset
  digitalWrite(bLOXL_PIN, HIGH);
  digitalWrite(bLOXC_PIN, HIGH);
  digitalWrite(bLOXR_PIN, HIGH);
  delay(10);

  // activating LOX1 and reseting LOX2
  digitalWrite(bLOXL_PIN, HIGH);
  digitalWrite(bLOXC_PIN, LOW);
  digitalWrite(bLOXR_PIN, LOW);
    delay(10);
  if (!bLoxL.begin(bLOXL_ADDRESS)) {
    Serial.println(F("Failed to boot bottom left VL53L0X"));
    while(1);
  }
  delay(10);
   digitalWrite(bLOXC_PIN, HIGH);
  // initing center
  if(!bLoxC.begin(bLOXC_ADDRESS)) {
    Serial.println(F("Failed to boot center VL53L0X"));
    while(1);
  }
  delay(10);

  // activating Right
  digitalWrite(bLOXR_PIN, HIGH);
  delay(10);

  //initing Right
  if(!bLoxR.begin(bLOXR_ADDRESS)) {
    Serial.println(F("Failed to boot right VL53L0X"));
    while(1);
  }
  Serial.printf("All Connected\n");
   
}
void setup() {
  Wire.begin();
  Serial.begin(9600);
  panel.begin();
  panel.setBrightness(37);
  panel.show(); //initialize all off
  waitFor(Serial.isConnected,10000);

  pinMode(LOXL_PIN, OUTPUT);
  pinMode(LOXC_PIN, OUTPUT);
  pinMode(LOXR_PIN, OUTPUT);
  pinMode(bLOXL_PIN, OUTPUT);
  pinMode(bLOXC_PIN, OUTPUT);
  pinMode(bLOXR_PIN, OUTPUT);

  Serial.println("Shutdown pins inited...");


  digitalWrite(LOXL_PIN, LOW);
  digitalWrite(LOXC_PIN, LOW);
  digitalWrite(LOXR_PIN, LOW);
  digitalWrite(bLOXL_PIN, LOW);
  digitalWrite(bLOXC_PIN, LOW);
  digitalWrite(bLOXR_PIN, LOW);

  Serial.println("Both in reset mode...(all 3 are low)");
  
  
  Serial.println("Starting BME...");
  status = bme.begin(BPEADDRESS);
  setID();
  lastTime=millis();
   matrixFill(rainbow2[5]);
   n=0;
   
  if (status == FALSE) {
    Serial.printf("Bme280 at address 0x%02X failed to start", BPEADDRESS); 
   } else {
    Serial.printf("Bme280 at address 0x%02X online", BPEADDRESS);
   }
}

// loop() runs over and over again, as quickly as it can execute.
void loop() {
  MQTT_connect();
  MQTT_ping();

  //Looks for visitors
  loxR.rangingTest(&measureR, false);
  bLoxR.rangingTest(&bMeasureR, false);
  loxC.rangingTest(&measureC, false);
  bLoxC.rangingTest(&bMeasureC, false);
  loxL.rangingTest(&measureL, false);
  bLoxL.rangingTest(&bMeasureL, false);

  if (millis()-lastTime > ONCEASOMETHING){
    start = positioner();
    Serial.printf("start: %i\n",start);
    if (start== -1){
    createDots();
  } else {
    // Send our position
    if(mqtt.Update()) {
      pubFeed.publish(start);
    }
    if(bMeasureR.RangeMilliMeter<bLOXR_MAX || measureR.RangeMilliMeter<LOXR_MAX){
      Serial.printf(" Right Top: %i, bottom :%i\n",measureR.RangeMilliMeter,bMeasureR.RangeMilliMeter);
    }
    if(bMeasureC.RangeMilliMeter<bLOXC_MAX || measureC.RangeMilliMeter<LOXC_MAX){
      Serial.printf(" Center Top: %i, bottom :%i\n",measureC.RangeMilliMeter,bMeasureC.RangeMilliMeter);
    }
    if(bMeasureL.RangeMilliMeter<bLOXL_MAX || measureL.RangeMilliMeter<LOXL_MAX){
      Serial.printf(" Left Top: %i, bottom :%i\n",measureL.RangeMilliMeter,bMeasureL.RangeMilliMeter);
    }
    tempF = 9*(bme.readTemperature()/5.0)+32; 
    
    humidRH = bme.readHumidity();
    
    
    lastTime=millis();
    }
  }
}
void createDots(){
  byte r,g,b;
  pointX= random(20);
  pointY= random(15);
  brightVal=random(75,150);
  //randomColor= hueWheel(rainbow2[random(6)]);
    
  // Bitwise op for rgb
  //r = randomColor>>16 & 0xFF;
  //g = randomColor>>8 & 0xFF;
  //b = randomColor & 0xFF;

  r= random(255);
  g= random(255);
  b= random(255);
  //Serial.printf("Color: R: %i, G: %i, B: %i\n",r,g,b);
  setPixel(myMatrix[pointX][pointY],r,g,b,brightVal);
    
  }
void setPixel( uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint16_t brightness) {
	panel.setPixelColor(n, (brightness*r/255) , (brightness*g/255), (brightness*b/255));
  panel.show();
}
  

void lineRunner(int pos_x,int pos_y,int color){

 int* myRow=myMatrix[pos_y];
 int k,r,g,b;
  for (k=0; k<15; k++){

  // Bitwise op for rgb
  r = hueWheel(color)>>16 & 0xFF;
  g = hueWheel(color)>>8 & 0xFF;
  b = hueWheel(color) & 0xFF;

  //Serial.printf("Line Color: R: %i, G: %i, B: %i\n",r,g,b);
  setPixel(myMatrix[pos_x][k],r,g,b,100);
    // k-1
  if (k > 0){
    setPixel(myMatrix[pos_x][k-1],r,g,b,200);
  }
    //k-2
  if (k == pos_x){
    setPixel(myMatrix[pos_x][k-1],200,200,200,200);
    }
  }

}
// Super medium-fidelity person-position tracker
int positioner(){ // uses TOF to see which threshold is active 
  int handPos;
  int colorAngle;
  int x_val; 
  int y_val;
  //If we have any reads within the boundaries of the max
  if ((bMeasureR.RangeMilliMeter<bLOXR_MAX)||bMeasureC.RangeMilliMeter<bLOXC_MAX||bMeasureL.RangeMilliMeter<bLOXL_MAX) {
    // Let's compare left and right
    if(bMeasureL.RangeMilliMeter < bMeasureR.RangeMilliMeter) {
      //to the left
      Serial.printf("left \n");
      // and then center to left
      if(bMeasureL.RangeMilliMeter < bMeasureC.RangeMilliMeter){
        x_val= 0;
        if (bMeasureL.RangeMilliMeter <200){
          y_val =random(0,2);
        } else if (bMeasureL.RangeMilliMeter <300){
         y_val =random(3,5);
        } else if (bMeasureL.RangeMilliMeter <325){
          y_val =random(6,8);
        } else if (bMeasureL.RangeMilliMeter <350){
          y_val =random(9,11);
        } else if (bMeasureL.RangeMilliMeter <375){
          y_val =random(12,14);
        } else if (bMeasureL.RangeMilliMeter <400){
          y_val =random(15,17);
        } else {
          y_val =random(16,19);
        }
      } else {
        x_val= 4;
        if (bMeasureC.RangeMilliMeter <200){
          y_val =random(0,2);
        } else if (bMeasureC.RangeMilliMeter <300){
         y_val =random(3,5);
        } else if (bMeasureC.RangeMilliMeter <325){
          y_val =random(6,8);
        } else if (bMeasureC.RangeMilliMeter <350){
          y_val =random(9,11);
        } else if (bMeasureC.RangeMilliMeter <375){
          y_val =random(12,14);
        } else if (bMeasureC.RangeMilliMeter <400){
          y_val =random(15,17);
        } else {
          y_val =random(16,19);
        }
      }
    } else {
      //to the right
     // Serial.printf("right %i to %i \n",bMeasureL.RangeMilliMeter,bMeasureR.RangeMilliMeter);
      if(bMeasureR.RangeMilliMeter < bMeasureC.RangeMilliMeter){
        x_val= 5;
        if (bMeasureR.RangeMilliMeter <200){
          y_val =random(0,2);
        } else if (bMeasureR.RangeMilliMeter <300){
         y_val =random(3,5);
        } else if (bMeasureR.RangeMilliMeter <325){
          y_val =random(6,8);
        } else if (bMeasureR.RangeMilliMeter <350){
          y_val =random(9,11);
        } else if (bMeasureR.RangeMilliMeter <375){
          y_val =random(12,14);
        } else if (bMeasureR.RangeMilliMeter <400){
          y_val =random(15,17);
        } else {
          y_val =random(16,19);
        }
      } else {
        x_val= 0;
        if (bMeasureC.RangeMilliMeter <200){
          y_val =random(0,2);
        } else if (bMeasureC.RangeMilliMeter <300){
         y_val =random(3,5);
        } else if (bMeasureC.RangeMilliMeter <325){
          y_val =random(6,8);
        } else if (bMeasureC.RangeMilliMeter <350){
          y_val =random(9,11);
        } else if (bMeasureC.RangeMilliMeter <375){
          y_val =random(12,14);
        } else if (bMeasureC.RangeMilliMeter <400){
          y_val =random(15,17);
        } else {
          y_val =random(16,19);
        }
        }
      }
      //slightly randomize base values
      x_val=random(x_val,6);
      y_val=random (y_val,2);
      //Serial.printf("x_val: %i y_val: %i\n",x_val, y_val);
      handPos=  myMatrix[x_val][y_val];
      Serial.printf("x_val: %i y_val: %i handpos: %i\n",x_val, y_val, handPos);
      colorAngle= random(360);
      lineRunner(y_val,x_val, colorAngle);
    }else {
      handPos=-1;
    }
    // the interesting value is on the left, center, or right
    setPixel(handPos,200,0,0,155);
    return handPos;
  }

  
void  pixelFill(int start, int end, int color) {
    int _i;
    for (_i = start; _i <= end; _i++ ){
        panel.setPixelColor(_i, color);
    }
    panel.show();
    
  }
void matrixFill(int color){
  for(i=0;i<20;i++) {
    for(j=0;j<15;j++) {
      panel.setPixelColor(myMatrix[i][j],color);
      panel.show();
      delay(10);
    }
  }
}
void rainbow(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256; j++) {
    for(i=0; i<panel.numPixels(); i++) {
      panel.setPixelColor(i, hueWheel((i+j) & 255));
    }
    panel.show();
    delay(wait);
  }
}
// Convert hue to rgb (hue values 0 - 255 equal 0 to 360 degrees)
uint32_t hueWheel(byte WheelPos) {
  if(WheelPos < 85) {
   return panel.Color(255 - WheelPos * 3, WheelPos * 3, 0);
  } else if(WheelPos < 170) {
   WheelPos -= 85;
   return panel.Color( 0, 255 - WheelPos * 3, WheelPos * 3);
  } else {
   WheelPos -= 170;
   return panel.Color( WheelPos * 3, 0, 255 - WheelPos * 3);
  }
}

// Convert rgb hexcolor to hue
byte reverseWheel(uint32_t color) {
  byte r,g,b;

  r = color>>16 & 0xFF;
  g = color>>8 & 0xFF;
  b = color & 0xFF;

  if(b==0) {
    return (g/3);
  }

  if(g==0) {
    return ((r/3)+170);
  }

  if(r==0) {
    return ((b/3)+85);
  }

  return -1;
}
void MQTT_connect() {
  int8_t ret;
 
  // Return if already connected.
  if (mqtt.connected()) {
    return;
  }
 
  Serial.print("Connecting to MQTT... ");
 
  while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected
       Serial.printf("Error Code %s\n",mqtt.connectErrorString(ret));
       Serial.printf("Retrying MQTT connection in 5 seconds...\n");
       mqtt.disconnect();
       delay(5000);  // wait 5 seconds and try again
  }
  Serial.printf("MQTT Connected!\n");
}

bool MQTT_ping() {
  static unsigned int last;
  bool pingStatus;

  if ((millis()-last)>120000) {
      Serial.printf("Pinging MQTT \n");
      pingStatus = mqtt.ping();
      if(!pingStatus) {
        Serial.printf("Disconnecting \n");
        mqtt.disconnect();
      }
      last = millis();
  }
  return pingStatus;
}

IOT LiveLights Capstone Repository

Github repository for C++ code

Credits

Caroline C. Blaker
3 projects • 6 followers
Web builder becoming things builder. Focused on humans, beauty, and joy.
Contact

Comments

Please log in or sign up to comment.