Manual Grub Boot for ZFS Root

As I was messing with making my EFI partition larger, I managed to corrupt the system. My best guess was that my new partition sizes weren't properly (re)loaded before I formated them. Thus, even though both boot and EFI partitions had all files properly restored, during boot I would end up dropped into the Grub prompt.

While I do not often end up in such situation, I already know grub from my Surface Go adventures. So I did what I had done many times before (gpt2 is my boot partition):

set root=(hd0,gpt2)
linux /vmlinuz-6.8.0-28-generic
initrd /initrd.img-6.8.0-28-generic

This moved needle a bit by dumped me into the initramfs prompt. At least here it did helpfully indicate that the issue was (corrupted disk). However, it was obvious something was still wrong as my root ZFS partition was nowhere to be found. Thus, no fsck to fix the issue.

Initial thought was to just load ZFS filesystem:

zpool import Tank/System
zfs mount Tank/System
exit

Well, this actually caused the system to crash as filesystem wasn't properly overlaid. So I had to figure out either how to reload the root partition from the initramfs prompt or to go back to the drawing board.

Thankfully, Looking at my other computer's Grub configuration, I noticed the way forward. There, I saw that linux command has an extra ZFS-related argument. Thus, I adjusted my grub commands accordingly (the example below assumes the root dataset is Tank/System):

set root=(hd0,gpt2)
linux /vmlinuz-6.8.0-28-generic root=ZFS=Tank/System
initrd /initrd.img-6.8.0-28-generic
boot

And this brought my system back to its bootable self.


PS: Since the boot file system was actually readable, I decided to simply copy files to a temporary location, format both boot and EFI partitions, and then copy the data back.

mkdir /mnt/{efi,boot}-copy
rsync -avxAHWX /boot/efi/ /mnt/efi-copy/
rsync -avxAHWX /boot/     /mnt/boot-copy/

umount /boot/efi
umount /boot

DISK1=</dev/disk/by-id/...>
yes | mkfs.ext4 $DISK1-part2
mkfs.vfat -F 32 -n EFI -i 4d65646f $DISK1-part1

mount /boot
mount /boot/efi
rsync -avxAHWX /mnt/boot-copy/ /boot/
rsync -avxAHWX /mnt/efi-copy/  /boot/efi/

rm -rf /mnt/{boot,efi}-copy

Pulling Rotary Encoder State

When it comes to microcontrollers reading a rotary encoder, I often see it being done using interrupts. While that is not necessarily the wrong thing to do, it can crowd the interrupt handler with something that's not really critical functionality, especially if microcontroller can handle it within its base loop just as effectively.

To start with, the code is available here and consists of two exported functions:

void rotary_init(void);
enum ROTARY_DIRECTION rotary_getDirection(void);

If we disregards rotary_init which just initializes a few variables, all the significant code is in rotary_getDirection function. To use it, simply call this function once in a while from the main loop, and it will return one of three values (ROTARY_DIRECTION_NONE, ROTARY_DIRECTION_LEFT, or ROTARY_DIRECTION_RIGHT) corresponding to the detected movement.

The heart of the functionality is in the following code:

    uint8_t currRotState = getRotaryState();
    if (currRotState != lastRotState) {
        histRotState = (uint8_t)(histRotState << 2) | currRotState;
        lastRotState = currRotState;
        uint8_t filteredRotState = histRotState & 0x3F;
        if (filteredRotState == 0x07) { return ROTARY_DIRECTION_LEFT; }
        if (filteredRotState == 0x0B) { return ROTARY_DIRECTION_RIGHT; }
    }
    return ROTARY_DIRECTION_NONE;

First, we get the state in binary for each of the 2 contacts available. Thus, the output can be either 0x00, 0x01, 0b10, or 0b11, depending on which contacts are closed. If the state has changed compared to the previous reading, we append it to the variable used to store previous states. As each state is 2 bits long, this means we can easily fit all 4 states in a single 8-bit value by shifting it 2 bits at a time.

If your switch is new and shiny, this miis where you might stop. However, in the real world, switches are dirty and thus can skip a state or two. For example, one of my rotary encoders would skip a state every once in a while. This means that a proper code would simply make experience worse as time goes by.

However, due to redundancies in how the encoder functions, detecting three neighboring states still gives you plenty of information to go by without misidentifying the direction of scrolling. In my code, detection starts when both encoder contacts are active (i.e. 0b11 state). On all encoders I worked with (admitely, mostly PEC12R series from Bourns), this actually nicely aligns with steps and almost perfectly filters wear-induced noise.

And yes, you can adjust the code slightly to make it run in an interrupt routine if you are so inclined.

Different DHCP for Windows/Linux Dual Boot

While I am using Linux as my daily driver, I still need to use Windows from time to time. This means that I have both systems to back up. As I try to be a good boy, all my backup scripts are running on my server. However, due to how they are written, it's rather hard to distinguish which system is running at what time. One way to solve this would be to rewrite the scripts to enable auto-detection, but a simpler alternative would be to assign different IP addresses to Windows and Linux thus leaving working scripts alone. Using a static IP could be a simple solution, but where's the fun in that? I decided to make my Mikrotik router assign a different DHCP address depending on which system I boot.

When it comes to Mikrotik routers, the configuration options for delivering DHCP are quite rich but not very flexible when matching inputs. Yes, there is an option to recognize the OS (vendor-class), but this requires a different pool for those devices. If you want to match inputs without managing multiple pools, mac-address and client-id are only differentiators. Since I don't want to mess with MAC address, choice is clear.

In Windows, it is hard to alter client identifier, so I decided not to alter its values at all. But Linux, as always, is a land of opportunity. So, to change the class identifier, it's enough to just tell the network manager you want to do so. In the example below, I wanted to change the client identifier on a wireless network named Ursa Major:

nmcli con modify "Ursa Major" ipv4.dhcp-client-id "Ubuntu"

Alternatively, you can edit a file in /etc/netplan (the exact name varies per system; it was /etc/netplan/90-NM-c70925a8-cdca-491a-a6b3-db2263db425a.yaml for me). There, just add the ipv4.dhcp-client-id: "<whatever>" setting under the networkmanager key. My file looked something like this (some sections omitted):

network:
  version: 2
  wifis:
    NM-c70925a8-cdca-491a-a6b3-db2263db425a:
      access-points:
        "Ursa Major":
          networkmanager:
            uuid: "c70925a8-cdca-491a-a6b3-db2263db425a"
            name: "Ursa Major"
            passthrough:
              ipv4.dhcp-client-id: "Ubuntu"

Combine either of these changes with RouterOS client-id matching, and you have the same machine using different IPs, depending on the OS it booted in.

Powering Mikrotik Router from Netgear GS305EP

Occasionally I find myself with piece of information that is unlikely to help anybody other than me in future. But that never prevented me from making a post before, so why should it now. In the left corner, we have my existing Mikrotik Audience router that yearns for some power via passive PoE. And in the right corner we have Netgear GS305EP PoE switch that'll give its best using 802.3af/at. Can we make them play together?

Short answer is no - you cannot power router that uses passive PoE from a switch that provides only 802.3af/at. But... Depending on both the exact passive PoE device and how the active PoE switch is doing detection, you might get lucky and things will work.

First prerequisite is for client device to support 56V on PoE input. Anything lower than that and you'll probably fry your device if you attempt powering it. Second prerequisite is that active PoE device supports legacy mode where resistors are used to determine power level. And lastly, your client device needs to have those resistors even though it doesn't support active PoE as such. In Settings this would look like this:

Power Mode .....: Pre-802.3at
Power Limit Type: User
Power Limit (W) : 30.0
Detection Type .: 4pt 802.3af + Legacy

If you are lucky with all that, you might have power flowing between two officially incompatible devices.

Caveat emptor!