Last active
January 3, 2021 01:24
-
-
Save meisterluk/d6945cb013091053b714d4b7b622401f to your computer and use it in GitHub Desktop.
Creating a firework with the Raspberry Pi 3 Model B Sense HAT 8×8 LED matrix and Lua 5.3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- load pixels.lua (attached as part of this gist) | |
package.path = package.path .. ';?.lua' | |
local hat = require('rpi_sensehat_pixels') | |
-- a generic color table | |
local colortable = { | |
[0] = '0000FF', | |
[1] = '00FF00', | |
[2] = 'FF0000', | |
[3] = '1354AA', | |
[4] = 'AAAAAA', | |
[5] = 'FFFF00', | |
[6] = '00FFFF', | |
[7] = 'FFCC33', | |
} | |
-- set pixel at led_id to color | |
local set_pixel = function (led_id, color) | |
local fd = io.open(hat.sense_hat_device_path(), "w") | |
fd:seek("set", 2 * (led_id - 1)) | |
fd:write(hat.color_to_bin(color)) | |
fd:close() | |
end | |
-- Convert a (x, y) point on this 8×8 LED matrix | |
-- into a LED id. The x=0 y=0 is at the bottom left | |
-- and x=1 y=0 is one to the right. | |
local xy_to_id = function (x, y) | |
return 57 + x - 8 * y | |
end | |
-- is the given point (x, y) on the circle with | |
-- center (cx, cy) and radius r? | |
local point_is_on_circle = function (cx, cy, r, x, y) | |
local in_big_circle = (x - cx) * (x - cx) + (y - cy) * (y - cy) - r * r <= 1 | |
local in_small_circle = (x - cx) * (x - cx) + (y - cy) * (y - cy) - (r-1) * (r-1) <= 1 | |
return in_big_circle and not in_small_circle | |
end | |
-- draw the explosion circle of a firework | |
-- with color and center (cx, cy) at time t ∈ {0, 1, …, 4} | |
local draw_explosion = function (color, cx, cy, t) | |
if t == 0 then | |
set_pixel(xy_to_id(cx, cy), color) | |
end | |
if t > 0 and t <= 4 then | |
for x = 0, 7 do | |
for y = 0, 7 do | |
if point_is_on_circle(cx, cy, t, x, y) then | |
set_pixel(xy_to_id(x, y), color) | |
end | |
end | |
end | |
end | |
end | |
-- draw a firework at some random pixel of Sense HAT | |
-- (actually, the rocket rises at least 2 pixels, | |
-- so it is not arbitrary) | |
local draw_firework = function () | |
hat.clear_pixels() | |
local cx = math.random(8) - 1 | |
local cy = math.random(6) + 1 | |
local color = colortable[math.random(8) - 1] | |
-- draw the rising rocket | |
for t = 0, (cy - 1) do | |
set_pixel(xy_to_id(cx, t), color) | |
hat.sleep(0.1) | |
hat.clear_pixels() | |
end | |
-- draw the explosion circle | |
for t = 0, 4 do | |
draw_explosion(color, cx, cy, t) | |
hat.clear_pixels() | |
end | |
end | |
for e = 0, 14 do | |
draw_firework() | |
hat.sleep(0.6) | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local M = {_TYPE='module', _NAME='sensehat_pixels', _VERSION='0.0.1'} | |
-- Sleep this thread | |
function sleep(n) | |
io.popen("sleep " .. tostring(n)):read() | |
end | |
-- Split a string into lines | |
function lines(str) | |
local t = {} | |
local function helper(line) | |
table.insert(t, line) | |
return "" | |
end | |
helper(str:gsub("(.-)\r?\n", helper)) | |
return t | |
end | |
-- Does file $path exist? | |
function file_exists(path) | |
local fd = io.open(path, "r") | |
if fd ~= nil then | |
io.close(fd) | |
return true | |
else | |
return false | |
end | |
end | |
-- List files and folders in $path | |
function list_directory(path) | |
return io.popen([[ls -1 ]] .. path):lines() | |
end | |
-- Find sense hat device | |
function sense_hat_device_path() | |
local target_name = "RPi-Sense FB" | |
for node in list_directory("/sys/class/graphics") do | |
if string.sub(node, 1, 2) == "fb" then | |
local name_path = "/sys/class/graphics/" .. node .. "/name" | |
if file_exists(name_path) then | |
local fd = io.open(name_path, "r") | |
local content = fd:read() | |
fd:close() | |
if content == target_name then | |
return "/dev/" .. node | |
end | |
end | |
end | |
end | |
end | |
-- Represent a given color as RGB565 byte string | |
function color_to_bin(rgb) | |
if rgb:len() ~= 6 then | |
error("color " .. rgb .. " is not a 6-digit hex color") | |
end | |
local r = tonumber(rgb:sub(1, 2), 16) | |
local g = tonumber(rgb:sub(3, 4), 16) | |
local b = tonumber(rgb:sub(5, 6), 16) | |
r = (r >> 3) & 0x1F | |
g = (g >> 2) & 0x3F | |
b = (b >> 3) & 0x1F | |
local comb = (r << 11) + (g << 5) + b | |
return string.pack("H", comb) | |
end | |
-- Given a string with 8 lines with length 8 each, | |
-- each byte represents a color. The byte to color | |
-- map is given by the second argument. | |
function pixel_from_picture(pic, map) | |
local data = {} | |
local lineno = 1 | |
for _, line in pairs(lines(pic)) do | |
if line:len() == 0 then | |
goto next | |
end | |
if line:len() ~= 8 then | |
error("line must have length 8") | |
end | |
for i = 1, #line do | |
local total_index = (lineno - 1) * 8 + i | |
if map[line:sub(i, i)] == nil then | |
error("character '" .. line:sub(i, i) .. "' could not be mapped to color") | |
end | |
data[total_index] = map[line:sub(i, i)] | |
end | |
lineno = lineno + 1 | |
::next:: | |
end | |
return data | |
end | |
function default_colors() | |
local color_table = { | |
["white"] = "FFFFFF", ["silver"] = "C0C0C0", ["gray"] = "808080", | |
["black"] = "000000", ["red"] = "FF0000", ["maroon"] = "800000", | |
["yellow"] = "FFFF00", ["olive"] = "808000", ["lime"] = "00FF00", | |
["green"] = "008000", ["aqua"] = "00FFFF", ["teal"] = "008080", | |
["blue"] = "0000FF", ["navy"] = "000080", ["fuchsia"] = "FF00FF", | |
["purple"] = "800080" | |
} | |
color_table[" "] = color_table["black"] | |
color_table["X"] = color_table["teal"] | |
color_table["0"] = color_table["white"] | |
color_table["1"] = color_table["blue"] | |
return color_table | |
end | |
function letter_pics() | |
return { | |
["E"] = [[ | |
000000 | |
000000 | |
00 | |
000000 | |
000000 | |
00 | |
000000 | |
000000 | |
]], | |
["H"] = [[ | |
00 00 | |
00 00 | |
00 00 | |
000000 | |
000000 | |
00 00 | |
00 00 | |
00 00 | |
]], | |
["I"] = [[ | |
00 | |
00 | |
00 | |
00 | |
00 | |
00 | |
00 | |
00 | |
]], | |
["L"] = [[ | |
00 | |
00 | |
00 | |
00 | |
00 | |
00 | |
000000 | |
000000 | |
]], | |
["O"] = [[ | |
0000 | |
000000 | |
00 00 | |
00 00 | |
00 00 | |
00 00 | |
000000 | |
0000 | |
]], | |
} | |
end | |
-- Given a map with indices 1..64, each index | |
-- is mapped to a color to be set on raspberry | |
-- pi's sense hat | |
function set_pixels(pixels) | |
local fd = io.open(sense_hat_device_path(), "w") | |
for idx, color in pairs(pixels) do | |
fd:seek("set", 2 * (idx - 1)) | |
fd:write(color_to_bin(color)) | |
end | |
end | |
-- clear pixels on the RPi Sense hat | |
function clear_pixels() | |
local color_table = {} | |
for i = 1, 64 do | |
color_table[i] = "000000" | |
end | |
set_pixels(color_table) | |
end | |
return { | |
sleep = sleep, | |
file_exists = file_exists, | |
list_directory = list_directory, | |
sense_hat_device_path = sense_hat_device_path, | |
color_to_bin = color_to_bin, | |
pixel_from_picture = pixel_from_picture, | |
default_colors = default_colors, | |
letter_pics = letter_pics, | |
set_pixels = set_pixels, | |
clear_pixels = clear_pixels | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment