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

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 

14 

15from __future__ import ( 

16 unicode_literals, 

17 absolute_import, 

18 print_function, 

19 division, 

20 ) 

21str = type('') 

22 

23import os 

24 

25import pigpio 

26 

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) 

42 

43 

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. 

50 

51 While this does mean only the daemon itself should control the pins, the 

52 architecture does have several advantages: 

53 

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 

60 

61 You can construct pigpio pins manually like so:: 

62 

63 from gpiozero.pins.pigpio import PiGPIOFactory 

64 from gpiozero import LED 

65 

66 factory = PiGPIOFactory() 

67 led = LED(12, pin_factory=factory) 

68 

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

72 

73 from gpiozero.pins.pigpio import PiGPIOFactory 

74 from gpiozero import LED 

75 

76 factory = PiGPIOFactory(host='192.168.0.2') 

77 led = LED(12, pin_factory=factory) 

78 

79 .. note:: 

80 

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. 

86 

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 = [] 

105 

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 

116 

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 

128 

129 @property 

130 def host(self): 

131 return self._host 

132 

133 @property 

134 def port(self): 

135 return self._port 

136 

137 def _get_revision(self): 

138 return self.connection.get_hardware_revision() 

139 

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] 

147 

148 def spi(self, **spi_args): 

149 intf = super(PiGPIOFactory, self).spi(**spi_args) 

150 self._spis.append(intf) 

151 return intf 

152 

153 def ticks(self): 

154 return self._connection.get_current_tick() 

155 

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 

164 

165 

166class PiGPIOPin(PiPin): 

167 """ 

168 Extends :class:`~gpiozero.pins.pi.PiPin`. Pin implementation for the 

169 `pigpio`_ library. See :class:`PiGPIOFactory` for more information. 

170 

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 } 

183 

184 GPIO_PULL_UPS = { 

185 'up': pigpio.PUD_UP, 

186 'down': pigpio.PUD_DOWN, 

187 'floating': pigpio.PUD_OFF, 

188 } 

189 

190 GPIO_EDGES = { 

191 'both': pigpio.EITHER_EDGE, 

192 'rising': pigpio.RISING_EDGE, 

193 'falling': pigpio.FALLING_EDGE, 

194 } 

195 

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

199 

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) 

213 

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' 

220 

221 def _get_function(self): 

222 return self.GPIO_FUNCTION_NAMES[self.factory.connection.get_mode(self.number)] 

223 

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

231 

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

240 

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

254 

255 def _get_pull(self): 

256 return self._pull 

257 

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

268 

269 def _get_frequency(self): 

270 if self._pwm: 

271 return self.factory.connection.get_PWM_frequency(self.number) 

272 return None 

273 

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 

293 

294 def _get_bounce(self): 

295 return None if not self._bounce else self._bounce / 1000000 

296 

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

303 

304 def _get_edges(self): 

305 return self.GPIO_EDGES_NAMES[self._edges] 

306 

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 

314 

315 def _call_when_changed(self, gpio, level, ticks): 

316 super(PiGPIOPin, self)._call_when_changed(ticks, level) 

317 

318 def _enable_event_detect(self): 

319 self._callback = self.factory.connection.callback( 

320 self.number, self._edges, self._call_when_changed) 

321 

322 def _disable_event_detect(self): 

323 if self._callback is not None: 

324 self._callback.cancel() 

325 self._callback = None 

326 

327 

328class PiGPIOHardwareSPI(SPI): 

329 """ 

330 Hardware SPI implementation for the `pigpio`_ library. Uses the ``spi_*`` 

331 functions from the pigpio API. 

332 

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) 

352 

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 ) 

359 

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

372 

373 @property 

374 def closed(self): 

375 return self._handle is None or self.pin_factory.connection is None 

376 

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

383 

384 def _get_clock_mode(self): 

385 return self._spi_flags & 0x3 

386 

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) 

395 

396 def _get_select_high(self): 

397 return bool((self._spi_flags >> (2 + self._device)) & 0x1) 

398 

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) 

405 

406 def _get_bits_per_word(self): 

407 return (self._spi_flags >> 16) & 0x3f 

408 

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) 

415 

416 def _get_rate(self): 

417 return self._baud 

418 

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) 

426 

427 def _get_lsb_first(self): 

428 return bool((self._spi_flags >> 14) & 0x1) if self._port else False 

429 

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) 

443 

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] 

452 

453 

454class PiGPIOSoftwareSPI(SPI): 

455 """ 

456 Software SPI implementation for the `pigpio`_ library. Uses the ``bb_spi_*`` 

457 functions from the pigpio API. 

458 

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 

488 

489 def _conflicts_with(self, other): 

490 return not ( 

491 isinstance(other, PiGPIOSoftwareSPI) and 

492 (self._select_pin) != (other._select_pin) 

493 ) 

494 

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

507 

508 @property 

509 def closed(self): 

510 return self._closed 

511 

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

521 

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 ) 

529 

530 def _get_clock_mode(self): 

531 return self._spi_flags & 0x3 

532 

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) 

542 

543 def _get_select_high(self): 

544 return bool(self._spi_flags & 0x4) 

545 

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) 

553 

554 def _get_lsb_first(self): 

555 return bool(self._spi_flags & 0xc000) 

556 

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) 

568 

569 def _get_rate(self): 

570 return self._baud 

571 

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) 

580 

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] 

590 

591 

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) 

596 

597 

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)