More tidying up

Ensure LEDCollection cleans up upon construction failure, rename some
internals to be a bit more obvious, rename PinGPIOUnsupported to
PinUnsupported, and some other stuff I've forgotten!
This commit is contained in:
Dave Jones
2016-10-21 14:54:34 +01:00
parent c570b8f09b
commit b0c807da19
9 changed files with 69 additions and 69 deletions

View File

@@ -105,7 +105,7 @@ Errors
.. autoexception:: PinEdgeDetectUnsupported .. autoexception:: PinEdgeDetectUnsupported
.. autoexception:: PinGPIOUnsupported .. autoexception:: PinUnsupported
.. autoexception:: PinSPIUnsupported .. autoexception:: PinSPIUnsupported

View File

@@ -211,20 +211,24 @@ class LEDCollection(CompositeOutputDevice):
initial_value = kwargs.pop('initial_value', False) initial_value = kwargs.pop('initial_value', False)
order = kwargs.pop('_order', None) order = kwargs.pop('_order', None)
LEDClass = PWMLED if pwm else LED LEDClass = PWMLED if pwm else LED
super(LEDCollection, self).__init__( try:
*( super(LEDCollection, self).__init__(
pin_or_collection *(
if isinstance(pin_or_collection, LEDCollection) else pin_or_collection
LEDClass(pin_or_collection, active_high, initial_value) if isinstance(pin_or_collection, LEDCollection) else
for pin_or_collection in args LEDClass(pin_or_collection, active_high, initial_value)
), for pin_or_collection in args
_order=order, ),
**{ _order=order,
name: pin_or_collection **{
if isinstance(pin_or_collection, LEDCollection) else name: pin_or_collection
LEDClass(pin_or_collection, active_high, initial_value) if isinstance(pin_or_collection, LEDCollection) else
for name, pin_or_collection in kwargs.items() LEDClass(pin_or_collection, active_high, initial_value)
}) for name, pin_or_collection in kwargs.items()
})
except:
self.close()
raise
leds = [] leds = []
for item in self: for item in self:
if isinstance(item, LEDCollection): if isinstance(item, LEDCollection):

View File

@@ -11,7 +11,7 @@ import os
import atexit import atexit
import weakref import weakref
import warnings import warnings
from collections import namedtuple from collections import namedtuple, defaultdict
from itertools import chain from itertools import chain
from types import FunctionType from types import FunctionType
from threading import Lock from threading import Lock
@@ -194,7 +194,7 @@ class Device(ValuesMixin, GPIOBase):
property, the :attr:`value` property, and the :meth:`close` method). property, the :attr:`value` property, and the :meth:`close` method).
""" """
_pin_factory = None # instance of a Factory sub-class _pin_factory = None # instance of a Factory sub-class
_reservations = {} # maps pin addresses to lists of devices _reservations = defaultdict(list) # maps pin addresses to lists of devices
_res_lock = Lock() _res_lock = Lock()
def __repr__(self): def __repr__(self):
@@ -226,19 +226,14 @@ class Device(ValuesMixin, GPIOBase):
) )
with self._res_lock: with self._res_lock:
for address in addresses: for address in addresses:
try: for device_ref in Device._reservations[address]:
conflictors = self._reservations[address]
except KeyError:
conflictors = []
self._reservations[address] = conflictors
for device_ref in conflictors:
device = device_ref() device = device_ref()
if device is not None and self._conflicts_with(device): if device is not None and self._conflicts_with(device):
raise GPIOPinInUse( raise GPIOPinInUse(
'pin %s is already in use by %r' % ( 'pin %s is already in use by %r' % (
'/'.join(address), device) '/'.join(address), device)
) )
conflictors.append(weakref.ref(self)) Device._reservations[address].append(weakref.ref(self))
def _release_pins(self, *pins_or_addresses): def _release_pins(self, *pins_or_addresses):
""" """
@@ -254,7 +249,7 @@ class Device(ValuesMixin, GPIOBase):
) )
with self._res_lock: with self._res_lock:
for address in addresses: for address in addresses:
self._reservations[address] = [ Device._reservations[address] = [
ref for ref in self._reservations[address] ref for ref in self._reservations[address]
if ref() not in (self, None) # may as well clean up dead refs if ref() not in (self, None) # may as well clean up dead refs
] ]
@@ -265,13 +260,13 @@ class Device(ValuesMixin, GPIOBase):
:meth:`_release_pins` for further information). :meth:`_release_pins` for further information).
""" """
with self._res_lock: with self._res_lock:
Device._reservations = { Device._reservations = defaultdict(list, {
address: [ address: [
ref for ref in conflictors ref for ref in conflictors
if ref() not in (self, None) if ref() not in (self, None)
] ]
for address, conflictors in self._reservations.items() for address, conflictors in self._reservations.items()
} })
def _conflicts_with(self, other): def _conflicts_with(self, other):
""" """

View File

@@ -118,8 +118,8 @@ class PinFixedPull(PinError, AttributeError):
class PinEdgeDetectUnsupported(PinError, AttributeError): class PinEdgeDetectUnsupported(PinError, AttributeError):
"Error raised when attempting to use edge detection on unsupported pins" "Error raised when attempting to use edge detection on unsupported pins"
class PinGPIOUnsupported(PinError, NotImplementedError): class PinUnsupported(PinError, NotImplementedError):
"Error raised when attempting to obtain a GPIO interface on unsupported pins" "Error raised when attempting to obtain a pin interface on unsupported pins"
class PinSPIUnsupported(PinError, NotImplementedError): class PinSPIUnsupported(PinError, NotImplementedError):
"Error raised when attempting to obtain an SPI interface on unsupported pins" "Error raised when attempting to obtain an SPI interface on unsupported pins"

View File

@@ -71,9 +71,9 @@ class SourceMixin(object):
def close(self): def close(self):
try: try:
super(SourceMixin, self).close() super(SourceMixin, self).close()
self.source = None
except AttributeError: except AttributeError:
pass pass
self.source = None
def _copy_values(self, source): def _copy_values(self, source):
for v in source: for v in source:

View File

@@ -12,6 +12,7 @@ from ..exc import (
PinInvalidFunction, PinInvalidFunction,
PinSetInput, PinSetInput,
PinFixedPull, PinFixedPull,
PinUnsupported,
PinSPIUnsupported, PinSPIUnsupported,
PinPWMUnsupported, PinPWMUnsupported,
PinEdgeDetectUnsupported, PinEdgeDetectUnsupported,
@@ -58,7 +59,7 @@ class Factory(object):
:meth:`pin` for the same pin specification must return the same :meth:`pin` for the same pin specification must return the same
object. object.
""" """
raise PinGPIOUnsupported("GPIO not supported by this pin factory") raise PinUnsupported("Individual pins are not supported by this pin factory")
def pin_address(self, spec): def pin_address(self, spec):
""" """

View File

@@ -32,10 +32,12 @@ class LocalPiFactory(PiFactory):
def __init__(self): def __init__(self):
super(LocalPiFactory, self).__init__() super(LocalPiFactory, self).__init__()
self.spi_hardware_class = LocalPiHardwareSPI self.spi_classes = {
self.spi_software_class = LocalPiSoftwareSPI ('hardware', 'exclusive'): LocalPiHardwareSPI,
self.shared_spi_hardware_class = LocalPiHardwareSPIShared ('hardware', 'shared'): LocalPiHardwareSPIShared,
self.shared_spi_software_class = LocalPiSoftwareSPIShared ('software', 'exclusive'): LocalPiSoftwareSPI,
('software', 'shared'): LocalPiSoftwareSPIShared,
}
# Override the pins dict to be this class' pins dict. This is a bit of # Override the pins dict to be this class' pins dict. This is a bit of
# a dirty hack, but ensures that anyone evil enough to mix pin # a dirty hack, but ensures that anyone evil enough to mix pin
# implementations doesn't try and control the same pin with different # implementations doesn't try and control the same pin with different
@@ -74,7 +76,7 @@ class LocalPiHardwareSPI(SPI, Device):
raise ImportError('failed to import spidev') raise ImportError('failed to import spidev')
self._port = port self._port = port
self._device = device self._device = device
self._intf = None self._interface = None
self._address = factory.address + ('SPI(port=%d, device=%d)' % (port, device),) self._address = factory.address + ('SPI(port=%d, device=%d)' % (port, device),)
super(LocalPiHardwareSPI, self).__init__() super(LocalPiHardwareSPI, self).__init__()
self._reserve_pins( self._reserve_pins(
@@ -83,9 +85,9 @@ class LocalPiHardwareSPI(SPI, Device):
factory.pin_address(9), factory.pin_address(9),
factory.pin_address((8, 7)[device]) factory.pin_address((8, 7)[device])
) )
self._intf = SpiDev() self._interface = SpiDev()
self._intf.open(port, device) self._interface.open(port, device)
self._intf.max_speed_hz = 500000 self._interface.max_speed_hz = 500000
def _conflicts_with(self, other): def _conflicts_with(self, other):
return not ( return not (
@@ -94,17 +96,17 @@ class LocalPiHardwareSPI(SPI, Device):
) )
def close(self): def close(self):
if self._intf: if self._interface:
try: try:
self._intf.close() self._interface.close()
finally: finally:
self._intf = None self._interface = None
self._release_all() self._release_all()
super(LocalPiHardwareSPI, self).close() super(LocalPiHardwareSPI, self).close()
@property @property
def closed(self): def closed(self):
return self._intf is None return self._interface is None
def __repr__(self): def __repr__(self):
try: try:
@@ -119,31 +121,31 @@ class LocalPiHardwareSPI(SPI, Device):
:attr:`bits_per_word` bits or less) to the SPI interface, and reads an :attr:`bits_per_word` bits or less) to the SPI interface, and reads an
equivalent number of words, returning them as a list of integers. equivalent number of words, returning them as a list of integers.
""" """
return self._intf.xfer2(data) return self._interface.xfer2(data)
def _get_clock_mode(self): def _get_clock_mode(self):
return self._intf.mode return self._interface.mode
def _set_clock_mode(self, value): def _set_clock_mode(self, value):
self._intf.mode = value self._interface.mode = value
def _get_lsb_first(self): def _get_lsb_first(self):
return self._intf.lsbfirst return self._interface.lsbfirst
def _set_lsb_first(self, value): def _set_lsb_first(self, value):
self._intf.lsbfirst = bool(value) self._interface.lsbfirst = bool(value)
def _get_select_high(self): def _get_select_high(self):
return self._intf.cshigh return self._interface.cshigh
def _set_select_high(self, value): def _set_select_high(self, value):
self._intf.cshigh = bool(value) self._interface.cshigh = bool(value)
def _get_bits_per_word(self): def _get_bits_per_word(self):
return self._intf.bits_per_word return self._interface.bits_per_word
def _set_bits_per_word(self, value): def _set_bits_per_word(self, value):
self._intf.bits_per_word = value self._interface.bits_per_word = value
class LocalPiSoftwareSPI(SPI, OutputDevice): class LocalPiSoftwareSPI(SPI, OutputDevice):

View File

@@ -35,10 +35,12 @@ class PiFactory(Factory):
self._info = None self._info = None
self.pins = {} self.pins = {}
self.pin_class = None self.pin_class = None
self.spi_hardware_class = None self.spi_classes = {
self.spi_software_class = None ('hardware', 'exclusive'): None,
self.shared_spi_hardware_class = None ('hardware', 'shared'): None,
self.shared_spi_software_class = None ('software', 'exclusive'): None,
('software', 'shared'): None,
}
def close(self): def close(self):
for pin in self.pins.values(): for pin in self.pins.values():
@@ -93,7 +95,7 @@ class PiFactory(Factory):
doesn't matter). doesn't matter).
""" """
spi_args, kwargs = self._extract_spi_args(**spi_args) spi_args, kwargs = self._extract_spi_args(**spi_args)
shared = kwargs.pop('shared', False) shared = 'shared' if kwargs.pop('shared', False) else 'exclusive'
if kwargs: if kwargs:
raise SPIBadArgs( raise SPIBadArgs(
'unrecognized keyword argument %s' % kwargs.popitem()[0]) 'unrecognized keyword argument %s' % kwargs.popitem()[0])
@@ -108,10 +110,7 @@ class PiFactory(Factory):
'port': 0, 'port': 0,
'device': {8: 0, 7: 1}[spi_args['select_pin']], 'device': {8: 0, 7: 1}[spi_args['select_pin']],
} }
if shared: return self.spi_classes[('hardware', shared)](self, **hardware_spi_args)
return self.shared_spi_hardware_class(self, **hardware_spi_args)
else:
return self.spi_hardware_class(self, **hardware_spi_args)
except Exception as e: except Exception as e:
warnings.warn( warnings.warn(
SPISoftwareFallback( SPISoftwareFallback(
@@ -125,10 +124,7 @@ class PiFactory(Factory):
key: pin.number if isinstance(pin, Pin) else pin key: pin.number if isinstance(pin, Pin) else pin
for key, pin in spi_args.items() for key, pin in spi_args.items()
} }
if shared: return self.spi_classes[('software', shared)](self, **spi_args)
return self.shared_spi_software_class(self, **spi_args)
else:
return self.spi_software_class(self, **spi_args)
def _extract_spi_args(self, **kwargs): def _extract_spi_args(self, **kwargs):
""" """

View File

@@ -76,10 +76,12 @@ class PiGPIOFactory(PiFactory):
port=int(os.getenv('PIGPIO_PORT', 8888))): port=int(os.getenv('PIGPIO_PORT', 8888))):
super(PiGPIOFactory, self).__init__() super(PiGPIOFactory, self).__init__()
self.pin_class = PiGPIOPin self.pin_class = PiGPIOPin
self.spi_hardware_class = PiGPIOHardwareSPI self.spi_classes = {
self.spi_software_class = PiGPIOSoftwareSPI ('hardware', 'exclusive'): PiGPIOHardwareSPI,
self.shared_spi_hardware_class = PiGPIOHardwareSPIShared ('hardware', 'shared'): PiGPIOHardwareSPIShared,
self.shared_spi_software_class = PiGPIOSoftwareSPIShared ('software', 'exclusive'): PiGPIOSoftwareSPI,
('software', 'shared'): PiGPIOSoftwareSPIShared,
}
self._connection = pigpio.pi(host, port) self._connection = pigpio.pi(host, port)
self._host = host self._host = host
self._port = port self._port = port