Beyond December

    Having set out to work on a range of mostly Psion-related ideas and tasks during my 2025 December Adventure, I’m incredibly pleased with what I accomplished: I moved a collection of things forwards, started some new projects, and had quite a bit of fun doing it.

    Some of my highlights:

    • began my Organiser journey (2, 4, 19)
    • nudged Psion emulation forwards (15, 16, 30)
    • maintained various retro devices (26, 27)
    • continued to slowly back away from Big Tech (17, 18, 22, 23)
    • designed and printed things for our home (9, 13, 24)
    • helped keep OPL alive (5, 6, 7, 8, 9, 11, 12)
    • made it just that little bit little easier to daily-drive Psions in the 2026 (2, 28, 29, 31)

    I’ve really enjoyed this December Adventure. It’s encouraged me to be more deliberate about the ideas I explore each day, and I’ve found the practice of daily write-ups to be incredibly positive.

    The regular cadence has forced me to metaphorically put down what I’m working on, pause, and reflect on what I’ve done and where it’s going. This has helped me significantly in planning and prioritizing when working on increasingly interdependent projects. It’s also been incredibly rewarding to share more frequently what I create and receive realtime feedback, while also gaining the sense of external accountability (real or imagined) that comes from setting my approach down in a public forum.

    With that in mind, I’m keen to continue a more regular practice of writing going into the new year. I’m not sure quite what that needs to look like yet, so I imagine you’ll see a few experiments over the coming weeks and months—I don’t want to overwhelm folks (or myself) with daily updates, but I think whatever I do needs to be regular to ensure that I don’t end up with a backlog.

    To start with, I’d like to focus on specific projects to help move them forwards a little more intentionally, so I’m planning to have a theme for each week. I’ll write brief daily journal or devlog (that I may or may not publish), and edit it into weeknotes.

    Next Steps

    I’d love to keep working on many of my December Adventure themes in the coming year. For the established projects, I plan to track the new tasks that have emerged during the month as issues in their respective repositories. This will allow others to engage with them in a public forum, and ensure I don’t feel so overwhelmed (or forget anything):

    • OpoLua
      • Automated OpoLua Qt builds (#614)
      • OpoLua Qt macOS builds don’t correctly embed Qt (#615)
      • Move common resources into core directory (#617)
      • Add documentation to the website (#618)
      • Define and support EPOC16 SIS files (#619)
    • PsiEmu
      • Automatically download ROMs (#1)
      • MAME creates cfg and nvram directories in the current working directory (#2)
      • Support serial port configuration (#3)
      • Package for various platforms (#4)
      • Support browsing and mounting SSDs (#5)
    • Reconnect
      • Support incremental backups for EPOC16 and EPOC32 devices (#126)
      • Support converting EPOC16 PIC files (#350)
      • Per file-type conversion options (#352)
      • Support converting EPOC16 Word files to Markdown (#354)
      • Creating folders fails on EPOC16 devices (#351)
      • Adopt Glitter update library (#353)
      • Add Word2Text license to the website (#355)
      • Don’t install stub SIS files on EPOC16 devices (#356)
      • Use the Word2Text package (#357)
      • Automatically expand the ‘My Psion’ sidebar item when a device connects (#358)
      • Reconnect sometimes hangs when quitting the main browser app (#359)
      • Add a wallpaper manager (#360)
    • plptools
      • Support server mode (#63)
    • Folders
      • Show filenames (#231)
      • Crash when changing sidebar selection or sort order during initial load (#232)
      • Improve the sort drop-down menu (#233)
    • Thoughts
      • Support image attachments (#185)
    • Psion Community Website
      • Show a sidebar with the site structure (#1)

    If you’re excited to help work on some of these—or any other—projects, please don’t hesitate to get in touch; I’m always looking for collaborators.

    There are also a few smaller projects I’d still love to write up, or otherwise publish:

    • RMRSoft preservation
    • MiSTer x PVM
    • My minimalist read later strategy
    • Nezumi
    • PsiBoard
    • OPL support for Highlight.js
    • Little Luggable assembly

    This just leaves the ideas and explorations that will have to wait for next year’s adventure (or a lazy Sunday):

    • Keyboard support for the LZ64
    • Psion webring
    • Software Index additions (UID listing, CLI for plptools/Linux)
    • Revisit and ship Thoughts for EPOC32
    • Design a minimal Psion USB-C cable
    • Archive Palmtop magazine scans
    • Web-based Psion emulation
    • Thoughts for iOS
    • Print feet for Anytime x Nixie
    • Try out GlobalTalk
    • Time zone logger
    • Write about model-viewer
    • Write about my 3D printed brackets
    • Series 7 emulation
    • Try out Plan9

    Finally, Thank You

    I’d like to take the time to thank everyone who followed along this month, and to thank the couple of folks who donated this month (you know who you are) 🙇‍♂️. Waking up to your emails and messages really gave me a huge boost. If you have the time I strongly encourage you to give a little love to the creators and open source developers who make a difference to your life—the emotional support and encouragement goes immeasurably far.

    December Adventure Day 31

    Wrapping Up

    Day 31 brought my December Adventure to a close for another year. I spent much of the day trying to work out how to write up day 30’s plptools-focused AI experiment (about which I remain deeply conflicted), and reflecting on the past month of adventuring. I also managed to squeeze in a last couple of tasks: enabling Doxygen builds for plptools, and designing and printing a holder for 2 AA batteries (a must when daily-driving my two favorite writing devices—a Psion, or Pomera DM30).

    Since I’ve a fair few thoughts about where to take everything I’ve worked on in my adventures, I’ll cover these in a follow-up post.

    Doxygen

    In plptools, the current group of maintainers have inherited a C++ codebase that’s very much of its era, making it hard to follow at times. With a view to helping us (and newcomers) understand what’s going on, I’d like to encourage a gradual process of adding documentation (especially class-level documentation) to the project. To help with this, I added a simple Doxygen configuration to the project and set up documentation builds in our CI.

    You can view the (sparse) documentation here.

    Battery Holder

    There’s a myriad designs for AA battery holders out there, but I couldn’t find any simple screw-cap tubes—something that would fit in a pencil case—so I designed my own:

    I’d like to update the design to add a rubber o-ring to help the screw lid to stay closed and give it a water-tight seal but, so far, I’m pretty pleased with the design. I plan to publish the STL files in the coming days.

    December Adventure Day 30

    Serial Ports and Templates

    Reconnect was on my mind as I started day 30 of my December Adventure, and I found my thoughts drifting to its relationship with plptools, which provides the Psion Link Protocol implementation and many other conveniences. Allowing myself the distraction—longer-term architectural noodlings are always useful—I ended up spending much of the day focused on plptools, exploring new features and considering its future direction. I also tool a little time to fix a longstanding bug in my website that I spotted while writing-up this adventure.

    plptools

    Speaking with Alex and Fabrice—the other maintainers of plptools—we settled on a couple of small improvements that would significantly improve the usability and testability of plptools. Both are aimed at improving connectivity and compatibility:

    • Support serial devices without DTR/DSR signalling

      Some cheaper RS232 adapters and operating systems (looking at you, Haiku) don’t support these hardware signals meaning they don’t work with plptools. By offering a compatibility mode that ignores these signals, we should be able to significantly increase support at the cost of some connection robustness. A worthwhile trade-off.

    • TCP Serial Port Emulation

      We’d love to be able to connect plptools to Psion emulators. This would serve, not only as a convenience for transferring files to and from the emulators themselves, but as a way to quickly test plptools against a broad range of deices. (Shocking as it may sound, we don’t always have every Psion device ever made to hand.) MAME provides an emulated serial port that streams unframed serial data over a TCP connection, and we would like to explicitly support this in plptools.

    Keen to dig into something with some near-term benefits, I decided to take a shot at getting plptools to work with MAME.

    Configuring MAME

    First-up, I spent a little time figuring out how to enable the serial port in MAME:

    mame \
        psion3a2 \
        -sibo serial \
        -sibo:serial:rs232 null_modem \
        -bitbanger socket.127.0.0.1:1234 \
    

    Constructing this command took quite a lot longer than I’d have liked so, for future travellers, here’s my understanding of how it breaks down:

    • -sibo serial—enable the serial feature
    • -sibo:serial:rs232 null_modem—configure the serial feature to use a null modem
    • -bitbanger socket.127.0.0.1:1234—use the bitbanger interface connecting to 127.0.0.1, port 1234

    In the process of working all this out, I discovered a couple of commands which might be helpful to others:

    • Use -listslots to show image-specific options:

      mame psion3a2 -listslots
      

      This will output something like:

      SYSTEM           SLOT NAME        SLOT OPTIONS     SLOT DEVICE NAME
      ---------------- ---------------- ---------------- ----------------------------
      psion3a2         sibo             fax              Psion 3-Fax Modem
                                      parallel         Psion 3-Link Parallel Printer Interface
                                      serial           Psion 3-Link RS232 Serial Interface
      
    • Used in combination with a specific slot option, '-listslots' will show you the additional slots and options for that slot1:

      mame psion3a2 -sibo serial -listslots
      

      This shows the serial port specific configuration options:

      SYSTEM           SLOT NAME        SLOT OPTIONS     SLOT DEVICE NAME
      ---------------- ---------------- ---------------- ----------------------------
      psion3a2         sibo             fax              Psion 3-Fax Modem
                                      parallel         Psion 3-Link Parallel Printer Interface
                                      serial           Psion 3-Link RS232 Serial Interface
      
                     sibo:serial:rs232 dec_loopback     RS-232 Loopback (DEC 12-15336-00)
                                      h19              Heath H19 Terminal (Serial Port)
                                      ie15             IE15 Terminal
                                      keyboard         Serial Keyboard
                                      loopback         RS-232 Loopback
                                      mockingboard     Sweet Micro Systems Mockingboard D
                                      msystems_mouse   Mouse Systems Non-rotatable Mouse (HLE)
                                      nss_tvi          Novag Super System TV Interface
                                      null_modem       RS-232 Null Modem
                                      patch            RS-232 Patch Box
                                      printer          Serial Printer
                                      pty              Pseudo Terminal
                                      rs232_sync_io    RS-232 Synchronous I/O
                                      rs_printer       Radio Shack Serial Printer
                                      scorpion         Micro-Robotics Scorpion Intelligent Controller
                                      sunkbd           Sun Keyboard Adaptor
                                      swtpc8212        SWTPC8212 Terminal
                                      terminal         Serial Terminal
                                      votraxtnt        Votrax Type 'N Talk (Serial Port)
      

    Armed with an appropriate command, I was quickly able to use netcat (nc -l -p 1234) to echo text sent from the Psion’s Comms program:

    Supporting TCP (or Now I Have an AI Problem)

    The serial port implementation in plptools is isolated to packet.cc where it’s treated as a classic POSIX file descriptor. This makes it easy to drop in a replacement TCP socket file descriptor (at least as a proof-of-concept). Since most C socket handling is boilerplate, and I knew exactly what I wanted, I thought it might prove a good test for AI—I find almost every aspect of generative AI deeply concerning, but I do my best to try it out every now and again so I can speak from at least some level of experience and knowledge. With very little prompting, Claude (Sonnet 4.5) was able to produce the following:

    // This blocks rather than going back into a listening state.
    int
    init_tcp(const char *port_str, int debug)
    {
        int listen_fd, conn_fd;
        struct sockaddr_in addr;
        int port = atoi(port_str);
        int optval = 1;
    
        if (debug)
            printf("creating TCP listener on port %d...\n", port);
    
        listen_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (listen_fd < 0) {
            perror("socket");
            exit(1);
        }
    
        setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
    
        memset(&addr, 0, sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = INADDR_ANY;
        addr.sin_port = htons(port);
    
        if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
            perror("bind");
            exit(1);
        }
    
        if (listen(listen_fd, 1) < 0) {
            perror("listen");
            exit(1);
        }
    
        if (debug)
            printf("waiting for connection...\n");
    
        conn_fd = accept(listen_fd, NULL, NULL);
        if (conn_fd < 0) {
            perror("accept");
            exit(1);
        }
    
        close(listen_fd);
    
        if (debug)
            printf("connection accepted, fd=%d\n", conn_fd);
    
        return conn_fd;
    }
    

    This function accepts an incoming TCP connection and returns a file descriptor, serving as a drop-in replacement for init_serial as implemented by mp_serial.c. I updated the code to call init_tcp with the appropriate arguments and, without any further modification, I was able to start up the plptools daemon (ncpd) and connect to a MAME Psion emulator. Success! 🥳

    (I’d include a screenshot here, but my attempts to reproduce the experiment have been unsuccessful.)

    In spite of this early success, I now feel like I have an AI problem: this code is clearly not production quality (it’s blocking; errors aren’t propagated; it makes no attempt to support multiple sequential or concurrent connections) and, while some might be willing to commit it to a project, the legality and copyright is deeply ambiguous. Although I’m not too worried that what I ultimately produce will be derivative (almost all generated code is boilerplate), I regret using AI here: there’s a lot of work required to turn it into production code and I doubt it saved me more than half an hour of development time, while incurring significant environmental, ethical, and legal cost.

    I plan to revisit this over the coming weeks and (manually) design and implement an architecture which better fits plptools.

    Date Formatting

    If you view the December Adventure or Archive pages on this website, you’ll see that the list of posts is broken into sections, one for each year. This website is built using InConext (my own take on a static site generator) and uses Tilt, a Lua-based templating language designed by Tom, my partner in crime on many a project.

    The template for generating these archive pages looks like this:

    {% include "common.lua" %}
    {% function content() %}
        <div class="post">
            {% include "post_header.html" %}
            <article class="post-content">
    
                {{ document.render() }}
    
                {%
                    -- Get the posts.
                    posts = document.query("posts")
                    this_year = ""
                    previous_year = ""
                %}
    
                {% for index, post in ipairs(posts) do %}
    
                    {%
                        -- Get the year of the current post.
                        if post.date then
                            this_year = post.date.format("yyyy")
                        else
                            this_year = "Undated"
                        end
                    %}
    
                    {%
                        -- Start a new section if the year is different from the previous one.
                    %}
                    {% if index == 1 then %}
                        {% if this_year then %}
                            {% if not query then %}<h1>{{ this_year }}</h1>{% end %}
                        {% end %}
                        <ul class="pagelist short">
                    {% else %}
                        {% if this_year ~= previous_year then %}
                            </ul>
                            {% if this_year then %}
                                <h1>{{ this_year }}</h1>
                            {% end %}
                            <ul class="pagelist short">
                        {% end %}
                    {% end %}
    
                    <li><span class="date">{% if post.date then %}{{ post.date.format(site.metadata.day_month_format_short) }}{% end %}</span> <a href="{{ post.url }}">{{ post.title }}{% if post.subtitle then %}: {{ post.subtitle }}{% end %}</a></li>
    
                    {% if last(posts, index) then %}
                        </ul>
                    {% end %}
    
                    {% previous_year = this_year %}
                {% end %}
    
            </article>
    
        </div>
    {% end %}
    {% include "default.html" %}
    

    The approach to determining the section fairly simple: the template iterates over the ordered posts (for index, post in ipairs(posts) do), generates the text representation of the year component of each post’s publish date (this_year = post.date.format("yyyy")) and, if it differs from that of the previous post (if this_year ~= previous_year then), prints a new header, and starts a new ul. While it feels a little inelegant (functional, it is not), this has always proven reliable. Until yesterday:

    The astute reader might have already noticed the problem in the template, but it took me a little while to spot: it turns out there’s quite a difference between Y and y date format specifiers (used when getting the years from posts). They are defined as follows:

    • y —Year. Normally the length specifies the padding, but for two letters it also specifies the maximum length.
    • Y—Year (in “Week of Year” based calendars). Normally the length specifies the padding, but for two letters it also specifies the maximum length. This year designation is used in ISO year-week calendar as defined by ISO 8601, but can be used in non-Gregorian based calendar systems where week date processing is desired. May not always be the same value as calendar year.

    I was incorrectly using the ‘week of year’ year. 🤦

    Week numbers are a fascinating unit of time as, depending on how the weeks align with a given year, the last days of a year might appear in week 1 of the following year, or the first days of the new year, in week 52 of the previous one. Thankfully, the fix was simple: change YYYY to yyyy.


    1. Turtles all the way down.