Thursday, December 13, 2007

Redial Final:::Jabberjockeys

INTRODUCTION


Jabberjockeys consist of a pair of underwear (one male, one female) which discreetly inform a partner when the other gets aroused. By sensing subtle changes in temperature, moisture and pressure the undergarments detect arousal. The underwear automatically notifies the partner by activating vibrating motors sewn into the fabric of their underwear, thus enabling them to discreetly share their heightened emotions.

Sexual arousal is a personal state; but one that is most satisfyingly experienced with a partner. If a person becomes aroused in a public setting, it is difficult to notify their partner of their arousal without creating a disturbance. This project aims to solve this dilemma by providing a discreet and effective way for couples to share their spontaneous and private compulsions.

Jabberjockeys employs a whole hodgepodge of languages in getting two pairs of inanimate underwear to communicate in any sort of articulate manner. These languages are, to wit: Asterisk, Arduino, Java, Python, and good old-fashioned HTML.


(arranging circuits for the panties and boxer briefs)



ASTERISK



I will begin with the Asterisk dialplan, which is of most interest to people in Shawn Van Every's Redial class.

Essentially, this code does two major things:

1) It reads users' keypresses and sends this information to the woman's phone. Since her panties have four motors in them, a user will press the motor number (1-4) followed by the intensity of vibration (0-4). If a user wants to turn all of the motors on or off at once, they can press 5 followed by the intensity level (0-4).

2) It runs an EAGI script that will analyze the user's voice and cause the panties to vibrate accordingly. Quiet encouragement will cause the panties to purr subtly. More vociferous input will cause the panties to hum enthusiastically. Silence will make them stop altogether.

The Asterisk dialplan is a follows:



exten => s,1,Wait(2);
exten => s,2,Answer();
exten => s,3,Playback(beep);
exten => s,4,WaitExten(300);
exten => 10,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=A"); This CURLs a Python script on the phone.
exten => 10,2,Goto(mtf245,s,3);
exten => 11,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=B");
exten => 11,2,Goto(mtf245,s,3);
exten => 12,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=C");
exten => 12,2,Goto(mtf245,s,3);
exten => 13,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=D");
exten => 13,2,Goto(mtf245,s,3);
exten => 14,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=E");
exten => 14,2,Goto(mtf245,s,3);
exten => 20,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=F");
exten => 20,2,Goto(mtf245,s,3);
exten => 21,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=G");
exten => 21,2,Goto(mtf245,s,3);
exten => 22,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=H");
exten => 22,2,Goto(mtf245,s,3);
exten => 23,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=I");
exten => 23,2,Goto(mtf245,s,3);
exten => 24,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=J");
exten => 24,2,Goto(mtf245,s,3);
exten => 30,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=K");
exten => 30,2,Goto(mtf245,s,3);
exten => 31,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=L");
exten => 31,2,Goto(mtf245,s,3);
exten => 32,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=M");
exten => 32,2,Goto(mtf245,s,3);
exten => 33,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=N");
exten => 33,2,Goto(mtf245,s,3);
exten => 34,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=O");
exten => 34,2,Goto(mtf245,s,3);
exten => 40,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=P");
exten => 40,2,Goto(mtf245,s,3);
exten => 41,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=Q");
exten => 41,2,Goto(mtf245,s,3);
exten => 42,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=R");
exten => 42,2,Goto(mtf245,s,3);
exten => 43,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=S");
exten => 43,2,Goto(mtf245,s,3);
exten => 44,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=T");
exten => 44,2,Goto(mtf245,s,3);
exten => 50,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=U");
exten => 50,2,Goto(mtf245,s,3);
exten => 51,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=V");
exten => 51,2,Goto(mtf245,s,3);
exten => 52,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=W");
exten => 52,2,Goto(mtf245,s,3);
exten => 53,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=X");
exten => 53,2,Goto(mtf245,s,3);
exten => 54,1,System(curl "https://fargo.mymobilesite.net/lob/lob.py?name=Y");
exten => 54,2,Goto(mtf245,s,3);
exten => 6,1,EAGI(/home/mtf245/asterisk_agi/runMe.sh); Pointing to shell script with EAGI
exten => 6,2,Goto(mtf245,s,3);
exten => i,n,Goto(mtf245,s,3); Loops back for next input
exten => t,n,Goto(mtf245,s,3);




(extensive research went into choosing vibrator locations)



JAVA EAGI


The Java EAGI script called from the dialplan is essentially the same code as that in Shawn's tutorial, with a couple subtle changes. The script essentially listens to the audio stream coming from the users phone and analyzes its volume. A louder average will make the panties vibrate harder, while a softer volume makes the vibrate gentler. My additions are as follows:

1) For smoother numbers, and because the Python script on the woman's phone can only handle one request every few seconds, I am re-averaging the numbers we get. I also upped the frequency it reads from the audio stream to intervals of 100 milliseconds, for greater accuracy.

2) Every 6 seconds or so, Java makes an HTTP request to the phone, sending it data that corresponds to the intensity of the user's voice. While we simply used a CURL to do this in Asterisk, we are using a regular HTTP request with GET data, here.

The additional code is as follows:



class ReadingThread implements Runnable
{
public void run()
{
try
{
int cnt = 0;
int loopcnt = 0;
long totalavg = 0;
int totaltotal;
while (audioLoop)
{
byte[] inputBytes = new byte[8000];
int readBytes = 0;

readBytes = ais.read(inputBytes);

int avg = 0;
long total = 0;
for (int i = 0; i < readBytes; i++)
{
total += unsignedByteToInt(inputBytes[i]);
}
avg = (int)(total/readBytes);

// now re-average
loopcnt++;
totalavg+=avg;

if (loopcnt > 98) {
totaltotal = (int)(totalavg/100);
char param = 'U';
if (totaltotal < 8) {
param ='U';
}
else if (totaltotal < 15) {
param ='V';
}
else if (totaltotal < 22) {
param ='W';
}
else if (totaltotal < 30) {
param ='X';
}
else {
param ='Y';
}

try {
// script we are hitting is https://fargo.mymobilesite.net/lob/lob.py?name=XXX
// Construct data
client.send("******** TOTAL AVG: "+totaltotal);
java.net.URL url = new java.net.URL("https://fargo.mymobilesite.net/lob/lob.py?name="+param);
HttpURLConnection m_con = (HttpURLConnection)url.openConnection();
System.out.println(m_con.getResponseMessage());
m_con.disconnect();

} catch (Exception e) {
client.send("ERROR MAKING HTTP REQUEST");
}

// reset loop
loopcnt = 0;
totalavg = 0;
totaltotal = 0;

}

/* for testing
String stemp = Integer.toString(avg);
fsos.write(stemp + " ");
if (cnt%10 == 0)
{
fsos.write("\n");
fsos.flush();
}
*/

Thread.sleep(100);
}
}
catch (IOException ioe)
{
System.out.println("Bad bad");
ioe.printStackTrace();
}
catch (InterruptedException ie)
{

}
}

int unsignedByteToInt(byte b) {
return Math.abs((int)b);
}
}




(relentless user-testing puts our panties to the test)



PYTHON


In the above dialplan and Java EAGI, we are sending data to a Python script on the woman's phone. The woman's phone is a Nokia N-95, which happens to be running its own Apache web server. Nokia has dubbed this Mobile Web Server, and it makes all sorts of projects like this really easy to do.

The Python script I wrote sits in a public directory on this server, and can be hit from any browser. So you could actually just manipulate the panties from your web browser, if you wanted. The URL is:
https://fargo.mymobilesite.net/lob/lob.py?name=XXX
where XXX is the motor/level desired for vibration.

The Python script connects to a BlueSmirf bluetooth device that has been embroidered into panties. After making a connection, it can be re-run as many times as you want with new GET data. When the script gets data, it relays this data on to the panties, where an Arduino Lilypad microcontroller has been pre-programmed to interpret this data and vibrate the motors accordingly.

The Python script is as follows:



MAIN_TMPL = "plain.psp"
FORM_TMPL = "form"

# define request handler
def handler(req):
hrh = HRHandler()
result = hrh.handler(req)
del hrh
return result

class HRHandler(object):
def __init__(self):
pass

def handler(self, requ):
self.req = self.parse_args(requ)
args = self.req.get_vars
try:
name = str(args['name'])
except KeyError:
name = None
title = ''
if name != None:
title = 'You just entered, ' + name
global sock
sock.send(name)
else:
title = 'Enter a number'
global sock
sock=socket.socket(socket.AF_BT,socket.SOCK_STREAM)
SparkAddress = "00-a0-96-1b-27-46"
target=(SparkAddress,1)
sock.connect(target)
content = ''
contentFromFile = open((apache.server_root() + '/htdocs/lob/' + TMPL_DIR + '/' + FORM_TMPL+'.html'), 'r')
temp = contentFromFile.readlines()
contentFromFile.close()
for line in temp:
content += line + '\n'

requ.content_type = 'text/html; charset=UTF-8'
requ.html_head = ''
requ.html_body_attr = ''

fname = TMPL_DIR + '\\' + MAIN_TMPL
template = psp.PSP(requ, filename = fname)
template.run({'content':content, 'html_head':requ.html_head, 'html_body_attr':requ.html_body_attr, 'title':title})

del template
return apache.OK

def parse_args(self, req):
req.get_vars = {}
try:
if req.args:
temp = req.args.split('&')
for arg in temp:
arg = arg.split('=')
req.get_vars[arg[0]] = arg[1]
except IndexError:
pass
return req

def sendbt(val):
global sock
sock.send(val)


The "form" file included at the beginning of the script is actually an HTML template with a form for inputing and submitting data from a browser.



(the Lilypad Arduino is connected to vibrators)



ARDUINO


The Arduino code is the heart and soul of Jabberjockeys. It assigns digital pins to their respective motors, then waits for input from the BlueSmirf. Then it turns on and off certain motors with a long, inelegant set of conditional statements. You can ignore the variables for galvanic skin response, since we haven't yet added this feature:


int galvanicPin = 0; // select the input pin for the galvanic
int galvanicVal = 0; // variable to store the value coming from the sensor
int galvanicThresh = 700;
int motorPin[] = {6,9,10,11};
int motorValue = 1024;
int potPin = 5; //for testing
int potValue = 1024;
char inByte = ' ';
//int connectionStatus = 0;
int motor1Status = 0;
int motor2Status = 0;
int motor3Status = 0;
int motor4Status = 0;

void setup() {
Serial.begin(57600);
pinMode(6, OUTPUT);
pinMode(9, OUTPUT);
pinMode(10, OUTPUT);
pinMode(11, OUTPUT);
}

void loop() {
readRespondSensor();
// setMotorValue();
buttonCheck();
// printValues();
delay(100);
}

void readRespondSensor(){
galvanicVal = analogRead(galvanicPin); // read the value from the sensor
if (galvanicVal > galvanicThresh){
// Serial.println("THIS IS WHERE WE CALL THE SCRIPT FOR THE OTHER UNDERWEAR");
}
}

void buttonCheck() {
if (Serial.available()) {
inByte = Serial.read();
if (inByte == 'A') {
digitalWrite(motorPin[0],LOW); // turn off motor
motor1Status = 0;
}
else if (inByte == 'B') {
analogWrite(motorPin[0],64); // determine voice levels and vary strenth of motor
motor1Status = 1;
}
else if (inByte == 'C') {
analogWrite(motorPin[0],128); // determine voice levels and vary strenth of motor
motor1Status = 2;
}
else if (inByte == 'D') {
analogWrite(motorPin[0],192); // determine voice levels and vary strenth of motor
motor1Status = 3;
}
else if (inByte == 'E') {
analogWrite(motorPin[0],255); // determine voice levels and vary strenth of motor
motor1Status = 4;
}
else if (inByte == 'F') {
digitalWrite(motorPin[1],LOW); // turn off motor
motor2Status = 0;
}
else if (inByte == 'G') {
analogWrite(motorPin[1],64); // determine voice levels and vary strenth of motor
motor2Status = 1;
}
else if (inByte == 'H') {
analogWrite(motorPin[1],128); // determine voice levels and vary strenth of motor
motor2Status = 2;
}
else if (inByte == 'I') {
analogWrite(motorPin[1],192); // determine voice levels and vary strenth of motor
motor2Status = 3;
}
else if (inByte == 'J') {
analogWrite(motorPin[1],255); // determine voice levels and vary strenth of motor
motor2Status = 4;
}
else if (inByte == 'K') {
digitalWrite(motorPin[2],LOW); // turn off motor
motor3Status = 0;
}
else if (inByte == 'L') {
analogWrite(motorPin[2],64); // determine voice levels and vary strenth of motor
motor3Status = 1;
}
else if (inByte == 'M') {
analogWrite(motorPin[2],128); // determine voice levels and vary strenth of motor
motor3Status = 2;
}
else if (inByte == 'N') {
analogWrite(motorPin[2],192); // determine voice levels and vary strenth of motor
motor3Status = 3;
}
else if (inByte == 'O') {
analogWrite(motorPin[2],255); // determine voice levels and vary strenth of motor
motor3Status = 4;
}
else if (inByte == 'P') {
digitalWrite(motorPin[3],LOW); // turn off motor
motor4Status = 0;
}
else if (inByte == 'Q') {
analogWrite(motorPin[3],64); // determine voice levels and vary strenth of motor
motor4Status = 1;
}
else if (inByte == 'R') {
analogWrite(motorPin[3],128); // determine voice levels and vary strenth of motor
motor4Status = 2;
}
else if (inByte == 'S') {
analogWrite(motorPin[3],192); // determine voice levels and vary strenth of motor
motor4Status = 3;
}
else if (inByte == 'T') {
analogWrite(motorPin[3],255); // determine voice levels and vary strenth of motor
motor4Status = 4;
}
else if (inByte == 'U') {
analogWrite(motorPin[0],LOW); // determine voice levels and vary strenth of motor
analogWrite(motorPin[1],LOW); // determine voice levels and vary strenth of motor
analogWrite(motorPin[2],LOW); // determine voice levels and vary strenth of motor
analogWrite(motorPin[3],LOW); // determine voice levels and vary strenth of motor
}
else if (inByte == 'V') {
analogWrite(motorPin[0],64); // determine voice levels and vary strenth of motor
analogWrite(motorPin[1],64); // determine voice levels and vary strenth of motor
analogWrite(motorPin[2],64); // determine voice levels and vary strenth of motor
analogWrite(motorPin[3],64); // determine voice levels and vary strenth of motor
}
else if (inByte == 'W') {
analogWrite(motorPin[0],128); // determine voice levels and vary strenth of motor
analogWrite(motorPin[1],128); // determine voice levels and vary strenth of motor
analogWrite(motorPin[2],128); // determine voice levels and vary strenth of motor
analogWrite(motorPin[3],128); // determine voice levels and vary strenth of motor
}
else if (inByte == 'X') {
analogWrite(motorPin[0],192); // determine voice levels and vary strenth of motor
analogWrite(motorPin[1],192); // determine voice levels and vary strenth of motor
analogWrite(motorPin[2],192); // determine voice levels and vary strenth of motor
analogWrite(motorPin[3],192); // determine voice levels and vary strenth of motor
}
else if (inByte == 'Y') {
analogWrite(motorPin[0],255); // determine voice levels and vary strenth of motor
analogWrite(motorPin[1],255); // determine voice levels and vary strenth of motor
analogWrite(motorPin[2],255); // determine voice levels and vary strenth of motor
analogWrite(motorPin[3],255); // determine voice levels and vary strenth of motor
} else {
for (int i=0; i <= 3; i++){
digitalWrite(motorPin[i], LOW);
}
}
}
}


void setMotorValue () {
//potValue = analogRead(potPin);
motorValue = potValue;
}

void printValues(){
Serial.print("galvanicVal = ");
Serial.print(galvanicVal);
Serial.println();
// Serial.print("connectionStatus = ");
// Serial.print(connectionStatus);
Serial.println();
Serial.print("motor1Status = ");
Serial.print(motor1Status);
Serial.println();
Serial.print("motor2Status = ");
Serial.print(motor2Status);
Serial.println();
}

No comments: