Coverage for /usr/lib/python3/dist-packages/gpiozero/pins/pi.py: 50%
132 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-05 16:40 +0100
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-05 16:40 +0100
1# vim: set fileencoding=utf-8:
2#
3# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
4#
5# Copyright (c) 2016-2021 Dave Jones <dave@waveform.org.uk>
6#
7# SPDX-License-Identifier: BSD-3-Clause
9from __future__ import (
10 unicode_literals,
11 absolute_import,
12 print_function,
13 division,
14 )
15str = type('')
17import io
18from threading import RLock, Lock
19from types import MethodType
20from collections import defaultdict
21try:
22 from weakref import ref, WeakMethod
23except ImportError:
25 from ..compat import WeakMethod
26import warnings
28try:
29 from spidev import SpiDev
30except ImportError:
31 SpiDev = None
33from . import Factory, Pin
34from .data import PiBoardInfo
35from ..exc import (
36 PinNoPins,
37 PinNonPhysical,
38 PinInvalidPin,
39 SPIBadArgs,
40 SPISoftwareFallback,
41 )
44SPI_HARDWARE_PINS = {
45 0: {
46 'clock': 11,
47 'mosi': 10,
48 'miso': 9,
49 'select': (8, 7),
50 },
51}
54def spi_port_device(clock_pin, mosi_pin, miso_pin, select_pin):
55 """
56 Convert a mapping of pin definitions, which must contain 'clock_pin', and
57 'select_pin' at a minimum, to a hardware SPI port, device tuple. Raises
58 :exc:`~gpiozero.SPIBadArgs` if the pins do not represent a valid hardware
59 SPI device.
60 """
61 for port, pins in SPI_HARDWARE_PINS.items():
62 if all((
63 clock_pin == pins['clock'],
64 mosi_pin in (None, pins['mosi']),
65 miso_pin in (None, pins['miso']),
66 select_pin in pins['select'],
67 )):
68 device = pins['select'].index(select_pin)
69 return (port, device)
70 raise SPIBadArgs('invalid pin selection for hardware SPI')
73class PiFactory(Factory):
74 """
75 Extends :class:`~gpiozero.Factory`. Abstract base class representing
76 hardware attached to a Raspberry Pi. This forms the base of
77 :class:`~gpiozero.pins.local.LocalPiFactory`.
78 """
79 def __init__(self):
80 super(PiFactory, self).__init__()
81 self._info = None
82 self.pins = {}
83 self.pin_class = None
85 def close(self):
86 for pin in self.pins.values():
87 pin.close()
88 self.pins.clear()
90 def reserve_pins(self, requester, *pins):
91 super(PiFactory, self).reserve_pins(
92 requester, *(self.pi_info.to_gpio(pin) for pin in pins))
94 def release_pins(self, reserver, *pins):
95 super(PiFactory, self).release_pins(
96 reserver, *(self.pi_info.to_gpio(pin) for pin in pins))
98 def pin(self, spec):
99 n = self.pi_info.to_gpio(spec)
100 try:
101 pin = self.pins[n]
102 except KeyError:
103 pin = self.pin_class(self, n)
104 self.pins[n] = pin
105 return pin
107 def _get_revision(self):
108 """
109 This method must be overridden by descendents to return the Pi's
110 revision code as an :class:`int`. The default is unimplemented.
111 """
112 raise NotImplementedError
114 def _get_pi_info(self):
115 if self._info is None:
116 self._info = PiBoardInfo.from_revision(self._get_revision())
117 return self._info
119 def spi(self, **spi_args):
120 """
121 Returns an SPI interface, for the specified SPI *port* and *device*, or
122 for the specified pins (*clock_pin*, *mosi_pin*, *miso_pin*, and
123 *select_pin*). Only one of the schemes can be used; attempting to mix
124 *port* and *device* with pin numbers will raise
125 :exc:`~gpiozero.SPIBadArgs`.
127 If the pins specified match the hardware SPI pins (clock on GPIO11,
128 MOSI on GPIO10, MISO on GPIO9, and chip select on GPIO8 or GPIO7), and
129 the spidev module can be imported, a hardware based interface (using
130 spidev) will be returned. Otherwise, a software based interface will be
131 returned which will use simple bit-banging to communicate.
133 Both interfaces have the same API, support clock polarity and phase
134 attributes, and can handle half and full duplex communications, but the
135 hardware interface is significantly faster (though for many simpler
136 devices this doesn't matter).
137 """
138 spi_args, kwargs = self._extract_spi_args(**spi_args)
139 shared = bool(kwargs.pop('shared', False))
140 if kwargs:
141 raise SPIBadArgs(
142 'unrecognized keyword argument %s' % kwargs.popitem()[0])
143 try:
144 port, device = spi_port_device(**spi_args)
145 except SPIBadArgs:
146 # Assume request is for a software SPI implementation
147 pass
148 else:
149 try:
150 return self._get_spi_class(shared, hardware=True)(
151 pin_factory=self, **spi_args)
152 except Exception as e:
153 warnings.warn(
154 SPISoftwareFallback(
155 'failed to initialize hardware SPI, falling back to '
156 'software (error was: %s)' % str(e)))
157 return self._get_spi_class(shared, hardware=False)(
158 pin_factory=self, **spi_args)
160 def _extract_spi_args(self, **kwargs):
161 """
162 Given a set of keyword arguments, splits it into those relevant to SPI
163 implementations and all the rest. SPI arguments are augmented with
164 defaults and converted into the pin format (from the port/device
165 format) if necessary.
167 Returns a tuple of ``(spi_args, other_args)``.
168 """
169 dev_defaults = {
170 'port': 0,
171 'device': 0,
172 }
173 default_hw = SPI_HARDWARE_PINS[dev_defaults['port']]
174 pin_defaults = {
175 'clock_pin': default_hw['clock'],
176 'mosi_pin': default_hw['mosi'],
177 'miso_pin': default_hw['miso'],
178 'select_pin': default_hw['select'][dev_defaults['device']],
179 }
180 spi_args = {
181 key: value for (key, value) in kwargs.items()
182 if key in pin_defaults or key in dev_defaults
183 }
184 kwargs = {
185 key: value for (key, value) in kwargs.items()
186 if key not in spi_args
187 }
188 if not spi_args:
189 spi_args = pin_defaults
190 elif set(spi_args) <= set(pin_defaults):
191 spi_args = {
192 key: None if spi_args.get(key, default) is None else
193 self.pi_info.to_gpio(spi_args.get(key, default))
194 for key, default in pin_defaults.items()
195 }
196 elif set(spi_args) <= set(dev_defaults):
197 spi_args = {
198 key: spi_args.get(key, default)
199 for key, default in dev_defaults.items()
200 }
201 try:
202 selected_hw = SPI_HARDWARE_PINS[spi_args['port']]
203 except KeyError:
204 raise SPIBadArgs(
205 'port %d is not a valid SPI port' % spi_args['port'])
206 try:
207 selected_hw['select'][spi_args['device']]
208 except IndexError:
209 raise SPIBadArgs(
210 'device must be in the range 0..%d' %
211 len(selected_hw['select']))
212 spi_args = {
213 key: value if key != 'select_pin' else selected_hw['select'][spi_args['device']]
214 for key, value in pin_defaults.items()
215 }
216 else:
217 raise SPIBadArgs(
218 'you must either specify port and device, or clock_pin, '
219 'mosi_pin, miso_pin, and select_pin; combinations of the two '
220 'schemes (e.g. port and clock_pin) are not permitted')
221 return spi_args, kwargs
223 def _get_spi_class(self, shared, hardware):
224 """
225 Returns a sub-class of the :class:`SPI` which can be constructed with
226 *clock_pin*, *mosi_pin*, *miso_pin*, and *select_pin* arguments. The
227 *shared* argument dictates whether the returned class uses the
228 :class:`SharedMixin` to permit sharing instances between components,
229 while *hardware* indicates whether the returned class uses the kernel's
230 SPI device(s) rather than a bit-banged software implementation.
231 """
232 raise NotImplementedError
235class PiPin(Pin):
236 """
237 Extends :class:`~gpiozero.Pin`. Abstract base class representing a
238 multi-function GPIO pin attached to a Raspberry Pi. Descendents *must*
239 override the following methods:
241 * :meth:`_get_function`
242 * :meth:`_set_function`
243 * :meth:`_get_state`
244 * :meth:`_call_when_changed`
245 * :meth:`_enable_event_detect`
246 * :meth:`_disable_event_detect`
248 Descendents *may* additionally override the following methods, if
249 applicable:
251 * :meth:`close`
252 * :meth:`output_with_state`
253 * :meth:`input_with_pull`
254 * :meth:`_set_state`
255 * :meth:`_get_frequency`
256 * :meth:`_set_frequency`
257 * :meth:`_get_pull`
258 * :meth:`_set_pull`
259 * :meth:`_get_bounce`
260 * :meth:`_set_bounce`
261 * :meth:`_get_edges`
262 * :meth:`_set_edges`
263 """
264 def __init__(self, factory, number):
265 super(PiPin, self).__init__()
266 self._factory = factory
267 self._when_changed_lock = RLock()
268 self._when_changed = None
269 self._number = number
270 try:
271 factory.pi_info.physical_pin(repr(self))
272 except PinNoPins:
273 warnings.warn(
274 PinNonPhysical(
275 'no physical pins exist for %s' % repr(self)))
277 @property
278 def number(self):
279 return self._number
281 def __repr__(self):
282 return 'GPIO%d' % self._number
284 @property
285 def factory(self):
286 return self._factory
288 def _call_when_changed(self, ticks, state):
289 """
290 Called to fire the :attr:`when_changed` event handler; override this
291 in descendents if additional (currently redundant) parameters need
292 to be passed.
293 """
294 method = self._when_changed()
295 if method is None:
296 self.when_changed = None
297 else:
298 method(ticks, state)
300 def _get_when_changed(self):
301 return None if self._when_changed is None else self._when_changed()
303 def _set_when_changed(self, value):
304 with self._when_changed_lock:
305 if value is None:
306 if self._when_changed is not None:
307 self._disable_event_detect()
308 self._when_changed = None
309 else:
310 enabled = self._when_changed is not None
311 # Have to take care, if value is either a closure or a bound
312 # method, not to keep a strong reference to the containing
313 # object
314 if isinstance(value, MethodType): 314 ↛ 317line 314 didn't jump to line 317, because the condition on line 314 was never false
315 self._when_changed = WeakMethod(value)
316 else:
317 self._when_changed = ref(value)
318 if not enabled: 318 ↛ exitline 318 didn't return from function '_set_when_changed', because the condition on line 318 was never false
319 self._enable_event_detect()
321 def _enable_event_detect(self):
322 """
323 Enables event detection. This is called to activate event detection on
324 pin :attr:`number`, watching for the specified :attr:`edges`. In
325 response, :meth:`_call_when_changed` should be executed.
326 """
327 raise NotImplementedError
329 def _disable_event_detect(self):
330 """
331 Disables event detection. This is called to deactivate event detection
332 on pin :attr:`number`.
333 """
334 raise NotImplementedError