Based on the concept Jasper and I developed in week 12, we made a ring structure that could house an LED strip from the inside, and attached it to a circular board. The board served as a reflective surface for the lights and to connect the wires to the supporting base.
We tested with several diffusion materials, and found that matted paper yielded the best results in terms of our desired light quality and lux level.
A second smaller ring was then made and aligned in the middle, concentrically to the larger ring on the board.
To control the LED lights for this fixture, four major libraries are used:
The WiFiNINA library to get internet connection and query the live weather forecast data from OpenWeatherMap;
The RTC zero library that pulls the time epochs from the internet;
The ArduinoJSON library to parse the JSON weather forecast retrieved from the OpenWeatherMap;
The ColorConverter library to adjust the color of the two rings based on the weather forecast.
This week, we continued on our prototype for the tiles fixture.
LED Base Making
Based on the feedback we got from last week’s class, we redesigned the base structure by enlarging the size of the tile while decreasing the perimeter of the LED square, so that the light source is less exposed.
Pushbutton Holdings on Base
We re-calibrated the size & depth of the button holdings to support the expected weight of the concrete tile.
Concrete Tile Making
Wooden molds were made to hold and shape the concrete tile.
Wood Anchors and Safety Thread of Concrete Tiles
We put wood anchors in the corners of the tile, so that the tile can be fixed onto the base with these anchors going into the holdings. A safety thread was also embedded into the concrete tile to prevent it from falling off.
Base Painting
The holding base (or the “wall”) was painted to provide stronger and better diffusion of the LED lights.
Dried Tile with Safety Thread Installed on the Base
Once the tile was fully tried, we took it out and installed it onto the base with its safety thread hooked up to a fixed screw at the back of the base.
Testing of the First Tile
Illumination test were run for the tile to verify the intensity and color of the LED light source.
Multiple Tiles (Failed)
We tried to make more tiles using the same procedure. However, all of the four tiles we made after the first tile broke apart when we attempted to take them out of the mold.
Our guess was that since the wooden molds had been used for at least once, the mold had soaked plenty of water during the curing of multiple tiles, and became somewhat mixed with the concrete. This had created unevenly distributed pressure when we were taking the tiles off, and it eventually broken them, no matter how light and careful we tried.
At this point, making 9 tiles for the following week seemed impossible.
Remake of Broken Tile
While there was an option to go with just one tile, we discussed if there was possibility to do something with the broken ones. Thanks to Adrian, we realized that we could recreate a different style of concrete tiles by putting their broken pieces together, and let light shine through it.
We picked one tile that seemed repairable and glued it together. We then used the flashlight on our phones to see if light could go through the cracks. And it did!
So, we further sanded the internal edges of the pieces (to create more room for the light), and Arnab helped us by using epoxy to fill all the cracks and put the pieces back together.
Reborn of Tiles & Final Work
The epoxy worked really well, and we managed to control the lighting of both the normal tile and the cracked tile in a uniform manner.
For Week 7’s Light Fixture, Rui and I planned to work together again and make a wall sconce light fixture.
Concept
Our idea was inspired from the game Tic-tac-toe, and we wanted to transform it into a wall sconce that comprised of nine glowing tiles.
The light will come from the back of each tile, projected onto the background fixture that hosts all the tiles, and mediated out to the environment.
Interaction for Brightness Control
There will be three levels of brightness for each tile: None, Weak, and Strong. There will be two ways to control the brightness: Individual Mode and Centralized Mode.
Under the Individual Mode, which is also the default mode, each tile acts on its own. Users can adjust its brightness though consecutive tapping, and the brightness will loop between None -> Weak -> Strong -> None.
Under the Centralized Mode, users will press & hold on the center tile for a while to activate it, and then all the tiles will be turned into Brightness Selection phase. The top row will represent Strong, the middle Weak, and the Bottom None. Then, the user can tap on one of the tiles to apply its brightness to all other tiles.
Material Plan
Rui mentioned that he is interesting concrete texture, so we are planning to experiment with making concrete tiles using modes. For the background, we’re thinking of using wood as the host.
For the lighting source of each tile, we plan to use LED strips, and fix it onto the back of each tile, and power them using 12V DC converted from AC. Peizo sensors might be used to identify user behaviors.
A current estimation of total power required is:
80mA * 20 * 9 * 12V = 172.8W
Diffuser materials are yet to be determined. The challenging part might be making the light that coming out of the strips appear as homogenized as possible, as well as the light between multiple tiles as homogenized as possible.
This week’s trip to the lighting shops at Bowery street was fun – people have managed to do all sorts of tricks on tweaking the lights to produce hundreds of effects!
Below are some interesting ones I found.
— Diffusers —
Crystal – a dazzling effect created by tons of crystals surrounded LED strips in the middle.
Stained Glass? .. their yellow & red-ish color adds to the reality effect of the light source.
Bubbles – just adding some air bubbles into the glass diffuser gives it a totally different flavor.
Rasin/Glass..? Irregular diffuser carves light at the curvy edges of different shapes.
I have no clue how this circular rainbow is created.
Triangle – a light beam is bent into 60 degrees in the 3D space by this triangular disffuser.
Double mirror trick – Light is trapped in this tiny, thin space created by two reflective layers, resulting in an seemingly deep pathways. The curvature of the diffuser helps bending the pathways toward the center.
—Structures —
Some simple but effective structures that create a stage for the light sources.
—-Other—-
Ghost light in a normal shell..?
The light source is actually made of tiny glowing balls lining up in spirals. Also, the reflected world captured in the outer layer of glass is magnificent.
This week’s task is to create the “effect” of a candle. I worked with Rui Wang at ITP and we created a roly-poly candle that can simulate how the candle light would react to its shifting center of gravity when you give it a gentle push.
Ideation
Considering what type of candle to build, we checked several examples online, and found Hayeon Hwang’s brush light. She used a neopixel jewel vertically to create the light of a candle, and used brush hairs to diffuse – this had led to an amazing simulation in terms of the color, texture and shape of the candle light. But since it can be viewed from only one angle, Rui and I, after discussion, agreed that we still want to make a candle that can be seen 360. Eventually, we landed on the idea to make one that no longer stands still on a table — it can wobble, and its light will change with respect to how light moves under a wobbling environment.
Prototype
In considering how to create the effect of a candle, rather than just the candle itself, we broke down the characteristics of the candle light into the following aspects:
a constant, steady light base comprised of three colors: a little bit of blue in the bottom core, a red ring surrounding the blue at the bottom, and a gradual transition to bright yellow at the top-outer half
occasional flickering
occasional hue shifting between the red part and the yellow part
occasional deforming of the light shape due to small changes in air turbulence
So we decided to:
combine a neopixel ring and a jewel to create this varying color base
use timed and random functions to recreate different layers of flickering, hue shifting and deformation
And tested with diffuser:
We also used an accerlerometer to tune the intensity of different pixels so that the overall candle light can respond to the tilting of the candle.
We made a base similar to a rolyl-poly toy as the fixture of the candle:
Final Work
Code
#include <ColorConverter.h>
#include <Adafruit_Circuit_Playground.h>
#include <Interval.h>
#include <Adafruit_NeoPixel.h>
Interval flickerInterval1;
Interval flickerInterval2;
Interval flickerRestoreInterval;
Interval randomDimInterval;
ColorConverter converter;
//switch
float overallIntensityScale = 0;
int ignitionCount = 0;
int dir = 0; //0 = netrual, 1 = tilted north, 5 = tilted south, 9 directions in total, clock-wise
int strongBaseLED = 5;
int weakBaseLED = 0;
int strongUpperLED = 3;
int weakUpperLED = 6;
//base ring
const int neoPixelPin1 = 9;
const int pixelCount1 = 10;
float baseH = 18; // from RGB 255, 83, 12
float baseS = 100;
float baseI = 53;
float baseChange = 0.1;
float upperChange = 0.1;
//upper ring
const int neoPixelPin2 = 10;
const int pixelCount2 = 7;
float upperH = 18;
float upperS = 100;
float upperI = 53;
float coreH = 240;
float coreS = 100;
float coreI = 50;
float baseIntensityScale = 0.7;
float upperIntensityScale = 1;
float coreIntensityScale = 0.3;
float defaultDirIntensity = 10;
float dirIntensity = 100;
float dirChange = 2;
boolean isRandomDim = false;
float randomIntensityOffset = 0;
long randomDimTime;
//float flickerIntensity = 255;
int flickerDim = 0;
boolean flickerRestored = true;
long flickerTime;
// set up upper ring:
Adafruit_CPlay_NeoPixel strip2 = Adafruit_CPlay_NeoPixel(pixelCount2, neoPixelPin2, NEO_GRBW + NEO_KHZ800);
int prevX, prevY, prevZ;
void setup() {
CircuitPlayground.begin();
strip2.begin(); // initialize pixel strip
strip2.clear(); // turn all LEDs off
Serial.begin(9600);
//flicer randomly
flickerInterval1.setInterval(flickerDim1, 7000);
flickerInterval2.setInterval(flickerDim2, 9890);
randomDimInterval.setInterval(randomDim, 6000);
}
void loop() {
//accel--------------------------------------------
int x = CircuitPlayground.motionX();
int y = CircuitPlayground.motionY();
int z = CircuitPlayground.motionZ();
Serial.print("x: ");
Serial.print(x);
Serial.print(" y: ");
Serial.print(y);
Serial.print(" z: ");
Serial.println(z);
int accel = abs(x - prevX) + abs(y - prevY) + abs(z - prevZ);
Serial.print("total: ");
Serial.println(accel);
prevX = x;
prevY = y;
prevZ = z;
//ignition--------------------------
if (accel > 0) {
ignitionCount++;
Serial.print("ignition count: ");
Serial.println(ignitionCount);
}
if (ignitionCount == 10) {
overallIntensityScale = 0.5;
} else if (ignitionCount == 12) {
overallIntensityScale = 0;
} else if (ignitionCount == 20) {
overallIntensityScale = 0.8;
} else if (ignitionCount == 22) {
overallIntensityScale = 0;
} else if (ignitionCount >= 30) {
overallIntensityScale = 1;
}
//direction control--------------------
if (x <= -2 && y == 0) {
dir = 1;
} else if (x <= -2 && y >= 2) {
dir = 2;
} else if (x == 2 && y >= 2) {
dir = 3;
} else if (x >= 2 && y >= 2) {
dir = 4;
} else if (x >= 2 && y == 0) {
dir = 5;
} else if (x >= 2 && y <= -2) {
dir = 6;
} else if (x == 0 && y <= -2) {
dir = 7;
} else if (x <= -2 && y <= -2) {
dir = 8;
} else {
dir = 0;
}
//--------------------------------------------
//flickerDim
if (!flickerRestored) {
Serial.println("needs restore!");
// flickerRestoreInterval.setTimeout(flickerRestore, 49);
if (millis() > flickerTime + 49) {
flickerRestore();
}
}
//randomDim
if (isRandomDim) {
Serial.println("needs restore random!");
randomIntensityOffset = random(0, 3);
if (millis() > randomDimTime + 2500) {
randomDimRestore();
}
}
switch (dir) {
case 0:
//do noting to change the strong, weak LED
break;
case 1:
strongBaseLED = 5;
weakBaseLED = 0;
strongUpperLED = 3;
weakUpperLED = 6;
break;
case 2:
strongBaseLED = 3;
weakBaseLED = 8;
strongUpperLED = 4;
weakUpperLED = 1;
break;
case 3:
strongBaseLED = 2;
weakBaseLED = 7;
strongUpperLED = 4;
weakUpperLED = 1;
break;
case 4:
strongBaseLED = 0;
weakBaseLED = 5;
strongUpperLED = 5;
weakUpperLED = 2;
break;
case 5:
strongBaseLED = 0;
weakBaseLED = 5;
strongUpperLED = 6;
weakUpperLED = 3;
break;
case 6:
strongBaseLED = 9;
weakBaseLED = 4;
strongUpperLED = 6;
weakUpperLED = 3;
break;
case 7:
strongBaseLED = 7;
weakBaseLED = 2;
strongUpperLED = 1;
weakUpperLED = 4;
break;
case 8:
strongBaseLED = 6;
weakBaseLED = 1;
strongUpperLED = 2;
weakUpperLED = 5;
break;
}
//change intensity based on direction
if (dir == 0) {
if (dirIntensity >= 25) {
dirIntensity = dirIntensity - dirChange;
}
} else {
if (dirIntensity < 50) {
dirIntensity = dirIntensity + dirChange;
}
}
delay(2);
//determine the final RGB value
RGBColor baseColorStrong = converter.HSItoRGB(baseH, baseS, (dirIntensity - flickerDim - randomIntensityOffset) * baseIntensityScale * overallIntensityScale);
RGBColor baseColorWeak = converter.HSItoRGB(baseH, baseS, (50 - dirIntensity - flickerDim - randomIntensityOffset) * baseIntensityScale * overallIntensityScale);
RGBColor baseColorNeutral = converter.HSItoRGB(baseH, baseS, (defaultDirIntensity - flickerDim - randomIntensityOffset) * baseIntensityScale * overallIntensityScale);
RGBColor upperColorStrong = converter.HSItoRGB(upperH, upperS, (dirIntensity - flickerDim - randomIntensityOffset) * upperIntensityScale * overallIntensityScale);
RGBColor upperColorWeak = converter.HSItoRGB(upperH, upperS, 50 - (dirIntensity - flickerDim - randomIntensityOffset) * upperIntensityScale * overallIntensityScale);
RGBColor upperColorNeutral = converter.HSItoRGB(upperH, upperS, (defaultDirIntensity - flickerDim - randomIntensityOffset) * upperIntensityScale * overallIntensityScale);
RGBColor coreColor = converter.HSItoRGB(coreH, coreS, (defaultDirIntensity - flickerDim - randomIntensityOffset) * coreIntensityScale * overallIntensityScale);
//add a constant shifting of HUE for two rings
baseH = baseH + baseChange;
if (baseH < 14 || baseH > 22) {
baseChange = -baseChange;
}
upperH = upperH + upperChange;
if (upperH < 10 || upperH > 22) {
upperChange = -upperChange;
}
for (int pixel = 0; pixel < pixelCount1; pixel++) {
CircuitPlayground.setBrightness(255);
//base ring
if (pixel == strongBaseLED) {
// CircuitPlayground.strip.setPixelColor(pixel, baseColor.red * orangeScale * flickerIntensity / 255 * dirIntensity / 255, baseColor.green * orangeScale * flickerIntensity / 255 * dirIntensity / 255, baseColor.blue * orangeScale * flickerIntensity / 255 * dirIntensity / 255 ); // set the color for this pixel
CircuitPlayground.strip.setPixelColor(pixel, baseColorStrong.red, baseColorStrong.green, baseColorStrong.blue); // set the color for this pixel
} else if (pixel == weakBaseLED) {
// CircuitPlayground.strip.setPixelColor(pixel, baseColor.red * orangeScale * flickerIntensity / 255 * (1 - dirIntensity / 255), baseColor.green * flickerIntensity / 255 * orangeScale * (1 - dirIntensity / 255), baseColor.blue * flickerIntensity / 255 * orangeScale * (1 - dirIntensity / 255)); // set the color for this pixel
CircuitPlayground.strip.setPixelColor(pixel, baseColorWeak.red, baseColorWeak.green, baseColorWeak.blue); // set the color for this pixel
} else {
// CircuitPlayground.strip.setPixelColor(pixel, baseColor.red * orangeScale * flickerIntensity / 255 * defaultDirIntensity / 255, baseColor.green * orangeScale * flickerIntensity / 255 * defaultDirIntensity / 255, baseColor.blue * orangeScale * flickerIntensity / 255 * defaultDirIntensity / 255); // set the color for this pixel
CircuitPlayground.strip.setPixelColor(pixel, baseColorNeutral.red, baseColorNeutral.green, baseColorNeutral.blue); // set the color for this pixel
}
//upper ring
if (pixel == 0) {
// strip2.setPixelColor(pixel, 0 * blueScale * flickerIntensity / 255, 0 * blueScale * flickerIntensity / 255, 255 * blueScale * flickerIntensity / 255); // set inner most blue
strip2.setPixelColor(pixel, coreColor.red, coreColor.green, coreColor.blue); // set the color for this pixel
} else if (pixel == strongUpperLED) {
// strip2.setPixelColor(pixel, red * redScale * flickerIntensity / 255 * dirIntensity / 255, green * redScale * flickerIntensity / 255 * dirIntensity / 255, blue * redScale * flickerIntensity / 255 * dirIntensity / 255 ); // set the color for this pixel
strip2.setPixelColor(pixel, upperColorStrong.red, upperColorStrong.green, upperColorStrong.blue); // set the color for this pixel
} else if (pixel == weakUpperLED) {
// strip2.setPixelColor(pixel, red * redScale * flickerIntensity / 255 * (1 - dirIntensity / 255), green * redScale * flickerIntensity / 255 * (1 - dirIntensity / 255), blue * redScale * flickerIntensity / 255 * (1 - dirIntensity / 255)); // set the color for this pixel
strip2.setPixelColor(pixel, upperColorWeak.red, upperColorWeak.green, upperColorWeak.blue); // set the color for this pixel
}
else {
// strip2.setPixelColor(pixel, red * redScale * flickerIntensity / 255 * defaultDirIntensity / 255, green * redScale * flickerIntensity / 255 * defaultDirIntensity / 255, blue * redScale * flickerIntensity / 255 * defaultDirIntensity / 255 ); // set the color for this pixel
strip2.setPixelColor(pixel, upperColorNeutral.red, upperColorNeutral.green, upperColorNeutral.blue); // set the color for this pixel
}
//delay(500);
}
CircuitPlayground.strip.show(); // refresh the strip
strip2.show(); // refresh the strip
flickerInterval1.check();
flickerInterval2.check();
flickerRestoreInterval.check();
randomDimInterval.check();
}
void flickerDim1() {
flickerDim = 50;
flickerRestored = false;
flickerTime = millis();
}
void flickerDim2() {
flickerDim = 50;
flickerRestored = false;
flickerTime = millis();
}
void flickerRestore() {
Serial.print("restored!");
flickerDim = 0;
flickerRestored = true;
}
void randomDim() {
isRandomDim = true;
randomDimTime = millis();
}
void randomDimRestore() {
randomIntensityOffset = 0;
isRandomDim = false;
}