Tatu Projects Journal
Making and finishing S-1335 clock radio PWA

Last update: 2024-09-29

In July I started a clock radio project to explore mobile phone / PWA development and to find a resting place for my old phone. These motivations were examined in my kick-off journal post from July. I've now released the app in PWA form which can be found at https://clock.tazca.com. Android/Linux builds are available on GitHub.

This is a retrospective examination of the process and the end products. An examination of the present source code is available at https://tazca.github.io/s1335.

End product

Flutter has been a pretty convenient platform to build the software side on. It has straightforward if verbose widget trees. Building on Linux/iOS/Android/web has been a generally good experience, but of course bugs and mistakes don't manifest equally on all platforms. E.g. using shared_preferences before initializing Flutter only lead to crashing on native platforms, but worked fine as PWA. In similar vein, assets seem to be handled differently on web and native platforms, requiring shims to address assets being on domain.tld/assets/assets/.. rather than domain.tld/assets/... Mobile Safari also required using Image.network / NetworkImage rather than Image.asset / AssetImage.

options.webp

The two clock faces are both explicitly 24-hour, I didn't think AM/PM variation on 7-segment one would be worth the effort. That's about it what they have in common.

map.webp

The solar clock face is a timezone-less face putting longitude dependent solar noon at 12 o'clock position, showing earth with sun going around it. The perspective has either north or south pole in the very middle. Then it calculates the day-night line based on sun's position and day of the year (sun declination), and shows user location based on latitude.

wireframe.webp

Anticipating use on OLEDs at some point, I put in an OLED jiggle to avoid some pixels staying on 24/7. The 7-segment face goes around in a circular motion, completing one rotation every 30 minutes. The solar face has to remove the gray dayside fill, but otherwise does the same circular movement.

radio.webp

The radio deck is still quite simple, the user can only add new sources via direct URL. I left the adding method as a dropdown in case I figured out more engaging ways to add new ones, but alas, none so far.

Transcluded documentation

One side-project I've worked on using this project is developing a transcluded documentation workflow. I had written a literate program using node architecture (noweb) as part of my MSc thesis, but transclusive architecture would allow using an external IDE, which is quite essential in bigger programs. The literate document for S-1335 can be found at https://tazca.github.io/s1335, but producing this wasn't quite as fluid as with noweb.

My tools were Emacs + org-transclusion and then VSCodium as the actual IDE. There were two larger issues, both due to immaturity of org-transclusion. Sometimes the transcluded buffers would 'blow through' and be saved as part of the document, overwriting the #+transclusion nodes and ruining the document. This is essentially a blocking issue for any future pursuit. Another issue is that org-transclusion does not (yet) do regex matching, which makes delimiting transcluded code blocks often more difficult and fragile than needed. Although regex is not a panacea, and arbitrary delimiting like with noweb will remain out of reach. A special coding style could be used, but in this case Dart enforces an ecosystem-wide style making this less a solution. For fluid but far from arbitrary delimiting, org-transclusion has defun parameter for matching Lisp blocks and additional rules could be written to match e.g. braces in Dart.

Even with aforementioned issues fixed, the experience is still too insulated between the two environments. A method to launch a VSCodium (or other IDE) instance to matching rows is essential to allow working from Emacs as the root editor, but still easily access IDE tooling. This is a non-issue luckily when Emacs itself has good enough IDE tooling for a language.

Journey

The proof of concept / exploratory programming phase lasted about two weeks in July. At this point the app functioned well for my personal needs. I then wanted to work on making it more universally usable. I refactored it into a less cludgey platform, moving Clock logic into its own controller, cleaning up root MaterialApp. Looking back, at this point I could've started integrating a test suite, but it flew under my radar, as I've done no GUI/widget testing before. I was more interested in starting to do an actually usable GUI with PageView and putting up settings for different things. Some vacation trips, working on my spin-off, and 6 weeks later, I was ready with my 1.0.0.

Spin-off hype

The solar clock face kinda stole the show in my perspective. I've rarely used the 7-segment face even though it was what I originally intended to use. During development I realized that if I could turn rotation into linear motion (modeling trigonometric functions) to move the day-night line back and forth, I could mechanize the algorithm used for the face.

This project does lean on a powerful enough clock mechanism that still stays accurate even under strain. I'm counting on hour hands having roughly 1000 times more torque than second hands. If this is true, it should have enough torque, as the clock has very symmetric weight distribution which means only overcoming friction. I've been using PETG as the material, which as bendy plastic allows kinetic energy to be buffered to fight stiction.

Two gearings are required. One set to halve the RPM and transmit the force of hour hand to outer rim for 24-hour rotations, and a second set that does a 24 * (366/365) -hour rotation. This means doing an extra rotation every year and model the circular motion needed to derive day-night line. This circular motion is then transformed into harmonic linear motion using scotch yoke, which then moves the day-night line. 366 and 365 have a nice low factors of 61 and 73 meaning 61 and 73 teeth gears. Doing 365:366.25 would be more accurate, but would mean a 293-teeth gear. Similarly we could do 365:364, since the direction of circular motion does not matter, but 364 has 91 as its largest factor which is much worse than 61.

clock.webp

Some early prototypes. Stay tuned! I hope there's enough torque in these off-the-shelf clock mechanisms.