Close

Free MQTT sample!

A project log for IoT RGB LED Matrix Controller (ESP32)

This project is about building and programming an ESP32 based controller for a 32x32 RGB LED matrix, controlled from Node-RED over MQTT

solenoidSolenoid 03/30/2018 at 19:250 Comments

Since this project requires an MQTT server and knowledge on how to set it up it makes the building/coding curve a bit steep at first.

I wanted to make this project more accessible and set up my own Node-RED server to publish, once a minute, the english word clock display to a public MQTT broker (HiveMQ) where one can point the matrix to test it:

I updated the dummy configuration file in GitHub repository so this will be the default setting. The time is UTC+1 (Swiss time), so unless one is in the same timezone the clock will be wrong… but good enough for a test.

The other issue with unprotected public MQTT brokers is that anybody can send data to the broker with the same topic name and then the matrix will behave strangely. I simply expect fair usage.

Once the matrix is set up and working one can make a private server. How to set up a Node-RED server is a bit out of the scope of this project, but the most important thing, after the set up is the flow design.

The Node-RED flow that generates the english word clock display looks like the following:

The configuration for the trigger:

The JavaScript code inside the "English word clock" function that generates this display:

var matrix_w = 32;
var matrix_h = 32;

var font_pixel = {
    "width": 3,
    "height": 5,
    " ": "000000000000000",
    "<": "001010100010001",
    ">": "100010001010100",
    "%": "100001010100001",
    "=": "000111000111000",
    ".": "000000000000010",
    ":": "000010000010000",
    "-": "000000111000000",
    "/": "001001101100100",
    "'": "010010000000000",
    "0": "011101101101110",
    "1": "010110010010010",
    "2": "110001010100111",
    "3": "110001010001110",
    "4": "101101111001001",
    "5": "111100110001110",
    "6": "011100111101111",
    "7": "111001010100100",
    "8": "111101111101111",
    "9": "111101111001110",
    "A": "010101111101101",
    "B": "110101110101110",
    "C": "011100100100011",
    "D": "110101101101110",
    "E": "111100111100111",
    "F": "111100111100100",
    "G": "011100111101110",
    "H": "101101111101101",
    "I": "111010010010111",
    "J": "001001001101010",
    "K": "101101110101101",
    "L": "100100100100111",
    "M": "101111111101101",
    "N": "101111111111101",
    "O": "010101101101010",
    "P": "110101110100100",
    "Q": "010101101111011",
    "R": "110101111110101",
    "S": "011100010001110",
    "T": "111010010010010",
    "U": "101101101101011",
    "V": "101101101010010",
    "W": "101101111111101",
    "X": "101101010101101",
    "Y": "101101010010010",
    "Z": "111001010100111",
    "a": "000110011101111",
    "b": "100110101101110",
    "c": "000011100100011",
    "d": "001011101101011",
    "e": "000011101110011",
    "è": "110011101110011",
    "f": "001010111010010",
    "g": "000011101011110",
    "h": "100110101101101",
    "i": "010000010010010",
    "j": "001000001001110",
    "k": "100101110110101",
    "l": "110010010010111",
    "m": "000111111111101",
    "n": "000110101101101",
    "o": "000010101101010",
    "p": "000110101110100",
    "q": "000011101011001",
    "r": "000011100100100",
    "s": "000011110011110",
    "t": "010111010010011",
    "u": "000101101101011",
    "v": "000101101111010",
    "w": "000101111111111",
    "x": "000101010010101",
    "y": "000101101010100",
    "z": "000111011110111",
};

// General canvas drawing function, draws a black image by default
function draw_canvas(callback)
{
    // Allocate screen data
    var data = new Array(32*32);
    
    // Initialise image data to black screen
    for(var i = 0; i < data.length; i++)
    {
        // Set chars instead of numbers
        data[i] = {"red": 0, "green": 0, "blue": 0};
    }
    
    // Call the callback to fill the rest
    data = (callback)(data);

    return data;
}

// Helper function to set a pixel value within limits
function setPixel(data, x, y, color)
{
    if(x >= 0 && x < matrix_w && y >= 0 && y < matrix_h)
    {
        data[y * matrix_w + x] = color;
    }
    
    return data;
}

function draw_letter(data, pos_x, pos_y, letter, color = {"red": 1, "green": 1, "blue": 1}, font_type = font_pixel)
{
    if(font_type[letter] === undefined)
    {
        return;
    }
    
    for(var y = font_type["height"] - 1; y >= 0; y--)
    {
        for(var x = font_type["width"] - 1; x >= 0; x--)
        {
            // If pixel is set draw it
            if(font_type[letter].charAt(y * font_type["width"] + x) == 1)
            {
                data = setPixel(data, pos_x + x, pos_y + y, color);
            }
        }
    }
    
    return data;
}

function draw_text(data, pos_x, pos_y, text, color = {"red": 1, "green": 1, "blue": 1}, font_type = font_pixel)
{
    // Return immediately when text is not provided
    if(!text)
    {
        return;
    }

    // For every letter draw it on the canvas
    for(var i = 0; i < text.length; i++)
    {
        // Add one pixel width between letters
        data = draw_letter(data, pos_x + i * (font_type["width"] + 1), pos_y, text[i], color, font_type);
    }
    
    return data;
}

function draw_english_clock(data)
{
    var d = new Date();

    // Get the hour
    var hour = d.getHours();
    var hour_text = ["midnight ", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven"];

    // Get the minutes
    var minute = d.getMinutes();
    var minute_text = ["", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "quarter", "sixteen", "seven- teen", "eighteen", "nineteen", "twenty", "twenty- one", "twenty- two", "twenty- three", "twenty- four", "twenty- five", "twenty- six", "twenty- seven", "twenty- eight", "twenty- nine", "half", "twenty- nine", "twenty- eight", "twenty- seven", "twenty- six", "twenty- five", "twenty- four", "twenty- three", "twenty- two", "twenty- one", "twenty", "nineteen", "eighteen", "seven- teen", "sixteen", "quarter", "fourteen", "thirteen", "twelve", "eleven", "ten", "nine", "eight", "seven", "six", "five", "four", "three", "two", "one"];

    // Construct the minute phrase
    var time_text = minute_text[minute]
    if(minute === 0)
    {
        // Exact hour is an exception
        time_text = hour_text[hour] + " o'clock";
    }
    else if(minute <= 30)
    {
        // Before and on 30 minutes it is past the current hour
        time_text += " past " + hour_text[hour];
    }
    else
    {
        // After 30 minutes it is "to" the next hour (modulo 24)
        var next_hour = (hour + 1) % 24;
        time_text += " to " + hour_text[next_hour];
    }
    time_text = time_text.split(" ");

    draw_text(data, 1, 1, "It is", {"red": 255, "green": 255, "blue": 255});
    draw_text(data, 1, 7, time_text[0], {"red": 255, "green": 255, "blue": 255});
    draw_text(data, 1, 13, time_text[1], {"red": 255, "green": 255, "blue": 255});
    draw_text(data, 1, 19, time_text[2], {"red": 255, "green": 255, "blue": 255});
    draw_text(data, 1, 25, time_text[3], {"red": 255, "green": 255, "blue": 255});

    return data;
}

msg.payload = draw_canvas(draw_english_clock);

return msg;

 The JavaScript code inside the "Compress data for stream" function that compresses the data for transfer:

var rgb = new Buffer(msg.payload.length * 2);

for(var i = 0; i < msg.payload.length; ++i)
{
    // Maximum values are 255, divide by 16 to get 4 bits per pixel
    var red = (msg.payload[i].red >> 4);
    var green = (msg.payload[i].green >> 4);
    var blue = (msg.payload[i].blue >> 4);
    // Send 2 bytes per pixel
    rgb[i*2] = (red & 0xff);
    rgb[i*2+1] = (green << 4) + (blue & 0xf);
}

msg.payload = rgb;

return msg;

The configuration of the broker in the last node must obviously be different if one implements it on their own server, at least don’t use the same topic name:

All this will get you started on your very own display styles and graphics.

Discussions