Shell – Run udev script every minute (displaying Dualshock 4 Battery level)

cronscriptingshell-scriptudev

I created a simple script that changes the Dualshock 4 Gamecontroller LED color bar based on its Battery status (using the answer https://gaming.stackexchange.com/questions/336934/how-to-set-default-color-and-brightness-of-leds-of-the-dualshock-4-controller-on and battery level status from https://wiki.gentoo.org/wiki/Sony_DualShock):

#!/bin/bash

function float_to_int() { 
  echo $1 | cut -d. -f1    # or use -d, if decimals separator is ,
}

LED=$(echo "$1" | egrep -o '[[:xdigit:]]{4}:[[:xdigit:]]{4}:[[:xdigit:]]{4}\.[[:xdigit:]]{4}')

[[ -z "$LED" || ! -d "/sys/class/leds/$LED:global" ]] && exit

BRIGHTNESS=0.2

POWER=$(cat "/sys/class/power_supply/sony_controller_battery_$2/capacity")

GREEN=$(float_to_int $(echo "($POWER*255/100*$BRIGHTNESS)" | bc -l))
RED=$(float_to_int $(echo "((255 - $POWER*255/100)*$BRIGHTNESS)" | bc -l))
BLUE=0

echo $RED > /sys/class/leds/$LED:red/brightness
echo $GREEN > /sys/class/leds/$LED:green/brightness
echo $BLUE > /sys/class/leds/$LED:blue/brightness

I created a very simple udev rule under /etc/udev/rules.d/10-local.rules that runs this script as soon as the controller is connected.

ACTION=="add", SUBSYSTEM=="input", ATTRS{uniq}=="XX:XX:XX:XX:XX:XX" RUN+="/usr/local/bin/ds4led '%p' XX:XX:XX:XX:XX:XX"

This works perfectly so far. What I need right now is to have this script run every minute or so (otherwise the battery status will not get updated).

I tried several ways to achieve this:

Running the entire script in a do-while-loop

while :
do
    // CODE
    sleep 60
done

-> This works for displaying the battery status, but the controller itself is no longer usable

Running the entire script in a do-while-loop in the background

For that I used the brackets & ampersand syntax:

(
while :
do
    // CODE
    sleep 60
done
) &

-> This works in principle, the controller is usable. But as soon as the controller is disconnected, the script proceeds to run, spits out errors and additionally, it annoys me that multiple instances of this script are running constantly.

Solution?

How could we solve the problem of multiple instances constantly running and the script proceeding to run even when the controller is disconnected?

Sure, I could write an additional script, running on the removal of the controller, that kills the still running script from the start. But at this point, this solution seems very unelegant. Isn't there an easy way to have this script run on every minute? I thought of spawning a cronjob from this script that runs every minute and is stopped as soon as the controller is unplugged, but this also does not seem so elegant.

Best Answer

I believe you're trying to solve the wrong problem. The udev subsystem reports when the device is connected (and if you're careful, when it's disconnected). On the other hand, the cron subsystem will run a process on a regular basis.

So put the two together and use udev to enable or disable a toggle that permits a cron script to perform its action.

At a trivial level it might be something like this (and no, I don't particularly recommend a flag file in /tmp unless you protect it with permissions and ownership)

Udev

ACTION=="add" [...] RUN+="touch /tmp/.ds4led_on"

Cron script

#!/bin/bash
#
[[ -f "/tmp/.ds4led_on" ]] || exit 0

# ...Code to perform the update...
Related Question