Those jumbo screens at concerts that display your text messages can be a lot of fun. Wouldn't it be great if you could have the same thing for your own parties or social gatherings? Well I tested that question. As it turns out, guests love it


I've always loved the idea of sending messages to the big screens you usually find at concerts and other large events. I figured it could be scaled down, while still being just as entertaining. It may not have one of those spiffy 5-digit phone numbers, but the Google Voice number that it uses works just as well - especially since it also works as a vanity number for my handle :)

After testing the sign at a party held at home, I noticed that people really seemed to enjoy it. One part of it that led to people enjoying it was the fact that the messages were anonymous. While it would have been really easy to make the sign show who the message was from, if theres one thing I've learned from the internet, it's that people love being anonymous. Leaving a message that your peers can see, without knowing who wrote it, will always invoke the most curious behavior.

With any anonymity, comes that behavior. I already knew that, so I took the liberty to set up some bad-word filters to start replacing parts of messages. You can see that list in one of the scripts below - which is most likely causing Google to flag my page as inappropriate, but I digress... Not only did this keep the board clean, but it was even more entertaining to people to see such 'odd' words come up in their messages. It only kept people texting more and more to see what other replacements they could figure out.

All in all, it really seems this experiment was a success. People loved it, and I loved setting it up. I highly recommend you do the same for your next party or other event. As long as your guests have their cell phones, they're definitely going to be tempted to send something.

Technical Details

The Party Scroller makes use of several servers and several scripts to get a message from your phone to the sign. Check out the flowchart for a quick visual reference. Basically, heres the gist of it: The user sends a text message from their phone to my Google Voice number, which will then send an email out to a special email address which pipes the email into a PHP script. The PHP script will extract the necessary data from the email, and put it into a database. Meanwhile, another computer is continually checking the database for a new message every 20 seconds. When it sees a new one, it will grab it from the database and display it on the board.


Want to set something like this up yourself? You will need the following items:

Step 1: Creating the Email Scripts

Download this PHP email class, which was written by Arvin Castro. It can be found on his site, Upload this class to your own web host. The following script uses it, so place it wherever you want the rest of your code.

Edit the script below. Put in your database name, password, host, etc, then upload it to the same directory you put the above email class in. This script is the script that emails will get sent to. It is going to analyze the contents of the emails and put them into your database.


$realEmailAddress = ""; //Your actual email address. NOT the google voice one.
$saveIndividualEmails = 0; //Set to 1 to log every email received to a text file in this directory. Useful for debugging
$saveLogFile = 0; //Set to 1 to save incoming stats to a log file in this directory. Useful for debugging

$dbHost = "localhost"; //Wheres your db host? Usually localhost
$dbName = "partyScroller"; //Which database?
$dbUser = "brianGaut"; //Database username that has access to above database
$dbPass = "randomPassWord"; //Db users password?

require_once '';

//Time that the script received the email
$emailTime = time();

// Parse incoming email from STDIN
$email = email::parseSTDIN();

if($saveIndividualEmails == 1){
	//Save the RAW email to a file
	$fh = fopen($emailTime . ".txt", 'w');
	fwrite($fh, $email->raw);

// Check if verification email was received. Will in reality only be used one time
	if($email->getSenderEmail() == '') {
		// Forward verification email to your email address
		mail($realEmailAddress, 'Google Email Verification', $email->getTextContent(), 'from: '.$email->getRecipientEmail());

//Parse the email FROM url for GV specific variables
	$parsedSenderUrl = parse_url('http://'.$email->getSenderEmail());

// Check if the email came from Google Voice
	if('' == $parsedSenderUrl['host']) {
		$isFromGv = 1;
		$isFromGv = 0;

// Splice the sender's email address to get the details
// format:
	list($recipient, $sender, $messageid) = explode('.', $parsedSenderUrl['user']);
	$message = trim($email->getTextContent());
// $messageid  - message id used by GVoice for threading messages. This is NOT unique!!!
// $recipient  - your google voice number
// $sender     - phone number of sender
// $message    - content of the SMS message

//Try to get rid of that stupid GV generated tagline. the 'Sent using SMS-to-email' line at the end
	$txtToFind = "\n\n--\nSent";
	$start = strpos($message, $txtToFind);
	if($start > 0){
		$message = substr($message,0,$start);

//Lets put that info into the database

	$conn = mysql_connect($dbHost , $dbUser ,$dbPass );
	$result = mysql_select_db($dbName , $conn);

	$messageId = mysql_real_escape_string(trim($messageid));
	$toNumber = mysql_real_escape_string(trim($recipient));
	$fromNumber = mysql_real_escape_string(trim($sender));
	$smsText = mysql_real_escape_string(trim($message));

	$sql = ("
			(`username`, `time`, `message`)

		$latestRow = mysql_insert_id();

if($saveLogFile == 1){
//Log the details into the logfile
$data = ("
	Time: " . $emailTime . "
	MID: " . $messageid . "
	To: ". $recipient . "
	From: " . $sender . "
	MSG: " . $message . "
	Inserted Row ID: ".$latestRow."
	SQL: ".$sql."

	$fh = fopen("log.txt", 'a+');
	fwrite($fh, $data);


Note that this script was adapted from a similar one also written by Arvin Castro. His original script can be found on his article about receiving incoming text messages from Google Voice, which helped me out a lot on getting this LED sign working

Step 2: Designing the Database

By default, the script above uses a database table called 'txtMessages'. You can run the following SQL to create that table.

CREATE TABLE txtMessages (
	`message` VARCHAR(512),
	`username` VARCHAR(32),
	`time` INT(11),
	`last_displayed` INT(11)

Step 3: Configuring Email

These instructions will show you how to create a new email address and configure it to send all incoming emails to the email parsing script that we uploaded earlier. It assumes that you are using a web host which gives you access to cPanel.

Step 4: Configuring Google Voice

Step 5: Controlling the L.E.D. Sign

We now need to set up a script that will send messages to the LED sign. This script below is specifically for a Beta Brite LED Sign. If you are using another sign, all is not lost. You will simply need to change this script, and this script only.


my $message = $ARGV[0];
my $message_type = $ARGV[1];

my $serial_port_to_use = "/dev/ttyS0";

($second, $minute, $hour, $dayOfMonth, $month, $yearOffset, $dayOfWeek, $dayOfYear, $daylightSavings) = localtime();
if(length($minute) < 2){
	$minute = "0" . $minute;
my $theTime = "$hour:$minute";


	my $NUL            = "\0\0\0\0\0\0";       # NUL - Sending 6 nulls for wake up sign and set baud neg.
	my $SOH            = "\x01";               # SOH - Start of header
	my $TYPE           = "Z";                       # Type Code - Z = All signs. See Protocol doc for more info
	my $SIGN_ADDR      = "00";                      # Sign Address - 00 = broadcast, 01 = sign address 1, etc
	my $STX            = "\x02";               # STX - Start of Text character
	# These are other useful variables
	my $ETX            = "\x03";            # End of TeXt
	my $ESC            = "\x1b";            # Escape character
	my $EOT            = "\004";            # End of transmission
	# We group some of the variables above to make life easy.
	# This leaves us 2 type of init strings we can add to the front of our frame.
	my $INIT="$NUL$SOH$TYPE$SIGN_ADDR$STX";         # Most used.
	my $INIT_NOSTX="$NUL$SOH$TYPE$SIGN_ADDR";               # Used for nested messages.
	my $WRITE ="A"; # Write TEXT file
	my $LABEL = "A"; #File A
	my $DPOS=" "; # Leave as a space for BetaBrite one line sign
	my $ROTATE ="A"; # Message travels right to left.
	my $COMPRESSED ="t"; # Skinny letters that rotate as above
	my $HOLD ="b"; # Message remains stationary.
	my $FLASH ="c"; # Message remains stationary and flashes

	my $RED = chr(28) . "1";
	my $GREEN = chr(28) . "2";
	my $YELLOW = chr(28) . "3";
	my $RED_DIM = chr(28) . "4";
	my $GREEN_DIM = chr(28) . "5";
	my $YELLOW_DIM = chr(28) . "6";
	my $ORANGE = chr(28) . "7";
	my $YELLOW_LIGHT = chr(28) . "8";
	my $RAINBOW = chr(28) . "9";
	my $RAINBOW_MOVING = chr(28) . "A";
	my $RAINBOW_FADE = chr(28) . "B";
	my $RAINBOW_CYCLE = chr(28) . "C";


$message =~ s/\[G\]/$GREEN/gi;
$message =~ s/\[GD\]/$GREEN_DIM/gi;
$message =~ s/\[R\]/$RED/gi;
$message =~ s/\[RD\]/$RED_DIM/gi;
$message =~ s/\[Y\]/$YELLOW/gi;
$message =~ s/\[YD\]/$YELLOW_DIM/gi;
$message =~ s/\[YL\]/$YELLOW_LIGHT/gi;
$message =~ s/\[O\]/$ORANGE/gi;
$message =~ s/\[R1\]/$RAINBOW/gi;
$message =~ s/\[R2\]/$RAINBOW_MOVING/gi;
$message =~ s/\[R3\]/$RAINBOW_FADE/gi;
$message =~ s/\[R4\]/$RAINBOW_CYCLE/gi;

$message =~ s/\[T\]/$theTime/gi;

	open(BETABRITE, ">" . $serial_port_to_use);
	#set a message

####print BETABRITE "$NUL" . "$SOH" . "$TYPE" . "$SIGN_ADDR" .  "$STX" . "E\$" . "$EOT";

####print BETABRITE "$NUL" . "$SOH" . "$TYPE" . "$SIGN_ADDR" .  "$STX" . "E(0" . "$EOT";

####Puts up a message

if($message_type eq "f"){
if($message_type eq "h"){

print BETABRITE "$INIT" . "AA" . "$ESC" . "$DPOS" . "$MODE" . "$GREEN" . "$message" . "$EOT";

#print "Message has been sent to board: " . $message . "\n";

Save the above perl script as '' on the computer that your LED sign is plugged into. Give it execute rights. (chmod +x

Step 6: Party Scrolling

The file script is a php script that will be run via command line and continuously loop, looking for new messages on the database. If it sees one, it will do some word filtering, then pass the message to the above perl script, which actually puts it on the sign.

$rightNow = time();

This script will scroll text messages onto a Betabrite LED board.
It obtains the messages from a remote database. The messages are
put into the database from an email piped into another php script.
The text messages get sent in an email by Google Voice.

Written by Brian Gaut -

//Show messages up to X seconds old. Make sure to adjust for server <-> server time differences, if any
	$debugIt = 1; //1 = lots of information on command line, 0 = only message changes

//Show messages up to X seconds old. Make sure to adjust for server <-> server time differences, if any
	$ageLimit = 60*15; //15 mins

//How often to check the database and update the sign
	$updateEvery = "20"; //Every 20 seconds

//Where is the script that sends messages to the LED sign?
	$pathToLedScript = "/home/brianGaut/myScripts/";

//Database Server details
	$mainServer = "";
	$mainUser = "dbUserName";
	$mainPassword = "dbUserPassword";
	$mainDatabase = "partyScroller";

function connect(){
	global $conn; //Only one persistant connection. No need for db class most likely
	global $mainServer;
	global $mainUser;
	global $mainPassword;
	global $mainDatabase;

	//Use a persistant connection
	$conn = mysql_pconnect($mainServer, $mainUser,$mainPassword)
		or die("MySQL -Connection- FAILED!");

	$result = mysql_select_db($mainDatabase, $conn)
		or die("MySQL -db Selection- FAILED!");
	return $conn;

function mysqlit($sql){
	global $conn;
		$conn = connect();
	$result = mysql_query($sql, $conn);
	return $result;

function dbg($x){
	global $debugIt;
	if($debugIt == 1){
		echo $x . "\n";

//Dont touch these. (Init'ing some vars)
	$lastUpdate = 0;
	$timeLimitCutoff = $rightNow - $ageLimit;
	$previousMessage = ''; //For comparing

function sendToSign($txt,$from){
	global $previousMessage;

	//Lets clean up the text before we send it to the sign
	$message = ($txt);

	$message = str_replace("\$", "\\$", $message);
	$message = str_replace("\\'", "'", $message);
	$message = str_replace("\"", "'", $message);
	$message = str_replace("#", "\#", $message);
	$message = str_replace("`", "'", $message);

	$message = str_ireplace("f u c k e r","G e r b i l e r", $message);
	$message = str_ireplace("gtfo","Get outta my house", $message);
	$message = str_ireplace("pudi","Jello", $message);
	$message = str_ireplace("puddi","Jello", $message);
	$message = str_ireplace("fucker","Gerbiler", $message);
	$message = str_ireplace("fucking","Gerbiling", $message);
	$message = str_ireplace("fuck","Gerbil", $message);
	$message = str_ireplace("asshole","Nugget Hole", $message);
	$message = str_ireplace("dumbass","Nugget Hat", $message);
	$message = str_ireplace(" ass ","Nugget", $message);
	$message = str_ireplace(" ass.","Nugget", $message);
	$message = str_ireplace("bitches","Noodles", $message);
	$message = str_ireplace("bitching","Noodling", $message);
	$message = str_ireplace("bitchin","Noodlin", $message);
	$message = str_ireplace("bitch","Noodle", $message);
	$message = str_ireplace("cunt","Chipootle", $message);
	$message = str_ireplace("twat","Chipootle", $message);
	$message = str_ireplace("clit","Chipootle", $message);
	$message = str_ireplace("cock","Sock Puppet", $message);
	$message = str_ireplace("dick","Sock Puppet", $message);
	$message = str_ireplace("shit","Playdough", $message);
	$message = str_ireplace("tits","Whoppers", $message);
	$message = str_ireplace("titts","Whoppers", $message);
	$message = str_ireplace("titties","Whoppers", $message);
	$message = str_ireplace("boobs","Whoppers", $message);
	$message = str_ireplace("boobies","Whoppers", $message);
	$message = str_ireplace("jugs","Whoppers", $message);
	$message = str_ireplace("jugz","Whoppers", $message);
	$message = str_ireplace("knockers","Whoppers", $message);
	$message = str_ireplace("nigger","Hot Carl", $message);
	$message = str_ireplace("niga","Hot Carl", $message);
	$message = str_ireplace("nigga","Hot Carl", $message);
	$message = str_ireplace("faggot","(...Im just some ignorant kid who thinks calling somebody gay makes me cool...)", $message);
	$message = str_ireplace("fag","(...Im just some ignorant kid who thinks calling somebody gay makes me cool...)", $message);

	if($message != $previousMessage){
		if($message == " "){
			echo "[Blanking Sign]\n";
			echo "Sending to sign via [".$from."]: " . $message . "\n";

		`perl "$pathToLedScript" "$message"`;

		dbg("This was the same as the last message! Not doing anything");

	$previousMessage = $message;


//This is the main loop that will continue until the script is killed
	while(($rightNow - $lastUpdate) > $updateEvery ){
		dbg("Its been ".$updateEvery." seconds! Looking for messages larger than " . $timeLimitCutoff);

		//Update our time vars
		$lastUpdate = $rightNow;
		$timeLimitCutoff = $rightNow - $ageLimit;

		$sql = ("
				`time` > '".$timeLimitCutoff."'
				`last_displayed` ASC
			LIMIT 0,1
		if(!$result = mysqlit($sql)){
			//it didnt work! db is probably down.
			dbg("Could not connect to database. Will try again later");

		if(!$row = mysql_fetch_array($result)){
			//If the result is empty, then just blank out the sign
			dbg("No messages to display");
			sendToSign(" ","Nobody");
			dbg("Found a message to display");
			$txtMessage = $row['message'];

			//Is it a new message?
			if($row['last_displayed'] == "0"){
				dbg("Its brand new! Now pinging the notification page");

				//The below webpage is actually a page that simply plays
				//a certain WAV file on the computers speakers.
				exec("wget -O - --quiet > /dev/null &");

			//before we send the text to the sign, lets update the db row, saying we did it
			$updateSql = ("UPDATE `txtMessages` SET `last_displayed` = '".$rightNow."' WHERE `m_id` = '".$row['m_id']."' LIMIT 1;");
			//whoops.... maybe i should have used a class, heh

			dbg("Updating last_displayed data for this message");
			if(!$updateResult = mysqlit($updateSql)){
				echo "ERROR - Could not update last_displayed status for this message\n";
			unset($updateResult); //advice for others: use a db class! :P

			//Ok, send the message to the sign function


	$rightNow = time(); //Latest time
	//Sleep for a sec, that way we dont overload our computer
	// with time() updates. (They totally add up quick)

//And thats that :)


Save this file as 'partyScroller.php' and as with the previous script, give it execute rights.

Step 7: RUN IT!

To run your LED sign updater, just go into a shell or command prompt and run it with php.

#> php partyScroller.php



I am trying to use your setup, but the PHP email class is no longer hosted. Do you have a copy of it?
Gavin Groce
hey how would I pass a message to your script from the command line with
Gavin Groce
I got this working on windows... why you ask? Because none of my laptops have serial ports and debian removed support for keyspan serial devices... so I had to change a few things like remove the var for the perl script and put the location after the perl command and comment out the setting where it is declared at the top... I am using php5 CLI and Strawberry perl (I had to force the Win32::Serial package to install, so you might try Active State Perl) I did not get the sound file working yet... What I want to know is how to make the messages change color and blink, etc.... I see it in the perl script but dont follow it at all
Gavin Groce
I have it running under windows with PHP5 and Strawberry Perl... the only kicker is I had to put the path to the perl script and // out the var for it... I did not try the audio file yet How did you get yours to flash and change colors? I see it in the perl script but dont follow it at all *sill me*
mmh... GVoice not avaible in my country... suggestions how to avoid it?!
i did something similar a few months back with an old phone and gammu on the server side, writing the sms directly to the database. didn't know you can receive txt messages with google voice.
You. Are. AMAZING.

Oh, and FIRST!!1
Submit a Comment
Email (We send a validation email. Also used to display your Gravatar)