diff --git a/configure b/configure
index 4a9fedec0e6b5b434ca36e5ca21ed22a8d3809d2..d4a4436e807ae0aa1704a0f1929fcd2a90a63640 100755
--- a/configure
+++ b/configure
@@ -1559,7 +1559,7 @@ outfiles="$outfiles Makeconf GNUmakefile \
 	utils/cvsupd.pl utils/newnode utils/grantnodetype \
 	utils/nsgen/GNUmakefile utils/nsgen/webnsgen \
 	utils/link_config utils/import_commitlog utils/dhcpd_wrapper \
-	utils/opsreboot utils/deletenode utils/webdeletenode \
+	utils/opsreboot utils/deletenode utils/webdeletenode utils/spewleds \
 	www/GNUmakefile www/defs.php3 www/dbdefs.php3 \
 	www/swish.conf www/websearch \
 	vis/GNUmakefile vis/webvistopology vis/dbvistopology \
diff --git a/utils/GNUmakefile.in b/utils/GNUmakefile.in
index bd0e935396d4e3deba64a10543a73c2d4b8b1f01..054396dc8841ffb3cde2bceaa90802704630305d 100644
--- a/utils/GNUmakefile.in
+++ b/utils/GNUmakefile.in
@@ -18,7 +18,7 @@ BIN_SCRIPTS	= delay_config sshtb create_image node_admin link_config
 SBIN_SCRIPTS	= vlandiff vlansync withadminprivs export_tables cvsupd.pl \
                   eventping grantnodetype import_commitlog dhcpd_wrapper \
 		  opsreboot deletenode node_statewait
-LIBEXEC_SCRIPTS	= webcreateimage newnode webdeletenode
+LIBEXEC_SCRIPTS	= webcreateimage newnode webdeletenode spewleds
 
 #
 # Force dependencies on the scripts so that they will be rerun through
diff --git a/utils/spewleds.in b/utils/spewleds.in
new file mode 100755
index 0000000000000000000000000000000000000000..c08cf18e38f4702421e4dff6f28ce582b5a7dd66
--- /dev/null
+++ b/utils/spewleds.in
@@ -0,0 +1,79 @@
+#!/usr/bin/perl -wT
+
+#
+# EMULAB-COPYRIGHT
+# Copyright (c) 2004 University of Utah and the Flux Group.
+# All rights reserved.
+#
+
+#
+# Simple script to connect to the LED status port on a stargate and spew the LED status
+# to stdout
+#
+
+use English;
+use Getopt::Std;
+use IO::Socket;
+
+use lib '@prefix@/lib';
+use libdb;
+
+my $LED_STATUS_PORT = 1812;
+
+# Make output unbuffered
+$| = 1;
+
+#
+# Args
+#
+if (@ARGV != 1) {
+    die "Usage: spewleds mote\n";
+}
+
+my ($mote) = @ARGV;
+
+#
+# Untaint the argument. 
+#
+if ($mote =~ /^([-\w.]+)$/) {
+	$mote = $1;
+} else {
+    die("Tainted node name: $mote");
+}
+
+#
+# Make sure they have permissions to see this node
+#
+if (! TBNodeAccessCheck($UID, TB_NODEACCESS_READINFO, $mote)) {
+    print STDERR
+    "*** osload: Not enough permission to view that node!\n";
+    exit 1;
+}
+
+#
+# Make sure it's a stargate or garcia (XXX garcia should be temporary)
+#
+my ($type, $class) = TBNodeType($mote);
+if ($type ne "garcia" && $class ne "sg") {
+    die "Node $mote is not of the correct type ($type,$class)\n";
+}
+
+#
+# Connect to the LED status port
+#
+my $sock = new IO::Socket::INET (
+                                  PeerAddr => "$mote",
+                                  PeerPort => "$LED_STATUS_PORT",
+                                  Proto => 'tcp',
+                                 );
+die "Could not create socket: $!\n" unless $sock;
+
+#
+# Okie, just loop on this guy forever
+#
+while (my $string = <$sock>) {
+    print "$string"
+}
+
+close $sock;
+exit 0;
diff --git a/www/BlinkenLichten.class b/www/BlinkenLichten.class
index 3e6b3fb2c59e0fe162a19145fae62db8317dcb69..265cbe65d9a70cee43c4783c859d4475deaf7770 100644
Binary files a/www/BlinkenLichten.class and b/www/BlinkenLichten.class differ
diff --git a/www/BlinkenLichten.java b/www/BlinkenLichten.java
index 85237de7514df964d3594687ec272606cf618d8f..0a40a5c4cabb2dc218ba64b08f34f6b03d9a2793 100644
--- a/www/BlinkenLichten.java
+++ b/www/BlinkenLichten.java
@@ -34,7 +34,9 @@ public class BlinkenLichten
     /**
      * The current status of the light, just on/off for now.
      */
-    private boolean on;
+    private boolean red_on;
+    private boolean green_on;
+    private boolean yellow_on;
     
     public BlinkenLichten()
     {
@@ -79,15 +81,19 @@ public class BlinkenLichten
     
     public void run()
     {
-	byte buffer[] = new byte[1];
+	byte buffer[] = new byte[6];
 	
 	try
 	{
 	    /* Just read a character at a time from the other side. */
-	    while( this.is.read(buffer) > 0 )
+	    // Bad, bad, bad
+	    //while( this.is.read(buffer) > 0 )
+	    while( this.is.read(buffer,0,6) > 0 )
 	    {
 		/* 1 == on, 0 == off */
-		this.on = (buffer[0] == '1');
+		this.red_on = (buffer[0] == '1');
+		this.green_on = (buffer[2] == '1');
+		this.yellow_on = (buffer[4] == '1');
 		
 		repaint();
 	    }
@@ -108,16 +114,29 @@ public class BlinkenLichten
     public void paint(Graphics g)
     {
 	Dimension size = getSize();
+	int width = size.width / 3;
+	int height = size.height;
 
 	/*
-	 * Just paint the entire canvas provided to the applet, no need to get
-	 * fancy.
+	 * Paint each of the three LED values
 	 */
-	if( this.on )
+	if (this.red_on)
 	    g.setColor(Color.red);
 	else
-	    g.setColor(Color.black);
-	g.fillRect(0, 0, size.width, size.height);
+	    g.setColor(Color.red.darker().darker());
+	g.fillRect(0, 0, width, height);
+
+	if (this.green_on)
+	    g.setColor(Color.green);
+	else
+	    g.setColor(Color.green.darker().darker());
+	g.fillRect(width, 0, width, height);
+
+	if (this.yellow_on)
+	    g.setColor(Color.yellow);
+	else
+	    g.setColor(Color.yellow.darker().darker());
+	g.fillRect(width *2, 0, width, height);
     }
     
     public void destroy()
diff --git a/www/ledpipe.php3 b/www/ledpipe.php3
index 675d18a6f5e1dde2b25052f162cfed5f00e0dfa9..166efb75664b012bf243cfb591d03d02fb82bd80 100644
--- a/www/ledpipe.php3
+++ b/www/ledpipe.php3
@@ -7,12 +7,7 @@
 include("defs.php3");
 
 #
-# Standard Testbed Header
-#
-#PAGEHEADER("Watch Experiment Log");
-
-#
-# Only known and logged in users can end experiments.
+# Only known and logged in users can watch LEDs
 #
 $uid = GETLOGIN();
 LOGGEDINORDIE($uid);
@@ -42,11 +37,48 @@ header("Cache-Control: no-cache, must-revalidate");
 header("Pragma: no-cache");
 flush();
 
-for ($lpc = 0; $lpc < 30; $lpc++) {
-	sleep(1);
-	$on_off = $lpc % 2;
-	echo "$on_off";
-	flush();
+#for ($lpc = 0; $lpc < 30; $lpc++) {
+    #	sleep(1);
+    #	$on_off = $lpc % 2;
+    #	echo "$on_off";
+    #	flush();
+    #}
+
+#
+# Silly, I can't get php to get the buffering behavior I want with a socket, so
+# we'll open a pipe to a perl process
+#
+$socket = popen("$TBSUEXEC_PATH $uid nobody spewleds $node","r");
+if (!$socket) {
+    USERERROR("Error opening $node - $errstr",1);
+}
+
+#
+# Clean up when the remote user disconnects
+#
+function SPEWCLEANUP()
+{
+    global $socket;
+
+    if (!$socket || !connection_aborted()) {
+	exit();
+    }
+    pclose($socket);
+    exit();
+}
+ignore_user_abort(1);
+register_shutdown_function("SPEWCLEANUP");
+
+#
+# Just loop forver reading from the socket
+#
+while(!feof($socket)) {
+
+    # Bad rob! No biscuit!
+    $onoff = fread($socket,6);
+    echo "$onoff";
+    flush();
 }
+fclose($socket);
 
 ?>
diff --git a/www/moteleds.php3 b/www/moteleds.php3
new file mode 100644
index 0000000000000000000000000000000000000000..c975ef5958d04d77416ecdcedcd27e0b4941f504
--- /dev/null
+++ b/www/moteleds.php3
@@ -0,0 +1,80 @@
+<?php
+#
+# EMULAB-COPYRIGHT
+# Copyright (c) 2004 University of Utah and the Flux Group.
+# All rights reserved.
+#
+
+include("defs.php3");
+include("showstuff.php3");
+
+#
+# Make sure they are logged in
+#
+$uid = GETLOGIN();
+LOGGEDINORDIE($uid);
+
+#
+# Verify page arguments.
+# 
+if (!isset($pid) ||
+    strcmp($pid, "") == 0) {
+    USERERROR("You must provide a Project ID.", 1);
+}
+
+if (!isset($eid) ||
+    strcmp($eid, "") == 0) {
+    USERERROR("You must provide an Experiment ID.", 1);
+}
+
+#
+# Standard Testbed Header now that we have the pid/eid okay.
+#
+PAGEHEADER("View mote LEDs ($pid/$eid)");
+
+#
+# Make sure they have permission to view this experiment
+#
+if (! TBExptAccessCheck($uid, $pid, $eid, $TB_EXPT_READINFO)) {
+    USERERROR("You do not have permission to view experiment $exp_eid!", 1);
+}
+
+#
+# Get a list of all nodes in this experiment of type 'garcia' or 'stargate'
+#
+$query_result =
+    DBQueryFatal("select r.node_id,t.type,t.class from reserved as r ".
+		     "left join nodes as n on ".
+		     "     r.node_id=n.node_id ".
+		     "left join node_types as t on ".
+		     "     n.type=t.type ".
+		     "where r.pid='$pid' and r.eid='$eid'");
+		     
+if (mysql_num_rows($query_result) == 0) {
+    echo "<h3>No nodes to display in this experiment!</h3>\n";
+} else {
+    echo "<center>
+          <h3>Blinky Lights</h3>
+          </center>
+          <table align=center cellpadding=2 border=1>
+	  <tr><th>Node</th><th>LEDs</th>\n";
+    while ($row = mysql_fetch_array($query_result)) {
+	if ($row['type'] != "garcia" && $row['class'] != "sg") {
+	    # Only the LEDs, mam
+	    continue;
+	}
+	echo "<tr><th>$row[node_id]</th><td>";
+	SHOWBLINKENLICHTEN($uid,
+	    $HTTP_COOKIE_VARS[$TBAUTHCOOKIE],
+	"ledpipe.php3?node=$row[node_id]");
+    }
+    echo "</table>\n";
+
+}
+
+#
+# Standard Testbed Footer
+# 
+PAGEFOOTER();
+
+?>
diff --git a/www/showexp.php3 b/www/showexp.php3
index b3ddad56367c79c0758da47c1b053eb4f54ec9f7..a0cdd5dd0c92fd4a2c1063771db0430f7eb5c256 100644
--- a/www/showexp.php3
+++ b/www/showexp.php3
@@ -72,6 +72,20 @@ $isbatch    = $row["batchmode"];
 $wireless   = $row["wirelesslans"];
 $linktest_running = $row["linktest_pid"];
 
+#
+# Get a list of node types and classes in this experiment
+#
+$query_result =
+    DBQueryFatal("select distinct t.type,t.class from reserved as r " .
+		 "       left join nodes as n on r.node_id=n.node_id ".
+		 "       left join node_types as t on n.type=t.type ".
+		 "where r.eid='$eid' and r.pid='$pid'");
+while ($row = mysql_fetch_array($query_result)) {
+    $classes[$row['class']] = 1;
+    $types[$row['type']] = 1;
+}
+
+
 echo "<font size=+2>Experiment <b>".
      "<a href='showproject.php3?pid=$pid'>$pid</a>/".
      "<a href='showexp.php3?pid=$pid&eid=$eid'>$eid</a></b></font>\n";
@@ -194,6 +208,13 @@ if ($wireless) {
 WRITESUBMENUBUTTON("Show History",
 		   "showstats.php3?showby=expt&which=$expindex");
 
+# Blinky lights - but only if they have nodes of the correct type in their
+# experiment
+if ($types['garcia'] || $classes['sg']) {
+    WRITESUBMENUBUTTON("Show Blinky Lights",
+		   "moteleds.php3?pid=$exp_pid&eid=$exp_eid");
+}
+
 if (ISADMIN($uid)) {
     if ($expstate == $TB_EXPTSTATE_ACTIVE) {
 	SUBMENUSECTION("Beta-Test Options");
diff --git a/www/showstuff.php3 b/www/showstuff.php3
index 7bceaa0a705756ff4a417bddc4300edf8ac2ddb1..ee6ba4b4a5aa7a98c980ca93f764e25e13123152 100644
--- a/www/showstuff.php3
+++ b/www/showstuff.php3
@@ -1004,7 +1004,7 @@ function SHOWEXPLIST($type,$id,$gid = "") {
 #                      $HTTP_COOKIE_VARS[$TBAUTHCOOKIE],
 #                      "ledpipe.php3?node=em1");
 #
-function SHOWBLINKENLICHTEN($uid, $auth, $pipeurl, $width = 10, $height = 10) {
+function SHOWBLINKENLICHTEN($uid, $auth, $pipeurl, $width = 30, $height = 10) {
 	echo "
           <applet code='BlinkenLichten.class' width='$width' height='$height'>
             <param name='pipeurl' value='$pipeurl'>