Source/values is a more advanced feature of GPIO Zero, but it’s incredibly powerful and much simpler than you’d think! You can do more with less code, and it saves you from using while loops to keep updating the state of your devices. This clever concept allows you to describe how devices should behave, making your code more concise.
The full article can be found in The MagPi 55 and was written by Ben Nuttall.
Source/values is easy to get started with, and takes a keen mind to master. There’s some magic going on beneath the hood! You will learn how it all works, and put it into action by creating logic gates (AND, OR, and NAND), using LEDs and buttons.
What is source/values?
GPIO Zero is more than just a friendly API: it comes with some powerful tools for helping you program your next project. While you can start with simple procedural code, where each line is followed in sequence, you can progress to event-driven programming, meaning various events can be triggered in any order, or at the same time. And finally, the darkest of dark arts, source/values brings forth a third programming paradigm: declarative. This means you describe an object’s behaviour in one line, and it will follow that instruction in the background. This is really handy if you have lots of things going on at once!
Source/values is the name for the concept in GPIO Zero for telling a device how it should behave, or for connecting up multiple devices together so they act in a particular way. Every GPIO Zero device has a .value property which you can read at any time: a button will tell you if it’s pressed (True) or released (False), and an LED will tell you if it’s lit (True) or not (False). Output devices (like LEDs) can have their value set; led.value = True will light the LED, just like using led.on().
Every device also has a .values property, which is an iterator constantly yielding the current value. Output devices have a .source property which is where you tell the device where it should get its values from. Commonly, this will be another device’s .values property, which is an easy way of connecting two devices together. For example, led.source = button.values means the LED will be on when the button is pressed.
The magic under the hood
This concept was developed by Dave Jones (author of the picamera library and co-developer of GPIO Zero) based on an idea formed in a GitHub issue. The way it works is that when a device’s source is set, a background thread is created to constantly set the device’s value to the next item in the iterator. If source is set to another device's .values, this is effectively always reading the other device’s current value. It doesn’t have to be, though: it can be any iterator, or you can write your own function to send values to the device.
A handy thing about GPIO Zero is the fact that devices tend to have a standard value range – usually a sliding scale from 0 to 1 (or True and False, which are effectively just 0 and 1). This means that you can pass the values from one device into another: like a potentiometer (0-1) into a PWM LED (0-1) to dial up the brightness. Exceptions include Motor, which goes from -1 to 1 (-1 being full speed backwards, 1 full speed forwards), and composite devices like Robot, which is a tuple (-1, -1) to (1, 1) for left and right motor speeds.
Why is it useful?
Using this method of connecting devices is much more concise. Rather than using a while loop (five lines) to constantly check if a button is pressed to turn an LED on or off, or using an event-driven approach (two lines: whenpressed and whenreleased), one line tells it to match the button: led.source = button.values.
As well as connecting two devices directly together, you can process the value in between, using a custom function. GPIO Zero provides a set of commonly used tools for this kind of processing. Importing the negated function from gpiozero.tools allows you to specify that an LED should be lit when the button isn’t pressed: led.source = negated(button.values). See more source tools in the docs.
Programming logic gates
Let’s begin. First, wire up two buttons and three LEDs on a breadboard. You’ll need to import the gpiozero LED and Button classes, and the allvalues, anyvalues, and negated source tools. Create objects representing each of the LEDs and buttons, providing the pin numbers they’re wired to. The buttons represent two inputs; the LEDs three outputs. You’re going to program the first LED to represent the binary AND of the two inputs, the second to represent the OR, and the third the NAND.
To define the AND of the two buttons, you’ll need to use the allvalues function from gpiozero.tools; allvalues yields True if all of the inputs are True, which is an AND gate:
out_1.source = all_values(in_1.values, in_2.values)
To define the OR of the two buttons, you can use the anyvalues source tool; anyvalues yields True if any of the inputs are True, like an OR gate:
out_2.source = any_values(in_1.values, in_2.values)
You could define the NAND button using a combination of negated and all_values, but since you have already calculated the AND, you can simply read the state of the AND LED and negate that:
out_3.source = negated(out_1.values)
Finally, if you’re running this code from a file rather than a Python shell, the last line, pause(), will keep your script running. Run your code and you’ll see the NAND LED lit up. That’s because (with no buttons pressed), it’s the only gate that’s active according to the rules. Try pressing the buttons in different combinations to check your logic! Now try programming some more logic gates: XOR, NOR, and XNOR.
from gpiozero import LED, Button from gpiozero.tools import all_values, any_values, negated from signal import pause in_1 = Button(23) in_2 = Button(15) out_1 = LED(24) out_2 = LED(18) out_3 = LED(14) out_1.source = all_values(in_1.values, in_2.values) out_2.source = any_values(in_1.values, in_2.values) out_3.source = negated(out_1.values) pause()