{"id":2188,"date":"2026-06-10T09:42:38","date_gmt":"2026-06-10T07:42:38","guid":{"rendered":"https:\/\/www.sabulo.com\/sb\/?p=2188"},"modified":"2026-06-10T09:42:41","modified_gmt":"2026-06-10T07:42:41","slug":"ye-olde-out-of-office-building-a-7-5-bayeux-tapestry-e-paper-status-sign","status":"publish","type":"post","link":"https:\/\/www.sabulo.com\/sb\/uncategorized\/ye-olde-out-of-office-building-a-7-5-bayeux-tapestry-e-paper-status-sign\/","title":{"rendered":"Ye Olde Out of Office: Building a 7.5&#8243; Bayeux Tapestry E-Paper Status Sign"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Modern office status boards are clinically boring. A generic Slack dot or a sterile digital tablet screaming &#8220;In a Meeting&#8221; does nothing for the soul.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As an educator and engineer, I wanted my lab door to project absolute <em>gravitas<\/em>. And by gravitas, I mean hauling 11th-century artistic flair and self-deprecating academic humor straight out of 1066.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Enter the <strong>Bayeux Tapestry E-Paper Status Board<\/strong>. Driven by a web-connected microcontroller, a 7.5-inch monochrome e-ink panel, a custom PHP dashboard, and a healthy dose of grammatically loose Middle English, this display announces my whereabouts to students like a digital medieval artifact.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Visual Philosophy: &#8220;Heikki\u2019s Laboure&#8221;<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To respect the original aesthetic, every screen mimics the classic &#8220;Tituli&#8221; (the embroidered Latin inscriptions on the actual tapestry). I opted for Middle English spellings, making liberal use of the historical &#8220;V&#8221; and &#8220;U&#8221; swap\u2014because carving curves into a tapestry canvas (or a matrix buffer) is a legendary pain.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The final suite of status screens includes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>EMAIL:<\/strong> <em>HEIKKI ENJOYETH EMAIL<\/em> <\/li>\n\n\n\n<li><strong>TEACHING:<\/strong> <em>HEIKKI TEACHETH STVDENTS<\/em> <\/li>\n\n\n\n<li><strong>LAB MAINTENANCE:<\/strong> <em>HEIKKI MAINTAINETH VNIVERSITAS 3D LAB<\/em> <\/li>\n\n\n\n<li><strong>SILLY STUFF:<\/strong> <em>HEIKKI MAKETH SILLLY THINGYES<\/em> <\/li>\n\n\n\n<li><strong>MEETINGS:<\/strong> <em>HEIKKI ENJOYETH MEETINGYES<\/em> <\/li>\n\n\n\n<li><strong>LUNCH:<\/strong> <em>HEIKKI EATETH LVNCH<\/em> <\/li>\n\n\n\n<li><strong>AWAY:<\/strong> <em>HEIKKI IS AWAYE TO STRANGE LANDS<\/em> <\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/i0.wp.com\/www.sabulo.com\/sb\/wp-content\/uploads\/2026\/06\/blogiin.png?ssl=1\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"825\" height=\"496\" src=\"https:\/\/i0.wp.com\/www.sabulo.com\/sb\/wp-content\/uploads\/2026\/06\/blogiin.png?resize=825%2C496&#038;ssl=1\" alt=\"\" class=\"wp-image-2190\" srcset=\"https:\/\/i0.wp.com\/www.sabulo.com\/sb\/wp-content\/uploads\/2026\/06\/blogiin.png?resize=1024%2C616&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.sabulo.com\/sb\/wp-content\/uploads\/2026\/06\/blogiin.png?resize=300%2C181&amp;ssl=1 300w, https:\/\/i0.wp.com\/www.sabulo.com\/sb\/wp-content\/uploads\/2026\/06\/blogiin.png?resize=768%2C462&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.sabulo.com\/sb\/wp-content\/uploads\/2026\/06\/blogiin.png?w=1140&amp;ssl=1 1140w\" sizes=\"auto, (max-width: 825px) 100vw, 825px\" \/><\/a><figcaption class=\"wp-element-caption\">The final set of images. These were made with the <a href=\"https:\/\/htck.github.io\/bayeux\/#!\/\">Historic Tale Construction Kit<\/a>.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This is the set of images I see on the website, when I change the status. <a href=\"https:\/\/www.instagram.com\/p\/DZMlqCYNf_g\/\">The images can be seen changing on my Instagram.<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Battle with E-Ink: Pure Trial and Error<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Taking a gorgeous, color image of woven blue fabric or antique parchment and slamming it into a 1-bit monochrome display (where a pixel is strictly <em>black<\/em> or <em>white<\/em>) is where my sanity was tested. If you push a raw image straight over, the screen rewards you with an illegible, muddy smudge.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To make digital pixels actually look like physical linen, I routed everything through <strong>Image2cpp<\/strong> using the <strong>Atkinson Dithering<\/strong> algorithm. Atkinson provides an organic, stippled &#8220;grain&#8221; that perfectly mimics a textile from a few feet away.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, getting the contrast right required a grueling gauntlet of individual adjustments:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>The Inverted Canvas Dilemma:<\/strong> For high-impact statuses like <em>STVPID THINGYES<\/em> or <em>MEETINGYES<\/em>, the dark blue fabric background looked incredible when I checked &#8220;Invert image colors&#8221; and anchored the brightness threshold at <strong>104<\/strong>. This made the white text and character outlines cleanly pop from across the hallway.<\/li>\n\n\n\n<li><strong>The Fine-Line Trap:<\/strong> When I tried that exact inverted setup on detail-dense layouts like the <em>EMAIL<\/em> or <em>TEACHING<\/em> screens, the dithered black dots completely devoured the fine linework (like the horse&#8217;s legs and the edges of the scrolls). The solution? I rejected inversion for those specific assets, keeping a clean, white background to let the intricate sketches breathe.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">The Backend Architecture<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">An offline status board is just an expensive desk ornament. To control the sign remotely, I built a lightweight web backend using PHP that serves as my digital control room. This backend is actually the same that has been running my gatekeeper, <a href=\"https:\/\/www.sabulo.com\/sb\/3d-printing-2\/the-morse-moai-statue-project\/\" data-type=\"link\" data-id=\"https:\/\/www.sabulo.com\/sb\/3d-printing-2\/the-morse-moai-statue-project\/\" target=\"_blank\" rel=\"noreferrer noopener\">a 3D printed Moai statue that can display messages and morse them out too<\/a>. Adding this e-paper capability wasn&#8217;t actually very hard &#8211; I can update both the image and the message by pressing a button on the website, or, I can just edit the message that the Morse Moai is displaying and sending as morse code.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The control panel features preset buttons for my daily routine. Clicking a button captures a live timestamp, builds a structured 4-line status string, and writes it directly to a flat log file.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here is the lightweight PHP state-handler:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\n    $myFile = \"myfile.txt\";\n    $redirect = false;\n\n    \/\/ Handle Preset Buttons\n    if(isset($_POST&#91;'preset'])) {\n        $id = $_POST&#91;'preset'];\n        $dt = date(\"d.m. H:i\");\n        $line1 = \"As of $dt, \";\n        $line4 = $id; \/\/ The critical image index sent to the display\n\n        switch($id) {\n            case \"1\": $line2 = \"Heikki is having fun\"; $line3 = \"teaching students\"; break;\n            case \"2\": $line2 = \"Heikki maintains\"; $line3 = \"the 3D + Robo Lab\"; break;\n            case \"3\": $line2 = \"Heikki seems to be\"; $line3 = \"enjoying his emails\"; break;\n            case \"4\": $line2 = \"Heikki is devouring\"; $line3 = \"his delectable lunch\"; break;\n            case \"5\": $line2 = \"Heikki is designing\"; $line3 = \"totally silly stuff\"; break;\n            case \"6\": $line2 = \"Heikki is taking part\"; $line3 = \"in a groovy meeting\"; break;\n            case \"7\": $line2 = \"Heikki is out, but\"; $line3 = \"try Teams or email\"; break;\n        }\n        \n        $fh = fopen($myFile, 'w') or die(\"can't open file\");\n        fwrite($fh, $line1 . \"\\n\" . $line2 . \"\\n\" . $line3 . \"\\n\" . $line4);\n        fclose($fh);\n        $redirect = true;\n    }\n\n    if ($redirect) {\n        header(\"Location: index.php\");\n        exit();\n    }\n?><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The real payload here is <code>$line4<\/code>. This passes the raw asset ID (<code>1<\/code> through <code>7<\/code>) straight down the pipe to the hardware client.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Under the Hood: The Firmware<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The brain of the display is handled by a microcontroller running an efficient paged-drawing loop utilizing the <code>GxEPD<\/code> library. By loading all seven custom <strong>640 x 384<\/strong> bitmaps directly into flash memory headers, the device avoids dynamic RAM allocation overhead and updates reliably.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The microcontroller simply performs an HTTP GET request to pull down <code>the text file<\/code>. It reads the lines, extracts that trailing index integer from line 4, and pipes it straight into an Arduino <code>switch<\/code> statement to fire a paged screen update:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#include &lt;GxEPD.h>\n#include &lt;GxGDEW075T8\/GxGDEW075T8.h>\n#include &lt;GxIO\/GxIO_SPI\/GxIO_SPI.h>\n#include &lt;GxIO\/GxIO.h>\n\n\/\/ Import custom 1-bit tapestry bitmaps\n#include \"maintenance.h\"\n#include \"silly.h\"\n#include \"email.h\"\n#include \"meetings.h\"\n#include \"lunch.h\"\n#include \"teaching.h\"\n#include \"away.h\"\n\n\/\/ SPI Pin Configurations for the E-Paper Display\nconst int EPD_CS   = 5;\nconst int EPD_DC   = 17;\nconst int EPD_RST  = 16;\nconst int EPD_BUSY = 4;\n\nGxIO_Class io(SPI, EPD_CS, EPD_DC, EPD_RST);\nGxEPD_Class display(io, EPD_RST, EPD_BUSY);\n\n\/\/ Global pointer referencing our active state asset array\nconst unsigned char* nykyinenKuva = maintenance_image;\n\nvoid naytaKuvaCallback(const void*);\n\nvoid setup() {\n  Serial.begin(115200);\n  display.init(115200);\n  display.setRotation(0); \n\n  Serial.println(\"System Ready! Enter 1-7 in Serial Monitor to force refresh:\");\n}\n\nvoid loop() {\n  \/\/ Production loops parse the file index; Serial handles local lab testing\n  if (Serial.available() > 0) {\n    char input = Serial.read();\n    bool validoitu = true;\n\n    switch (input) {\n      case '1': nykyinenKuva = maintenance_image; break;\n      case '2': nykyinenKuva = silly_image;       break;\n      case '3': nykyinenKuva = email_image;       break;\n      case '4': nykyinenKuva = meetings_image;    break;\n      case '5': nykyinenKuva = lunch_image;       break;\n      case '6': nykyinenKuva = teaching_image;    break;\n      case '7': nykyinenKuva = away_image;        break;\n      default: validoitu = false; break;\n    }\n\n    if (validoitu) {\n      Serial.println(\"Refreshing e-paper screen...\");\n      display.drawPaged(naytaKuvaCallback, 0);\n      display.powerDown();\n    }\n  }\n}\n\nvoid naytaKuvaCallback(const void*) {\n  display.fillScreen(GxEPD_WHITE);\n  display.drawBitmap(nykyinenKuva, 0, 0, 640, 384, GxEPD_WHITE);\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Hardware Lessons Learned: Purging the Ghosts<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When dealing with heavy Atkinson dithering on a large 7.5-inch panel, you will inevitably run into <strong>ghosting<\/strong>. The physically charged micro-particles inside the e-ink screen have an irritating &#8220;state memory&#8221;. If you attempt a quick partial refresh when transitioning from a dark fabric background (<em>Lunch<\/em>) to a clean white canvas (<em>Teaching<\/em>), a ghostly, pixelated shadow of the previous scene will haunt your display.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The fix? <strong>Always enforce a full refresh cycle<\/strong> when transitioning between major asset styles. It takes a few extra seconds of flashing, but it purges the screen state completely, ensuring the whites stay bright and the lines remain razor-sharp.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now, whether I am lecturing or actively <em>smiting<\/em> an uncooperative 3D printer with a battleaxe, my door sign says it all with timeless, medieval charm.<\/p>\n<div class=\"pvc_clear\"><\/div><p id=\"pvc_stats_2188\" class=\"pvc_stats all  \" data-element-id=\"2188\" style=\"\"><i class=\"pvc-stats-icon medium\" aria-hidden=\"true\"><svg aria-hidden=\"true\" focusable=\"false\" data-prefix=\"far\" data-icon=\"chart-bar\" role=\"img\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\" class=\"svg-inline--fa fa-chart-bar fa-w-16 fa-2x\"><path fill=\"currentColor\" d=\"M396.8 352h22.4c6.4 0 12.8-6.4 12.8-12.8V108.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v230.4c0 6.4 6.4 12.8 12.8 12.8zm-192 0h22.4c6.4 0 12.8-6.4 12.8-12.8V140.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v198.4c0 6.4 6.4 12.8 12.8 12.8zm96 0h22.4c6.4 0 12.8-6.4 12.8-12.8V204.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v134.4c0 6.4 6.4 12.8 12.8 12.8zM496 400H48V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-16c0-8.84-7.16-16-16-16zm-387.2-48h22.4c6.4 0 12.8-6.4 12.8-12.8v-70.4c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v70.4c0 6.4 6.4 12.8 12.8 12.8z\" class=\"\"><\/path><\/svg><\/i> <img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"16\" height=\"16\" alt=\"Loading\" src=\"https:\/\/i0.wp.com\/www.sabulo.com\/sb\/wp-content\/plugins\/page-views-count\/ajax-loader-2x.gif?resize=16%2C16&#038;ssl=1\" border=0 \/><\/p><div class=\"pvc_clear\"><\/div>","protected":false},"excerpt":{"rendered":"<p>Modern office status boards are clinically boring. A generic Slack dot or a sterile digital tablet screaming &#8220;In a Meeting&#8221; &hellip; <a href=\"https:\/\/www.sabulo.com\/sb\/uncategorized\/ye-olde-out-of-office-building-a-7-5-bayeux-tapestry-e-paper-status-sign\/\" class=\"more-link\">More <span class=\"screen-reader-text\">Ye Olde Out of Office: Building a 7.5&#8243; Bayeux Tapestry E-Paper Status Sign<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n<div class=\"pvc_clear\"><\/div>\n<p id=\"pvc_stats_2188\" class=\"pvc_stats all  \" data-element-id=\"2188\" style=\"\"><i class=\"pvc-stats-icon medium\" aria-hidden=\"true\"><svg aria-hidden=\"true\" focusable=\"false\" data-prefix=\"far\" data-icon=\"chart-bar\" role=\"img\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\" class=\"svg-inline--fa fa-chart-bar fa-w-16 fa-2x\"><path fill=\"currentColor\" d=\"M396.8 352h22.4c6.4 0 12.8-6.4 12.8-12.8V108.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v230.4c0 6.4 6.4 12.8 12.8 12.8zm-192 0h22.4c6.4 0 12.8-6.4 12.8-12.8V140.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v198.4c0 6.4 6.4 12.8 12.8 12.8zm96 0h22.4c6.4 0 12.8-6.4 12.8-12.8V204.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v134.4c0 6.4 6.4 12.8 12.8 12.8zM496 400H48V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-16c0-8.84-7.16-16-16-16zm-387.2-48h22.4c6.4 0 12.8-6.4 12.8-12.8v-70.4c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v70.4c0 6.4 6.4 12.8 12.8 12.8z\" class=\"\"><\/path><\/svg><\/i> <img loading=\"lazy\" decoding=\"async\" width=\"16\" height=\"16\" alt=\"Loading\" src=\"https:\/\/www.sabulo.com\/sb\/wp-content\/plugins\/page-views-count\/ajax-loader-2x.gif\" border=0 \/><\/p>\n<div class=\"pvc_clear\"><\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[102,64,74,69,1],"tags":[65],"class_list":["post-2188","post","type-post","status-publish","format-standard","hentry","category-e-paper","category-esp32-development-board","category-internet-of-things-iot","category-micropython","category-uncategorized","tag-esp32"],"a3_pvc":{"activated":true,"total_views":6,"today_views":0},"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p6vhqE-zi","_links":{"self":[{"href":"https:\/\/www.sabulo.com\/sb\/wp-json\/wp\/v2\/posts\/2188","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sabulo.com\/sb\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sabulo.com\/sb\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sabulo.com\/sb\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sabulo.com\/sb\/wp-json\/wp\/v2\/comments?post=2188"}],"version-history":[{"count":3,"href":"https:\/\/www.sabulo.com\/sb\/wp-json\/wp\/v2\/posts\/2188\/revisions"}],"predecessor-version":[{"id":2192,"href":"https:\/\/www.sabulo.com\/sb\/wp-json\/wp\/v2\/posts\/2188\/revisions\/2192"}],"wp:attachment":[{"href":"https:\/\/www.sabulo.com\/sb\/wp-json\/wp\/v2\/media?parent=2188"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sabulo.com\/sb\/wp-json\/wp\/v2\/categories?post=2188"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sabulo.com\/sb\/wp-json\/wp\/v2\/tags?post=2188"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}