Merge pull request #558 from RPi-Distro/statusboard2

Add StatusZero and StatusBoard
This commit is contained in:
Ben Nuttall
2017-06-29 23:22:33 +01:00
committed by GitHub
10 changed files with 598 additions and 349 deletions

View File

@@ -127,6 +127,20 @@ Energenie
:inherited-members: :inherited-members:
:members: :members:
StatusZero
==========
.. autoclass:: StatusZero
:inherited-members:
:members:
StatusBoard
===========
.. autoclass:: StatusBoard
:inherited-members:
:members:
SnowPi SnowPi
====== ======
@@ -168,4 +182,3 @@ CompositeDevice
.. autoclass:: CompositeDevice(\*args, _order=None, \*\*kwargs) .. autoclass:: CompositeDevice(\*args, _order=None, \*\*kwargs)
:members: :members:

View File

@@ -90,11 +90,13 @@ digraph classes {
ButtonBoard->HoldMixin; ButtonBoard->HoldMixin;
PiLiter->LEDBoard; PiLiter->LEDBoard;
PiLiterBarGraph->LEDBarGraph; PiLiterBarGraph->LEDBarGraph;
StatusZero->LEDBoard;
TrafficLights->LEDBoard; TrafficLights->LEDBoard;
SnowPi->LEDBoard; SnowPi->LEDBoard;
PiTraffic->TrafficLights; PiTraffic->TrafficLights;
PiStop->TrafficLights; PiStop->TrafficLights;
TrafficLightsBuzzer->CompositeOutputDevice; TrafficLightsBuzzer->CompositeOutputDevice;
StatusBoard->CompositeOutputDevice;
FishDish->TrafficLightsBuzzer; FishDish->TrafficLightsBuzzer;
TrafficHat->TrafficLightsBuzzer; TrafficHat->TrafficLightsBuzzer;
Robot->CompositeDevice; Robot->CompositeDevice;
@@ -115,4 +117,3 @@ digraph classes {
PingServer->InternalDevice; PingServer->InternalDevice;
CPUTemperature->InternalDevice; CPUTemperature->InternalDevice;
} }

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 KiB

After

Width:  |  Height:  |  Size: 253 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -80,6 +80,8 @@ from .boards import (
TrafficLights, TrafficLights,
PiTraffic, PiTraffic,
PiStop, PiStop,
StatusZero,
StatusBoard,
SnowPi, SnowPi,
TrafficLightsBuzzer, TrafficLightsBuzzer,
FishDish, FishDish,

View File

@@ -12,7 +12,7 @@ except ImportError:
from time import sleep from time import sleep
from itertools import repeat, cycle, chain from itertools import repeat, cycle, chain
from threading import Lock from threading import Lock
from collections import OrderedDict from collections import OrderedDict, Counter
from .exc import ( from .exc import (
DeviceClosed, DeviceClosed,
@@ -795,6 +795,102 @@ class PiStop(TrafficLights):
pwm=pwm, initial_value=initial_value) pwm=pwm, initial_value=initial_value)
class StatusZero(LEDBoard):
"""
Extends :class:`LEDBoard` for The Pi Hut's `STATUS Zero`_: a Pi Zero sized
add-on board with three sets of red/green LEDs to provide a status
indicator.
The following example designates the first strip the label "wifi" and the
second "raining", and turns them green and red respectfully::
from gpiozero import StatusZero
status = StatusZero('wifi', 'raining')
status.wifi.green.on()
status.raining.red.on()
:param str \*labels:
Specify the names of the labels you wish to designate the strips to.
You can list up to three labels. If no labels are given, three strips
will be initialised with names 'one', 'two', and 'three'. If some, but
not all strips are given labels, any remaining strips will not be
initialised.
.. _STATUS Zero: https://thepihut.com/statuszero
"""
default_labels = ('one', 'two', 'three')
def __init__(self, *labels, **kwargs):
pins = (
(17, 4),
(22, 27),
(9, 10),
)
if len(labels) == 0:
labels = self.default_labels
elif len(labels) > len(pins):
raise ValueError("StatusZero doesn't support more than three labels")
dup, count = Counter(labels).most_common(1)[0]
if count > 1:
raise ValueError("Duplicate label %s" % dup)
super(StatusZero, self).__init__(_order=labels, **{
label: LEDBoard(red=red, green=green, _order=('red', 'green'), **kwargs)
for (green, red), label in zip(pins, labels)
})
class StatusBoard(CompositeOutputDevice):
"""
Extends :class:`CompositeOutputDevice` for The Pi Hut's `STATUS`_ board: a
HAT sized add-on board with five sets of red/green LEDs and buttons to
provide a status indicator with additional input.
The following example designates the first strip the label "wifi" and the
second "raining", turns the wifi green and then activates the button to
toggle its lights when pressed::
from gpiozero import StatusBoard
status = StatusBoard('wifi', 'raining')
status.wifi.lights.green.on()
status.wifi.button.when_pressed = status.wifi.lights.toggle
:param str \*labels:
Specify the names of the labels you wish to designate the strips to.
You can list up to three labels. If no labels are given, three strips
will be initialised with names 'one' to 'five'. If some, but not all
strips are given labels, any remaining strips will not be initialised.
.. _STATUS: https://thepihut.com/status
"""
default_labels = ('one', 'two', 'three', 'four', 'five')
def __init__(self, *labels, **kwargs):
pins = (
(17, 4, 14),
(22, 27, 19),
(9, 10, 15),
(5, 11, 26),
(13, 6, 18),
)
if len(labels) == 0:
labels = self.default_labels
elif len(labels) > len(pins):
raise ValueError("StatusBoard doesn't support more than five labels")
dup, count = Counter(labels).most_common(1)[0]
if count > 1:
raise ValueError("Duplicate label %s" % dup)
super(StatusBoard, self).__init__(_order=labels, **{
label: CompositeOutputDevice(
button=Button(button),
lights=LEDBoard(
red=red, green=green, _order=('red', 'green'), **kwargs
), _order=('button', 'lights'))
for (green, red, button), label in zip(pins, labels)
})
class SnowPi(LEDBoard): class SnowPi(LEDBoard):
""" """
Extends :class:`LEDBoard` for the `Ryanteck SnowPi`_ board. Extends :class:`LEDBoard` for the `Ryanteck SnowPi`_ board.
@@ -1184,4 +1280,3 @@ class Energenie(SourceMixin, Device):
def off(self): def off(self):
self.value = False self.value = False

View File

@@ -315,23 +315,28 @@ class CompositeDevice(Device):
self._named = frozendict({}) self._named = frozendict({})
self._namedtuple = None self._namedtuple = None
self._order = kwargs.pop('_order', None) self._order = kwargs.pop('_order', None)
if self._order is None: try:
self._order = sorted(kwargs.keys()) if self._order is None:
else: self._order = sorted(kwargs.keys())
for missing_name in set(kwargs.keys()) - set(self._order): else:
raise CompositeDeviceBadOrder('%s missing from _order' % missing_name) for missing_name in set(kwargs.keys()) - set(self._order):
self._order = tuple(self._order) raise CompositeDeviceBadOrder('%s missing from _order' % missing_name)
super(CompositeDevice, self).__init__() self._order = tuple(self._order)
for name in set(self._order) & set(dir(self)): for name in set(self._order) & set(dir(self)):
raise CompositeDeviceBadName('%s is a reserved name' % name) raise CompositeDeviceBadName('%s is a reserved name' % name)
for dev in chain(args, kwargs.values()):
if not isinstance(dev, Device):
raise CompositeDeviceBadDevice("%s doesn't inherit from Device" % dev)
self._named = frozendict(kwargs)
self._namedtuple = namedtuple('%sValue' % self.__class__.__name__, chain(
('device_%d' % i for i in range(len(args))), self._order))
except:
for dev in chain(args, kwargs.values()):
if isinstance(dev, Device):
dev.close()
raise
self._all = args + tuple(kwargs[v] for v in self._order) self._all = args + tuple(kwargs[v] for v in self._order)
for dev in self._all: super(CompositeDevice, self).__init__()
if not isinstance(dev, Device):
raise CompositeDeviceBadDevice("%s doesn't inherit from Device" % dev)
self._named = frozendict(kwargs)
self._namedtuple = namedtuple('%sValue' % self.__class__.__name__, chain(
(str(i) for i in range(len(args))), self._order),
rename=True)
def __getattr__(self, name): def __getattr__(self, name):
# if _named doesn't exist yet, pretend it's an empty dict # if _named doesn't exist yet, pretend it's an empty dict
@@ -377,6 +382,7 @@ class CompositeDevice(Device):
if self._all: if self._all:
for device in self._all: for device in self._all:
device.close() device.close()
self._all = ()
@property @property
def closed(self): def closed(self):

View File

@@ -30,6 +30,8 @@ def setup_function(function):
'test_led_board_fade_background', 'test_led_board_fade_background',
'test_led_bar_graph_pwm_value', 'test_led_bar_graph_pwm_value',
'test_led_bar_graph_pwm_initial_value', 'test_led_bar_graph_pwm_initial_value',
'test_statusboard_kwargs',
'test_statuszero_kwargs',
) else MockPin ) else MockPin
def teardown_function(function): def teardown_function(function):
@@ -778,3 +780,113 @@ def test_energenie():
pins[5].assert_states_and_times([(0.0, False), (0.1, True), (0.25, False)]) pins[5].assert_states_and_times([(0.0, False), (0.1, True), (0.25, False)])
device1.close() device1.close()
assert repr(device1) == '<gpiozero.Energenie object closed>' assert repr(device1) == '<gpiozero.Energenie object closed>'
def test_statuszero_init():
with StatusZero() as sz:
assert sz.namedtuple._fields == ('one', 'two', 'three')
with StatusZero('a') as sz:
assert sz.namedtuple._fields == ('a',)
with StatusZero('a', 'b') as sz:
assert sz.namedtuple._fields == ('a', 'b')
with StatusZero('a', 'b', 'c') as sz:
assert sz.namedtuple._fields == ('a', 'b', 'c')
with pytest.raises(ValueError):
StatusZero('a', 'b', 'c', 'd')
with pytest.raises(ValueError):
StatusZero('0')
with pytest.raises(ValueError):
StatusZero('foo', 'hello world')
with pytest.raises(ValueError):
StatusZero('foo', 'foo')
def test_statuszero():
with StatusZero() as sz:
assert isinstance(sz.one, LEDBoard)
assert isinstance(sz.two, LEDBoard)
assert isinstance(sz.three, LEDBoard)
assert isinstance(sz.one.red, LED)
assert isinstance(sz.one.green, LED)
assert sz.value == ((False, False), (False, False), (False, False))
sz.on()
assert sz.value == ((True, True), (True, True), (True, True))
sz.one.green.off()
assert sz.one.value == (True, False)
def test_statuszero_kwargs():
with StatusZero(pwm=True, initial_value=True) as sz:
assert isinstance(sz.one, LEDBoard)
assert isinstance(sz.two, LEDBoard)
assert isinstance(sz.three, LEDBoard)
assert isinstance(sz.one.red, PWMLED)
assert isinstance(sz.one.green, PWMLED)
assert sz.value == ((1, 1), (1, 1), (1, 1))
sz.off()
assert sz.value == ((0, 0), (0, 0), (0, 0))
def test_statuszero_named():
with StatusZero('a') as sz:
assert isinstance(sz.a, LEDBoard)
assert isinstance(sz.a.red, LED)
with pytest.raises(AttributeError):
sz.one
def test_statusboard_init():
with StatusBoard() as sb:
assert sb.namedtuple._fields == ('one', 'two', 'three', 'four', 'five')
with StatusBoard('a') as sb:
assert sb.namedtuple._fields == ('a',)
with StatusBoard('a', 'b') as sb:
assert sb.namedtuple._fields == ('a', 'b',)
with StatusBoard('a', 'b', 'c', 'd', 'e') as sb:
assert sb.namedtuple._fields == ('a', 'b', 'c', 'd', 'e')
with pytest.raises(ValueError):
StatusBoard('a', 'b', 'c', 'd', 'e', 'f')
with pytest.raises(ValueError):
StatusBoard('0')
with pytest.raises(ValueError):
StatusBoard('foo', 'hello world')
with pytest.raises(ValueError):
StatusBoard('foo', 'foo')
def test_statusboard():
with StatusBoard() as sb:
assert isinstance(sb.one, CompositeOutputDevice)
assert isinstance(sb.two, CompositeOutputDevice)
assert isinstance(sb.five, CompositeOutputDevice)
assert isinstance(sb.one.button, Button)
assert isinstance(sb.one.lights, LEDBoard)
assert isinstance(sb.one.lights.red, LED)
assert isinstance(sb.one.lights.green, LED)
assert sb.value == ((False, (False, False)), (False, (False, False)),
(False, (False, False)), (False, (False, False)),
(False, (False, False)))
sb.on()
assert sb.value == ((False, (True, True)), (False, (True, True)),
(False, (True, True)), (False, (True, True)),
(False, (True, True)))
sb.one.lights.green.off()
assert sb.one.value == (False, (True, False))
def test_statusboard_kwargs():
with StatusBoard(pwm=True, initial_value=True) as sb:
assert isinstance(sb.one, CompositeOutputDevice)
assert isinstance(sb.two, CompositeOutputDevice)
assert isinstance(sb.five, CompositeOutputDevice)
assert isinstance(sb.one.button, Button)
assert isinstance(sb.one.lights, LEDBoard)
assert isinstance(sb.one.lights.red, PWMLED)
assert isinstance(sb.one.lights.green, PWMLED)
assert sb.value == ((False, (1, 1)), (False, (1, 1)), (False, (1, 1)),
(False, (1, 1)), (False, (1, 1)))
sb.off()
assert sb.value == ((False, (0, 0)), (False, (0, 0)), (False, (0, 0)),
(False, (0, 0)), (False, (0, 0)))
def test_statusboard_named():
with StatusBoard('a') as sb:
assert isinstance(sb.a, CompositeOutputDevice)
assert isinstance(sb.a.button, Button)
assert isinstance(sb.a.lights, LEDBoard)
assert isinstance(sb.a.lights.red, LED)
with pytest.raises(AttributeError):
sb.one

View File

@@ -91,7 +91,7 @@ def test_composite_device_sequence():
assert len(device) == 2 assert len(device) == 2
assert device[0].pin.number == 4 assert device[0].pin.number == 4
assert device[1].pin.number == 5 assert device[1].pin.number == 5
assert device.namedtuple._fields == ('_0', '_1') assert device.namedtuple._fields == ('device_0', 'device_1')
def test_composite_device_values(): def test_composite_device_values():
with CompositeDevice( with CompositeDevice(