Merge pull request #283 from waveform80/remote-pins

Fix #278
This commit is contained in:
Dave Jones
2016-04-16 00:35:32 +01:00
5 changed files with 75 additions and 67 deletions

View File

@@ -25,35 +25,46 @@ integer number instead, it uses one of the following classes to provide the
4. :class:`gpiozero.pins.native.NativePin` 4. :class:`gpiozero.pins.native.NativePin`
You can change the default pin implementation by over-writing the 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 from gpiozero.pins.native import NativePin
import gpiozero.devices import gpiozero.devices
# Force the default pin implementation to be NativePin # Force the default pin implementation to be NativePin
gpiozero.devices.DefaultPin = NativePin gpiozero.devices.pin_factory = NativePin
from gpiozero import LED from gpiozero import LED
# This will now use NativePin instead of RPiGPIOPin # This will now use NativePin instead of RPiGPIOPin
led = LED(16) 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 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.pins.native import NativePin
from gpiozero import LED from gpiozero import LED
led = LED(NativePin(16)) 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 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:: to utilize pins that are part of IO extender chips. For example::

View File

@@ -36,18 +36,18 @@ from .exc import (
from .pins import _pins_shutdown from .pins import _pins_shutdown
try: try:
from .pins.rpigpio import RPiGPIOPin from .pins.rpigpio import RPiGPIOPin
DefaultPin = RPiGPIOPin pin_factory = RPiGPIOPin
except ImportError: except ImportError:
try: try:
from .pins.rpio import RPIOPin from .pins.rpio import RPIOPin
DefaultPin = RPIOPin pin_factory = RPIOPin
except ImportError: except ImportError:
try: try:
from .pins.pigipod import PiGPIOPin from .pins.pigipod import PiGPIOPin
DefaultPin = PiGPIOPin pin_factory = PiGPIOPin
except ImportError: except ImportError:
from .pins.native import NativePin from .pins.native import NativePin
DefaultPin = NativePin pin_factory = NativePin
_PINS = set() _PINS = set()
@@ -365,7 +365,7 @@ class GPIODevice(Device):
if pin is None: if pin is None:
raise GPIOPinMissing('No pin given') raise GPIOPinMissing('No pin given')
if isinstance(pin, int): if isinstance(pin, int):
pin = DefaultPin(pin) pin = pin_factory(pin)
with _PINS_LOCK: with _PINS_LOCK:
if pin in _PINS: if pin in _PINS:
raise GPIOPinInUse( raise GPIOPinInUse(

View File

@@ -243,29 +243,28 @@ CM_SODIMM = {
PI_REVISIONS = { PI_REVISIONS = {
# rev model pcb_rev released soc manufacturer ram storage usb eth wifi bt csi dsi headers # 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}, ), 0x2: ('B', '1.0', '2012Q1', 'BCM2835', 'Egoman', 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}, ), 0x3: ('B', '1.0', '2012Q3', '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}, ), 0x4: ('B', '2.0', '2012Q3', 'BCM2835', 'Sony', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},),
'0004': ('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},),
'0005': ('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},),
'0006': ('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},),
'0007': ('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},),
'0008': ('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},),
'0009': ('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},),
'000d': ('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},),
'000e': ('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},),
'000f': ('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}, ),
'0010': ('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}, ),
'0011': ('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}, ),
'0012': ('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}, ),
'0013': ('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}, ),
'0014': ('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}, ),
'0015': ('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}, ),
'a01041': ('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}, ),
'a21041': ('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}, ),
'900092': ('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}, ),
'a02082': ('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}, ),
'a22082': ('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) # 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) # 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) # RRRR - Revision (0, 1, or 2)
i = int(revision, base=16) if not (revision & 0x800000):
if not (i & 0x800000): raise ValueError('cannot parse "%x"; this is not a new-style revision' % revision)
raise ValueError('cannot parse "%s"; this is not a new-style revision' % revision)
try: try:
model = { model = {
0: 'A', 0: 'A',
@@ -526,15 +524,15 @@ def _parse_pi_revision(revision):
6: 'CM', 6: 'CM',
8: '3B', 8: '3B',
9: 'Zero', 9: 'Zero',
}[(i & 0xff0) >> 4] }[(revision & 0xff0) >> 4]
if model in ('A', 'B'): if model in ('A', 'B'):
pcb_revision = { pcb_revision = {
0: '1.0', # is this right? 0: '1.0', # is this right?
1: '1.0', 1: '1.0',
2: '2.0', 2: '2.0',
}[i & 0x0f] }[revision & 0x0f]
else: else:
pcb_revision = '1.%d' % (i & 0x0f) pcb_revision = '1.%d' % (revision & 0x0f)
released = { released = {
'A': '2013Q1', 'A': '2013Q1',
'B': '2012Q1' if pcb_revision == '1.0' else '2012Q4', 'B': '2012Q1' if pcb_revision == '1.0' else '2012Q4',
@@ -549,17 +547,17 @@ def _parse_pi_revision(revision):
0: 'BCM2835', 0: 'BCM2835',
1: 'BCM2836', 1: 'BCM2836',
2: 'BCM2837', 2: 'BCM2837',
}[(i & 0xf000) >> 12] }[(revision & 0xf000) >> 12]
manufacturer = { manufacturer = {
0: 'Sony', 0: 'Sony',
1: 'Egoman', 1: 'Egoman',
2: 'Embest', 2: 'Embest',
}[(i & 0xf0000) >> 16] }[(revision & 0xf0000) >> 16]
memory = { memory = {
0: 256, 0: 256,
1: 512, 1: 512,
2: 1024, 2: 1024,
}[(i & 0x700000) >> 20] }[(revision & 0x700000) >> 20]
storage = { storage = {
'A': 'SD', 'A': 'SD',
'B': 'SD', 'B': 'SD',
@@ -595,7 +593,7 @@ def _parse_pi_revision(revision):
'CM': {'SODIMM': CM_SODIMM}, 'CM': {'SODIMM': CM_SODIMM},
}.get(model, {'P1': PLUS_P1}) }.get(model, {'P1': PLUS_P1})
except KeyError: except KeyError:
raise ValueError('unable to parse new-style revision "%s"' % revision) raise ValueError('unable to parse new-style revision "%x"' % revision)
else: else:
return ( return (
model, model,
@@ -635,6 +633,10 @@ def pi_info(revision=None):
except IOError: except IOError:
_PI_REVISION = 'unknown' _PI_REVISION = 'unknown'
revision = _PI_REVISION revision = _PI_REVISION
try:
revision_int = int(revision, base=16)
except ValueError:
raise PinUnknownPi('unknown RPi revision "%s"' % revision)
try: try:
( (
model, model,
@@ -651,7 +653,7 @@ def pi_info(revision=None):
csi, csi,
dsi, dsi,
headers, headers,
) = PI_REVISIONS[revision] ) = PI_REVISIONS[revision_int]
except KeyError: except KeyError:
try: try:
( (
@@ -669,7 +671,7 @@ def pi_info(revision=None):
csi, csi,
dsi, dsi,
headers, headers,
) = _parse_pi_revision(revision) ) = _parse_pi_revision(revision_int)
except ValueError: except ValueError:
raise PinUnknownPi('unknown RPi revision "%s"' % revision) raise PinUnknownPi('unknown RPi revision "%s"' % revision)
headers = { headers = {

View File

@@ -98,25 +98,20 @@ class PiGPIOPin(Pin):
GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()} 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()} GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()}
PI_INFO = None
def __new__(cls, number, host='localhost', port=8888): 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: try:
return cls._PINS[(host, port, number)] return cls._PINS[(host, port, number)]
except KeyError: except KeyError:
self = super(PiGPIOPin, cls).__new__(cls) self = super(PiGPIOPin, cls).__new__(cls)
try: try:
self._connection = cls._CONNECTIONS[(host, port)] self._connection, self._pi_info = cls._CONNECTIONS[(host, port)]
except KeyError: except KeyError:
self._connection = pigpio.pi(host, port) 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: try:
cls.PI_INFO.physical_pin('GPIO%d' % number) self._pi_info.physical_pin('GPIO%d' % number)
except PinNoPins: except PinNoPins:
warnings.warn( warnings.warn(
PinNonPhysical( PinNonPhysical(
@@ -124,7 +119,7 @@ class PiGPIOPin(Pin):
self._host = host self._host = host
self._port = port self._port = port
self._number = number 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._pwm = False
self._bounce = None self._bounce = None
self._when_changed = None self._when_changed = None
@@ -167,7 +162,7 @@ class PiGPIOPin(Pin):
self.frequency = None self.frequency = None
self.when_changed = None self.when_changed = None
self.function = 'input' 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): def _get_function(self):
return self.GPIO_FUNCTION_NAMES[self._connection.get_mode(self._number)] return self.GPIO_FUNCTION_NAMES[self._connection.get_mode(self._number)]
@@ -204,7 +199,7 @@ class PiGPIOPin(Pin):
def _set_pull(self, value): def _set_pull(self, value):
if self.function != 'input': if self.function != 'input':
raise PinFixedPull('cannot set pull on non-input pin %r' % self) 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) raise PinFixedPull('%r has a physical pull-up resistor' % self)
try: try:
self._connection.set_pull_up_down(self._number, self.GPIO_PULL_UPS[value]) self._connection.set_pull_up_down(self._number, self.GPIO_PULL_UPS[value])

View File

@@ -19,9 +19,9 @@ def setup_function(function):
import gpiozero.devices import gpiozero.devices
# dirty, but it does the job # dirty, but it does the job
if function.__name__ in ('test_robot', 'test_ryanteck_robot', 'test_camjam_kit_robot'): if function.__name__ in ('test_robot', 'test_ryanteck_robot', 'test_camjam_kit_robot'):
gpiozero.devices.DefaultPin = MockPWMPin gpiozero.devices.pin_factory = MockPWMPin
else: else:
gpiozero.devices.DefaultPin = MockPin gpiozero.devices.pin_factory = MockPin
def teardown_function(function): def teardown_function(function):
MockPin.clear_pins() MockPin.clear_pins()