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

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 

8 

9from __future__ import ( 

10 unicode_literals, 

11 absolute_import, 

12 print_function, 

13 division, 

14 ) 

15str = type('') 

16 

17import io 

18from threading import RLock, Lock 

19from types import MethodType 

20from collections import defaultdict 

21try: 

22 from weakref import ref, WeakMethod 

23except ImportError: 

24 

25 from ..compat import WeakMethod 

26import warnings 

27 

28try: 

29 from spidev import SpiDev 

30except ImportError: 

31 SpiDev = None 

32 

33from . import Factory, Pin 

34from .data import PiBoardInfo 

35from ..exc import ( 

36 PinNoPins, 

37 PinNonPhysical, 

38 PinInvalidPin, 

39 SPIBadArgs, 

40 SPISoftwareFallback, 

41 ) 

42 

43 

44SPI_HARDWARE_PINS = { 

45 0: { 

46 'clock': 11, 

47 'mosi': 10, 

48 'miso': 9, 

49 'select': (8, 7), 

50 }, 

51} 

52 

53 

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

71 

72 

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 

84 

85 def close(self): 

86 for pin in self.pins.values(): 

87 pin.close() 

88 self.pins.clear() 

89 

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

93 

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

97 

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 

106 

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 

113 

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 

118 

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

126 

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. 

132 

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) 

159 

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. 

166 

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 

222 

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 

233 

234 

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: 

240 

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` 

247 

248 Descendents *may* additionally override the following methods, if 

249 applicable: 

250 

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

276 

277 @property 

278 def number(self): 

279 return self._number 

280 

281 def __repr__(self): 

282 return 'GPIO%d' % self._number 

283 

284 @property 

285 def factory(self): 

286 return self._factory 

287 

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) 

299 

300 def _get_when_changed(self): 

301 return None if self._when_changed is None else self._when_changed() 

302 

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

320 

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 

328 

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