Shallow Thoughts

Akkana's Musings on Open Source Computing and Technology, Science, and Nature.

Fri, 08 May 2026

Unit Testing TkInter Apps

I've been making a lot of tweaks lately to MetaPho, in particular its Python/TkInter based replacement for my C/GTK2 image viewer Pho.

Pho has always had quite a few modes: it can be fullscreen, in a window sized for the current image, or in a fixed-size window; images can be scaled to the window/screen size, or you can zoom in/out, or you can view them at full size (pixel for pixel). It's fairly common that when I fix a bug in one mode, it introduces a new bug in a different mode because of the way the scaling code works.

Ideally, in a complicated program, you guard against problems like that with automated tests. But that's hard to do in a GUI (graphical user interface) app. A window comes up, but how do you make it do different things? How do you check whether it's showing the right thing, or if it's the right size?

I've tried a couple times to find hints on how to unit test Python scripts in either Tk or GTK, but there's not much help available. I think most people just give up and don't test their GUIs — just as I've always given up.

This time, I decided to really dive in and see if I could write a TkInter unit test script for testing all those different TkPho modes. It wasn't easy, but now I have a basic framework that I should be able to use for other GUI apps as well.

Read more ...

Tags: , ,
[ 13:55 May 08, 2026    More programming | permalink to this entry | ]

Sun, 03 May 2026

Coffee Shirts

[A man sitting on a down ponderosa in a canyon, wearing a t-shirt that looks like tie-dye except the dye is all brown] I'm not a major coffee drinker, but Dave is, and he's varied over the years in how he prefers to make his coffee. For a long time he used an espresso maker, then a French press, then cold press, but lately, he's been making a variety of cold press he calls "sun coffee". It's similar to "sun tea", where you mix tea leaves with water in a pitcher in a sunny window for a few days.

That means that eventually, it has to be filtered. We don't want want to use disposable paper filters. There are lots of options, but I like the solution Dave came up with: he uses an old white t-shirt. Two layers of t-shirt material does a pretty decent job of filtering (you might need to shift to another place on the shirt halfway through, depending on how much coffee you brewed and how finely it's ground).

After filtering, you wring out the filter and dump the grounds in a bucket where eventually it can be transferred somewhere like a path out in the yard. (We used to use it in the garden or in the compost bin on the theory that plants like more acidic soil, but the plants didn't do well so we've stopped that.) The coffee gets stored in the fridge.

Read more ...

Tags:
[ 11:38 May 03, 2026    More misc | permalink to this entry | ]

Thu, 30 Apr 2026

How to Re-initialize a Stuck ESP32 (in CircuitPython)

I designed my particulate air quality sensor project around Adafruit's PyPortal. It uses a ESP32 coprocessor for networking.

Unfortunately, the ESP32 is a little flaky. It tends to lose track of the network after an hour or so:

ESP32 not responding
Traceback (most recent call last):
  File "code.py", line 182, in 
  File "adafruit_requests.py", line 725, in post
  File "adafruit_requests.py", line 649, in request
  File "adafruit_connection_manager.py", line 331, in get_socket
  File "adafruit_connection_manager.py", line 248, in _get_connected_socket
  File "adafruit_connection_manager.py", line 61, in connect
  File "adafruit_esp32spi/adafruit_esp32spi_socketpool.py", line 114, in connect
  File "adafruit_esp32spi/adafruit_esp32spi.py", line 899, in socket_connect
  File "adafruit_esp32spi/adafruit_esp32spi.py", line 801, in socket_open
  File "adafruit_esp32spi/adafruit_esp32spi.py", line 422, in _send_command_get_response
  File "adafruit_esp32spi/adafruit_esp32spi.py", line 378, in _wait_response_cmd
  File "adafruit_esp32spi/adafruit_esp32spi.py", line 292, in _wait_for_ready
TimeoutError: ESP32 not responding

I tried re-initializing the network, but it didn't help: re-initializing always died with Timed out waiting for SPI char and SCK in use.

There are lots of people asking about this on the net, but I couldn't find a discussion that actually had a solution for how to re-initialize a stuck ESP32. So I asked Claude. I know, AI, eww ... but Claude seems to have access to CircuitPython code and discussions that Google doesn't index, so sometimes it's the best way to find out how to solve CircuitPython problems. It took a couple of iterations (each requiring a few hours of testing, since it typically takes an hour or so before the network stops working), but we got there. Here's what seems to work for me.

Read more ...

Tags: ,
[ 10:10 Apr 30, 2026    More hardware | permalink to this entry | ]

Fri, 17 Apr 2026

Particular: A Particulate Air Quality Sensor

We're thinking about replacing our ancient fireplace with a modern wood stove. There are lots of reasons, but one is that the house smells smoky when we use the fireplace (which is pretty much every night in winter), and I can't help wondering what all that smoke is doing to my lungs.

Dave insists that the smoke all gets sucked up the chimney and I shouldn't worry about it. I tried to look it up, but it seems like there's hardly any published research on that (or maybe I was just choosing the wrong search terms).

[a boxy blue air quality sensor in a crate made out of popsicle sticks, with a microcontroller with screen on top] But why not actually measure it? I've occasionally wanted a particulate matter sensor anyway; we get a lot of wildfire smoke here in New Mexico most summers (sometimes from local fires, sometimes from as far away as California or Canada) and sometimes the air quality can get pretty bad.

Of course you can buy ready-to-go air quality sensors. But what's the fun in that, when you can make your own for about half the price? (If you don't count the value of your time, that is.)

Read more ...

Tags: , ,
[ 15:09 Apr 17, 2026    More hardware | permalink to this entry | ]

Mon, 06 Apr 2026

Blacklisting a Module in the Linux Kernel (in 2026)

As part of a quest to disable the HDMI audio devices that Linux's audio system pipewire is so fond of (about which, more in a separate article), I got the bright idea of blacklisting the snd_hda_codec_hdmi kernel module. (Don't do that; it isn't a good solution because it breaks other things.)

But at least along the way I learned how to blacklist kernel modules, which isn't as simple as the net might make you think.

Read more ...

Tags: ,
[ 11:40 Apr 06, 2026    More linux/kernel | permalink to this entry | ]

Sun, 22 Mar 2026

Controlling Pipewire's Misconfigured Audio Output Sinks

One of the worst breakages from the *grade (I hesitate to call it an upgrade) to Debian Trixie was audio. The old PulseAudio setup — which had been working beautifully for the last several years — was replaced by a new sound system called Pipewire that sits on top of PulseAudio and, well, basically, breaks it.

Recently I decided it was finally time to figure out Pipewire's broken handling of audio output. The main problem: half the time, upon booting, my audio doesn't work, and if I run pavucontrol to see the configuration, I see three different HDMI audio devices as well as the laptop's built-in Intel audio chip. Most of the time my laptop is plugged in to an HDMI monitor, yes — but that monitor has no speakers or other audio hardware, so I basically never want HDMI audio. And in any case there's only one monitor connected, not three.

(And yes, there are occasionally times I might want HDMI sound, like if I want to give a presentation over a projector that uses sound. That has happened to me once in my life, so far.)

So every time I boot, there's a good chance that audio won't work and I'll have to fire up pavucontrol, go to the Output Devices tab, mute all three of the HDMI sinks, unmute the built-in speaker sink, and click the button to make the built-in speaker the default sink. (There's no way to tell what the previous default was: pavucontrol, although it has buttons to set a sink as default, doesn't show what the current default is.)

Read more ...

Tags: ,
[ 16:37 Mar 22, 2026    More linux | permalink to this entry | ]

Tue, 17 Mar 2026

USB Errors in dmesg, Solved

For many years, I've been annoyed at how my Linux computer (a Lenovo Carbon X1, gen 7) fills dmesg with errors every few seconds like:

usb usb3: root hub lost power or was reset
(sometimes it was usb4 rather than usb3, or different but obviously related messages).

It makes it hard to see real messages in dmesg. I thought (NOTE: this was a stupid assumption) that since it said "root hub", that meant it was some kind of bad hardware design in the hub that's built in to the laptop, so I just put up with it.

Recently I complained about it on #linux and someone challenged me to actually try unplugging things to figure out what was actually causing it.

Read more ...

Tags: , ,
[ 09:52 Mar 17, 2026    More linux/kernel | permalink to this entry | ]

Sat, 21 Feb 2026

A More Time Zone Tolerant datetime Class

Yesterday I signed in to the billtracker, and got an error page when trying to display my bill list:

[ ... ]
   File "/var/www/nmbilltracker/billtracker/app/models.py", line 766, in location_html
     if self.last_action_date > self.scheduled_date.replace(tzinfo=None):
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 TypeError: can't compare offset-naive and offset-aware datetimes

Python's datetime class drives me crazy.

Any given datetime object might or might not have a timezone. Those that do are called "timezone aware" or just "aware" datetimes; those without a timezone are called "unaware" or "naive". Any given function might or might not return a timezone-aware datetime. If you ever mess up and call a function that returns a timezone when you didn't expect one, or vice versa, or if a function you call changes in that respect, now you have a hidden time bomb that will crash your program the next time you do any sort of comparison with or subtraction from another datetime, and by then, you may have no idea way of finding out where the problematic time came from so you can guard against it happening again.

Read more ...

Tags: , ,
[ 18:53 Feb 21, 2026    More programming | permalink to this entry | ]