Stranger Things is a science fiction-horror web TV show. I have never actually seen the show, however, I know that it features a very interesting concept of a wall covered in party lights and letters of the alphabet. This wall used for communicating with the supernatural.
Can we build a wall which allows our Newcastle office to communicate with our London office?
There are various projects online which set out to build a version of this wall, however, most resembled the set-up of a web page running on a raspberry pi, which controlled LED's directly from the pins. I wanted to remove the need for a Raspberry pi and use a much cheaper micro-controller, whilst pushing more control into the cloud.
This article will describe the basic set-up that I used to create this wall for our Newcastle office to communicate with the London office. This was only a prototype for a bit of fun and to prove the reliability of MQTT, but there are lots of ways this could be expanded to add functionality, purpose or security. I have also simplified the code to the bare basics.
Hardware
Below is the hardware, I purchased for this project. You could use any Arduino based microcontroller, however, I really recommend looking at the NodeMCU, as it's incredibly cost effective and has a WiFi component built in. As for the lights; any WS1811 or WS1812 addressable LEDs will work well.
MQTT
The heart of this project is MQTT. This is a very simple protocol used commonly in the Internet of Things, due to its speed and simplicity. Essentially MQTT is broken into a few basic concepts:
- Broker - This is the server which receives and forwards messages between devices.
- Topic - A broker can have multiple topics. A topic is essentially a channel for the devices to talk on.
- Publish - Devices can publish messages to a topic on the broker.
- Subscribe - Devices can listen to a topic for any incoming messages.
There are a few free MQTT brokers hosted in the cloud (here, here and here), but it is also straightforward to set up your own using mosquitto.
Building the interface
The interface was a simple HTML page with letters of the alphabet. I didn't spend very much time on the presentation of this page, but it allows the user to click any letter. The markup contained two data tags for LED index and the colour index.
<span class="letter" data-led="5" data-color="1">A</span>
<span class="letter" data-led="7" data-color="2">B</span>
<span class="letter" data-led="8" data-color="3">C</span>
<span class="letter" data-led="10" data-color="4">D</span>
I then used this Javascript library for connecting to the MQTT broker along with the following code.
$(document).ready(function () {
client = new Paho.MQTT.Client("broker.mqttdashboard.com", Number(8000), "clientId-" + sessionId);
client.onConnectionLost = onConnectionLost;
client.onMessageArrived = onMessageArrived;
client.connect({ onSuccess: onConnect });
});
function onConnect() {
client.subscribe(mqttDestination);
}
$(".letter").mousedown(function () {
sendLetterPress($(this), true);
});
$(".letter").mouseup(function () {
sendLetterPress($(this), false);
});
function sendLetterPress($letter, isOn) {
var led = $letter.attr("data-led");
var color = $letter.attr("data-color");
if (!isOn) {
color = 0;
}
var message = led + "," + color;
sendMessage(message);
}
function sendMessage(message) {
message = new Paho.MQTT.Message(message);
message.destinationName = mqttDestination;
client.send(message);
}
This simple page was uploaded to Microsoft Azure and hosted using a cloud web app instance.
Building the hardware
The LED's were connected to the NodeMCU's 5v pin, ground pin and D1 pin (GPIO5). Check here for pin information.
Next, I used the Arduino IDE to program the NodeMCU with a connection to the MQTT server.
The plugins needed for this project are:
- ESP8266wifi
- PubSubClient
- Adafruit_NeoPixel
The following snippet is the full code used on the NodeMCU. Its a bit rough due to it being a quick hacky project, but it works reliably and shows how the different plugins work together.
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif
#define LIGHTPIN 5
#define NUMPIXELS 50
const char* ssid = "";
const char* password = "";
const char* mqtt_server = "";
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, LIGHTPIN, NEO_GRB + NEO_KHZ800);
int totalColours = 5;
uint32_t colors[] = { pixels.Color(0, 0, 0), pixels.Color(0, 255, 0), pixels.Color(255, 0, 0), pixels.Color(0, 0, 255), pixels.Color(255, 255, 0) };
long lastReconnectAttempt = 0;
long lastMessage = 0;
int currentStandbyStatus = -100;
WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;
void setup() {
pixels.begin();
pixels.show();
setStandby(3);
Serial.begin(9600);
setup_wifi();
lastReconnectAttempt = 0;
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
}
void setup_wifi() {
delay(10);
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void callback(char* topic, byte* payload, unsigned int length) {
lastMessage = millis();
setStandby(0);
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
String message = "";
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
message += (char)payload[i];
}
Serial.println();
Serial.println("message: " + message);
int msgMode = getValue(message, ',', 0).toInt();
if(msgMode == 0) {
int led = getValue(message, ',', 1).toInt();
int color = getValue(message, ',', 2).toInt();
pixels.setPixelColor(led, colors[color]);
pixels.show();
} else if(msgMode == 1) {
getAttention();
}
}
boolean reconnect() {
if (client.connect("arduinoClient")) {
client.subscribe("TheTinIoST");
}
return client.connected();
}
void loop()
{
if (!client.connected()) {
setStandby(1);
Serial.println("not connected");
long now = millis();
if (now - lastReconnectAttempt > 5000) {
lastReconnectAttempt = now;
if (reconnect()) {
Serial.println("Successfully connected to MQTT");
setStandby(-1);
lastReconnectAttempt = 0;
}
}
} else {
client.loop();
long now = millis();
if(now - lastMessage > 10000 && currentStandbyStatus != -1) {
setStandby(-1);
}
}
}
String getValue(String data, char separator, int index)
{
int found = 0;
int strIndex[] = {0, -1};
int maxIndex = data.length()-1;
for(int i=0; i<=maxIndex && found<=index; i++){
if(data.charAt(i)==separator || i==maxIndex){
found++;
strIndex[0] = strIndex[1]+1;
strIndex[1] = (i == maxIndex) ? i+1 : i;
}
}
return found>index ? data.substring(strIndex[0], strIndex[1]) : "";
}
void setStandby(int state) {
currentStandbyStatus = state;
for(int i=0, j=1; i < NUMPIXELS; i++, j++){
if(j >= totalColours) {
j = 1;
}
if(state == -1) {
pixels.setPixelColor(i, colors[j]);
}
else {
pixels.setPixelColor(i, colors[state]);
}
}
pixels.show();
}
void getAttention() {
for(int j=1; j < totalColours; j++){
for(int i=0; i < NUMPIXELS; i++){
pixels.setPixelColor(i, colors[j]);
}
pixels.show();
delay(200);
}
setStandby(0);
}
The Result
Here is a short demo of the wall spelling out the word "Happy"