~derf

You have reached the personal homepage of an entity commonly known as derf / derfnull / Birte Friesel. Hi! 👋

If you are looking for the more professional side of me, you may take a look at my Publications (see below) or head directly to my work homepage: Dr. Birte Kristina Friesel @ Universität Osnabrück

Resources

Contact

You can reach me by E-Mail (d​erf@fina​lr​ewind.org) and on IRC (derf0 @ OFTC, hackint). My PGP key for E-Mail encryption is 64FE6EC0 55560F9E F13A3044 19E6E524 EBB177BA. I occasionally post stuff on the Fediverse (@derf@social.skyshaper.org).

The remainder of this page duplicates a curated sub-set of projects and the latest blog entries.

Projects

> dbris 'Eichlinghofen H-Bahn, Dortmund' 'Dortmund Hbf'

19.01. 16:51  (00:21)  17:12   .  Bus  S

Bus 440 → Oespel S-Bahnhof, Dortmund
16:51 (+1)  ab  Eichlinghofen H-Bahn, Dortmund
16:56 (+1)  an  Oespel S-Bahnhof, Dortmund

Fußweg 46m  (≈ 3 min.)
S 1 → Dortmund Hbf   .
17:01 (+5)  ab  Dortmund-Oespel  2
17:12 (+2)  an  Dortmund Hbf  7
> hafas 'Eichlinghofen H-Bahn, Dortmund' 'Dortmund Hbf'

00:15        Schw-B HB5  (0:03)  S 1

Schw-B HB5 → Universität S-Bahnhof, Dortmund
21:51  ab  Eichlinghofen H-Bahn, Dortmund
21:55  an  Universität S-Bahnhof, Dortmund

Walk 37m  (approx. 3 minutes)
S 1 → Dortmund Hbf
21:58  ab  Dortmund Universität: 2
22:06  an  Dortmund Hbf: 4
> efa Essen Martinstr Düsseldorf Hbf
14:34 ab  Essen Martinstr.: Bstg. 1      Straßenbahn 108      Essen Altenessen Bf Schleife
14:38 an  Essen Hauptbahnhof: Bstg. 1

14:47 ab  Essen Hauptbahnhof: 2          R-Bahn RE11 (RRX)    Düsseldorf Hbf
15:24 an  Düsseldorf Hbf: 10
> dbris-m 'Bochum Hbf'
06:39  ( +7)   ICE 843                             Berlin Hbf  5
06:39  ( +7)   ICE 853                             Berlin Hbf  5
06:51  (+19)       S 1                              Essen Hbf  7
06:37  ( +1)   ICE 527                            München Hbf  3
Zug fährt abweichend mit nur einem Zugteil. Die Wagen 31 - 39 entfallen.
> hafas-m 'Hamburg Dammtor'
13:49  ( +1)  RE 7     Flensburg                  3
13:49  ( +1)  RE 7     Kiel Hbf                   3
13:49         S 5      Buxtehude                  2
13:50  ( +4)  Bus 5    Nedderfeld, Hamburg
13:50         U 1      Ohlstedt, Hamburg
> efa-m -s VVO Dresden Hbf
13:40 ( -2)  5      66           Lockwitz
13:41        3      3         .  Wilder Mann
13:44        4      3         .  Coschütz
13:44        6      66           Freital-Deuben
13:46 ( +4)  6      360          Kurort Altenberg Bahnhof
13:46        5      360          Dresden Ammonstraße / Budapester Straße
13:48 ( +1)  1      7         *  Weixdorf
13:51        1      10        .  Tolkewitz
13:52        Gl.10  RE3          Hof Hbf

News

I recently got myself a new laptop (yes, in 2026, of all times – but at 360€, I'd say the price was quite acceptable): a Chuwi Minibook X. Performance-wise, it's nothing to write home about – it's slightly faster than the X270 I've been using since 2017, and I don't need any more than that. However, with a 10.5" Full HD touchscreen that supports 360° rotation, it's a really nifty netbook / tablet hybrid, and pretty much exactly the type of device that I've been looking for. Especially for hiking and related trips, I like having a laptop with me to pass the time on the train, but a 13.5" laptop was just too unwieldy for that.

When running the Minibook X with Gnome or similar environments, orientation-dependent screen rotation etc. should just happen automatically. In my case, I'm running i3, so I need to do that on my own. And, even if you don't want automatic rotation, you do need to make some changes at least once after booting: the netbook is using a tablet screen, and thus its native orientation is portrait mode.

Boot-Time Screen Orientation

Add the following parameter to the kernel command line (e.g., on Debian, by appending it to GRUB_CMDLINE_LINUX_DEFAULT in /etc/default/grub):

video=DSI-1:panel_orientation=right_side_up

Reading Out the Screen Angle

The netbook contains two identical accelerometers, one in the screen and one in the base. By default, Linux only exposes the one in the screen. There are ways around that, but in my case, a screen-only solution is sufficient.

So: /sys/bus/i2c/drivers/mxc4005/i2c-MDA6655:00/iio:device0/in_accel_?_raw contains raw readings in x, y, and z direction, depending on what you substitute for ?. You could do some fancy trigonometry now, or you could just use the simplest heuristic that you can come up with. I opted for the latter:

def get_accel(axis):
    with open(
        f"/sys/bus/i2c/drivers/mxc4005/i2c-MDA6655:00/iio:device0/in_accel_{axis}_raw",
        "r",
    ) as f:
        x = int(f.read())
    return x


if __name__ == "__main__":
    while True:
        x = get_accel("x")
        y = get_accel("y")
        if x < -500 and mode != "up":
            new_mode = "up"
            # The screen is in normal laptop orientation
        elif x > 500 and mode != "down":
            new_mode = "down"
            # The screen is in inverse laptop orientation ("tent mode")
        if y < -500 and mode != "normal":
            new_mode = "normal"
            # The screen is in its native orientation: it has been rotated into portrait mode so that the hinge is on the left when looking at the screen
        elif y > 500 and mode != "flip":
            new_mode = "flip"
            # The screen has been rotated into portrait mode so that the hinge is on the right when looking at the screen

        # ...

        time.sleep(1)

Changing Screen Orientation

Adjusting the screen orientation actually consists of two commands: one for output (xrandr, as usual), and one for touch input (xinput coordinate transformation matrix). Otherwise, touchscreen events will no longer map to the right display coordinates.

Laptop Configuration ("up")

  • xrandr --output DSI-1 --rotate right
  • xinput set-prop pointer:Goodix Capacitive TouchScreen --type=float Coordinate Transformation Matrix 0 1 0 -1 0 1 0 0 1

The second line sets the 3×3 coordinate transformation matrix to the following value:

 0  1  0
-1  0  1
 0  0  1

Tent Configuration ("down")

  • xrandr --output DSI-1 --rotate left
  • xinput set-prop pointer:Goodix Capacitive TouchScreen --type=float Coordinate Transformation Matrix 0 -1 1 1 0 0 0 0 1

The second line sets the 3×3 coordinate transformation matrix to the following value:

 0 -1  1
 1  0  0
 0  0  1

Tablet Configuration 1 ("normal")

  • xrandr --output DSI-1 --rotate normal
  • xinput set-prop pointer:Goodix Capacitive TouchScreen --type=float Coordinate Transformation Matrix 1 0 0 0 1 0 0 0 1

The second line sets the 3×3 coordinate transformation matrix to the following value:

 1  0  0
 0  1  0
 0  0  1

Tablet Configuration 2 ("flip")

  • xrandr --output DSI-1 --rotate inverted
  • xinput set-prop pointer:Goodix Capacitive TouchScreen --type=float Coordinate Transformation Matrix -1 0 1 0 -1 1 0 0 1

The second line sets the 3×3 coordinate transformation matrix to the following value:

-1  0  1
 0 -1  1
 0  0  1

Toggling Keyboard and Touchpad

Outside of laptop mode, I disable keyboard and touchpad so that I can actually use the device like a tablet.

Disabling Keyboard and Touchpad ("down", "normal", "flip")

  • xinput disable 'AT Translated Set 2 keyboard'
  • xinput disable 'XXXX0000:05 0911:5288 Touchpad'

Enabling Keyboard and Touchpad ("normal")

  • xinput enable 'AT Translated Set 2 keyboard'
  • xinput enable 'XXXX0000:05 0911:5288 Touchpad'

Setting the Wallpaper

After each rotation, you should set the wallpaper again to ensure that it is displayed correctly. How to do that depends on your setup – in my case, I'm using a simple wrapper around feh.

I have uploaded the full auto-rotate script at (sans custom wallpaper-setter) at chuwi-accel.py

I finally got multi-platform / multi-arch Docker builds to work!

In principle, if you want to provide a single Docker image for both amd64 and arm64, all you need to do is run docker buildx build --platform linux/amd64,linux/arm64 …. However, in order for this to actually work, there are several hoops to jump through. The following how-to is mostly reconstructed from short-term memory and shell history, so you may want to double-check what I'm writing with the documentation.

Docker Storage Format

First, if you're using Docker version 28 or earlier, you need to change the storage format to one that supports multi-platform containers. In my case, merging the following content into /etc/docker/daemon.json was sufficient:

{
  "features": {
    "containerd-snapshotter": true
  }
}

Not Recommended: binfmt / qemu

I first tried multi-platform builds by installing qemu-system-aarch64 and binfmt-support – in this case, Docker will use (slow!) software emulation for any selected, non-native platform. However, I never got that to work – the non-native part would always fail at random places, and given its slow progress I was not very keen on debugging it.

Instead, I opted for a setup with two different, native build hosts: one for amd64 and one for arm64. In my case, I have an amd64 VM as the main build host, and an aarch64 / armv8 SoC as build host for arm64. I want to run all commands on the amd64 VM, and the arm64 host should be fully remote-controlled.

Adding Contexts and Builders

In order to make the amd64 VM aware of the arm64 build host, we need two aspects: a context and a builder.

docker context create raspi4 --docker 'host=ssh://user@host'
docker buildx create --use --name arm64_raspi4 --platform linux/arm64 raspi4

(adjust user and host according to your setup)

(Note: the second line may not be required if all you want / need is a single multi-platform builder – see below)

At this point, we can build either for amd64 or arm64, but not yet both at the same time. If we tried to run docker buildx build --platform linux/amd64,linux/arm64 … now, it would fall back to software emulation via qemu for one of the two platforms, depending on which of the two builders (default / arm64_raspi4) is currently selected.

Note: In principle, you can also adjust the arm64 host's systemd docker invocation to include -H tcp://0.0.0.0:port, and then use --docker 'host=tcp://ip:port' when creating the context. However, that will give anyone on the local network docker (and, thus, root) access to the arm64 host. An SSH connection is a much better choice.

Adding a Multi-Platform Builder

Luckily, Docker has a concept of builders that consist of multiple endppoints:

docker buildx create --use --name multiarch default
docker buildx create --append --name multiarch raspi4

As docker buildx ls shows, we now have a builder that supports two sets of platforms:

NAME/NODE             DRIVER/ENDPOINT                   STATUS     BUILDKIT         PLATFORMS
multiarch*            docker-container
 \_ multiarch0         \_ unix:///var/run/docker.sock   running    v0.29.0          linux/amd64, linux/amd64/v2, linux/386
 \_ multiarch1         \_ raspi4                        running    v0.29.0          linux/arm64, linux/arm/v7, linux/arm/v6

And, as the asterisk indicates, it has been selected as builder for all subsequent docker commands. At this point, building works as intended. In my case, I'm using the following commandline to also tag and push the multi-platform image to docker hub:

docker buildx build --push --platform linux/amd64,linux/arm64 --tag derfnull/db-fakedisplay:${VERSION} --tag derfnull/db-fakedisplay:latest --build-arg=dbf_version=${VERSION} .

Travel-Routing-DE-HAFAS-0.11.tar.gz (signature)

  • Update service definitions
  • Fix "uninitialized value" warnings

Travel-Routing-DE-DBRIS-0.11.tar.gz (signature)

  • Segment: Add is_cancelled accessor
  • dbris: indicated cancelled stops and segments

Travel-Routing-DE-DBRIS-0.10.tar.gz (signature)

  • Handle wonky backend timestamps during DST transition (e.g. CET to CEST)
  • dbris: -m / --modes-of-transit: identifiers are now case-insensitive
  • Connection::Segment: new accessors: dep_stop, arr_stop
  • dbris: Show arrival date for cross-midnight connections

Travel-Status-DE-DBRIS-0.27.tar.gz (signature)

  • Handle wonky backend timestamps during DST transition (e.g. CET to CEST)

Travel-Status-DE-VRR-3.20.tar.gz (signature)

  • Handle wonky backend timestamps during DST transition (e.g. CET to CEST)

Travel-Status-DE-DBRIS-0.26.tar.gz (signature)

  • Update train names (patch by Lili Chelsea Urban)
  • dbris: Indicate when we are waiting for a request

Travel-Status-DE-DBRIS-0.25.tar.gz (signature)

  • ...::Formation::Sector: Remove cube_meters and cube_percent accessors. The corresponding data is no longer provided by the backend.

Travel-Status-DE-VRR-3.19.tar.gz (signature)

  • EFA->new: add optional num_results argument (default: 40)
  • efa: add -n / --num-results option

Travel-Routing-DE-DBRIS-0.09.tar.gz (signature)

  • Increase default timeout to 20 seconds to deal with bahn.de's WAF now delaying most requests by about 10 seconds.

Travel-Status-DE-DBRIS-0.24.tar.gz (signature)

  • Increase default timeout to 20 seconds to deal with bahn.de's WAF now delaying most requests by about 10 seconds.

Travel-Status-DE-IRIS-2.04.tar.gz (signature)

  • Update station and meta databases for rail replacement service around Düsseldorf / Wuppertal / Hagen / Dortmund

Travel-Status-DE-IRIS-2.03.tar.gz (signature)

  • Add missing geocoordinates for two stations
  • Update meta database

Travel-Routing-DE-DBRIS-0.08.tar.gz (signature)

  • Deutsche Bahn have rolled out a rather aggressive Akamai EdgeSuite WAF configuration, and too-frequent requests made with this module may cause the corresponding IP address or range to be blocked for yet-to-be-determined amounts of time. Send more plausible requests with randomized user agent strings and additional headers to maybe hopefully slightly reduce the risk of this happening.
  • New dependency: UUID