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

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 

10 

11from __future__ import ( 

12 unicode_literals, 

13 absolute_import, 

14 print_function, 

15 division, 

16 ) 

17nstr = str 

18str = type('') 

19 

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 

30 

31try: 

32 from spidev import SpiDev 

33except ImportError: 

34 SpiDev = None 

35 

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 

42 

43 

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

63 

64 

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

76 

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 

86 

87 def _get_revision(self): 

88 return get_pi_revision() 

89 

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] 

97 

98 @staticmethod 

99 def ticks(): 

100 return monotonic() 

101 

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) 

110 

111 

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. 

120 

121 .. warning:: 

122 

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) 

131 

132 

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 

150 

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

157 

158 @property 

159 def closed(self): 

160 return self._interface is None 

161 

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

168 

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) 

176 

177 def _get_clock_mode(self): 

178 return self._interface.mode 

179 

180 def _set_clock_mode(self, value): 

181 self._interface.mode = value 

182 

183 def _get_lsb_first(self): 

184 return self._interface.lsbfirst 

185 

186 def _set_lsb_first(self, value): 

187 self._interface.lsbfirst = bool(value) 

188 

189 def _get_select_high(self): 

190 return self._interface.cshigh 

191 

192 def _set_select_high(self, value): 

193 self._interface.cshigh = bool(value) 

194 

195 def _get_bits_per_word(self): 

196 return self._interface.bits_per_word 

197 

198 def _set_bits_per_word(self, value): 

199 self._interface.bits_per_word = value 

200 

201 def _get_rate(self): 

202 return self._interface.max_speed_hz 

203 

204 def _set_rate(self, value): 

205 self._interface.max_speed_hz = int(value) 

206 

207 

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 

223 

224 def _conflicts_with(self, other): 

225 return not ( 

226 isinstance(other, LocalPiSoftwareSPI) and 

227 (self._select.pin.number != other._select.pin.number) 

228 ) 

229 

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

238 

239 @property 

240 def closed(self): 

241 return self._bus is None 

242 

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

253 

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

262 

263 def _get_clock_mode(self): 

264 with self._bus.lock: 

265 return (not self._bus.clock.active_high) << 1 | self._clock_phase 

266 

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) 

273 

274 def _get_lsb_first(self): 

275 return self._lsb_first 

276 

277 def _set_lsb_first(self, value): 

278 self._lsb_first = bool(value) 

279 

280 def _get_bits_per_word(self): 

281 return self._bits_per_word 

282 

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) 

287 

288 def _get_select_high(self): 

289 return self._select.active_high 

290 

291 def _set_select_high(self, value): 

292 with self._bus.lock: 

293 self._select.active_high = value 

294 self._select.off() 

295 

296 

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) 

302 

303 

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)