diff --git a/docs/api_pins.rst b/docs/api_pins.rst index 83a9dae..5ad818c 100644 --- a/docs/api_pins.rst +++ b/docs/api_pins.rst @@ -25,35 +25,46 @@ integer number instead, it uses one of the following classes to provide the 4. :class:`gpiozero.pins.native.NativePin` You can change the default pin implementation by over-writing the -``DefaultPin`` global in the ``devices`` module like so:: +``pin_factory`` global in the ``devices`` module like so:: from gpiozero.pins.native import NativePin import gpiozero.devices # Force the default pin implementation to be NativePin - gpiozero.devices.DefaultPin = NativePin + gpiozero.devices.pin_factory = NativePin from gpiozero import LED # This will now use NativePin instead of RPiGPIOPin led = LED(16) +``pin_factory`` is simply a callable that accepts a single argument: the number +of the pin to be constructed (this prototype *may* be expanded in future). This +means you can define it as a function that provides additional parameters to an +underlying class. For example, to default to creating pins with +:class:`gpiozero.pins.pigpiod.PiGPIOPin` on a remote pi called ``remote-pi``:: + + from gpiozero.pins.pigpiod import PiGPIOPin + import gpiozero.devices + + def my_pin_factory(number): + return PiGPIOPin(number, host='remote-pi') + + gpiozero.devices.pin_factory = my_pin_factory + + from gpiozero import TrafficLights + + # This will now use pins on remote-pi (assuming it has the + # pigpiod daemon installed and running) + tl = TrafficLights(13, 19, 26) + Alternatively, instead of passing an integer to the device constructor, you -can pass a :class:`Pin` object itself:: +can pass an object derived from :class:`Pin` itself:: from gpiozero.pins.native import NativePin from gpiozero import LED led = LED(NativePin(16)) -This is particularly useful with implementations that can take extra parameters -such as :class:`~gpiozero.pins.pigpiod.PiGPIOPin` which can address pins on -remote machines:: - - from gpiozero.pins.pigpiod import PiGPIOPin - from gpiozero import LED - - led = LED(PiGPIOPin(16, host='my_other_pi')) - In future, this separation of pins and devices should also permit the library to utilize pins that are part of IO extender chips. For example:: diff --git a/gpiozero/devices.py b/gpiozero/devices.py index 7aa5e32..97c4040 100644 --- a/gpiozero/devices.py +++ b/gpiozero/devices.py @@ -36,18 +36,18 @@ from .exc import ( from .pins import _pins_shutdown try: from .pins.rpigpio import RPiGPIOPin - DefaultPin = RPiGPIOPin + pin_factory = RPiGPIOPin except ImportError: try: from .pins.rpio import RPIOPin - DefaultPin = RPIOPin + pin_factory = RPIOPin except ImportError: try: from .pins.pigipod import PiGPIOPin - DefaultPin = PiGPIOPin + pin_factory = PiGPIOPin except ImportError: from .pins.native import NativePin - DefaultPin = NativePin + pin_factory = NativePin _PINS = set() @@ -365,7 +365,7 @@ class GPIODevice(Device): if pin is None: raise GPIOPinMissing('No pin given') if isinstance(pin, int): - pin = DefaultPin(pin) + pin = pin_factory(pin) with _PINS_LOCK: if pin in _PINS: raise GPIOPinInUse( diff --git a/gpiozero/pins/data.py b/gpiozero/pins/data.py index df0a8ef..86a91e5 100644 --- a/gpiozero/pins/data.py +++ b/gpiozero/pins/data.py @@ -243,29 +243,28 @@ CM_SODIMM = { PI_REVISIONS = { # rev model pcb_rev released soc manufacturer ram storage usb eth wifi bt csi dsi headers - 'beta': ('B', '?', '2012Q1', 'BCM2835', '?', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ), - '0002': ('B', '1.0', '2012Q1', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ), - '0003': ('B', '1.0', '2012Q3', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ), - '0004': ('B', '2.0', '2012Q3', 'BCM2835', 'Sony', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), - '0005': ('B', '2.0', '2012Q4', 'BCM2835', 'Qisda', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), - '0006': ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), - '0007': ('A', '2.0', '2013Q1', 'BCM2835', 'Egoman', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), - '0008': ('A', '2.0', '2013Q1', 'BCM2835', 'Sony', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), - '0009': ('A', '2.0', '2013Q1', 'BCM2835', 'Qisda', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), - '000d': ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), - '000e': ('B', '2.0', '2012Q4', 'BCM2835', 'Sony', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), - '000f': ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), - '0010': ('B+', '1.2', '2014Q3', 'BCM2835', 'Sony', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), - '0011': ('CM', '1.2', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 0, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ), - '0012': ('A+', '1.2', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ), - '0013': ('B+', '1.2', '2015Q1', 'BCM2835', 'Egoman', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), - '0014': ('CM', '1.1', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 0, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ), - '0015': ('A+', '1.1', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ), - 'a01041': ('2B', '1.1', '2015Q1', 'BCM2836', 'Sony', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), - 'a21041': ('2B', '1.1', '2015Q1', 'BCM2836', 'Embest', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), - '900092': ('Zero', '1.2', '2015Q4', 'BCM2835', 'Sony', 512, 'MicroSD', 1, 0, False, False, 0, 0, {'P1': PLUS_P1}, ), - 'a02082': ('3B', '1.2', '2016Q1', 'BCM2837', 'Sony', 1024, 'MicroSD', 4, 1, True, True, 1, 1, {'P1': PLUS_P1}, ), - 'a22082': ('3B', '1.2', '2016Q1', 'BCM2837', 'Embest', 1024, 'MicroSD', 4, 1, True, True, 1, 1, {'P1': PLUS_P1}, ), + 0x2: ('B', '1.0', '2012Q1', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ), + 0x3: ('B', '1.0', '2012Q3', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ), + 0x4: ('B', '2.0', '2012Q3', 'BCM2835', 'Sony', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + 0x5: ('B', '2.0', '2012Q4', 'BCM2835', 'Qisda', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + 0x6: ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + 0x7: ('A', '2.0', '2013Q1', 'BCM2835', 'Egoman', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + 0x8: ('A', '2.0', '2013Q1', 'BCM2835', 'Sony', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + 0x9: ('A', '2.0', '2013Q1', 'BCM2835', 'Qisda', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + 0xd: ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + 0xe: ('B', '2.0', '2012Q4', 'BCM2835', 'Sony', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + 0xf: ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + 0x10: ('B+', '1.2', '2014Q3', 'BCM2835', 'Sony', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), + 0x11: ('CM', '1.2', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 0, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ), + 0x12: ('A+', '1.2', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ), + 0x13: ('B+', '1.2', '2015Q1', 'BCM2835', 'Egoman', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), + 0x14: ('CM', '1.1', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 0, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ), + 0x15: ('A+', '1.1', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ), + 0xa01041: ('2B', '1.1', '2015Q1', 'BCM2836', 'Sony', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), + 0xa21041: ('2B', '1.1', '2015Q1', 'BCM2836', 'Embest', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), + 0x900092: ('Zero', '1.2', '2015Q4', 'BCM2835', 'Sony', 512, 'MicroSD', 1, 0, False, False, 0, 0, {'P1': PLUS_P1}, ), + 0xa02082: ('3B', '1.2', '2016Q1', 'BCM2837', 'Sony', 1024, 'MicroSD', 4, 1, True, True, 1, 1, {'P1': PLUS_P1}, ), + 0xa22082: ('3B', '1.2', '2016Q1', 'BCM2837', 'Embest', 1024, 'MicroSD', 4, 1, True, True, 1, 1, {'P1': PLUS_P1}, ), } @@ -513,9 +512,8 @@ def _parse_pi_revision(revision): # PPPP - Processor (0=2835, 1=2836, 2=2837) # TTTTTTTT - Type (0=A, 1=B, 2=A+, 3=B+, 4=2B, 5=Alpha (??), 6=CM, 8=3B, 9=Zero) # RRRR - Revision (0, 1, or 2) - i = int(revision, base=16) - if not (i & 0x800000): - raise ValueError('cannot parse "%s"; this is not a new-style revision' % revision) + if not (revision & 0x800000): + raise ValueError('cannot parse "%x"; this is not a new-style revision' % revision) try: model = { 0: 'A', @@ -526,15 +524,15 @@ def _parse_pi_revision(revision): 6: 'CM', 8: '3B', 9: 'Zero', - }[(i & 0xff0) >> 4] + }[(revision & 0xff0) >> 4] if model in ('A', 'B'): pcb_revision = { 0: '1.0', # is this right? 1: '1.0', 2: '2.0', - }[i & 0x0f] + }[revision & 0x0f] else: - pcb_revision = '1.%d' % (i & 0x0f) + pcb_revision = '1.%d' % (revision & 0x0f) released = { 'A': '2013Q1', 'B': '2012Q1' if pcb_revision == '1.0' else '2012Q4', @@ -549,17 +547,17 @@ def _parse_pi_revision(revision): 0: 'BCM2835', 1: 'BCM2836', 2: 'BCM2837', - }[(i & 0xf000) >> 12] + }[(revision & 0xf000) >> 12] manufacturer = { 0: 'Sony', 1: 'Egoman', 2: 'Embest', - }[(i & 0xf0000) >> 16] + }[(revision & 0xf0000) >> 16] memory = { 0: 256, 1: 512, 2: 1024, - }[(i & 0x700000) >> 20] + }[(revision & 0x700000) >> 20] storage = { 'A': 'SD', 'B': 'SD', @@ -595,7 +593,7 @@ def _parse_pi_revision(revision): 'CM': {'SODIMM': CM_SODIMM}, }.get(model, {'P1': PLUS_P1}) except KeyError: - raise ValueError('unable to parse new-style revision "%s"' % revision) + raise ValueError('unable to parse new-style revision "%x"' % revision) else: return ( model, @@ -635,6 +633,10 @@ def pi_info(revision=None): except IOError: _PI_REVISION = 'unknown' revision = _PI_REVISION + try: + revision_int = int(revision, base=16) + except ValueError: + raise PinUnknownPi('unknown RPi revision "%s"' % revision) try: ( model, @@ -651,7 +653,7 @@ def pi_info(revision=None): csi, dsi, headers, - ) = PI_REVISIONS[revision] + ) = PI_REVISIONS[revision_int] except KeyError: try: ( @@ -669,7 +671,7 @@ def pi_info(revision=None): csi, dsi, headers, - ) = _parse_pi_revision(revision) + ) = _parse_pi_revision(revision_int) except ValueError: raise PinUnknownPi('unknown RPi revision "%s"' % revision) headers = { diff --git a/gpiozero/pins/pigpiod.py b/gpiozero/pins/pigpiod.py index 3dafefc..a14ef9e 100644 --- a/gpiozero/pins/pigpiod.py +++ b/gpiozero/pins/pigpiod.py @@ -98,25 +98,20 @@ class PiGPIOPin(Pin): GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()} GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()} - PI_INFO = None - def __new__(cls, number, host='localhost', port=8888): - # XXX What about remote pins? This should probably be instance - # specific rather than class specific for pigpio. Need to check how - # to query remote info though... - if cls.PI_INFO is None: - cls.PI_INFO = pi_info() try: return cls._PINS[(host, port, number)] except KeyError: self = super(PiGPIOPin, cls).__new__(cls) try: - self._connection = cls._CONNECTIONS[(host, port)] + self._connection, self._pi_info = cls._CONNECTIONS[(host, port)] except KeyError: self._connection = pigpio.pi(host, port) - cls._CONNECTIONS[(host, port)] = self._connection + revision = hex(self._connection.get_hardware_revision())[2:] + self._pi_info = pi_info(revision) + cls._CONNECTIONS[(host, port)] = (self._connection, self._pi_info) try: - cls.PI_INFO.physical_pin('GPIO%d' % number) + self._pi_info.physical_pin('GPIO%d' % number) except PinNoPins: warnings.warn( PinNonPhysical( @@ -124,7 +119,7 @@ class PiGPIOPin(Pin): self._host = host self._port = port self._number = number - self._pull = 'up' if cls.PI_INFO.pulled_up('GPIO%d' % number) else 'floating' + self._pull = 'up' if self._pi_info.pulled_up('GPIO%d' % number) else 'floating' self._pwm = False self._bounce = None self._when_changed = None @@ -167,7 +162,7 @@ class PiGPIOPin(Pin): self.frequency = None self.when_changed = None self.function = 'input' - self.pull = 'up' if self.PI_INFO.pulled_up('GPIO%d' % self.number) else 'floating' + self.pull = 'up' if self._pi_info.pulled_up('GPIO%d' % self.number) else 'floating' def _get_function(self): return self.GPIO_FUNCTION_NAMES[self._connection.get_mode(self._number)] @@ -204,7 +199,7 @@ class PiGPIOPin(Pin): def _set_pull(self, value): if self.function != 'input': raise PinFixedPull('cannot set pull on non-input pin %r' % self) - if value != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self._number): + if value != 'up' and self._pi_info.pulled_up('GPIO%d' % self._number): raise PinFixedPull('%r has a physical pull-up resistor' % self) try: self._connection.set_pull_up_down(self._number, self.GPIO_PULL_UPS[value]) diff --git a/tests/test_boards.py b/tests/test_boards.py index 304e128..627325e 100644 --- a/tests/test_boards.py +++ b/tests/test_boards.py @@ -19,9 +19,9 @@ def setup_function(function): import gpiozero.devices # dirty, but it does the job if function.__name__ in ('test_robot', 'test_ryanteck_robot', 'test_camjam_kit_robot'): - gpiozero.devices.DefaultPin = MockPWMPin + gpiozero.devices.pin_factory = MockPWMPin else: - gpiozero.devices.DefaultPin = MockPin + gpiozero.devices.pin_factory = MockPin def teardown_function(function): MockPin.clear_pins()