Coverage for /usr/lib/python3/dist-packages/gpiozero/devices.py: 42%
248 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) 2015-2021 Dave Jones <dave@waveform.org.uk>
6# Copyright (c) 2015-2019 Ben Nuttall <ben@bennuttall.com>
7# Copyright (c) 2016 Andrew Scheller <github@loowis.durge.org>
8#
9# SPDX-License-Identifier: BSD-3-Clause
11from __future__ import (
12 unicode_literals,
13 print_function,
14 absolute_import,
15 division,
16 )
17nstr = str
18str = type('')
20import os
21import atexit
22import weakref
23import warnings
24from collections import namedtuple, OrderedDict
25from itertools import chain
26from types import FunctionType
28from .threads import _threads_shutdown
29from .mixins import (
30 ValuesMixin,
31 SharedMixin,
32 )
33from .exc import (
34 BadPinFactory,
35 DeviceClosed,
36 CompositeDeviceBadName,
37 CompositeDeviceBadOrder,
38 CompositeDeviceBadDevice,
39 GPIOPinMissing,
40 GPIOPinInUse,
41 GPIODeviceClosed,
42 NativePinFactoryFallback,
43 PinFactoryFallback,
44 )
45from .compat import frozendict
47native_fallback_message = (
48'Falling back to the experimental pin factory NativeFactory because no other '
49'pin factory could be loaded. For best results, install RPi.GPIO or pigpio. '
50'See https://gpiozero.readthedocs.io/en/stable/api_pins.html for more information.'
51)
54class GPIOMeta(type):
55 # NOTE Yes, this is a metaclass. Don't be scared - it's a simple one.
57 def __new__(mcls, name, bases, cls_dict):
58 # Construct the class as normal
59 cls = super(GPIOMeta, mcls).__new__(mcls, name, bases, cls_dict)
60 # If there's a method in the class which has no docstring, search
61 # the base classes recursively for a docstring to copy
62 for attr_name, attr in cls_dict.items():
63 if isinstance(attr, FunctionType) and not attr.__doc__:
64 for base_cls in cls.__mro__:
65 if hasattr(base_cls, attr_name):
66 base_fn = getattr(base_cls, attr_name)
67 if base_fn.__doc__:
68 attr.__doc__ = base_fn.__doc__
69 break
70 return cls
72 def __call__(cls, *args, **kwargs):
73 # Make sure cls has GPIOBase somewhere in its ancestry (otherwise
74 # setting __attrs__ below will be rather pointless)
75 assert issubclass(cls, GPIOBase)
76 if issubclass(cls, SharedMixin): 76 ↛ 80line 76 didn't jump to line 80, because the condition on line 76 was never true
77 # If SharedMixin appears in the class' ancestry, convert the
78 # constructor arguments to a key and check whether an instance
79 # already exists. Only construct the instance if the key's new.
80 key = cls._shared_key(*args, **kwargs)
81 try:
82 self = cls._instances[key]()
83 self._refs += 1
84 except (KeyError, AttributeError) as e:
85 self = super(GPIOMeta, cls).__call__(*args, **kwargs)
86 self._refs = 1
87 # Replace the close method with one that merely decrements
88 # the refs counter and calls the original close method when
89 # it reaches zero
90 old_close = self.close
91 def close():
92 self._refs = max(0, self._refs - 1)
93 if not self._refs:
94 try:
95 old_close()
96 finally:
97 try:
98 del cls._instances[key]
99 except KeyError:
100 # If the _refs go negative (too many closes)
101 # just ignore the resulting KeyError here -
102 # it's already gone
103 pass
104 self.close = close
105 cls._instances[key] = weakref.ref(self)
106 else:
107 # Construct the instance as normal
108 self = super(GPIOMeta, cls).__call__(*args, **kwargs)
109 # At this point __new__ and __init__ have all been run. We now fix the
110 # set of attributes on the class by dir'ing the instance and creating a
111 # frozenset of the result called __attrs__ (which is queried by
112 # GPIOBase.__setattr__). An exception is made for SharedMixin devices
113 # which can be constructed multiple times, returning the same instance
114 if not issubclass(cls, SharedMixin) or self._refs == 1: 114 ↛ 116line 114 didn't jump to line 116, because the condition on line 114 was never false
115 self.__attrs__ = frozenset(dir(self))
116 return self
119# Cross-version compatible method of using a metaclass
120class GPIOBase(GPIOMeta(nstr('GPIOBase'), (), {})):
121 def __setattr__(self, name, value):
122 # This overridden __setattr__ simply ensures that additional attributes
123 # cannot be set on the class after construction (it manages this in
124 # conjunction with the meta-class above). Traditionally, this is
125 # managed with __slots__; however, this doesn't work with Python's
126 # multiple inheritance system which we need to use in order to avoid
127 # repeating the "source" and "values" property code in myriad places
128 if hasattr(self, '__attrs__') and name not in self.__attrs__: 128 ↛ 129line 128 didn't jump to line 129, because the condition on line 128 was never true
129 raise AttributeError(
130 "'%s' object has no attribute '%s'" % (
131 self.__class__.__name__, name))
132 return super(GPIOBase, self).__setattr__(name, value)
134 def __del__(self):
135 # NOTE: Yes, we implicitly call close() on __del__(), and yes for you
136 # dear hacker-on-this-library, this means pain!
137 #
138 # It's entirely for the convenience of command line experimenters and
139 # newbies who want to re-gain those pins when stuff falls out of scope
140 # without managing their object lifetimes "properly" with "with" (but,
141 # hey, this is an educational library at heart so that's the way we
142 # roll).
143 #
144 # What does this mean for you? It means that in close() you cannot
145 # assume *anything*. If someone calls a constructor with a fundamental
146 # mistake like the wrong number of params, then your close() method is
147 # going to be called before __init__ ever ran so all those attributes
148 # you *think* exist, erm, don't. Basically if you refer to anything in
149 # "self" within your close method, be preprared to catch AttributeError
150 # on its access to avoid spurious warnings for the end user.
151 #
152 # "But we're exiting anyway; surely exceptions in __del__ get
153 # squashed?" Yes, but they still cause verbose warnings and remember
154 # that this is an educational library; keep it friendly!
155 self.close()
157 def close(self):
158 """
159 Shut down the device and release all associated resources (such as GPIO
160 pins).
162 This method is idempotent (can be called on an already closed device
163 without any side-effects). It is primarily intended for interactive use
164 at the command line. It disables the device and releases its pin(s) for
165 use by another device.
167 You can attempt to do this simply by deleting an object, but unless
168 you've cleaned up all references to the object this may not work (even
169 if you've cleaned up all references, there's still no guarantee the
170 garbage collector will actually delete the object at that point). By
171 contrast, the close method provides a means of ensuring that the object
172 is shut down.
174 For example, if you have a breadboard with a buzzer connected to pin
175 16, but then wish to attach an LED instead:
177 >>> from gpiozero import *
178 >>> bz = Buzzer(16)
179 >>> bz.on()
180 >>> bz.off()
181 >>> bz.close()
182 >>> led = LED(16)
183 >>> led.blink()
185 :class:`Device` descendents can also be used as context managers using
186 the :keyword:`with` statement. For example:
188 >>> from gpiozero import *
189 >>> with Buzzer(16) as bz:
190 ... bz.on()
191 ...
192 >>> with LED(16) as led:
193 ... led.on()
194 ...
195 """
196 # This is a placeholder which is simply here to ensure close() can be
197 # safely called from subclasses without worrying whether super-classes
198 # have it (which in turn is useful in conjunction with the mixin
199 # classes).
200 #
201 # P.S. See note in __del__ above.
202 pass
204 @property
205 def closed(self):
206 """
207 Returns :data:`True` if the device is closed (see the :meth:`close`
208 method). Once a device is closed you can no longer use any other
209 methods or properties to control or query the device.
210 """
211 raise NotImplementedError
213 def _check_open(self):
214 if self.closed:
215 raise DeviceClosed(
216 '%s is closed or uninitialized' % self.__class__.__name__)
218 def __enter__(self):
219 return self
221 def __exit__(self, exc_type, exc_value, exc_tb):
222 self.close()
225class Device(ValuesMixin, GPIOBase):
226 """
227 Represents a single device of any type; GPIO-based, SPI-based, I2C-based,
228 etc. This is the base class of the device hierarchy. It defines the basic
229 services applicable to all devices (specifically the :attr:`is_active`
230 property, the :attr:`value` property, and the :meth:`close` method).
232 .. attribute:: pin_factory
234 This attribute exists at both a class level (representing the default
235 pin factory used to construct devices when no *pin_factory* parameter
236 is specified), and at an instance level (representing the pin factory
237 that the device was constructed with).
239 The pin factory provides various facilities to the device including
240 allocating pins, providing low level interfaces (e.g. SPI), and clock
241 facilities (querying and calculating elapsed times).
242 """
243 pin_factory = None # instance of a Factory sub-class
245 def __init__(self, **kwargs):
246 # Force pin_factory to be keyword-only, even in Python 2
247 pin_factory = kwargs.pop('pin_factory', None)
248 if pin_factory is None: 248 ↛ 253line 248 didn't jump to line 253, because the condition on line 248 was never false
249 if Device.pin_factory is None:
250 Device.pin_factory = Device._default_pin_factory()
251 self.pin_factory = Device.pin_factory
252 else:
253 self.pin_factory = pin_factory
254 if kwargs: 254 ↛ 255line 254 didn't jump to line 255, because the condition on line 254 was never true
255 raise TypeError("Device.__init__() got unexpected keyword "
256 "argument '%s'" % kwargs.popitem()[0])
257 super(Device, self).__init__()
259 @staticmethod
260 def _default_pin_factory():
261 # We prefer RPi.GPIO here as it supports PWM, and all Pi revisions. If
262 # no third-party libraries are available, however, we fall back to a
263 # pure Python implementation which supports platforms like PyPy
264 #
265 # NOTE: If the built-in pin factories are expanded, the dict must be
266 # updated along with the entry-points in setup.py.
267 default_factories = OrderedDict((
268 ('rpigpio', 'gpiozero.pins.rpigpio:RPiGPIOFactory'),
269 ('lgpio', 'gpiozero.pins.lgpio:LGPIOFactory'),
270 ('rpio', 'gpiozero.pins.rpio:RPIOFactory'),
271 ('pigpio', 'gpiozero.pins.pigpio:PiGPIOFactory'),
272 ('native', 'gpiozero.pins.native:NativeFactory'),
273 ))
274 name = os.environ.get('GPIOZERO_PIN_FACTORY')
275 if name is None: 275 ↛ 292line 275 didn't jump to line 292, because the condition on line 275 was never false
276 # If no factory is explicitly specified, try various names in
277 # "preferred" order. For speed, we select from the dictionary above
278 # rather than importing pkg_resources and using load_entry_point
279 for name, entry_point in default_factories.items(): 279 ↛ 291line 279 didn't jump to line 291, because the loop on line 279 didn't complete
280 try:
281 mod_name, cls_name = entry_point.split(':', 1)
282 module = __import__(mod_name, fromlist=(cls_name,))
283 pin_factory = getattr(module, cls_name)()
284 if name == 'native': 284 ↛ 285line 284 didn't jump to line 285, because the condition on line 284 was never true
285 warnings.warn(NativePinFactoryFallback(native_fallback_message))
286 return pin_factory
287 except Exception as e:
288 warnings.warn(
289 PinFactoryFallback(
290 'Falling back from %s: %s' % (name, str(e))))
291 raise BadPinFactory('Unable to load any default pin factory!')
292 elif name in default_factories:
293 # As above, this is a fast-path optimization to avoid loading
294 # pkg_resources (which it turns out was 80% of gpiozero's import
295 # time!)
296 mod_name, cls_name = default_factories[name].split(':', 1)
297 module = __import__(mod_name, fromlist=(cls_name,))
298 return getattr(module, cls_name)()
299 else:
300 # Slow path: load pkg_resources and try and find the specified
301 # entry-point. Try with the name verbatim first. If that fails,
302 # attempt with the lower-cased name (this ensures compatibility
303 # names work but we're still case insensitive for all factories)
304 import pkg_resources
305 group = 'gpiozero_pin_factories'
306 for factory in pkg_resources.iter_entry_points(group, name):
307 return factory.load()()
308 for factory in pkg_resources.iter_entry_points(group, name.lower()):
309 return factory.load()()
310 raise BadPinFactory('Unable to find pin factory "%s"' % name)
312 def __repr__(self):
313 try:
314 self._check_open()
315 return "<gpiozero.%s object>" % (self.__class__.__name__)
316 except DeviceClosed:
317 return "<gpiozero.%s object closed>" % (self.__class__.__name__)
319 def _conflicts_with(self, other):
320 """
321 Called by :meth:`Factory.reserve_pins` to test whether the *other*
322 :class:`Device` using a common pin conflicts with this device's intent
323 to use it. The default is :data:`True` indicating that all devices
324 conflict with common pins. Sub-classes may override this to permit
325 more nuanced replies.
326 """
327 return True
329 @property
330 def value(self):
331 """
332 Returns a value representing the device's state. Frequently, this is a
333 boolean value, or a number between 0 and 1 but some devices use larger
334 ranges (e.g. -1 to +1) and composite devices usually use tuples to
335 return the states of all their subordinate components.
336 """
337 raise NotImplementedError
339 @property
340 def is_active(self):
341 """
342 Returns :data:`True` if the device is currently active and
343 :data:`False` otherwise. This property is usually derived from
344 :attr:`value`. Unlike :attr:`value`, this is *always* a boolean.
345 """
346 return bool(self.value)
349class CompositeDevice(Device):
350 """
351 Extends :class:`Device`. Represents a device composed of multiple devices
352 like simple HATs, H-bridge motor controllers, robots composed of multiple
353 motors, etc.
355 The constructor accepts subordinate devices as positional or keyword
356 arguments. Positional arguments form unnamed devices accessed by treating
357 the composite device as a container, while keyword arguments are added to
358 the device as named (read-only) attributes.
360 For example:
362 .. code-block:: pycon
364 >>> from gpiozero import *
365 >>> d = CompositeDevice(LED(2), LED(3), LED(4), btn=Button(17))
366 >>> d[0]
367 <gpiozero.LED object on pin GPIO2, active_high=True, is_active=False>
368 >>> d[1]
369 <gpiozero.LED object on pin GPIO3, active_high=True, is_active=False>
370 >>> d[2]
371 <gpiozero.LED object on pin GPIO4, active_high=True, is_active=False>
372 >>> d.btn
373 <gpiozero.Button object on pin GPIO17, pull_up=True, is_active=False>
374 >>> d.value
375 CompositeDeviceValue(device_0=False, device_1=False, device_2=False, btn=False)
377 :param Device \\*args:
378 The un-named devices that belong to the composite device. The
379 :attr:`value` attributes of these devices will be represented within
380 the composite device's tuple :attr:`value` in the order specified here.
382 :type _order: list or None
383 :param _order:
384 If specified, this is the order of named items specified by keyword
385 arguments (to ensure that the :attr:`value` tuple is constructed with a
386 specific order). All keyword arguments *must* be included in the
387 collection. If omitted, an alphabetically sorted order will be selected
388 for keyword arguments.
390 :type pin_factory: Factory or None
391 :param pin_factory:
392 See :doc:`api_pins` for more information (this is an advanced feature
393 which most users can ignore).
395 :param Device \\*\\*kwargs:
396 The named devices that belong to the composite device. These devices
397 will be accessible as named attributes on the resulting device, and
398 their :attr:`value` attributes will be accessible as named elements of
399 the composite device's tuple :attr:`value`.
400 """
401 def __init__(self, *args, **kwargs):
402 self._all = ()
403 self._named = frozendict({})
404 self._namedtuple = None
405 self._order = kwargs.pop('_order', None)
406 pin_factory = kwargs.pop('pin_factory', None)
407 try:
408 if self._order is None:
409 self._order = sorted(kwargs.keys())
410 else:
411 for missing_name in set(kwargs.keys()) - set(self._order):
412 raise CompositeDeviceBadOrder(
413 '%s missing from _order' % missing_name)
414 self._order = tuple(self._order)
415 for name in set(self._order) & set(dir(self)):
416 raise CompositeDeviceBadName(
417 '%s is a reserved name' % name)
418 for dev in chain(args, kwargs.values()):
419 if not isinstance(dev, Device):
420 raise CompositeDeviceBadDevice(
421 "%s doesn't inherit from Device" % dev)
422 self._named = frozendict(kwargs)
423 self._namedtuple = namedtuple(
424 '%sValue' % self.__class__.__name__, chain(
425 ('device_%d' % i for i in range(len(args))), self._order))
426 except:
427 for dev in chain(args, kwargs.values()):
428 if isinstance(dev, Device):
429 dev.close()
430 raise
431 self._all = args + tuple(kwargs[v] for v in self._order)
432 super(CompositeDevice, self).__init__(pin_factory=pin_factory)
434 def __getattr__(self, name):
435 # if _named doesn't exist yet, pretend it's an empty dict
436 if name == '_named':
437 return frozendict({})
438 try:
439 return self._named[name]
440 except KeyError:
441 raise AttributeError("no such attribute %s" % name)
443 def __setattr__(self, name, value):
444 # make named components read-only properties
445 if name in self._named:
446 raise AttributeError("can't set attribute %s" % name)
447 return super(CompositeDevice, self).__setattr__(name, value)
449 def __repr__(self):
450 try:
451 self._check_open()
452 named = len(self._named)
453 unnamed = len(self) - len(self._named)
454 if named > 0 and unnamed > 0:
455 return "<gpiozero.%s object containing %d devices: %s and %d unnamed>" % (
456 self.__class__.__name__,
457 len(self), ', '.join(self._order),
458 len(self) - len(self._named)
459 )
460 elif named > 0:
461 return "<gpiozero.%s object containing %d devices: %s>" % (
462 self.__class__.__name__,
463 len(self),
464 ', '.join(self._order)
465 )
466 else:
467 return "<gpiozero.%s object containing %d unnamed devices>" % (
468 self.__class__.__name__,
469 len(self)
470 )
471 except DeviceClosed:
472 return super(CompositeDevice, self).__repr__()
474 def __len__(self):
475 return len(self._all)
477 def __getitem__(self, index):
478 return self._all[index]
480 def __iter__(self):
481 return iter(self._all)
483 @property
484 def all(self):
485 # XXX Deprecate this in favour of using the instance as a container
486 return self._all
488 def close(self):
489 if getattr(self, '_all', None):
490 for device in self._all:
491 device.close()
492 self._all = ()
494 @property
495 def closed(self):
496 return all(device.closed for device in self)
498 @property
499 def namedtuple(self):
500 """
501 The :func:`~collections.namedtuple` type constructed to represent the
502 value of the composite device. The :attr:`value` attribute returns
503 values of this type.
504 """
505 return self._namedtuple
507 @property
508 def value(self):
509 """
510 A :func:`~collections.namedtuple` containing a value for each
511 subordinate device. Devices with names will be represented as named
512 elements. Unnamed devices will have a unique name generated for them,
513 and they will appear in the position they appeared in the constructor.
514 """
515 return self.namedtuple(*(device.value for device in self))
517 @property
518 def is_active(self):
519 """
520 Composite devices are considered "active" if any of their constituent
521 devices have a "truthy" value.
522 """
523 return any(self.value)
526class GPIODevice(Device):
527 """
528 Extends :class:`Device`. Represents a generic GPIO device and provides
529 the services common to all single-pin GPIO devices (like ensuring two
530 GPIO devices do no share a :attr:`pin`).
532 :type pin: int or str
533 :param pin:
534 The GPIO pin that the device is connected to. See :ref:`pin-numbering`
535 for valid pin numbers. If this is :data:`None` a :exc:`GPIODeviceError`
536 will be raised. If the pin is already in use by another device,
537 :exc:`GPIOPinInUse` will be raised.
538 """
539 def __init__(self, pin=None, **kwargs):
540 super(GPIODevice, self).__init__(**kwargs)
541 # self._pin must be set before any possible exceptions can be raised
542 # because it's accessed in __del__. However, it mustn't be given the
543 # value of pin until we've verified that it isn't already allocated
544 self._pin = None
545 if pin is None: 545 ↛ 546line 545 didn't jump to line 546, because the condition on line 545 was never true
546 raise GPIOPinMissing('No pin given')
547 # Check you can reserve *before* constructing the pin
548 self.pin_factory.reserve_pins(self, pin)
549 pin = self.pin_factory.pin(pin)
550 self._pin = pin
551 self._active_state = True
552 self._inactive_state = False
554 def _state_to_value(self, state):
555 return int(state == self._active_state)
557 def _read(self):
558 try:
559 return self._state_to_value(self.pin.state)
560 except (AttributeError, TypeError):
561 self._check_open()
562 raise
564 def close(self):
565 super(GPIODevice, self).close()
566 if getattr(self, '_pin', None) is not None:
567 self.pin_factory.release_pins(self, self._pin.number)
568 self._pin.close()
569 self._pin = None
571 @property
572 def closed(self):
573 try:
574 return self._pin is None
575 except AttributeError:
576 return True
578 def _check_open(self):
579 try:
580 super(GPIODevice, self)._check_open()
581 except DeviceClosed as e:
582 # For backwards compatibility; GPIODeviceClosed is deprecated
583 raise GPIODeviceClosed(str(e))
585 @property
586 def pin(self):
587 """
588 The :class:`Pin` that the device is connected to. This will be
589 :data:`None` if the device has been closed (see the
590 :meth:`~Device.close` method). When dealing with GPIO pins, query
591 ``pin.number`` to discover the GPIO pin (in BCM numbering) that the
592 device is connected to.
593 """
594 return self._pin
596 @property
597 def value(self):
598 return self._read()
600 def __repr__(self):
601 try:
602 return "<gpiozero.%s object on pin %r, is_active=%s>" % (
603 self.__class__.__name__, self.pin, self.is_active)
604 except DeviceClosed:
605 return "<gpiozero.%s object closed>" % self.__class__.__name__
608def _devices_shutdown():
609 if Device.pin_factory is not None:
610 with Device.pin_factory._res_lock:
611 reserved_devices = {
612 dev
613 for ref_list in Device.pin_factory._reservations.values()
614 for ref in ref_list
615 for dev in (ref(),)
616 if dev is not None
617 }
618 for dev in reserved_devices:
619 dev.close()
620 Device.pin_factory.close()
621 Device.pin_factory = None
624def _shutdown():
625 _threads_shutdown()
626 _devices_shutdown()
628atexit.register(_shutdown)