Author Archives: Jo Redwood

About Jo Redwood

Student at the University of Plymouth, studying digital art and technology.

IDAT210 – Weather Transplant

Full readme: here.

Research was carried out to identify the necessary components for the project and the means of operating them alongside development. Specialist parts were purchased and ordered along with a number of spares so that in the eventuality of components failing they could serve as immediate replacements. Development of the main components was done in isolation from each other to ensure modularity for potential future expansion and clarity in the code.

Work on the device itself started by creating a code function for calculating the speed at which each of the fans will need to run at to provide full three hundred and sixty degree control over wind direction. I chose to use processing for this task as I could create a visual interface and use it to send information to an arduino using a serial connection. I chose to use them for their simplicity and open source design ethic which fosters creativity and hobbyist development.

The software calculates the positions of each fan in a circle and measures the distance from the nearest position/s to the required angle. This distance is then mapped and converted into a percentage figure for the speed of each fan. This is mapped again to a value between zero and 255 which is accepted for arduino’s analogOut function for controlling its Pulse Width Modulation duty cycle. If the duty cycle – which is the percentage of the time that something is on – on a 12v signal is 50% then the effective voltage is 6v as the signal switches on and off very rapidly. Through this I can programmatically control the speed at which the fans run. As the arduino only outputs a maximum of 5v and I required 12v I experimented with MOSFET transistors. When powered by the 5v signal from the arduino the transistor allows current on a 12v rail from a computer power supply to pass through. As this is switching rapidly for the PWM signal it has the effect of amplifying it.

For the sake of simplicity I replaced the transistor with a ULN2003 DIP chip which could do the job for all of the fans in a single component.

The wind portion of the device has six 140mm 12 volt desktop computer fans arranged in a ring facing inwards so that they draw air from the surrounding area.

After assembling this I improved the software by adding controls so that users can manually manipulate the device.

The second major component was the temperature sensor that would be necessary for controlling the heating system which would need to know the current temperature so that it could switch off when above the target temperature of on when below it. For this I used a digital thermometer chip which can be run off the arduino. The software on the arduino board reads this information and uses it to calculate the difference between the desired temperature and the current one. From this the states of heating and cooling elements can be controlled. This feedback loop regulates the system continuously.

The final component was the method for heating the space. After considering the use of heating elements I decided to use light bulbs as the source of heat. Wishing to avoid meddling with mains electricity I chose to use a remote control plug kit. I took apart the remote and worked out how it functioned so that I could circuit bend it for control by the arduino. Eventually I got the system working but during testing it inexplicably stopped functioning.

I decided to go for a more elegant yet more dangerous solution and use simple relays instead which I wired into the base of the three desk lamps for safety. Out of each lamp are two lines, when 12 volts are passed through it the relays switch on providing power to the 60 watt bulb. The three lamps were positioned around the fans pointing inwards. The lamps heat up the air within the space. A limitation of this open system is that new colder air is blown into the device and the warm air exits it through a combination of convection currents and the effect of the fans. In later versions this would be addressed with an enclosure that helps conserve air within the system whilst allowing venting to assure a quick transition between temperatures and user access.

At the end of development the separate elements were brought together and a single piece of arduino code was created to control them containing an even more simplified version of the simple analogue firmata. A reserved analogue pin was used to receive the target temperature from the processing sketch which it compares against the current temperature and controls the lamps. Due to the calculations involved I left fan control handling to the processing sketch, later versions may have greater independence from the host computer.

Final wiring diagram:

Latest Arduino Source:

#include <Firmata.h>
#include <OneWire.h>

OneWire ds(2);
byte data[12];
byte analogPin = 0;

int lamps[3];

float tTemp = 20;

void analogWriteCallback(byte pin, int value){
    if(pin == 3) {
       tTemp = (value-128)/2;
       return;
    }
    if (IS_PIN_PWM(pin)) {
        pinMode(PIN_TO_DIGITAL(pin), OUTPUT);
        analogWrite(PIN_TO_PWM(pin), value);
    }
}

void setup(){
    Firmata.setFirmwareVersion(0, 1);
    Firmata.attach(ANALOG_MESSAGE, analogWriteCallback);
    Firmata.begin(57600);
    
    pinMode(13,OUTPUT);
    
    pinMode(28,OUTPUT);
    pinMode(26,OUTPUT);
    pinMode(24,OUTPUT);

}

void loop(){
 
    controlLamps(getTemp(),tTemp);
    //Serial.println(getTemp());
    while(Firmata.available()) {
        Firmata.processInput();
    }
}


void controlLamps(float currentTemp, float targetTemp){
    float delta = currentTemp - targetTemp;
    //delta = -2;
    


    if(delta <= 1){
      digitalWrite(28,HIGH);  
      digitalWrite(26,LOW);  
      digitalWrite(24,LOW);  
    }  
    if(delta <= -2){
      digitalWrite(28,HIGH);  
      digitalWrite(26,HIGH);  
      digitalWrite(24,LOW);  
    }    
    if(delta <= -4){
      digitalWrite(28,HIGH);  
      digitalWrite(26,HIGH);  
      digitalWrite(24,HIGH);  
    }
    delay(200);
}


float getTemp(){
  byte i;
  byte present = 0;
  byte addr[8];

  if (!ds.search(addr)){
      ds.reset_search();
  }
  ds.reset();
  ds.select(addr);
  ds.write(0x44,1);
  delay(1000);
  present = ds.reset();
  ds.select(addr);    
  ds.write(0xBE);

  for ( i = 0; i < 9; i++) {
    data[i] = ds.read();
  }

  int tempdata = data[0]*1;
  return float(tempdata/2)+(float((tempdata%2)*5)/10);
}

Latest processing source:

import processing.serial.*;
import cc.arduino.*;
import controlP5.*;

ControlP5 control;
float windSpeed = 50;
float angleKnob = 0;

Arduino arduino;

XMLElement xml;
float angle = 0;

float temp = 25;

int additional = 0;

fans f;

void setup(){
  frameRate(15);
  smooth();
  size(200,200);
  f = new fans(6, 33, 100);
  
  arduino = new Arduino(this, Arduino.list()[0], 57600);

  arduino.pinMode(3, Arduino.INPUT);
  
  arduino.pinMode(7, Arduino.OUTPUT); 
  arduino.pinMode(8, Arduino.OUTPUT);
  arduino.pinMode(9, Arduino.OUTPUT);
  arduino.pinMode(10, Arduino.OUTPUT);
  arduino.pinMode(11, Arduino.OUTPUT);
  arduino.pinMode(12, Arduino.OUTPUT);

          
  control = new ControlP5(this);
  control.addSlider("windSpeed",0,100,50,5,5,100,10);
  control.addKnob("angleKnob",0,360,0,5,160,40);
}

void draw(){
  getData();
  f.update(); 
  f.display();
  
 // println(round(f.getFans()[1]));
 
  arduino.analogWrite(3, round(temp*2)+128);
  f.setStrength(windSpeed);
  angle = angleKnob;
  
  arduino.analogWrite(12, round(map(f.getFans()[0], 0,100,0,255 )) + additional);
  arduino.analogWrite(11, round(map(f.getFans()[1], 0,100,0,255 )) + additional);
  arduino.analogWrite(10, round(map(f.getFans()[2], 0,100,0,255 )) + additional);
  arduino.analogWrite(9, round(map(f.getFans()[3], 0,100,0,255 )) + additional);
  arduino.analogWrite(8, round(map(f.getFans()[4], 0,100,0,255 )) + additional);
  arduino.analogWrite(7, round(map(f.getFans()[5], 0,100,0,255 )) + additional);
}

void getData(){
  //angle = constrain(angle + random(-5,5),0,360);
  /*
  String url = "http://www.isleofwightweather.com/rss.xml";
  xml = new XMLElement(this, url);
  String desc = xml.getChildren("channel/item/description")[0].getContent();
  String[] strs = split(desc,'|');
  float angle = float(match(strs[10], "[0-9,.]+")[0] );
  temp = float(match(strs[2], "[0-9,.]+")[0] );

  */
  f.setAngle(angle);
}
class fans{
  float angle;
  float strength;
  int numFans;
  float segAng;
  float[] fans = new float[12];
  float[] posX = new float[12];
  float[] posY = new float[12];
  float angPosX;
  float angPosY;

  fans(int newNumFans, float newAngle, float newStrength){
    strength = newStrength;
    angle = newAngle;
    numFans = newNumFans;
    segAng = 360/numFans;
    for(int i = 0; i < numFans;i++){
      float X = (cos(radians(i*segAng)) * (80 - i)) + 100;
      float Y = (sin(radians(i*segAng)) * (80 - i)) + 100;
      posX[i] = X;
      posY[i] = Y;
    }
  }
  
  void display(){
    background(50,150,200);
    //angle++;
    if(angle > 360) angle = 0;
    pushStyle();
    stroke(255,0,0);
    strokeWeight(5);
    line(width/2, height/2, getAngPosX(angle,strength/2),getAngPosY(angle,strength/2));
    popStyle(); 
    for(int i = 0; i < numFans;i++){
      float X = (cos(radians(i*segAng)) * (80 - i)) + width/2;
      float Y = (sin(radians(i*segAng)) * (80 - i)) + height/2;
      pushStyle();
      fill(map(fans[i],0,100,0,255));
      ellipse(X, Y, 25, 25);
      popStyle();
    }
  }
  
  void update(){
    for(int i = 0; i < numFans;i++){
      fans[i] = 0;
      if(angle >= ((i-1)*segAng)&&(angle < ((i+1)*segAng))){
        fans[i] = map(dist(getAngPosX(angle,80),getAngPosY(angle,80),posX[i],posY[i]),80,0,0,strength);
        if((i == numFans-1)&&(fans[i-1] == 0)){
          fans[0] = 100 - fans[i];
        }
      }
    } 
  }
  
  float[] getFans(){
    return fans; 
  }
  
  void setStrength(float newStrength){
    strength = newStrength;
  }
  
  void setAngle(float newAngle){
    angle = newAngle;
  }
  
  private float getAngPosX(float a, float r){    
    return (cos(radians(a)) * r) + width/2;
  }
  private float getAngPosY(float a, float r){
    return (sin(radians(a)) * r) + height/2;
  }
}

IDAT209 – Tweetflu

Eventually I decided to use processing for the core of the software as it is more easily able to read XML and access mySQL databases.

For the map itself we used the Google maps API which provided all the features we needed. To get the data into the page, we used PHP to access the database and cycle through each row adding the code for displaying an icon at the infected account’s last know location.

More posts here.

IDAT209 – Tweetflu 2, The Retweeting

The data collection portion of this project will be written in Java because we have some experience with it and it’s more appropriate then other languages we know.

I’ve started with a small experiment using php to get the location of a tweet then list the users near it at the time which appears to work fairly well, it is now a case of porting and expanding this.

The application will have to be able to read JSON and manipulate a mySQL/SQLite database.

IDAT209 – Tweetflu

For this project we came up with the idea of using geolocated tweets in twitter to create a game on a huge scale. At the start a single user will be recorded in a database as ‘infected’. Their geotagged tweets provide a time and place which is then queried using Twitter’s search API for more people who were in the near at the time. These people also become ‘infected’ and the process is repeated for everyone in the database at regular intervals. The information stored in the database will allow us to visualise the spread of the ‘virus’. We predict that the spread will be exponential and with enough time and resources would reach most of the people that use geotagging. The game is based on Virus which is in turn based on hit/tag.

There are two halves to the project, the first is the data collection and the second is visualisation of this data. The biggest problem is the limitation on the number of queries to the twitter APIs a single client can make within an hour before being blocked and the potential for the number of accounts overwhelming the system.

IDAT204 – DoodleTash

The Idea
For this project we first looked at the newly available HTML5, CSS3 and related features. From these we selected a few and explored possible applications for them. After coming up with a number of ideas including a real-time collaborative painting application we decided to focus on the painting aspect. The idea of a simple painting application led us to the idea of doodling on newspapers. With the decline of newspaper sales we can recreate the act of doodling as a web application. We then focused this on drawing mustaches on people specifically.

The Application
We wanted the application to be as intuitively designed as possible. We utilised the File API feature available in some browsers to allow users to drag images from their computer directly onto the drawing area.

The image is loaded into the canvas and the user can then draw on the image with several pen sizes and colours. When the user is happy with their doodle they can submit it to be displayed on the site.

An interesting effect emerged when testing, the doodles people create are slightly influenced by which previous submissions people see as examples.

Team Roles
Jo Redwood – Main coder
Simon Batty – Styling
Tom Saunders – Interface and Presentation

The site can be viewed at:
http://pluxel.co.uk/doodletash/

Compatibility
Works best with Google Chrome
Firefox 3.6

http://datsimon.blogspot.com/2011/02/i-dat-204-doodletash.html

IDAT211 – The .OBJ file

In-order to create code output the terrain I had to pick and understand a 3D model file format.

I explored a number of formats by exporting a flat square from blender and loaded the resulting file into a text editor from this I found the simplest format for me to use which was Wavefront OBJ. The advantages were that it was relatively simple to adapt my terrain code to to output, it was relatively light preventing the model file becoming too large.


mtllib mat.mtl
usemtl sand
v 1 0.0 1
v 1 0.0 2
v 2 0.0 2
v 2 0.0 1
f 1 2 3 4
usemtl sand
v 1 0.0 2
v 1 0.0 3
v 2 0.0 3
v 2 0.0 2
f 5 6 7 8
usemtl sand
v 1 0.0 3
v 1 0.0 4
v 2 0.0 4
v 2 0.0 3
f 9 10 11 12
...

mtllib – ource of the materials
usemtl – material for the face
v – location of a point
f – points to join to make a face

The output portion of my code that writes the .obj file:

for (int x = 1; x < musicArray.length-1; x++) {
 for (int y = 1; y < rows-1; y++) {
 
 data[len] = "usemtl sand";  
 if(Zarray[x][y] > 3)data[len] = "usemtl grass";
 if(Zarray[x][y] > 35)data[len] = "usemtl mountain";   
 if(Zarray[x][y] > 60)data[len] = "usemtl snow";  
      
 data[len+1] = "v " + x + " " + Zarray[x][y] + " " + y;
 data[len+2] = "v " + x + " " + Zarray[x][y+1] + " " + (y+1);
 data[len+3] = "v " + (x+1) + " " + Zarray[x+1][y+1] + " " + (y+1);
 data[len+4] = "v " + (x+1) + " " + Zarray[x+1][y] + " " + y;
 data[len+5] = "f " +face+ " " +(face+1)+ " " +(face+2)+ " " +(face+3);
 face+=4;
 len+=6;
 }
 println((x/(musicArray.length/100)) + "%");
}

IDAT211 – Materials

As I was using the .OBJ format I needed to use a .MTL file for the materials. When an .OBJ file is imported into blender it also imports MTL files referenced within it.

I decided not to use textures for a number of reasons, the main one was that I wasn’t attempting to make it realistic but still recognizable.

The .MTL file I used in the final version was:

newmtl sand
Kd 1.000 1.000 0.600
illum 0
newmtl grass
Kd 0.300 0.700 0.400
illum 0
newmtl mountain
Kd 0.700 0.700 0.700
illum 0
newmtl snow
Kd 1.000 1.000 1.000
illum 0
newmtl water
Kd 0.400 0.400 1.000
illum 0