diff options
author | uvok | 2025-08-10 18:29:01 +0200 |
---|---|---|
committer | uvok | 2025-08-10 18:29:01 +0200 |
commit | a510673221fe872a75ed1f868ba6d3a1a762615e (patch) | |
tree | a71ba10569c797ee321e51a0879154871b5a3733 | |
parent | f082814544146376870be8d4a95aa9412d8fc0f1 (diff) |
Finish article
-rw-r--r-- | _drafts/building-an-epaper-badge.md | 260 |
1 files changed, 222 insertions, 38 deletions
diff --git a/_drafts/building-an-epaper-badge.md b/_drafts/building-an-epaper-badge.md index 866e0e8..43cd56b 100644 --- a/_drafts/building-an-epaper-badge.md +++ b/_drafts/building-an-epaper-badge.md @@ -14,32 +14,33 @@ Two weeks before [Awoostria]({% post_url 2025-07-30-awoostria-con-report %}): So it begins… The story how I built myself an ePaper badge. -Actually, the story begins way earlier, when I still had a physical Raspberry -Pi running stuff in my home network. I wanted to tinker around a bit and bought -myself a Waveshare ePaper. These are simple black-and-white displays which -maintain their content when the power switches off. They are also inside eBook -readers. +Actually, the story begins way earlier (unrelated, when I still had a physical +Raspberry Pi running stuff in my home network). I wanted to tinker around a bit +and bought myself a Waveshare ePaper. These are simple black-and-white displays +which maintain their content when the power switches off. They are also inside +eBook readers. -Some years ago I wanted to build myself an electronic door sign for the EAST -convention with these, and I wanted to go "as minimal as possible". I wanted to -use one of the MSP430 controllers I had laying around, and I wanted to switch -motives via MiFare RFID transponders (using an MFRC5xx reader). Work on that -development never really took off. +This was also when I wanted to build myself an electronic door sign for the +EAST convention with these, and I wanted to go "as minimal as possible". I +wanted to use one of the MSP430 controllers I had laying around, and I wanted +to change motives via MiFare RFID transponders (using an MFRC5xx reader). Work +on that development never really took off. (Ugh, too little flash for all the +pictures, too much stuff to code myself!) ## Requirements So this time, I simply said "fuck it", and threw an ESP32 on the problem. Also, I decided to use [PlatformIO](https://platformio.org/), a toolchain/SDK/library manager. I started with the Arduino framework, which is… -pretty wasteful in terms of resources (Flash/RAM/…), but speeds up development -significantly. +pretty wasteful in terms of resources (Flash, RAM, CPU etc.), but speeds up +development significantly. I had a simple ESP32 devboard, and one of the Waveshare modules, and started coding. … -But wait, what to I even want to achieve? Well, I wanted to "mood badge", i.e. +But wait, what do I even want to achieve? Well, I wanted to "mood badge", i.e. show my current mood with funny pictures. I couldn't get one on previous conventions, so I was just gonna build one myself. @@ -64,7 +65,8 @@ support *some* Waveshare displays. The problem with Waveshare displays is, they don't disclose which display controller they use. So it's kind of an trial-or-error procedure. Or rather, you can look at their example code, figure out which commands they are using, and compare what commands GxEPD2 uses. -That's a bit cumbersome. +That's a bit cumbersome. But still better than writing everything myself. Also, +it supports graphics primitives! <span id="pic"></span> ## Get the picture on the display @@ -76,13 +78,15 @@ but that makes it even more complicated, actually. Even when you don't use it, refresh is slow. So, you definitely can't throw a color picture on the display, nor a monochrome -one. There are displays which support a few gray-levels, though. +one. There are displays which support a few gray-levels, but I don't have one +of those. So. What to? The solution is "dithering". I.e. you trick your eye into perceiving grey by having clusters of black and white pixels. There is some -background to dithering (see below), but I simply used either GIMP, the -Floyd-something algorithm, or one of the "ordered" modes of ImageMagick. It was -a bit of trial-and-error and seeing-what-looks-best. +technical background to dithering (see the Reference section), but I simply +used either GIMP with the Floyd-something algorithm, or one of the "ordered" +modes of ImageMagick. It was a bit of trial-and-error and +seeing-what-looks-best. The result, then, looks like this: @@ -100,26 +104,30 @@ rotation first, though. ## How to attach the badge to myself I have a Waveshare module/PiHat (which is too heavy), and a simple "ePaper -sheet" including a housing for it. The housing can only fit the ePaper, not the -devboard, though. Also, it would be too cumbersome to attach to the devboard - -loose wires! So, at this point, I decided to switch from the prototyping -platform onto something better. +sheet" including a plastic housing for it. The housing can only fit the ePaper, +not the devboard, though. Also, it would be too cumbersome to attach to the +devboard - loose wires! So, at this point, I decided to switch from the +prototyping platform onto something better. Fortunately, Elecrow provides a -[CrowPanel](https://www.elecrow.com/crowpanel-esp32-2-9-e-paper-hmi-display-with-128-296-resolution-black-white-color-driven-by-spi-interface.html), +[CrowPanel](https://www.elecrow.com/wiki/CrowPanel_ESP32_E-paper_2.9-inch_HMI_Display.html), which is exactly what I need. It has a display, a built-in ESP32 controller, a housing, and even some switches! As a huge plus, they even specify which display controller they use. I had to try some of the GxEPD2 display classes, but finally found one working. I decided to glue magnets onto the housing, and attach the display via magnets -on the inner side of my shirt &emdash; not ideal. I positioned the magnets in +on the inner side of my shirt — not ideal. I positioned the magnets in the (vertical) middle of the housing, so it wobbles and is not readable. Also, I accidentally washed the shirt after Awoostria with the magnets still sticking -inside &emdash; and now there's a hole in it. :( This problem is still +inside — and now there's a hole in it :( . This problem is still unsolved. I can kinda attach the magnets to the housing screws at the top, but that's not *very* stable. +When starting to work with the Elecrow display, at first nothing would work. +When I looked at their example code I noticed there's an additional power pin +that needs to be toggled. + <span id="setdisp"></span> ## Set what is displayed @@ -129,10 +137,10 @@ something "more direct", so I added the of coding, I added a service and some characteristics, so the available motives could be read via BLE. Also, the motive could be selected via another characteristic. I started writing the characteristic with *nRF Connect For -Mobile*, but started writing an app later. +Mobile*, but started writing an app [later](#theapp). Actually, the switch-selection was a bit troublesome. Redrawing the whole -display takes around 2 seconds &emdash; but that is not acceptable when +display takes around 2 seconds — but that is not acceptable when navigating the presets one-by-one. By looking at the API, I found out you can select a "partial region". @@ -142,14 +150,14 @@ vertical-center-right of the display with the mood text. The picture stays the same, but the text reflects the selected mood. The selection is confirmed my pressing the rotary switch. -This is still not ideal. The first update must be a full one, and I don't save -the selected preset in NVS &emdash; I don't want to destroy the flash by -hundreds/thousands of write cycles. I don't have a solution to this, yet. Maybe -I'm gonna integrate an microSD card (there's a slot for that!n the CrowPanel). -I'm gonna research a good wear-levelling file system for that. Probably not +This is still not ideal. The first update after power-up must be a full one, +and I don't save the selected preset in NVS — I don't want to destroy the +flash by lots of write cycles. I don't have a solution to this, yet. Maybe I'm +gonna integrate an microSD card (there's a slot for that in the CrowPanel). +I'm gonna research a good wear-levelling file system for that. Probably not FAT. That doesn't need to be readable on the PC. (And even if, simple stuff -can be written in FUSE). Drawing the first motive on power-up would also be an -option, but I don't like it that much. +can be written in FUSE). Alternatively, drawing the first motive on power-up +would also be an option, but I don't like it that much. <span id="powersave"></span> ## Power saving @@ -170,7 +178,15 @@ why! Apropos of powering: The badge is normally unpowered and only powered if I need to change the motive. I don't want to have an USB cable hanging on me the whole -time! +time! Using a battery might be an option, but I had problems with mismatched +connectors — the Elecrow display doesn't have a standard JST connector +like the LiPo battery I bought (Li-Ion might even be the safer option? I have +no clue about this stuff. With a quick search, I only found these cyclindric +Li-Ion batteries and have no idea how I would connect them). The badge seems to +have a "mini" variant of that connector. + +For the time being, I power it with a USB powerbank, but this I still consider +an unsolved problem. <span id="theapp"></span> ## The app @@ -1020,9 +1036,177 @@ settled for Flutter. Again, this was completely new to me. I started off with a popular BLE library, which turned out to be an unfortunate choice, as Linux support had a few quirks. That was probably a good thing in hindsight, as this made me abstract -away the BLE stuff in implementation classes, and only use the abstract base -classes in the code. Well, you can see what the code looks like, I linked my -repo below. +away the BLE stuff in implementation classes, so I could easily try out +different libraries, and only use the abstract base classes in the code. Well, +you can see what the code looks like, I linked my repo below. This is what the +motive selection looks like: + +<img src='data:image/gif;base64, +R0lGODlhkwH0AffGABkYHAoKCR4dIiAeJSUiKiglLS0pMysnMDIuOjAuNDYxPDY0OTo1RDw3REI8TUU ++UUZDSUhFS0lCVk5LUU1FWkhBVVFOVFFJX1RRVlZTWVlWXFxZXlVMZFhPZ15bYVxSbF9VcGFeZGNZdW +lefGRadmRgZ2djaWpmbGxob25rcWtgf3JudHRxd3dzeXp2fHx4f5UiMp41RKQ2RqI8Sqk+TaE3R6lAT +6ZAUatDUqdJW7BJWa5QXahMY6tRZqpSaKlZc6tZdLNbaLdmcrRhbW1akm5jgnVpi3NniH56gXxqnXlp +loJ9hIN1nIByl7twgYFvoYRzo4p7pIx8qYh4o5B/q8J7hYSAh4aCiYmFi42Ij4+KkZKNlJWQl5eSmZq +VnJ2Yn5KCrp6aoJaGspqKtZ2MvJeIso+Aq6KdpKWIt6WLuqWWvaWgp6ahqKqlrK2or6+psLKttLWwt7 +axuLu1vL24v8aDjcuNlc2WntCSmtCepdOjqtesstuzuaaPwaWSxKmWyauZzaqYxK6e1LCf1bOZzraoy +b+5wL2xzrOg17Wi2rmk3Lys37Klxr+q5bun4MO9xMO409+8wMGs5sKt6Mex7siy78y19M6498+4+NO8 +/dG89eC9w8bAx8fByMvFzM7Iz8/J0M3C2tLM1NPJ3tfR2NvV3N7Y39bQ1+THy+fO0evW2dnP4tbM4d/ +Y4NzT5dfA/tnD/uLc5eXd6+jd6eDH/+LJ/+jO/+vR//HW//TZ//ne/+bg5+fh6erk7O7o7/To6u3n8O +/o8PPs9fbv+Pfw+f73//////7+/v/6//jy9gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAACH5BAAAAAAAIf8LSW1hZ2VNYWdpY2sNZ2FtbWE9MC40NTQ1NQAsAAAAAJMB9AEACP +8AMwkcSLCgwYMIEypcyLChw4cQI0qcSLGixYsYM2pMiMnSJEVOcIicQbIkjh5oBClqRMnSpY0wY8qcS +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' + alt="Screenshot of the Flutter app, allowing selection from + sleepy, hungry, hugs?, uvok, overstim, contact, games?" +/> ## Resources |