Building a Sign for My Office, Part 1

Some time ago, Adafruit had a sale on the M0 Feather WiFi board. Normally $39.95 (USD), it was marked down to $9.95. I purchased this, alongside the RGB Matrix Featherwing and a 64x32 LED Matrix with the goal of creating a relatively simple sign for my office. The idea is to, through some command on my home server, send arbitrary strings of text to display: “Do Not Disturb”, “Gaming”, “Working”.

This shouldn’t be so difficult in theory

This simple diagram shows at least 3 separate pieces to this idea. There is the sign's controller that must somehow receive messages over WiFi, the server that must send messages crafted by users, and the user requires some interface to craft messages to be sent. There are a wide array of possible solutions to many parts of this problem.

For starters, this recipe sticks to COTS hardware - specifically well documented and supported parts. This allows for the use of libraries that should do the bulk of the heavy lifting. We know that some means of communication is required between the controller and a server. WiFi facilitates the data transfer but some application solution is required to send data. Brief research brings up several possible routes to success. We could use some sort of HTTP Server to communicate via JSON or XML. We could build and use a REST API, or utilize MQTT.

There are a wide array of scenarios where different solutions and protocols are better. In our circumstance the sign is hardwired to power and on WiFi. Since we aren't so concerned about power and can assume a bit more reliability (probably close to the AP) we can lean more on our physical layer. If we were using a different means of communication, or had different power and range constraints, a different application solution would be needed to accommodate the constraints of the connection. 

This project proceeds with MQTT because its architecture is relatively simple for me to understand, has an easy-to-install server software package, and does not have a lot of overhead. The Feather M0 and its 32KB of RAM should be more than enough to get a WiFi connection and run the MQTT client.

Put simply, MQTT's architecture enables communication through a broker that receives communication from publishers of various topics and sends that to subscribers of those topics (Cedalo, 2023). The roles are not static and can change - A publisher can also be a subscriber. 

With this in mind, we should revisit the architecture. We'll extend this to visualize each distinct part of the system:

The controller's complete tech stack includes some code to communicate with the MQTT broker, some code to communicate with the WiFi network, and some code to display text to the screen. Gluing these three pieces together will be the core of the controller's tech stack. WiFi, MQTT client, and display drivers will all be external libraries. The controller communicates with the broker that is hosted on a separate system via WiFi.

The MQTT broker is Eclipse Mosquitto, hosted on a linux system. A second MQTT client, acting as a publisher, will be run from the same system. It is possible to have the second client run on any other system connected to the same network. This client should take text strings from the terminal and pass it through to our controller.

Enough talk, let’s fight

The first step is going to be setting up our development environment. Ensure that you have installed the Arduino IDE, and take a moment to go through Adafruit's instructions on updating the WiFi firmware of your board. You will need to install the following libraries:

Arduino WiFi 101

Adafruit Protomatter OR Adafruit GFX (This will depend on wether you are using a TFT/OLED or RGB Matrix display)

Arduino-mqtt - https://github.com/256dpi/arduino-mqtt

This guide uses a python-based MQTT client. It could very well be based in any language, but here it is python. You will need a recent version of Python 3 installed (3.11+) and the paho-mqtt library. This can be installed from pip.

There is more to this project than uploading a sketch. You will also need to host and configure an MQTT broker. This is relatively simple to do, see https://mosquitto.org/download/ The “Management Center” enhancement is also recommended to make configuration easier. Technically any MQTT broker should work as long as one client can write to a topic, and one can subscribe. 

My setup is running Eclipse Mosquitto with docker on my linux box, but there is no issue running this on MacOS or Windows. Everything should generally work the same. 

Recall our architecture earlier - We have effectively two clients to write, and this must be reflected in the broker. Mosquitto provides for all sorts of access control and grouping functionality that are out of scope here. The most important settings are reflected in the screenshot below:

We will need to create a username, password, and client ID for each client. For better organization, they have been added to a group created called “signs”. We also have set them both as “Client” in the client role to allow for read/write access to topics from both clients. Practically speaking the ground_office client (representing the actual sign hung on the door) doesn't have a need to write and could be set to “topic_observe”.

The Sign Updater

The sign updater is a simple python script that will ask for the message, and then what topic it should be published to. 

The first section, Configuration Options:

from paho.mqtt import client as mqtt_client

# Configuration options

broker = "broker_address"

port = 1883

client_id = "Python Sign Updater"

username = "username"

password = "password"

These are the configuration settings to change. The broker address, port (assumed to be default), client id, username, and password. These last 3 were set above and should match what is configured there.

The second section just defines the method to initialize the connection and is largely based on Dekun Tao's example here. The final section grabs the necessary input and publishes it to the broker:

def run():
    print("\t****************************************")
    print("\t***         MQTT Sign Updater        ***")
    print("\t****************************************")

    client = connect_mqtt()
    client.loop_start()
    
    # Wait to make sure we are connected before proceeding
    while not client.is_connected():
        time.sleep(0.1)
        
    message = input("\nPlease enter brief message to display on sign: ")
    topic = input("\nPlease enter the topic the sign is subsribed to: ")
    pub_msg = client.publish(topic, message, qos=1)
    
    client.disconnect()
    client.loop_stop()

The final two lines just tell python to execute the run function when this python script is run.

The physical sign client (arduino)

The arduino code is a bit more involved, but it isn't too scary.

The first section addresses configuration options specific to the arduino and hardware configuration:

// Pin Definitions for the screen
#define D_CS A3
#define D_RST A4
#define D_DC A5
#define BROKER_IP "0.0.0.0" //IP address of MQTT broker
#define CLIENT_ID "Ground Office" //ID that will be used to connect to broker
#define UNAME "public" // Username that will be used to connect to broker
#define PASS "public" // Password that will be used to connect to broker
#define CLIENT_TOPIC "signs/gf_sign" // Configure what topic corresponds to this sign

#include <Adafruit_GFX.h> //Graphics library
#include <Adafruit_ST7789.h> //Hardware library for ST7789 display
#include <WiFi101.h> //WiFi library
#include <MQTT.h> // MQTT Client library
#include <SPI.h>

#include "arduino_secrets.h" //Used to store private wifi credentials

char ssid[] = SECRET_SSID;
char pass[] = SECRET_PASS;
int status = WL_IDLE_STATUS;

//Create the display
Adafruit_ST7789 display = Adafruit_ST7789(D_CS, D_DC, D_RST);

//Create WiFi device and MQTT client
WiFiClient wifi;
MQTTClient client;

Depending on the hardware used, configure the pins for the display, the IP address of the broker , client options (username, password, topic), load the necessary libraries and credentials for WiFi connections, and then create the component objects.

The setup function sets the pins used for the WiFi board in this example, and then begins the connection to the WiFi network and broker. The loop function doesn't really do much at all. It calls the MQTT client loop (to handle the client-broker communication and call the function that handles what to do when a message is received) and reconnects the sign to WiFi if it disconnects for some reason.

In the messaageReceived function, the arduino blanks the display and prints the payload. The library maintainer provides wonderful examples that were incorporated here.

GIF in action

What’s next?

This all started because I found a really cheap WiFi dev board on adafruit, and I wanted a sign for my office door. You'll notice the protomatter library wasn't actually used at all here - Part 1 experiments with a simple architecture but there are several improvements to be made:

What happens if WiFi drops? Connect() is not very smart because it just waits for a message. There's no logic to consider a temporary drop (out of range, goofyness, WiFi down, deauthentication attack) so it may be worthwhile to look into caching the message to display in these cases.

There's basically no security. Communication is sent in the clear and using the default configuration, TLS is not used. The entire stack will need updated to utilize TLS, and while this is not difficult, it is out of scope for part 1.

On the topic of security, it is worth investigating the Mosquitto configuration in more detail. Clients can have different roles configured that will correspond to levels of access on the broker. This access control list will determine which clients are allowed to write to specific topics vs who can just subscribe. The sign client should be configured with the least amount of access to the broker, in case it were hijacked the most credentials could do should be receiving messages.

The terminal application is not very robust. What happens in publish if something fails, or if the message isn't delivered correctly? The app should be better built to take in arguments so this can simply be called by other applications.

This is built on a tiny LCD screen. It needs to be built with an RGB matrix, and eventually some sort of consideration to a case must be made.

Clean up the arduino code. Arduino secrets should really become “config.h” and the pin values, mqtt stuff, etc, can be put there. It doesn't necessarily “do” anything but make the code cleaner.

Part 1 introduces the fundamentals of the tech stack to build a somewhat scalable sign updating solution, but future parts will incorporate security and build a more robust interface.

Previous
Previous

I love you, to the moon and back