Coverage for /usr/lib/python3/dist-packages/gpiozero/pins/local.py: 39%
182 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) 2016-2021 Dave Jones <dave@waveform.org.uk>
6# Copyright (c) 2020 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 absolute_import,
14 print_function,
15 division,
16 )
17nstr = str
18str = type('')
20import io
21import errno
22import struct
23import warnings
24from collections import defaultdict
25from threading import Lock
26try:
27 from time import monotonic
28except ImportError:
29 from time import time as monotonic
31try:
32 from spidev import SpiDev
33except ImportError:
34 SpiDev = None
36from . import SPI
37from .pi import PiFactory, PiPin, SPI_HARDWARE_PINS, spi_port_device
38from .spi import SPISoftwareBus
39from ..devices import Device, SharedMixin
40from ..output_devices import OutputDevice
41from ..exc import DeviceClosed, PinUnknownPi, SPIInvalidClockMode
44def get_pi_revision():
45 revision = None
46 try:
47 with io.open('/proc/device-tree/system/linux,revision', 'rb') as f:
48 revision = hex(struct.unpack(nstr('>L'), f.read(4))[0])[2:]
49 except IOError as e:
50 if e.errno != errno.ENOENT:
51 raise e
52 with io.open('/proc/cpuinfo', 'r') as f:
53 for line in f:
54 if line.startswith('Revision'):
55 revision = line.split(':')[1].strip().lower()
56 if revision is not None: 56 ↛ 61line 56 didn't jump to line 61, because the condition on line 56 was never false
57 overvolted = revision.startswith('100')
58 if overvolted: 58 ↛ 59line 58 didn't jump to line 59, because the condition on line 58 was never true
59 revision = revision[-4:]
60 return int(revision, base=16)
61 raise PinUnknownPi(
62 'unable to locate Pi revision in /proc/device-tree or /proc/cpuinfo')
65class LocalPiFactory(PiFactory):
66 """
67 Extends :class:`~gpiozero.pins.pi.PiFactory`. Abstract base class
68 representing pins attached locally to a Pi. This forms the base class for
69 local-only pin interfaces (:class:`~gpiozero.pins.rpigpio.RPiGPIOPin`,
70 :class:`~gpiozero.pins.rpio.RPIOPin`, and
71 :class:`~gpiozero.pins.native.NativePin`).
72 """
73 pins = {}
74 _reservations = defaultdict(list)
75 _res_lock = Lock()
77 def __init__(self):
78 super(LocalPiFactory, self).__init__()
79 # Override the reservations and pins dict to be this class' attributes.
80 # This is a bit of a dirty hack, but ensures that anyone evil enough to
81 # mix pin implementations doesn't try and control the same pin with
82 # different backends
83 self.pins = LocalPiFactory.pins
84 self._reservations = LocalPiFactory._reservations
85 self._res_lock = LocalPiFactory._res_lock
87 def _get_revision(self):
88 return get_pi_revision()
90 def _get_spi_class(self, shared, hardware):
91 return {
92 (False, True): LocalPiHardwareSPI,
93 (True, True): LocalPiHardwareSPIShared,
94 (False, False): LocalPiSoftwareSPI,
95 (True, False): LocalPiSoftwareSPIShared,
96 }[shared, hardware]
98 @staticmethod
99 def ticks():
100 return monotonic()
102 @staticmethod
103 def ticks_diff(later, earlier):
104 # NOTE: technically the guarantee to always return a positive result
105 # cannot be maintained in versions where monotonic() is not available
106 # and we fall back to time(). However, in that situation we've no
107 # access to a true monotonic source, and no idea how far the clock has
108 # skipped back so this is the best we can do anyway.
109 return max(0, later - earlier)
112class LocalPiPin(PiPin):
113 """
114 Extends :class:`~gpiozero.pins.pi.PiPin`. Abstract base class representing
115 a multi-function GPIO pin attached to the local Raspberry Pi.
116 """
117 def _call_when_changed(self, ticks=None, state=None):
118 """
119 Overridden to provide default ticks from the local Pi factory.
121 .. warning::
123 The local pin factory uses a seconds-based monotonic value for
124 its ticks but you *must not* rely upon this behaviour. Ticks are
125 an opaque value that should only be compared with the associated
126 :meth:`Factory.ticks_diff` method.
127 """
128 super(LocalPiPin, self)._call_when_changed(
129 self._factory.ticks() if ticks is None else ticks,
130 self.state if state is None else state)
133class LocalPiHardwareSPI(SPI):
134 def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin, pin_factory):
135 self._port, self._device = spi_port_device(
136 clock_pin, mosi_pin, miso_pin, select_pin)
137 self._interface = None
138 if SpiDev is None:
139 raise ImportError('failed to import spidev')
140 super(LocalPiHardwareSPI, self).__init__(pin_factory=pin_factory)
141 to_reserve = {clock_pin, select_pin}
142 if mosi_pin is not None:
143 to_reserve.add(mosi_pin)
144 if miso_pin is not None:
145 to_reserve.add(miso_pin)
146 self.pin_factory.reserve_pins(self, *to_reserve)
147 self._interface = SpiDev()
148 self._interface.open(self._port, self._device)
149 self._interface.max_speed_hz = 500000
151 def close(self):
152 if self._interface is not None:
153 self._interface.close()
154 self._interface = None
155 self.pin_factory.release_all(self)
156 super(LocalPiHardwareSPI, self).close()
158 @property
159 def closed(self):
160 return self._interface is None
162 def __repr__(self):
163 try:
164 self._check_open()
165 return 'SPI(port=%d, device=%d)' % (self._port, self._device)
166 except DeviceClosed:
167 return 'SPI(closed)'
169 def transfer(self, data):
170 """
171 Writes data (a list of integer words where each word is assumed to have
172 :attr:`bits_per_word` bits or less) to the SPI interface, and reads an
173 equivalent number of words, returning them as a list of integers.
174 """
175 return self._interface.xfer2(data)
177 def _get_clock_mode(self):
178 return self._interface.mode
180 def _set_clock_mode(self, value):
181 self._interface.mode = value
183 def _get_lsb_first(self):
184 return self._interface.lsbfirst
186 def _set_lsb_first(self, value):
187 self._interface.lsbfirst = bool(value)
189 def _get_select_high(self):
190 return self._interface.cshigh
192 def _set_select_high(self, value):
193 self._interface.cshigh = bool(value)
195 def _get_bits_per_word(self):
196 return self._interface.bits_per_word
198 def _set_bits_per_word(self, value):
199 self._interface.bits_per_word = value
201 def _get_rate(self):
202 return self._interface.max_speed_hz
204 def _set_rate(self, value):
205 self._interface.max_speed_hz = int(value)
208class LocalPiSoftwareSPI(SPI):
209 def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin, pin_factory):
210 self._bus = None
211 self._select = None
212 super(LocalPiSoftwareSPI, self).__init__(pin_factory=pin_factory)
213 try:
214 self._clock_phase = False
215 self._lsb_first = False
216 self._bits_per_word = 8
217 self._bus = SPISoftwareBus(clock_pin, mosi_pin, miso_pin)
218 self._select = OutputDevice(
219 select_pin, active_high=False, pin_factory=pin_factory)
220 except:
221 self.close()
222 raise
224 def _conflicts_with(self, other):
225 return not (
226 isinstance(other, LocalPiSoftwareSPI) and
227 (self._select.pin.number != other._select.pin.number)
228 )
230 def close(self):
231 if self._select:
232 self._select.close()
233 self._select = None
234 if self._bus is not None:
235 self._bus.close()
236 self._bus = None
237 super(LocalPiSoftwareSPI, self).close()
239 @property
240 def closed(self):
241 return self._bus is None
243 def __repr__(self):
244 try:
245 self._check_open()
246 return 'SPI(clock_pin=%d, mosi_pin=%d, miso_pin=%d, select_pin=%d)' % (
247 self._bus.clock.pin.number,
248 self._bus.mosi.pin.number,
249 self._bus.miso.pin.number,
250 self._select.pin.number)
251 except DeviceClosed:
252 return 'SPI(closed)'
254 def transfer(self, data):
255 with self._bus.lock:
256 self._select.on()
257 try:
258 return self._bus.transfer(
259 data, self._clock_phase, self._lsb_first, self._bits_per_word)
260 finally:
261 self._select.off()
263 def _get_clock_mode(self):
264 with self._bus.lock:
265 return (not self._bus.clock.active_high) << 1 | self._clock_phase
267 def _set_clock_mode(self, value):
268 if not (0 <= value < 4):
269 raise SPIInvalidClockMode("%d is not a valid clock mode" % value)
270 with self._bus.lock:
271 self._bus.clock.active_high = not (value & 2)
272 self._clock_phase = bool(value & 1)
274 def _get_lsb_first(self):
275 return self._lsb_first
277 def _set_lsb_first(self, value):
278 self._lsb_first = bool(value)
280 def _get_bits_per_word(self):
281 return self._bits_per_word
283 def _set_bits_per_word(self, value):
284 if value < 1:
285 raise ValueError('bits_per_word must be positive')
286 self._bits_per_word = int(value)
288 def _get_select_high(self):
289 return self._select.active_high
291 def _set_select_high(self, value):
292 with self._bus.lock:
293 self._select.active_high = value
294 self._select.off()
297class LocalPiHardwareSPIShared(SharedMixin, LocalPiHardwareSPI):
298 @classmethod
299 def _shared_key(cls, clock_pin, mosi_pin, miso_pin, select_pin,
300 pin_factory):
301 return (clock_pin, select_pin)
304class LocalPiSoftwareSPIShared(SharedMixin, LocalPiSoftwareSPI):
305 @classmethod
306 def _shared_key(cls, clock_pin, mosi_pin, miso_pin, select_pin,
307 pin_factory):
308 return (clock_pin, select_pin)