Group 4: LumiTouch Frames to How's the Weather

Made by Freya Yang and Isabel Fleck

Created: December 11th, 2024

0

Project Summary 


Intention

Lumi Touch Remake - "How’s the Weather" was designed to bridge emotional and physical distances between friends in different locations. The prototype enables users to subtly communicate their emotional state and mood through tactile interaction with a bird-shaped device and accompanying glowing lights. The concept revolves around the metaphor of weather to represent moods: sunny, cloudy, rainy, and stormy, making it intuitive and accessible. The device fosters empathy and a sense of presence by translating physical interactions into visual feedback. This project explores how IoT devices can deepen emotional connections in today’s digitally connected but emotionally fragmented world.

Historical Cases

This project draws inspiration from historical emotional communication devices like the LoveBox or the Tactile Internet, which use haptic and visual elements to convey feelings. Drawing from theories of affective computing and emotional design (Norman, 2004), this project emphasizes subtlety and intimacy in interaction. The bird design was inspired by symbolic connections to nature and harmony, aligning with the Lumi Touch's objective to create gentle, non-intrusive communication. We reference prior works on embodied interaction (Dourish, 2001) to underline the importance of physical touch in human connection.

0

Remaking


Approach Overview

The key goal was to rebuild the Lumi Touch frame as an IoT-enabled emotional interface. The focus was on reviving tactile and visual feedback mechanisms to communicate across physical distances seamlessly.

Process

First, we started the initial sketches early brainstorming metaphors of touch and weather. Material choices are components included NeoPixel LEDs for dynamic lighting and a Particle Photon for connectivity. We then developed the interaction folow, which is squeezing the bird sends a signal to trigger corresponding lights on a paired device. The challenge of this process is that we still need to ensure real-time responsiveness and crafting a bird that is ergonomic and durable if we really want to push our prototypes with our design concept.


Bill of Materials 

The materials we used for the prototype include:
1. Particle Photo microcontroller x 2
2. NeoPixel LED stip x2
3. Pressure sensor (for detecting squeezes) x2
4. a bird prototype
5. Power supply(laptops), jumping wires and usb converter


Workflow Diagram

Input: User squeezes the bird

Processing: The signal is sent via the Particle Photon
Output: The paired Lumi Touch frame emits corresponding light patterns


Circuit Diagram 

[Insert circuit diagram here]


Rebuild Prototype

 

[Firgue 1: First Attempt, using LED as the first prototype demo]

[Figure 2: Second Attempt, using the NeoPixel LED Strip]

Code

[Insert Code here]

0
// This #include statement was automatically added by the Particle IDE.
#include <neopixel.h>

#include "Particle.h"
#include "neopixel.h"

SYSTEM_MODE(AUTOMATIC);

// Define pin and NeoPixel configuration for Photon 2
#if (PLATFORM_ID == 32)
#define NEOPIXEL_PIN SPI // Use MOSI for Photon 2
#else
#define NEOPIXEL_PIN D3 // Use D3 for other Particle devices
#endif

#define NUM_PIXELS 8     // Number of LEDs in the NeoPixel strip
#define PIXEL_TYPE WS2812B // Specify the NeoPixel type

// Pressure sensor pin
#define PRESSURE_PIN A0

// Create a NeoPixel object
Adafruit_NeoPixel strip(NUM_PIXELS, NEOPIXEL_PIN, PIXEL_TYPE);

void setup() {
    strip.begin();   // Initialize NeoPixel strip
    strip.show();    // Turn off all LEDs initially
    Serial.begin(9600); // Debugging
    
    // Subscribe to events from the Particle Cloud
    Particle.subscribe("weatherEvent", weatherEventHandler);
}

// Function prototypes for weather effects
void thunderEffect();
void rainEffect();
void cloudyEffect();
void sunnyEffect();

// Function to publish weather events
void publishWeatherEvent(const char* eventName) {
    String event = String("weatherEvent/") + eventName;
    Particle.publish(event);  // Publish the event with the prefix
    weatherEventHandler(event.c_str(), ""); // Call event handler immediately
}

// Particle event handler
void weatherEventHandler(const char* event, const char* data) {
    String eventString = String(event);

    if (eventString == "weatherEvent/sunny") {
        sunnyEffect(); // Call sunny effect function
    }
    else if (eventString == "weatherEvent/cloudy") {
        cloudyEffect(); // Call cloudy effect function
    }
    else if (eventString == "weatherEvent/rainy") {
        rainEffect(); // Call rainy effect function
    }
    else if (eventString == "weatherEvent/thunder") {
        thunderEffect(); // Call thunder effect function
    }
}

void loop() {
    // Read the pressure sensor value
    int pressure = analogRead(PRESSURE_PIN);
    Serial.println(pressure); // Debugging the sensor value

    // Check how long the pressure is held
    static unsigned long pressStartTime = 0;
    static bool isPressed = false;

    if (pressure > 1000) { // Threshold for detecting a press
        if (!isPressed) {
            pressStartTime = millis(); // Start timing
            isPressed = true;
        }
    } else {
        isPressed = false;
    }

    if (isPressed) {
        unsigned long pressDuration = millis() - pressStartTime;

        if (pressDuration > 5000) {
            publishWeatherEvent("thunder");
        } else if (pressDuration > 3000) {
            publishWeatherEvent("rainy");
        } else if (pressDuration > 2000) {
            publishWeatherEvent("cloudy");
        } else if (pressDuration > 1000) {
            publishWeatherEvent("sunny");
        }
    } else {
        strip.clear(); // Turn off LEDs when not pressed
        strip.show();
    }
}

// Thunder: Blue and yellow flash
void thunderEffect() {
    for (int i = 0; i < NUM_PIXELS; i++) {
        strip.clear();
        if (i % 2 == 0) {
            strip.setPixelColor(i, strip.Color(0, 0, 128)); // Dim blue
        } else {
            strip.setPixelColor(i, strip.Color(128, 128, 0)); // Dim yellow
        }
        strip.show();
        delay(50);
    }
    strip.clear();
    strip.show();
}

// Rain: Light dim blue light
void rainEffect() {
    for (int i = 0; i < NUM_PIXELS; i++) {
        strip.clear();
        strip.setPixelColor(i, strip.Color(0, 0, 128)); // Dim blue
        strip.show();
        delay(100);
    }
}

// Cloudy: Dim violet/lavender light
void cloudyEffect() {
    static int brightness = 0;
    static int direction = 5;

    brightness += direction;
    if (brightness >= 100 || brightness <= 0) {
        direction = -direction;
    }

    uint32_t color = strip.Color(75 + brightness, 50 + brightness, 150 + brightness); // Dim lavender/violet
    for (int i = 0; i < NUM_PIXELS; i++) {
        strip.setPixelColor(i, color);
    }
    strip.show();
    delay(50);
}

// Sunny: Warm orange light
void sunnyEffect() {
    static int brightness = 0;
    static int direction = 5;

    brightness += direction;
    if (brightness >= 255 || brightness <= 50) {
        direction = -direction;
    }

    uint32_t color = strip.Color(255, 140, 0); // Warm orange
    for (int i = 0; i < NUM_PIXELS; i++) {
        strip.setPixelColor(i, color);
    }
    strip.show();
    delay(50);
}
Click to Expand
0

Reinterpreting


Approach 

A paragraph of approach description here. 

Process

A paragraph of approach description here.

Conceptual Design

An Interactive IoT device that allows you to check in with a friend about their current mood and how they are feeling, and tell them how you are doing.

[Figure 3: How's weather Design Concept Storyboard]


Prototype:

Bills of Materials: 


Workflow Diagram:

Circuit Diagram:

Code:

[Insert code description here

[Insert code below]


0
#include "Particle.h"
#include "neopixel.h"

SYSTEM_MODE(AUTOMATIC);

// Define pin and NeoPixel configuration for Photon 2
#if (PLATFORM_ID == 32)
#define NEOPIXEL_PIN SPI // Use MOSI for Photon 2
#else
#define NEOPIXEL_PIN D3 // Use D3 for other Particle devices
#endif

#define NUM_PIXELS 8     // Number of LEDs in the NeoPixel strip
#define PIXEL_TYPE WS2812B // Specify the NeoPixel type

// Pressure sensor pin
#define PRESSURE_PIN A0

// Create a NeoPixel object
Adafruit_NeoPixel strip(NUM_PIXELS, NEOPIXEL_PIN, PIXEL_TYPE);

void setup() {
    strip.begin();   // Initialize NeoPixel strip
    strip.show();    // Turn off all LEDs initially
    Serial.begin(9600); // Debugging

    // Subscribe to events from the Particle Cloud
    Particle.subscribe("weatherEvent", weatherEventHandler);
}

// Function prototypes for weather effects
void thunderEffect();
void rainEffect();
void cloudyEffect();
void sunnyEffect();

// Function to publish weather events
void publishWeatherEvent(const char* eventName) {
    String event = String("weatherEvent/") + eventName;
    Particle.publish(event);  // Publish the event with the prefix
    weatherEventHandler(event.c_str(), ""); // Call event handler immediately
}

// Particle event handler
void weatherEventHandler(const char* event, const char* data) {
    String eventString = String(event);

    if (eventString == "weatherEvent/sunny") {
        sunnyEffect(); // Call sunny effect function
    }
    else if (eventString == "weatherEvent/cloudy") {
        cloudyEffect(); // Call cloudy effect function
    }
    else if (eventString == "weatherEvent/rainy") {
        rainEffect(); // Call rainy effect function
    }
    else if (eventString == "weatherEvent/thunder") {
        thunderEffect(); // Call thunder effect function
    }
}

void loop() {
    // Read the pressure sensor value
    int pressure = analogRead(PRESSURE_PIN);
    Serial.println(pressure); // Debugging the sensor value

    // Determine weather event based on pressure level
    if (pressure > 3600) {
        publishWeatherEvent("thunder");
    } else if (pressure > 2700) {
        publishWeatherEvent("rainy");
    } else if (pressure > 1800) {
        publishWeatherEvent("cloudy");
    } else if (pressure > 900) {
        publishWeatherEvent("sunny");
    } else {
        strip.clear(); // Turn off LEDs when pressure is too low
        strip.show();
    }
}

// Thunder: Blue and yellow flash
void thunderEffect() {
    for (int i = 0; i < NUM_PIXELS; i++) {
        strip.clear();
        if (i % 2 == 0) {
            strip.setPixelColor(i, strip.Color(0, 0, 128)); // Dim blue
        } else {
            strip.setPixelColor(i, strip.Color(128, 128, 0)); // Dim yellow
        }
        strip.show();
        delay(50);
    }
    strip.clear();
    strip.show();
}

// Rain: Light dim blue light
void rainEffect() {
    for (int i = 0; i < NUM_PIXELS; i++) {
        strip.clear();
        strip.setPixelColor(i, strip.Color(0, 0, 128)); // Dim blue
        strip.show();
        delay(100);
    }
}

// Cloudy: Dim violet/lavender light
void cloudyEffect() {
    static int brightness = 0;
    static int direction = 5;

    brightness += direction;
    if (brightness >= 100 || brightness <= 0) {
        direction = -direction;
    }

    uint32_t color = strip.Color(75 + brightness, 50 + brightness, 150 + brightness); // Dim lavender/violet
    for (int i = 0; i < NUM_PIXELS; i++) {
        strip.setPixelColor(i, color);
    }
    strip.show();
    delay(50);
}

// Sunny: Warm orange light
void sunnyEffect() {
    static int brightness = 0;
    static int direction = 5;

    brightness += direction;
    if (brightness >= 255 || brightness <= 50) {
        direction = -direction;
    }

    uint32_t color = strip.Color(255, 140, 0); // Warm orange
    for (int i = 0; i < NUM_PIXELS; i++) {
        strip.setPixelColor(i, color);
    }
    strip.show();
    delay(50);
}
Click to Expand
x