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

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 

10 

11from __future__ import ( 

12 unicode_literals, 

13 print_function, 

14 absolute_import, 

15 division, 

16 ) 

17nstr = str 

18str = type('') 

19 

20import os 

21import atexit 

22import weakref 

23import warnings 

24from collections import namedtuple, OrderedDict 

25from itertools import chain 

26from types import FunctionType 

27 

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 

46 

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) 

52 

53 

54class GPIOMeta(type): 

55 # NOTE Yes, this is a metaclass. Don't be scared - it's a simple one. 

56 

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 

71 

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 

117 

118 

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) 

133 

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() 

156 

157 def close(self): 

158 """ 

159 Shut down the device and release all associated resources (such as GPIO 

160 pins). 

161 

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. 

166 

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. 

173 

174 For example, if you have a breadboard with a buzzer connected to pin 

175 16, but then wish to attach an LED instead: 

176 

177 >>> from gpiozero import * 

178 >>> bz = Buzzer(16) 

179 >>> bz.on() 

180 >>> bz.off() 

181 >>> bz.close() 

182 >>> led = LED(16) 

183 >>> led.blink() 

184 

185 :class:`Device` descendents can also be used as context managers using 

186 the :keyword:`with` statement. For example: 

187 

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 

203 

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 

212 

213 def _check_open(self): 

214 if self.closed: 

215 raise DeviceClosed( 

216 '%s is closed or uninitialized' % self.__class__.__name__) 

217 

218 def __enter__(self): 

219 return self 

220 

221 def __exit__(self, exc_type, exc_value, exc_tb): 

222 self.close() 

223 

224 

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). 

231 

232 .. attribute:: pin_factory 

233 

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). 

238 

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 

244 

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__() 

258 

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) 

311 

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__) 

318 

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 

328 

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 

338 

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) 

347 

348 

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. 

354 

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. 

359 

360 For example: 

361 

362 .. code-block:: pycon 

363 

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) 

376 

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. 

381 

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. 

389 

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). 

394 

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) 

433 

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) 

442 

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) 

448 

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__() 

473 

474 def __len__(self): 

475 return len(self._all) 

476 

477 def __getitem__(self, index): 

478 return self._all[index] 

479 

480 def __iter__(self): 

481 return iter(self._all) 

482 

483 @property 

484 def all(self): 

485 # XXX Deprecate this in favour of using the instance as a container 

486 return self._all 

487 

488 def close(self): 

489 if getattr(self, '_all', None): 

490 for device in self._all: 

491 device.close() 

492 self._all = () 

493 

494 @property 

495 def closed(self): 

496 return all(device.closed for device in self) 

497 

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 

506 

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)) 

516 

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) 

524 

525 

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`). 

531 

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 

553 

554 def _state_to_value(self, state): 

555 return int(state == self._active_state) 

556 

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 

563 

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 

570 

571 @property 

572 def closed(self): 

573 try: 

574 return self._pin is None 

575 except AttributeError: 

576 return True 

577 

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)) 

584 

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 

595 

596 @property 

597 def value(self): 

598 return self._read() 

599 

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__ 

606 

607 

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 

622 

623 

624def _shutdown(): 

625 _threads_shutdown() 

626 _devices_shutdown() 

627 

628atexit.register(_shutdown)