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.
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.
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:
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.
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. …
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 …
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 …
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.
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 …
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.
Hello!
I've got both an SL login (or name) and a place to build stuff. But as it happens I am some what of a naif regarding scripting. Can you describe the scripts used to make the "glow" property work for the items that are at the extreme bottom of your site?
Also did you configure your device to wear an Ethernet shield or did you ultimately use another method?
—-
Gregg (Interested Doctor Who fan in the US.)