Jiggle-Ometer
pod_no_walls.jpg

A prototype investigating ways of transferring data between a shaker attached to an Arduino Board and some creatures in Second Life. When the shaker is agitated, the creatures in Second Life will be agitated. Although this is NOT the type of interaction I will be using in my main project (it is far too obvious), it provides a quick and easy way of checking that the data is getting through and also forces me to start thinking about the aesthetics of the system.

jiggleometer_web.jpg

some thoughts on the visual design of the creatures. Technical details below …


Jiggleometer

The obvious component to use would be an accelerometer, but they cost £30-£50 each and I am too cheap to spend that on something I won't ultimately be using. The solution I found uses a simple tilt switch and a scripted counter to produce an integer value which roughly tracks how rapidly it is being jiggled.

jiggle_circuit_dia.jpg jiggleometer02_web.jpg

This very simple circuit is based on the Arduino 101 button switch example. I just replaced the button switch with a tilt switch and fixed it to the underside of the casing lid. The casing is made from an empty party popper and an old lid. The clever bit is in the Arduino code …

/* ++++ARDUINO CODE++++
 * Jiggleometer
 * by Annie Spinster
 * No xyz axes, just a simple tool for roughly measuring how much something is being shaken using a tilt switch.
 * 
 * Starting Point: 
 * Button
 * by DojoDave <http://www.0j0.org>
 *
 * Uses a counter to measure how much a tilt switch is being shaken. 
 * Turns on and off a light emitting diode(LED) connected to digital  
 * pin 13, when shaking the tilt switch.
 *
 * http://www.arduino.cc/en/Tutorial/Button
 */

int ledPin = 13;                // choose the pin for the LED
int inputPin = 7;               // choose the input pin (for a tilt switch)
int val = 0;                    // variable for reading the pin status
int lastVal = 0;                // variable for storing the previous value
int leakyTotal = 0;             // counter keeping track of HIGH/LOW changes and settling back to 0 over time
unsigned long time;             // variable for reading time in milliseconds since it started running

void setup() {
  pinMode(ledPin, OUTPUT);      // declare LED as output
  pinMode(inputPin, INPUT);     // declare tilt switch as input
  Serial.begin(9600);           // open a serial port
}

void loop(){
  val = digitalRead(inputPin);  // read the state of the tilt switch
  time = millis();              // read the time since the programme started 

// every 1/20 second, if the state of the switch has changed add 1 to the counter
// THIS CAN BE USED FOR CALIBRATION
if(((time%50)==0)&&(val!=lastVal)){
  leakyTotal++;

  if (val == HIGH) {            // check if the input is HIGH
    digitalWrite(ledPin, LOW);  // turn LED OFF
  } 
  else {
    digitalWrite(ledPin, HIGH); // turn LED ON
  }

  lastVal = val;                // store the current switch state as the previous state for the next loop
}

// every 1/2 second, if the counter is above 0 subtract 1 from the counter
// and print the results to the serial port.
// THIS CAN BE USED FOR CALIBRATION
if((time%500)==0){  
  if(leakyTotal>0){
    leakyTotal--;
  }
  Serial.println(leakyTotal);
}
}

Sending the data

I am using Pachube, a project initiated by Usman Haque:

A web service that enables people to tag and share real time sensor data from objects, devices and spaces around the world, facilitating interaction between remote environments, both physical and virtual

More details on this page


I am following this tutorial for connecting Arduino to Pachube. To keep things simple, I first build the example exactly as it is in the tutorial:

lightsensorcircuit_web.jpg scheme_LDR.gif

Light Sensor Circuit: Light Detecting Resistor and a 1KOhm resistor. Voltage is read between the two on analog pin 0. Built from this example.

I decided to keep the light sensor as part of this project as it is more similar to the type of data I want to use. The light sensor was pushed through an artificial flower and then soldered onto the circuit hidden behind the petals.

whole_kit.jpg flower.jpg

Old stuff
The example uses Firmata: "a generic protocol for communicating with microcontrollers from software on a host computer" with the Firmata v2 Arduino library for Processing. This means that if I run the StandardFirmata program (in examples) on the Arduino board, it can be controlled directly from Processing. It also means I will need to rewrite my code, moving my jiggleometer counter from Arduino to Processing. For now, though, I will continue with the light sensor example.

Communication with Pachube uses EEML - Extended Environments Markup Language. The Processing EEML library will write this automatically.

Here's the processing code:

//import the libraries
import processing.serial.*;
import cc.arduino.*;
import eeml.*;

Arduino arduino;               //declare an Arduino object
float myValue, lastUpdate; //myValue is what we are sending and lastUpdate is used for the timer
DataOut dOut;                 //declare a DataOut object to handle the data

void setup()
{

  //list available serial devices and choose the Arduino
  println(Arduino.list());
  arduino = new Arduino(this, Arduino.list()[1], 115200); 

  //tell the DataOut object what data to send and where to send it 
  dOut = new DataOut(this, "http://www.pachube.com/api/1415.xml", "my_api_key_here");
  dOut.addData(0,"light sensor, LDR, light level");
}

void draw()
{
  //read analog pin 0 on the Arduino board
  myValue = arduino.analogRead(0);
  println(myValue);

      //timer - happens once every 10 seconds ...
      if ((millis() - lastUpdate) > 10000){
        println("ready to PUT: ");

        //update the object's value and send to the Pachube feed using PUT. 
        //A response code will be returned  
        dOut.update(0, myValue);
        int response = dOut.updatePachube();
        println(response);

        //reset the timer
        lastUpdate = millis();
    }
}

To get the example to work I needed to set a static IP address for my computer and set up port forwarding. I found this site very helpful.


Updated method
Although Firmata worked fine for the original example, I ran into problems when I tried to rewrite my Processing code and I couldn't get it to work again (probably something very simple).

The solution I found was:

/*++++ARDUINO CODE++++
 * Jiggleometer and light sensor
 * by Annie Spinster
 * No xyz axes, just a simple tool for roughly measuring how much something is being shaken using a tilt switch.
 * 
 * Starting Point: 
 * Button
 * by DojoDave <http://www.0j0.org>
 * http://www.arduino.cc/en/Tutorial/Button
 *
 * Uses a counter to measure how much a tilt switch is being shaken. 
 * Turns on and off a light emitting diode(LED) connected to digital  
 * pin 13, when shaking the tilt switch. Also reads an analog input from a light sensor.
 *
 *
 */

int ledPin = 13;                // choose the pin for the LED
int diginputPin = 7;            // choose the digital input pin (for a tilt switch)
int analoginputPin = 0;         // choose the analog input pin (for a light sensor)
int digVal = 0;                 // variable for reading the digital pin status
int lastDigVal = 0;             // variable for storing the previous digital value
float leakyTotal = 0;           // counter for jiggleometer keeping track of HIGH/LOW changes and settling back to 0 over time
float analogVal = 0.0;          // variable for reading the analog pin
unsigned long time;             // variable for reading time in milliseconds since it started running

void setup() {
  pinMode(ledPin, OUTPUT);      // declare LED as output
  pinMode(diginputPin, INPUT);  // declare tilt switch as input
  pinMode(analoginputPin, INPUT);  // declare light sensor as input
  Serial.begin(9600);           // open a serial port
}

void loop(){
  digVal = digitalRead(diginputPin);       // read the state of the tilt switch
  analogVal = analogRead(analoginputPin);  // read the value of the light meter
  time = millis();                         // read the time since the programme started 

  // every 1/20 second, if the state of the switch has changed add 1 to the counter
  // THIS CAN BE USED FOR CALIBRATION
  if(((time%50)==0)&&(digVal!=lastDigVal)){
    leakyTotal++;

    if (digVal == HIGH) {                    // check if the input is HIGH
      digitalWrite(ledPin, LOW);             // turn LED OFF
    } 
    else {
      digitalWrite(ledPin, HIGH);            // turn LED ON
    }

    lastDigVal = digVal;                     // store the current switch state as the previous state for the next loop
  }

  // every 1/2 second, if the counter is above 0 make it 2/3 of its previous value
  // THIS CAN BE USED FOR CALIBRATION
  if((time%500)==0){  
    if(leakyTotal>0){
      leakyTotal=((leakyTotal*2)/3);
    }
    if(leakyTotal<1){                        // if it's less than 1 set it as 0
      leakyTotal=0;
    }
    int analogInt = int(analogVal);          // convert the values from floats to ints 
    int analogLeaky = int(leakyTotal);
    Serial.print(analogLeaky);               // send the data on the serial port
    Serial.print(", ");
    Serial.print(analogInt);
    Serial.println();
  }
}

… and then in Processing …

/**++++PROCESSING CODE++++
 * Read and Send
 * 
 * Read data from the serial port and send to Pachube
 * Works with a light sensor (analog) and a "jiggle-ometer" (digital)
 */

//import the libraries. serial lets us read the Arduino board and eeml lets us send the data to Pachube
import processing.serial.*;
import eeml.*;

Serial myPort;                                  // declare a serial object
String jiggleVal;                               
String buff = "";                               // buffer to hold the incoming bytes
float lastUpdate;                               // used in a timer
int lightsensor, jigglesensor;                  // variables to hold the data 

DataOut dOut;                                   // DataOut is an eeml library class. This is what will send the data to Pachube.

void setup() 
{

  String portName = Serial.list()[1];            // Arduino port is the second in the list - COM4
  myPort = new Serial(this, portName, 9600);     // define the serial object

  // tell the DataOut object which data stream to update  
  dOut = new DataOut(this, "http://www.pachube.com/api/1415.xml", "MY_PACHUBE_API_KEY_GOES_HERE");

  // set up what data to send and add some tags
  dOut.addData(0,"light sensor, LDR, light level");
  dOut.addData(1,"jiggleometer, tilt-switch");
}

void draw()
{

  if ( myPort.available() > 0) {                  // If data is available,
    serialEvent(myPort.read());                   // read the data

    //timer - happens every 5 seconds               
    if ((millis() - lastUpdate) > 5000){
        println("ready to PUT: ");
        dOut.update(0, lightsensor);              // send the data
        dOut.update(1, jigglesensor);
        println("lightsensor = "+lightsensor+", jigglesensor = "+jigglesensor);

        int response = dOut.updatePachube();      // check the data got there ok  
        println(response);
        lastUpdate = millis();                    // reset the timer
    }
  }

}

// this happens when data is sent over the serial port.
void serialEvent(int serial){
  if(serial != 10) {                              // if serial=10 it's a carriage return
    buff += char(serial);                         // put the first byte in the buffer
  }

  else {  //if it is a carriage return
    if (buff.length() > 1) {                      // if there's enough data, 
      buff = buff.substring(0, buff.length()-1);  // chop off the carriage return at the end
      jiggleVal = buff;                           // convert the contents of buff to an int

      String[] values = splitTokens(jiggleVal, ", ");

         //messy bugfix for occasional ArrayIndexOutOfBoundsException     
         if(values.length==2){            
         lightsensor = int(values[1]);             // change the values of the variables
         jigglesensor = int(values[0]);
         }

       // clean up a bit
       buff = "";
       myPort.clear();
    }

  }
}

here's a screenshot from the home page of my feed. The feed updates once every 5 seconds but the historical data and graph only update every 15 minutes. …

pachube_scr01_web.jpg

and here's a widget I made showing the latest historical data. Built using the PachuBlog app and Widgetbox …

The data comes in the wrong way round: the darker it gets the higher the number, so it's a dark meter, not a light meter. I think to correct this I need to reverse the polarity of the circuit.


Second Life

I built a virtual environment for my first test creatures on my tiny plot of land. This is very roughly thrown together using freebie textures I had but I am quite pleased with how enormous I managed to make my little skypod appear …

tardis_pod.jpg

At one end of the pod is a little cave (small or far away?) to hide the vanishing point where the textures come together messily. Glowing, tentacled creatures float beneath the surface of a misty lake. At the other end of the pod, some fleshy plants grow up from behind lurid coloured rocks …

rocks.jpg

An LSL script in one of the rocks asks for the data from the Pachube server:

//++++LSL CODE++++
//Uses llHTTPRequest to fetch data from the Pachube server and speaks it on a channel. 
//Displays the data as hovertext.

//these are concatenated later but are left here so it is easy to change feeds and datatypes
string server = "http://www.pachube.com/api/";
integer receive_feed = 1415;
string data_type = ".csv";
string api_key = "MY_PACHUBE_API_KEY_HERE";

integer lightsensor_id = 0;     // integers to hold the values
integer jiggleometer_id = 1;
key http_request_id_get;        // key variable to hold the incoming, unparsed data 

default{
  state_entry(){

    //make a timer to happen every 5 secs
    llSetTimerEvent(5.0);
  }

  timer(){
    //ask Pachube for an update
    string remote_url = server + (string)receive_feed + data_type +  "?key=" + api_key;
    http_request_id_get = llHTTPRequest(remote_url, [], "");
  }

  //this happens when Pachube responds
  http_response(key request_id, integer status, list metadata, string body){

    if (request_id == http_request_id_get){

      //parse the incoming data into a list (a bit like an array)
      list remote_variables = llParseString2List(body,[","],[]);

      //get the data we want to use in two integer variables
      integer v = llList2Integer(remote_variables,lightsensor_id);
      integer j = llList2Integer(remote_variables,jiggleometer_id);

      //speak the data on channel 290669
      llSay (290669, body);

      //change the hovertext
      llSetText("received remote data: " + body + ", darkness variable is: " + (string) v + ", jiggleometer reads: " + (string) j, <1,1,1>, 1);  
    } 
  }
}

… and a script in the plants listens for the data and changes the appearance of the plants. They glow and dim with the light variable and wobble around when the jiggle-ometer is shaken. This script also sends out data to the rest of the creatures.

plants_response.jpg

here's the code in the plants …

//****LSL SCRIPT****
//listens to incoming data on a channel, parses the results and uses them to influence 
//position and amount of glow of the object containing the script.
//broadcasts the results on a different channel.

integer lightsensor_id = 0;        //where to look in the incoming data
integer jiggleometer_id = 1;
float my_glow;                     //the target for glow to move towards
float current_glow;                //how much glow there is now
vector orig_pos;                   //position of object when rezzed
float lightdata;                   //variables to hold the incoming data
float jiggledata;
integer up;                        //used as a boolean so we know whether it should jiggle left or right

default{

    state_entry(){
        llListen(290669,"",NULL_KEY,"");   //listen on channel 290669
        llSetTimerEvent(0.1);              //set up a timer event every 1/10 second
        current_glow = 0.0;                //plants are not glowing when the script starts
        orig_pos = llGetPos();             //find out where the object is
    }

    //this happens when the script "hears" something on the designated channel 
    listen(integer channel, string name, key id, string message){

      //parse the incoming data into a list (a bit like an array)
      list remote_variables = llParseString2List(message,[","],[]);

      //get the data we want to use in two integer variables
      float l = llList2Float(remote_variables,lightsensor_id);
      jiggledata = llList2Float(remote_variables,jiggleometer_id);

      //make sure the light variable is within sensible bounds
      if(l<1){
          l=1;
      }

      else if(l>1000){
          l=1000;
      }

      //make the object glow depending on how much light there is
      //my_glow is the amount the glow should be moving towards
      //depending on how much light is reaching the sensor
      my_glow = (1.0 - (l/1000))/4;
      while(my_glow!=current_glow){
          if(my_glow>current_glow){
            current_glow+=0.01;
            if (my_glow<current_glow){
                current_glow=my_glow;
            }
        }
        else if(my_glow<current_glow){
            current_glow-=0.01;
            if(my_glow>current_glow){
                current_glow=my_glow;
            }
        }
    llSetLinkPrimitiveParams(LINK_SET, [PRIM_GLOW, ALL_SIDES, current_glow]);
    }
}

timer(){

    //make a string object to hold a message to send to other objects
    string send_jiggle;
    //if the jiggleometer reads 0 return to original position
    if (jiggledata==0){
      llSetPos(orig_pos);
      send_jiggle = "<0.0,0.0,0.0>";
    }

    //else move according to how much jiggle there is
    else{

      if(up==FALSE){
        float newx = -(jiggledata/100);
        llSetPos(orig_pos+<newx,0,0>);
        float newz = newx*10;
        vector jiggle_send = <0.0,0.0,newz>;
        send_jiggle = (string)jiggle_send;
        up=TRUE;
      }
      else{
        float newx = (jiggledata/100);
        llSetPos(orig_pos+<newx,0,0>);
        float newz = newx*10;
        vector jiggle_send = <0.0,0.0,newz>;
        send_jiggle = (string)jiggle_send;
        up=FALSE;
      }
    }

    //tell the other creatures how much to move on channel 966092
    llSay(966092, send_jiggle);
  }  
}

The tentacle creatures only respond to the jiggle-ometer data. It would be possible to script changes in the amount of glow depending on the light variable as well, but this would be a bit complicated as different faces of the prims have different amounts of glow. Lots of experimentation is needed and this is something I will investigate further. Here are two shots of the tentacle creatures taken close together to show the up-down movement of the jiggle-ometer effect …

tentacles.jpg

The tentacle creatures each run a very simple script that listens on a channel for information from the plants and moves them by the right amount. All the hard sums are done by the plant script …

//****LSL SCRIPT****
//listens on a channel and moves the object depending on the incoming vector data

vector jiggle;            //how much to move
vector orig_pos;          //where the object is at the start    

default
{
    state_entry()
    {
        llListen( 966092, "", NULL_KEY, "" );     //listen on channel 966092
        orig_pos = llGetPos();                    //find out where the object is at the start
    }

    //when the script "hears" something ...    
    listen( integer channel, string name, key id, string message )

    {
        jiggle = (vector)message;                 //change the incoming string to a vector
        llSetPos(orig_pos+jiggle);                //move the object

    }
}

old stuff
Some early experiments using the "glow" property of SL prims to respond to the light sensor with 1 - a strong light on it, 2 - in normal daylight and 3 - covered with an old film container.

j01_bright_rl.jpg j01_bright_sl.jpg
j01_med_rl.jpg j01_med_sl.jpg
j01_dark_rl.jpg j01_dark_sl.jpg

Add a New Comment
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License