An urban space that blooms with people.
The Concept

What if urban spaces could come alive with the people in them?

Our concept explores a playful approach to public intervention, where installations respond to the presence, movement and energy of the people around them. Instead of lights staying on all the time, they wake up when people arrive and take a rest when the space is empty. The idea is to turn everyday urban spaces into interactive environments where people don’t just occupy the space but become part of its experience.

Meet an urban installation that doesn’t just glow, it breathes! Designed as a series of vertical columns, the installation comes alive when it senses people nearby. As the surrounding sound levels change, so do its colours, turning the energy of the space into a playful light display. But there’s more! Its origami-inspired skin unfolds and moves with the help of servo motors, creating a breathing effect whenever the lights come on. Part light source, part kinetic sculpture and part interactive experience, it brings a little life and magic into the urban landscape.

The Code :

#include <U8x8lib.h> // OLED library (text only, uses very little memory) //FINAL
#include <Wire.h> // I2C (built in)
#include <Servo.h> // servo library (built in)
#include <Adafruit_NeoPixel.h> // NeoPixel library

Adafruit_NeoPixel pixels(10, 6, NEO_GRB + NEO_KHZ800); // strip: 10 pixels, pin 6
U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE); // the screen
Servo myServo; // the servo

void setup() {
pinMode(2, INPUT); // motion sensor on pin 2
pixels.begin(); // start the strip
pixels.clear(); // all pixels off at the start
pixels.show(); // send it to the strip
u8x8.begin(); // start the screen
u8x8.setFlipMode(1); // turn the screen 180 degrees
u8x8.setFont(u8x8_font_chroma48medium8_r); // basic font
myServo.attach(7); // servo on pin 7 (Grove port D7)
myServo.write(0); // start at 0 degrees
}

void loop() {

// these remember their value from one loop to the next
static unsigned long motionTime = 0; // time of the last motion
static int lastMotion = 0; // was there motion last loop? (1 = yes)
static int lightsOn = 0; // 1 = lights on, 0 = lights off
static int stripWasOn = 0; // was the strip on last loop? (1 = yes)
static int servoAngle = 0; // where the servo is right now
static int servoTarget = 0; // where the servo should go (0 or 90)


int motion = digitalRead(2); // 1 if someone moved, 0 if not

if (motion == 1) { // motion right now
motionTime = millis(); // restart the 3 second timer
lightsOn = 1; // lights stay on while there is movement
if (lastMotion == 0) { // motion just started (not there last loop)
servoTarget = 90; // every time the lights come on, the servo goes to 90
}
}
lastMotion = motion; // remember for the next loop

if (millis() – motionTime > 3000) { // 3 seconds with no movement?
lightsOn = 0; // turn lights fully off
servoTarget = 0; // lights off, so the servo stops repeating
}


int low = 1023; // smallest sound reading
int high = 0; // biggest sound reading

for (int i = 0; i < 100; i++) { // take 100 readings
int v = analogRead(A2); // read the sound sensor
if (v < low) {
low = v;
}
if (v > high) {
high = v;
}
}

int sound = high – low; // loudness


int brightness = map(sound, 0, 300, 40, 255); // louder = brighter (40 = dimmest)
if (brightness > 255) {
brightness = 255;
}
if (brightness < 40) { // never below 40, so lights stay on during movement
brightness = 40;
}


int howMany = 0;
if (sound < 40) {
howMany = 3; // quiet = 3 pixels
} else if (sound < 120) {
howMany = 6; // medium = 6 pixels
} else {
howMany = 10; // loud = all 10 pixels
}


int maxSound = 1023; // highest sound value (change this after measuring
int band = maxSound / 5; // the sound range divided into 5 equal bands

int red = 0; // main colour: amount of red (0 to 255)
int green = 0; // main colour: amount of green (0 to 255)
int blue = 0; // main colour: amount of blue (0 to 255)
int red2 = 0; // second colour: amount of red
int green2 = 0; // second colour: amount of green
int blue2 = 0; // second colour: amount of blue
int maxMix = 50; // how far the gradient goes toward the second colour (percent)

if (sound < band) { // band 1 (quietest) = purple and blue, about equal
red = 255;
green = 0;
blue = 255;
red2 = 0;
green2 = 0;
blue2 = 255;
maxMix = 100; // goes all the way from purple to blue
} else if (sound < band * 2) { // band 2 = green + some blue
red = 0;
green = 255;
blue = 0;
red2 = 0;
green2 = 0;
blue2 = 255;
} else if (sound < band * 3) { // band 3 = purple and blue, about equal
red = 255;
green = 0;
blue = 255;
red2 = 0;
green2 = 0;
blue2 = 255;
maxMix = 100; // goes all the way from purple to blue
} else if (sound < band * 4) { // band 4 = red + some blue
red = 255;
green = 0;
blue = 0;
red2 = 0;
green2 = 0;
blue2 = 255;
} else { // band 5 (loudest) = blue + some green
red = 0;
green = 0;
blue = 255;
red2 = 0;
green2 = 255;
blue2 = 0;
}


if (lightsOn == 1) { // lights on: update the strip every loop
pixels.setBrightness(brightness); // set brightness
pixels.clear(); // turn all pixels off
for (int i = 0; i < howMany; i++) {
int mix = i * maxMix / howMany; // how much of the second colour for this pixel
int r = (red * (100 – mix) + red2 * mix) / 100; // blend red
int g = (green * (100 – mix) + green2 * mix) / 100; // blend green
int b = (blue * (100 – mix) + blue2 * mix) / 100; // blend blue
pixels.setPixelColor(i, pixels.Color(r, g, b)); // this pixel’s colour
}
pixels.show(); // send to the strip
} else if (stripWasOn == 1) { // lights just went off: send “all off” once
pixels.clear();
pixels.show();
}
stripWasOn = lightsOn; // remember for the next loop


while (servoAngle < servoTarget) { // target is higher: move up
servoAngle = servoAngle + 1; // move 1 degree
myServo.write(servoAngle); // send it to the servo
delay(10); // short wait so the servo can follow
}
if (servoAngle == 90) { // reached 90, so now go back to 0
servoTarget = 0;
}
while (servoAngle > servoTarget) { // target is lower: move down
servoAngle = servoAngle – 1; // move 1 degree back
myServo.write(servoAngle);
delay(10);
}
if (servoAngle == 0 && lightsOn == 1) { // back at 0 and lights still on, so go again
servoTarget = 90;
}
u8x8.setCursor(0, 1); // column 0, row 1
if (lightsOn == 1) {
u8x8.print(F(“Light: ON “)); // extra space wipes old letters
} else {
u8x8.print(F(“Light: OFF”));
}
u8x8.setCursor(0, 3); // column 0, row 3
u8x8.print(F(“Sound: “));
u8x8.print(sound); // the sound level
u8x8.print(F(” “)); // extra spaces wipe old digits
}

The Process

The Prototype: