Last active
August 29, 2015 14:03
-
-
Save blindman2k/61d6a57a35990dc05bc3 to your computer and use it in GitHub Desktop.
This is some old code we used to light up the 16x16 LED display. The image loading no longer works because we lost the PHP code which used ImageMagick to convert the JPEGs and animated GIFS into bitstreams. But the rest works fine.
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
// ------------------------------------------------------------------------ | |
// Takes a string, renders it and sends it to the device for display | |
text <- null; | |
function displayText(_text) { | |
device.send("text", _text); | |
text = _text; | |
} | |
// ------------------------------------------------------------------------ | |
// Takes a name of an animation and sends it to the device for rendering | |
animation <- null; | |
seed <- ""; | |
function displayAnimation(_animation, _seed=null) { | |
if (_animation != "none") { | |
device.send("clear", null); | |
url = null; | |
} | |
device.send("animate", {"animation":_animation, "seed":_seed}); | |
animation = _animation; | |
seed = _seed; | |
// Send the text overlay | |
if (text) displayText(text); | |
} | |
// ------------------------------------------------------------------------ | |
// Takes a url, sends it to the server to be converted. Reads the converted image | |
// and sends it on to the device as a blob of rgb bitmap frames. | |
url <- null; | |
function displayImage(_url) { | |
local convertUrl = "http://conversion.server.com/leddisplay/read.php"; | |
if (_url) animation = "none"; | |
server.log("Loading url: " + _url); | |
local params = {"url": _url}; | |
local res = http.get(convertUrl + "?" + http.urlencode(params)).sendasync(function(res) { | |
if (res.statuscode == 200) { // "OK" | |
server.log("Finished loading url: " + _url + " (" + res.body.len() + ")"); | |
if (res.body.len() == 0) { | |
server.log("The image returned from the conversion is blank. Rejecting it.") | |
return; | |
} else if (res.body.len() > 24000) { | |
server.log("This image is too big. Rejecting it.") | |
return; | |
} | |
try { | |
// Check the file is valid | |
if (res.body.len() < 12) { | |
server.log("This image is really too small. Rejecting it.") | |
return; | |
} | |
// Read the headers out of the file | |
local i = 0; | |
local f = res.body[i++] & 0xFF << 24 | res.body[i++] & 0xFF << 16 | res.body[i++] & 0xFF << 8 | res.body[i++] & 0xFF; | |
local w = res.body[i++] & 0xFF << 24 | res.body[i++] & 0xFF << 16 | res.body[i++] & 0xFF << 8 | res.body[i++] & 0xFF; | |
local h = res.body[i++] & 0xFF << 24 | res.body[i++] & 0xFF << 16 | res.body[i++] & 0xFF << 8 | res.body[i++] & 0xFF; | |
// Double check the size again | |
if (res.body.len() < 12+(f*w*h*3)) { | |
server.log("This image is too small. Rejecting it.") | |
return; | |
} else if (res.body.len() > 12+(f*w*h*3)) { | |
server.log("This image is too big but I am going to truncate it.") | |
} | |
// Clear the server | |
device.send("clear", null); | |
// Put each frame in a blob | |
for (local ff = 0; ff < f; ff++) { | |
local frame = blob(w*h*3); | |
for (local ii = 0; ii < w*h*3; ii++) { | |
frame.writen(res.body[i++], 'b'); | |
} | |
device.send("rgb", frame); | |
} | |
// Send the text overlay | |
if (text) displayText(text); | |
url = _url; | |
} catch (e) { | |
server.log(e); | |
} | |
} else { | |
server.log("Error " + res.statuscode + " loading " + _url); | |
} | |
}); | |
} | |
// ------------------------------------------------------------------------ | |
// Creates a web page for letting the user control the display. This is served off the agent URL. | |
// Available functions: upload an image (url), display text, clear screen. | |
http.onrequest(function (request,response) { | |
local newurl = null, newtext = null, newanimation = null; | |
if (request.method == "GET") { | |
server.log("GET REQUEST"); | |
if ("url" in request.query) newurl = request.query.url; | |
if ("text" in request.query) newtext = request.query.text; | |
if ("animation" in request.query) newanimation = request.query.animation; | |
if ("seed" in request.query) seed = request.query.seed; | |
} else if (request.method == "POST") { | |
local post = http.urldecode(request.body); | |
if ("url" in post) newurl = post.url; | |
if ("text" in post) newtext = post.text; | |
if ("animation" in post) newanimation = post.animation; | |
if ("seed" in post) seed = post.seed; | |
} | |
if (newurl != null) { | |
displayImage(newurl); | |
} else { | |
newurl = url ? url : ""; | |
} | |
if (newtext != null) { | |
displayText(newtext); | |
} else { | |
newtext = text ? text : ""; | |
} | |
if (newanimation != null) { | |
displayAnimation(newanimation, seed); | |
} else { | |
newanimation = animation ? animation : ""; | |
} | |
local html = ""; | |
html += "<html><head><meta name='viewport' content='width=device-width'></head><body>"; | |
html += "Set the animated image on the LED display.<br/>"; | |
html += "<form method='post'>"; | |
html += "<img height=160 width=160 src='" + newurl + "' /><br/>"; | |
local images = ["http://www.shoesuperstore.com.au/media/icons/Black.gif", | |
"http://www.mariowiki.com/images/e/e5/Animated_Yakumario.gif", | |
"http://fanart-central.net/avatars/67977.gif", | |
"http://i925.photobucket.com/albums/ad98/MarioBabyLuigi/Dinothingparty.gif", | |
"http://www.imagessharedstorage.com/files/g4xpdJ01QAokc04qH252QQk4li15NYAtSxZu4NlLOPtoElVLeOE/Seven_segment_display-animated1.gif", | |
"http://www.purchase.org/public/default/frontend/standard/images/subscribe-loader.gif", | |
"http://ak.imgfarm.com/images/cursormania/files/22/11246a.gif", | |
"http://www.gifs.net/Animation11/Sports/Track_and_Field/Child_runs_4.gif", | |
"http://moniabenini.com/mod/monia/img/running_dog.gif", | |
"http://www.123cursors.com/freecursors/9082.gif", | |
"http://www.freeonlinestuffs.com/images/cursors/flag-cursors.gif", | |
"https://si0.twimg.com/profile_images/513097239/JenStarkFavicon.gif", | |
"http://www.freewebs.com/al-smith/_traffic_light_p.gif", | |
"http://www.ljplus.ru/img4/p/i/pinwizz/runtr13.gif", | |
"http://zeldapower.com/forum/images/misc/fire.gif", | |
"http://i585.photobucket.com/albums/ss300/mndless/forum/cartman1.gif", | |
"http://files.backyardchickens.com/img/smilies/D.gif", | |
"http://downloads.totallyfreecursors.com/thumbnails/tail.gif", | |
"http://cdn.fupa.com/small/dupworld.gif", | |
"http://beta.scratch.mit.edu/static/site/galleries/thumbnails/15/5870.png", | |
"http://24.media.tumblr.com/f1fe83709fa1e9c265d755ca9383a5ce/tumblr_midbyoylyd1r6thx9o1_500.gif", | |
"http://forum.thefunmouse.com/images/smilies/EatCheese.gif", | |
"http://songbird61.mypldi.net/music/aninotes21.gif", | |
"http://www.unexplained-mysteries.com/forum/uploads/av-128070.gif", | |
"http://i623.photobucket.com/albums/tt313/Webswimmer12/dance_banana_not_1_.gif" | |
]; | |
for (local i = 0; i < images.len(); i++) { | |
html += "<a href='?url=" + images[i] + "'><img src='" + images[i] + "' width=32 height=32 /></a> "; | |
} | |
html += "<br/>\n"; | |
html += "Manual: <input name='url' size='100' value='" + newurl + "'><br/>"; | |
html += "<input type='submit'><br/><br/>"; | |
html += "</form>"; | |
html += "<form method='post'>"; | |
html += "Animation: <select name='animation'>"; | |
local animations = ["none", "clear", "life", "randomwalk", "matrix"]; | |
for (local i = 0; i < animations.len(); i++) { | |
local selected = (animations[i] == newanimation) ? "selected" : ""; | |
html += "<option value='" + animations[i] + "' " + selected + ">" + animations[i] + "</option>"; | |
} | |
html += "</select> "; | |
html += "Seed: <input name='seed' size='100' value='" + seed + "'><br/>"; | |
html += "<input type='submit'><br/><br/>"; | |
html += "</form>"; | |
html += "<form method='post'>"; | |
html += "Text overlay: <input name='text' size='100' value='" + newtext + "'><br/>"; | |
html += "<input type='submit'><br/><br/>"; | |
html += "</form>"; | |
html += "</body>"; | |
response.send(200, html); | |
}); | |
// ------------------------------------------------------------------------ | |
// This code executes when the device boots. | |
started <- false; | |
device.on("status", function (dummy=null) { | |
started = true; | |
}); | |
// ------------------------------------------------------------------------ | |
// This code is executed when the agent boots | |
server.log("Agent started! URL is " + http.agenturl()); | |
device.send("clear", null); |
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
// ----------------------------------------------------------------------------- | |
const MAGIC_STRING = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; | |
class ledDisplay { | |
width = 0; | |
height = 0; | |
speed = 5; | |
aniFrame = 0; | |
animation = 0; | |
aniBuffer = null; | |
animationSeed = null; | |
textBuffer = null; | |
lastFrame = 0; | |
// ......................................................................... | |
constructor(_width=16, _height=16) { | |
local before = imp.getmemoryfree(); | |
/* Hardware Configuration | |
The LED strings use a SPI-like CLK+Data protocol | |
and run up to 20MHz according to some Googling. | |
We request 15MHz since it gives us a little more | |
headroom. | |
*/ | |
hardware.configure(SPI_257); | |
hardware.spi.configure(SIMPLEX_TX, 15000); | |
width = _width; | |
height = _height; | |
aniBuffer = imageMap(width, height); | |
textBuffer = ""; | |
agent.on("clear", clear.bindenv(this)); | |
agent.on("rgb", loadRGB.bindenv(this)); | |
agent.on("text", loadText.bindenv(this)); | |
agent.on("animate", loadAnimation.bindenv(this)); | |
agent.send("status", "started"); | |
server.log("Device started!"); | |
// This makes power usage more but timing more accurate | |
imp.enableblinkup(true); | |
imp.wakeup(0, updateDisplay.bindenv(this)); | |
imp.wakeup(10, logFrameRate.bindenv(this)); | |
local after = imp.getmemoryfree(); | |
server.log("Before = " + before + ", used = " + (before - after) + ", after = " + after); | |
} | |
// ......................................................................... | |
function clear(dummy=null) { | |
local before = imp.getmemoryfree(); | |
server.log("Clear"); | |
aniBuffer.clear(); | |
textBuffer = ""; | |
local after = imp.getmemoryfree(); | |
server.log("Before = " + before + ", used = " + (before - after) + ", after = " + after); | |
} | |
// ......................................................................... | |
function loadText(text) { | |
local before = imp.getmemoryfree(); | |
server.log("Display text: " + text); | |
textBuffer = text; | |
local after = imp.getmemoryfree(); | |
server.log("Before = " + before + ", used = " + (before - after) + ", after = " + after); | |
} | |
// ......................................................................... | |
function loadRGB(rgb) { | |
local before = imp.getmemoryfree(); | |
animation = "image"; | |
animationSeed = null; | |
aniFrame = 0; | |
aniBuffer.loadAnimation("image"); | |
aniBuffer.addFrame(rgb); | |
local after = imp.getmemoryfree(); | |
server.log("Before = " + before + ", used = " + (before - after) + ", after = " + after); | |
} | |
// ......................................................................... | |
function loadAnimation(params) { | |
server.log("Starting animation: " + params.animation); | |
animation = params.animation; | |
animationSeed = params.seed; | |
aniFrame = 0; | |
} | |
// ......................................................................... | |
function updateDisplay() { | |
// Setup the next display event (putting it here makes it independant of the time to execute the code below) | |
imp.wakeup(1.0/speed, updateDisplay.bindenv(this)); | |
if (animation) { | |
// If we have an animation, then draw it. | |
aniBuffer.tick(aniFrame, animation, animationSeed); | |
} else { | |
aniBuffer.clear(); | |
} | |
// Overlay any text | |
if (textBuffer) { | |
overlayText(); | |
} | |
// Render the display | |
if (aniBuffer.changed) { | |
renderDisplay(); | |
} | |
aniFrame++; | |
} | |
// ......................................................................... | |
function overlayText() { | |
local w = textBuffer.len() * 6; | |
local x = width - aniFrame%(width + w); | |
local y = 0; | |
aniBuffer.addText(textBuffer, x, 3); | |
// Blink down the bottom to indicate frame rate | |
aniBuffer.setRGB(width-1, height-1, 0x00, 0xFF - ((aniFrame%0x0A) * 0x11), 0x00); | |
} | |
// ......................................................................... | |
function renderDisplay() { | |
// Magic String | |
// Before the LEDs will take new values they must be | |
// primed by sending a 0x00 byte. The number of | |
// zero bytes which must be sent it dependant on the | |
// length of the string, but is unclear exactly how. | |
// From experimentation 12 bytes is enough for a | |
// 16x16 display. We will make it larger since it | |
// no adverse affect other than a slightly longer | |
// transmit time | |
// Send the image | |
hardware.spi.write(MAGIC_STRING); | |
hardware.spi.write(aniBuffer.render()); | |
hardware.spi.write(MAGIC_STRING); | |
} | |
// ......................................................................... | |
function logFrameRate() { | |
server.log("Framerate: " + ((0.0 + aniFrame - lastFrame) / 10.0)); | |
// loadText(format("%0.1f fps", (0.0 + aniFrame - lastFrame) / 10.0)); | |
lastFrame = aniFrame; | |
imp.wakeup(10, logFrameRate.bindenv(this)); | |
} | |
} | |
// -------------------------------------------------------- | |
class imageMap { | |
width = 0; | |
height = 0; | |
rgb = null; | |
pointer_x = 0; | |
pointer_y = 0; | |
changed = false; | |
animations = null; | |
aniClass = null; | |
// ......................................................................... | |
constructor(_width=16, _height=16) { | |
width = _width; | |
height = _height; | |
rgb = blob(width * height * 3); | |
changed = false; | |
animations = {"clear": clear_animation, "image": image_animation, "randomwalk": randomwalk_animation, "life": life_animation, "matrix": matrix_animation }; | |
} | |
// ......................................................................... | |
function setRGB(x, y, r, g, b) { | |
if (x < 0 || x >= width || y < 0 || y >= height) return false; | |
// Shift origin to the top left | |
local _y = height-y-1; | |
// Reverse every second row | |
local _x = (_y % 2 == 0) ? x : width - x - 1; | |
// Gamma correction part | |
if (r != 0x00 && r != 0xFF) { | |
r = r.tofloat() / 255.0; | |
r = (63.5*(math.pow(r,3)+r)).tointeger(); | |
} | |
if (g != 0x00 && g != 0xFF) { | |
g = g.tofloat() / 255.0; | |
g = (63.5*(math.pow(g,3)+g)).tointeger(); | |
} | |
if (b != 0x00 && b != 0xFF) { | |
b = b.tofloat() / 255.0; | |
b = (63.5*(math.pow(b,3)+b)).tointeger(); | |
} | |
// Seek and write the buffer | |
rgb.seek((_y * width + _x) * 3, 'b'); | |
rgb.writen((g>>1) | 0x80, 'b'); | |
rgb.writen((r>>1) | 0x80, 'b'); | |
rgb.writen((b>>1) | 0x80, 'b'); | |
changed = true; | |
} | |
// ......................................................................... | |
function setHex(x, y, h) { | |
if (x < 0 || x >= width || y < 0 || y >= height) return false; | |
local _y = height-y-1; // Origin is bottom left | |
local _x = (_y % 2 == 0) ? x : width - x - 1; // Reverse every second row | |
rgb.seek((_y * width + _x) * 3, 'b'); | |
rgb.writen(h >> 9 | 0x80, 'b'); | |
rgb.writen(h >> 17 | 0x80, 'b'); | |
rgb.writen(h >> 1 | 0x80, 'b'); | |
changed = true; | |
} | |
// ......................................................................... | |
function clear() { | |
// Free up the animation | |
if (aniClass) { | |
aniClass.clear(); | |
aniClass = null; | |
} | |
// Initialise the rows with arrays of pixels | |
rgb.seek(0, 'b'); | |
for (local y = 0; y < height; y++) { | |
for (local x = 0; x < width; x++) { | |
rgb.writen(0x80, 'b'); | |
rgb.writen(0x80, 'b'); | |
rgb.writen(0x80, 'b'); | |
} | |
} | |
changed = true; | |
} | |
// ......................................................................... | |
function copy(src) { | |
// Initialise the rows with arrays of pixels | |
if (src) { | |
rgb = clone(src.rgb); | |
changed = true; | |
} | |
} | |
// ......................................................................... | |
function setPointer(x, y) { | |
pointer_x = x; | |
pointer_y = y; | |
} | |
// ......................................................................... | |
function movePointer(x=1, y=0, wrap_x=1, wrap_y=1) { | |
pointer_x += x; | |
pointer_y += y; | |
// Wrap to the right | |
if (pointer_x >= width) { | |
pointer_x = 0; | |
pointer_y += wrap_y; | |
// Wrap to the top | |
if (pointer_y >= height) { | |
pointer_y = 0; | |
// Wrap to the bottom | |
} else if (pointer_y < 0) { | |
pointer_y = height-1; | |
} | |
} | |
// Wrap to the left | |
if (pointer_x < 0) { | |
pointer_x = width-1; | |
pointer_y += wrap_y; | |
// Wrap to the top | |
if (pointer_y >= height) { | |
pointer_y = 0; | |
// Wrap to the bottom | |
} else if (pointer_y < 0) { | |
pointer_y = height-1; | |
} | |
} | |
// Wrap to the top | |
if (pointer_y >= height) { | |
pointer_y = 0; | |
pointer_x += wrap_x; | |
// Wrap to the right | |
if (pointer_x >= width) { | |
pointer_x = 0; | |
// Wrap to the left | |
} else if (pointer_x < 0) { | |
pointer_x = width-1; | |
} | |
} | |
// Wrap at the bottom | |
if (pointer_y < 0) { | |
pointer_y = height-1; | |
pointer_x += wrap_x; | |
// Wrap to the right | |
if (pointer_x >= width) { | |
pointer_x = 0; | |
// Wrap to the left | |
} else if (pointer_x < 0) { | |
pointer_x = width-1; | |
} | |
} | |
} | |
// ......................................................................... | |
function drawDot(r, g, b) { | |
setRGB(pointer_x, pointer_y, r, g, b); | |
} | |
// ......................................................................... | |
function addText(text, x = 0, y = 0, r = 0x00, g = 0x11, b = 0xFF) { | |
for (local i = 0; i < text.len(); i++) { | |
// Extract the letter | |
local c = asciiTo5x8(text[i]); | |
// For each column of pixels | |
for (local p = 0; p < 5; p++) { | |
// Skip over invisible pixels | |
if (x + p < 0 || x + p >= width) continue; | |
// For each pixel in the current column | |
for (local q = 0; q < 8; q++) { | |
// Skip over invisible pixels | |
if (y + 8 - q < 0 || y + 8 - q >= height) continue; | |
// Calculate the actual pixel value | |
local cs = c[p]; | |
local pixel = (cs >> q) & 0x01; | |
if (pixel == 0x01) { | |
setRGB(x + p, y + 8 - q, r, g, b); | |
} | |
} | |
} | |
// Move to the next letter | |
x += 6; | |
y += 0; | |
// Some basic optimisations | |
if (x >= width || y >= height) break; | |
} | |
} | |
// ......................................................................... | |
function asciiTo5x8(c) { | |
switch (c) { | |
case ' ': return [ 0x00,0x00,0x00,0x00,0x00]; // SP ----- -OO-- OO-OO ----- -O--- OO--O -O--- -OO-- | |
case '!': return [ 0xfa,0xfa,0x00,0x00,0x00]; // ! ----- -OO-- OO-OO -O-O- -OOO- OO--O O-O-- -OO-- | |
case '"': return [ 0xe0,0xc0,0x00,0xe0,0xc0]; // " ----- -OO-- O--O- OOOOO O---- ---O- O-O-- ----- | |
case '#': return [ 0x24,0x7e,0x24,0x7e,0x24]; // # ----- -OO-- ----- -O-O- -OO-- --O-- -O--- ----- | |
case '$': return [ 0x24,0xd4,0x56,0x48,0x00]; // $ ----- -OO-- ----- -O-O- ---O- -O--- O-O-O ----- | |
case '%': return [ 0xc6,0xc8,0x10,0x26,0xc6]; // % ----- ----- ----- OOOOO OOO-- O--OO O--O- ----- | |
case '&': return [ 0x6c,0x92,0x6a,0x04,0x0a]; // & ----- -OO-- ----- -O-O- --O-- O--OO -OO-O ----- | |
case '\'': return [ 0xc0,0xc0,0x00,0x00,0x00]; // ' ----- ----- ----- ----- ----- ----- ----- ----- | |
// | |
case '(': return [ 0x7c,0x82,0x00,0x00,0x00]; // ( ---O- -O--- ----- ----- ----- ----- ----- ----- | |
case ')': return [ 0x82,0x7c,0x00,0x00,0x00]; // ) --O-- --O-- -O-O- --O-- ----- ----- ----- ----O | |
case '*': return [ 0x10,0x7c,0x38,0x7c,0x10]; // * --O-- --O-- -OOO- --O-- ----- ----- ----- ---O- | |
case '+': return [ 0x10,0x10,0x7c,0x10,0x10]; // + --O-- --O-- OOOOO OOOOO ----- OOOOO ----- --O-- | |
case ',': return [ 0x06,0x07,0x00,0x00,0x00]; // , --O-- --O-- -OOO- --O-- ----- ----- ----- -O--- | |
case '-': return [ 0x10,0x10,0x10,0x10,0x10]; // - --O-- --O-- -O-O- --O-- -OO-- ----- -OO-- O---- | |
case '.': return [ 0x06,0x06,0x00,0x00,0x00]; // . ---O- -O--- ----- ----- -OO-- ----- -OO-- ----- | |
case '/': return [ 0x04,0x08,0x10,0x20,0x40]; // / ----- ----- ----- ----- --O-- ----- ----- ----- | |
// | |
case '0': return [ 0x7c,0x8a,0x92,0xa2,0x7c]; // 0 -OOO- --O-- -OOO- -OOO- ---O- OOOOO --OOO OOOOO | |
case '1': return [ 0x00,0x42,0xfe,0x02,0x00]; // 1 O---O -OO-- O---O O---O --OO- O---- -O--- ----O | |
case '2': return [ 0x46,0x8a,0x92,0x92,0x62]; // 2 O--OO --O-- ----O ----O -O-O- O---- O---- ---O- | |
case '3': return [ 0x44,0x92,0x92,0x92,0x6c]; // 3 O-O-O --O-- --OO- -OOO- O--O- OOOO- OOOO- --O-- | |
case '4': return [ 0x18,0x28,0x48,0xfe,0x08]; // 4 OO--O --O-- -O--- ----O OOOOO ----O O---O -O--- | |
case '5': return [ 0xf4,0x92,0x92,0x92,0x8c]; // 5 O---O --O-- O---- O---O ---O- O---O O---O -O--- | |
case '6': return [ 0x3c,0x52,0x92,0x92,0x8c]; // 6 -OOO- -OOO- OOOOO -OOO- ---O- -OOO- -OOO- -O--- | |
case '7': return [ 0x80,0x8e,0x90,0xa0,0xc0]; // 7 ----- ----- ----- ----- ----- ----- ----- ----- | |
// | |
case '8': return [ 0x6c,0x92,0x92,0x92,0x6c]; // 8 -OOO- -OOO- ----- ----- ---O- ----- -O--- -OOO- | |
case '9': return [ 0x60,0x92,0x92,0x94,0x78]; // 9 O---O O---O ----- ----- --O-- ----- --O-- O---O | |
case ':': return [ 0x36,0x36,0x00,0x00,0x00]; // : O---O O---O -OO-- -OO-- -O--- OOOOO ---O- O---O | |
case ';': return [ 0x36,0x37,0x00,0x00,0x00]; // ; -OOO- -OOOO -OO-- -OO-- O---- ----- ----O --OO- | |
case '<': return [ 0x10,0x28,0x44,0x82,0x00]; // < O---O ----O ----- ----- -O--- ----- ---O- --O-- | |
case '=': return [ 0x24,0x24,0x24,0x24,0x24]; // = O---O ---O- -OO-- -OO-- --O-- OOOOO --O-- ----- | |
case '>': return [ 0x82,0x44,0x28,0x10,0x00]; // > -OOO- -OO-- -OO-- -OO-- ---O- ----- -O--- --O-- | |
case '?': return [ 0x60,0x80,0x9a,0x90,0x60]; // ? ----- ----- ----- --O-- ----- ----- ----- ----- | |
// | |
case '@': return [ 0x7c,0x82,0xba,0xaa,0x78]; // @ -OOO- -OOO- OOOO- -OOO- OOOO- OOOOO OOOOO -OOO- | |
case 'A': return [ 0x7e,0x90,0x90,0x90,0x7e]; // A O---O O---O O---O O---O O---O O---- O---- O---O | |
case 'B': return [ 0xfe,0x92,0x92,0x92,0x6c]; // B O-OOO O---O O---O O---- O---O O---- O---- O---- | |
case 'C': return [ 0x7c,0x82,0x82,0x82,0x44]; // C O-O-O OOOOO OOOO- O---- O---O OOOO- OOOO- O-OOO | |
case 'D': return [ 0xfe,0x82,0x82,0x82,0x7c]; // D O-OOO O---O O---O O---- O---O O---- O---- O---O | |
case 'E': return [ 0xfe,0x92,0x92,0x92,0x82]; // E O---- O---O O---O O---O O---O O---- O---- O---O | |
case 'F': return [ 0xfe,0x90,0x90,0x90,0x80]; // F -OOO- O---O OOOO- -OOO- OOOO OOOOO O---- -OOO- | |
case 'G': return [ 0x7c,0x82,0x92,0x92,0x5c]; // G ----- ----- ----- ----- ----- ----- ----- ----- | |
// | |
case 'H': return [ 0xfe,0x10,0x10,0x10,0xfe]; // H O---O -OOO- ----O O---O O---- O---O O---O -OOO- | |
case 'I': return [ 0x82,0xfe,0x82,0x00,0x00]; // I O---O --O-- ----O O--O- O---- OO-OO OO--O O---O | |
case 'J': return [ 0x0c,0x02,0x02,0x02,0xfc]; // J O---O --O-- ----O O-O-- O---- O-O-O O-O-O O---O | |
case 'K': return [ 0xfe,0x10,0x28,0x44,0x82]; // K OOOOO --O-- ----O OO--- O---- O---O O--OO O---O | |
case 'L': return [ 0xfe,0x02,0x02,0x02,0x02]; // L O---O --O-- O---O O-O-- O---- O---O O---O O---O | |
case 'M': return [ 0xfe,0x40,0x20,0x40,0xfe]; // M O---O --O-- O---O O--O- O---- O---O O---O O---O | |
case 'N': return [ 0xfe,0x40,0x20,0x10,0xfe]; // N O---O -OOO- -OOO- O---O OOOOO O---O O---O -OOO- | |
case 'O': return [ 0x7c,0x82,0x82,0x82,0x7c]; // O ----- ----- ----- ----- ----- ----- ----- ----- | |
// | |
case 'P': return [ 0xfe,0x90,0x90,0x90,0x60]; // P OOOO- -OOO- OOOO- -OOO- OOOOO O---O O---O O---O | |
case 'Q': return [ 0x7c,0x82,0x92,0x8c,0x7a]; // Q O---O O---O O---O O---O --O-- O---O O---O O---O | |
case 'R': return [ 0xfe,0x90,0x90,0x98,0x66]; // R O---O O---O O---O O---- --O-- O---O O---O O-O-O | |
case 'S': return [ 0x64,0x92,0x92,0x92,0x4c]; // S OOOO- O-O-O OOOO- -OOO- --O-- O---O O---O O-O-O | |
case 'T': return [ 0x80,0x80,0xfe,0x80,0x80]; // T O---- O--OO O--O- ----O --O-- O---O O---O O-O-O | |
case 'U': return [ 0xfc,0x02,0x02,0x02,0xfc]; // U O---- O--O- O---O O---O --O-- O---O -O-O- O-O-O | |
case 'V': return [ 0xf8,0x04,0x02,0x04,0xf8]; // V O---- -OO-O O---O -OOO- --O-- -OOO- --O-- -O-O- | |
case 'W': return [ 0xfc,0x02,0x3c,0x02,0xfc]; // W ----- ----- ----- ----- ----- ----- ----- ----- | |
// | |
case 'X': return [ 0xc6,0x28,0x10,0x28,0xc6]; // O O---O O---O OOOOO -OOO- ----- -OOO- --O-- ----- | |
case 'Y': return [ 0xe0,0x10,0x0e,0x10,0xe0]; // Y O---O O---O ----O -O--- O---- ---O- -O-O- ----- | |
case 'Z': return [ 0x86,0x8a,0x92,0xa2,0xc2]; // Z -O-O- O---O ---O- -O--- -O--- ---O- O---O ----- | |
case '[': return [ 0xfe,0x82,0x82,0x00,0x00]; // [ --O-- -O-O- --O-- -O--- --O-- ---O- ----- ----- | |
case '\\': return [ 0x40,0x20,0x10,0x08,0x04]; // \ -O-O- --O-- -O--- -O--- ---O- ---O- ----- ----- | |
case ']': return [ 0x82,0x82,0xfe,0x00,0x00]; // ] O---O --O-- O---- -O--- ----O ---O- ----- ----- | |
case '^': return [ 0x20,0x40,0x80,0x40,0x20]; // ^ O---O --O-- OOOOO -OOO- ----- -OOO- ----- OOOOO | |
case '_': return [ 0x02,0x02,0x02,0x02,0x02]; // _ ----- ----- ----- ----- ----- ----- ----- ----- | |
// | |
case '`': return [ 0xc0,0xe0,0x00,0x00,0x00]; // ` -OO-- ----- O---- ----- ----O ----- --OOO ----- | |
case 'a': return [ 0x04,0x2a,0x2a,0x2a,0x1e]; // a -OO-- ----- O---- ----- ----O ----- -O--- ----- | |
case 'b': return [ 0xfe,0x22,0x22,0x22,0x1c]; // b --O-- -OOO- OOOO- -OOO- -OOOO -OOO- -O--- -OOOO | |
case 'c': return [ 0x1c,0x22,0x22,0x22,0x14]; // c ----- ----O O---O O---O O---O O---O OOOO- O---O | |
case 'd': return [ 0x1c,0x22,0x22,0x22,0xfc]; // d ----- -OOOO O---O O---- O---O OOOO- -O--- O---O | |
case 'e': return [ 0x1c,0x2a,0x2a,0x2a,0x10]; // e ----- O---O O---O O---O O---O O---- -O--- -OOOO | |
case 'f': return [ 0x10,0x7e,0x90,0x90,0x80]; // f ----- -OOOO OOOO- -OOO- -OOOO -OOO- -O--- ----O | |
case 'g': return [ 0x18,0x25,0x25,0x25,0x3e]; // g ----- ----- ----- ----- ----- ----- ----- -OOO- | |
// | |
case 'h': return [ 0xfe,0x10,0x10,0x10,0x0e]; // h O---- -O--- ----O O---- O---- ----- ----- ----- | |
case 'i': return [ 0x00,0x00,0xbe,0x02,0x00]; // i O---- ----- ----- O---- O---- ----- ----- ----- | |
case 'j': return [ 0x02,0x01,0x01,0x21,0xbe]; // j O---- -O--- ---OO O--O- O---- OO-O- OOOO- -OOO- | |
case 'k': return [ 0xfe,0x08,0x14,0x22,0x00]; // k OOOO- -O--- ----O O-O-- O---- O-O-O O---O O---O | |
case 'l': return [ 0x00,0x00,0xfe,0x02,0x00]; // l O---O -O--- ----O OO--- O---- O-O-O O---O O---O | |
case 'm': return [ 0x3e,0x20,0x18,0x20,0x1e]; // m O---O -O--- ----O O-O-- O---- O---O O---O O---O | |
case 'n': return [ 0x3e,0x20,0x20,0x20,0x1e]; // n O---O -OO-- O---O O--O- OO--- O---O O---O -OOO- | |
case 'o': return [ 0x1c,0x22,0x22,0x22,0x1c]; // o ----- ----- -OOO- ----- ----- ----- ----- ----- | |
// | |
case 'p': return [ 0x3f,0x22,0x22,0x22,0x1c]; // p ----- ----- ----- ----- ----- ----- ----- ----- | |
case 'q': return [ 0x1c,0x22,0x22,0x22,0x3f]; // q ----- ----- ----- ----- -O--- ----- ----- ----- | |
case 'r': return [ 0x22,0x1e,0x22,0x20,0x10]; // r OOOO- -OOOO O-OO- -OOO- OOOO- O--O- O---O O---O | |
case 's': return [ 0x12,0x2a,0x2a,0x2a,0x04]; // s O---O O---O -O--O O---- -O--- O--O- O---O O---O | |
case 't': return [ 0x20,0x7c,0x22,0x22,0x04]; // t O---O O---O -O--- -OOO- -O--- O--O- O---O O-O-O | |
case 'u': return [ 0x3c,0x02,0x04,0x3e,0x00]; // u O---O O---O -O--- ----O -O--O O-OO- -O-O- OOOOO | |
case 'v': return [ 0x38,0x04,0x02,0x04,0x38]; // v OOOO- -OOOO OOO-- OOOO- --OO- -O-O- --O-- -O-O- | |
case 'w': return [ 0x3c,0x06,0x0c,0x06,0x3c]; // w O---- ----O ----- ----- ----- ----- ----- ----- | |
// | |
case 'x': return [ 0x22,0x14,0x08,0x14,0x22]; // x ----- ----- ----- ---OO --O-- OO--- -O-O- -OO-- | |
case 'y': return [ 0x39,0x05,0x06,0x3c,0x00]; // y ----- ----- ----- --O-- --O-- --O-- O-O-- O--O- | |
case 'z': return [ 0x26,0x2a,0x2a,0x32,0x00]; // z O---O O--O- OOOO- --O-- --O-- --O-- ----- O--O- | |
case '{': return [ 0x10,0x7c,0x82,0x82,0x00]; // { -O-O- O--O- ---O- -OO-- ----- --OO- ----- -OO-- | |
case '|': return [ 0xee,0x00,0x00,0x00,0x00]; // | --O-- O--O- -OO-- --O-- --O-- --O-- ----- ----- | |
case '}': return [ 0x82,0x82,0x7c,0x10,0x00]; // } -O-O- -OOO- O---- --O-- --O-- --O-- ----- ----- | |
case '~': return [ 0x40,0x80,0x40,0x80,0x00]; // ~ O---O --O-- OOOO- ---OO --O-- OO--- ----- ----- | |
case '_': return [ 0x60,0x90,0x90,0x60,0x00]; // _ ----- OO--- ----- ----- ----- ----- ----- ----- | |
// | |
default: return [ 0x00,0x00,0x00,0x00,0x00]; // | |
} | |
} | |
// ......................................................................... | |
function loadAnimation(type, seed = null) { | |
if (aniClass == null || aniClass.changed(type, seed)) { | |
aniClass = animations[type](width, height, seed); | |
} | |
} | |
// ......................................................................... | |
function addFrame(frame) { | |
if (aniClass) aniClass.addFrame(frame); | |
} | |
// ......................................................................... | |
function tick(aniFrame, type="clear", seed=null) { | |
// Load the aniClass if it is required. | |
if (aniClass == null || aniClass.changed(type, seed)) { | |
if (type in animations) { | |
loadAnimation(type, seed); | |
// Update the LCD screen | |
lcd.write(0, type); | |
lcd.write(1, width + "x" + height); | |
} else { | |
return; | |
} | |
} | |
// Now animate the aniClass and draw it | |
if (aniClass) { | |
aniClass.tick(aniFrame); | |
aniClass.draw(this); | |
} | |
} | |
// ......................................................................... | |
function render() { | |
changed = false; | |
return rgb; | |
} | |
} | |
// -------------------------------------------------------- | |
class animation { | |
width = 0; | |
height = 0; | |
type = null; | |
seed = null; | |
constructor(_width, _height, _seed) { | |
width = _width; | |
height = _height; | |
seed = _seed; | |
type = "base"; | |
} | |
function clear() { | |
width = 0; | |
height = 0; | |
type = null; | |
seed = null; | |
} | |
function changed(_type, _seed) { | |
return (type != _type || seed != _seed); | |
} | |
function tick(frame=0) { | |
} | |
function draw(canvas) { | |
canvas.clear(); | |
} | |
function addFrame(frame) { | |
} | |
} | |
// -------------------------------------------------------- | |
class clear_animation extends animation { | |
constructor(_width, _height, _seed) { | |
base.constructor(_width, _height, _seed); | |
type = "clear"; | |
} | |
} | |
// -------------------------------------------------------- | |
class randomwalk_animation extends animation { | |
constructor(_width, _height, _seed) { | |
base.constructor(_width, _height, _seed); | |
type = "randomwalk"; | |
} | |
function draw(canvas) { | |
canvas.clear(); | |
canvas.movePointer(math.rand()%3-1, math.rand()%3-1); | |
canvas.drawDot(0xFF, 0x00, 0x00); | |
} | |
} | |
// -------------------------------------------------------- | |
class image_animation extends animation { | |
frameBuffer = null; | |
next = null; | |
constructor(_width, _height, _seed) { | |
base.constructor(_width, _height, _seed); | |
type = "image"; | |
frameBuffer = []; | |
} | |
function addFrame(rgb) { | |
// Read the blob header (frames, width, height) | |
if (typeof rgb != "blob") return; | |
// Read the data and copy it into the buffer for display | |
rgb.seek(0, 'b'); | |
local frame = imageMap(width, height); | |
for (local y = 0; y < height; y++) { | |
for (local x = 0; x < width; x++) { | |
local r = rgb.readn('b'); | |
local g = rgb.readn('b'); | |
local b = rgb.readn('b'); | |
frame.setRGB(x, y, r, g, b); | |
} | |
} | |
frameBuffer.push(frame); | |
} | |
function clear() { | |
base.clear(); | |
frameBuffer.clear(); | |
next = null; | |
} | |
function tick(aniFrame = 0) { | |
local frameId = aniFrame % frameBuffer.len(); | |
if (frameId in frameBuffer) next = frameBuffer[frameId]; | |
else next = null; | |
} | |
function draw(canvas) { | |
canvas.copy(next); | |
} | |
} | |
// -------------------------------------------------------- | |
class matrix_animation extends animation { | |
vectors = null; | |
tail = 7; | |
constructor(_width, _height, _seed) { | |
base.constructor(_width, _height, _seed); | |
type = "matrix"; | |
vectors = []; | |
for (local i = 0; i < width; i++) { | |
local vector = {"pos": -1, "speed": 1, "color": {"r": 0x00, "g": 0xFF, "b": 0x00}}; | |
vectors.push(vector); | |
} | |
} | |
function clear() { | |
base.clear(); | |
vectors.clear(); | |
vectors = null; | |
} | |
function tick(aniFrame = 0) { | |
for (local i = 0; i < vectors.len(); i++) { | |
local step = aniFrame % vectors[i].speed == 0 ? 1 : 0; | |
vectors[i].pos = (vectors[i].pos + step) % (height+tail); | |
if (vectors[i].pos == 0) { | |
vectors[i].speed = 1 + (math.rand() % 4); | |
switch (seed) | |
{ | |
case "rainbow": | |
vectors[i].color.r = math.rand() % 0xFF; | |
vectors[i].color.g = math.rand() % 0xFF; | |
vectors[i].color.b = math.rand() % 0xFF; | |
break; | |
case "red": | |
vectors[i].color.r = 0xFF; | |
vectors[i].color.g = 0x00; | |
vectors[i].color.b = 0x00; | |
break; | |
case "blue": | |
vectors[i].color.r = 0x00; | |
vectors[i].color.g = 0x00; | |
vectors[i].color.b = 0xFF; | |
break; | |
case "green": | |
default: | |
vectors[i].color.r = 0x00; | |
vectors[i].color.g = 0xFF; | |
vectors[i].color.b = 0x00; | |
break; | |
} | |
} | |
} | |
} | |
function draw(canvas) { | |
for (local x = 0; x < vectors.len(); x++) { | |
for (local y = 0; y <= height; y++) | |
{ | |
local intensity = tail-vectors[x].pos+y; | |
local color = {"r": 0x00, "g": 0x00, "b": 0x00}; | |
if (intensity >= 0 && intensity <= tail) { | |
color.r = vectors[x].color.r * intensity / tail; | |
color.g = vectors[x].color.g * intensity / tail; | |
color.b = vectors[x].color.b * intensity / tail; | |
} | |
canvas.setRGB(x, y, color.r, color.g, color.b); | |
} | |
canvas.setRGB(x, vectors[x].pos, 0xFF, 0xFF, 0xFF); | |
} | |
} | |
} | |
// -------------------------------------------------------- | |
class life_animation extends animation { | |
matrix = null; | |
matrix1 = null; | |
matrix2 = null; | |
signatures = null; | |
color = 0xFF0000; | |
constructor(_width, _height, _seed) { | |
base.constructor(_width, _height, _seed); | |
type = "life"; | |
// Make the main matrix | |
matrix1 = []; | |
for (local x = 0; x < width; x++) { | |
local row = []; | |
for (local y = 0; y <height; y++) { | |
row.push(false); | |
} | |
matrix1.push(row); | |
} | |
// Make the secondary matrix | |
matrix2 = []; | |
for (local x = 0; x < width; x++) { | |
local row = []; | |
for (local y = 0; y <height; y++) { | |
row.push(false); | |
} | |
matrix2.push(row); | |
} | |
// Assign the primary as the initial matrix | |
matrix = matrix1; | |
// Clear the history of signatures | |
signatures = []; | |
} | |
function clear() { | |
matrix = null; | |
matrix1 = null; | |
matrix2 = null; | |
signatures = null; | |
color = 0xFF0000; | |
} | |
function reset(populate = false) { | |
// Choose a drawing color | |
color = (math.rand() % 0xFF << 16) | (math.rand() % 0xFF << 8) | (math.rand() % 0xFF); | |
// Blank all the values | |
for (local x = 0; x < width; x++) { | |
for (local y = 0; y <height; y++) { | |
matrix[x][y] = false; | |
} | |
} | |
// Populate it with a starting lineup | |
if (populate) { | |
local rx = math.rand(); | |
local ry = math.rand(); | |
switch (seed) { | |
case "pentomino": | |
// R-pentomino | |
matrix[(rx+0) % width][(ry+1) % height] = true; | |
matrix[(rx+1) % width][(ry+0) % height] = true; | |
matrix[(rx+1) % width][(ry+1) % height] = true; | |
matrix[(rx+1) % width][(ry+2) % height] = true; | |
matrix[(rx+2) % width][(ry+0) % height] = true; | |
break; | |
case "glider": | |
// Glider | |
matrix[(rx+0) % width][(ry+2) % height] = true; | |
matrix[(rx+1) % width][(ry+2) % height] = true; | |
matrix[(rx+2) % width][(ry+2) % height] = true; | |
matrix[(rx+2) % width][(ry+1) % height] = true; | |
matrix[(rx+1) % width][(ry+0) % height] = true; | |
break; | |
case "toad": | |
// Toad | |
matrix[(rx+0) % width][(ry+0) % height] = true; | |
matrix[(rx+1) % width][(ry+0) % height] = true; | |
matrix[(rx+2) % width][(ry+0) % height] = true; | |
matrix[(rx+1) % width][(ry+1) % height] = true; | |
matrix[(rx+2) % width][(ry+1) % height] = true; | |
matrix[(rx+3) % width][(ry+1) % height] = true; | |
break; | |
case "beehive": | |
// Beehive | |
matrix[(rx+0) % width][(ry+1) % height] = true; | |
matrix[(rx+0) % width][(ry+2) % height] = true; | |
matrix[(rx+1) % width][(ry+0) % height] = true; | |
matrix[(rx+2) % width][(ry+1) % height] = true; | |
matrix[(rx+2) % width][(ry+2) % height] = true; | |
matrix[(rx+1) % width][(ry+3) % height] = true; | |
break; | |
case "": | |
case "demo": | |
// Glider | |
matrix[1][4] = true; | |
matrix[2][4] = true; | |
matrix[3][4] = true; | |
matrix[3][3] = true; | |
matrix[2][2] = true; | |
// Toad | |
matrix[10][4] = true; | |
matrix[11][4] = true; | |
matrix[12][4] = true; | |
matrix[11][5] = true; | |
matrix[12][5] = true; | |
matrix[13][5] = true; | |
// Beehive | |
matrix[0][9] = true; | |
matrix[0][10] = true; | |
matrix[1][8] = true; | |
matrix[2][9] = true; | |
matrix[2][10] = true; | |
matrix[1][11] = true; | |
// Blinker | |
matrix[4][12] = true; | |
matrix[4][13] = true; | |
matrix[4][14] = true; | |
// Aberations | |
matrix[14][14] = true; | |
matrix[14][15] = true; | |
matrix[15][14] = true; | |
matrix[15][15] = true; | |
break; | |
default: | |
try { | |
if (seed.tointeger() > 0) { | |
for (local i = 0; i < seed.tointeger(); i++) { | |
matrix[math.rand()%width][math.rand()%height] = true; | |
} | |
} | |
} catch (e) { | |
for (local i = 0; i < math.rand()%100; i++) { | |
matrix[math.rand()%width][math.rand()%height] = true; | |
} | |
} | |
} | |
} | |
} | |
function check_stuck() { | |
if (signatures == null || signatures.len() < 2) return false; | |
// Check if any signatures match | |
for (local i = 0; i < signatures.len(); i++) { | |
for (local j = i+1; j < signatures.len(); j++) { | |
if (signatures[i] == signatures[j]) { | |
// We have two identical signatures. Exit; | |
signatures.clear(); | |
return true; | |
} | |
} | |
} | |
// Let's keep the signatures under control | |
if (signatures.len() > 30) { | |
signatures = signatures.slice(-30); | |
} | |
return false; | |
} | |
function iterate() { | |
// Iterate over all the pixels and create the next iteration. | |
local current = matrix; | |
local next = (matrix == matrix1) ? matrix2 : matrix1; | |
local signature = ""; | |
for (local x = 0; x < width; x++) { | |
for (local y = 0; y <height; y++) { | |
local neighbours = count_neighbours(current, x, y); // find neighbours | |
if (neighbours == 2) next[x][y] = current[x][y]; // 2 - stay same | |
else if (neighbours == 3) next[x][y] = true; // 3 - come alive/stay alive | |
else next[x][y] = false; // die off/stay dead | |
signature += next[x][y] ? "." : "o"; | |
} | |
} | |
// Store the signature | |
signatures.push(signature); | |
// Flip the matrices | |
matrix = next; | |
} | |
function count_neighbours(matrix, x, y) { | |
local neighbours = 0; | |
for (local i = -1; i <= 1; i++) { | |
for (local j = -1; j <= 1; j++) { | |
if (i != 0 || j != 0) { | |
local a = (x + i + width) % 16; | |
local b = (y + j + height) % 16; | |
if (matrix[a][b]) neighbours++; | |
} | |
} | |
} | |
return neighbours; | |
} | |
function draw(canvas) { | |
for (local x = 0; x < width; x++) { | |
for (local y = 0; y < height; y++) { | |
canvas.setHex(x, y, matrix[x][y] ? color : 0x00); | |
} | |
} | |
} | |
function tick(aniFrame = 0) { | |
if (check_stuck()) { | |
reset(true); | |
} else { | |
iterate(); | |
} | |
} | |
} | |
// -------------------------------------------------------- | |
// LCD register | |
const IOEXP_ADDR = 0x40; | |
const LCD_CLEARDISPLAY = 0x01; | |
const LCD_RETURNHOME = 0x02; | |
const LCD_ENTRYMODESET = 0x04; | |
const LCD_DISPLAYCONTROL = 0x08; | |
const LCD_CURSORSHIFT = 0x10; | |
const LCD_FUNCTIONSET = 0x20; | |
const LCD_SETCGRAMADDR = 0x40; | |
const LCD_SETDDRAMADDR = 0x80; | |
const CMD = 0; | |
const DATA = 2; | |
const E = 4; | |
class lcdDisplay { | |
// ......................................................................... | |
constructor() { | |
// Init IO expander | |
hardware.configure(I2C_12); | |
hardware.i2c12.write(IOEXP_ADDR, "\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00"); | |
// All outputs | |
hardware.i2c12.write(IOEXP_ADDR, "\x00\x00"); | |
// Enable 4 bit mode on display | |
setpins(0x14); | |
setpins(0x10); | |
imp.sleep(0.1); | |
// Set up display | |
send(LCD_FUNCTIONSET | 0x08, CMD); | |
imp.sleep(0.1); | |
// Display on | |
send(LCD_CLEARDISPLAY, CMD); | |
imp.sleep(0.1); | |
send(LCD_RETURNHOME, CMD); | |
imp.sleep(0.2); | |
// Display on | |
send(LCD_DISPLAYCONTROL | 0x07, CMD); | |
imp.sleep(0.1); | |
// Blank the display | |
write(0, " \x00"); | |
write(1, " \x00"); | |
} | |
// ......................................................................... | |
function setpins(p) { | |
// 0x80 = backlight on | |
hardware.i2c12.write(IOEXP_ADDR, format("\x09%c", p | 0x80)); | |
} | |
// ......................................................................... | |
function send(value, mode) { | |
// High nibble first | |
local buf = (value & 0xf0) >> 1; | |
// Send with and without enable | |
setpins(buf | mode | E); | |
setpins(buf | mode); | |
// Now low bits | |
local buf = (value & 0x0f) << 3; | |
// Send with and without enable | |
setpins(buf | mode | E); | |
setpins(buf | mode); | |
} | |
// ......................................................................... | |
function write(line, s) { | |
send(LCD_SETDDRAMADDR | (line?0x40:0), CMD); | |
s = format("%-16s", s); | |
foreach(c in s) send(c, DATA); | |
imp.sleep(0.1); | |
} | |
} | |
// ----------------------------------------------------------------------------- | |
led <- ledDisplay(16, 16); | |
lcd <- lcdDisplay(); |
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
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); | |
// This is a CodeIgniter controller so requires all of CodeIgniter to be there. | |
class LEDDisplay extends CI_Controller { | |
public function read() | |
{ | |
$debug = isset($_REQUEST['debug']); | |
$height = isset($_REQUEST['height']) ? $_REQUEST['height'] : 8; | |
$width = isset($_REQUEST['width']) ? $_REQUEST['width'] : 40; | |
$url = "http://www.imagessharedstorage.com/files/g4xpdJ01QAokc04qH252QQk4li15NYAtSxZu4NlLOPtoElVLeOE/Seven_segment_display-animated1.gif"; | |
$url = isset($_REQUEST['url']) ? $_REQUEST['url'] : $url; | |
$hash = md5($url); | |
if (!$debug) { | |
header("Content-type: application/octet-stream"); | |
header('Content-Disposition: attachment; filename="image.rgb"'); | |
} | |
// Load the cache if its not already loaded | |
if (!is_readable($hash)) { | |
mkdir($hash); | |
// Create a new imagick object and read in the image | |
file_put_contents("$hash/frame_XXX_o.gif", file_get_contents($url)); | |
$img = new Imagick("$hash/frame_XXX_o.gif"); | |
// Flatten the frame layers | |
$img = $img->coalesceImages(); | |
// Resize each frame and store the original and thumbnail frames | |
$f = 0; | |
foreach ($img as $frame) { | |
$frame->writeImage(sprintf("$hash/frame_%03d_o.gif", $f)); | |
$frame->thumbnailImage($width, $height); | |
$frame->writeImage(sprintf("$hash/frame_%03d_t.gif", $f)); | |
$f++; | |
} | |
} | |
// Now out put the image frames | |
if ($debug) { | |
$img = new Imagick("$hash/frame_XXX_o.gif"); | |
echo "<html><body style='background-color: #000000'>"; | |
echo "<img src='$hash/frame_XXX_o.gif' width='230' /><br/><hr/>\n"; | |
for ($f = 0; true; $f++) { | |
$fn_o = sprintf("$hash/frame_%03d_o.gif", $f); | |
$fn_t = sprintf("$hash/frame_%03d_t.gif", $f); | |
if (is_readable($fn_o) && is_readable($fn_t)) { | |
echo "<img src='$fn_o' width='230' /><br/>\n"; | |
echo "<img src='$fn_t' width='230' /><br/>\n"; | |
echo "<pre>"; | |
$img = new Imagick($fn_t); | |
$pixels = $img->exportImagePixels(0, 0, $width, $height, "ARGB", Imagick::PIXEL_CHAR); | |
for ($i = $p = 0; $i < count($pixels); $i += 4, $p++) { | |
$y = floor($p / $height); | |
$x = $p % $width; | |
$pixel = (($pixels[$i+1] & 0xFF) << 16) | (($pixels[$i+2] & 0xFF) << 8) | $pixels[$i+3] & 0xFF; | |
$line = ""; | |
if ($x == $width-1) { | |
$line = "\n"; | |
} | |
if ($pixels[$i] == 0) { | |
printf("<font color='#%06X'>_</font>%s", $pixel, $line); | |
} else { | |
printf("<font color='#%06X'>@</font>%s", $pixel, $line); | |
} | |
} | |
echo "</pre><br/><hr/>\n"; | |
} else { | |
break; | |
} | |
} | |
} else { | |
$bin = ""; | |
for ($f = 0; true; $f++) { | |
$fn_o = sprintf("$hash/frame_%03d_o.gif", $f); | |
$fn_t = sprintf("$hash/frame_%03d_t.gif", $f); | |
if (is_readable($fn_o) && is_readable($fn_t)) { | |
$img = new Imagick($fn_t); | |
$pixels = $img->exportImagePixels(0, 0, $width, $height, "ARGB", Imagick::PIXEL_CHAR); | |
for ($i = 0; $i < count($pixels); $i += 4) { | |
if ($pixels[$i] == 0) { | |
// This is transparent. | |
$bin .= pack("CCC", 0x0, 0x0, 0x0); | |
} else { | |
$bin .= pack("CCC", $pixels[$i+1] & 0xFF, $pixels[$i+2] & 0xFF, $pixels[$i+3] & 0xFF); | |
} | |
} | |
} else { | |
break; | |
} | |
} | |
echo pack("NNN", $f, $width, $height) . $bin; | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment