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:
- Broker: broker.hivemq.com
- Port: 1883
- Topic: iot-rgb-led-matrix/clock
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
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.