Skip to content

Instantly share code, notes, and snippets.

@blindman2k
Last active August 29, 2015 14:03
Show Gist options
  • Save blindman2k/61d6a57a35990dc05bc3 to your computer and use it in GitHub Desktop.
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.
// ------------------------------------------------------------------------
// 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);
// -----------------------------------------------------------------------------
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();
<?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