Komorebi, final project

For my final I have tried to create Komorebi, that reacts to the ITP weather sensor.

I have been fascinated with Komorebi for a long time, specifically the way the leaves part in the wind and come back together. I know that ITP has this weather sensor, currently being managed by Yeseul Song, who helped me with the code for the sensor.

The code repo for the weather station is here on GitHub. This code makes requests to the server to get the data specified in a date range (only get 10 minutes at a time, because Arduinos have a small memory!). Brent Bailey is currently working on making it possible to make a call to just get the latest entry, but this is not available yet. This is what I really need for my code, and my work around has been to just manually update the dates for now, and experimented with automatically updating the date range depending in millis(), but it is a bad solution.

https://github.com/ITPNYU/ITPower/tree/master/WeatherStation/GetData/GetWeatherStationData

DESIGN

Komorebi is a simple and delicate light phenomenon, so I want my design to reflect this. I think that this sort of silhouette is interesting, and I imagined a reveal of light and shadows akin to these works, however where the light is also a functional light, giving a sense or a presence of the outdoors, but illuminating the wall with light that doesn’t resemble leaves clearly (no cutouts).

I initially devised a sconce design that would consist of a single brass plate, hiding leafs made from brass mesh behind it. I have had good results with getting a nice impression of a leaf from this translucent material.

I have brass sheet metal, but I would want the final to be a beaten brass plate, slightly bowl shaped like the one in the image below. Hiding the side view and the leaves. Because of this method of bouncing the light into the back of the brass I experimented with standoffs, to keep the design airy. And with the least possible shadows coming from things other than leaves. This proved quite difficult!

Gervasoni brass lamp (https://www.masonionline.com/brass-95-96). This is the look I want the sconce to have.

This is the current iteration of the sconce. The light shows up very red in the video, but in real life it is much more white/green. I have adjusted the RGB values in the code a lot (R and B down, G up) in order to counterbalance the warm pink hue which is a result of the brass sheet reflection.

CODE

I ended up spending most of my time on the code for this one, and I think the design suffered for it a bit, but I wanted to make sure that I can move the light between each LED and that it would indeed read like Komorebi.

The code is here: https://github.com/louiselessel/Lightandinteractivity/tree/master/Komorebi

The WindLight.ino contains the code without any calls for the data, the GetWeatherSensorData.ino contains the same code, but getting the right data. I’ve taken out my failed attempt at updating the variable for getting data from the sensor, since I was made aware of a better way (see changes).

These are the parameters I am receiving from the sensor.

  {
    "id": 3329,
    "recorded_at": "2020-05-06T19:44:14.000Z",
    "wind_dir": 158,
    "winddir_avg2m": 165,
    "windspeedmph": 0.07,
    "windgustdir_10m": 113,
    "windgustmph_10m": 33.16,
    "rainin": 0,
    "dailyrainin": 0.01,
    "rain_avg1m": null,
    "rain_avg10m": null,
    "temperature": 26.26,
    "humidity": 39.85,
    "pressure": 100.97,
    "illuminance": 1151.61,
    "uva": 57.99,
    "uvb": 27.31,
    "uvindex": 0.08
  },

The code works by creating wind gusts, every X seconds interval (here 0.5 for testing), these are based on the windgustmph_10m parameter. I think I’m going to experiment further with getting these dependent on the windspeedmph, so they reflect how overall windy it is outside.

    if (now > last_time + 500) {
      // control only 6 pixels is on at a time
      startP = random(0, 22);
      endP = startP + random(4, 6);

      // wind gusts
      windMovementRight(200, 255, 200, windgust);
      windMovementLeft(150, 255, 150, windgust);
      //delay(random(50, 1000));

      startP = startP + 2;
      endP = startP + random(4, 8);

      windMovementRight(200, 255, 200, windgust);
      windMovementLeft(75, 125, 75, windgust);
      //delay(random(50, 100));
      now = millis();
      last_time = now;
    }

I attempted to use both the neopixels RGB and the HSI https://github.com/tigoe/ColorConverter from Tom Igoe, and found that I lost a lot of light in using the color converter – though it was much more convenient for fading on the intensity parameter.

void windMovementRight(uint8_t wait, RGBColor color) {
  for (uint16_t i = startP; i < endP; i++) {
    strip.setPixelColor(i, color.red, color.green, color.blue);
    if (i > startP) {
      strip.setPixelColor(i - 1, color.red / 2, color.green / 2, color.blue / 2);
    }
    if (i > startP + 1) {
      strip.setPixelColor(i - 2, 0, 0, 0);
    }
    strip.show();
    delay(wait);
  }
}

I think the key to making it look more correct right now, is getting a much better fade curve, I don’t know if it is COVID brain or thesis brain, but I was having trouble getting the fade curve right with the void loop and the timing of the calls to the sensor as well. I have implemented a very rudimentary dimming, because in real life it is not like the movements of the leaves are following any fade curve, they may as well move in an instance. So I decided I would improve on these later as needed. And I think it is needed.


CHANGES

First of all, programmatically re-creating this phenomenon turned out to be much more difficult then I could imagine! So this is going to be a longer project for me, because I’ve gotten very obsessed with the idea now.

There are a number of future investigations I want to make:

  • I actually like the brass sheet metal, and the design I ended with, with the leaves sticking out, but I need to rethink the attachment of the LED to the brass plate as the material is so thin, cutting the holes in it, causes it to bend, despite my best efforts. I don’t want anything to break the surface of the brass. But if I could get my hands on some more rough brass, that is still what I want for the bowl-like shape.
  • I was trying to figure out a way to get the data calls to the sensor to run automatically after startup, by using millis(), until I was made aware of the RTC zero library, so I will change my code to use this, in this way I can also make gradients in the light over the course of the light, I think this would elevate the design – https://github.com/arduino-libraries/RTCZero
  • Finally, I think changing to an online API of wind data, so that a user could get the weather data near them no matter where they were in the world, could make this into an interesting sellable design.

I will return to this design in the future, after thesis craze is over!


As a final note on this class, I found this great list of Light Design books for inspiration going forward (I’m putting these on my wish list!):

https://www.dexigner.com/directory/cat/Lighting-Design/Books


FULL CODE (WindLight.ino)

#include <Adafruit_NeoPixel.h> //including the Adafruit library$12
#include <ColorConverter.h>
#define PIN A7 //defining the PWM pin
#define N_LEDS 24      //number of LED units on the strip
#define N_LEDS_USE 7

Adafruit_NeoPixel strip = Adafruit_NeoPixel(N_LEDS, PIN, NEO_RGB + NEO_KHZ800);; //declared neopixel object
ColorConverter converter;

int startP = 9;
int endP = startP + 6;

int h = 120;         // hue
int sat = 15;        // saturation
int i = 90;        // intensity
int change = 1;              // increment to change hue by
RGBColor color;


uint32_t last_time;
uint32_t sway_time;

void setup() {
  strip.begin(); // This initializes the NeoPixel library.
  strip.clear();
  strip.show();
}

void loop() {

  uint32_t now = millis();

  int windspeedmph =  2.77; // mph
  int windgust =  33.16;

  windgust = constrain(int(map (windgust, 0, 50, 200, 100)), 100, 200); // mph
  //Serial.println(windgust);

  // wind avg
  int wind_dir = 260; //285;
  // approximate direction, set start point pixels, so we can see wind direction
  if (wind_dir < 90) {
    startP = 0;
  } else if (wind_dir < 180) {
    startP = 6 ;
  } else if (wind_dir < 270) {
    startP = 12 ;
  } else if (wind_dir < 360) {
    startP = 18 ;
  }
  //startP = constrain(int(map (wind_dir, 0, 360, 0, 24)), 0, 24);
  endP = startP + 1;
  //Serial.println(startP);

  // increment
  if (now > sway_time + 500) {
    i = i + change;
    if (i < 50 || i > 99) {
      change = -change;
    }
    sway_time = now;
    color = converter.HSItoRGB(h, sat, i);
  }
  Serial.println(i);

// make light swaying with intensity changes at start point pixels.
  for (uint16_t i = startP; i < endP; i++) {
    strip.setPixelColor(i, color.red, color.green, color.blue);
    strip.show();
  }

  boolean test = true;

  // make a wind gust
  if (test) {
    if (now > last_time + 500) {
      // control only 6 pixels is on at a time
      startP = random(0, 22);
      endP = startP + random(4, 6);

      // wind gusts
      windMovementRight(200, 255, 200, windgust);
      windMovementLeft(150, 255, 150, windgust);
      //delay(random(50, 1000));

      startP = startP + 2;
      endP = startP + random(4, 8);

      windMovementRight(200, 255, 200, windgust);
      windMovementLeft(75, 125, 75, windgust);
      //delay(random(50, 100));
      now = millis();
      last_time = now;
    }
  } else {
    // make a wind gust
    if (now > last_time + 500) {
      // control only 6 pixels is on at a time
      startP = random(0, 22);
      endP = startP + random(4, 6);

      // wind gusts
      color = converter.HSItoRGB(120, 10, 255);
      windMovementRight(windgust, color);
      windMovementLeft(windgust, color);
      //delay(random(50, 1000));

      startP = startP + 2;
      endP = startP + random(4, 8);

      windMovementRight(windgust, color);
      windMovementLeft(windgust, color);
      //delay(random(50, 100));
      now = millis();
      last_time = now;
    }
  }
}


void windMovementRight(int r, int g, int b, uint8_t wait) {
  for (uint16_t i = startP; i < endP; i++) {
    // on
    strip.setPixelColor(i, r, g, b);
    // off
    if (i > startP) {
      strip.setPixelColor(i - 1, r / 2, g / 2, b / 2);
    }
    if (i > startP + 1) {
      strip.setPixelColor(i - 2, 0, 0, 0);
    }
    //strip.setPixelColor(random(startP, endP), r, g, b);
    //strip.setPixelColor(startP, r, g, b);
    strip.show();
    delay(wait);
  }
  strip.setPixelColor(startP, 0, 0, 0);
  strip.show();
}

void windMovementLeft(int r, int g, int b, uint8_t wait) {
  for (uint16_t i = endP; i > startP; i--) {
    // on
    strip.setPixelColor(i, r, g, b);
    // off
    if (i > startP) {
      strip.setPixelColor(i + 1, r / 2, g / 2, b / 2);
    }
    if (i > startP + 1) {
      strip.setPixelColor(i + 2, 0, 0, 0);
    }
    //strip.setPixelColor(random(startP, endP), r, g, b);
    //strip.setPixelColor(endP, r, g, b);
    strip.show();
    delay(wait);
  }
  strip.setPixelColor(endP, 0, 0, 0);
  strip.show();
}


void windMovementRight(uint8_t wait, RGBColor color) {
  for (uint16_t i = startP; i < endP; i++) {
    strip.setPixelColor(i, color.red, color.green, color.blue);
    if (i > startP) {
      strip.setPixelColor(i - 1, color.red / 2, color.green / 2, color.blue / 2);
    }
    if (i > startP + 1) {
      strip.setPixelColor(i - 2, 0, 0, 0);
    }
    strip.show();
    delay(wait);
  }
}

void windMovementLeft(uint8_t wait, RGBColor color) {
  for (uint16_t i = endP; i > startP; i--) {
    strip.setPixelColor(i, color.red, color.green, color.blue);
    if (i > startP) {
      strip.setPixelColor(i + 1, color.red / 2, color.green / 2, color.blue / 2);
    }
    if (i > startP + 1) {
      strip.setPixelColor(i + 2, 0, 0, 0);
    }
    strip.show();
    delay(wait);
  }
}