Use MQTT to control an RGB Matrix on an ESP32 (xkcd/927)

Following success in getting my ESP32-C3FH4-RGB board to display the current cheerlights colour

Turn an ESP32-C3FH4-RGB board into a Cheerlight using Arduino IDE

(and my failure to get it reliably to work using Micropython),   I wanted to make it a generic device that could be controlled over MQTT

So I uploaded this program (follow other post to get setup with correct libs)

//#include <ESP8266WiFi.h>

#include <WiFi.h>
#include <esp_task_wdt.h>

#include <PubSubClient.h>
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
 #include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif



// Update these with values suitable for your network.
const char* ssid = "xxx";
const char* password = "xxx";
const char* mqtt_server = "simplesi.cloud"; // use your own/public broker
int cheerColour = 255;
int loopCount = 0;
int pixelPos = 0;

#define mqtt_port 1883
#define MQTT_SERIAL_RECEIVER_CH "matrix/5x5"

//ESP32 Only but safe to leave in for all devices
#define WDT_TIMEOUT    10


  //Uncomment for ESP32-C3FH4-RGB
  #define LED_PIN        8
  #define LED_COUNT     25
  #define HEARTBEAT_PIN 10

  
  // Uncomment for WEMOS D1 mini with 4 NEOpixels
  //#define LED_PIN        4
  //#define LED_COUNT      4
  //#define HEARTBEAT_PIN  2

// How many NeoPixels are attached?


// NeoPixel brightness, 0 (min) to 255 (max)
#define BRIGHTNESS 10 // Set BRIGHTNESS to about 1/5 (max = 255)

// Declare our NeoPixel strip object:
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
// Argument 1 = Number of pixels in NeoPixel strip
// Argument 2 = Arduino pin number (most are valid)
// Argument 3 = Pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
//   NEO_RGBW    Pixels are wired for RGBW bitstream (NeoPixel RGBW products)


WiFiClient wifiClient;

PubSubClient client(wifiClient);

void setup_wifi() {
    delay(10);
    // We start by connecting to a WiFi network
    Serial.println();
    Serial.print("Connecting to ");
    Serial.print(ssid);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
    }
    randomSeed(micros());
    Serial.println("");
    Serial.print("WiFi connected.  ");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP32Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str())) {
      Serial.println("connected");
      //Once connected,...
      // ... and resubscribe
      client.subscribe(MQTT_SERIAL_RECEIVER_CH);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void callback(char* topic, byte *payload, unsigned int length) {
    //Serial.print("message on topic: ");
    //Serial.println(topic);
    //Serial.print("data:");  
    //Serial.write(payload, length);
    //Serial.print(" == ");
    pixelPos = -1;
    cheerColour = strip.Color(payload[0], payload[1], payload[2]);
    Serial.println(cheerColour);
    if (length == 3) {
      strip.fill(cheerColour);
    } else if (length == 4) {
      pixelPos = payload[3];
      strip.setPixelColor(pixelPos,cheerColour);
    } else if (length == 5) {
      pixelPos = payload[3] + (5 * payload[4]);
      strip.setPixelColor(pixelPos,cheerColour);
    } else if (length > 5) {
      for (int i = 0; i < (length / 3); i++) {
        strip.setPixelColor(i, strip.Color(payload[i * 3], payload[(i * 3) + 1], payload[(i * 3) + 2]));
      }
    }
    //Serial.println(pixelPos);
    strip.show();
    Serial.println(length);
    
}

void setup() {
  strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip.show();            // Turn OFF all pixels ASAP
  strip.setBrightness(BRIGHTNESS);
  pinMode(HEARTBEAT_PIN, OUTPUT);
  Serial.begin(115200);
  Serial.println("starting....");
  Serial.setTimeout(500);// Set time out for 
  setup_wifi();
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
  reconnect();
  Serial.begin(115200);
  //Serial.println("Configuring WDT...");
  esp_task_wdt_init(WDT_TIMEOUT, true); //enable panic so ESP32 restarts
  esp_task_wdt_add(NULL); //add current thread to WDT watch
}


int last = millis();
void loop() {
  client.loop();
  loopCount += 1;
  if (loopCount == 20000) {
    digitalWrite(HEARTBEAT_PIN, LOW);   // turn the LED on (HIGH is the voltage level)
  } 
  if (loopCount == 40000) {
    digitalWrite(HEARTBEAT_PIN, LOW);    // turn the LED off by making the voltage LOW
    loopCount = 0;
  }
  // resetting WDT every 5s
  if (millis() - last >= 5000) { // && i < 3) {
      //Serial.println("Resetting WDT...");
      esp_task_wdt_reset();
      last = millis();
  }
 }

The main change is that the program is looking for byte payloads that describe which pixel/pixels to change to a RGB colour

My xkcd/927

My new encoding/decoding standard is this

Payload length == 3 : change all pixels to the RGB value of the bytes

Payload length == 4: assume 4th byte contains pixel number – set that pixel to the RGB value of first 3 bytes

Payload length ==5: assume 4th byte= x co-ord, 5th byte= y co-ord and set that pixel to the RB value of first 3 bytes

Payload length >5: assume a sequence of 3 byte RGB values and apply them in sequence to the pixels of the matrix (useful for setting a pattern using one MQTT message)

If anyone has better ideas on how to do this or expand upon it – please let me know 🙂

Sending MQTT messages from Micropython running on a WEMOS D1 mini ESP8266

I thne wrote this Micropython program on another microcontroller, to convert received cheerlight colours and transcode them into a 5×5 matrix of the 1st letter of the colour and send it across to the ESP32-C3FH4-RGB

# Cheerlights - all pixels
# Uses the Cheerlights MQTT API to switch the LED colours to the
# current RGB value from Cheerlights
#
# Update via a Tweet with the content "#cheerlights <colour>"
# see https://cheerlights.com for more information
#
# blog: https://dev.to/andypiper/making-a-cheerdot-with-micropython-3ocf
#  slight mods by Simon Walters @cymplecy@fosstodon.org
# requires umqtt e.g.
#
# mpremote mip install umqtt.simple
# mpremote mip install umqtt.robust

import machine
import network
from umqtt.robust import MQTTClient
from machine import Pin
#from neopixel import NeoPixel
import time
wdt = None;
if wdt:
    from machine import WDT
    wdt = WDT(timeout=5000)  # enable it with a timeout of 5s
    wdt.feed()

# config network and broker
my_ssid = "xxx"
my_pass = "yyy"
cl_broker = "simplesi.cloud" # use your own or public broker as this is password protected for writing

#neopin = Pin(4, Pin.OUT)  # NeoPixel control on Pin 8
#pixels = 7  # we have 25 pixels, set as a constant here for loops
#np = NeoPixel(neopin, pixels)

# setup the status LED
status_led = Pin(2, Pin.OUT) #14 for WEMOS D1 clone
status_led.off()
#np.fill((0,0,0))
#np.write()
def do_connect():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.config(dhcp_hostname=client_name())
    if not wlan.isconnected():
        status_led.off()
        print("connecting to network...")
        wlan.connect(my_ssid, my_pass)
        while not wlan.isconnected():
            status_led.on()
            time.sleep(0.5)
            status_led.off()
            time.sleep(0.5)
            if wdt: wdt.feed()
    status_led.on()  # status LED shows connected
    print("network config:", wlan.ifconfig())


def cl_callback(topic, payload):
    #cl_rgb = hex_to_rgb(payload)
    #print(str(int(time.time() / 60)) + " message received: " + str(cl_rgb))
    print (payload)
    print (payload[0])
    data = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
    mqtt.publish("matrix/5x5",chr(0)+chr(0)+chr(0),True,1)
    cheerColourBytes = [255,0,0]
    if (payload.decode('utf-8') == "red"):
        data =[1,1,1,1,0, 1,0,0,0,1, 1,1,1,1,0, 1,0,0,1,0, 1,0,0,0,1]
        cheerColourBytes = [255,0,0]
        #for i in range(len(data)):
        #    if (data[i] == 1):
        #        mqtt.publish("matrix/5x5",bytearray([255,0,0,i]),True,1)
    elif (payload.decode('utf-8') == "green"):
        data =[1,1,1,1,0, 1,0,0,0,0, 1,0,1,1,0, 1,0,0,1,0, 1,1,1,1,0]
        cheerColourBytes = [0,255,0]
    elif (payload.decode('utf-8') == "blue"):
        data = [1,1,1,0,0, 1,0,0,1,0, 1,1,1,0,0, 1,0,0,1,0, 1,1,1,0,0]
        cheerColourBytes = [0,0,255]
    elif (payload.decode('utf-8') == "cyan"):
        data = [1,1,1,1,0, 1,0,0,0,0, 1,0,0,0,0, 1,0,0,0,0, 1,1,1,1,0]
        cheerColourBytes = [0,255,255]
    elif (payload.decode('utf-8') == "magenta"):
        data = [1,0,0,0,1, 1,1,0,1,1, 1,0,1,0,1, 1,0,0,0,1, 1,0,0,0,1]
        cheerColourBytes = [255,0,255]
    elif (payload.decode('utf-8') == "yellow"):
        data = [1,0,0,0,1, 0,1,0,1,0, 0,0,1,0,0, 0,0,1,0,0, 0,0,1,0,0]
        cheerColourBytes = [255,255,0]
    elif (payload.decode('utf-8') == "pink"):
        data = [1,1,1,1,0, 1,0,0,0,1, 1,1,1,1,0, 1,0,0,0,0, 1,0,0,0,0]
        cheerColourBytes = [255,203,192]
    elif (payload.decode('utf-8') == "purple"):
        data = [1,1,1,1,0, 1,0,0,0,1, 1,1,1,1,0, 1,0,0,0,0, 1,0,0,0,0]
        cheerColourBytes = [128,0,128]
    elif (payload.decode('utf-8') == "orange"):
        data = [0,1,1,0,0, 1,0,0,1,0, 1,0,0,1,0, 1,0,0,1,0, 0,1,1,0,0]
        cheerColourBytes = [255,128,0]
    elif (payload.decode('utf-8') == "oldlace"):
        data = [0,1,1,0,0, 1,0,0,1,0, 1,0,0,1,0, 1,0,0,1,0, 0,1,1,0,0]
        cheerColourBytes = [253,230,245]
    elif (payload.decode('utf-8') == "white"):
        data = [1,0,1,0,1, 1,0,1,0,1, 1,0,1,0,1, 1,0,1,0,1, 0,1,0,1,0]
        cheerColourBytes = [255,255,255]

    #code to convert 1/0 list above to triple bytearray values
    print(bytearray([num for elem in list(map(lambda x: [255,0,0] if (x == 1) else [0,0,0], data)) for num in elem]))
    mqtt.publish("matrix/5x5",bytearray([num for elem in list(map(lambda x: cheerColourBytes if (x == 1) else [0,0,0], data)) for num in elem]),True,1)



def client_name():
    m_id = machine.unique_id()
    client_id = (
        "{:02x}{:02x}{:02x}{:02x}".format(m_id[0], m_id[1], m_id[2], m_id[3])
        + "-cl-dot"
    )
    return client_id



# the main code
# connect to network and broker and subscribe to Cheerlights RGB topic

do_connect()
mqtt = MQTTClient(client_name(), cl_broker, user = "xxx", password = "xxx")
mqtt.connect()
mqtt.set_callback(cl_callback)
mqtt.subscribe("cheerlights/filtered")

count = 0;
while True:
    mqtt.check_msg()
    # heartbeat status led code if needed - note WEMOS D1 mini has inverted status light
    if (count == 2000):
        status_led.on()
    if (count == 4000):
        status_led.on()
        if wdt: wdt.feed()
        count = 0
    count += 1
    
    

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *