4

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.

3

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...
3
  • You'd probably want to add (!) a "remove" action to remove the /tmp file. Also, beware of /tmp file cleaners, unless the device is added/removed frequently enough to avoid stale tmp files. – Jeff Schaller Nov 2 '19 at 21:15
  • Thank you for your quick comment! I'm facing another problem though: My udev does not seem to be able to write to files.. I created a simple echo XXX >> /tmp/ds4led script. But whenever I connect a new controller, this appears in journalctl: Process '/home/jonas/workspace/DS4/ds4led_write '/devices/pci0000:00/0000:00:14.0/usb1/1-7/1-7:1.0/bluetooth/hci0/hci0:256/0005:054C:09CC.0029/input/input124/event18' 00:1f:e2:e5:c3:2e' failed with exit code 1' – Jonas Dedden Nov 2 '19 at 21:30
  • Okay, now I'm getting even stranger errors. I pulled it back just to "echo 123. But still, my journalctl shows: systemd-udevd[3897]: Process 'echo 123' failed with exit code 1. It seems that no udev rule is working anymore right now.. Do you have any ideas on how to fix this? – Jonas Dedden Nov 2 '19 at 22:00
0

With the help of roaima's answer, I finally made it work:

I created 3 different scripts in /usr/local/bin:

ds4led

#!/bin/bash

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

function setrgb() {
    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
}

if [ "$#" -ne 2 ]
then
    while read -r line; do
        linesplit=($line)

        LEDINPUT=${linesplit[0]}
        POWERINPUT=${linesplit[1]}

        setrgb $LEDINPUT $POWERINPUT
     done < /tmp/ds4led
else
    setrgb $1 $2
fi

ds4led_write

#!/bin/bash

/usr/local/bin/ds4led $1 $2
echo $1 $2 >> /tmp/ds4led

ds4led_remove

#!/bin/bash

sed "\!$1!d" /tmp/ds4led --in-place

My /etc/udev/rules.d/20-ds4.rules file:

ACTION=="add", KERNEL=="event28", ATTRS{uniq}=="00:1f:e2:e5:c3:2e" RUN+="/usr/local/bin/ds4led_write '%p' 00:1f:e2:e5:c3:2e"
ACTION=="remove", KERNEL=="event28", ATTRS{uniq}=="00:1f:e2:e5:c3:2e" RUN+="/usr/local/bin/ds4led_remove '%p'"

sudo crontab -e

* * * * * /usr/local/bin/ds4led

Explanation:

Whenever a Dualshock 4 controller is connected, its serial number and its battery identifier is written into a file /tmp/ds4led. On Connection, the ds4led script is called directly with just exactly those identifiers to set the RGB bar instantly to the right color. Afer that, a crontab is run every minute that checks this file under /tmp/ds4led for connected devices and performs the RGB bar procedure. Whenever a controller is disconnected, its entry in the file is removed.

udev-rules: Since every connect of a dualshock 4 controller generated around 8 udev triggers because of my coarse filter, I tried to find an udev event which would only trigger once per connection. In total, every of those devices triggered:

/devices/pci0000:00/0000:00:14.0/usb1/1-7/1-7:1.0/bluetooth/hci0/hci0:256/0005:054C:09CC.0013/input/input67 00:1f:e2:e5:c3:2e
/devices/pci0000:00/0000:00:14.0/usb1/1-7/1-7:1.0/bluetooth/hci0/hci0:256/0005:054C:09CC.0013/input/input68 00:1f:e2:e5:c3:2e
/devices/pci0000:00/0000:00:14.0/usb1/1-7/1-7:1.0/bluetooth/hci0/hci0:256/0005:054C:09CC.0013/input/input66 00:1f:e2:e5:c3:2e
/devices/pci0000:00/0000:00:14.0/usb1/1-7/1-7:1.0/bluetooth/hci0/hci0:256/0005:054C:09CC.0013/input/input67/mouse3 00:1f:e2:e5:c3:2e
/devices/pci0000:00/0000:00:14.0/usb1/1-7/1-7:1.0/bluetooth/hci0/hci0:256/0005:054C:09CC.0013/input/input66/js0 00:1f:e2:e5:c3:2e
/devices/pci0000:00/0000:00:14.0/usb1/1-7/1-7:1.0/bluetooth/hci0/hci0:256/0005:054C:09CC.0013/input/input68/event27 00:1f:e2:e5:c3:2e
/devices/pci0000:00/0000:00:14.0/usb1/1-7/1-7:1.0/bluetooth/hci0/hci0:256/0005:054C:09CC.0013/input/input66/event28 00:1f:e2:e5:c3:2e
/devices/pci0000:00/0000:00:14.0/usb1/1-7/1-7:1.0/bluetooth/hci0/hci0:256/0005:054C:09CC.0013/input/input67/event26 00:1f:e2:e5:c3:2e

I just chose "event28" as the filter since it would only appear once and has something to do with the actual joystick part of the controller (don't forget that every dualshock controller consists of a touchpad and additional emulated devices). However, I am not certain what "event28" actually is.

3
  • Don't use /tmp. In my answer it was only a suggestion for illustrative purposes. It's too easy for a user to create the semaphore file manually and break your code. – roaima Nov 3 '19 at 9:33
  • What else would you suggest? I don't know where files like that usually would go in a Linux system. I made it root-only writeable, so it should not be able to be tinkered with from other users. – Jonas Dedden Nov 3 '19 at 19:23
  • Making the file won't stop me as a user creating the file before you want it. (If you're the only user, you can probably trust yourself...) – roaima Nov 3 '19 at 20:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.