Jekyll2023-07-17T07:45:48+00:00https://www.tnhh.net/feed.xmlHuan Truong's PensieveMy site, that hosts my blog and funsNotes on installing Rollo/Beeprt CUPS driver for Raspberry Pi OS (Arm64)2023-07-17T00:00:00+00:002023-07-17T00:00:00+00:00https://www.tnhh.net/posts/rollo-arm64-driver<p>TLDR: If you just want your darn Rollo Thermal Printer to work with Raspberry Pi OS and don't mind running some binary blobs from me with sudo permissions, then here comes the tarred driver: <a href="/downloads/Rollo_arm64.tar.gz"><code class="language-plaintext highlighter-rouge">Rollo_arm64.tar.gz</code></a>. Just untar it, run <code class="language-plaintext highlighter-rouge">sudo ./install.sh</code>, then go to the CUPS admin interface, add new printer, pick the PPD file that you just extracted from this package. I also patched the PPD file to solve some typos from Rollo PPD definitions, you're welcome.</p>
<p>If you want to not trust me with binary blobs, then read on.</p>
<p>If you encounter errors complaining about "Filter errors" while trying to get the Rollo USB Thermal Printer to work with your Arm64 Raspberry Pi, it's because Rollo <a href="https://www.rollo.com/driver-linux/">released the driver for Arm32 only</a>, without being explicit about the driver being 32-bit only. I suppose they released this driver some time ago when Raspberry Pi OS was 32-bit.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>./install.sh
<span class="nv">$ </span><span class="nb">tar </span>xvf /tmp/Printer_ThermalPrinter.tar.gz
<span class="nv">$ </span>file Printer_ThermalPrinter/Filter/rastertolabel
Printer_ThermalPrinter/Filter/rastertolabel: ELF 32-bit LSB pie executable, ARM, EABI5 version 1 <span class="o">(</span>SYSV<span class="o">)</span>, dynamically linked, interpreter /lib/ld-linux-armhf.so.3, <span class="k">for </span>GNU/Linux 3.2.0, BuildID[sha1]<span class="o">=</span>067e1c402edb5e3c8155ad3eeab240544b89ff74, with debug_info, not stripped
</code></pre></div></div>
<p>Too bad, <code class="language-plaintext highlighter-rouge">rastertolabel</code> is some hacked-together closed-source binary crap. After being confused for a while, I realized that proski <a href="https://github.com/proski/cups/commit/9ade138db4387ed016f70feb11a3b7a05daf04ca">added the support for CUPS</a>, but the patch <a href="https://github.com/michaelrsweet/lprint/issues/54">never got merged to mainline CUPS</a>.</p>
<p>So if you're looking for using the Raspberry Pi as a thermal print server, you should check out the cups driver from proski's CUPS repository, compile it, and use Rollo's PPD file in the closed driver's suite. Configuring and making the CUPS suite is quite normal and boring so I won't bore you with the details here, this is left as an exercise for you – the readers.</p>
<p>Hope that saved you some frustrations with their driver situation.</p>
<p>PS: Rollo is a good thermal printer, I have no problems with it. It has been one of my favorite tech items as I ship stuff for Ebay/friends from time to time and having it is very convenient. With this driver, I could print from my iPhone, which is nice.</p>TLDR: If you just want your darn Rollo Thermal Printer to work with Raspberry Pi OS and don't mind running some binary blobs from me with sudo permissions, then here comes the tarred driver: Rollo_arm64.tar.gz. Just untar it, run sudo ./install.sh, then go to the CUPS admin interface, add new printer, pick the PPD file that you just extracted from this package. I also patched the PPD file to solve some typos from Rollo PPD definitions, you're welcome.Is your computer too slow to keep up with your typing?2021-07-01T00:00:00+00:002021-07-01T00:00:00+00:00https://www.tnhh.net/posts/keyboard-ime-performance<p>From time to time, I have the pleasure of hearing a claim so outrageous, it's almost unbelievable. One such claim is that the computer/OS being too slow/laggy to type (ordinary documents) on. I heard it from two different people on two different occasions.</p>
<p>So the claim was the computer that can do a bazillion operations on your computer can't even process and display text as fast as your hand can type. Can it possibly be true?</p>
<p>Here is my video summarizing what I found. If you rather read, read on.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/UeUN4MRVzAo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<h2 id="cool-storytime">Cool storytime</h2>
<p>First, a little bit of clarification. The stories I heard were not about computers typing English, they were about computers typing Vietnamese. Vietnamese is basically Latin with a couple funny hooks on top of the characters, like this: <code class="language-plaintext highlighter-rouge">Tiếng Việt</code>. To type Vietnamese, one of the most popular methods (now it has become de-facto) is TELEX. TELEX uses the unmodified Latin keyboard to express the hook by certain simple rules. An example would be to get <code class="language-plaintext highlighter-rouge">Tiếng Việt</code>, you type <code class="language-plaintext highlighter-rouge">Tieengs Vieetj</code>. There, two <code class="language-plaintext highlighter-rouge">e</code>s make the <code class="language-plaintext highlighter-rouge">ê</code>, <code class="language-plaintext highlighter-rouge">s</code> makes the <code class="language-plaintext highlighter-rouge">ế</code>, and <code class="language-plaintext highlighter-rouge">j</code> makes the <code class="language-plaintext highlighter-rouge">ệ</code>. Therefore, creating an IME for Vietnamese is a tedious job, but there were no heuristics involved, so it is possible to make a very high-performance IME. This problem is very much solvable and has been solved many times.</p>
<p>The first time I heard about the computer being slow was around 1998 when I was a kid in Vietnam. At the time, there was a profession called "typist," whose job was to digitalize handwriting or poorly photocopied books/documents. That's one of the services provided by then-popular photocopy shops (like FedEx print shops here). At the time, Vietnam's de-facto word processor was Microsoft Office 95/97 on Windows 95. Back then, there was no Unicode, so Vietnamese documents utilize special fonts that have high-ASCII characters replaced by Vietnamese characters. Using the English version of Office with Vietnamese fonts worked well enough for most people. Given the IME program (named ABC, widely used in North Vietnam and in governmental offices) was originally written for Windows 3.1 and the computer has gotten so much faster since working with Vietnamese was already relatively painless.</p>
<p>I visited a photocopy shop one day, and I saw a lady who typed so extraordinarily fast – I don't think I have seen anyone typing as fast as she did since! Instead of using just Windows and MS Office, the typist switched to MS-DOS from time to time. On MS-DOS, she used a Vietnamese text editor called <a href="/posts/bked-gui-as-tui.html">BKED that I wrote about</a>. BKED has a lot of limitations so I was intrigued as to why she didn't "just" use MS Office. She told me that it's because typing Vietnamese with Windows is slow. With BKED, everything is so limited, but the program is fast and can keep up with her for once. That claim surprised me.</p>
<p>The second time I heard about it was from my friend in 2008 or so. At that point, Unicode was widely accepted as the standard and the IME of choice for most people in Vietnam was the (mostly open-source) program called Unikey that runs on Windows. There were two versions of Unikey, the newer version is 4.x – that is a core rewrite of 3.x. The 4.x release has so many features (and it was free) that upgrading wasn't even a question. When I hung out with my friend in the coffee shop, I saw her still using 3.x and I inquired. She responded: The 4.x felt slow and 3.x felt fast to her.</p>
<p>So I went on to investigate myself and from my impression, there was something different about Unikey 3.x that makes it actually more pleasant to use. Maybe it was the performance? So I changed some of the features of Unikey 3.x to match what of 4.x (because the author never released the source code for 4.x) and used it myself, and <a href="/projects.html">posted the modded version on my website</a>.</p>
<p>Since then, I mostly switched to Linux, and I never felt the same kind of responsiveness on Linux typing Vietnamese compared to typing with Unikey on Windows – there was something odd I couldn't tell why. Maybe it's because the IME in Linux underlines the pre-edit words while Windows doesn't?</p>
<p>Nonetheless, I never had the chance to empirically check the claim of speed myself.</p>
<h2 id="mythbusting-the-performance-claim">Mythbusting the performance claim</h2>
<p>I was inspired by prior attempts to understand keyboard latency, notably <a href="https://www.youtube.com/watch?v=wdgULBpRoXk">Ben Eater's</a>
and <a href="https://danluu.com/keyboard-latency/">Dan Luu's</a>. If you haven't read or watched their attempts, I suggest you watch Ben's video now, it will be relevant to what we discuss here.</p>
<p>I thought it would be possible to do such a latency test for Vietnamese IME across systems. Since the USB latency issue has been investigated by Ben, I don't plan to redo it. I will just use and a USB keyboard and pretend that USB latency is acceptable. This USB keyboard limitation makes it hard to verify the first claim about DOS being faster (because I don't think we have a USB HID keyboard driver for DOS). Overall, I am less concerned about the latency from pressing the key to the character displays on the screen. I am more concerned about whether the overhead of the IME can keep up with the keystrokes I make.</p>
<p>Since I don't type fast enough or consistently enough, I thought to make a piece of hardware to type for me. My initial thought was to use an Arduino to send the keystrokes. However, since I have a Raspberry Pico laying around, I decided it's time for me to learn Raspberry Pi Pico. I modified the tinyUSB's HID composite example so that when I hold the button on the Pico, the Pico will send the HID keys as follows: <code class="language-plaintext highlighter-rouge">### Tieengs Vieetj thaatj giauf vaf ddepj<Enter></code>, where <code class="language-plaintext highlighter-rouge">###</code> is the sequence number, it increases by one for each line. The leading number is typed by the microcontroller itself to help me keep track of how many lines have been typed since I started holding the button. With the help of the IME on the host computer, the computer will output something like <code class="language-plaintext highlighter-rouge">012 Tiếng Việt thật giàu và đẹp</code>.</p>
<p>Next is to figure out how fast should we make the keyboard type. Sending keys to the computer via the HID protocol is not straightforward. Ben Eater mentioned that USB is a polling protocol. The host computer checks for the key on the keyboard every so often. TinyUSB library allows the guest device to configure that interval. I configured it to the highest frequency possible – that is <code class="language-plaintext highlighter-rouge">1000Hz</code> (the host computer is supposed to poll every <code class="language-plaintext highlighter-rouge">1ms</code>). It means that theoretically, I can send 1000 keyboard events every second. Now we have to send a key-up event for each key-down event so that halves the number of keyboard events we can send to 500 events per sec.</p>
<p>Other than polling rate, there are other interesting tidbits as well. It seems that the <a href="https://www.devever.net/~hl/usbnkro">standard HID protocol allows us to specify 6 simultaneous keyboard scancodes</a> on every poll (event). So with effort, we can stress the system to <code class="language-plaintext highlighter-rouge">500*6=3000</code> keystrokes per second. However, I decided 500 keystrokes per second is probably good enough.</p>
<p>I don't think any human being can achieve the rate of 500 keystrokes per second consistently. However, we have to keep in mind that human keystrokes are very bursty, we can probably type a word we're familiar with really fast.</p>
<p>So I tested it on a couple of systems. I have the following setups:</p>
<ul>
<li>A Thinkpad X1 Nano running Ubuntu 21.04 ("ThinkPad X1 Linux") with 11th-gen Core i5 (Editor: Kate, IME: iBus-bamboo packaged with Ubuntu).</li>
<li>A vintage Thinkpad X40 running Windows 2000 ("ThinkPad X40 Win2k") with 1.2Ghz Pentium M (Editor: Notepad, IME: Unikey 3.6). The Thinkpad also runs MS-DOS within Windows 2000 ("ThinkPad X40 DOS") (Editor + IME: BKED).</li>
<li>A vintage Core i5 "Ivy" desktop running Windows 10 64bit ("Ivy Desktop Win10") (Editor: Notepad, IME: Unikey 4.3).</li>
<li>A Macbook Pro 2015 (Monterey Beta) ("Macbook Pro 2015") (Editor: TextEdit in plaintext mode, IME: Built-in aka Unikey engine).</li>
</ul>
<p>To make sure that I didn't have a bug in the Raspberry Pi Pico code, I decided to do a sanity check. I let the Pico type without an IME on the Thinkpad X1 and counted the keystrokes per second. It managed to type 100 (correct) lines of <code class="language-plaintext highlighter-rouge">### tieengs vieetj thaatj giauf vaf ddepj<Enter></code> in around 8.78 seconds on my stopwatch. Each line is 42 keystrokes so that makes out to 4200 keystrokes in total. The rate is: <code class="language-plaintext highlighter-rouge">4200/8.78=478</code> keystrokes/sec. This looks correct. That means any slowdown is due to either the OS or the IME. For every system, I set a baseline with no IME to see the difference that the OS/HW makes.</p>
<p>The table summarizes my results across the system.</p>
<table>
<thead>
<tr>
<th>System</th>
<th>IME</th>
<th>Editor</th>
<th>Completion time</th>
<th>Correct?</th>
<th>Remarks</th>
</tr>
</thead>
<tbody>
<tr>
<td>Thinkpad X1 Linux</td>
<td>None</td>
<td>Kate</td>
<td>8.78</td>
<td>Y</td>
<td>Baseline - Doesn't produce Vietnamese</td>
</tr>
<tr>
<td>Thinkpad X1 Linux</td>
<td>Bamboo</td>
<td>Kate</td>
<td>8.42</td>
<td>N</td>
<td>Every now and then have a leading number wrong (1)</td>
</tr>
<tr>
<td>Ivy Desktop Win10</td>
<td>None</td>
<td>Notepad</td>
<td>11.00</td>
<td>Y</td>
<td>Baseline - Seems buffered towards the end</td>
</tr>
<tr>
<td>Ivy Desktop Win10</td>
<td>Unikey4</td>
<td>Notepad</td>
<td>10.57</td>
<td>Y</td>
<td> </td>
</tr>
<tr>
<td>Thinkpad X40 Win2k</td>
<td>None</td>
<td>Notepad</td>
<td>8.93</td>
<td>Y</td>
<td>Baseline</td>
</tr>
<tr>
<td>Thinkpad X40 Win2k</td>
<td>Unikey3</td>
<td>Notepad</td>
<td>8.50</td>
<td>N</td>
<td>First two lines were not good, everything after was good (2)</td>
</tr>
<tr>
<td>Thinkpad X40 DOS</td>
<td>None</td>
<td>EDIT.COM</td>
<td>50.59</td>
<td>Y</td>
<td>Baseline - Extremely buffered</td>
</tr>
<tr>
<td>Thinkpad X40 DOS</td>
<td>BKED</td>
<td>BKED</td>
<td>26.02</td>
<td>Y</td>
<td>Extremely buffered (3)</td>
</tr>
<tr>
<td>Macbook Pro 2015</td>
<td>None</td>
<td>TextEdit</td>
<td>46.75</td>
<td>Y</td>
<td>Baseline - Extremely buffered</td>
</tr>
<tr>
<td>Macbook Pro 2015</td>
<td>Built-in</td>
<td>TextEdit</td>
<td>120.01</td>
<td>Y</td>
<td>Extremely buffered and painful (4)</td>
</tr>
</tbody>
</table>
<p>Remarks:</p>
<ol>
<li>It was surprising to me that the IME literally got the numbers wrong. It doesn't have to process anything in that case. Sounds like a bug. When I switched to sublime text, it got a lot of words wrong, but sublime is not native to KDE so I don't know whether I should blame it.</li>
<li>It looks like the processor branch prediction needs to be trained for a while for it to be efficient.</li>
<li>Really surprised me that BKED is actually 2x faster than DOS 7.1's EDIT.COM running fullscreen.</li>
<li>macOS was extremely disappointing in every regard. Even a text without IME fills up the buffer and the text is backed up.</li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>It is easy to see that the keyboard feels slow is not just a subjective experience. It is true that with some programs/hardware, the computer can lag behind your keystrokes and can even produce incorrect result when you type too fast. This issue is especially observable when combined with a buggy or slow IME.</p>
<p>Both macOS and Linux trailed behind Windows in terms of accuracy and performance even when compared to a very old Windows computer.</p>
<p>Even though I wasn't a fan of macOS's Vietnamese IME, it took me by surprise how poorly macOS does with regard to typing text. Even without the IME, macOS does extremely poorly when it comes to processing and rendering text. Loooooong after I stopped holding the button, macOS was still typing and keeping up from the buffer. Something in the stack is very inefficient.</p>
<p>In most regards, Linux is actually much better than expected, given the Bamboo folks fix the number bug in its engine. The issue is very serious when it comes to Sublime Text, but that's another issue.</p>
<p>Although the performance in DOS was abysmal, I think it's due to the emulation layer in Windows rather than it's the OS that makes it slow. Unfortunately, I haven't found a way to do HID under DOS. USB is not a good implementation to understand the limits of DOS-based programs. The findings in this test do not disprove the fast typist's claims of DOS being faster.</p>
<p>Windows + Unikey remain the kind of the hill. It has served the Vietnamese community right since early 2000. Huge kudos to Pham Kim Long who wrote this wonderful engine.</p>
<p>If you want to replicate my result, I have uploaded the source code of the <a href="https://github.com/htruong/tinyusb/commit/1ecd3dc57829f7d89acd319bd8b9dcb33b10bb04">keyboard tester program to github</a>. Any microcontroller capable of running the tinyUSB stack should be able to run this program.</p>From time to time, I have the pleasure of hearing a claim so outrageous, it's almost unbelievable. One such claim is that the computer/OS being too slow/laggy to type (ordinary documents) on. I heard it from two different people on two different occasions.Lullaby of the artillery2021-05-21T00:00:00+00:002021-05-21T00:00:00+00:00https://www.tnhh.net/posts/lullaby-of-the-artillery<p>Not until a month ago did I learn that my dad had horrific experiences during the Vietnam war as a Northern Vietnamese citizen. He was a student when the war was at its height in 1972. All the students in his university had to move from the capital to a province far away to avoid being bombed. When people just finished moving, the new place got bombed. He had to move to another city to do an internship. When he just started the internship, the place he worked at got bombed and he had to move again. One time, he had to emerge from a house, which turned into rubble during the 1972 bombing operation (Also known as the "Operation Linebacker II" in the US).</p>
<p>I spent much of my childhood growing up in the countryside of northern Vietnam. When I grew up, most evidence of the war was erased from the surface of our everyday lives, except for a few things here and there. I only remember two prominent details.</p>
<p>The first thing was my neighbor's land. My next-door neighbor, who is a farmer, used to have an artificial water pond in front of his house they call the "Bomb pond." I think several family members of his died due to a bomb strike that created the pond. The bomb made the underground bomb shelter collapse, burying his family members alive. I remember never hearing him mention that incident. His two kids were born before the war concluded and he named them what roughly translated to "Vic" and "Tory." (People in Vietnam often name their kids in succession to form a statement or a wish like that. "Perse-Verant (in) Fight-Ting, Vic-Tory En-Sures" would not be a bad idea for names for a family with 8 kids. That was not a joke.)</p>
<p>There was a guava tree next to the bomb pond. Its fruits were sweet and its shade was nice. The tree has a Y-shaped branch that I enjoyed climbing and sitting on very much when I was 8 or so. I would often take a book and enjoy the sweet ripe, or crunchy green guavas when coming back from school. I sometimes wonder if the amazing taste of the fruits was due to the natural fertilizer that blessed the land years ago?</p>
<p>The other thing I remember is Trinh Cong Son's music. Trinh was a prolific songwriter during the Vietnam war, often referred to as the Bob Dylan of Vietnam. He wrote hundreds of anti-war songs, which pleased neither side of the war. Many of his songs were banned in North Vietnam and many of them are still banned today. Together with Khanh Ly, a singer, they made a duo performing his anti-war songs from university grounds to university grounds.</p>
<p>It's not hard to see the parallel between Trinh and Dylan. Dylan wrote "Blowing in the Wind" in 1963, in which he asked, "How many times must the cannonballs fly before they're forever banned?" Trinh wrote "Lullaby of the artillery" around that time.</p>
<p>Here is the translation of the banned lyrics that I listened to since I was 3 on the radio cassette player. Strange enough, it was a lullaby, to me, at least.</p>
<blockquote>
<p>Night after night fall shells t'wards town<br />
A sweeper stops her broom to hear<br />
As shells do fly, a mother wakes<br />
As shells do fly, a child's soft cry<br />
Bright flares burst open above the mountains</p>
</blockquote>
<blockquote>
<p>Night after night fall shells t'wards town<br />
A sweeper stops her broom to hear<br />
Each round's dark trip, a child in fright<br />
A shelter hit–O! Horrid sight<br />
Each night keeps flashing the face of our land</p>
</blockquote>
<blockquote>
<p>O! Tons of bombs fall on the gate<br />
The bombs like rain fall on our fields<br />
A house burns red at the end of the lane<br />
Grenades, claymores the trucks haul down<br />
Such endless stores they bring through town<br />
Our mothers' bones lie everywhere</p>
</blockquote>
<blockquote>
<p>Night after night fall shells t'wards town<br />
A sweeper stops her broom to hear<br />
Night after night, the future quakes<br />
As shells like empty prayers repeat<br />
A child half-living each night waits listening</p>
</blockquote>
<blockquote>
<p>Night after night fall shells t'wards town<br />
A sweeper stops her broom to hear<br />
Each night, the rounds, they sing for us<br />
Familiar sounds, like sad refrains<br />
What child will ever see home again.</p>
</blockquote>
<p><em>(Translated by Rich Fuller)</em></p>
<p>When I came to visit my neighbor years after moving away to the city, the bomb pond has been filled and converted into a big brickyard. Perseverant, Victory ensured, indeed. Both Vic and Tory were married and my neighbor enjoyed having several grandkids running around and riding bikes on the old bomb pond's grounds. For one, I was glad to see the kids grew up not knowing anything about the bomb pond (or the delicious guava tree, for that matter).</p>
<p>Trinh passed away 20 years ago, and Khanh Ly is currently residing in California. She immigrated to the US after the collapse of Saigon.</p>
<p>I am here, at the same time contemplating about my baby's names. I think I will be giving him an American name as he will be born an American. And I think I will give him a Vietnamese name to remind him of his origins. I am sure his granddad won't tell his grandkid the life story until the kid is old enough to take it. If I was any worthy of a benchmark, it will be roughly 35 years from now.</p>
<p>One time I paid a visit to my neighbor, he learned that I was going to study in the US. He told me "The US has much you can learn from, despite them being a capitalistic empire." At that point, I wonder whether my uneducated farmer neighbor was brainwashed by all the propaganda, or it was me.</p>
<p>Life is is nothing short of spectacular.</p>Not until a month ago did I learn that my dad had horrific experiences during the Vietnam war as a Northern Vietnamese citizen. He was a student when the war was at its height in 1972. All the students in his university had to move from the capital to a province far away to avoid being bombed. When people just finished moving, the new place got bombed. He had to move to another city to do an internship. When he just started the internship, the place he worked at got bombed and he had to move again. One time, he had to emerge from a house, which turned into rubble during the 1972 bombing operation (Also known as the "Operation Linebacker II" in the US).Zenreader: A 4.7 inches E-Ink RSS Reader Powered by ESP322021-04-12T00:00:00+00:002021-04-12T00:00:00+00:00https://www.tnhh.net/posts/zenreader-4.7-in-rss-eink-reader<p>I have been long wanting a PDA of sorts with an E-Ink screen that allows me to read stuff on the Internet without eye strains. But the Kindle <em>really</em> sucks as a newsreader device because the experimental browser never works correctly.</p>
<p>I don't know what the folks at Amazon were thinking when they make the browser suck this much. Let's say I want to read an article from Hacker News. The browser doesn't render HN correctly, sigh. When I tap on a link, it takes 10 seconds for the page to render. After 10 seconds, the browser renders the article as a desktop/tablet browser and it's zoomed out. So the resolution is to enable reader mode which requires me to tap the 3-dots menu and Article mode. With any luck, I will see the article in a somewhat presentable form. But it doesn't flip one page at a time, it works like a tablet so it scrolls pixels at a time. When I'm done with all that bullshit and want to tap back to go back to HN, it goes back to the article page without the reader mode enabled, and renders it all over again, making me wait and tap once more. That's not all: The browser doesn't go back from time to time, which makes it very frustrating.</p>
<p>Recently I came across the M5Paper with a big screen and a wireless chipset. I thought that could scratch my itch. So I went ahead and hacked together a firmware to make it an E-ink RSS Reader that mostly works for now. You can see it in action here:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/NYiZOIYzch8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<p>The ESP32 is a microcontroller that has very little RAM and isn't quite suited to deal with HTML and such. So I had to use a Raspberry Pi as a rendering proxy and transforms the RSS and the text on the page so that the ESP32 can digest. The idea is to transform XML RSS to JSON and transform any article URL to plaintext by a nodejs script via an HTTP API SaaS or whatever you call it. Even so, I think I'm stretching the ability of the little controller. Anyways, it works most of the time – or at least, I hope, not worse than the Kindle. At least now I can flip one page at a time.</p>
<p><img src="/assets/posts-images/zenreader1.jpg" alt="Screenshot" /></p>
<p><img src="/assets/posts-images/zenreader2.jpg" alt="Screenshot" /></p>
<p>I have only worked on this for two days ago, so there are a lot of rough edges and my Raspberry Pi proxy server may go down any time so be careful and don't abuse it. It's literally running on a tmux session right now and when it dies, it will not come back unless I manually connect to the Pi to resurrect the script. But I thought it might be useful to put the code up early to make it easy for everyone to experiment with it. Maybe you could contribute to it too.</p>
<p>You can get the client code that runs on the ESP32/M5Paper at <a href="https://github.com/htruong/zenreader">https://github.com/htruong/zenreader</a>. I'm not making the server code available now, because it is messy and runs on duck-tape now. But basically, you have to answer two endpoints, one for the RSS feed <a href="https://github.com/htruong/zenreader/blob/aa6e8f052160de36f3e583d0f47ff9e895a4beb3/src/frame/frame_feedcontent.cpp#L59">xml-to-json proxy</a>, and one for the <a href="https://github.com/htruong/zenreader/blob/aa6e8f052160de36f3e583d0f47ff9e895a4beb3/src/frame/frame_urlreader.cpp#L237">article reader mode transform</a>.</p>
<p>I hope that's enough of a motivation for you to get up and start hacking E-Ink with me.</p>I have been long wanting a PDA of sorts with an E-Ink screen that allows me to read stuff on the Internet without eye strains. But the Kindle really sucks as a newsreader device because the experimental browser never works correctly.FOSSASIA 2021: My Showmewebcam talk and more2021-03-07T00:00:00+00:002021-03-07T00:00:00+00:00https://www.tnhh.net/posts/showmewebcam-fossasia<p>I will be talking about Showmewebcam at the FOSSASIA 2021 summit next week on March 15. The presentation is titled "Building an open-source webcam with a Raspberry Pi for Work, Play, and Education," in the Hardware, Firmware, Chips track. In 15-20 minutes, I will be talking about how to leverage the Raspberry Pi as a general-purpose gadget to make it a versatile tool for remote conferencing with an emphasis on education. I will also be talking about general software development philosophy, and how to maintain the project as a hobby.</p>
<p>By the way, the summit has quite a bit of interesting talks that I also look forward to attending:</p>
<ul>
<li>Bunnie Huang's Keynote: "Precursor: Trustable Open Hardware For Everyday Use." Bunnie is a huge source of inspiration for me personally and is hilarious to listen to. The precursor is a open source hardware phone that has very strange goals that are to provide trust to users who are very concerned about privacy.</li>
<li>Panel: "What's Next in Open Source Firmware and Open Hardware." Lots of respectable voices, and I'm sure they know a lot of things we don't.</li>
<li>Greg Kroah-Hartman's Keynote "Linux Kernel Development." GKH is a legend and as I want to learn more about Linux kernel development in the long run, I am eager to learn from him.</li>
<li>Seeed Studio's Eric Pan's "How Open Ecosystem Empowers Developers to Digitalize Industries." I have more than once get hardware from Seeed and would love to hear their perspectives on hardware.</li>
<li>Huyen Chip's "Machine learning for scalable applications" - I want to get into Machine learning eventually but haven't got the time to do it. Perhaps I can start somewhere.</li>
<li>"Open Street Map x Grab - a model for successful open collaboration with the community" As a taxi driver, I love everything Grab.</li>
</ul>
<p>The conference is free to register, so if you're interested, please register and add it to your calendar. Lurking from my event link below should give you the links to all the talks. You can add any event to your calendar so it reminds you when to join. The timing is odd for North American friends but should be very convenient for friends in Vietnam. I will hang out to answer any questions you might have for the project after the presentation. Your support is a huge encouragement for me to work on odd projects such as this one.</p>
<p><a href="https://eventyay.com/e/fa96ae2c/session/6835">Link to event</a></p>I will be talking about Showmewebcam at the FOSSASIA 2021 summit next week on March 15. The presentation is titled "Building an open-source webcam with a Raspberry Pi for Work, Play, and Education," in the Hardware, Firmware, Chips track. In 15-20 minutes, I will be talking about how to leverage the Raspberry Pi as a general-purpose gadget to make it a versatile tool for remote conferencing with an emphasis on education. I will also be talking about general software development philosophy, and how to maintain the project as a hobby.Your phone vs. Supercomputers2021-02-07T00:00:00+00:002021-02-07T00:00:00+00:00https://www.tnhh.net/posts/phone-power<p>Today, I looked up how many <a href="https://en.wikipedia.org/wiki/FLOPS">floating point operations per second</a> (FLOPS) that an iPhone can do. It turns out the A14 chip in the iPhone <a href="https://www.forbes.com/sites/moorinsights/2020/10/15/apple-claims-the-iphone-12s-a14-bionic-challenges-laptops-but-gives-no-details/">can do about 11 TFlOPS according to Apple</a>. Let's give Apple the benefits of the doubt and not get too deep into how they got that number, and put it to perspective and compare the iPhone with the <a href="https://en.wikipedia.org/wiki/History_of_supercomputing">Top 500 Supercomputers</a> in the world over the years:</p>
<p><img src="/assets/posts-images/iphone-vs-supercomputers.png" alt="iPhone vs Supercomputers" /></p>
<p>The green line is how much computing power that little phone I'm holding in my hands has. If released in 2009, it would have made it to the list of top 500 supercomputers in the world! It has more computing power than the most powerful computer in the world in 2002. It has the combined computing power of the 1997's top 500 supercomputers in the whole world.</p>
<p>People talked about how the computer on Appolo was so slow. I get it, but I can hardly relate to that. I wasn't born then. However, 2009 was just 12 years ago. I could still remember it vividly. I was a freshman student then.</p>
<p>Reflecting on that, I figured:</p>
<ul>
<li>Something with the form factor and power envelope of the phone alone is already exceedingly powerful. It could potentially do things that humankind couldn't achieve just ten years ago.</li>
<li>On the other hand, I don't need to have the most whizz-bang computer or phone to make programs that do amazing things. Writing programs for older hardware can be a pleasure. Supporting older hardware makes programs I create more accessible to a larger part of the human population.</li>
<li>As a programmer, when I complain about the phone or any computer I have is not powerful enough for whatever I want to do, it's much more likely it's me doing something wrong.</li>
<li>The world changes faster than my mind conceives. I need to be humble about what I know. Things I know to be fast becomes slow sooner than I expect. In the same realm, maybe ideas that I know to be good become bad sooner than I expect. Those ideas are not necessarily limited to technology: Maybe ideas in science, humanities, and society also envolve that way. It is just harder to put into numbers like TFLOPS.</li>
</ul>
<p>We have the most power computer in our pocket. Doing something amazing with that power should constantly be a question on our mind. All that power just to push one more ads is a fucking stupid and disgusting idea.</p>Today, I looked up how many floating point operations per second (FLOPS) that an iPhone can do. It turns out the A14 chip in the iPhone can do about 11 TFlOPS according to Apple. Let's give Apple the benefits of the doubt and not get too deep into how they got that number, and put it to perspective and compare the iPhone with the Top 500 Supercomputers in the world over the years:Dark, Light, and the open-source microscope2021-01-18T00:00:00+00:002021-01-18T00:00:00+00:00https://www.tnhh.net/posts/dark-light-scope<p>During the long weekend break, I watched quite a bit of science documentaries. One of which was "Dark and Light." The documentary talks about how light allows us to begin understanding what is around us. The discovery of the scope – the device that allows us to bend light was crucial to us to see things, big and small. The telescope allowed us to look up to the sky and see the stars. The microscope allowed us to look down to the really tiny creatures that our naked eyes can't see. One of the first people to have looked up in the sky with a telescope was Isaac Newton. He discovered that there are stars that our eyes are not able to see, thus realizing we aren't at the center of the universe. The first person to have documented such small creatures was Robert Hooke. He drew the first flea in an amazing amount of detail in 1665. He then drew little compartments he saw when magnifying the cork, thus coined the term "cell." The ability to have the technology to see light better opens the flood gate to so many discoveries that set us apart as human.</p>
<p><img src="/assets/posts-images/Hooke-microscope.png" alt="Hooke microscope - Wikipedia" /></p>
<p>My mom used to teach middle school biology in a remote village outside of Hanoi, Vietnam. One day, when I was about eight years old or so, she brought home an old shabby compound microscope from her lab. That was the most wonderful thing I have seen. I used to stare at leaves and saw cells, at ants, and I saw their body in intricate details. I put everything in the scope to see what it looked like. Too bad, my mom had to return the scope after a couple of days. Yet the wonder of the small world intrigued me since.</p>
<p><img src="/assets/posts-images/HookeFlea01.jpg" alt="Hooke Flea - Wikipedia" /></p>
<p>Those big a-ha moments are not easy to come by. Humans have been around for around 200,000 years, but we just learned to see beyond our naked eyes in less than 500 years. We have been living in the dark for more than 99% of the time we existed.</p>
<p>Incremental progress is still being made every day. We are still very early in the game. Yesterday, my professor sent me a video of someone using the software I wrote called show-me webcam to convert an analog scope to digital.</p>
<p>I knew there will be someone someday who would do exactly that. A digital scope makes it easier to share our curiosity and wonder about the world around us to everybody. That's the power of open-source software and hardware at work. It is just a piece of code that turns a piece of hardware to act as a webcam. However, the hardware is not limited to being just a webcam to show faces – the ability to adapt it to make it do all the wonderful things to understand this world better is in the hands of the users. I am glad to see signs of progress being made. And I am so glad and so proud to be a part of it, however insignificant.</p>
<p>If you want to watch Dark and Light:</p>
<ul>
<li><a href="https://www.dailymotion.com/video/x21szxu">Dark and Light - Part 1: Light</a></li>
<li><a href="https://www.dailymotion.com/video/x609945">Dark and Light - Part 2: Dark</a></li>
</ul>
<p>And the show-me webcam powered microscope:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=xTO2D06ckTo">Show-me webcam powered microscope</a></li>
</ul>During the long weekend break, I watched quite a bit of science documentaries. One of which was "Dark and Light." The documentary talks about how light allows us to begin understanding what is around us. The discovery of the scope – the device that allows us to bend light was crucial to us to see things, big and small. The telescope allowed us to look up to the sky and see the stars. The microscope allowed us to look down to the really tiny creatures that our naked eyes can't see. One of the first people to have looked up in the sky with a telescope was Isaac Newton. He discovered that there are stars that our eyes are not able to see, thus realizing we aren't at the center of the universe. The first person to have documented such small creatures was Robert Hooke. He drew the first flea in an amazing amount of detail in 1665. He then drew little compartments he saw when magnifying the cork, thus coined the term "cell." The ability to have the technology to see light better opens the flood gate to so many discoveries that set us apart as human.Notes on running Ubuntu Linux on the Thinkpad X1 Nano2021-01-12T00:00:00+00:002021-01-12T00:00:00+00:00https://www.tnhh.net/posts/ubuntu-thinkpad-nano<p>I received the Thinkpad X1 Nano last night and wiped Windows to install Ubuntu. I was too lazy to install Ubuntu from scratch, so I used <a href="https://clonezilla.org/">CloneZilla</a> to transfer the current Linux installation the Dell XPS laptop that I was using to have to it.</p>
<p>For the new cloned laptop to boot, I had to set the Secure Boot in the UEFI setup interface to not be in the "customer" mode. The Thinkpad X1 Nano is using the 11th Generation Intel chipset so it needs a new Linux kernel. As the Dell XPS laptop was already running 5.10 so I did not have to do anything extra. The laptop just booted. For reference, I installed <code class="language-plaintext highlighter-rouge">5.10.0-1008-oem</code> kernel.</p>
<p>Audio worked out of the box, but the volume control didn't. So to fix that, I edited <code class="language-plaintext highlighter-rouge">/usr/share/alsa/ucm2/sof-hda-dsp/HiFi.conf</code> <a href="https://www.sysorchestra.com/linux-mint-20-upgrade-on-lenovo-thinkpad-x1-carbon-7th-sound-and-fingerprints/">as if you're working on the X1 Carbon 7th gen</a>. The only difference to make it work with the X1 Nano is that instead of changing <code class="language-plaintext highlighter-rouge">Speaker</code> to <code class="language-plaintext highlighter-rouge">Master</code>, I changed it to <code class="language-plaintext highlighter-rouge">PGA1.0 1 Master</code>.</p>
<p>For the fingerprint reader to work, I also followed the same post in the link above. That was one item that I didn't expect it to work, but it did!</p>
<p>Please let me know if it works for you.</p>
<p><img src="/assets/posts-images/thinkpadx1nano.png" alt="Thinkpad X1 Nano Gen1" /></p>I received the Thinkpad X1 Nano last night and wiped Windows to install Ubuntu. I was too lazy to install Ubuntu from scratch, so I used CloneZilla to transfer the current Linux installation the Dell XPS laptop that I was using to have to it.Show-me Webcam: Building an open-source and high-quality webcam with a Raspberry Pi 0 W2020-08-02T00:00:00+00:002020-08-02T00:00:00+00:00https://www.tnhh.net/posts/show-me-webcam<iframe width="560" height="315" src="https://www.youtube.com/embed/8qo2LUFLHgE" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<p><em>(As with any recipe, if you just want to see how to build the webcam yourself, skip to the <strong>Recipe</strong> section. The following wall of text is just my life story.)</em></p>
<h2 id="my-motivations">My motivations</h2>
<p>I've been thinking about webcams for a while after I read about numerous pieces about <a href="https://www.digitaltrends.com/computing/laptops-need-better-webcams/">how terrible</a> <a href="https://fossbytes.com/why-are-we-still-using-bad-laptop-webcams-low-quality/">laptop built-in webcams</a> <a href="https://onezero.medium.com/why-your-laptop-webcam-is-still-a-piece-of-junk-9b57971d8d">are</a> in the day and age of zooming-from-home. People seem to have problems with laptop built-in webcams on the following aspects:</p>
<ul>
<li>The webcams that come with your laptops (even the most expensive ones) have terrible picture quality.</li>
<li>They can't be trusted: People put tapes on them. <a href="https://support.apple.com/en-us/HT211148">You are advised against doing it, though</a>.</li>
</ul>
<p>There are a couple of remedies people came up with for the built-in webcam quality:</p>
<ul>
<li>Buy an external webcam. Cons: They only give marginally better results on picture quality.</li>
<li>Use your phone to do meetings. Cons: Small phone screen, impossible to see shared meeting screens well, impossible to share screens, impossible to access work documents at the same time, limited by the functionalities that the mobile app provides.</li>
<li>Use your phone as a webcam. Cons: Cumbersome software setup, lags.</li>
<li>Use a professional camera + HDMI capture card as a webcam.</li>
</ul>
<p>Many "professional" streamers choose the last solution, and I have tried to do it myself too. It took me two months to wait for my $50 capture card from China to arrive. Then when it finally came, I realized this is a very cumbersome setup. First, my camera (a Sony NEX-5R) cannot output a "clean" HDMI output (i.e. without the GUI elements). The only way to get rid of that stupid overlay is to buy another newer camera (say, a Sony A7 is $500+ used). Second, the camera does not charge or stay charged on USB, so I have to buy another dummy battery that feeds from a DC source. Third, this setup is not portable.</p>
<p>Moreover, none of the above solutions allow us to trust, control, and improve on them (At least, physically disconnecting them solves the privacy concern, which isn’t something you can do with a laptop built-in camera).</p>
<h2 id="the-raspberry-pi-high-quality-camera">The Raspberry Pi High-Quality Camera</h2>
<p>So I set out to build a webcam of my own. Recently, I noticed that the Raspberry Pi Foundation released a high-quality camera, which is a fantastic candidate. It's a professional camera with a very big sensor and an assortment of interchangeable lenses. Being in the right hands, this can be a very powerful tool to achieve many things. Can it be a webcam to use with my ordinary computer though?</p>
<p>I know it is possible to stream the webcam over the network, and then use a computer to turn the network stream back to a virtual webcam. However, because this setup depends on the operating system, the setup is not portable. It also introduces lags which makes the voice trails behind the images.</p>
<p>So in short, I want the Pi Camera to appear as just an ordinary USB webcam to my laptop.</p>
<p><img src="/assets/posts-images/showmewebcam/picam_hw.jpg" alt="Pi Cam" /></p>
<p>With a bit of googling, I learned that it is possible to make the Pi appear as a USB webcam! There is a mode on the Pi 0 USB port called <a href="https://learn.adafruit.com/turning-your-raspberry-pi-zero-into-a-usb-gadget">USB gadget mode</a>, which makes that port a guest port for the system it's plugged in to. I got a good start with <a href="http://www.davidhunt.ie/raspberry-pi-zero-with-pi-camera-as-usb-webcam/">Dave Hunt's post</a>. However, there are things to be desired with his software solution. First, it uses Raspbian as a base, which means you have a writable root, and an abrupt disconnection of power (we will do that with a webcam) will cause filesystem corruption. Second, the camera takes a long time to start. Those are the two main barriers to this approach: It works as a hack, but it works less well as a solution you'd depend on.</p>
<p>So, I set out to build a more robust firmware for the project. The following is the documentation of what I did to get the firmware working, and I think what I went through can be helpful if you want to roll your firmware to do things you'd like a dedicated embedded firmware for your Pi 0, too.</p>
<h2 id="building-a-webcam-embedded-firmware-with-buildroot">Building a webcam embedded firmware with Buildroot</h2>
<p>One of the tools that I grew fond of recently is Buildroot. Buildroot is a framework/tool to allow you to roll out your own Linux OS/distribution for embedded devices. It powers <a href="https://github.com/flutter/buildroot">a couple of</a> <a href="https://github.com/teslamotors/buildroot">big</a> <a href="https://openwrt.org/docs/techref/buildroot">projects</a>. So I set out to build my firmware with Buildroot.</p>
<p>I plan to do the following:</p>
<ul>
<li>Do the initial bring-up and get the baseline firmware with busybox working.</li>
<li>Get the firmware to connect to my Wi-Fi network and enable SSH access.</li>
<li>Do all the customizations to bring up the UVC webcam.</li>
<li>Disable Wi-Fi and SSH access.</li>
<li>Add optimizations to protect the device from abrupt power disconnects.</li>
</ul>
<p>For step 1, I followed the tutorial on <a href="https://armphibian.wordpress.com/2019/10/01/how-to-build-raspberry-pi-zero-w-buildroot-image/">ARM fever</a> to have to build a baseline image. Except having to tweak some wireless issues so I could remotely debug the Pi 0 via SSH, the tutorial was spot-on. I knew if the Pi 0 only USB OTG port was in peripheral (gadget) mode, it would have been impossible to connect a USB ethernet dongle to it, so SSH-over-wireless was essential. The package for enabling the SSH access to the Pi is dropbear. Getting a buildroot Pi working with dropbear took me a night.</p>
<p><img src="/assets/posts-images/showmewebcam/showmewebcam.png" alt="Buildroot menuconfig" /></p>
<p><em>The very helpful menuconfig system that comes with buildroot</em></p>
<p>Next, it looked to me that a simple sysv-init system was going to be a growing pain. I don't know how to exactly deal with an udev system that the UVC-webcam project assumes, so I switched the init system to systemd which gives me udev automatically. But this change to systemd changes a lot of how networking is done, so getting back a console was quite frustrating. There are a couple of ways you can get a root shell to the Pi: Via <code class="language-plaintext highlighter-rouge">ttyACM0</code> (the 40 pin serial headers, which I don't have soldered), via the HDMI console (<code class="language-plaintext highlighter-rouge">tty1</code>), or the USB port in gadget mode (<code class="language-plaintext highlighter-rouge">ttyGS1</code>), or via SSH (the issue was that get wireless and wired networking was not functional). The issue was that I wasn't sure of what parameters could be used to set them up when none of the venues were working. Without a way to see what wasn't working by looking at the logs or a way to fix issues on the fly, the job became significantly harder. I learned that my <a href="https://gist.github.com/htruong/7df502fb60268eeee5bca21ef3e436eb">chroot-to-pi script</a> helped debug boot issues. After seeing the Pi failing to give me the console, I could still chroot to the SD card and call <code class="language-plaintext highlighter-rouge">journalctl</code> to see what went wrong in the last boot with the script. After a while I learned of the names of the device and what went wrong, I was able to get a login prompt via the HDMI console. Once I got in the barebone Pi working with systemd, it became quite a bit easier to set up the serial console on the rest of the interfaces.</p>
<p>Then, I did quite a bit of reading on how to <a href="https://buildroot.org/downloads/manual/manual.html">set up an external buildroot tree</a>. This step is to avoid having to mangle between the buildroot tree and my system. I added the UVC-webcam package to my tree, that was very simple.</p>
<p>Then the rest was just fixing things and adding boot params in the right places until the whole system worked. The last part was to make the filesystem read-only. That task was done automatically with the rootfs being squashfs. This broke a couple of services at startup that assumed a writable rootfs, but that was not hard to fix – mounting <code class="language-plaintext highlighter-rouge">/var</code> as a <code class="language-plaintext highlighter-rouge">tmpfs</code> solved the issue.</p>
<p>Finally, it was time to remove all the networking support that I added for debugging purposes and just set up the serial access on the USB gadget interface.</p>
<p>The camera boots very quickly in 13 seconds. We can even get root on the webcam by logging in the serial interface presented on the Pi at 115200 baud as <code class="language-plaintext highlighter-rouge">root:root</code>.</p>
<p><img src="/assets/posts-images/showmewebcam/showmewebcam1.png" alt="Root" /></p>
<p><em>Now you can have root on your webcam too!</em></p>
<h2 id="quality-of-the-pi-webcam">Quality of the Pi Webcam</h2>
<p><img src="/assets/posts-images/showmewebcam/webcam.jpg" alt="Webcam" /></p>
<p><em>Built-in Dell XPS13 2019 webcam</em></p>
<p><img src="/assets/posts-images/showmewebcam/iphone.jpg" alt="iPhone" /></p>
<p><em>iPhone SE 2 front camera</em></p>
<p><img src="/assets/posts-images/showmewebcam/camera.jpg" alt="Camera" /></p>
<p><em>Sony NEX-5R via capture card</em></p>
<p><img src="/assets/posts-images/showmewebcam/picam.jpg" alt="Camera" /></p>
<p><em>Pi Camera via Show-me Webcam</em></p>
<p>For my untrained eyes, the quality of the Pi cam is way better than a laptop webcam but a little bit worse than a dedicated camera. It does bokeh very well and has a fixed focus so I would consider it a plus rather than a minus. The camera outputs a consistent 30fps on Windows and Linux, but it does not work on macOS <a href="https://github.com/showmewebcam/showmewebcam/issues/1">for some reason</a>.</p>
<p>I suppose it is possible to improve the firmware/software even more. For example, we can make it so the webcam auto-pans to the face. Or maybe we can warn the user when the webcam is out of focus. Or maybe we can even replace the background. However, those are the niceties that can be added later.</p>
<p>If you'd like to take on the adventure with the current firmware, build it yourself, or contribute to the project, please head to the <a href="https://github.com/showmewebcam/showmewebcam">project repo</a>.</p>
<h2 id="recipe">Recipe</h2>
<ul>
<li>Raspberry Pi 0 W (I have not tested with the Pi 0 non-Wireless)</li>
<li>Pi 0 W Camera Ribbon (comes with the Pi 0 camera case or you can buy a longer one somewhere else, the stock one that comes with the camera very likely will not work).</li>
<li>Raspberry Pi Camera or Raspberry Pi High-Quality Camera</li>
<li>A micro SD card with at least 64MB of storage.</li>
</ul>
<h2 id="directions">Directions</h2>
<ul>
<li>Assemble the camera.</li>
<li><a href="https://github.com/showmewebcam/showmewebcam/releases/">Download the show-me webcam binary release</a>.</li>
<li>Download and use Etcher to write the image to the SD card.</li>
<li>Use the USB data port (the one in the middle of the Pi, not the one on the edge) to connect to a computer.</li>
<li>Enjoy!</li>
</ul>
<h2 id="a-remark-about-the-security-of-show-me-webcam">A remark about the security of Show-me Webcam</h2>
<p>This project is more of an exercise on embedded and open-source hardware first and a secure webcam second. I even allow root access from the host computer with a well-known password. Perhaps that's not something you would want a secure webcam to do. With the root prompt, an SD card, and a wireless-capable chip and no integrity checking, it only takes a bit of slip-up for the webcam to work against you. Please be mindful of where you plug the webcam to and who gets physical access to it. Even when root console access is disabled, someone can just as easily swap the firmware on the card when they get physical access to it. Please keep in mind that "with great power comes great responsibility." On the bright side of the problem, we should enjoy living in the day and age of being able to write a webcam firmware ourselves. I don't think it was ever possible before.</p>My venture in hacking a fake vintage radio2020-06-24T00:00:00+00:002020-06-24T00:00:00+00:00https://www.tnhh.net/posts/adventures-hacking-fake-vivitar-vintage-radio<p><img src="https://media0.giphy.com/media/dCcLQ5TLFANMj0p3Uh/giphy.gif" alt="Press F to pay respects" /></p>
<p>For a year or so I have owned a nice fake-vintage radio/bluetooth speaker that originally caught my eye for sale in a FedEx office. The front has a quite nice VFD-style LED to show the status, a volume knob and four hard buttons. It has Bluetooth, USB, AUX and FM input. The radio and bluetooth was not bad, but there was nothing to be impressed about. It was definitely not "smart."</p>
<p>I decided to hack it to make it a bit smarter: to do AirPlay and be a smart alarm clock and whatever else I could think of. Since it was inexpensive, I had nothing to lose and everything to win. I thought putting a Raspberry Pi 0 or something in it would be nice.</p>
<p>My original plan was to somehow figure out how to hijack all the external components and control them to make the front and back of the radio as neat-looking as possible. At least I hoped to hijack the speaker, the volume knob, and the buttons. The problem I foresaw was that those components are often not documented and there was little chance that I could get a datasheet for them. I was very worried that I might have to give up the fake-VFD LED screen.</p>
<p>It turns out that I had to spend a year hacking it on and off. Here goes.</p>
<p><img src="/assets/posts-images/fake-radio-hack/fedex-sale.jpg" alt="FedEx sale" /></p>
<p>First, I gutted the radio and saw that it has a main PCB with the LED panel directly soldered to it.</p>
<p><img src="/assets/posts-images/fake-radio-hack/radio-disassembled.jpg" alt="Disassembled radio" /></p>
<p>The chip on the PCB isn't something I recognized. Luckily, the knob and the hard buttons are on a separate breakout board and are connected to the mainboard via a connector.</p>
<p><img src="/assets/posts-images/fake-radio-hack/main-pcb.jpg" alt="Main PCB" /></p>
<h2 id="the-speaker">The speaker</h2>
<p>The speaker is also easy to deal with, as it is completely ordinary. It only required me to desolder it from the mainboard. As the Pi 0 has no built-in soundcard, I had to get a separate sound card for it.</p>
<h3 id="audio-injector-zero">Audio Injector Zero</h3>
<p>Initially, I used the Audio Injector Zero that I have no use for normally. To save space, I decided to permanently sandwich the Audio Injector to the Pi 0. I also had to buy a cheap amp for it, as the sound card can't directly drive the speaker.</p>
<p>One little issue was that the sound card produces stereo input, and the amp is also stereo, but I only have one speaker. I needed to mix both channels left and right to that speaker somehow. To that end, I am sure a Linux guru could make it work with configuring the sound card in software. However, I am not extremely well-versed with how ALSA or Pulse Audio works. I just wired both channels left and right of the amp to one channel input of the amp to mix the channels. For power, the amp needs 5V input, so I siphoned the two power pins from the Raspberry Pi.</p>
<p>I was surprised when the amp produced a very annoying hum. The hum was extremely noticeable when the Pi is under load. I don't know exactly why this happens, but I spent way too much time on this with no significant improvement. Ultimately, I decided it's the cheap amp that was giving me a hard time, because it was way too noisy. I know the Audio Injector itself was okay because the headphones output was clean.</p>
<h3 id="google-aiy-voice-hat">Google AIY voice hat</h3>
<p>After giving up on the Audio Injector Zero + Amp solution, I tried a different route: Drive the speaker directly with the Google AIY voice bonnet hat. Because the Google AIY sound card doesn't provide an easy way to set up on a plain raspbian, I just got their distro and started from there.</p>
<p>The speaker was hum-free after switching to the Google AIY soundcard. The tradeoff was that the Google Voice Bonnet V2 sound card made the stack quite a bit thicker. It has a female row of pins soldered on it, so I couldn't solder it directly on top of the Pi Zero.</p>
<p><img src="/assets/posts-images/fake-radio-hack/sound-cards.jpg" alt="Sound cards" /></p>
<h2 id="multimedia-buttons-and-volume-knob">Multimedia buttons and volume knob</h2>
<p>The front panel of the radio has two sets of elements: A knob of some sort to control the volume and four hard buttons. It has four wires hanging out of it. After I inspected the traces on the PCB, it was apparent that there is one common ground. The three other pins are for the buttons and the knob. All of the buttons are connected to a single pin, and the volume knob is connected to the other two.</p>
<p><img src="/assets/posts-images/fake-radio-hack/buttons-knob.jpg" alt="Buttons and knobs" /></p>
<p>As a software person, I didn't know what the knob is called. <a href="http://dbindner.freeshell.org/">Dr. Don Bindner</a> suggested that the button might be a rotary encoder, and that turned out to be true. It seemed <a href="http://www.learningaboutelectronics.com/Articles/Rotary-encoder-circuit.php">trivial enough to read the knob</a>.</p>
<p>The buttons are more interesting. After poking around with a multimeter, it turned out that the buttons were tactile but each of them is connected in series to a different resistor. Then they all connect out to the same pin. The software in the chip scans the pin and figures out what button was pressed by sampling from an Analog-to-Digital (ADC) Converter reading. As the Pi has no ADC, we need something else that has an ADC to figure out what the button was pressed. Of course, I could as well hijack the tactile buttons and not have to deal with the ADC stuff but that seemed too aesthetically unpleasant for me.</p>
<p>I decided to pull out anArduino Pro Micro clone that I bought from China years ago. My reasoning for using the Pro Micro:</p>
<ol>
<li>The Arduino Pro Micro is super cheap: $2 cheap.</li>
<li>Even if I could add an ADC to the Pi, I need to continuously read the ADC from the Pi. That is a pain in the neck. I can't guarantee the timings on the Pi without going to a lot of care.</li>
<li>The Pro Micro can emulate a keyboard over USB, so the Pi can use triggerhappy to catch it.</li>
</ol>
<p>So with that, I made my arduino sketch and it worked out. I wanted to emulate the Winamp-style buttons ZXCV for multimedia keys and UD for volume up/down. Later on, I was quite annoyed with it because the keyboard dumps the keystrokes to the terminal, and <a href="https://androidcommunity.com/very-odd-bug-found-in-jailbreaking-process-20081109/">it could be bad</a>. I decided to implement the keys as multimedia keys. Unfortunately, you need a HID library to implement multimedia keys. Luckily, there is a library called <a href="https://github.com/NicoHood/HID">HID-Project</a> to achieve exactly what I wanted. After some tweakings for debounce, I could get the volume knob and the buttons to work exactly as I expected:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">pressKey</span><span class="p">(</span><span class="kt">uint16_t</span> <span class="n">k</span><span class="p">,</span> <span class="kt">int</span> <span class="n">d</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Consumer</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">k</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">d</span><span class="p">)</span> <span class="p">{</span>
<span class="n">delay</span><span class="p">(</span><span class="n">d</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">kbd_loop</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">millis</span><span class="p">()</span> <span class="o"><</span> <span class="n">debounce</span> <span class="o">+</span> <span class="n">debouncerMs</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Volume rotary handling</span>
<span class="n">boolean</span> <span class="n">encoderA</span> <span class="o">=</span> <span class="n">digitalRead</span><span class="p">(</span><span class="n">encoderPinA</span><span class="p">);</span>
<span class="n">boolean</span> <span class="n">encoderB</span> <span class="o">=</span> <span class="n">digitalRead</span><span class="p">(</span><span class="n">encoderPinB</span><span class="p">);</span>
<span class="k">if</span> <span class="p">((</span><span class="n">encoderA</span> <span class="o">==</span> <span class="n">HIGH</span><span class="p">)</span> <span class="o">&&</span> <span class="p">(</span><span class="n">encoderB</span> <span class="o">==</span> <span class="n">HIGH</span><span class="p">))</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">((</span><span class="n">encoderALast</span> <span class="o">==</span> <span class="n">LOW</span><span class="p">)</span> <span class="o">&&</span> <span class="p">(</span><span class="n">encoderBLast</span> <span class="o">==</span> <span class="n">HIGH</span><span class="p">))</span> <span class="p">{</span>
<span class="n">pressKey</span><span class="p">(</span><span class="n">MEDIA_VOL_UP</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">((</span><span class="n">encoderBLast</span> <span class="o">==</span> <span class="n">LOW</span><span class="p">)</span> <span class="o">&&</span> <span class="p">(</span><span class="n">encoderALast</span> <span class="o">==</span> <span class="n">HIGH</span><span class="p">))</span> <span class="p">{</span>
<span class="n">pressKey</span><span class="p">(</span><span class="n">MEDIA_VOL_DOWN</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">encoderALast</span> <span class="o">=</span> <span class="n">encoderA</span><span class="p">;</span>
<span class="n">encoderBLast</span> <span class="o">=</span> <span class="n">encoderB</span><span class="p">;</span>
<span class="c1">// Multimedia buttons</span>
<span class="kt">int</span> <span class="n">pushBtnRead</span> <span class="o">=</span> <span class="n">analogRead</span><span class="p">(</span><span class="n">pushBtnPin</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">pushBtnRead</span> <span class="o">></span> <span class="mi">900</span> <span class="o">&&</span> <span class="n">debounce</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">debounce</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">pushBtnRead</span> <span class="o"><=</span> <span class="mi">264</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">pushBtnRead</span> <span class="o">></span> <span class="mi">200</span><span class="p">)</span> <span class="p">{</span>
<span class="n">pressKey</span><span class="p">(</span><span class="n">MEDIA_NEXT</span><span class="p">,</span> <span class="n">delayMultimediaKeyMs</span><span class="p">);</span> <span class="c1">// Next</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">pushBtnRead</span> <span class="o">></span> <span class="mi">150</span><span class="p">)</span> <span class="p">{</span>
<span class="n">pressKey</span><span class="p">(</span><span class="n">MEDIA_PREVIOUS</span><span class="p">,</span> <span class="n">delayMultimediaKeyMs</span><span class="p">);</span> <span class="c1">// Prev</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">pushBtnRead</span> <span class="o">></span> <span class="mi">115</span><span class="p">)</span> <span class="p">{</span>
<span class="n">pressKey</span><span class="p">(</span><span class="n">MEDIA_STOP</span><span class="p">,</span> <span class="n">delayMultimediaKeyMs</span><span class="p">);</span> <span class="c1">// Stop/M</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">pressKey</span><span class="p">(</span><span class="n">MEDIA_PLAY_PAUSE</span><span class="p">,</span> <span class="n">delayMultimediaKeyMs</span><span class="p">);</span> <span class="c1">// Play pause</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">debounce</span> <span class="o">=</span> <span class="n">millis</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>I could test the Arduino implementation right on my development computer with <code class="language-plaintext highlighter-rouge">xev</code> so that was really nice.</p>
<h2 id="the-fake-vfd-led-screen">The fake VFD LED screen</h2>
<p>The screen was the part that I had the most doubt about being able to control, because I have never worked with such a device before. Before trying to reverse-engineer it, I tried to look up similar 7-segment LED screens online hoping to find something similar. Those screens often have more than 8 pins (1 for the ground, 7 for each segment, and some more to select the digit. This one is nothing like that: It has only 7 pins. I wanted to give it up and just buy another screen that has a datasheet to save myself from trouble but I couldn't find anything that would fit into the original cutout for the screen. So I had to bite the bullet and hack the LED screen (I had nothing better to do in the craziness of the pandemic).</p>
<h3 id="general-workings">General workings</h3>
<p>There was no giveaway from the PCB what the ground pin for the display might be. I only had a multimeter in hand so what I did was set it to the diode tester mode. Then I probed pairs of pins to see what lights up. As luck would have it, I found out that each pair of pins lit up a different segment on the screen. So there was no common ground at all! I drew the screen and noted what pin pair light up each segment (excuse my nasty draft paper):</p>
<p><img src="/assets/posts-images/fake-radio-hack/matrix-draft.jpg" alt="Draft Matrix" /></p>
<p>First, I was really annoyed because the screen is yet another custom device I had to reverse engineer. Then, it really impressed me that this screen is quite well designed for such a cheap device. There were exactly 42 segments on this LED screen! The number of different segments is exactly the maximum number of permutations between any 2 pins: P(7,2) = 42. After some more digging, I found out that this wiring scheme is called <a href="https://hackaday.com/tag/charlieplexing/">Charlieplexing</a>. So each LED segment has a voltage drop of 1.860V, and my Arduino is 5V. I assumed a 20mA current and used a LED calculator and to figure out that I need to connect a 150K resistor to the LED. Because of the charlieplexing setup, I actually need half of that resistance for each pin (it will be apparent as you read on), so I soldered a 68K resistor to each pin.</p>
<h3 id="control-the-led-screen-programmatically">Control the LED screen programmatically</h3>
<p>The question then became how to control this LED screen programmatically. It was clear to me I can't possibly draw every segment by toggling the pins on and off in one whole sweep. Then I realized that I need to think of the LED screen like an <a href="https://www.youtube.com/watch?v=r38nVmxBfvM">analog TV screen</a>. Thus, each of the pins can be thought of as a horizontal scanline – except they are not on the same “line”! This might be quite obvious to those who have dealt with CRTs in the past (which I have not), but it might be hard to imagine for those who grew up with LCDs.</p>
<p>Let's say I want to draw the screen with segment 17, 18, and 11 lit up. That means I have to do two sweeps. First, pull pin 1 to GND and pull pin 2 to V_LED to light up segment 18. Then, pull pin 4 to GND and pull pin 1 and 7 to V_LED to light up segment 17 and 11 at once. Do it fast enough, and my eyes won't be able to tell we are flashing them!</p>
<p>I had to set up a matrix of the screen buffer of what segments I want to draw. Then I needed a function that goes around and pulls one of the 7 pins to GND. It needs to pull whatever pins that are responsible for the ON segments in that matrix to VCC. Whatever pin that I give it VCC, it will need to have 150K resistance to prevent LED burnout. I needed half of that resistance on each pin (VCC and GND), because the GND pin rotates throughout.</p>
<p>The function that scans the LED screen buffer (<code class="language-plaintext highlighter-rouge">scr_buf[SCR_SEGMENTS]</code>) and draw one scanline in a loop:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">const</span> <span class="n">byte</span> <span class="n">SCR_LED_PINS_COUNT</span> <span class="o">=</span> <span class="mi">7</span><span class="p">;</span>
<span class="k">static</span> <span class="k">const</span> <span class="n">byte</span> <span class="n">SCR_SEGMENTS</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span>
<span class="k">static</span> <span class="k">const</span> <span class="n">byte</span> <span class="n">scr_segment</span><span class="p">[</span><span class="n">SCR_LED_PINS_COUNT</span> <span class="o">*</span> <span class="n">SCR_LED_PINS_COUNT</span><span class="p">]</span> <span class="o">=</span><span class="p">{</span>
<span class="mo">00</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">13</span><span class="p">,</span> <span class="mi">16</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span>
<span class="mi">17</span><span class="p">,</span> <span class="mo">00</span><span class="p">,</span> <span class="mi">19</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">23</span><span class="p">,</span> <span class="mi">22</span><span class="p">,</span> <span class="mi">11</span><span class="p">,</span>
<span class="mi">18</span><span class="p">,</span> <span class="mi">24</span><span class="p">,</span> <span class="mo">00</span><span class="p">,</span> <span class="mi">26</span><span class="p">,</span> <span class="mi">28</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span>
<span class="mi">14</span><span class="p">,</span> <span class="mi">25</span><span class="p">,</span> <span class="mi">32</span><span class="p">,</span> <span class="mo">00</span><span class="p">,</span> <span class="mi">29</span><span class="p">,</span> <span class="mi">39</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span>
<span class="mi">15</span><span class="p">,</span> <span class="mi">21</span><span class="p">,</span> <span class="mi">33</span><span class="p">,</span> <span class="mi">27</span><span class="p">,</span> <span class="mo">00</span><span class="p">,</span> <span class="mi">37</span><span class="p">,</span> <span class="mi">41</span><span class="p">,</span>
<span class="mi">30</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">31</span><span class="p">,</span> <span class="mi">38</span><span class="p">,</span> <span class="mi">40</span><span class="p">,</span> <span class="mo">00</span><span class="p">,</span> <span class="mi">36</span><span class="p">,</span>
<span class="mi">9</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">34</span><span class="p">,</span> <span class="mi">35</span><span class="p">,</span> <span class="mo">00</span>
<span class="p">};</span>
<span class="kt">void</span> <span class="nf">scr_draw</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// Set everything as INPUT, high impedance</span>
<span class="k">for</span> <span class="p">(</span><span class="n">byte</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">SCR_LED_PINS_COUNT</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">pinMode</span><span class="p">(</span><span class="n">scr_led_pins</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">INPUT</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="n">byte</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">j</span> <span class="o"><</span> <span class="n">SCR_LED_PINS_COUNT</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">scr_current_scan_i</span> <span class="o">==</span> <span class="n">j</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Pull the current scanline HIGH</span>
<span class="n">pinMode</span><span class="p">(</span><span class="n">scr_led_pins</span><span class="p">[</span><span class="n">scr_current_scan_i</span><span class="p">],</span> <span class="n">OUTPUT</span><span class="p">);</span>
<span class="n">digitalWrite</span><span class="p">(</span><span class="n">scr_led_pins</span><span class="p">[</span><span class="n">scr_current_scan_i</span><span class="p">],</span> <span class="n">HIGH</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">scr_buf</span><span class="p">[</span> <span class="n">scr_segment</span><span class="p">[</span><span class="n">scr_current_scan_offset</span> <span class="o">+</span> <span class="n">j</span><span class="p">]</span> <span class="p">])</span> <span class="p">{</span>
<span class="c1">// Set the pins that are supposed to light up the segments</span>
<span class="n">pinMode</span><span class="p">(</span><span class="n">scr_led_pins</span><span class="p">[</span><span class="n">j</span><span class="p">],</span> <span class="n">OUTPUT</span><span class="p">);</span>
<span class="n">digitalWrite</span><span class="p">(</span><span class="n">scr_led_pins</span><span class="p">[</span><span class="n">j</span><span class="p">],</span> <span class="n">LOW</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">scr_current_scan_i</span> <span class="o">=</span> <span class="p">(</span><span class="n">scr_current_scan_i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">%</span> <span class="n">SCR_LED_PINS_COUNT</span><span class="p">;</span>
<span class="n">scr_current_scan_offset</span> <span class="o">=</span> <span class="n">scr_current_scan_i</span> <span class="o">*</span> <span class="mi">7</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>To draw a number, the fact that I assigned each digit in the LED matrix consecutive segment numbers turned out to be very helpful. That way, I could write a simple, common function to manipulate the segments to display a digit at all positions: Everything I needed to change was the start segment.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">const</span> <span class="kt">bool</span> <span class="n">scr_digit_segments</span><span class="p">[][</span><span class="n">SCR_DIGIT_LED_SEGMENTS</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
<span class="c1">//A B C D E F G</span>
<span class="p">{</span> <span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">0</span> <span class="p">},</span> <span class="c1">// 0</span>
<span class="p">{</span> <span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span> <span class="p">},</span> <span class="c1">// 1</span>
<span class="c1">// and so on</span>
<span class="p">};</span>
<span class="k">static</span> <span class="k">const</span> <span class="n">byte</span> <span class="n">scr_lcd_digit_start_segment</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">19</span><span class="p">,</span> <span class="mi">27</span><span class="p">,</span> <span class="mi">35</span> <span class="p">};</span>
<span class="kt">void</span> <span class="nf">scr_draw_numer</span><span class="p">(</span><span class="n">byte</span> <span class="n">number</span><span class="p">,</span> <span class="n">byte</span> <span class="n">digit</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">number</span> <span class="o">></span> <span class="mi">10</span> <span class="o">||</span> <span class="n">digit</span> <span class="o">></span> <span class="mi">3</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">byte</span> <span class="n">start_segment</span> <span class="o">=</span> <span class="n">scr_lcd_digit_start_segment</span><span class="p">[</span><span class="n">digit</span><span class="p">];</span>
<span class="k">for</span> <span class="p">(</span><span class="n">byte</span> <span class="n">seg</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">seg</span> <span class="o"><</span> <span class="n">SCR_DIGIT_LED_SEGMENTS</span><span class="p">;</span> <span class="n">seg</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">scr_buf</span><span class="p">[</span><span class="n">seg</span> <span class="o">+</span> <span class="n">start_segment</span><span class="p">]</span> <span class="o">=</span> <span class="n">scr_digit_segments</span><span class="p">[</span><span class="n">number</span><span class="p">][</span><span class="n">seg</span><span class="p">];</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>It worked:</p>
<p><img src="/assets/posts-images/fake-radio-hack/lcd-worked.jpg" alt="LCD screen worked" /></p>
<p>In slow motion, it looks quite trippy:</p>
<p><img src="https://media.giphy.com/media/W4dO7Ccfk0CtYBJdf7/giphy.gif" alt="LCD screen scanline slo-mo" /></p>
<h2 id="putting-11-together-was-not-that-easy">Putting 1+1 together was not that easy</h2>
<p>I wanted the Arduino Pro Micro to handle both the LED screen drawing and the multimedia buttons, because I didn't want to waste an extra Arduino board (Moreover, if I had used two Arduino boards I would have also needed a USB hub since the Pi 0 only has one USB port, and I don't think I have space inside the radio compartment for it). My loop function looks like so:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
<span class="n">kbd_loop</span><span class="p">();</span>
<span class="n">scr_draw</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Each procedure has to run pretty fast in the loop. How fast? For the screen to not flicker, I need it to refresh at 30Hz minimum. That means each “scanline” needs to take less than 4ms, as 1000(ms) / 30 (refreshes) / 7 (scanlines/refresh) = 4 ms/scanline.</p>
<p>But life ain't that easy! I could clearly see the flickerings in my LCD when I added <code class="language-plaintext highlighter-rouge">kbd_read</code> to the loop.</p>
<h3 id="optimizing-the-button-handling-procedure">Optimizing the button handling procedure</h3>
<p>I found out that the main reason that the loop is slow was because using the <code class="language-plaintext highlighter-rouge">analogRead</code> function to read the ADC hangs until the ADC until it finishes sampling the read. After googling around, I found a trick to set up the ADC so it continuously sample port A0 and I could read the ADC register whenever I want:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">kbd_setup</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// Blah...</span>
<span class="n">digitalWrite</span><span class="p">(</span><span class="n">A0</span><span class="p">,</span> <span class="n">HIGH</span><span class="p">);</span>
<span class="n">ADCSRA</span> <span class="o">=</span> <span class="n">bit</span> <span class="p">(</span><span class="n">ADEN</span><span class="p">);</span>
<span class="n">ADCSRA</span> <span class="o">|=</span> <span class="n">bit</span> <span class="p">(</span><span class="n">ADPS2</span><span class="p">);</span>
<span class="n">ADMUX</span> <span class="o">=</span> <span class="n">bit</span> <span class="p">(</span><span class="n">REFS0</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">analogPinToChannel</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="o">&</span> <span class="mh">0x07</span><span class="p">);</span>
<span class="n">bitSet</span> <span class="p">(</span><span class="n">ADCSRA</span><span class="p">,</span> <span class="n">ADSC</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">kbd_loop</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// This returns right away</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">pushBtnRead</span> <span class="o">=</span> <span class="n">ADC</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="optimizing-the-screen-drawing-function">Optimizing the screen drawing function</h3>
<p>Although not as important, the <code class="language-plaintext highlighter-rouge">scr_draw</code> function runs in a loop and it's quite complex; it better be fast. I tried to give myself the exercise of accelerating the screen draw function. The <code class="language-plaintext highlighter-rouge">pinMode</code> and <code class="language-plaintext highlighter-rouge">digitalWrite</code> functions are not very performant. It turns out there's a better way to do it: you can write to a set of PORT and DDR registers to set the values of a set of rows at once. Because of the pin layouts of the Pro Micro, I only have as many as 6 of them on the same PORT, and one has to be another. I settled with 6 of them on PORTB and 1 of them on PORTE (pin 7), so the fast draw function looks like so:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">const</span> <span class="n">byte</span> <span class="n">SCR_PORTE_POS</span> <span class="o">=</span> <span class="mi">6</span><span class="p">;</span>
<span class="k">static</span> <span class="k">const</span> <span class="n">byte</span> <span class="n">SCR_PORTE_POS_MASK</span> <span class="o">=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="mi">6</span><span class="p">;</span>
<span class="kt">void</span> <span class="nf">scr_draw_fast</span><span class="p">()</span> <span class="p">{</span>
<span class="n">byte</span> <span class="n">pinIo</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// Everything is INPUT</span>
<span class="n">byte</span> <span class="n">pinVal</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// Everything is LOW</span>
<span class="n">byte</span> <span class="n">currBit</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">byte</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">j</span> <span class="o"><</span> <span class="n">SCR_LED_PINS_COUNT</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">scr_current_scan_i</span> <span class="o">==</span> <span class="n">j</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Turn the current scanline on</span>
<span class="c1">// Set it as OUTPUT HIGH</span>
<span class="n">pinIo</span> <span class="o">|=</span> <span class="n">currBit</span><span class="p">;</span>
<span class="n">pinVal</span> <span class="o">|=</span> <span class="n">currBit</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">scr_buf</span><span class="p">[</span> <span class="n">scr_segment</span><span class="p">[</span><span class="n">scr_current_scan_offset</span> <span class="o">+</span> <span class="n">j</span><span class="p">]</span> <span class="p">])</span> <span class="p">{</span>
<span class="c1">// Force Turn the segment on</span>
<span class="c1">// Set it as OUTPUT LOW</span>
<span class="n">pinIo</span> <span class="o">|=</span> <span class="n">currBit</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">currBit</span> <span class="o">=</span> <span class="n">currBit</span> <span class="o"><<</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Move the first LED pin to port PE6</span>
<span class="c1">// Rest is PortB</span>
<span class="n">DDRB</span> <span class="o">=</span> <span class="p">(</span><span class="n">DDRB</span> <span class="o">&</span> <span class="mi">1</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">pinIo</span> <span class="o">&</span> <span class="n">B11111110</span><span class="p">);</span>
<span class="n">PORTB</span> <span class="o">=</span> <span class="p">(</span><span class="n">PORTB</span> <span class="o">&</span> <span class="mi">1</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">pinVal</span> <span class="o">&</span> <span class="n">B11111110</span><span class="p">);</span>
<span class="n">DDRE</span> <span class="o">=</span> <span class="p">(</span><span class="n">DDRE</span> <span class="o">&</span> <span class="o">~</span><span class="n">SCR_PORTE_POS_MASK</span><span class="p">)</span> <span class="o">|</span> <span class="p">((</span><span class="n">pinIo</span> <span class="o">&</span> <span class="mi">1</span><span class="p">)</span> <span class="o"><<</span> <span class="n">SCR_PORTE_POS</span><span class="p">);</span>
<span class="n">PORTE</span> <span class="o">=</span> <span class="p">(</span><span class="n">PORTE</span> <span class="o">&</span> <span class="o">~</span><span class="n">SCR_PORTE_POS_MASK</span><span class="p">)</span> <span class="o">|</span> <span class="p">((</span><span class="n">pinVal</span> <span class="o">&</span> <span class="mi">1</span><span class="p">)</span> <span class="o"><<</span> <span class="n">SCR_PORTE_POS</span><span class="p">);</span>
<span class="n">scr_current_scan_i</span> <span class="o">=</span> <span class="p">(</span><span class="n">scr_current_scan_i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">%</span> <span class="n">SCR_LED_PINS_COUNT</span><span class="p">;</span>
<span class="n">scr_current_scan_offset</span> <span class="o">=</span> <span class="n">scr_current_scan_i</span> <span class="o">*</span> <span class="mi">7</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now, everything worked and my screen (finally) no longer flickered!</p>
<h3 id="controlling-the-lcd-screen-from-the-raspberry-pi">Controlling the LCD screen from the Raspberry Pi</h3>
<p>Next, I needed some way of communicating with the LCD screen from the Pi 0 to give it data to display. To make things easy, I set up a 115200 TTL serial connection and parsed the commands. For simplicity, the commands all have a fixed format and length <code class="language-plaintext highlighter-rouge">CXNNNN</code>. C is character C to signify the start of command, then X to signify command, then NNNN to signify the parameters. For example, to set the time to 14:00, I would have to send <code class="language-plaintext highlighter-rouge">CT1400</code>. It was quite trivial so I have nothing to write about it, other than this requires me to code up and track how many characters I have received and decide when to process the whole command. This task can be achieved through a quick-and-dirty <a href="https://en.wikipedia.org/wiki/Finite-state_machine">state machine</a> – a very important concept if you need to have any hope in tracking what state the system is in.</p>
<p>The only thing I have to admit was that I didn't understand how the switch statement works in C. Apparently, this is illegal:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">const</span> <span class="n">byte</span> <span class="n">SER_CMD_FIXED_LENGTH</span> <span class="o">=</span> <span class="mi">6</span><span class="p">;</span>
<span class="n">byte</span> <span class="n">ser_cmd</span><span class="p">[</span><span class="n">SER_CMD_FIXED_LENGTH</span><span class="p">];</span>
<span class="kt">void</span> <span class="nf">ser_process_command</span><span class="p">()</span> <span class="p">{</span>
<span class="n">byte</span> <span class="n">cmd</span> <span class="o">=</span> <span class="n">ser_cmd</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="k">switch</span> <span class="p">(</span><span class="n">cmd</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="sc">'A'</span><span class="p">:</span>
<span class="n">byte</span> <span class="n">my_var</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="sc">'T'</span><span class="p">:</span>
<span class="n">byte</span> <span class="n">my_var</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// blah</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Can you tell why?</p>
<p>It was because I redeclared <code class="language-plaintext highlighter-rouge">my_var</code>. Apparently everything in the switch statement in C shares the same scope. We learn new things everyday!</p>
<h2 id="optimizing-power-consumption">Optimizing power consumption</h2>
<p>I plugged in the contraption through a USB power sniffer-thingie and saw that the Arduino Pro Micro and the fake VFD LED screen consumped 0.02 A. Could I possibly squeeze more power from it? I tried everything I could with hardware timers/interrupts and nothing really drove down the power consumption.</p>
<p>In a desperate attempt to optimize the power consumption, I found out that there was a project called <a href="https://github.com/rocketscream/Low-Power">Low-Power</a>. It uses the hardware watchdog timer to keep the microcontroller in a special low power state and that shaves around 0.01A from total power consumption. However, the wakeup interval is very weird, the fastest I could do was 15ms. That's four times slower than my slowest acceptable refresh rate (4ms/scanline). And rightfully so, it was really slow and made the screen flicker. But for some obscure reason that I don't understand, the timer fires every 15ms when there are no USB data lines connected. However, as soon as you have USB data lines connected, the timer fires at 1ms. I don't know why so, but it worked to my advantage, so I couldn't complain. My loop function looks like so:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// SLEEP_15MS sleeps exactly for 1MS when you have USB data lines connected???</span>
<span class="n">LowPower</span><span class="p">.</span><span class="n">idle</span><span class="p">(</span><span class="n">SLEEP_15MS</span><span class="p">,</span> <span class="n">ADC_ON</span><span class="p">,</span> <span class="n">TIMER4_OFF</span><span class="p">,</span> <span class="n">TIMER3_OFF</span><span class="p">,</span> <span class="n">TIMER1_OFF</span><span class="p">,</span>
<span class="n">TIMER0_OFF</span><span class="p">,</span> <span class="n">SPI_OFF</span><span class="p">,</span> <span class="n">USART1_ON</span><span class="p">,</span> <span class="n">TWI_OFF</span><span class="p">,</span> <span class="n">USB_ON</span><span class="p">);</span>
<span class="n">kbd_events_process</span><span class="p">();</span>
<span class="n">scr_events_process</span><span class="p">();</span>
<span class="n">ser_events_process</span><span class="p">();</span>
<span class="n">_millis</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Note that the <code class="language-plaintext highlighter-rouge">millis()</code> function no longer returns the correct number of milliseconds passed. So, I had to manually count the number of milliseconds in a variable and make sure that each of the procedures I called doesn't consume more than 1 ms. I had to eliminate all calls of <code class="language-plaintext highlighter-rouge">delay()</code> in the code, because that would not only make the screen flicker, but also surely mess up the counter. That was also tedious and inconvenient but quite trivial.</p>
<p>And with that, I was able to drive down the power consumption of the Arduino to 0.01A. Fantastic!</p>
<h2 id="setting-up-the-pi">Setting up the Pi</h2>
<p>I need the Pi as my fake radio to do three things for now:</p>
<ol>
<li>Play my songs through Airplay from my iPhone: <a href="https://github.com/mikebrady/shairport-sync">Shairport-sync</a> is an excellent project.</li>
<li>Handle multimedia buttons: <a href="https://blog.0x79.com/raspberry-pi-volume-keys-with-triggerhappy.html">Triggerhappy</a> does the job.</li>
<li>Tell the Arduino the time, as the Arduino doesn't know itself.</li>
</ol>
<p>Items 1 and 2 are pretty self-explanatory. For part 3, I was getting really lazy and sloppy. So while I could do things more efficiently and less error-prone, I just assume that the device comes up on the Pi as <code class="language-plaintext highlighter-rouge">/dev/ttyACM0</code> and this simple cron script syncs the time from the Pi to the Arduino every hour and at startup. I have found the Arduino to be surprisingly accurate, it doesn't drift more than a minute after 24 hours, so it doesn't need the Pi to tell it the time that often.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#CTXXX sets the time</span>
<span class="c">#CC1000 displays the current time on the fake LED screen</span>
<span class="nv">TIME_NOW</span><span class="o">=</span><span class="si">$(</span><span class="nb">date</span> +<span class="s1">'%H%M'</span><span class="si">)</span><span class="p">;</span> <span class="nb">echo </span>CT<span class="s2">"</span><span class="k">${</span><span class="nv">TIME_NOW</span><span class="k">}</span><span class="s2">"</span>CC1000 <span class="o">></span>/dev/ttyACM0
</code></pre></div></div>
<p>Then the rest is just to put the Pi into the enclosure and enjoy my smart fake-vintage radio!</p>
<h2 id="conclusion">Conclusion</h2>
<p><img src="https://media0.giphy.com/media/dCcLQ5TLFANMj0p3Uh/giphy.gif" alt="Press F to pay respects" /></p>
<p><em>Please press F to pay respects to the fallen fake radio.</em></p>
<p>That was way too much work, but in the end, it is a labor of love and I am really happy with the result!</p>
<p>My source code for the Arduino portion can be found on <a href="https://github.com/htruong/vivitar-radio-hacks/tree/main">my vivitar-radio-hacks Github repository</a>. I hope it was fun for you to read it as it was fun for me to do it.</p>
<p>I thank Dr. Bindner who helped proofread this post. By the way, I also know that Dr. Bindner is writing an Arduino book that is supposed to be complete soon!</p>