- 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)
- OpoLua
- PsiEmu
- 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
- Thoughts
- Support image attachments (#185)
- Psion Community Website
- Show a sidebar with the site structure (#1)
- RMRSoft preservation
- MiSTer x PVM
- My minimalist read later strategy
- Nezumi
- PsiBoard
- OPL support for Highlight.js
- Little Luggable assembly
- 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
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.
-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 to127.0.0.1, port1234Use
-listslotsto show image-specific options:mame psion3a2 -listslotsThis 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 InterfaceUsed in combination with a specific slot option,
'-listslots'will show you the additional slots and options for that slot1:mame psion3a2 -sibo serial -listslotsThis 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)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.-
Turtles all the way down. ↩
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:
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):
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:
This just leaves the ideas and explorations that will have to wait for next year’s adventure (or a lazy Sunday):
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:
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:
In the process of working all this out, I discovered a couple of commands which might be helpful to others:
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:
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.