benc's tildagon notes

Notes and adventures with my tildagon. Don't expect this stuff to make too much sense.


December 2025

spraying away battery acid with contact cleaner and visiting the toxic waste dump

the battery leaked a bit while my tildagon was waiting otherwise patiently for my attention

many people in their christmas holidays queuing up. more berlin dogshit.

after spray, everything except the down button works. very occasionally the down button does work, but mostly it either makes no input, or starts input clicks repeatedly which stops on reset.

ordered replacement ribbon cable (x5) from china. using link from tildagon website. a few days shipping - perhaps longer getting from Liege to my door than from China to Liege, much postage. it arrived without any customs trouble, which i was unsure about given recent changes in de-minimis exemptions.

while its disappointing that the down button doesn't work right now, this isn't overall a disappointment: it forced me to pay attention to whats going on with the hardware much more than if I was just tinkering, and that was interesting.

flashing a red LED using breakout board

Protoboard Hexpansion by jakew

even with nothing on at all these boards still do something: the tildagon OS notices a board plugged in and turns on the back-LED above that board. and also emits a log message to the serial console

soldered a header on. turns out that board is a fraction of a mm too short for a header to fit and for the hexpansion connector to go in completely but only very slightly out so it "works".

at least for this use case, probably EMF Badge Interposer Hexpansion by Gavin's Creations would have been better.

Low speed pins couldn't flash my LED with the firmware I had. But that was documented on the tildagon website for that firmware version. so I rebuilt the firmware from github.

I didn't build an app to flash the LEDs: I used the USB serial port to ctrl-C out of the firmware and run pin commands in the micropython REPL.

Build firmware

Pretty much I followed the instructions in the readme of emfcamp/badge-2024-software and it worked. It was a bit confusing about how to get the badge into firmware loading mode. I don't quite believe the button sequence in that README.

piezo PWM

pimoroni rainbow hat for raspberry pi - I had this sitting around in my box, waiting for a use. The piezo there is powered off 5v, not 3v, in as much as I needed to feed 3.3v into the 5v supply not the 3.3v supply on the connector

PWM module on any of the high speed pins. I didn't try the low speed pins. Set duty to 0 or 100 for off, and to 50% (512/1023) for tone. Set pwm frequency to change the frequency of the tone.

hook into eventbus to make it beep when Something Happens (eg hexpansion insert/remove)

the driver app doesn't start at startup: i guess if it was in a hexpansion EEPROM, it would? but I don't have one of those.

this is interesting enough to think about getting components soldered onto protoboard

PR for documentation about eventbus synchronous vs async calls

test app that counts button presses using two different callbacks, one of each synchronicity.

tildagon-eventcounter app

Documentation PR #281

rule 22 1-d cellular automata on front LEDs

Doesn't look very good on such a small size, and especially bad because with 12 leds it rapidly turns into a boring pattern due to being a nice round number. I added some randomisation and colours which makes it look better.

it was a good first app though and I have a few other circular ideas to play with.

tildagon-pattern-ca app

in the badge-2024-software repo theres PR #235 unmerged, working on pluggable patterns via apps, which is otherwise a bit weirdly supported right now.

eventbus usage: the docs and examples I've seen attach events to the eventbus in app __init__ and then recommend removing them at minimise time. These aren't symmetric: when the app is reselected on the launcher, the events aren't reattached. I made a new foregrounding concept in my apps using a boolean. but I wonder if there's a better way to do it? I think it could be done on reactivate event notification, for example.

sharpie

I couldn't remember what the 6 buttons on the tildagon conventially do, except for up/down which are in up down positions. Get my sharpie out and annotate the front board. Also fill my name in on the cloud space that perhaps is there for that purpose.


January 2026

even more chemical rinse

the down button is working for its longest ever! 10 minutes so far...

sequencer

playing round with ideas for an on-badge sequencer/programmer app. learning about ctx library for UI. got a play-only UI going. but it can't be used for on-badge programming which is the whole point...

now trying out the Menu UI... the example is a bit confusing, about how to activate/deactivate menu inside bigger application but I think I see what is going on now and I'm making my app have a "UI delegation" mode where I can hand off to other components like Menu easily for UI modal stuff - essentially foregrounding different bits of code with their own update/draw methods. So maybe the whole thing should be made of UI delegates?

I've got a decent sketch of an interactive program scroller and three modes: play, edit, menu - with the menu not doing anything, but it needs to lead into editing steps

down button is still working

A few days after that last chemical rinse, the down button is still working! (9th January 2026)

more sequencer stuff

learning how to make menus actually do something (the two callbacks and how to disable menu UI once user has chosen something). there's a menu._cleanup method that although the _ hints it is internal, seems to actually be the way to stop a menu - i.e. that's the piece that unregisters the button event hooks, sort of the equivalent of backgrounding it.

Another weirdness in menus seems to be that I get a select event immediately when the menu is rendered. Possibly because I'm constructing it inside the event handler for button down, and so (as deliberately labelled as undefined behaviour in the in-source documentation) the newly registered event handler in the menu for the same event is allowed to be fired. Well that's awkward. Which means maybe I have to construct the menu outside of the event handler. e.g. in update. I think that is straightforward to do.

Functionality implemented today: colour picker. step deleting. pause steps. loop counter steps.

maximising an app

I've previously wondered about how to correctly detect when an app is put back in the foreground, so it can re-attach its button events. Some of the documentation talks about doing this in __init__ which doesn't work when you go back to the app afer minimising it: the app object already exists and is not created again, so __init__ doesn't run again.

So I've been doing this in update, with a flag to detect when update runs when the app thinks it was previously backgrounded. But I'm hitting a race condition - after setting the app to be minimised, update gets called afterwards at least once, and that thinks the app has been brought right back into the foreground and does the foregrounding behaviour. This is causing a problem with pattern generation: I re-enable the patterns as part of backgrounding, and the pattern regenerates for a frame (or maybe a few frames) and then my app disables patterns again and so the pattern freezes.

Another way I thought about was listening to foregrounding events, and so I tried that out and it seems to work ok.

emote events

Earlier, I wired a piezo buzzer into a hexpansion, and made it beep on all delivered events - which is mostly apps coming to the foreground/background and hexpansion insert/remove. But how can I make it do more with what is happening on the tildagon? That led to:

How does an arbitrary app do "something" with arbitrary hexpansions?

How does a hexpansion find "stuff" to do?

I'm experimenting with a new set of system events, Emote Events, that an app can fired onto the event bus when an interesting positive or negative (good/bad) thing happens in that app. Hexpansion drivers (or other code) can listen for those events and drive their hexpansion to give a "good feeling" or "bad feeling" - what ever that means for that particular hexpansion. For example, LEDs might flash green/red, but a sound board might play a happy or sad sound. That range of meaning provides pressure for the emote events to be: semantic (don't say "flash green", say "emote happiness") and constrained.

I'm trying this out on my own firmware build. The events need to be in the base firmware, absent techniques for working with "installable events" - such as module loading/detection.

The modifications to the firmware are fairly small: three empty class definitions in modules/events/

I mentioned this in badge IRC and a few people seemed in favour, so I'll make some PRs for this.

When-events for sequencer

I made sequencer app able to trigger sections of code, based on two triggers: Play starts (the existing implicit behaviour) and when a button is pressed.

firmware PRs

I made three firmware PRs:

#246 - to add emote events

#247 - to make pattern mirroring settings controllable

#248 - to make pattern mirroring mirror onto the back live (which I think was the intention of the original author), because once I made the setting to turn mirroring on, it turned out the existing mirroring impl didn't look nice.

wifi flame pattern

made a wifi-strength flame pattern, inspired by the datenklo poles. it's not doing the colour curves enough to make me happy. but otherwise I'm mostly happy with the effect.

hacking on micropython asyncio and eventbus dispatching

my flame app makes the launcher UI extremely sluggish - 1 second or so latency on up/down keypresses. I wonder why. an excuse to poke inside asyncio and use my own hacked up micropython. which the docker build image for tildagon makes pretty easy: it builds from a source checkout so that is easily hackable.

I added some prints on the asyncio task scheduler scheduling tasks or being idle. I don't get any idles. which surprises me - I guess there's some task that is hard looping? maybe that's ok as long as it is still giving up the scheduler.

I can see the scheduler running ok in the trouble some situation. I can see prints take 10ms or so per line, which I guess makes sense at 115200 baud on the serial console, and so putting in debugging prints slows down the scheduler. I can see the IRQs from buttons firing immediately, when I press a button. My first hypothesis was my app was somehow interfering with button IRQs by talking to wifi and LEDs so much. But that doesn't seem to be true. So is something weird happening in the event bus that makes events be delivered slowly?

Let's stick some debugs in the eventbus code for event arrival and dispatch.

Looks like my app is making a RequestForegroundPop event repeatedly - this happens in update(), but update should be suspended when the first RequestForegroundPop event happens... ???

Turns out that the pausing of update() only happens if the run loop subsequently makes a render - because that's where the suspend happens. And because I didn't have any UI rendering to do, I made my update() return False to inhibit that. Thinking it might make things a tiny bit faster.

Remove that return False and it all works properly.

wiflame and 1d-CA as background non-pattern

Now wiflame and sequencer conflict on their use of LEDs: sequencer turns off the pattern. but wiflame isn't acting as a pattern. That PR i mentioned before, #235 to make apps be able to run through the pattern mechanisms - that's probably the right place for wiflame and the cellular automata pattern I made before. So maybe I can pull that branch into my local firmware and try out that feature.

Another sound output

I found another speaker (that came with a bbc micro:bit kit) that is a speaker rather than a piezo. with an onboard amplifier of some kind. The exact same PWM driver code already installed on my tildagon is enough to drive that and it is much louder, more reminiscent of a BBC Micro inbuilt speaker.

Try out PR #235

PR #235 which adds user-installable patterns, merges cleanly into my firmware. I ported my two patterns to that API and that worked pretty straightforwardly. Made a comment on the UI.

Got my tildagon in a state where it needed external intervention (which I am totally set up to do) - a user pattern which crashes at __init__ time. Patched a try/except into my firmware so that it carries on rather than breaks the boot.

Working on the sequencer LED steps

Splitting out the colour picker UI from the LED step creator UI, because the LED step also needs an LED picker: I want it to let you pick which LEDs to set the colour of, not just pick the colour.

eventbus documentation

Do a bit of work on the eventbus documentation, driven by wanting a place to put the documentation for emote events.

my own hexpansion PCBs

I'd like to build my own hexpansion PCB. Thinking about what I'd like. Especially as the cost is in the setup/delivery, and the per-board marginal cost is quite low, this points towards me designing something I can give away 20 of, in addition to whatever I use myself.

Current sketch is a connector PCB that has 14 through-the-hole pins eg for headers, and 5 (2x power + 3x GPIO) pads for banana plugs and crocodile clips (like the micro:bit edge connector without the in-between tracks they have for their edge connector). The 5 pads are an attempt at letting you build a flashing LED / button kind of project "right away" and also some base area to use for sticking onto further boards that attach to the 14-pin header. The two immediate projects are: stick on a mini-breadboard, so prototypes can actually be worn around. Attach bornhack 2021 badge boad - eg with non-permanent double-sided tape between that area and the back of the bornhack badge. Attach tidal badge as a serial terminal display / joystick input

so maybe time to have a play in kicad to see what it looks like more seriously.

down button is broken again...

After a few weeks of working, I left the tildagon powered down for a few days and the down button has stopped working.

Test out an app I helped someone get started with on IRC earlier: a badge app

Ordered a load of bits from Pimoroni, now waiting for the shipment: a GPS module, some LED noodles, a CO2 sensor, an 8x8 infrared time-of-flight distance sensor, three LED letters B E N, with the intention of at least doing something badgy with all of these.

Split out back leds code out of hexpansion manager

I want emotes to make this flash green or red, which means coordination needs to be done with hexpansion LED code. Split into a separate manager which is entirely event drive, using hexpansion events and emoji events.

Try out simulator

Specifically, I want to be able to show Sequencer at Show Us Your Screens next week.

It seems to work. Though I will also take the badge to show stuff like IMU interaction.

publish rule 22 pattern to app store

herehttps://apps.badge.emfcamp.org/apps/03402032/

Took a few tries to get the metadata right to get it in the pattern category

Tried to try this in the simulator. I needed to patch the simulator to make patterns/ appear in a fake location, like it already does for apps/. And the settings dialog box hangs. I haven't looked why.

ctx.rgb range is 0..1 not 0..255

I noticed colours seeming to saturate too much in a bit of experimentation. Someone else on IRC appeared with a similar problem, so I investigated the ctx. Looks like ctx.rgb takes a float from 0..1, but the reference material at https://tildagon.badge.emfcamp.org/tildagon-apps/reference/ctx/ has examples going up to 255, which is saturating at 1.

Made a nice hal-eye demonstrator to show the actual possible gradient of colours.

Now I can go fix up the bug in sequencer app.

breakout garden fun

I've got 5 breakout garden boards lined up in front of me. I want to use two of them this week for FOSDEM: the co2 sensor and the GPS. I've got another breakout garden board that I already soldered pins onto, so it won't fit into this setup. I guess I can take them off. It's a colour sensor and one of the interesting questions I have for sequencer is: what are interesting ways to input colours?

First, I'll do a bit of soldering to turn my existing out-of-spec breakout board into something I can use with my breakout garden breakout board. So I can work on the software/i2c interfacing of things. Then when I see that's working, maybe it will tempt me into building a proper 3x breakout garden board on the other protohex that I got.

I realised that for i2c + low speed interrupts, I don't need the back row (closest to the badge) row of pins, which were the ones causing trouble, as long as I add on somewhere to get power - the board already distributes that down the two side columns, far from the badge/physical conflict zone.

Create an I2C object with the appropriate hexpansion port number and run i2c.scan() and I can see the i2c addresses of the various devices as I click them into the single breakout garden ports. So thats enough wiring for experimenting with i2c protocol software.

co2

Learning a bit how about the tildagon firmware does i2c (because its going via that multiplexer thing). Seems to actually behave as if the multiplexer isn't there, API-wise, because theres a special i2c implementation for tildagon that knows about the multiplexor.

Got it decoding i2c responses, 554 ppm CO2 where I'm sitting in my apartment. Time to open the door and see what happens... it should fall to somewhere low 400s I hope.

16:20:16 554
--opened door--
16:20:45 547
16:21:02 553
16:21:49 618
16:22:16 624
16:23:09 972
16:23:40 1039
... uuuuh i feel like this should be going down not up? 16:24:33 996
16:25:47 770
16:26:26 731
16:27:02 686
16:28:26 546
16:28:55 532
16:30:30 518
16:31:28 683
16:32:19 840
... ok what if i stop sitting right here with it on my desk near my face?
16:32:58 871
16:33:37 822
16:35:26 536
16:37:09 466
16:39:40 412
... ok i've moved back to my desk it looks like it was going down 16:39:59 411
16:41:06 557
16:42:12 783
.. ok lets move the sensor behind my laptop screen about a metre away from me, over to the far side of my table
16:43:24 733
16:43:44 714
16:45:19 534
16:46:22 456
16:48:08 420
.. ok not sitting right over the sensor seems to be doing ok. the room is still being ventilated by my open apartment door
.. i've closed the door now
16:52:03 398

gps i2c

Now I am successfully reading in data from the i2c co2 sensor, switch to one of the other breakout garden boards I want for FOSDEM: gps.

Once I looked at the library and had a play it was fairly straightforward to see how to read this as if it was a serial stream. Although it likes to stick \n as padding bytes in there which makes things a bit weird although shouldn't affect the protocol: \r is used for end of line so I can ignore \n entirely.

So its merrily outputting a bunch of NMEA sentences every second.

But, sitting at my desk, it can't see any satellites.

It's also doing a cold start, and I have no idea what that would look like in the output messages.

So I've moved over near the window with at least a little bit of sky visible, although not a huge amount in this courtyard.

There's even a wikipedia page specifically about time to first fix for GPS, https://en.wikipedia.org/wiki/Time_to_first_fix which talks about the almanac being transmitted every 12.5 minutes.

And after about 5 minutes at the window, I see $GLGSV,1,1,01,75,,,20*64 which I think says it is seeing GLONASS satellite 75.

and then by the time I've written that, it's gone away again but I can see a GPS satellite 08 now.

various satellites coming into view from GPS and GLONASS. but never more than one at once and the SNR always seems low.

so after an hour, how about putting this out on my balcony which might have some more sky? it's cold... but dry at least. and I can leave my laptop inside on the end of the USB cable console.

now it sees 2 GPS and 1 GLONASS satellite.

and 5 mins later, 3 GPS and 2 GLONASS. still now fix though.

and more later (an hour later)... its got 3 satellites on each of GPS and GLONASS and the green PPS LED is flashing. Here's the location message: $GNRMC,183114.000,A,5230.7803,N,01327.3842,E,0.00,171.87,280126,,,A*7B

now it has 9 satellives on GPS and 10 on GLONASS, so its having to send three G?GSV messages for each of those systems to enumerate them

There's some meta data I can probably visualize...

The coordinates even roughly match up with what wikipedia says for where I live.

Here's a whole 1s cycle, the verbose version because not every line is output every second.

$GNGGA,184456.000,5230.7785,N,01327.3702,E,1,7,1.56,46.3,M,44.6,M,,*72
$GPGSA,A,3,10,08,02,,,,,,,,,,1.81,1.56,0.92*08
$GLGSA,A,3,75,69,76,85,,,,,,,,,1.81,1.56,0.92*1E
$GPGSV,3,1,10,08,73,235,17,10,62,072,30,27,55,157,16,02,51,277,27*77
$GPGSV,3,2,10,32,25,126,,23,24,050,,01,19,266,16,14,10,330,*70
$GPGSV,3,3,10,16,06,189,,41,,,*4C
$GLGSV,3,1,09,85,66,322,42,69,52,088,18,70,29,155,,76,27,315,26*6D
$GLGSV,3,2,09,68,25,029,,75,23,253,19,86,09,287,,77,06,357,*6D
$GLGSV,3,3,09,83,02,098,*54
$GNRMC,184456.000,A,5230.7785,N,01327.3702,E,0.00,343.68,280126,,,A*77
$GNVTG,343.68,T,,M,0.00,N,0.00,K,A*29

Now what happens if i bring it back inside to my desk?

It still seems to think it can see 19 satellites... lets give it a while to see if they go away? it's lost its fix and the green LED has stopped flashing on the board.

ok, so i'm generally happy I can get the info I want from this GPS.

Especially it gives me a time source. Although when I have internet I should also be able to NTP (I think the badge is not already doing that)...

and the next breakout to try is the RTC module I got for my bike but never got round to using. or even trying out.

battery and disk status

A little interlude learning how to use the power module for looking at power supply status, and os.statvfs to see disk usage. Both of those are things I'd like to log in my data logger.

RTC / i2c

I got this as noted above for my bike, for datalogging there. I never got round to using it. So now I'm going to try it out because I'd like my tildagon to have real time. Even without internet or GPS.

It is this breakout garden board from Pimoroni: https://shop.pimoroni.com/products/rv3028-real-time-clock-rtc-breakout

big bike battery

I have a 4 unit lipoly that I used for powering my bike that comes from the same range as the existing tildagon battery that I threw away right at the start of this journal.

Cut that out of bike prototype and plug it into the tildagon. It's still mostly charged. The charge LED on the back board goes from flashing red to solid red. and I can now disconnect my USB cable and the tildagon stays awake. aka how it should have been all along...

This battery is almost as big as the tildagon itself! Probably the right place is on the back of the back board. In bike prototype I had it cable-tied on. Or maybe I have the right kind of tape here to attach it.

3x breakout garden protohex hexpansion

I have three breakouts I want to use. Time for protoboard building. The breakout garden connectors I have have too short pins for me to get my verowire pencil to stay on, so I'll do solder traces instead, for everything except the interrupt pins, which I don't need today. Later, I can wire them with regular wires I guess, point to point.

here's a video of what I want to do https://www.youtube.com/watch?v=kROaQZOYNIw

That was a bit frustrating but its done and now I have three breakgarden i2c devices connected into the same hexpansion port and my code from yesterday for talking to co2 sensor and for talking to gps is working without modification.

GPS NMEA with \n padding

The NMEA messages that come out of the GPS are padded with \n when there is nothing more in the buffer, so I made the code detect that and pause polling the GPS for more data for a longer period - apart from at the start, there won't be any more data for almost a second once this happens, so a third of a second delay seems to be ok. I'm not trying to get sub-second accuracy.

Data logger app

This is only for FOSDEM starting tomorrow with an emphasis on collecting raw data for processing later. So effort on driving the sensors and logging to a file. with whatever random UI happens on the way.

Fairly straight forward to make an ugly looking app that logs one json dict per line, which is a format I've found easy to process elsewhere.

Now time to take it a walk around the neighbourhood.

February 2026

FOSDEM

3 people recognised tildagon, plenty of people asked about it

I ran data logger for a data, grabbed the logs. Then day 2, ran a modified data logger which captured readings much faster as I realised while walking round that I could walk quite far in a minute.

On the train back, playing with visualization of the GPS vs access point readings. The data doesn't give me quite the distance-over-space view I was hoping for but there is plenty to play with in preparation for doing this again some how.

Using breakout garden connectors on a badge is a terrible thing to do - the breakouts kept popping out. I ended up taping them in place with electrical tape, and then they just came loose and stopped working sometimes, rather than falling on the ground and me hoping I noticed.

repl while running

I was vaguely aware of aiorepl in micropython and vaguely annoyed by no repl while the tildagon OS is running, instead having to crash out to get a reply.

Someone else raised a similar complaint on IRC and opened issue #252, which turned out to be an hours work and a couple of lines in my PR 254 to make aiorepl run alongside the rest of the tildagon OS.