"""Collection of LEDs-related utilities for the *Darth-Vader-RPi* project.
"""
import logging
import threading
import time
from logging import NullHandler
from darth_vader_rpi.utils import add_spaces_to_msg
from darth_vader_rpi.slot_leds_sequences import ACTION, CALM
try:
import RPi.GPIO as GPIO
except ImportError:
import SimulRPi.GPIO as GPIO
logger = logging.getLogger(__name__)
logger.addHandler(NullHandler())
_DEFAULT_SEQ = {'action': ACTION, 'calm': CALM}
[docs]def turn_off_led(channel):
"""Turn off a LED from a given channel.
Parameters
----------
channel : int
Channel number associated with a LED which will be turned off.
"""
# logger.debug("LED {} off".format(led))
GPIO.output(channel, GPIO.LOW)
[docs]def turn_on_led(channel):
"""Turn on a LED from a given channel.
Parameters
----------
channel : int
Channel number associated with a LED which will be turned on.
"""
# logger.debug("LED {} on".format(led))
GPIO.output(channel, GPIO.HIGH)
[docs]def turn_on_slot_leds(top_led, middle_led, bottom_led, leds_sequence="action",
delay_between_steps=0.5, time_per_step=0.5):
"""A thread's **target function** that turn on/off the three slot LEDs in a
precise sequence.
These three LEDs are associated with Darth Vader's three slots located on
his chest control box. These LEDs are labeled as '`top`', '`middle`', and
'`bottom`', respectively.
The three LEDs are turned on according to a default or custom sequence
which repeats itself. The accepted values for ``leds_sequence`` are
'`action`' and '`calm`' which represent Darth Vader's physiological state
as a sequence of LEDs blinking in a particular order.
The user can also provide its own ``leds_sequence`` by using a list of LED
labels {'`top`', '`midddle`', '`bottom`'} arranged in a sequence specifying
the order the slot LEDs should turn on/off, e.g. ``[['top', 'bottom'], [],
['middle'], []]`` will turn on/off the slot LEDs in this order::
1. top + bottom LEDs turned on
2. All LEDs turned off
3. middle LED turned on
4. All LEDs turned off
Each step in the sequence will last for ``time_per_step`` seconds.
There will be a delay of ``delay_between_steps`` seconds between
each step in the previous example.
The default sequences of slot LEDs were obtained from this
`YouTube video`_.
Parameters
----------
top_led : int
Channel number associated with the `Top` slot LED.
middle_led : int
Channel number associated with the `Middle` slot LED.
bottom_led : int
Channel number associated with the `Bottom` slot LED.
leds_sequence : str or list, optional
Sequence of slot LEDs on Darth Vader's chest box.
If ``leds_sequence`` is a string, then it takes on one of these values
which represent Darth Vader's physiological state: {'*action*',
'*calm*'}.
If ``leds_sequence`` is a list, then it must be a list of slot LED
labels {'`top`', '`middle`', '`bottom`'} arranged in a sequence as to
specify the order the slot LEDs should turn on/off, e.g.
``[['top', 'bottom'], [], ['middle'], []]`` will turn on/off the slot
LEDs in this order::
1. top + bottom LEDs turn on
2. All LEDs turn off
3. middle LED turn on
4. All LEDs turn off
delay_between_steps : float, optional
Delay in seconds between each step in the sequence. The default value
is 0.5 second.
time_per_step : float, optional
Time in seconds each step in the sequence will last. The default value
is 0.5 second.
.. important::
This also affects the time all LEDs will remain turned off if a
step in ``leds_sequence`` is an empty list.
.. important::
:meth:`turn_on_slot_leds` should be run by a thread and eventually
stopped from the main program by setting its ``do_run`` attribute to
`False` to let the thread exit from its target function.
**For example**:
.. code-block:: python
th = threading.Thread(target=turn_on_slot_leds,
args=(leds_channels))
th.start()
# Your other code ...
# Time to stop thread
th.do_run = False
th.join()
"""
# LED labels to Channel numbers Mapping (LCM)
lcm = dict((('top', top_led), ('middle', middle_led), ('bottom', bottom_led)))
if isinstance(leds_sequence, str):
leds_sequence = leds_sequence.lower()
assert leds_sequence in _DEFAULT_SEQ.keys(), \
"Wrong type of leds_sequence: '{}' (choose from {})".format(
leds_sequence, ", ".join(_DEFAULT_SEQ.keys()))
leds_sequence = _DEFAULT_SEQ[leds_sequence]
else:
assert isinstance(leds_sequence, list), \
"leds_sequence should be a string ({}) or a list: '{}'".format(
", ".join(_DEFAULT_SEQ.keys()), leds_sequence)
th = threading.currentThread()
subseq_idx = 0
# TODO: use SimulRPi API to get LEDs states
leds_states = dict(zip(lcm.keys(), [GPIO.LOW]*len(lcm)))
while getattr(th, "do_run", True):
leds_subsequence = leds_sequence[subseq_idx % len(leds_sequence)]
subseq_idx += 1
for channel_label, channel in lcm.items():
cur_state = leds_states[channel_label]
if channel_label in leds_subsequence:
if cur_state != GPIO.HIGH:
leds_states[channel_label] = GPIO.HIGH
turn_on_led(channel)
else:
if cur_state != GPIO.LOW:
leds_states[channel_label] = GPIO.LOW
turn_off_led(channel)
time.sleep(time_per_step+delay_between_steps)
logger.debug(add_spaces_to_msg("Stopping thread: {}".format(th.name)))