April Fool's Day with Bouncy Balls
|
In this article, Clint Rutkas builds a relay application that will drop 1,000 super bouncy balls from the ceiling as an April Fools gag! |
|
Clint Rutkas
Difficulty: Intermediate
Time Required: 3-6 hours
Cost: $100-$200
Hardware: Control Anything USB Relay Board, a drill, a ton of super bouncy balls and some zippie ties
|
Introduction
I'm tend to go a bit crazy when it comes to pranks. April Fool's day is almost like Christmas to me. So bombarding someone with 1,000 super bouncy balls seemed like the proper thing to do come the first possible moment.
But that someone was a bit harder than I thought. I needed a victim however but with how SpringCM's executive offices are designed, I quickly found him. My victim for this task is my CTO, Dave Dahl. I wasn't alone in this prank, I had my friend / co-worker, Andy Konkol, help out with the final assembly and testing of the application.
Here is the video of the final outcome over at YouTube.
The Prank Setup
You need a laptop, a rigging system, a WiFi network, a ton of bouncy balls, a drill, a spare ceiling tile, some string and a relay board. Here is the picture of the system finally done. The ceiling tile was modified with the sides shaved and gluing a piece of wood to the back to apply anchors into. To figure out how to wire up relay boards, I suggest reading Brian Peek's holiday lighting article. Brian provides diagrams and wiring safety information.

And we mustn't forget the balls. How does one buy super bouncy balls? It was surprisingly simple with Google and searching for "Super Bouncy balls". I got mine from Gumball Machine Warehouse for about $70 if memory serves. Each bag weights about 5 pounds I'd guess and is about a foot by a foot. Apply them gingerly onto the shaven tile since it isn't supported by the orginal 1/4" lip anymore.

Andy Konkol admiring the release rig. You can see we've mounted the drill to the fire system. You'll also notice the drill's trigger is tied down so all we need to do is hit the relay and the drill will be on.

So as every good programmer knows is to test often, and as this picture shows, it is well earned. Cleaning up the mess took about five minutes. A video of this test can be found over on YouTube. Since we didn't know how much it would hurt or cause damage, a human test subject was needed. I stepped up to plate and had the crap freaked out of myself even knowing the balls were going to be deployed.

The Application
The application is fairly simple and uses a Control Anything USB relay board and has a laptop placed on a ceiling tile. The laptop was left on and with Remote Desktop Connection, the application will be triggered.
Relay Code
I'm using the relay board for another project so currently it is a fairly simple codebase. This application was the test bed for the next project. How the relay board works is by sending a sequence of commands. 254 will prepare for a command, 29 then will turn all the relays off, 30 on. The Control Anything relay board has a host of other features I haven't had time yet to implement but will soon so you can check back for a richer API for the relay board. The company does provide Visual Basic 6 code examples and uses the chr$ command instead of a byte array.
C#
public bool AllRelaysOff()
{
ComPort.WriteBuffer(new byte[] { 254, 29 });
return relaySuccess();
}
public bool AllRelaysOn()
{
ComPort.WriteBuffer(new byte[] { 254, 30 });
return relaySuccess();
}
private bool relaySuccess()
{
return (ComPort.ReadBufferChar() == 85);
}
VB
Public Function AllRelaysOff() As Boolean
ComPort.WriteBuffer(New Byte() {254, 29})
Return relaySuccess()
End Function
Public Function AllRelaysOn() As Boolean
ComPort.WriteBuffer(New Byte() {254, 30})
Return relaySuccess()
End Function
Private Function relaySuccess() As Boolean
Return (ComPort.ReadBufferChar = 85)
End Function
Comport
The comport code is actually a slightly refractored version from my dance floor application. The class itself is a wrapper for the .Net SerialPort object which can be found in System.IO.Ports. Furthermore, it now inherits off IDisposable to help auto close the serial port and is designed aimed to help ease use for multiple applications.
C#
public class ComPortIO : IDisposable
{
#region varibles
public SerialPort ComPort
{
get { return _comPort; }
set { _comPort = value; }
}
private SerialPort _comPort;
public bool DoneWriting
{
get { return _doneWriting; }
set { _doneWriting = value; }
}
private bool _doneWriting;
public bool DisableWriting
{
get { return _disableWriting; }
set { _disableWriting = value; }
}
private bool _disableWriting;
#endregion
public void Dispose()
{
if (ComPort.IsOpen)
ComPort.Close();
}
#region constructors
public ComPortIO(string ComPortName, int BaudRate, bool DisableComWriting)
{
DisableWriting = DisableComWriting;
if (!DisableWriting)
{
if (!doesPortExist(ComPortName))
throw new ArgumentException("The serial port used is not valid", ComPortName);
ComPort = new SerialPort(ComPortName, BaudRate, Parity.None, 8, StopBits.One);
if (!ComPort.IsOpen)
ComPort.Open();
ComPort.ReadTimeout = 100;
}
}
#endregion
#region public functions
public void WriteBuffer(char[] Buffer)
{
if (!DisableWriting)
ComPort.Write(Buffer, 0, Buffer.Length);
}
public void WriteBuffer(byte[] Buffer)
{
if (!DisableWriting)
ComPort.Write(Buffer, 0, Buffer.Length);
}
public void WriteBuffer(string Buffer)
{
if (!DisableWriting)
ComPort.Write(Buffer);
}
public string ReadBuffer()
{
try
{
if (!DisableWriting && ComPort.IsOpen)
{
return ComPort.ReadLine();
}
}
catch (Exception) { }
return null;
}
public int ReadBufferChar()
{
try
{
if (!DisableWriting && ComPort.IsOpen)
{
return ComPort.ReadChar();
}
}
catch (Exception) { }
return -1;
}
public int ReadBufferByte()
{
try
{
if (!DisableWriting && ComPort.IsOpen)
{
return ComPort.ReadByte();
}
}
catch (Exception) { }
return -1;
}
#endregion
#region private functions
private static bool doesPortExist(string ComPortName)
{
string[] serialPortNames = SerialPort.GetPortNames();
for (int i = 0; i < serialPortNames.Length; i++)
{
if (string.Compare(serialPortNames[i], ComPortName, true) == 0)
return true;
}
return false;
}
#endregion
}
VB
Public Class ComPortIO
Implements IDisposable
Private _comPort As SerialPort
Private _doneWriting As Boolean
Private _disableWriting As Boolean
Public Sub New(ByVal ComPortName As String, ByVal BaudRate As Integer, ByVal DisableComWriting As Boolean)
MyBase.New()
DisableWriting = DisableComWriting
If Not DisableWriting Then
If Not doesPortExist(ComPortName) Then
Throw New ArgumentException("The serial port used is not valid", ComPortName)
End If
ComPort = New SerialPort(ComPortName, BaudRate, Parity.None, 8, StopBits.One)
If Not ComPort.IsOpen Then
ComPort.Open()
End If
ComPort.ReadTimeout = 100
End If
End Sub
Public Property ComPort() As SerialPort
Get
Return _comPort
End Get
Set(ByVal value As SerialPort)
_comPort = value
End Set
End Property
Public Property DoneWriting() As Boolean
Get
Return _doneWriting
End Get
Set(ByVal value As Boolean)
_doneWriting = value
End Set
End Property
Public Property DisableWriting() As Boolean
Get
Return _disableWriting
End Get
Set(ByVal value As Boolean)
_disableWriting = value
End Set
End Property
Public Overloads Sub Dispose() Implements IDisposable.Dispose
If ComPort.IsOpen Then
ComPort.Close()
End If
End Sub
Public Overloads Sub WriteBuffer(ByVal Buffer() As Char)
If Not DisableWriting Then
ComPort.Write(Buffer, 0, Buffer.Length)
End If
End Sub
Public Overloads Sub WriteBuffer(ByVal Buffer() As Byte)
If Not DisableWriting Then
ComPort.Write(Buffer, 0, Buffer.Length)
End If
End Sub
Public Overloads Sub WriteBuffer(ByVal Buffer As String)
If Not DisableWriting Then
ComPort.Write(Buffer)
End If
End Sub
Public Function ReadBuffer() As String
Try
If (Not DisableWriting _
AndAlso ComPort.IsOpen) Then
Return ComPort.ReadLine
End If
Catch
End Try
Return Nothing
End Function
Public Function ReadBufferChar() As Integer
Try
If (Not DisableWriting _
AndAlso ComPort.IsOpen) Then
Return ComPort.ReadChar
End If
Catch
End Try
Return -1
End Function
Public Function ReadBufferByte() As Integer
Try
If (Not DisableWriting _
AndAlso ComPort.IsOpen) Then
Return ComPort.ReadByte
End If
Catch
End Try
Return -1
End Function
Private Shared Function doesPortExist(ByVal ComPortName As String) As Boolean
Dim serialPortNames() As String = SerialPort.GetPortNames
Dim i As Integer = 0
Do While (i < serialPortNames.Length)
If (String.Compare(serialPortNames(i), ComPortName, True) = 0) Then
Return True
End If
i = (i + 1)
Loop
Return False
End Function
End Class
Deployment
As soon as Dave Dahl sat down, I clicked the drop button and the everything worked like clock work. If you want to see the video, once again it is over at YouTube for your April Fools Day pleasure.
Summary
Before you do such a prank, please be sure you your victim will be cool with it so you won't get fired. Thankfully, I wasn't and Dave is cool ... I hope. I wasn't happy totally with our deployment solution since it was partially visible due to the string. I'd also like to have had the entire system automated with maybe an IR sensor or a force sensor hidden in the seat. Given a few more weekends and actually planning this prank out a bit more, I think Andy and myself could have come up with a more robust system.
Thanks
A special thanks to Dave Dahl for being such a good sport and Andy Konkol for helping out.
Bio
Clint is an application developer for SpringCM, an on-demand, web-based document and content management system. His two primary development languages are C# and JavaScript. Prior to the ball drop project, he worked on his Disco Dance Floor. In his off time, he whips up other random weird projects and does twenty something activities with his friends. Clint’s blog is betterthaneveryone.com and can be emailed at clint@rutkas.com.