Introduction
In the spirit of Lava Lamp build monitoring and Automated Continuous Integration and the Ambient Orb, I purchased a BetaBrite one-line electronic LED sign. This two foot LED display beats the heck out of those retro-kitsch novelty build status indicators. The BetaBrite supports full text build status information in eight colors and 12 different font styles-- including animations! It's way cool; I have yet to see someone walk by my desk who isn't mesmerized by its hypnotic animation and colors. And it's not all that expensive, either. Sam's Club has the BetaBrite sign for a reasonable $160, and that includes the serial communication cable, handheld remote, and software.
The BetaBrite is fully programmable via the infrared remote, but keying in long messages on the remote is a giant pain. It's a lot easier to connect the BetaBrite to your PC through a RS-232 Serial to RJ-12 cable, then use the bundled Windows software to program the sign:
The Windows software works fine, but what I really wanted was a native .NET API. So, armed with the protocol document and a functioning BetaBrite connection, I set out to write an easy to use .NET API for the BetaBrite.
Understanding the BetaBrite LED Sign
The BetaBrite understands a subset of the Alpha Sign Communications Protocol. We're talking about RS-232 serial communications to a device with a whopping 32 kilobytes of internal memory-- not exactly a supercomputer. So, as you might expect, the protocol is a little primitive and sometimes confusing. I spent the last week poring over the documentation; here's what I found:
- BetaBrite only understands version 1.0 of the Alpha protocol
Anything in the Alpha Protocol Documentation referring to 2.0 or 3.0 features won't apply to the BetaBrite. Some of these are obvious, such as multiple line commands-- the BetaBrite only has one line-- and some are less obvious.
- All communications are in a standard packet format
The sign communicates with the PC via a RS-232 serial port connection at 9600,N,8,1. All of the messages sent to the sign will be in a standard packet format:
To simplify communications, all messages are sent in plain-text ASCII, with no unprintable high-bit ASCII characters. If high-bit ASCII is needed, it is encoded in a double-byte format as you'll see later.
- The sign stores "files" in slots labeled by a single ASCII character
Any ASCII character from 20h (space) to 7Eh (1/2 space) is valid as a file label, and you can allocate any combination of file labels up to the 32 kilobyte internal memory limit of the sign. Note that file label "0" is a so-called "priority label" and is treated a little differently, but other than that, they're all just named file labels for storing either Text, a String, or a Picture.
- All memory must be allocated in advance
I guess I've been spoiled by the automatic memory management and garbage collector of .NET, because this one took me a while to wrap my head around. Any time you program the sign, you must allocate all the memory you'll need in advance. Any attempt to allocate more memory later will destroy all the existing memory allocations! Be sure to allocate all the memory you'll need before writing anything to the sign. This isn't a big deal in practice, but it cannot be abstracted away, so you must be aware of it.
Architecture
Early on, I made the decision to implement the API as a set of three classes:
BetaBrite.Sign
This is the primary Public
interface for the sign. It drives the Protocol
and RS232
classes behind the scenes, so the user is protected from the complexities of both the Alpha Sign Communications Protocol and RS-232 serial communications. Here's a quick glance at it:
BetaBrite.Protocol
This Private
class factory defines all the low-level commands necessary to talk to the sign, which can all be rendered to a byte stream via the ToBytes()
method. Additionally, if you want to preview a "pretty printed" version of the command, you can call the overridden ToString()
method for the command. All commands inherit from the BaseCommand
class, which implements the standard Alpha packet format. The child commands must override the FormDataField
method, which returns the string of text specific to that particular command class. The Sign.SetDateAndTime
method illustrates how this works:
Public Sub SetDateAndTime(ByVal dt As DateTime)
Dim dc As New BetaBrite.Protocol.SetDateTimeCommand(dt)
If _IsDebug Then
Debug.Write(dc.ToString)
End If
Write(dc.ToBytes)
End Sub
Note that a few of the Protocol
Enum
s are exposed in the Sign
methods -- for transitions and so forth -- as these are unavoidably determined by the underlying protocol.
BetaBrite.RS232
This Private
class defines the communication method between the PC and the sign. It is almost completely Private
, however, you still need to provide the Sign
object with a comm port number. Internally, all Protocol
commands are rendered into byte streams and automatically transported to the sign using Cory Smith's DBComm class. It's unmodified, other than some header comments I added attributing it to Cory. I compiled this in so the entire BetaBrite DLL can be used as a standalone interface API with no other dependencies.
Using BetaBrite.Sign
As promised, this API is very simple to use. Here's the canonical "Hello, World" example -- assuming your BetaBrite is connected via the first serial port:
Dim bb As New BetaBrite.Sign(1)
bb.Open()
bb.Display("I <extchar=heart> BetaBrite!")
bb.Close()
Now, this example cheats a bit because the Display()
method is ultra-simple. This method can only display a single message (although it can be a very long message, up to 32 KB) as a result. In order to progress beyond "Hello World", I'll show examples of the three things you can show on a BetaBrite sign.
The Text item is the most frequently used, and represents formatted text followed by a transition state:
Dim bb As New BetaBrite.Sign(1)
With bb
.Open()
.UseMemoryText("D"c, 128)
.UseMemoryText("E"c, 128)
.UseMemoryText("F"c, 128)
.AllocateMemory()
.SetText("D"c, _
"<font=five><color=green>This is <font=seven>file D", _
Transition.Rotate)
.SetText("E"c, _
"<font=five><color=yellow>This is <font=seven>file E", _
Transition.WipeLeft)
SetText("F"c, _
"<font=five><color=red>time is <calltime>", _
Transition.RollDown)
.SetRunSequence("EDF")
.Close()
End With
If you run this code, you should see three different messages, with different colors, fonts, and transitions between the messages. Pay attention to the explicit memory allocation-- be sure you allocate enough memory for your text plus the one or two bytes of formatting codes per tag. As you may have noticed, text message strings support a set of lightweight HTML-style formatting codes, which you can find in the Text Formatting region of the Protocol
class: look for Protocol.ControlChars
, Protocol.Color
, Protocol.Font
, Protocol.ExtChar
and Protocol.CharAttrib
. I won't bother listing them all here, because the demonstration solution shows examples of each and every one. However, it is important to bear in mind that, unlike HTML, these tags do not support closing tags-- so if you change the color, it will stay changed for the rest of your messages! Be sure to change the color back when you're done.
The String item is a subset of text that acts like a variable:
Dim bb As New BetaBrite.Sign(1)
With bb
.Open()
.UseMemoryText("A"c, 128)
.UseMemoryString("B"c, 32)
.AllocateMemory()
.SetText("A"c, "You are customer number <callstring=B>")
.Close()
End With
I didn't specify a run sequence here because file A always runs by default. The main advantage of strings is that they can be dynamically updated without making the sign "flash", so think of them as variables. They support a subset of the formatting codes that text messages support, so unless you need a variable, stick with text messages.
The Picture item represents an 8-color bitmap of up to 80x7 pixels:
Dim bb as New BetaBrite.Sign(1)
With bb
.Open()
.UseMemoryText("A"c, 128)
.UseMemoryPicture("B"c)
.UseMemoryPicture("C"c)
.UseMemoryPicture("D"c)
.AllocateMemory()
.SetPicture("B"c, "betabrite_picture_triangle.bmp", 35, 7)
.SetPicture("C"c, "betabrite_picture_smiley.bmp", 10, 7)
.SetPicture("D"c, "betabrite_picture_demo.bmp")
.SetText("A"c, _
"<callpic=C><callpic=C><callpic=B><callpic=C>" & _
"<callpic=C><newline><callpic=D>", _
Transition.RollUp)
.Close()
End With
Note that, unless you specify otherwise, the maximum amount of picture memory (80x7) will be allocated in that file label. Here's what you should see when this is run:
This will scroll up and be replaced by another full-size 80x7 picture of some "modern art" I created. Now, I know what you're thinking: yes, you can load any arbitrary 80x7 pixel image into the BetaBrite, in whatever file formats .NET supports. However, do not expect this to look good! 80x7 is a tiny number of pixels, and the BetaBrite has a very limited palette: essentially two reds, two greens, and four yellows (see Protocol.PixelColor
). Try it and see, but don't say I didn't warn you. I recommend sticking with the 8-color bitmap templates I created in the demonstration solution; look in the \bin folder for these.
Animated images (ala animated .gifs) isn't supported natively, but can be hacked in using the <NoHold>
or <Speed5>
tags and a sequence of cleverly designed picture files. There's an example of simulating animation in the demonstration solution, too.
Some Limitations
I'm pretty happy with the way this API turned out, but I think it's worth mentioning a few of the things I decided not to do. Some of these might make sense as possible future enhancements, and some were intentionally avoided.
- It is BetaBrite-only. I thought about making the API generic enough to work with any Alpha sign, but then quickly decided that was a bad idea, because I don't have any other Alpha signs to test against.
- It is write-only. I only implemented the Write commands, but for every Write, there is a Read. Making the API read-write isn't essential for a PC connected sign, in my opinion, but it might be useful.
- It does not expose advanced run functionality. After setting the date and time on the sign, you can tell certain file labels to run at certain times of the day. The run sequence could display "GOOD MORNING" from 9 a.m. - 12 p.m., and "GOOD EVENING" from 6 p.m. - 9 a.m. Although this is supported in the
BetaBrite.Protocol
class, I didn't expose it in BetaBrite.Sign
. This would not be hard to do, but I don't think it's very useful for PC-connected signs -- I expect the PC to be overwriting the sign every few minutes anyway. It'd be more useful for signs that were running standalone.
Conclusion
If you own a BetaBrite LED sign, I think you'll have a lot of fun with these classes; they make it easy to write .NET applications that support the BetaBrite sign. There are many more details and comments in the demonstration solution provided at the top of the article, so check it out.
I hope you enjoyed this article. Please don't hesitate to provide feedback, good or bad! If you enjoyed this article, you may also like my other articles as well.
History
- Sunday, March 20th, 2005 - published.
- Thursday, April 21st, 2005 - version 1.1
- Modified picture methods to support directly passing a
Bitmap
.