"""Module that partly fakes `RPi.GPIO`_ and simulates some I/O devices.
It simulates these I/O devices connected to a Raspberry Pi:
- push buttons by listening to pressed keyboard keys and
- LEDs by displaying small circles blinking on the terminal along with \
their GPIO pin number.
When a LED is turned on, it is shown as a small red circle on the terminal. The
package `pynput`_ is used to monitor the keyboard for any pressed key.
..
TODO: also found in README_docs
.. highlight:: none
**Example: terminal output** ::
o [11] o [9] o [10]
.. highlight:: python
where each circle represents a LED (here they are all turned off) and the number
between brackets is the associated GPIO pin number.
.. important::
This library is not a Raspberry Pi emulator nor a complete mock-up of
`RPi.GPIO`_, only the most important functions that I needed for my
`Darth-Vader-RPi project`_ were added.
If there is enough interest in this library, I will eventually mock more
functions from `RPi.GPIO`_. Thus,
`let me know through SimulRPi's issues page`_ if you want me to add more
things to this library.
..
TODO: also found in README_docs.rst
.. _Darth-Vader-RPi project: https://github.com/raul23/Darth-Vader-RPi
.. _let me know through SimulRPi's issues page:
https://github.com/raul23/SimulRPi/issues
.. _pynput: https://pynput.readthedocs.io/en/latest/index.html
.. _RPi.GPIO: https://pypi.org/project/RPi.GPIO/
.. _RPi.GPIO wiki: https://sourceforge.net/p/raspberry-gpio-python/wiki/BasicUsage/
"""
import copy
import logging
import os
import threading
from logging import NullHandler
try:
from pynput import keyboard
except ImportError:
print("`pynput` couldn't be found. Thus, no keyboard keys will be detected "
"if pressed or released.\nIf you need this option, install pynput "
"with: pip install pynput.\n")
keyboard = None
from SimulRPi.mapping import default_key_to_channel_map
logger = logging.getLogger(__name__)
logger.addHandler(NullHandler())
RPI_INFO = {'P1_REVISION': 1}
RPI_REVISION = 1 # Deprecated
VERSION = 1
BOARD = 0
BCM = 1
HIGH = 1
LOW = 0
IN = 0
OUT = 1
PUD_UP = 1
PUD_DOWN = 0
MODES = {'BOARD': BOARD, 'BCM': BCM}
[docs]class Pin:
"""Class that represents a GPIO pin.
Parameters
----------
channel : int
GPIO channel number based on the numbering system you have specified
(`BOARD` or `BCM`).
gpio_function : int
Function of a GPIO channel: 1 (`GPIO.INPUT`) or 0 (`GPIO.OUTPUT`).
key : str or None, optional
Key associated with the GPIO channel, e.g. "k".
pull_up_down : int or None, optional
Initial value of an input channel, e.g. `GPIO.PUP_UP`. Default value is
:obj:`None`.
initial : int or None, optional
Initial value of an output channel, e.g. `GPIO.HIGH`. Default value is
:obj:`None`.
Attributes
----------
state : int
State of the GPIO channel: 1 (`HIGH`) or 0 (`LOW`).
"""
def __init__(self, channel, gpio_function, key=None, pull_up_down=None,
initial=None):
self.channel = channel
self.gpio_function = gpio_function
self.key = key
self.pull_up_down = pull_up_down
self.initial = initial
if gpio_function == IN:
self.state = HIGH
else:
self.state = LOW
[docs]class PinDB:
"""Class for storing and modifying :class:`Pin`\s.
Each instance of :class:`GPIO.Pin` is saved in a dictionary that maps it
to its channel number.
.. note::
The dictionary (a "database" of :class:`Pin`\s) must be accessed
through the different methods available in :class:`PinDB`, e.g.
:meth:`get_pin_from_channel`.
"""
def __init__(self):
self._pins = {}
# The other dict is only for INPUT channels with an associated key
# TODO: explain more
self._key_to_pin_map = {}
[docs] def create_pin(self, channel, gpio_function, key=None, pull_up_down=None,
initial=None):
"""Create an instance of :class:`GPIO.Pin` and save it in a dictionary.
Based on the given arguments, an instance of :class:`GPIO.Pin` is
created and added to a dictionary that acts like a database of pins
with key being the pin's channel and the value is an instance of
:class:`Pin`.
Parameters
----------
channel : int
GPIO channel number based on the numbering system you have
specified (`BOARD` or `BCM`).
gpio_function : int
Function of a GPIO channel: 1 (`GPIO.INPUT`) or 0 (`GPIO.OUTPUT`).
key : str or None, optional
Key associated with the GPIO channel, e.g. "k".
pull_up_down : int or None, optional
Initial value of an input channel, e.g. `GPIO.PUP_UP`. Default
value is :obj:`None`.
initial : int or None, optional
Initial value of an output channel, e.g. `GPIO.HIGH`. Default value
is :obj:`None`.
"""
self._pins[channel] = Pin(channel, gpio_function, key, pull_up_down,
initial)
# Update the other internal dict if key is given
if key:
# Input
# TODO: assert on gpop_function which should be INPUT?
self._key_to_pin_map[key] = self._pins[channel]
[docs] def get_pin_from_channel(self, channel):
"""Get a :class:`Pin` from a given channel.
Parameters
----------
channel : int
GPIO channel number associated with the :class:`Pin` to be
retrieved.
Returns
-------
Pin : :class:`GPIO.Pin` or :obj:`None`
If no :class:`Pin` could be retrieved based on the given channel,
:obj:`None` is returned. Otherwise, a :class:`Pin` object is
returned.
"""
return self._pins.get(channel)
[docs] def get_pin_from_key(self, key):
"""Get a :class:`Pin` from a given pressed/released key.
Parameters
----------
key : str
The pressed/released key that is associated with the :class:`Pin`
to be retrieved.
Returns
-------
Pin : :class:`GPIO.Pin` or :obj:`None`
If no :class:`Pin` could be retrieved based on the given key,
:obj:`None` is returned. Otherwise, a :class:`Pin` object is
returned.
"""
return self._key_to_pin_map.get(key)
[docs] def get_pin_state(self, channel):
"""Get a :class:`Pin`\'s state from a given channel.
The state associated with a :class:`Pin` can either be 1 (`HIGH`) or 0
(`LOW`).
Parameters
----------
channel : int
GPIO channel number associated with the :class:`Pin` whose state is
to be returned.
Returns
-------
state : int or :obj:`None`
If no :class:`Pin` could be retrieved based on the given channel
number, then :obj:`None` is returned. Otherwise, the
:class:`Pin`\'s state is returned: 1 (`HIGH`) or 0 (`LOW`).
"""
pin = self._pins.get(channel)
if pin:
return pin.state
else:
return None
[docs] def set_pin_key_from_channel(self, channel, key):
"""Set a :class:`Pin`\'s key from a given channel.
A :class:`Pin` is retrieved based on a given channel, then its
:attr:`key` is set with `key`.
Parameters
----------
channel : int
GPIO channel number associated with the :class:`Pin` whose key will
be set.
key : str
The new key that a :class:`Pin` will be updated with.
Returns
-------
retval : bool
Returns `True` if the :class:`Pin` was successfully set with `key`.
Otherwise, it returns `False`.
"""
pin = self.get_pin_from_channel(channel)
if pin:
old_key = pin.key
pin.key = key
# TODO: only update dict if the key is different from the actual
# pin's key but then return True or False if no update?
# if key != old_key:
del self._key_to_pin_map[old_key]
self._key_to_pin_map[key] = pin
return True
else:
return False
[docs] def set_pin_state_from_channel(self, channel, state):
"""Set a :class:`Pin`\'s state from a given channel.
A :class:`Pin` is retrieved based on a given channel, then its
:attr:`state` is set with `state`.
Parameters
----------
channel : int
GPIO channel number associated with the :class:`Pin` whose state
will be set.
state : int
State the GPIO channel should take: 1 (`HIGH`) or 0 (`LOW`).
Returns
-------
retval : bool
Returns `True` if the :class:`Pin` was successfully set with
`state`. Otherwise, it returns `False` because the pin doesn't
exist based on the given `channel`.
"""
pin = self.get_pin_from_channel(channel)
if pin:
# TODO: only update state if the state is different from the actual
pin.state = state
return True
else:
return False
[docs] def set_pin_state_from_key(self, key, state):
"""Set a :class:`Pin`\'s state from a given key.
A :class:`Pin` is retrieved based on a given key, then its
:attr:`state` is set with `state`.
Parameters
----------
key : str
The key associated with the :class:`Pin` whose state will be set.
state : int
State the GPIO channel should take: 1 (`HIGH`) or 0 (`LOW`).
Returns
-------
retval : bool
Returns `True` if the :class:`Pin` was successfully set with
`state`. Otherwise, it returns `False` because the pin doesn't
exist based on the given `key`.
"""
pin = self.get_pin_from_key(key)
if pin:
# TODO: only update state if the state is different from the actual
pin.state = state
return True
else:
return False
[docs]class Manager:
"""Class that manages the pin database (:class:`PinDB`) and the threads
responsible for displaying "LEDs" on the terminal and listening for keys
pressed/released.
The threads are not started right away in :meth:`__init__` but in
:meth:`input` for the listener thread and :meth:`output` for the
displaying thread.
They are eventually stopped in :meth:`cleanup`.
Attributes
----------
mode : int
Numbering system used to identify the I/O pins on an RPi: `BOARD` or
`BCM`. Default value is :obj:`None`.
warnings : bool
Whether to show warnings when using a pin other than the default GPIO
function (input). Default value is `True`.
enable_printing : bool
Whether to enable printing on the terminal. Default value is `True`.
pin_db : PinDB
A :class:`Pin` database. See :class:`PinDB` on how to access it.
key_to_channel_map : dict
A dictionary that maps keyboard keys (:obj:`string`) to GPIO channel
numbers (:obj:`int`). By default, it takes the keys and values defined
in :mod:`SimulRPi.mapping`'s keymap ``default_key_to_channel_map``.
channel_to_key_map : dict
The reverse dictionary of :attr:`key_to_channel_map`. It maps channels
to keys.
nb_prints : int
Number of times the displaying thread :attr:`th_display_leds` has
printed blinking circles on the terminal. It is used when debugging the
displaying thread.
th_display_leds : threading.Thread
Thread responsible for displaying small blinking circles on the
terminal as to simulate LEDs connected to an RPi.
th_listener : keyboard.Listener
Thread responsible for listening on any pressed or released key as to
simulate push buttons connected to an RPi.
.. note::
A keyboard listener is a :class:`threading.Thread`, and all
callbacks will be invoked from the thread.
**Ref.:** https://pynput.readthedocs.io/en/latest/keyboard.html#monitoring-the-keyboard
.. important::
If the module :mod:`pynput.keyboard` couldn't be imported, the
listener thread :attr:`th_listener` will not be created and the parts
of the ``SimulRPi`` library that monitors the keyboard for any pressed
or released key will be ignored. Only the thread
:attr:`th_display_leds` that displays "LEDs" on the terminal will be
created.
This is necessary for example in the case we are running tests on
travis and we don't want travis to install ``pynput`` in a headless
setup because an exception will get raised::
Xlib.error.DisplayNameError: Bad display name ""
The tests involving ``pynput`` will be performed with a mock version of
``pynput``.
"""
def __init__(self):
self.mode = None
self.warnings = True
self.enable_printing = True
self.pin_db = PinDB()
self.key_to_channel_map = copy.copy(default_key_to_channel_map)
self.channel_to_key_map = {v: k for k, v in
self.key_to_channel_map.items()}
self._output_channels = []
self.nb_prints = 0
self._leds = None
self.th_display_leds = threading.Thread(name="thread_display_leds",
target=self.display_leds,
args=())
if keyboard:
self.th_listener = keyboard.Listener(
on_press=self.on_press,
on_release=self.on_release)
self.th_listener.name = "thread_listener"
else:
self.th_listener = None
[docs] def add_pin(self, channel, gpio_function, pull_up_down=None, initial=None):
"""Add an input or output pin to the pin database.
An instance of :class:`Pin` is created with the given arguments and
added to the pin database :class:`PinDB`.
Parameters
----------
channel : int
GPIO channel number associated with the :class:`Pin` to be added in
the pin database.
gpio_function : int
Function of a GPIO channel: 1 (`GPIO.INPUT`) or 0 (`GPIO.OUTPUT`).
pull_up_down : int or None, optional
Initial value of an input channel, e.g. `GPIO.PUP_UP`. Default
value is :obj:`None`.
initial : int or None, optional
Initial value of an output channel, e.g. `GPIO.HIGH`. Default value
is :obj:`None`.
"""
key = None
if gpio_function == IN:
# Get key associated with the INPUT pin (button)
# TODO: raise exception if key not found
key = self.channel_to_key_map.get(channel)
elif gpio_function == OUT:
# No key since it is an OUTPUT pin (e.g. LED)
# Save the channel so the thread that displays LEDs knows what
# channels are OUTPUT and therefore connected to LEDs.
self._output_channels.append(channel)
self.pin_db.create_pin(channel, gpio_function,
key=key,
pull_up_down=pull_up_down,
initial=initial)
[docs] def display_leds(self):
"""Simulate LEDs on an RPi by blinking small circles on a terminal.
In order to simulate LEDs turning on/off on an RPi, small circles are
blinked on the terminal along with their GPIO pin number.
When a LED is turned on, it is shown as a small red circle on the
terminal.
.. highlight:: none
**Example: terminal output** ::
o [11] o [9] o [10]
.. highlight:: python
where each circle represents a LED (here they are all turned off) and
the number between brackets is the associated GPIO pin number.
.. note::
If :attr:`enable_printing` is set to `True`, the terminal's cursor
will be hidden. It will be eventually shown again in :meth:`cleanup`
which is called by the main program when it is exiting.
The reason is to avoid messing with the display of LEDs by the
displaying thread :attr:`th_display_leds`.
.. important::
:meth:`display_leds` should be run by a thread and eventually
stopped from the main thread 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=self.display_leds, args=())
th.start()
# Your other code ...
# Time to stop thread
th.do_run = False
th.join()
"""
# TODO: explain order outputs are setup is how the channels are shown
if self.enable_printing:
# Hide the cursor
os.system("tput civis")
print()
th = threading.currentThread()
while getattr(th, "do_run", True):
self._leds = ""
last_msg_length = len(self._leds) if self._leds else 0
# for channel in sorted(self.channel_output_state_map):
for channel in self._output_channels:
pin = self.pin_db.get_pin_from_channel(channel)
# TODO: pin could be None
if pin.state == HIGH:
# led = "\033[31mo\033[0m"
led = '\033[1;31;48m' + 'o' + '\033[1;37;0m'
else:
led = 'o'
self._leds += led + ' [{}] '.format(channel)
if self.enable_printing:
# If no spaces after the red o's representing the LEDs, then if
# you press tab or down, it will mess up the display to the right
print(' ' * last_msg_length, end='\r')
# print(self._leds, end='\r')
# TODO: explain
print(' {}{}'.format(self._leds, " " * 40), end="\r")
# sys.stdout.flush()
self.nb_prints += 1
# TODO: explain
if self.enable_printing and self._leds:
print(' {}{}'.format(self._leds, " " * 40))
# logger.debug("Stopping thread: {}()".format(self.display_leds.__name__))
logger.debug("Stopping thread: {}".format(th.name))
[docs] @staticmethod
def get_key_name(key):
"""Get the name of a keyboard key as a string.
The name of the special or alphanumeric key is given by the package
`pynput`_.
Parameters
----------
key : pynput.keyboard.Key or pynput.keyboard.KeyCode
The keyboard key (from :mod:`pynput.keyboard`) whose name will be
returned.
Returns
-------
key_name : str or None
Returns the name of the given keyboard key if one was found by
`pynput`_. Otherwise, it returns :obj:`None`.
"""
# print(key)
if hasattr(key, 'char'):
# Alphanumeric key (keyboard.KeyCode)
if key.char == '\x05':
key_name = "insert"
elif key.char == '\x1b':
key_name = "num_lock"
else:
key_name = key.char
elif hasattr(key, 'name'):
# Special key (keyboard.Key)
key_name = key.name
else:
# Unknown key
key_name = None
return key_name
[docs] def on_press(self, key):
"""When a valid keyboard key is pressed, set its state to `GPIO.LOW`.
Callback invoked from the thread :attr:`GPIO.Manager.th_listener`.
This thread is used to monitor the keyboard for any valid pressed key.
Only keys defined in the pin database are treated, i.e. keys that were
configured with :meth:`GPIO.setup` are further processed.
Once a valid key is detected as pressed, its state is changed to
`GPIO.LOW`.
Parameters
----------
key : pynput.keyboard.Key, pynput.keyboard.KeyCode, or None
The key parameter passed to callbacks is
* a :class:`pynput.keyboard.Key` for special keys,
* a :class:`pynput.keyboard.KeyCode` for normal alphanumeric keys, or
* :obj:`None` for unknown keys.
**Ref.:** https://bit.ly/3k4whEs
"""
self.pin_db.set_pin_state_from_key(self.get_key_name(key), state=LOW)
[docs] def on_release(self, key):
"""When a valid keyboard key is released, set its state to `GPIO.HIGH`.
Callback invoked from the thread :attr:`GPIO.Manager.th_listener`.
This thread is used to monitor the keyboard for any valid released key.
Only keys defined in the pin database are treated, i.e. keys that were
configured with :meth:`GPIO.setup` are further processed.
Once a valid key is detected as released, its state is changed to
`GPIO.HIGH`.
Parameters
----------
key : pynput.keyboard.Key, pynput.keyboard.KeyCode, or None
The key parameter passed to callbacks is
* a :class:`pynput.keyboard.Key` for special keys,
* a :class:`pynput.keyboard.KeyCode` for normal alphanumeric keys, or
* :obj:`None` for unknown keys.
**Ref.:** https://bit.ly/3k4whEs
"""
self.pin_db.set_pin_state_from_key(self.get_key_name(key), state=HIGH)
[docs] def update_keymap(self, new_keymap):
"""Update the default dictionary mapping keys and GPIO channels.
``new_keymap`` is a dictionary mapping some keys to their new GPIO
channels, and will be used to update the default key-channel mapping
defined in :mod:`SimulRPi.mapping`.
Parameters
----------
new_keymap : dict
Dictionary that maps keys (:obj:`str`) to their new GPIO channels
(:obj:`int`).
**For example**::
"key_to_channel_map":
{
"f": 24,
"g": 25,
"h": 23
}
.. note::
If a key is associated to a channel that is already taken by
another key, both keys' channels will be swapped. However, if a key
is being linked to a :obj:`None` channel, then it will take on the
maximum channel number available + 1.
"""
# TODO: assert keys (str) and channels (int)
# TODO: test uniqueness in channel numbers of new map
assert len(set(new_keymap.values())) == len(new_keymap)
orig_keych = {}
for key1, new_ch in new_keymap.items():
old_ch = self.key_to_channel_map.get(key1)
# Case 1: if key's channel is not found, maybe it is a special key
# or an alphanumeric key not already in the keymap
# Validate the key before updating the keymaps
if old_ch is None and not self.validate_key(key1):
# Invalid key: the key is neither a special nor an alphanum key
orig_keych.setdefault(key1, None)
raise TypeError("The key '{}' is invalid: only special and "
"alphanumeric keys recognized by `pynput` are "
"accepted. \nSee the documentation for "
"`SimulRPi.mapping` @ https://bit.ly/3fIsd9o "
"for a list of accepted keys.".format(key1))
key2 = self.channel_to_key_map.get(new_ch)
if key2 is None:
# Case 2: the new channel is not associated with any key in the
# keymap. Thus, add the key with the new channel in the keymaps
orig_keych.setdefault(key1, None)
self._update_keymaps_and_pin_db(key_channels=[(key1, new_ch)])
continue
elif key1 == key2 and new_ch == old_ch:
# Case 3: No update necessary since the key with the given
# channel is already in the keymap.
continue
else:
# Case 4: Update the keymaps to reflect the key with the new
# given channel.
orig_keych.setdefault(key1, old_ch)
orig_keych.setdefault(key2, new_ch)
if old_ch is None:
# The new key is not found in the default keymap at all.
# Thus, its channel is None and must be set to an integer
# channel which is equal to the maximum channel available
# plus one. Hence, the second key whose channel is being
# swapped with this new key will not receive a None channel
# which would be problematic since there can be many keys
# with None channels and the keymap channel_to_key_map
# would only keep one key with the None channel.
old_ch = max(self.channel_to_key_map.keys()) + 1
self._update_keymaps_and_pin_db(
key_channels=[(key1, new_ch), (key2, old_ch)])
if orig_keych:
# There were updates and/or there are invalid keys
msg = "Update of Key-to-Channel Map:\n"
for key, old_ch in orig_keych.items():
new_ch = self.key_to_channel_map.get(key)
msg += 'Key "{}": Channel {} ------> Channel {}\n'.format(
key, old_ch, new_ch)
logger.info(msg)
[docs] @staticmethod
def validate_key(key):
"""Validate if a key is recognized by `pynput`_
A valid key can either be:
* a :class:`pynput.keyboard.Key` for special keys (e.g. ``tab`` or \
``up``), or
* a :class:`pynput.keyboard.KeyCode` for normal alphanumeric keys.
Parameters
----------
key : str
The key (e.g. '`tab`') that will be validated.
Returns
-------
retval : bool
Returns `True` if it's a valid key. Otherwise, it returns `False`.
References
----------
:mod:`pynput` reference: https://pynput.readthedocs.io/en/latest/keyboard.html#reference
See Also
--------
:mod:`SimulRPi.mapping` : for a list of special keys supported by
`pynput`_.
"""
if not hasattr(keyboard.Key, key) and \
not (len(key) == 1 and key.isalnum()):
# Unrecognized key
# Neither a special key nor an alphanum key
return False
else:
return True
def _update_keymaps_and_pin_db(self, key_channels):
"""Update the two internal keymaps and the pin database.
:obj:`key_channels` is a list of two-tuples where each tuple contains
the key and its new channel with which it needs to be updated.
The different internal data structures need to be updated to reflect
these changes:
* the two keymaps :obj:`key_to_channel_map` and \
:obj:`channel_to_key_map`
* the pin database who is an instance of :class:`PinDB`
Parameters
----------
key_channels : list of tuple
Where each tuple contains the key and its new channel with which it
needs to be updated.
**For example**::
key_channels = [('f', 25), ('g', 23)])
where the key *'f'* will be mapped to the GPIO channel 25 and the
key *'g'* to the GPIO channel 23.
"""
for keych in key_channels:
key = keych[0]
channel = keych[1]
self.key_to_channel_map[key] = channel
self.channel_to_key_map[channel] = key
self.pin_db.set_pin_key_from_channel(channel, key)
manager = Manager()
[docs]def cleanup():
"""Clean up any resources (e.g. GPIO channels).
At the end of any program, it is good practice to clean up any resources
you might have used. This is no different with `RPi.GPIO`_. By returning
all channels you have used back to inputs with no pull up/down, you can
avoid accidental damage to your RPi by shorting out the pins.
[**Ref:** `RPi.GPIO wiki`_]
Also, the two threads responsible for displaying "LEDs" on the terminal and
listening for pressed/released keys are stopped.
.. note::
On an RPi, :meth:`cleanup` will:
* only clean up GPIO channels that your script has used
* also clear the pin numbering system in use (`BOARD` or `BCM`)
**Ref.:** `RPi.GPIO wiki`_
When using the package ``SimulRPi``, :meth:`cleanup` will:
* stop the displaying thread :attr:`Manager.th_display_leds`
* stop the listener thread :attr:`Manager.th_listener`
* show the cursor again which was hidden in \
:meth:`Manager.display_leds`
* reset the :obj:`GPIO.manager`'s attributes (an instance of \
:class:`Manager`)
"""
global manager
# Show cursor again
os.system("tput cnorm ")
# Check if displaying thread is alive. If the user didn't setup any output
# channels for LEDs, then the displaying thread was never started
if manager.th_display_leds.is_alive():
manager.th_display_leds.do_run = False
manager.th_display_leds.join()
logger.debug("Thread stopped: {}".format(manager.th_display_leds.name))
# Check if listener thread is alive. If the user didn't setup any input
# channels for buttons, then the listener thread was never started
if manager.th_listener and manager.th_listener.is_alive():
logger.debug("Stopping thread: {}".format(manager.th_listener.name))
manager.th_listener.stop()
manager.th_listener.join()
logger.debug("Thread stopped: {}".format(manager.th_listener.name))
# Reset Manager's attributes
del manager
manager = Manager()
# TODO: output to several channels, see https://bit.ly/2Dgk2Uf
[docs]def output(channel, state):
"""Set the output state of a GPIO pin.
Parameters
----------
channel : int
Output GPIO channel number based on the numbering system you have
specified (`BOARD` or `BCM`).
state : int
State of the GPIO channel: 1 (`HIGH`) or 0 (`LOW`).
.. note::
The displaying thread (for showing "LEDs" on the terminal) is started
if it is not alive, i.e. it is not already running.
"""
manager.pin_db.set_pin_state_from_channel(channel, state)
# Start the displaying thread only if it not already alive
if not manager.th_display_leds.is_alive():
manager.th_display_leds.start()
[docs]def setkeymap(key_to_channel_map):
"""Set the keymap dictionary with new keys and channels.
The default dictionary :obj:`default_key_to_channel_map` (defined in
:mod:`SimulRPi.mapping`) that maps keyboard keys to GPIO channels can be
modified by providing your own mapping ``key_to_channel_map`` containing
only the keys and channels that you want to be modified.
Parameters
----------
key_to_channel_map : dict
A dictionary mapping keys (:obj:`str`) and GPIO channels (:obj:`int`)
that will be used to update the default keymap found in
:mod:`SimulRPi.mapping`.
**For example**::
key_to_channel_map:
{
"q": 23,
"w": 24,
"e": 25
}
"""
manager.update_keymap(key_to_channel_map)
[docs]def setmode(mode):
"""Set the numbering system used to identify the I/O pins on an RPi within
`RPi.GPIO`.
There are two ways of numbering the I/O pins on a Raspberry Pi within
`RPi.GPIO`:
1. The `BOARD` numbering system: refers to the pin numbers on the P1 header
of the Raspberry Pi board
2. The `BCM` numbers: refers to the channel numbers on the Broadcom SOC.
Parameters
----------
mode : int
Numbering system used to identify the I/O pins on an RPi: `BOARD` or
`BCM`.
References
----------
Function description and more info from `RPi.GPIO wiki`_.
"""
assert mode in MODES.values(), \
"Wrong mode: {}. Mode should be one these values: {}".format(
mode, list(MODES.values()))
manager.mode = mode
[docs]def setprinting(enable_printing):
"""Enable printing on the terminal.
If printing is enabled, small blinking red circles will be shown on the
terminal, simulating LEDs connected to a Raspberry Pi. Otherwise, nothing
will be printed on the terminal.
Parameters
----------
enable_printing : bool
Whether to enable printing on the terminal.
"""
# TODO: stop displaying thread too?
manager.enable_printing = enable_printing
# TODO: setup more than one channel, see https://bit.ly/2Dgk2Uf
[docs]def setup(channel, gpio_function, pull_up_down=None, initial=None):
"""Setup a GPIO channel as an input or output.
To configure a channel as an input::
GPIO.setup(channel, GPIO.IN)
To configure a channel as an output::
GPIO.setup(channel, GPIO.OUT)
You can also specify an initial value for your output channel::
GPIO.setup(channel, GPIO.OUT, initial=GPIO.HIGH)
Parameters
----------
channel : int
GPIO channel number based on the numbering system you have specified
(`BOARD` or `BCM`).
gpio_function : int
Function of a GPIO channel: 1 (`GPIO.INPUT`) or 0 (`GPIO.OUTPUT`).
pull_up_down : int or None, optional
Initial value of an input channel, e.g. `GPIO.PUP_UP`. Default value is
:obj:`None`.
initial : int or None, optional
Initial value of an output channel, e.g. `GPIO.HIGH`. Default value is
:obj:`None`.
References
----------
`RPi.GPIO wiki`_
"""
# TODO: assert on mode
manager.add_pin(channel, gpio_function, pull_up_down, initial)
[docs]def setwarnings(show_warnings):
"""Set warnings when configuring a GPIO pin other than the default
(input).
It is possible that you have more than one script/circuit on the GPIO of
your Raspberry Pi. As a result of this, if RPi.GPIO detects that a pin has
been configured to something other than the default (input), you get a
warning when you try to configure a script. [**Ref:** `RPi.GPIO wiki`_]
Parameters
----------
show_warnings : bool
Whether to show warnings when using a pin other than the default GPIO
function (input).
"""
manager.warnings = show_warnings