Colour explorer

IMG_4567

This project uses a 3 colour led, a ps3 wireless controller, a RasPiO ProHat and Raspberry Pi to explorer colour addition and subtraction.

The Ps3 Controller I used was an AfterGlow wireless model that comes with its own WiFi dongle. Other models may also work, but this one is very simple to set up to work with the Raspberry Pi, and can be obtained from various outlets. Shop around as the price varies.

To use it with python you simply have to install one library using

sudo apt-get update
sudo apt-get install joystick

Plug the controller wifi dongle into a Pi usb socket and then it should start flashing. Press the central button on the controller marked “hold button to power on”, and it should connect to your Pi.

To test the controller use the command:

jstest /dev/input/js0

note: the controller will switch off automatically after a period of inactivity. Press the button again to reconnect.

The RasPiO ProHat is a very convenient breadboard Hat for the Pi, which has the GPIO pins laid out in numerical order, and the outputs are protected, so it is pretty robust, and you can use leds without series resistors. It also has an excellent series of simple projects utilising the GPIOZERO library which makes things very simple and intuitive to use.

This project builds on the RGBLed section of the ProHat GPIO programming pdf

First wire up a 3 colour led as per the example on page 19 of the RasPiO document.

The program you use is shown below:

from gpiozero import RGBLED
import subprocess,pygame,sys

#initialise items
pygame.init() 
clock = pygame.time.Clock()
ps3 = pygame.joystick.Joystick(0)
ps3.init()
led = RGBLED(red=17, green=18, blue=19)
modeflag=1
changeflag=0

#function to rescale joystick range (used to give 0-255 printed output)
def translate(value,inputMin,inputMax,outputMin,outputMax):
    inputSpan = inputMax-inputMin
    outputSpan=outputMax-outputMin
    valueScaled = float(value-inputMin)/ float(inputSpan)
    return outputMin + (valueScaled * outputSpan)

while True:
    try:
        print("colour wheel")
        pygame.event.pump() #update event queue
        #read left joystick and centre button values
        lud=ps3.get_axis(1) #left up down axis reading ranges -1 to +1
        llr=ps3.get_axis(0) #left left right axis reading ranges -1 to +1
        bflip=ps3.get_button(10) #left joystick button on PS23 controller
        #calcuate rgb values from joystick
        redv=-min(lud,0) #red on 0-> up axis (minus sign makes value increase
        bluev=-min(llr,0) #blue on 0-> left axis (minus sign makes value increase)
        greenv=max(llr,0) #green used on two axes 0->down and 0->right
        green2v=max(lud,0)
        if bflip==1 and changeflag==0: #toggle mode from subtractive to additive
            modeflag=modeflag*-1
            changeflag=1 #lock so that one toggle until button released
        if bflip==0: #release the lock when button released
            changeflag=0
        if ((lud > 0) and (llr > 0)): #disregard bottom right segment: set all leds off here
            led.color=(0,0,0)
            print("red:   0")
            print("green: 0")
            print("blue:  0") 
        elif modeflag==1: #additive mode
            led.color=(redv,(green2v+greenv),bluev)
            print("red:   "+str(int(translate(redv,0,1,0,255))))
            print("green: "+str(int(translate(greenv+green2v,0,1,0,255))))
            print("blue:  "+str(int(translate(bluev,0,1,0,255))))
            print("additive mode")
        else: #subtractive mode
            led.color=((1-redv),(1-green2v-greenv),(1-bluev)) #invert all led inputs
            print("red:   "+str(255-int(translate(redv,0,1,0,255))))
            print("green: "+str(255-int(translate(green2v+greenv,0,1,0,255))))
            print("blue:  "+str(255-int(translate(bluev,0,1,0,255))))
            print("subtractive mode")
        print("lud value: "+str(lud)) #print joystick raw values
        print("llr value: "+str(llr))
  int("LLR value: "+str(llr))
        clock.tick(20) #limit loop refresh rate
        subprocess.call("clear") #clear terminal screen before next pass
    except KeyboardInterrupt: #continue until ctrl-C is pressed
        print("\nExiting")
        pygame.quit()
        sys.exit(1) #use to ignore error retrace

The first section of the program,

from gpiozero import RGBLED
import subprocess,pygame,sys

#initialise items
pygame.init() 
clock = pygame.time.Clock()
ps3 = pygame.joystick.Joystick(0)
ps3.init()
led = RGBLED(red=17, green=18, blue=19)
modeflag=1
changeflag=0

imports the python libraries required by the program. From the gpiozero library RGBLED is imported which lets us control the 3 colour led with simple commands. The subprocess library is used when we clear the terminal screen using the screen command. The pygame library is used so that we can poll the ps3 controller. In this case we do not generate a separate screen (which we would for a graphical game) but we DO need to run the program from a terminal in the graphical desktop rather than the command line environment for it to work.Finally the sys library is used when we exit the program, using the sys.exit call, using it to suppress an error stream which would otherwise ensue when we use ctrl-C to stop the program running. The program then initialises various items. It initialises pygame, and sets up a clock used in the program to govern the main loop timing. It initialises the ps3 controller and sets a variable led which contains information about the RGBLED and the gpio connections used for the three leds. Finally it initialises two flags used to toggle between the two modes of operation of the program.

Often when rgb colours are expressed the individual colour values are shown as numbers in the range 0-255. This is because on simple systems an 8 bit number is used to control the colour, hence a range of 0-255. The next stage of the program consists of a function translate which is used to change the value actually fed to the RGB led command from a range of 0 to 1 to a range of 0 to 255. These altered values are the ones displayed on the screen when the program runs. The function simply works out the fraction of the input range the value represents, and scales it to fit the new required range, offsetting it from the base values (which in this case are both 0).

#function to rescale joystick range (used to give 0-255 printed output)
def translate(value,inputMin,inputMax,outputMin,outputMax):
    inputSpan = inputMax-inputMin
    outputSpan=outputMax-outputMin
    valueScaled = float(value-inputMin)/ float(inputSpan)
    return outputMin + (valueScaled * outputSpan)

The main part of the program now follows, inside a perpetual while True: loop.
This contains a second try:….except loop which continues until the keyboard exception is generated when ctrl-C is pressed. The first part of this code Gets inputs from the ps3 controller and calculates the values to be fed to the three leds.

while True:
    try:
        print("colour wheel")
        pygame.event.pump() #update event queue
        #read left joystick and centre button values
        lud=ps3.get_axis(1) #left up down axis reading ranges -1 to +1
        llr=ps3.get_axis(0) #left left right axis reading ranges -1 to +1
        bflip=ps3.get_button(10) #left joystick button on PS23 controller
        #calcuate rgb values from joystick
        redv=-min(lud,0) #red on 0-> up axis (minus sign makes value increase
        bluev=-min(llr,0) #blue on 0-> left axis (minus sign makes value increase)
        greenv=max(llr,0) #green used on two axes 0->down and 0->right
        green2v=max(lud,0)

It prints a heading colour wheel in the terminal, and then updates the event queue generated by pygame. It then reads the values currently held by the left hand joystick on the controller. lud measures the vertical axis (LeftUpDown)and llr the horizontal axis (LeftLeftRight) These are respectively numbered 1 and 0 by the python interface to the controller. Also recorded in the variable bflip is the value of the push button on the left hand joystick (button 10 on the controller) This is 1 when pushed, otherwise 0. Now the three values for the red, green and blue leds are calculated.

The red led is controlled by the upward movement from the central position of the left joystick. This varies from 0 (in the central position) to -1 (for maximum deflection upwards). so it is set to -min(lud,0) The minus sign ensures a positive value, and the min function prevents the positive value when the joystick is moved downwards from being registered. The value is stored in the variable redv. Similarly the blue value is set to -min(llr,0) It registers when the joystick is moved from the centre towards the left, which give a raw value of 0 to -1 respectively. Again the minus before the min function ensures a positive overall value, and the min function is used to ignore the output when the joystick is moved to the right. The value is stored in the variable bluev.

The green value is a bit more complex. So that we can achieve mixing of all three pairs of primary colours R+G, R+B and G+B, we allow green output for both of the remaining axes, i.e. as the lud joystick moves from the centre downwards (giving a raw output of 0 to 1) and  as the llr joystick moves from the centre to the right again giving a raw output of 0 to 1. These two outputs are calculated and stored in the variables greenv and greenv2 using the functions max(llr,0) and max(lud,0) respectively The max function ensures that in both cases the other halves (negative) of the axes are ignored. You will realise that in the bottom right quadrant of the joystick movement we will be adding two green values together, so later in the program output in this quadrant is blanked off and ignored.

        if bflip==1 and changeflag==0: #toggle mode from subtractive to additive
            modeflag=modeflag*-1
            changeflag=1 #lock so that one toggle until button released
        if bflip==0: #release the lock when button released
            changeflag=0
        if ((lud > 0) and (llr > 0)): #disregard bottom right segment: set all leds off here
            led.color=(0,0,0)
            print("red:   0")
            print("green: 0")
            print("blue:  0") 
        elif modeflag==1: #additive mode
            led.color=(redv,(green2v+greenv),bluev)
            print("red:   "+str(int(translate(redv,0,1,0,255))))
            print("green: "+str(int(translate(greenv+green2v,0,1,0,255))))
            print("blue:  "+str(int(translate(bluev,0,1,0,255))))
            print("additive mode")
        else: #subtractive mode
            led.color=((1-redv),(1-green2v-greenv),(1-bluev)) #invert all led inputs
            print("red:   "+str(255-int(translate(redv,0,1,0,255))))
            print("green: "+str(255-int(translate(green2v+greenv,0,1,0,255))))
            print("blue:  "+str(255-int(translate(bluev,0,1,0,255))))
            print("subtractive mode") v
        print("lud value: "+str(lud)) #print joystick raw values
        print("llr value: "+str(llr))
        print("LLR value: "+str(llr))
        clock.tick(20) #limit loop refresh rate
        subprocess.call("clear") #clear terminal screen before next pass
    except KeyboardInterrupt: #continue until ctrl-C is pressed
        print("\nExiting")
        pygame.quit()
        sys.exit(1) #use to ignore error retrace

The second half of the program starts by testing which mode of operation is to be used. This is toggled by the push button on the left hand joystick, whose value is stored in bflip. when this is set to 1, then initially changeflag will be 0. In this case the statement if bflip ==1 and changeflag == 0 will be true and modeflag will be multiplied by -1 changing its initial value from 1 to -1. changeflag will also be set to 1. This is to ensure that if the button is still pressed on the next iteration of the main loop then modeflag is NOT toggled again. It is only when bflip is detected to have reverted to 0 (ie button not pressed) that changeflag is reset to 0 allowing a subsequent press on the button to once again flip the modeflag back to 1 again.

The next few lines detect if lud and llr are both > 0 In this case the joystick will be somewhere in the bottom right quadrant, and we set the colour of all three leds to 0 effectively switching them off, and print that they all have zero value on the terminal screen. Otherwise the elif statement checks the value of the modeflag. If it is 1 then we are in additive mode.In this case the values redv,bluev and greenv + green2v are output to the three leds giving a colour dependent upon the joystick position in the remaining three quadrants. The scaled numbers (between 0 and 255 rather than 0 and 1 for the three outputs are calculated and displayed on the screen by the three print statements, followed by a further print statement saying that we are in Additive mode. The following else: statement occurs when modeflag = -1 i.e. we are in Subtractive mode. In this case the three led values are inverted by subtracting then from 1. Thus in the central position when the values were all 0, they now all become 1 giving a white light. As the joystick is moved away from the central position 1 or 2 of the leds will be dimmed giving a different total effective colour. Once again the bottom right quadrant is inhibited by the code already discussed above. The subtractive values are scaled and output by three print statements followed by a fourth stating the output mode is subtractive.

After the else: section two further print statements output the raw values of lud and llr so you can see how they relate to the led colour values generated. The clock.tick(20) sets a minimum time for the loop, and then the terminal screen is cleared using subprocess.call(“clear”) which has the same effect as if you had typed clear on the terminal keyboard. If ctrl-C is typed on the keyboard this has the effect of generating an exception which enters the last stage of the program. This prints an “Exiting” message, closes the pygame loop and then exits, suppressing the error trace by using sys.exit(1)

You can download the complete program here

A video of the program in action can be seen here.