0

I have a linux/bash script that sends multiple RGB color fades to multiple light. Every light can have it's own color but all colors should use the same amount of time to fade in/out. So it sends out a sequence of 3 differently targeted value-ranges depending on the initial values and based on a speed multiplier.

The problem I run into is that colors are defined by 3 channels Red, Green & Blue (0-255) And depending on the targeted color it could mean that one channel has a value of 10 and the other of 230. And for it to work smoothly I need them to start at 0 and finish at their maximum in the same amount of time/steps. To make it more problematic is the fact that I cannot use values as 0.112. It needs to be either 0 or 1.

At the moment I've been able to solve this problem by limiting the amount of colors I use and only set "half-range" values per channel. eg. R255 G128 B000. In this way I've been able to make it work (within an acceptable margin of inaccuracy.) for each channel I've made a separate base-multiplier that will influence the speed of each channel fading in/out (so for 255 2x, for 128 1x, for 000, 0x)

Since I have 3 lights and I only want to send out the value sequence to the lights that I need I also add up the 3 RGB values to see if the equal zero, if so, that assigned light will not be triggered.

[Q] Can somebody help me to optimise this script so it could work with all RGB values and I would also be able to make it fade between colors. The most important conditions will be that I need to be able to apply a global speed multiplier and the can not be values behind the comma.

Below is the script what I've made so far. I've taken out the duplicates for the other lights as it's basically copy-paste of the same lines, but with other names.

maxR=000, maxG=000, maxB=255, speed=10, 
toggle=$(($maxR + $maxG + $maxB))
subR=$(echo $maxR | cut -c-1), subG=$(echo $maxG | cut -c-1), subB=$(echo $maxB | cut -c-1), 
mulR=$(($subR*$speed)), mulG=$(($subG*$speed)), mulB=$(($subB*$speed))

count=0

while [ $count -lt 3 ] ; do
    count=$(($count +1))
    while [ $varR -lt $maxR ] || [ $valG -lt $maxG ] || [ $valB -lt $maxB ] ; do
        [[ $toggle -gt 0 ]] && $valR to red && $valG to green && $valB to blue
        varR=$(($varR +$mulR))
        valG=$(($valG +$mulG))
        valB=$(($valB +$mulB))
    done

    sleep 1

    while [ $varR -gt 0 ] || [ $valG -gt 0 ] || [ $valB -gt 0 ] ; do
        [[ $toggle -gt 0 ]] && $valR to red && $valG to green && $valB to blue
        varR=$(($varR -$mulR))
        valG=$(($valG -$mulG))
        valB=$(($valB -$mulB)) 
    done

    sleep 3
done
Chen Levy
  • 15,438
  • 17
  • 74
  • 92
Nixx
  • 15
  • 1
  • 7
  • Does your Busybox have Bash? Mine only has Ash. They are not the same. – Dennis Williamson Jul 22 '12 at 01:37
  • So, where's the android part of the question? – Diego Torres Milano Jul 22 '12 at 05:19
  • I have busybox 1.18.4 installed. It is on a android phone(android part of the Q) how to find out it's Bash or Ash? – Nixx Jul 22 '12 at 13:38
  • Try `echo "$BASH_VERSION"`. If the result is empty, it's `ash`. If at a shell prompt, you type `bash` and you get a command not found error, it's `ash`. If you type `ash` and you get a message saying something like "BusyBox v1.18.4 built-in shell (ash)", it's `ash`. – Dennis Williamson Jul 22 '12 at 23:38
  • ok, then I'm guessing it's Ash: If I go in SU mode and type echo $BASH_VERSION I get a empty blank line as response. When I type only *bash* in shell I get bash: not found, if I type only *ash* I get a new line with */ #* instead of the regular *#* – Nixx Jul 23 '12 at 08:54
  • Note that when you write `a=1, b=2,` then `a` is `'1,'` and `b` is `'2,'`, this is probably not what you intended. – Chen Levy Jul 23 '12 at 09:13
  • @chen; how do you mean? (more importantly, where?) This part of the script is working, and for the illustrative purpose I've put certain values in there that are in the end dynamic variables (eg. *sleep 3* is in the end script *sleep $var3sleep*) overall this works okay, but is quite limited and I'm hoping somebody can give me a clue on how to lift the limitations as stated in the description – Nixx Jul 23 '12 at 13:23

2 Answers2

1

This script generates sequences of RGB values that represent smoothly fading colors from any arbitrary color to any other. It uses only eight bit integers (Ash and Bash can only do integer math, although they are capable of working with larger values, of course).

Fades can be in any direction. Red might go from a small value to a large value while blue might go large to small, for example.

The minimum number of steps is taken between two colors. A step value and a sleep value can be passed to control the rate at which the fade occurs.

There is a function to directly set colors.

Some additional information is contained in comments.

There is some demo code at the end. It outputs a lot of text (the set_color() function uses echo as a placeholder). Use less or another pager so you can see the output and scroll through it:

./fader | less

Replace the echo with the command that actually sets the color.

#!/usr/local/bin/ash
#
# by Dennis Williamson - 2012-07-26
# for http://stackoverflow.com/questions/11594670/generate-color-fades-via-bash
#
# fade smoothly from any arbitrary color to another
#
# no values are range checked, but this feature could be easily added
#

set_color () {
    local light=$1
    red=$2 green=$3 blue=$4 # global - see below
    # placeholder for the command that sets led color on a particular light
    echo "$light, $red, $green, $blue"
}

abs () {
    if [ $1 -lt 0 ]
    then
        echo "$(($1 * -1))"
    else
        echo "$1"
    fi
}

max () {
    local arg max=0
    for arg
    do
        if [ "$arg" -gt "$max" ]
        then
            max=$arg
        fi
    done
    echo "$max"
}

fade () {
    local light=$1 step=$2 sleep=$3
    local start_red=$4 start_green=$5 start_blue=$6
    local end_red=$7 end_green=$8 end_blue=$9
    local delta_red=$(abs $(($end_red - $start_red)) )
    local delta_green=$(abs $(($end_green - $start_green)) )
    local delta_blue=$(abs $(($end_blue - $start_blue)) )
    local i=0
    local max=$(max "$delta_red" "$delta_green" "$delta_blue")

    if [ "$delta_red" = 0 ] && [ "$delta_green" = 0 ] && [ "$delta_blue" = 0 ]
    then
        return
    fi

    if [ "$step" -lt 1 ]
    then
        step=1
    fi

    while [ "$i" -le "$max" ]
    do

        red=$(( $start_red + ($end_red - $start_red)  * $i / $max ))
        green=$(( $start_green + ($end_green - $start_green) * $i / $max ))
        blue=$(( $start_blue + ($end_blue - $start_blue) * $i / $max))
        set_color "$light" "$red" "$green" "$blue"
        sleep $sleep
        i=$(($i + $step))
    done
    # $red, $green and $blue are global variables and will be available to the
    # caller after this function exits. The values may differ from the input
    # end values if a step other than one is chosen. Because of that, these
    # values are useful for subsequent calls as new start values to continue
    # an earlier fade or to reverse fade back to a previous start value.
}

# demos (produces a lot of output)

# fade LED, step, sleep, start R G B, end R G B
fade one 3 0 100 200 15 154 144 200
fade one 3 0 "$red" "$green" "$blue" 100 200 15
fade two 1 1 30 40 50 70 20 10
fade three 1 0 0 255 0 0 0 255

i=0
while [ "$i" -lt 10 ]
do
    set_color one 255 0 0
    set_color two 0 255 0
    set_color three 0 0 255
    sleep 2
    set_color one 0 255 0
    set_color two 0 0 255
    set_color three 255 0 0
    sleep 2
    set_color one 0 0 255
    set_color two 255 0 0
    set_color three 0 255 0
    sleep 2
    i=$(($i + 1))
done
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • wow, that looks promising! will check it out over the weekend. – Nixx Jul 27 '12 at 15:28
  • ok.. that weekend became a month :) Just had a look at it, but can't make it work properly. The ABS function doesn't work (arith: syntax error) when I run it in my android shell and the max function also give problem (integer expression expected) When running in CGwin and having replaced the $max with a fixed value it works, but the same will give problem in the android shell due to the ABS function. – Nixx Aug 26 '12 at 16:18
  • @Nixx: I made a couple of changes that may help. I changed the ternary operator in `abs()` to an `if` statement and initialized `$max` to 0 in `max()`. – Dennis Williamson Aug 26 '12 at 16:23
  • mmh.. i also seem to run into trouble assigning the light locations. the light structure is like this: /dir/*position*-name-*color*/file When I just adjust the set_color to something like: echo $red> /dir/first-name-red/file etc. then it doesn't work properly (get a load of "#: not found" messages (# being the output number) – Nixx Aug 26 '12 at 17:47
  • @Nixx: The `#: not found` message means you need a space before the `>` otherwise it's interpreted as a file descriptor. As for fading multiple lights simultaneously, my script has no ability to do that. It would be easily done if Ash supported arrays, but since it doesn't the code quickly becomes much more complex with lots of duplication. I would recommend re-implementing this in another language which supports arrays, such as AWK (which should be included in Busybox). – Dennis Williamson Aug 26 '12 at 19:42
  • Re-implementing is no option as I have trouble enough following this :) will see if I can make a dirty version for now. Thanks for all the advice and such! – Nixx Aug 26 '12 at 21:59
  • @Nixx: The edit you made to my answer was rejected by other users who reviewed it since it's not the appropriate way to do what you intended. You should instead post your new version as an *addition to* rather than a *replacement of* your original *question* - or - on a pastebin site. – Dennis Williamson Aug 26 '12 at 23:30
0

Yesterday I implemented something similar to what you are asking for, for this I decomposed the RGB colors to independently handle the transition.

For the transition I use the vector equation of the line.

r = p0 + tv

Suppose I want to transition from green (00ff00) to magenta (ff00ff)

So the starting point is the green color, p0 = (0, 255, 0)

then you need to find the director vector (v), for that you simply make a vector subtraction between the end point and the starting point

v = pf - p0

Where pf es the magenta color (255, 0, 255) (the final point)

Finally, the value of "t" is a floating point number, which indicates the number of steps you want to perform between one color and the other, this value is between 0 and 1, for example if you want the transition to occur in 255 steps, then divide 1/255

You must take into account that bash scripting does not support floating point operations, for this, you must use tools that allow you to do this type of operations, such as: bc or python or another

t=$(echo 'print(1/255)' | python3)

To calculate the value of r, in a particular coordinate (R,G,B) I used the following function with the help of the bc command

ecVec(){
    echo $(echo "scale=2;$1+$2*$3" | bc)
}

Finally, you have to do the transition cycle (with a for loop for example), calling de ecVec function for every component (Red, Green, Blue)

for i in $(seq -f "%03g" 1 255)
do  
        tInc=$(bc <<< "$i * $t")
        tIncRounded=$(printf "%.3f\n" $tInc)

        qRed="$(ecVec $iR $vR $tIncRounded)"
        qRedRounded=$(printf "%.0f" $qRed;)

        qGreen="$(ecVec $iG $vG $tIncRounded)"
        qGreenRounded=$(printf "%.0f" $qGreen;)

        qBlue="$(ecVec $iB $vB $tIncRounded)"
        qBlueRounded=$(printf "%.0f" $qBlue;)

        setColor $qRedRounded $qGreenRounded $qBlueRounded $LED 

done

I use the setColor function to visualize (output) the color transition

Note that I also used rounding, since the RGB color model only supports Integer numbers

That was my result

DualShock4 rainbow

Javier Larios
  • 192
  • 2
  • 9