The enchanted forest is a IoT ecosystem with interactive LED lightnings designed to activate the forest area in the Tunxis golf club as a new event venue.
Wedding and events venues are a key source of revenue for a variety of destinations-from hotels to golf courses. As a result, a significant emphasis is put on both positioning these destinations as potential wedding/event location and driving repeat business through the delivery of memorable experiences. Due to fickle and ever changing consumer preference, a continuous, customer-centric approach is required to deliver the best experience. Technology, and more specifically, the IoT presents unique opportunities to deliver this experience when implemented in a tasteful and integrated manner.
The objective of this project is to enhance the memorable wedding and other event experience offered by Tunixs Country Club (Connecticut) via introducing the IoT connectivity. There is a forest area locates right beside the wedding pavilion which is currently under utilized. Hence, we hope to activate the forest space, especially at night, as the extension of the current event venue and ultimately as the alternative event center.
We started with secondary research on the trend of wedding, especially with the fusion of IoT.
After that, possible technical precedents were studied in terms of technology and implementation.
We then conducted the stake holder interview including Loredana M. Soucy, Director of Sales & Banquets At Tunxis. The other is customer-side, Shaina Tsupros, a bride.
The object inventory of various event venues were studied to inspire the choice of item to embed the IoT connectivity.
At last, we study the event venue and process of Tunxis and created an ecosystem map to visualize the relationship between people, objects, data and more.
Tunxis can charge a premium for events and elevate its brand perception through the expansion of its outdoor area into The Enchanted Forest.
Every bride wants a wedding tailor made for her. At Tunxis, this is what we provide. From a completely customizable experiential dance environment, to a thematically lit enchanted forest, no two weddings will ever be alike. Immerse yourself in the forest and explore into the evening. Whether it be our magic firepit or responsive luminous ceiling!
Our wedding experience is broken down into three zones – the indoor dancing/ceremony area, the enchanted forest, and the after-dinner firepit. This is the order and flow of most weddings but can be customized based on your requirements.
The indoor dancing area has two distinctive lighting elements that define the space, the primary being an overhead luminous ceiling made of a low-resolution LED panel with integrated motion sensors that allow standardized images to react to the movement of dancers beneath them. The secondary, supporting, element is the traditional AV equipment that is integrated with the ceiling to provide a consistent lighting experience.
The enchanted forest is like other outdoor lighting experiences but differs in two key ways – it is reactive and utilizes different lighting styles to create distinct zones. The reactive nature is key to the wedding experience and allows the bride and groom to act as guides when ushering the guests into the outdoors after the event. The pathway, lined with LED strips integrated landscaped into the edges of the path, light up when the couple are in proximity and create a chain reaction, setting off other lighting zones within the forest. The distinct zones then add depth the experience, but providing some visual variety to guests as they enjoy a post-ceremony/dance stroll. These zones are created through glass tubes with LED lighting stuck in the ground which create a “grass like” feel, sections of trees lit bottom-up with LED spot lights and other sections of trees that lit top-down with led strips that create a cascading effect.A prototype was created to demonstrate the proposed enchanted forest and possibly be used as a test platform to verify the lighting effect before full scale implementation.
The physical model was constructed of laser cut acrylic board (white, 3mm). The lighting were modeled with various type of LEDs:
Trees: Adafruit Mini Skinny NeoPixel Digital RGB LED Strip - 60 LED/m
Fire pit: NeoPixel Ring - 16 x 5050 RGBW LEDs - Warm White
Path walk: White two pin LED
Pavilion Ceiling: iPad (with animated display)
The LEDs were all connected to a single Particle Photon. Controlled was done with basic Neopixel library.
#include "math.h"
#include "application.h"
#include <neopixel.h>
#define STATE_BLANK 1
#define STATE_START 2
#define STATE_SCENES 3
#define STATE_FIRE 4
#define SCENE_WEDDING 1
#define SCENE_HALLOWEEN 2
#define SCENE_CHRISMAS 3
int state = 1;
// IMPORTANT: Set pixel COUNT, PIN and TYPE
#define PIXEL_PIN1 D2
#define PIXEL_COUNT1 168
#define PIXEL_TYPE1 WS2812B
#define PIXEL_PIN2 D0
#define PIXEL_COUNT2 96
#define PIXEL_TYPE2 SK6812RGBW
Adafruit_NeoPixel strip(PIXEL_COUNT1, PIXEL_PIN1, PIXEL_TYPE1);
Adafruit_NeoPixel ring(PIXEL_COUNT2, PIXEL_PIN2, PIXEL_TYPE2);
//fire
#define NUMBER_OF_FLAMES 24 // depends on number of neopixel triplets. 5 for 16 NeoPixel ring. 4 for 12 NeoPixel ring
#define FLAME_WIDTH 4 // How wide are the flames (in LEDs)
#define FLICKER_CHANCE 3 // increase this to increase the chances an individual flame will flicker
uint32_t rez_range = 256*3;
#define D_ false
// console buttons:
struct flame_element{
int brightness;
int step;
int max_brightness;
long rgb[3];
byte state;
} flames[NUMBER_OF_FLAMES];
int new_brightness = 0;
unsigned long rgb[3]; //reusable temporary array
uint8_t scaleD_rgb[3];
byte acc;
#define SCALERVAL 256*3
const int flamecolors[22][3] = {
{ SCALERVAL, 0, 0},
{ SCALERVAL, 0, 0},
{ SCALERVAL, 0, 0},
{ SCALERVAL, 0, 0},
{ SCALERVAL, 0, 0},
{ SCALERVAL, 0, 0},
{ SCALERVAL, 0, 0},
{ SCALERVAL, 0, 0},
{ SCALERVAL, SCALERVAL*.4, },
{ SCALERVAL, SCALERVAL*.4, 0},
{ SCALERVAL, SCALERVAL*.4, 0},
{ SCALERVAL, SCALERVAL*.4, 0},
{ SCALERVAL, SCALERVAL*.3, 0},
{ SCALERVAL, SCALERVAL*.3, 0},
{ SCALERVAL, SCALERVAL*.3, 0},
{ SCALERVAL, SCALERVAL*.3, 0},
{ SCALERVAL, SCALERVAL*.3, 0},
{ SCALERVAL, SCALERVAL*.3, 0},
{ SCALERVAL, SCALERVAL*.3, },
{ SCALERVAL, SCALERVAL*.3, SCALERVAL}, // white
{ 0, SCALERVAL*.2, SCALERVAL}, // that one blue flame
{ SCALERVAL, SCALERVAL*.3, SCALERVAL*.5}
};
int rainbowBrightness=0;
uint8_t offset = 0; // Position of spinny eyes
uint32_t color = 0xffffff; // Start white
//double twinkleTime = 0;
uint32_t prevTime;
int sceneNum = 1;
void setup() {
strip.begin();
strip.setBrightness(50);
strip.show(); // Initialize all pixels to 'off'
ring.begin();
ring.setBrightness(50);
ring.show(); // Initialize all pixels to 'off'
Particle.function("changeState",changeState);
Particle.function("changeScene",changeScene);
randomSeed(analogRead(4));
InitFlames();
prevTime = millis();
}
void loop() {
//Switch state and update display ---------------------------------------------------
switch (state){
case STATE_BLANK:
ring.clear();
ring.show();
tree();
break;
case STATE_START:
ring.clear();
ring.show();
rainbow(20);
break;
case STATE_SCENES:
ring.clear();
ring.show();
scenes();
break;
case STATE_FIRE:
strip.clear();
strip.show();
//tree();
fire();
break;
}
}
//cloud function to switch case
int changeState (String nextState){
if (nextState.toInt() == 0 or nextState.toInt()>4 ){
state = (state + 1) % 5;
}
else{
state = nextState.toInt();
}
return state;
}
int changeScene (String nextScene){
if (nextScene.toInt() == 0 or nextScene.toInt()>3 ){
sceneNum = (sceneNum + 1) % 4;
}
else{
sceneNum = nextScene.toInt();
}
return sceneNum;
}
int ledNum (int treeNum, int height){
return (treeNum)*7+height;
}
void rainbow(uint8_t wait) {
uint16_t i, j;
for(j=0; j<256; j++) {
for(i=0; i<strip.numPixels(); i++) {
strip.setPixelColor(i, Wheel((i+j) & 255));
}
if (rainbowBrightness<150){
strip.setBrightness(rainbowBrightness);
if (j%2 ==0){
rainbowBrightness++;
}
}
strip.show();
delay(wait);
}
}
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
if(WheelPos < 85) {
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
} else if(WheelPos < 170) {
WheelPos -= 85;
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
} else {
WheelPos -= 170;
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
}
void tree (){
for (int i = 0; i<24; i++){
for(int j = 0; j<7; j++){
if (j<3){
strip.setPixelColor(ledNum(i,j), 255,100,22);
}
else{
strip.setPixelColor(ledNum(i,j), 50, 255,0);
}
}
}
strip.show();
}
void fire(){
for(byte flame_count=0; flame_count<NUMBER_OF_FLAMES; flame_count++) {
switch(flames[flame_count].state){
case 0: // reset
CreateNewFlame(flame_count);
break;
case 1: //increasing
new_brightness = flames[flame_count].brightness + flames[flame_count].step;
if (new_brightness > flames[flame_count].max_brightness){
UpdateFlameColor(flame_count, flames[flame_count].max_brightness);
flames[flame_count].brightness = flames[flame_count].max_brightness;
flames[flame_count].step = GetStepSize(); // pick a different speed for flame going out
flames[flame_count].state = 2;
} else {
UpdateFlameColor(flame_count, new_brightness);
flames[flame_count].brightness = new_brightness;
}
break;
case 2: //decreasing
new_brightness = flames[flame_count].brightness - flames[flame_count].step;
// chance to flicker/rekindle:
if (random(new_brightness) < FLICKER_CHANCE){
// rekindle:
flames[flame_count].state = 1; //increase again
flames[flame_count].brightness = max(GetMaxBrightness(), flames[flame_count].brightness);
flames[flame_count].step = GetStepSize();
} else {
if (new_brightness <1){
flames[flame_count].state = 0; // bottomed out - reset to next flame
flames[flame_count].brightness = 0;
UpdateFlameColor(flame_count, 0);
} else {
UpdateFlameColor(flame_count, new_brightness);
flames[flame_count].brightness = new_brightness;
}
}
break;
}
}
ring.show();
delay(50);
}
void InitFlames(){
// Sets initial states in flames array
for(byte i=0; i<NUMBER_OF_FLAMES; i++) {
flames[i].state=0;
}
}
void UpdateFlameColor(byte flame_num, int new_brightness){
//
uint32_t c = 0;
uint32_t color_channel_value;
byte rgb_channel;
new_brightness = min(new_brightness, flames[flame_num].max_brightness);
for(byte rgb_channel=0; rgb_channel<3; rgb_channel++) {
color_channel_value = flames[flame_num].rgb[rgb_channel];
color_channel_value = color_channel_value * (uint32_t)new_brightness; // keep it long
color_channel_value = color_channel_value/(uint32_t)rez_range;
rgb[rgb_channel] = max(0L,color_channel_value);
} // step through R G B
// spread possible values of 0 -768 across 3 pixels
for(byte sub_pixel=0; sub_pixel<FLAME_WIDTH; sub_pixel++) {
for(byte i=0; i<3; i++) { // rgb
acc = rgb[i]/3;
byte d = rgb[i]%3;
if (sub_pixel < d){
acc++;
}
scaleD_rgb[i] = acc;
}
c = ring.Color(scaleD_rgb[1],scaleD_rgb[0], 0 ,0);
if(scaleD_rgb[1]-scaleD_rgb[0]<5){
ring.setPixelColor(flame_num * FLAME_WIDTH + sub_pixel, c);
}
else{
ring.setPixelColor(flame_num * FLAME_WIDTH + sub_pixel, 0,0,0,100);
}
}
}
void CreateNewFlame(byte flame_num){
flames[flame_num].step = GetStepSize();
flames[flame_num].max_brightness = GetMaxBrightness();
flames[flame_num].brightness = 0;
flames[flame_num].state = 1;
byte color_index = random(22);
for(byte i=0; i<3; i++) {
flames[flame_num].rgb[i] = flamecolors[color_index][i];
}
}
int GetStepSize(){
return random(70)+1;
}
int GetMaxBrightness(){
int retVal;
// retVal = random(rez_range/4) + random(rez_range/4) + random(rez_range/4) + rez_range/4 +1; // bell curve
// retVal = random(rez_range*3/4) + rez_range/4; // flat distribution
retVal = random(rez_range/2) + rez_range/2; // brighter flat distribution
return retVal;
}
/*void twinkle(){
for(int i=0; i<PIXEL_COUNT1; i++) {
uint32_t c = 0;
if(((offset + i) & 16) < 2) c = color; // 4 pixels on...
strip.setPixelColor( i, c); // First eye
strip.setPixelColor(167-i, c); // Second eye (flipped)
}
strip.show();
offset++;
delay(50);
}*/
void scenes(){
//Switch state and update display ---------------------------------------------------
switch (sceneNum){
case SCENE_WEDDING:{
for (int i = 0; i<24; i++){
for(int j = 0; j<7; j++){
if ((i % 2) == 0){
strip.setPixelColor(ledNum(i,j), 150,140,140);
}
else{
strip.setPixelColor(ledNum(i,j), 155,100,30);
}
strip.show();
}
}
}
break;
case SCENE_HALLOWEEN:
breath();
break;
case SCENE_CHRISMAS:{
for (int i = 0; i<24; i++){
for(int j = 0; j<7; j++){
if ((i % 2) == 0){
strip.setPixelColor(ledNum(i,j), 255,0,0);
}
else{
strip.setPixelColor(ledNum(i,j), 0,255,50);
}
strip.show();
}
}
}
break;
}
}
void breath( ){
float n = 0.2;
float val = (exp(sin(millis()/1300.0*M_PI)) - 0.36787944)*108.0;
//Serial.println( val );
uint16_t i;
uint32_t c = strip.Color(n*val, val, 0);
for(i=0; i< strip.numPixels(); i++) {
strip.setPixelColor(i, c );
}
strip.show();
delay (10);
}
Click to Expand
We have study the space and objects involved in the event ecosystem, proposed and prototyped the solution in the short six weeks. The proposal was successfully validated via Guerrilla testing with 20 young adults and via comments during the demo. On the other hand, if the time permits, the potential of this blank lighting canvas should be explored in depth, including the customization process, the collaboration with audio to to bring a real enchanted experience.
The enchanted forest is a IoT ecosystem with interactive LED lightnings designed to activate the forest area in the Tunxis golf club as a new event venue.
December 18th, 2017