TXT ME
TXT ME receives SMS messages and displays them, one letter at a time, by raising arms with letters on the end. Our world is getting faster and faster, so this deliberately takes its time and slowly "reads out" your message. If you don't concentrate, you will miss a letter. You will find it hard to avoid trying to guess what a word will be when only a few letters have been revealed. Patience is required, it will not be rushed. If no one sends any messages, after a while it will read out TEXT ME as an invitation.
How does it work?
There is an Arduino Uno in the box, with a GSM shield. Most of the software was taken from the Arduino examples.
Video
How I Made It
I drew the parts in Inkscape (see the example above) and went to the Fablab in Berlin to cut 5 mm perspex using their 40 Watt Epilog Zing 6030 laser cutter. The cutter takes 30 cm x 60 cm sheets, so I brought enough of those along for the design plus a few extra for corrections. I moved the parts around in the drawings to make best use of the material. Peel off the top protective layer from the perspex but keep the bottom layer so that the laser doesn't scorch the bottom of the perspex. When the first parts are cut, check how well the parts go together and adjust the sizes for a good fit. I cut a hole in the baseplate for a servo, but used tie wraps for the servo on the arm. If I were to make it again, I would use 90° brackets to hold the servo on the arm. The hinges for the arms is just a piece of wire, threaded all of the way round. It might be nice to use EL wire for this, to light it up from inside.
What Were the Main Problems?
The servomotors were challenging. A 5 Euro servo with plastic gearing liked to vibrate incredibly violently when the arm was connected to it and I tried to move it. A 20 Euro servo was stable but did not cover the range of angles that I needed. A 10 Euro servo was stable, but ... over the course of a busy evening, the servo motor's precision varied, so that it started missing the "A" arm completely. On the following morning, it was back to normal. I guess that even more money is required to get a servo which has the required precision, or maybe some external sensors could offer regular auto-calibration.
The Arduino was very sensitive to my soldering iron switching on and off as the iron controls its temperature. As I couldn't do too much about that, once I had understood what was happening, I added a heartbeat LED which flashes for each pass through the main loop, to give me confidence that the Arduino was still running and not hung up, causing me to look for non-existent faults.
The Arduino Code
/* TEXT ME
This sketch, for the Arduino GSM shield, waits for an SMS message and displays it through the serial port and on Kim's TEXT ME machine.
Some code from Javier Zorzano / TD http://arduino.cc/en/Tutorial/GSMExamplesReceiveSMS */
/******************************************************************************************
* T H E C I R C U I T
*
* An external power supply is mandatory, otherwise the GSM shield doesn't work
* The GSM shield is plugged onto an Arduino Uno
* There are two servos both connected to GND and +5V* The rotary servo control line goes to pin 5
* The vertical servo control line goes to pin 10
* The ready LED goes to pin 11 via a 200 ohm resistor
*******************************************************************************************/
#include // Include the GSM library
#include // Include the servo library
#define PINNUMBER "XXXX" // PIN number for the GSM SIM card
#define rotaryPin 5 // Connect pin 5 to the rotary servo
#define verticalPin 10 // Connect pin 10 to the vertical servo
#define readyLed 11 // Connect pin 11 to LED which shows that GSM unit is connected
GSM gsmAccess; // Initialise the library instances
GSM_SMS sms;char senderNumber[20]; // Array to hold the number that an SMS is retrieved from
Servo rotaryServo; // Create servo object to control rotary servo
Servo verticalServo; // Create servo object to control vertical servo
int rotation [] = { 161, 154, 148, 142, 136, 130, 124, 118, 112, 105, 99, 92, 85, 78, 72, 66, 60, 54, 48, 42, 36, 30, 24, 19, 12, 7};
int numberOfLetters = 26;
int currentServoPosition;
int newServoPosition;
int up = 170; // Define the two extremes of vertical servo
int down = 117;
int inactivityTimer = 0;
/**************************************************************************************
* Set up
**************************************************************************************/
void setup()
{
Serial.begin (9600); // Initialise serial communication at 9600 rotary
Servo.attach (rotaryPin); // Attach the rotary servo vertical
Servo.attach (verticalPin); // Attach the finger servo boolean
notConnected = true; // GSM connection state
parkArm(); // Move to a safe space if not there already
prompt();
parkArm(); // Move to a safe space
while (notConnected) // Wait until GSM connection is established
{
digitalWrite(readyLed, LOW); // Turn the ready LED off
if (gsmAccess.begin (PINNUMBER) == GSM_READY) notConnected = false;
else
{
Serial.println ("Not connected");
delay (1000);
}
}
Serial.println ("GSM initialized"); // Confidence building message
Serial.println ("Waiting for messages");
digitalWrite (readyLed, HIGH); // Turn the ready LED on} /**************************************************************************************
* Main loop
**************************************************************************************/
void loop()
{
char c;
// If there are no SMSs available at regular intervals make a show
inactivityTimer = inactivityTimer++; // Count up for each loop
if (inactivityTimer > 300) // Approx. 1 sec per loop
{
inactivityTimer = 0; // Reset the tomer
prompt(); // Draw attention to yourself
}
// Heartbeat on LED to show that loop is still looping
digitalWrite(readyLed, LOW); // Turn the ready LED off
delay(100); // Time that LED is off
digitalWrite(readyLed, HIGH); // Turn the ready LED on
// If there is an SMS available
if (sms.available())
{
inactivityTimer = 0; // If message then reset inactivity timer
Serial.println("Message received from:");
// Read message bytes and print them
while(c=sms.read())
{
raiseLetterArm (c); // "Print" to perspex arms
}
sms.flush(); // Delete message from modem memory
} delay(1000);
}
/**************************************************************************************
* Prompt anyone nearby
**************************************************************************************/
void prompt()
{
raiseLetterArm (84); // "Print" TEXT ME to perspex arms
raiseLetterArm (69);
raiseLetterArm (88);
raiseLetterArm (84);
raiseLetterArm (77);
raiseLetterArm (69);
}
/**************************************************************************************
* Put the arm out of harm's way
**************************************************************************************/
void parkArm()
{
rotaryServo.attach (rotaryPin); // Attach the rotary servo
newServoPosition = 180; // Park where the arm can do no harm when switched on/off
currentServoPosition = 9; // Make sure that it is clear that a move is needed
rotateServo(); // Move the rotary servo
delay(2000); // Wait for the servo to get there
rotaryServo.detach (); // Keep things quiet
}
/**************************************************************************************
* Raise the letter arm specified
**************************************************************************************/
void raiseLetterArm (int charEntered)
{
rotaryServo.attach (rotaryPin); // Attach the servo on pin
// 'A' is 65 'a' is 97 'Z' is 90
if ((charEntered < 65) || ((charEntered > 91) && (charEntered < 97)) || (charEntered > 123))
{
// Then is not A to Z so do nothing - wait a bit
}
else
{
// Make lower case into upper case
if (charEntered > 96)
{
charEntered = charEntered - 32;
}
if ((charEntered > 64)&&(charEntered < 91))
{
newServoPosition = rotation [charEntered - 65]; // Set where to turn the arm to
rotateServo();
}
// Show what was sent:
Serial.print("You sent me: \'");
Serial.write(charEntered);
Serial.print("\' ASCII Value: ");
Serial.println(charEntered);
pressDown ();
}
rotaryServo.detach (); // Stop random movements}
/*********************************************************************************
* Push the finger down - make sure it is up before returning
!*********************************************************************************/
void pressDown()
{
verticalServo.attach (verticalPin); // Attach the finger servo
delay(500);
for (int i= 0; i < up-down; i++)
{
verticalServo.write (up-i); // Finger down one degree at a time
delay(10);
}
delay(2000);
for (int i= 0; i < up-down; i++)
{
verticalServo.write (down +i); // Finger up one degree at a time
delay(10);
}
delay(500);
verticalServo.detach (); // Stop random movements}
/*********************************************************************************
* Move the arm around - make sure that the finger is up before using it*********************************************************************************/
void rotateServo()
{
int difference;
int noOfSlowSteps = 5; // No of degrees to move slowly was 5
int waitForServo = 20; // Do not reduce too much or it misses steps
verticalServo.attach (verticalPin); // Attach the finger servo
verticalServo.write (up); // Make sure it's out of the way
verticalServo.detach (); // Detach
difference = newServoPosition - currentServoPosition;
if (difference > noOfSlowSteps)
{
rotaryServo.write (newServoPosition - noOfSlowSteps); // Quickly move
currentServoPosition = newServoPosition - noOfSlowSteps; // Note new position }
if (difference < -1*noOfSlowSteps)
{
rotaryServo.write (newServoPosition + noOfSlowSteps); // Quickly move
currentServoPosition = newServoPosition + noOfSlowSteps; // Note new position
}
difference = newServoPosition - currentServoPosition; // Recalculate difference if (difference > 0)
// Then slowly increase current position until difference is 0
{
for (int i = 0; i < difference+1; i++)
{
rotaryServo.write (currentServoPosition + i);
delay (waitForServo);
}
}
else // Then slowly decrease current position until difference is 0
{
for (int i = 0; i < (-1*difference)+1; i++)
{
rotaryServo.write (currentServoPosition - i);
delay (waitForServo);
}
}
currentServoPosition = newServoPosition; // Remember where arm is}
/**********************************************************************************
* Windscreen wiper test (commented out)
**********************************************************************************/
/* for (letter=0; letter < numberOfLetters; letter++)
{
newServoPosition = rotation [letter]; // Set where to turn the arm to
rotateServo();
Serial.print("rotation [letter] ");
Serial.println(rotation [letter]);
}
delay(5000);
for (letter=numberOfLetters; letter +1 > 0; letter--)
{
newServoPosition = rotation [letter]; // Set where to turn the arm to
rotateServo();
Serial.print("rotation [letter] ");
Serial.println(rotation [letter]);
}
delay(5000); */
/**********************************************************************************
/* To calibrate the rotary servo (commented out) - need to set potpin
**********************************************************************************/
/*
val = analogRead(potpin); // reads the value of the potentiometer (value between 0 and 1023)
val = map(val, 0, 1023, 0, 179); // scale it to use it with the servo (value between 0 and 180)
rotaryServo.write(val); // sets the servo position according to the scaled value
delay(15); // waits for the servo to get there
// print out the value you read:
Serial.println(val); */