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
Recent Comments