Let your Pi say Hello

Teach manners to machines

Claus

7 minute read

(Disclaimer: this is not a sponsored post. I bought everything myself, I just like a good product.)

With a cool hardware like this, the question is: what should it show? Temperature data? Boring. A clock? Even more boring. Twitter feeds? Too long. What could it be? Use this API to create something cool and let me know on twitter.

Let it shine!

Raspberry Pis have been used to collect information many times; several of my talks have featured temperature drivers, web API consumptions, and I even ran a database on it. Yet what has always been missing was the ability to do the reverse: display information.

There is a wealth of options here: run a full-blown desktop environment on a HDMI screen or on special displays. Commonly however Pis operate headlessly with a web interface that shows whatever it is receiving. Another way would be way simpler: LEDs. Personally I did like that option but I never got around to look at what’s out there and how I would connect these devices properly. Until, one September day, I discovered Maks Surguy’s product: the Neosegment display.

The pictures looked awesome and I felt like I could make something pretty, so I got three segments, each sporting two 7-segment-displays. They light up in individual colors per segment, so whatever came out of that would be nice and colorful.

Segmenting Letters and Numbers

Each of the segments consist of a single WS2812 LED to light it up, so effectively a piece of the display (containing two seven-segment-units) is a chain of 14 LEDs with three pins to hook it up to a Pi or Arduino: one data pin, one for power (5V), and one for grounding.

After tinkering with it for a while and lots of reading on the internet the way to operate those strips became clear. It was PWM - Pulse Width Modulation, which are very hard to generate and often used for fans or servos.

Thanks to a library with all kinds of language bindings (maybe I’ll try to create a pure Rust driver some day …) I didn’t have to worry about that though and with that handy the API was simple to implement. For more information and how to wire up the display, please read here.

Red, Green, Blue

In essence, the underlying API uses an array of numbers that control the color a segment lights up in. ‘Off’ is zero, ‘on’ anything higher, with the color corresponding to the well-known CSS color codes (0xff0000 is red, 0x00ff00 green, 0x0000ff blue).

To properly show stuff on the display, an all-zero array has to be prepared with the segments set to a color; e.g. this shows two horizontal lines (the uppermost and lowermost segment lights up in a bright white) on a piece with two seven-segment units:

[
    0, 0xffffff, 0, 0, 0, 0xffffff, 0, 
    0, 0, 0, 0, 0, 0, 0
] 

With that knowledge, it’s just a matter of creating the mapping of combinations to numbers or letters, a task that was quickly about inventing ways to display letters like ‘K’, ‘V’, ‘W’, ‘X’ and so forth.

As of this writing, this is the mapping table:

case "A": return setIndex(color, [0, 2, 3, 4, 5, 6]);
case "8":
case "B": return setIndex(color, [0, 1, 2, 3, 4, 5, 6]);
case "C": return setIndex(color, [0, 1, 4, 5]);
case "D": return setIndex(color, [0, 1, 2, 3, 6]);
case "E": return setIndex(color, [0, 1, 3, 4, 5]);
case "F": return setIndex(color, [0, 3, 4, 5]);
case "6":
case "G": return setIndex(color, [0, 1, 2, 3, 4, 5]);
case "H": return setIndex(color, [0, 2, 3, 4]);
case "1":
case "I": return setIndex(color, [0, 4]);
case "J": return setIndex(color, [0, 1, 2, 5, 6]);
case "K": return setIndex(color, [0, 3, 4]);
case "L": return setIndex(color, [0, 1, 4]);
case "M": return setIndex(color, [0, 2, 3, 5]);
case "N": return setIndex(color, [0, 2, 3]);
case "O": return setIndex(color, [0, 1, 2, 4, 5, 6]);
case "P": return setIndex(color, [0, 3, 4, 5, 6]);
case "Q": return setIndex(color, [2, 3, 4, 5, 6]);
case "R": return setIndex(color, [0, 3]);
case "5":
case "S": return setIndex(color, [1, 2, 3, 4, 5]);
case "T": return setIndex(color, [0, 4, 5]);
case "U": return setIndex(color, [0, 1, 2]);
case "V": return setIndex(color, [0, 1, 2, 4, 6]);
case "W": return setIndex(color, [0, 1, 2, 5]);
case "X": return setIndex(color, [0, 2, 3, 4, 6]);
case "Y": return setIndex(color, [1, 2, 3, 4, 6]);
case "Z": return setIndex(color, [0, 1, 3, 5, 6]);
case "0": return setIndex(color, [0, 1, 2, 4, 5, 6]);
case "2": return setIndex(color, [0, 1, 3, 5, 6]);
case "3": return setIndex(color, [1, 2, 3, 5, 6]);
case "4": return setIndex(color, [2, 3, 4, 6]);
case "7": return setIndex(color, [2, 4, 5, 6]);
case "9": return setIndex(color, [1, 2, 3, 4, 5, 6]);
default: return setIndex(color, []);

Webify and containerize

What good is this information if there is no easy way to integrate with it though? For this reason I created a project on Github, the pi-neosegment-api. Made with TypeScript, it exposes an interface to send numbers and letters to the display. Currently there are multiple ways to do this, including polling an Azure Service Bus endpoint, listening an MQTT topic, and a simple web service. Both queue endpoint listeners are under development and have limited functionality as of this writing.

Basic configuration options are available in a config.toml file that has to be present in the application directory. This is a minimum example that runs the HTTP server described below:

[display]
leds = 42
brightness = 128

[http]
port = 3000

GET /api/v1/display/write

Essentially there are three (GET) parameters: text, colors, and timeout. The first is self-explanatory, a string of text that should be shown. colors is an array of numbers representing the colors for each character, thus it has to have the same length as the text. The last parameter represents a value for how fast the “lines” should scroll.

There are some trade-offs that have been made here: by only having strings as an input parameter it’s not possible to display patterns at the moment and similarly each letter can only have a single color. It is quite possible that these limitations will be removed in future or additional APIs (see below).

As an example this would randomly change colors on the text ‘hello’ every three seconds (this is fish code):

while true
    curl -g "192.168.2.4:3000/api/v1/display/write?text=Hello&colors=["(random 0 16777215)","(random 0 16777215)","(random 0 16777215)","(random 0 16777215)","(random 0 16777215)"]"
    sleep 3
end

HELLO!

A different example could be this simple clock:

while true
    curl -g "192.168.2.4:3000/api/v1/display/write?text="(date +%H:%M)"&colors=["(random 0 16777215)","(random 0 16777215)","(random 0 16777215)","(random 0 16777215)","(random 0 16777215)"]"
    sleep 60
end

Note that the ‘:’ will be displayed as an empty space.

Run, Docker, Run

Containers are great and they take a lot of the more painful aspects away: installing unwanted dependencies and headers, compiling to specific libraries, running something from the internet as a privileged application. Consequently this application is containerized for ARM and available on the Docker hub.

To run the container use:

docker run --privileged -d \
    -p 3000:3000 \
    -v /path/to/config.toml:/app/config.toml \
    clma/pi-neosegment-api:arm 

As the name suggests, this is an ARM container that won’t run on a MacBook or PC, just on the Raspberry Pi (or similar).

The Use Case

As a frequent traveller, this API will be powered by home assistant which includes a simple telegram bot integration - a great way to send messages home when on the road!

This API is still under active development and future plans include:

  • Tests 😅
  • A plugin API to selectively include queue listeners and enable extensions
  • A RESTful endpoint for displaying patterns

Contributions are welcome of course, so don’t hesitate to send pull requests, issues or by sharing with others! Check it out and let me know what you think! As a final thought, I’d like to share a tiny part of the Microsoft office in Munich with you:

Oh no, not again