Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,6 @@ tags

# MKDocs build
site/

# Micropico
.micropico
15 changes: 15 additions & 0 deletions docs/examples/rgb_brightness.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from picozero import RGBLED
from time import sleep

rgb = RGBLED(red=1, green=2, blue=3)

# start with a mixed colour so brightness changes are obvious
rgb.color = (255, 128, 0) # orange

while True:
rgb.brightness = 0.2 # dim
sleep(1)
rgb.brightness = 0.6 # medium
sleep(1)
rgb.brightness = 1.0 # full
sleep(1)
4 changes: 2 additions & 2 deletions docs/examples/rgb_led.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from picozero import RGBLED
from time import sleep

rgb = RGBLED(red=2, green=1, blue=0)
rgb = RGBLED(red=1, green=2, blue=3)

rgb.red = 255 # full red
sleep(1)
rgb.red = 128 # half red
sleep(1)

rgb.on() # white
rgb.on() # white

rgb.color = (0, 255, 0) # full green
sleep(1)
Expand Down
14 changes: 14 additions & 0 deletions docs/recipes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,13 @@ Use :meth:`~picozero.RGBLED.toggle` and :meth:`~picozero.RGBLED.invert`:

.. literalinclude:: examples/rgb_toggle_invert.py

Brightness
~~~~~~~~~~

Adjust overall brightness while keeping the same colour:

.. literalinclude:: examples/rgb_brightness.py

Blink
~~~~~

Expand Down Expand Up @@ -244,6 +251,13 @@ Play individual notes and control the timing or perform another action:

.. literalinclude:: examples/speaker_notes.py

Play MIDI notes
~~~~~~~~~~~~~~~

Step through a few MIDI notes with custom durations:

.. literalinclude:: examples/speaker_midi_notes.py

Servo
-----

Expand Down
33 changes: 32 additions & 1 deletion picozero/picozero.py
Original file line number Diff line number Diff line change
Expand Up @@ -1094,6 +1094,9 @@ class RGBLED(OutputDevice, PinsMixin):
If :data:`True` (the default), construct :class:`PWMLED` instances for
each component of the RGBLED. If :data:`False`, construct
:class:`DigitalLED` instances.
:param float brightness:
The overall brightness of the LED as a value between 0.0 and 1.0.
Defaults to 1.0 (full brightness). This scales all color values proportionally.

"""

Expand All @@ -1105,10 +1108,14 @@ def __init__(
active_high=True,
initial_value=(0, 0, 0),
pwm=True,
brightness=1.0,
):
self._pin_nums = (red, green, blue)
self._leds = ()
self._last = initial_value
self._brightness = max(
0.0, min(1.0, float(brightness))
) # clamp between 0 and 1
LEDClass = PWMLED if pwm else DigitalLED
self._leds = tuple(
LEDClass(pin, active_high=active_high) for pin in (red, green, blue)
Expand All @@ -1118,7 +1125,9 @@ def __init__(
def _write(self, value):
if type(value) is not tuple:
value = (value,) * 3
for led, v in zip(self._leds, value):
# apply brightness scaling
scaled_value = tuple(v * self._brightness for v in value)
for led, v in zip(self._leds, scaled_value):
led.value = v

@property
Expand Down Expand Up @@ -1207,6 +1216,28 @@ def blue(self, value):
r, g, b = self.value
self.value = r, g, self._from_255(value)

@property
def brightness(self):
"""
Represents the overall brightness of the LED as a value between 0 and 1.
Setting brightness scales all color components proportionally.
"""
return self._brightness

@brightness.setter
def brightness(self, value):
# clamp value between 0 and 1
value = max(0.0, min(1.0, float(value)))
# get current unscaled color values
if self._brightness > 0:
# recover original color by dividing by current brightness
current_color = tuple(v / self._brightness for v in self.value)
else:
current_color = self.value
self._brightness = value
# reapply the color which will use new brightness
self.value = current_color

def on(self):
"""
Turns the LED on. This is equivalent to setting the LED color to white, e.g.
Expand Down
113 changes: 113 additions & 0 deletions tests/test_picozero.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,119 @@ def test_rgb_led_alt_values(self):

d.close()

def test_rgb_led_brightness_default(self):
d = RGBLED(1, 2, 3)

# default brightness should be 1.0
self.assertEqual(d.brightness, 1.0)

# setting color should not be affected by default brightness
d.value = (0.5, 0.5, 0.5)
self.assertAlmostEqual(d.value[0], 0.5, places=2)
self.assertAlmostEqual(d.value[1], 0.5, places=2)
self.assertAlmostEqual(d.value[2], 0.5, places=2)

d.close()

def test_rgb_led_brightness_init(self):
# test brightness parameter in constructor
d = RGBLED(1, 2, 3, brightness=0.5)

self.assertEqual(d.brightness, 0.5)

# set color to full white
d.value = (1, 1, 1)

# actual LED values should be scaled by brightness
self.assertAlmostEqual(d.value[0], 0.5, places=2)
self.assertAlmostEqual(d.value[1], 0.5, places=2)
self.assertAlmostEqual(d.value[2], 0.5, places=2)

d.close()

def test_rgb_led_brightness_scaling(self):
d = RGBLED(1, 2, 3, brightness=0.5)

# set color to red
d.color = (255, 0, 0)

# check that values are scaled by brightness
self.assertAlmostEqual(d.value[0], 0.5, places=2)
self.assertAlmostEqual(d.value[1], 0.0, places=2)
self.assertAlmostEqual(d.value[2], 0.0, places=2)

# set partial color
d.color = (128, 64, 32)

# values should be scaled proportionally
self.assertAlmostEqual(d.value[0], 0.25, places=2) # 128/255 * 0.5
self.assertAlmostEqual(d.value[1], 0.125, places=2) # 64/255 * 0.5
self.assertAlmostEqual(d.value[2], 0.0625, places=2) # 32/255 * 0.5

d.close()

def test_rgb_led_brightness_change(self):
d = RGBLED(1, 2, 3)

# set color first
d.value = (1, 0.5, 0.25)

# change brightness
d.brightness = 0.5

# color ratios should be preserved
self.assertAlmostEqual(d.value[0], 0.5, places=2)
self.assertAlmostEqual(d.value[1], 0.25, places=2)
self.assertAlmostEqual(d.value[2], 0.125, places=2)

# increase brightness
d.brightness = 0.8

self.assertAlmostEqual(d.value[0], 0.8, places=2)
self.assertAlmostEqual(d.value[1], 0.4, places=2)
self.assertAlmostEqual(d.value[2], 0.2, places=2)

d.close()

def test_rgb_led_brightness_clamping(self):
# test that brightness is clamped to 0-1 range
d = RGBLED(1, 2, 3, brightness=1.5)
self.assertEqual(d.brightness, 1.0)
d.close()

d = RGBLED(1, 2, 3, brightness=-0.5)
self.assertEqual(d.brightness, 0.0)

# test dynamic brightness clamping
d.brightness = 2.0
self.assertEqual(d.brightness, 1.0)

d.brightness = -1.0
self.assertEqual(d.brightness, 0.0)

d.close()

def test_rgb_led_brightness_zero(self):
d = RGBLED(1, 2, 3, brightness=0)

# set color
d.value = (1, 1, 1)

# all values should be 0 due to brightness
self.assertEqual(d.value[0], 0)
self.assertEqual(d.value[1], 0)
self.assertEqual(d.value[2], 0)

# changing brightness from zero should work
d.brightness = 0.5
d.value = (1, 0.5, 0.25)

self.assertAlmostEqual(d.value[0], 0.5, places=2)
self.assertAlmostEqual(d.value[1], 0.25, places=2)
self.assertAlmostEqual(d.value[2], 0.125, places=2)

d.close()

def test_servo_default_value(self):
d = Servo(1)

Expand Down