Coverage for /usr/lib/python3/dist-packages/gpiozero/pins/pigpio.py: 22%
321 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-02-10 12:38 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-02-10 12:38 +0000
1# vim: set fileencoding=utf-8:
2#
3# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
4#
5# Copyright (c) 2021 Kyle Morgan <kyle@knmorgan.net>
6# Copyright (c) 2016-2021 Dave Jones <dave@waveform.org.uk>
7# Copyright (c) 2020 Ben Nuttall <ben@bennuttall.com>
8# Copyright (c) 2019 Maksim Levental <maksim.levental@gmail.com>
9# Copyright (c) 2019 Aaron Rogers <aaron.kyle.rogers@gmail.com>
10# Copyright (c) 2016 BuildTools <david.glaude@gmail.com>
11# Copyright (c) 2016 Andrew Scheller <github@loowis.durge.org>
12#
13# SPDX-License-Identifier: BSD-3-Clause
15from __future__ import (
16 unicode_literals,
17 absolute_import,
18 print_function,
19 division,
20 )
21str = type('')
23import os
25import pigpio
27from . import SPI
28from .pi import PiPin, PiFactory, spi_port_device
29from ..mixins import SharedMixin
30from ..exc import (
31 PinInvalidFunction,
32 PinSetInput,
33 PinFixedPull,
34 PinInvalidPull,
35 PinInvalidBounce,
36 PinInvalidState,
37 SPIBadArgs,
38 SPIInvalidClockMode,
39 PinPWMFixedValue,
40 DeviceClosed
41)
44class PiGPIOFactory(PiFactory):
45 """
46 Extends :class:`~gpiozero.pins.pi.PiFactory`. Uses the `pigpio`_ library to
47 interface to the Pi's GPIO pins. The pigpio library relies on a daemon
48 (:command:`pigpiod`) to be running as root to provide access to the GPIO
49 pins, and communicates with this daemon over a network socket.
51 While this does mean only the daemon itself should control the pins, the
52 architecture does have several advantages:
54 * Pins can be remote controlled from another machine (the other
55 machine doesn't even have to be a Raspberry Pi; it simply needs the
56 `pigpio`_ client library installed on it)
57 * The daemon supports hardware PWM via the DMA controller
58 * Your script itself doesn't require root privileges; it just needs to
59 be able to communicate with the daemon
61 You can construct pigpio pins manually like so::
63 from gpiozero.pins.pigpio import PiGPIOFactory
64 from gpiozero import LED
66 factory = PiGPIOFactory()
67 led = LED(12, pin_factory=factory)
69 This is particularly useful for controlling pins on a remote machine. To
70 accomplish this simply specify the host (and optionally port) when
71 constructing the pin::
73 from gpiozero.pins.pigpio import PiGPIOFactory
74 from gpiozero import LED
76 factory = PiGPIOFactory(host='192.168.0.2')
77 led = LED(12, pin_factory=factory)
79 .. note::
81 In some circumstances, especially when playing with PWM, it does appear
82 to be possible to get the daemon into "unusual" states. We would be
83 most interested to hear any bug reports relating to this (it may be a
84 bug in our pin implementation). A workaround for now is simply to
85 restart the :command:`pigpiod` daemon.
87 .. _pigpio: http://abyz.me.uk/rpi/pigpio/
88 """
89 def __init__(self, host=None, port=None):
90 super(PiGPIOFactory, self).__init__()
91 if host is None:
92 host = os.environ.get('PIGPIO_ADDR', 'localhost')
93 if port is None:
94 # XXX Use getservbyname
95 port = int(os.environ.get('PIGPIO_PORT', 8888))
96 self.pin_class = PiGPIOPin
97 self._connection = pigpio.pi(host, port)
98 # Annoyingly, pigpio doesn't raise an exception when it fails to make a
99 # connection; it returns a valid (but disconnected) pi object
100 if self.connection is None:
101 raise IOError('failed to connect to %s:%s' % (host, port))
102 self._host = host
103 self._port = port
104 self._spis = []
106 def close(self):
107 super(PiGPIOFactory, self).close()
108 # We *have* to keep track of SPI interfaces constructed with pigpio;
109 # if we fail to close them they prevent future interfaces from using
110 # the same pins
111 if self.connection:
112 while self._spis:
113 self._spis[0].close()
114 self.connection.stop()
115 self._connection = None
117 @property
118 def connection(self):
119 # If we're shutting down, the connection may have disconnected itself
120 # already. Unfortunately, the connection's "connected" property is
121 # rather buggy - disconnecting doesn't set it to False! So we're
122 # naughty and check an internal variable instead...
123 try:
124 if self._connection.sl.s is not None:
125 return self._connection
126 except AttributeError:
127 pass
129 @property
130 def host(self):
131 return self._host
133 @property
134 def port(self):
135 return self._port
137 def _get_revision(self):
138 return self.connection.get_hardware_revision()
140 def _get_spi_class(self, shared, hardware):
141 return {
142 (False, True): PiGPIOHardwareSPI,
143 (True, True): PiGPIOHardwareSPIShared,
144 (False, False): PiGPIOSoftwareSPI,
145 (True, False): PiGPIOSoftwareSPIShared,
146 }[shared, hardware]
148 def spi(self, **spi_args):
149 intf = super(PiGPIOFactory, self).spi(**spi_args)
150 self._spis.append(intf)
151 return intf
153 def ticks(self):
154 return self._connection.get_current_tick()
156 @staticmethod
157 def ticks_diff(later, earlier):
158 # NOTE: pigpio ticks are unsigned 32-bit quantities that wrap every
159 # 71.6 minutes. The modulo below (oh the joys of having an *actual*
160 # modulo operator, unlike C's remainder) ensures the result is valid
161 # even when later < earlier due to wrap-around (assuming the duration
162 # measured is not longer than the period)
163 return ((later - earlier) % 0x100000000) / 1000000
166class PiGPIOPin(PiPin):
167 """
168 Extends :class:`~gpiozero.pins.pi.PiPin`. Pin implementation for the
169 `pigpio`_ library. See :class:`PiGPIOFactory` for more information.
171 .. _pigpio: http://abyz.me.uk/rpi/pigpio/
172 """
173 GPIO_FUNCTIONS = {
174 'input': pigpio.INPUT,
175 'output': pigpio.OUTPUT,
176 'alt0': pigpio.ALT0,
177 'alt1': pigpio.ALT1,
178 'alt2': pigpio.ALT2,
179 'alt3': pigpio.ALT3,
180 'alt4': pigpio.ALT4,
181 'alt5': pigpio.ALT5,
182 }
184 GPIO_PULL_UPS = {
185 'up': pigpio.PUD_UP,
186 'down': pigpio.PUD_DOWN,
187 'floating': pigpio.PUD_OFF,
188 }
190 GPIO_EDGES = {
191 'both': pigpio.EITHER_EDGE,
192 'rising': pigpio.RISING_EDGE,
193 'falling': pigpio.FALLING_EDGE,
194 }
196 GPIO_FUNCTION_NAMES = {v: k for (k, v) in GPIO_FUNCTIONS.items()}
197 GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()}
198 GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()}
200 def __init__(self, factory, number):
201 super(PiGPIOPin, self).__init__(factory, number)
202 self._pull = 'up' if self.factory.pi_info.pulled_up(repr(self)) else 'floating'
203 self._pwm = False
204 self._bounce = None
205 self._callback = None
206 self._edges = pigpio.EITHER_EDGE
207 try:
208 self.factory.connection.set_mode(self.number, pigpio.INPUT)
209 except pigpio.error as e:
210 raise ValueError(e)
211 self.factory.connection.set_pull_up_down(self.number, self.GPIO_PULL_UPS[self._pull])
212 self.factory.connection.set_glitch_filter(self.number, 0)
214 def close(self):
215 if self.factory.connection:
216 self.frequency = None
217 self.when_changed = None
218 self.function = 'input'
219 self.pull = 'up' if self.factory.pi_info.pulled_up(repr(self)) else 'floating'
221 def _get_function(self):
222 return self.GPIO_FUNCTION_NAMES[self.factory.connection.get_mode(self.number)]
224 def _set_function(self, value):
225 if value != 'input':
226 self._pull = 'floating'
227 try:
228 self.factory.connection.set_mode(self.number, self.GPIO_FUNCTIONS[value])
229 except KeyError:
230 raise PinInvalidFunction('invalid function "%s" for pin %r' % (value, self))
232 def _get_state(self):
233 if self._pwm:
234 return (
235 self.factory.connection.get_PWM_dutycycle(self.number) /
236 self.factory.connection.get_PWM_range(self.number)
237 )
238 else:
239 return bool(self.factory.connection.read(self.number))
241 def _set_state(self, value):
242 if self._pwm:
243 try:
244 value = int(value * self.factory.connection.get_PWM_range(self.number))
245 if value != self.factory.connection.get_PWM_dutycycle(self.number):
246 self.factory.connection.set_PWM_dutycycle(self.number, value)
247 except pigpio.error:
248 raise PinInvalidState('invalid state "%s" for pin %r' % (value, self))
249 elif self.function == 'input':
250 raise PinSetInput('cannot set state of pin %r' % self)
251 else:
252 # write forces pin to OUTPUT, hence the check above
253 self.factory.connection.write(self.number, bool(value))
255 def _get_pull(self):
256 return self._pull
258 def _set_pull(self, value):
259 if self.function != 'input':
260 raise PinFixedPull('cannot set pull on non-input pin %r' % self)
261 if value != 'up' and self.factory.pi_info.pulled_up(repr(self)):
262 raise PinFixedPull('%r has a physical pull-up resistor' % self)
263 try:
264 self.factory.connection.set_pull_up_down(self.number, self.GPIO_PULL_UPS[value])
265 self._pull = value
266 except KeyError:
267 raise PinInvalidPull('invalid pull "%s" for pin %r' % (value, self))
269 def _get_frequency(self):
270 if self._pwm:
271 return self.factory.connection.get_PWM_frequency(self.number)
272 return None
274 def _set_frequency(self, value):
275 if not self._pwm and value is not None:
276 if self.function != 'output':
277 raise PinPWMFixedValue('cannot start PWM on pin %r' % self)
278 # NOTE: the pin's state *must* be set to zero; if it's currently
279 # high, starting PWM and setting a 0 duty-cycle *doesn't* bring
280 # the pin low; it stays high!
281 self.factory.connection.write(self.number, 0)
282 self.factory.connection.set_PWM_frequency(self.number, int(value))
283 self.factory.connection.set_PWM_range(self.number, 10000)
284 self.factory.connection.set_PWM_dutycycle(self.number, 0)
285 self._pwm = True
286 elif self._pwm and value is not None:
287 if value != self.factory.connection.get_PWM_frequency(self.number):
288 self.factory.connection.set_PWM_frequency(self.number, int(value))
289 self.factory.connection.set_PWM_range(self.number, 10000)
290 elif self._pwm and value is None:
291 self.factory.connection.write(self.number, 0)
292 self._pwm = False
294 def _get_bounce(self):
295 return None if not self._bounce else self._bounce / 1000000
297 def _set_bounce(self, value):
298 if value is None:
299 value = 0
300 elif not 0 <= value <= 0.3:
301 raise PinInvalidBounce('bounce must be between 0 and 0.3')
302 self.factory.connection.set_glitch_filter(self.number, int(value * 1000000))
304 def _get_edges(self):
305 return self.GPIO_EDGES_NAMES[self._edges]
307 def _set_edges(self, value):
308 f = self.when_changed
309 self.when_changed = None
310 try:
311 self._edges = self.GPIO_EDGES[value]
312 finally:
313 self.when_changed = f
315 def _call_when_changed(self, gpio, level, ticks):
316 super(PiGPIOPin, self)._call_when_changed(ticks, level)
318 def _enable_event_detect(self):
319 self._callback = self.factory.connection.callback(
320 self.number, self._edges, self._call_when_changed)
322 def _disable_event_detect(self):
323 if self._callback is not None:
324 self._callback.cancel()
325 self._callback = None
328class PiGPIOHardwareSPI(SPI):
329 """
330 Hardware SPI implementation for the `pigpio`_ library. Uses the ``spi_*``
331 functions from the pigpio API.
333 .. _pigpio: http://abyz.me.uk/rpi/pigpio/
334 """
335 def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin, pin_factory):
336 port, device = spi_port_device(
337 clock_pin, mosi_pin, miso_pin, select_pin)
338 self._port = port
339 self._device = device
340 self._handle = None
341 super(PiGPIOHardwareSPI, self).__init__(pin_factory=pin_factory)
342 to_reserve = {clock_pin, select_pin}
343 if mosi_pin is not None:
344 to_reserve.add(mosi_pin)
345 if miso_pin is not None:
346 to_reserve.add(miso_pin)
347 self.pin_factory.reserve_pins(self, *to_reserve)
348 self._spi_flags = (8 << 16) | (port << 8)
349 self._baud = 500000
350 self._handle = self.pin_factory.connection.spi_open(
351 device, self._baud, self._spi_flags)
353 def _conflicts_with(self, other):
354 return not (
355 isinstance(other, PiGPIOHardwareSPI) and
356 (self.pin_factory.host, self._port, self._device) !=
357 (other.pin_factory.host, other._port, other._device)
358 )
360 def close(self):
361 try:
362 self.pin_factory._spis.remove(self)
363 except (ReferenceError, ValueError):
364 # If the factory has died already or we're not present in its
365 # internal list, ignore the error
366 pass
367 if not self.closed:
368 self.pin_factory.connection.spi_close(self._handle)
369 self._handle = None
370 self.pin_factory.release_all(self)
371 super(PiGPIOHardwareSPI, self).close()
373 @property
374 def closed(self):
375 return self._handle is None or self.pin_factory.connection is None
377 def __repr__(self):
378 try:
379 self._check_open()
380 return 'SPI(port=%d, device=%d)' % (self._port, self._device)
381 except DeviceClosed:
382 return 'SPI(closed)'
384 def _get_clock_mode(self):
385 return self._spi_flags & 0x3
387 def _set_clock_mode(self, value):
388 self._check_open()
389 if not 0 <= value < 4:
390 raise SPIInvalidClockMode("%d is not a valid SPI clock mode" % value)
391 self.pin_factory.connection.spi_close(self._handle)
392 self._spi_flags = (self._spi_flags & ~0x3) | value
393 self._handle = self.pin_factory.connection.spi_open(
394 self._device, self._baud, self._spi_flags)
396 def _get_select_high(self):
397 return bool((self._spi_flags >> (2 + self._device)) & 0x1)
399 def _set_select_high(self, value):
400 self._check_open()
401 self.pin_factory.connection.spi_close(self._handle)
402 self._spi_flags = (self._spi_flags & ~0x1c) | (bool(value) << (2 + self._device))
403 self._handle = self.pin_factory.connection.spi_open(
404 self._device, self._baud, self._spi_flags)
406 def _get_bits_per_word(self):
407 return (self._spi_flags >> 16) & 0x3f
409 def _set_bits_per_word(self, value):
410 self._check_open()
411 self.pin_factory.connection.spi_close(self._handle)
412 self._spi_flags = (self._spi_flags & ~0x3f0000) | ((value & 0x3f) << 16)
413 self._handle = self.pin_factory.connection.spi_open(
414 self._device, self._baud, self._spi_flags)
416 def _get_rate(self):
417 return self._baud
419 def _set_rate(self, value):
420 self._check_open()
421 value = int(value)
422 self.pin_factory.connection.spi_close(self._handle)
423 self._baud = value
424 self._handle = self.pin_factory.connection.spi_open(
425 self._device, self._baud, self._spi_flags)
427 def _get_lsb_first(self):
428 return bool((self._spi_flags >> 14) & 0x1) if self._port else False
430 def _set_lsb_first(self, value):
431 if self._port:
432 self._check_open()
433 self.pin_factory.connection.spi_close(self._handle)
434 self._spi_flags = (
435 (self._spi_flags & ~0xc000)
436 | (bool(value) << 14)
437 | (bool(value) << 15)
438 )
439 self._handle = self.pin_factory.connection.spi_open(
440 self._device, self._baud, self._spi_flags)
441 else:
442 super(PiGPIOHardwareSPI, self)._set_lsb_first(value)
444 def transfer(self, data):
445 self._check_open()
446 count, data = self.pin_factory.connection.spi_xfer(self._handle, data)
447 if count < 0:
448 raise IOError('SPI transfer error %d' % count)
449 # Convert returned bytearray to list of ints. XXX Not sure how non-byte
450 # sized words (aux intf only) are returned ... padded to 16/32-bits?
451 return [int(b) for b in data]
454class PiGPIOSoftwareSPI(SPI):
455 """
456 Software SPI implementation for the `pigpio`_ library. Uses the ``bb_spi_*``
457 functions from the pigpio API.
459 .. _pigpio: http://abyz.me.uk/rpi/pigpio/
460 """
461 def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin, pin_factory):
462 self._closed = True
463 self._select_pin = select_pin
464 self._clock_pin = clock_pin
465 self._mosi_pin = mosi_pin
466 self._miso_pin = miso_pin
467 super(PiGPIOSoftwareSPI, self).__init__(pin_factory=pin_factory)
468 # Can't "unreserve" MOSI/MISO on this implementation
469 self.pin_factory.reserve_pins(
470 self,
471 clock_pin,
472 mosi_pin,
473 miso_pin,
474 select_pin,
475 )
476 self._spi_flags = 0
477 self._baud = 100000
478 try:
479 self.pin_factory.connection.bb_spi_open(
480 select_pin, miso_pin, mosi_pin, clock_pin,
481 self._baud, self._spi_flags)
482 # Only set after opening bb_spi; if that fails then close() will
483 # also fail if bb_spi_close is attempted on an un-open interface
484 self._closed = False
485 except:
486 self.close()
487 raise
489 def _conflicts_with(self, other):
490 return not (
491 isinstance(other, PiGPIOSoftwareSPI) and
492 (self._select_pin) != (other._select_pin)
493 )
495 def close(self):
496 try:
497 self.pin_factory._spis.remove(self)
498 except (ReferenceError, ValueError):
499 # If the factory has died already or we're not present in its
500 # internal list, ignore the error
501 pass
502 if not self._closed and self.pin_factory.connection:
503 self._closed = True
504 self.pin_factory.connection.bb_spi_close(self._select_pin)
505 self.pin_factory.release_all(self)
506 super(PiGPIOSoftwareSPI, self).close()
508 @property
509 def closed(self):
510 return self._closed
512 def __repr__(self):
513 try:
514 self._check_open()
515 return (
516 'SPI(clock_pin=%d, mosi_pin=%d, miso_pin=%d, select_pin=%d)' % (
517 self._clock_pin, self._mosi_pin, self._miso_pin, self._select_pin
518 ))
519 except DeviceClosed:
520 return 'SPI(closed)'
522 def _spi_flags(self):
523 return (
524 self._mode << 0 |
525 self._select_high << 2 |
526 self._lsb_first << 14 |
527 self._lsb_first << 15
528 )
530 def _get_clock_mode(self):
531 return self._spi_flags & 0x3
533 def _set_clock_mode(self, value):
534 self._check_open()
535 if not 0 <= value < 4:
536 raise SPIInvalidClockMode("%d is not a valid SPI clock mode" % value)
537 self.pin_factory.connection.bb_spi_close(self._select_pin)
538 self._spi_flags = (self._spi_flags & ~0x3) | value
539 self.pin_factory.connection.bb_spi_open(
540 self._select_pin, self._miso_pin, self._mosi_pin, self._clock_pin,
541 self._baud, self._spi_flags)
543 def _get_select_high(self):
544 return bool(self._spi_flags & 0x4)
546 def _set_select_high(self, value):
547 self._check_open()
548 self.pin_factory.connection.bb_spi_close(self._select_pin)
549 self._spi_flags = (self._spi_flags & ~0x4) | (bool(value) << 2)
550 self.pin_factory.connection.bb_spi_open(
551 self._select_pin, self._miso_pin, self._mosi_pin, self._clock_pin,
552 self._baud, self._spi_flags)
554 def _get_lsb_first(self):
555 return bool(self._spi_flags & 0xc000)
557 def _set_lsb_first(self, value):
558 self._check_open()
559 self.pin_factory.connection.bb_spi_close(self._select_pin)
560 self._spi_flags = (
561 (self._spi_flags & ~0xc000)
562 | (bool(value) << 14)
563 | (bool(value) << 15)
564 )
565 self.pin_factory.connection.bb_spi_open(
566 self._select_pin, self._miso_pin, self._mosi_pin, self._clock_pin,
567 self._baud, self._spi_flags)
569 def _get_rate(self):
570 return self._baud
572 def _set_rate(self, value):
573 self._check_open()
574 value = int(value)
575 self.pin_factory.connection.bb_spi_close(self._select_pin)
576 self._baud = value
577 self.pin_factory.connection.bb_spi_open(
578 self._select_pin, self._miso_pin, self._mosi_pin, self._clock_pin,
579 self._baud, self._spi_flags)
581 def transfer(self, data):
582 self._check_open()
583 count, data = self.pin_factory.connection.bb_spi_xfer(
584 self._select_pin, data)
585 if count < 0:
586 raise IOError('SPI transfer error %d' % count)
587 # Convert returned bytearray to list of ints. bb_spi only supports
588 # byte-sized words so no issues here
589 return [int(b) for b in data]
592class PiGPIOHardwareSPIShared(SharedMixin, PiGPIOHardwareSPI):
593 @classmethod
594 def _shared_key(cls, clock_pin, mosi_pin, miso_pin, select_pin, pin_factory):
595 return (pin_factory.host, clock_pin, select_pin)
598class PiGPIOSoftwareSPIShared(SharedMixin, PiGPIOSoftwareSPI):
599 @classmethod
600 def _shared_key(cls, clock_pin, mosi_pin, miso_pin, select_pin, pin_factory):
601 return (pin_factory.host, clock_pin, select_pin)