New laptop part 5b: Prevent sleep / hibernation when home and in charge

Sunday, July 9, 2023

laptopframeworkpowersystemdudev

Introduction

In a previous post, I talked about my configuration for managing power (lock, suspend, hibernation, …). It works as expected, meaning it locks the screen after 10min via xautolock, and auto suspend via systemd logind.conf and sleep.conf files.

It is all fine, but usually when I am home I tend to leave the laptop switched on if I know I’m going to reuse it more or less quickly. And usually when I go back to it I don’t like seeing the laptop powered off. When I know I want it to go in sleep or hibernation, either I just unplug it or directly put it to sleep. Normally, software that needs to prevent system lock sends a signal to inhibit the system to shutdown/sleep. Read the freedesktop.org documentation to learn about the 7 inhibitor lock types and what you can do with it. In specific case, I couldn’t really use that so I went another way with systemd.

So what about my requirements? Well it is simple, I want to prevent lock/sleep/hibernation when I the laptop is used at home and the battery is charging.

I’m not at all an expert in systemd (I tried for a long time to avoid distro with systemd), so there might be errors or unnecessary things in this post, but I made it work for me… Let me know if you know how I could improve it :).

Let’s try!

The wrong start

Nota: Don’t copy past code from that section, they don’t work as expected.

To do so, I started creating a systemd service that will run before the sleep related services. To find sleep related services, one can list all available systemd target using this command:

systemctl list-unit-files --type target

In this example, the new service needs to run before the following target:

sleep.target
hibernate.target
hybrid-sleep.target
suspend.target
suspend-then-hibernate.target

Knowing the target, we can create a simple service that will run before any of these sleep related events. I created /etc/systemd/system/inhibitsleep.service with the following content:

[Unit]
Description=Inhibit suspend when home and plugged to electricity
Before=sleep.target hibernate.target hybrid-sleep.target suspend.target suspend-then-hibernate.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/inhibitsleep.sh

[Install]
RequiredBy=sleep.target hibernate.target hybrid-sleep.target suspend.target suspend-then-hibernate.target

Important part here are:

  • the Before= in the [Unit] that tells this service needs to run before the mentioned services
  • the RequiredBy= in the [Install] that indicates that this service is required by the sleep related services
  • The [service] is of type oneshot so the service manager will consider the unit up after the main process exits . The ExecStart= is just pointing to a simple shell script.

To enable this new service, a simple systemctl enable inhibitsleep.service will do the trick.

In my head, I would simply need to check if I was connected to my home wifi and if the laptop battery was charging… And I did just that in a small shell script that basically looked like this:

wifi=$(nmcli -t -f device,state,connection d | grep "${ssid}" | wc -l)
battery=$(upower -i /org/freedesktop/UPower/devices/battery_BAT1 | grep state | /usr/bin/awk '{print $2}')

if [[ "${wifi}" == "1" && "${battery}" == "charging" ]]; then
  /usr/bin/echo "Preventing sleep."
  exit 1
else
  /usr/bin/echo "Not preventing sleep."
  exit 0
fi

What I didn’t know is that when that script is triggered, some other services ran before and wifi was already disconnected before sleep… So that didn’t work at all!

Switching gears

The new idea was:

  • Creating a udev rule to run on AC plug that would trigger a shell script that would look at the status (charging or not) as well as the ssid I’m connected to (if any). Based on that, I write a value of the status in a file.
  • In the script fired by the systemd service above (inhibitsleep.service), check that value and act upon it.

Using this strategy would also simplify the manual trigger to enable/disable sleep (more on that in another post).

Udev rule on AC (un)plug

To run script when AC is (un)plugged, create the file /etc/udev/rules.d/90-ac-on.rules with the following content:

SUBSYSTEM=="power_supply", ATTR{online}=="1", RUN+="/usr/local/bin/powerSupplyCharging.sh"
SUBSYSTEM=="power_supply", ATTR{online}=="0", RUN+="/usr/local/bin/powerSupplyDischarging.sh"

Then, of course, we need to create both scripts (powerSupplyCharging.sh and powerSupplyDischarging)

/usr/local/bin/powerSupplyCharging.sh:

#!/usr/bin/env bash

ssid="<MySSID>"
wifi=$(/usr/bin/nmcli -t -f device,state,connection d | /usr/bin/grep "${ssid}" | wc -l)

if [[ "${wifi}" == "1" ]]; then
  /usr/bin/echo "1" > /tmp/.preventSleep
else
  /usr/bin/echo "0" > /tmp/.preventSleep
fi

/usr/local/bin/powerSupplyDischarging.sh:

#!/usr/bin/env bash

/usr/bin/echo "0" > /tmp/.preventSleep

Then reload udev rules:

sudo udevadm control --reload

Now I have either 0 or 1 in /tmp/.preventSleep each time to (un)plug the laptop. Now let’s use that to block locking.

Update the script launched by systemd

Update the script launched by systemd (see above) /usr/local/bin/inhibitsleep.sh to:

#!/usr/bin/env bash

toggle=$(cat /tmp/.preventSleep)

if [[ "${toggle}" == "1" ]]; then
  exit 1
else
  exit 0
fi

Then enable the systemd service:

sudo systemctl enable inhibitsleep.service

And voilà, it should prevent sleeping when charging and on my home WIFI. Every other situation, the usual happens, meaning that the laptop will go into suspend-then-hibernate mode.

Drawback of the solution

The solution isn’t perfect, though, and the issues I’ve noticed so far are:

  • The wifi get disconnected. Only for a short period and after hibernation is prevented, the wifi is connected again. Usually it doesn’t create any issue as the reconnection is fast.
  • The screen is still getting locked. This is both a good and a bad thing. Most of the time, I do prefer the automatic screen locking, even home (since now it is easy to unlock i3lock via fingerprint). But sometime I would prefer it didn’t. I have a manual weird way of blocking this that I’ll describe in the next post.
  • It also prevent manual sleep / hibernation if in the same set of conditions (in this case, at home and battery charging). I don’t mind because if I manually put the laptop to sleep, I want to make sure I’ve unplugged it anyway, so it is always a good reminder.

Conclusion

That’is for now, you can find the latest version of these scripts are on sr.ht, more specifically the bash scripts, the systemd config and the udev rules.


Contact

If you find any issue or have any question about this article, feel free to reach out to me via email, mastodon, matrix or even IRC, see the About Me page for details.

See Also

New laptop part 6: Managing multi screens with i3wm and autorandr

i3lock and fingerprint