💾 Archived View for going-flying.com › ~mernisse › 02.gmi captured on 2023-09-08 at 16:04:46. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-01-29)

➡️ Next capture (2024-06-16)

-=-=-=-=-=-=-

[07/29/2020 @09:30]: Automating daemon start on USB plug with systemd

Lets start out with this, just to get it out of the way. I hate systemd. I think what it does is great but I think the user-facing bits of it are awful. I also hate how it's been railroaded into way too much of the system and yet for some reason the Linux kernel folks continue to refuse to implement a lightweight message bus which means even a Raspberry Pi gets to run dbus.

Ok, that is out of the way, as I mentioned in my last note I hooked a VFD up to a CGI on my Gemini server. The end of that chain is a small Python HTTP server that translates a command into a serial command to send to the VFD. The VFD is connected to an ATmega32U4 which provides serial emulation over USB-CDC. Initially for testing I was just running the vfdserver process inside tmux but that's not at all a long-term solution so I set about trying to figure out how to get it to automatically run.

Internetting like it's 1998 again

Round 1

The easiest thing to do is just to create a systemd unit to run the server whenever. As long as the VFD is plugged in at boot this is fine, however if the VFD is not present then the process will terminate after throwing an exception over and over. That being said, it was what I tried first.

/etc/systemd/system/vfdserver.service

[Unit]
Description=Serial VFD server
After=network.target

[Service]
Type=simple
Restart=always
User=root
ExecStart=/usr/sbin/vfdserver.py /dev/ttyACM0

[Install]
WantedBy=multi-user.target

Two things bothered me about this though. I don't particularly like the idea of the server restarting over and over if I have to unplug the VFD to flash new firmware to it. Also it seems that if I re-plug it with the system running it may show up as /dev/ttyACM1 instead of /dev/ttyACM0 so that is not ideal.

Round 2

Obviously now I wanted to see if I could create a stable device name for the USB serial port. Having it flop around after unplug/plug is not ideal. Thankfully I've done this before with network interfaces so it was pretty easy to just add a udev rule to give this particular USB device a specific name.

/etc/udev/rules.d/99-vfd.rules

SUBSYSTEM=="tty", ATTRS{idVendor}=="239a", ATTRS{idProduct}=="000d", SYMLINK+="ttyUSB-VFD", GROUP="dialout", MODE="0666"

I set the USB vendor ID and product ID in the firmware of the device so it was trivial to make sure it was unique to this particular unit. This makes it always create a /dev/ttyUSB-VFD which I can now use in my systemd unit in place of /dev/ttyACM0. Awesome.

Round 3, Fight!

But that restarting thing. It bugged me that the system would be sitting there continually trying to run a server that had no shot in hell of starting. I also didn't want to make the script sit around and wait for the device to show up and then handle disconnect and reconnect itself. I mean it's like 90 lines of Python and based on the standard library's http.server.ThreadingHTTPServer so not exactly complex and I didn't really want to make it more complex. A little bit of digging shows that I can trigger systemd on device events. This makes sense to me given it at some point absorbed udev. Step one seems to be telling systemd about the device, that was a simple change in the rules file to add the systemd tag.

/etc/udev/rules.d/99-vfd.rules

SUBSYSTEM=="tty", ATTRS{idVendor}=="239a", ATTRS{idProduct}=="000d", SYMLINK+="ttyUSB-VFD", GROUP="dialout", MODE="0666", TAG+="systemd"

Now the device will show up as a systemd unit.

systemctl status /dev/ttyUSB-VFD

● dev-ttyUSB\x2dVFD.device - 2x24_Line_VFD_Display
   Follow: unit currently follows state of sys-devices-platform-soc-3f980000.usb-usb1-1\x2d1-1\x2d1.3-1\x2d1.3:1.0-tty-ttyACM0.device
   Loaded: loaded
   Active: active (plugged) since Wed 2020-07-29 09:21:33 EDT; 18min ago
   Device: /sys/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.3/1-1.3:1.0/tty/ttyACM0

Once systemd knows about it we can list it as a reverse dependancy on our service and somewhat shockingly systemd will do the right thing.

/etc/systemd/system/vfdserver.service

[Unit]
Description=Serial VFD server
After=network.target

[Service]
Type=simple
Restart=always
User=root
ExecStart=/usr/sbin/vfdserver.py /dev/ttyUSB-VFD

[Install]
WantedBy=dev-ttyUSB\x2dVFD.device

I plugged the display in and systemd promptly started up the server process.

● vfdserver.service - Serial VFD server
   Loaded: loaded (/etc/systemd/system/vfdserver.service; enabled; vendor preset: enabled)
   Active: active (running) since Wed 2020-07-29 09:21:33 EDT; 20min ago
 Main PID: 1332 (python3)
    Tasks: 1 (limit: 2068)
   CGroup: /system.slice/vfdserver.service
           └─1332 python3 /usr/sbin/vfdserver.py /dev/ttyUSB-VFD

Conclusion

Ok, so I was a little surprised it was that easy. I sort of expected there to be some kind of hackyness involved but at the same rate I guess it shouldn't be that shocking, running services on device plug events is kind of a big deal in the desktop world so it really *should* be a solved problem. Anyway I hope this look into how I built my silly little Gemini connected VFD display was interesting, or at least if not interesting helpful if you ever find yourself screwing around with systemd and udev. 🔨

vfdserver.py source

↩ back to index

backlinks [geminispace.info]

🚀 © MMXX-MMXXIII matt@going-flying.com