Pixie: Plant empathy

Made by Kavi Pather

Found in DioT 2019: Internet of Plants

Helping plants and humans to connect, communicate and ultimately foster a closer relationship through the magic of IoT

0

Understanding the feelings of plants

You know your plant is alive. Guess what … it probably has emotions too. It certainly feels on some level when it is too hot, too cold, thirsty or in need of some sunlight (just like you!). What if it could empathize with your plant, in the moment? What if your plant could communicate with you in an intuitive way. Introducing Pixie, the IoT weed that helps your plant to communicate with you.

Pixie monitors the light levels, temperature levels (and, coming in version 2: soil moisture levels) and is able to communicate what the plant needs using a single pixel. Pixie displays the emotions in the following ways:

  • Sleep/Breathing slowly: Sinusoidal green LED slow flash indicating low light levels and sleep
  • Anxiety/Breathing quickly: Sinusoidal green LED flash - indicating light levels too high
  • Well being: Sinusoidal green LED flash in a calm rhythm
  • Too hot: Flickering red candle
  • Too cold: Shivering blue

Pixie allow a owner to be more attuned to the needs of to their plant. Ultimately this will mean a less nervous owner and a happier plant! It also does this in a natural and 'polite' way without shouting at the user through notifications.

The video below shows the plant exhibiting these emotions in demo mode. Can you catch them all (the breathing ones are subtle but have tested well for effectiveness)?

0
Internet of Plants
Kavishin Pather - https://youtu.be/yGbJzlfLee4
0

"Helping plants and humans to connect, communicate and ultimately foster a closer relationship through the magic of IoT"

0

Approach

For this project, I aimed to focus on communicating emotion through the simplest of ambient displays - a simple RGB Led. It is an attempt to personify a plant without making the experience too obstrusive. It was also a technical challenge to be able to make the feelings of the plant seem intuitive to a human. It was also challenging to make the plant seem to 'breathe' without being distracting.

While the form of the device was not a requirement, I chose to explore it since I felt that it was important to communicate the idea appropriately. i.e. it was important to make the plant seem to communicate naturally.

In order to make the 'breath' animation seem more natural, I used a sine function rather than a straight-line approach. This was then tweaked to add pauses at the apex and trophs. A custom function allowed the timing to be adjusted to iteratively create the 'feel' of a human breathing. A similar approach will be used to create a 'shiver' (with a decreasing aptitude), however, a random brightness sequence was used to create a flicker):

Graph of the sine function

A sine graph

0

Process

The idea took flight gradually over several weeks. Once I had honed in on what I wanted to do, I sketched out the concept and planned the mechanics with pseudo code.

The process can be broadly outlined as follows:

1. Understand the brief by reading through it carefully

2. Ideate around the solutions that would be possible. Identify a solution that I am passionate in pursuing

3. Prototype electronics, this went through several versions and iterations. It took many hours to calibrate the precise timing

4. Ideate on the form of the prototype

5. Code up the final prototype. This part was challenging to get it working consistently. It is still a little unreliable

6. Build the 'looks like prototype.

7. Refine both of the above to ensure they work together (e.g. put the LED on a lead to combine them).

0

Technical Documentation

List of components (excl basic components):

  • Argon Particle Board with WIFI connector
  • 1x RGB LED
  • 2x 1k resistors
  • 1x TMP36 Temperature Sensor
  • 1x Photoresistor

Circuit diagram and code

The circuit diagram is provided below:


0
#include <math.h>


int clokfreq= 10; //Overall timer of the clock
float freq = 1/clokfreq;

int brightness = 255; // Full brightness for an CATHODE RGB LED is 0, OFF is 255

int redPin = D2;    // RED pin of the LED to PWM pin **d2**
int greenPin = D3;  // GREEN pin of the LED to PWM pin **d3**
int bluePin = D4;   // BLUE pin of the LED to PWM pin **D4**
int ePins[] = {redPin, greenPin, bluePin}; //RGB
int eColor[] = {250,250,250};
int eNull[] = {0,0,0};

//Create Light Sensor related variables
int lightPin = A1; //LIghtsensor
int lightLevel = 0;

// Create Temparature sensor related variables
int tempPin = A0; //Temparature sensor
double temperature = 0.0;
double temperatureF = 0.0;
int movAverage[] = {20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20};
int reading = 0;
int avetemp = 0;

bool tempNormal = false;

int last_published = -1;

bool demomode = true;
int ndemo = 0;


void setup() {

    //Initialise the pins
    pinMode(redPin, OUTPUT);
    pinMode(bluePin, OUTPUT);
    pinMode(greenPin, OUTPUT);
    pinMode(lightPin, INPUT);
    
    //Create output monitoring variables to Console - RGB
    Particle.variable("Red", eColor[0]);
    Particle.variable("Green", eColor[1]);
    Particle.variable("Blue", eColor[2]);
    
    Particle.function("vRed", changeLED);
    
    // Register a Particle variable here
    Particle.variable("temperature", &temperature, DOUBLE);
    Particle.variable("temperatureF", &temperatureF, DOUBLE);

    // Connect the temperature sensor to A0 and configure it
    // to be an input
    pinMode(tempPin, INPUT);
    
    testCycle();
    
}

int changeLED(String command){
//    int colorValue = command.toInt();;
//    outputt(colorValue, 0, 0);
//    return 1;
}

void testCycle(){
    eColor[0] = 0;
    eColor[1] = 255;
    eColor[2] = 0;
    for (int i = 0; i < 3; i++){
        outputt(eColor);
        delay(500);
        outputt(eNull);
        delay(500);    
    }
}

//Output the passed vector Value to the LED
void outputt(int outCol[3]){
    analogWrite(redPin, 255-outCol[0]);
    analogWrite(greenPin, 255-outCol[1]);
    analogWrite(bluePin, 255-outCol[2]);
}


//Pulse according to sine grap, nticks = number of clock cycles, tickval = distance in sine space
//amp = amplitude of sinegraph scalar; pticks = array of pause positions; pdurs = array of pause durations
void pulse(int outCol[3], int nticks, float tickval, float amp, int pticks[2], int pdurs[2]){
    int noPauses = 2;
    
    float scycle = 0; //track the x position of sine graph
    float scal = 0; //the scalar -> y position of sine graph
    int redvaltmp, bluevaltmp, greenvaltmp;
    int tmpCol[3];
    
    
    for (int lp = 0; lp < nticks; lp++){
        scycle = scycle + tickval;
        scal = (amp*sin(scycle)+1)/2; //convert to 0-1 space scalar for led out
    
        for (int i = 0; i < 3; i++){
            tmpCol[i] = round(outCol[i] * scal);
        }
    
        outputt(tmpCol);
        
        for (int lp2 = 0; lp2 < noPauses; lp2++){
            if (lp == pticks[lp2]){
                delay(pdurs[lp2]);
            }
        }
        delay(clokfreq);
    }
}

//Repeat pulse n times, accept parameters for cycle length and number of cycles
void pulse_durxn(int outCol[3], int ncycles, float cyclelen){
    int pticks[2] = {0,0};
    int pdurs[2] = {0,0};
    
    pulse(outCol, round(ncycles*cyclelen/clokfreq), 6.284*clokfreq/cyclelen, 1, pticks, pdurs);
}

//Use pulse and add pauses at top and bottom 
void breath(int outCol[3], int ncycles, float in_out, float outhold, float inhold, float depth){
    
    // set the breakpoints at the top and bottom of the sine graph 25% and 75% for sine graph that starts at 0
    int pticks[2] = {round(0.25*2*in_out/clokfreq),round(0.75*2*in_out/clokfreq)};
    
    //pass the hold delays through
    int pdurs[2] = {inhold,outhold};
    
    //in_out*2 is equal to one period of the grpah
    //in_out is equal to a breath in cycle or breath our cycle
    //2 x in_out/clokfreq = number of ticks for 1 period
    //6.284/above term is the length of one tick in sine unit space to cover (6.284 = 2 x pi)
    //0.95 to nto use the full range
    for (int lp = 0; lp < ncycles; lp++){
        pulse(outCol, round(2*in_out/clokfreq), 6.284*clokfreq/(2*in_out), depth, pticks, pdurs);
    }
}

//Flicker a colour; nscale is the scaling of the clock
void flicker(int outCol[3], int nscale){
    byte flicker[] = {180, 30, 89, 23, 255, 200, 90, 150, 60 , 230, 180, 45, 90};
    int tmpCol[3];
    
    for (int i = 0; i < 13; i++){
        for (int i2 = 0; i2 < 3; i2++){
            tmpCol[i2] = round(outCol[i2]*flicker[i]/255);   
        }
        outputt(tmpCol);
        delay(round(clokfreq*nscale));
    }
}

void shiver(int outCol[3], int nscale){
    byte flicker[] = {180, 180, 30, 180, 180, 30, 180, 180, 30, 30};
    int tmpCol[3];
    
    for (int i = 0; i < 10; i++){
        for (int i2 = 0; i2 < 3; i2++){
            tmpCol[i2] = round(outCol[i2]*flicker[i]/255);   
        }
        outputt(tmpCol);
        delay(round(clokfreq*nscale));
    }
}   

float updatetemp(float newtemp){
    float agg = 0;
    agg = newtemp;
    float updateval = 0.0;
    for (int i = 0; i < 49; i++){
        movAverage[i] = movAverage[i+1];
        agg = agg + movAverage[i+1];
    }
    movAverage[49] = newtemp;
    updateval = agg/50;
    return(updateval);
}

void to_spreadsheet( ){

    //check if 1 minute has elapsed
    if( last_published + 60000 < millis() ){
    	Particle.publish( "log_to_spreadsheet", String( lightLevel  ) );
    	Particle.publish( "log_to_spreadsheet", String( avetemp  ) );
    	last_published = millis();
    }
}
	

void loop() {
    
    //PROC: read in all sensors INPUT: Get from sensors OUTPUT: sensLightIn; senTempIn; sensSoilIn
    
    //Read light level
    lightLevel = analogRead(lightPin);
    //Particle.publish("light_reading", String(lightLevel));
    
    //Read temp
    reading = analogRead(tempPin);
    // The returned value from the device is going to be in the range from 0 to 4095
    // Calculate the voltage from the sensor reading
    double voltage = (reading * 3.3) / 4095.0;
    // Calculate the temperature and update our static variable
    temperature = (voltage - 0.5) * 100;
    avetemp = updatetemp(temperature);
    // Now convert to Farenheight
    temperatureF = ((temperature * 9.0) / 5.0) + 32.0;
    
    //Code for demo mode
    int ncyc = 1;
    if (demomode == true){
        if (ndemo < ncyc){
            //normal
            lightLevel = 1500;
            avetemp = 15;
        }else{
            if (ndemo < 2*ncyc){
                //bright
                lightLevel = 2500;
                avetemp = 15;
            }else{
                if (ndemo < 3*ncyc){
                    lightLevel = 500;
                    avetemp = 15;
                }else{
                    if (ndemo < 8*ncyc){
                        lightLevel = 1500;
                        avetemp = 30;
                    }else{
                        if (ndemo < 12*ncyc){
                            lightLevel = 1500;
                            avetemp = -2;
                        }
                    }
                }   
            }
        }
        ndemo ++;
    }
    if (ndemo > 12*ncyc){
        ndemo = 0;
    }
    
    //OUtput to console for monitoring
    Particle.publish("temperature", String(temperature));
    Particle.publish("avetemp", String(avetemp));
  
    //PROC: emote - NOTE only two sensors implemented
        //If both Agg < and Imm < - emote urgent; 
        //If Agg < and Imm > - emote gentle
        //If Agg > then emote well
        // Hierarchy - Temp, Light, Water
        //Well - Green stable - Pulse breath DONE
        //Too Hot - Flicker flame - Flicker flame DONE
        //Too Cold - Shiver cold - SOLVED
        //Too Dark - Green dim
        //Too Bright - Green bright
        //Thirsty -  Sick looking blue - May be too difficult
        //Swamped - Yellow blue - May be too difficult 
        
    if (avetemp < 10){
         //shiver
        eColor[0] = 0; eColor[1] = 0; eColor[2] = 255; //Red works better
        shiver(eColor, 3); //set to 100ms 10x 10ms clock
        tempNormal = false;
    }else{
        if (avetemp > 25){
            //falme
            //eColor[0] = 89; eColor[1] = 35; eColor[2] = 13; //Flame color - did not work
            eColor[0] = 255; eColor[1] = 0; eColor[2] = 0; //Red works better
            flicker(eColor, 10); //set to 100ms 10x 10ms clock
            tempNormal = false;
        }else{
            tempNormal = true;
        }
    }
    
    Particle.publish("TempNormal", String(tempNormal));
    if (tempNormal == true){
        if (lightLevel < 1000){
            //Sleep
            eColor[0] = 0; eColor[1] = 255; eColor[2] = 0;
            breath(eColor, 4, 2000, 1000, 400, 0.8);    
        }else{
            if (lightLevel > 2000){
                //Pant
                eColor[0] = 0; eColor[1] = 255; eColor[2] = 0;
                breath(eColor, 8, 120, 20, 0, 0.9);    
            }else{
                //Live
                eColor[0] = 0; eColor[1] = 255; eColor[2] = 0;
                breath(eColor, 6, 1000, 800, 200, 0.8);
            }
        }
    }

    //Output to IFFFT    
    to_spreadsheet();
    
    //tick the clock over
    delay(clokfreq);
}
Click to Expand
0

Connecting to IFFFT

Connecting to the IFFFT. A key benefit of a product such as this is the fact that it can log data and allow the user to understand what is going on with the plant. Ultimately, this could be used to make suggestions to the user on how better to take care of the plant.

In order to link this to and log the data, it uses the following IFFFT app:


0

Next steps

I envision that the project could be taken further in the following ways:

  • Improvement of the LED component to better communicate emotion
  • Improvement of the 'Shivering' emotion to include a sinusoidal decay algorithm
  • Incorporation of a soil sensor and a 'thirst' emotion as well as a 'feeling sick' emotion to indicate over-watering
  • As mentioned below, ultimately, this could be used to make suggestions to the user on how better to take care of the plant
  • Give it a form by incorporating the electronics more throughly. A key idea that I want to explore is a pipe-cleaner form factor which would allow it to cling onto a plant in the branches and/or stick into the soil


0

Reflection

It was overall an enjoyable project. I did however encounter some difficulties while executing it:

  1. I burnt out a TMP temparature sensor after following the incorrect tutorial on setting it up. I have learned that the technical numbers of these components are very important to pay attention to. In addition, the current one seems somewhat temperamental; I would consider swapping it out or using a higher quality component in the fullness of time.
  2. Getting the communication of emotion correct and intuitive was extremely challening. Subtlety is important and I spent many many hours tweaking the timings and algorithms. They could still use improvement. I have learned that the details matter in making things intuitive for people to glance at.
  3. I hit up against the limitations of the LED and this project could be improved hugely by using a more sophisticated component.
0

References:

  1. TMP36: https://diotlabs.daraghbyrne.me/docs/working-with-sensors/tmp36/
  2. Tutorials: https://diotlabs.daraghbyrne.me/
  3. Circuit Diagram Software: https://fritzing.org/home/
  4. Sine graph from: https://en.wikibooks.org/wiki/Trigonometry/Graphs_of_Sine_and_Cosine_Functions
0

More pictures of the prototype:

The overall setup:


The circuit:


x
Share this Project

This project is only accessible by signed in users. Be considerate and think twice before sharing.


Courses

49713 Designing for the Internet of Things

· 16 members

A hands-on introductory course exploring the Internet of Things and connected product experiences.


Focused on
Skills
About

Helping plants and humans to connect, communicate and ultimately foster a closer relationship through the magic of IoT

Created

November 7th, 2019