diff --git a/quick2wire/gpio.py b/quick2wire/gpio.py index fe8e3a8..d32eecd 100644 --- a/quick2wire/gpio.py +++ b/quick2wire/gpio.py @@ -18,11 +18,11 @@ def gpio_admin(subcommand, pin, pull=None): Out = "out" In = "in" - + Rising = "rising" Falling = "falling" Both = "both" - + PullDown = "pulldown" PullUp = "pullup" @@ -32,36 +32,36 @@ class PinAPI(object): def __init__(self, bank, index): self._bank = bank self._index = index - + @property def index(self): return self._index - + @property def bank(self): return self._bank - + def __enter__(self): self.open() return self - + def __exit__(self, exc_type, exc_value, traceback): self.close() - - value = property(lambda p: p.get(), - lambda p,v: p.set(v), + + value = property(lambda p: p.get(), + lambda p,v: p.set(v), doc="""The value of the pin: 1 if the pin is high, 0 if the pin is low.""") - + class PinBankAPI(object): def __getitem__(self, n): if 0 < n < len(self): raise ValueError("no pin index {n} out of range", n=n) return self.pin(n) - + def write(self): pass - + def read(self): pass @@ -69,19 +69,19 @@ def read(self): class Pin(PinAPI): """Controls a GPIO pin.""" - + __trigger__ = EDGE - + def __init__(self, bank, index, soc_pin_number, direction=In, interrupt=None, pull=None): """Creates a pin - + Parameters: user_pin_number -- the identity of the pin used to create the derived class. soc_pin_number -- the pin on the header to control, identified by the SoC pin number. direction -- (optional) the direction of the pin, either In or Out. interrupt -- (optional) pull -- (optional) - + Raises: IOError -- could not export the pin (if direction is given) """ @@ -91,42 +91,38 @@ def __init__(self, bank, index, soc_pin_number, direction=In, interrupt=None, pu self._direction = direction self._interrupt = interrupt self._pull = pull - - + + @property def soc_pin_number(self): return self._soc_pin_number - + def open(self): gpio_admin("export", self.soc_pin_number, self._pull) self._file = open(self._pin_path("value"), "r+") self._write("direction", self._direction) if self._direction == In: self._write("edge", self._interrupt if self._interrupt is not None else "none") - + def close(self): if not self.closed: - if self.direction == Out: - self.value = 0 self._file.close() self._file = None - self._write("direction", In) - self._write("edge", "none") gpio_admin("unexport", self.soc_pin_number) - + def get(self): """The current value of the pin: 1 if the pin is high or 0 if the pin is low. - + The value can only be set if the pin's direction is Out. - - Raises: + + Raises: IOError -- could not read or write the pin's value. """ self._check_open() self._file.seek(0) v = self._file.read() return int(v) if v else 0 - + def set(self, new_value): self._check_open() if self._direction != Out: @@ -134,38 +130,38 @@ def set(self, new_value): self._file.seek(0) self._file.write(str(int(new_value))) self._file.flush() - + @property def direction(self): """The direction of the pin: either In or Out. - + The value of the pin can only be set if its direction is Out. - + Raises: IOError -- could not set the pin's direction. """ return self._direction - + @direction.setter def direction(self, new_value): self._write("direction", new_value) self._direction = new_value - - @property + + @property def interrupt(self): """The interrupt property specifies what event (if any) will raise an interrupt. - - One of: + + One of: Rising -- voltage changing from low to high Falling -- voltage changing from high to low Both -- voltage changing in either direction None -- interrupts are not raised - + Raises: IOError -- could not read or set the pin's interrupt trigger """ return self._interrupt - + @interrupt.setter def interrupt(self, new_value): self._write("edge", new_value) @@ -174,33 +170,33 @@ def interrupt(self, new_value): @property def pull(self): return self._pull - + def fileno(self): """Return the underlying file descriptor. Useful for select, epoll, etc.""" return self._file.fileno() - + @property def closed(self): """Returns if this pin is closed""" return self._file is None or self._file.closed - + def _check_open(self): if self.closed: raise IOError(str(self) + " is closed") - + def _write(self, filename, value): with open(self._pin_path(filename), "w+") as f: f.write(value) - + def _pin_path(self, filename=""): return "/sys/devices/virtual/gpio/gpio%i/%s" % (self.soc_pin_number, filename) - + def __repr__(self): return self.__module__ + "." + str(self) - + def __str__(self): return "{type}({index})".format( - type=self.__class__.__name__, + type=self.__class__.__name__, index=self.index) @@ -212,14 +208,14 @@ def __init__(self, index_to_soc_fn, count=None): super(PinBank,self).__init__() self._index_to_soc = index_to_soc_fn self._count = count - + def pin(self, index, *args, **kwargs): return Pin(self, index, self._index_to_soc(index), *args, **kwargs) - + @property def has_len(self): return self._count is not None - + def __len__(self): if self._count is not None: return self._count @@ -249,49 +245,49 @@ def by_revision(d): # Maps header pin numbers to SoC GPIO numbers # See http://elinux.org/RPi_Low-level_peripherals # - # Note: - header pins are numbered from 1, SoC GPIO from zero + # Note: - header pins are numbered from 1, SoC GPIO from zero # - the Pi documentation identifies some header pins as GPIO0, # GPIO1, etc., but these are not the same as the SoC GPIO # numbers. - + _pi_header_1_pins = { - 3: by_revision({1:0, 2:2}), - 5: by_revision({1:1, 2:3}), - 7: 4, - 8: 14, - 10: 15, - 11: 17, - 12: 18, - 13: by_revision({1:21, 2:27}), - 15: 22, - 16: 23, - 18: 24, - 19: 10, - 21: 9, - 22: 25, - 23: 11, + 3: by_revision({1:0, 2:2}), + 5: by_revision({1:1, 2:3}), + 7: 4, + 8: 14, + 10: 15, + 11: 17, + 12: 18, + 13: by_revision({1:21, 2:27}), + 15: 22, + 16: 23, + 18: 24, + 19: 10, + 21: 9, + 22: 25, + 23: 11, 24: 8, 26: 7 } - + _pi_gpio_pins = [_pi_header_1_pins[i] for i in [11, 12, 13, 15, 16, 18, 22, 7]] - - + + def lookup(pin_mapping, i): try: if i >= 0: return pin_mapping[i] except LookupError: pass - + raise IndexError(str(i) + " is not a valid pin index") def map_with(pin_mapping): return lambda i: lookup(pin_mapping,i) - - + + pi_broadcom_soc = PinBank(lambda p: p) pi_header_1 = PinBank(map_with(_pi_header_1_pins)) pins = PinBank(map_with(_pi_gpio_pins), len(_pi_gpio_pins)) - + diff --git a/quick2wire/test_gpio.py b/quick2wire/test_gpio.py index 7901928..8a11798 100644 --- a/quick2wire/test_gpio.py +++ b/quick2wire/test_gpio.py @@ -9,93 +9,93 @@ class TestGPIO: def test_pin_must_be_opened_before_use_and_is_unusable_after_being_closed(self): pin = pins.pin(0) - + with pytest.raises(IOError): pin.value - + pin.open() try: pin.value finally: pin.close() - + with pytest.raises(IOError): pin.value - - + + def test_opens_and_closes_itself_when_used_as_a_context_manager(self): pin = pins.pin(0) - + with pin: pin.value - + with pytest.raises(IOError): pin.value - - + + def test_exports_gpio_device_to_userspace_when_opened_and_unexports_when_closed(self): with pins.pin(0) as pin: assert os.path.exists('/sys/class/gpio/gpio17/value') - + assert not os.path.exists('/sys/class/gpio/gpio17/value') - - + + def test_can_set_and_query_direction_of_pin_when_open(self): with pins.pin(0) as pin: pin.direction = Out assert pin.direction == Out - + assert content_of("/sys/class/gpio/gpio17/direction") == "out\n" - + pin.direction = In assert pin.direction == In - + assert content_of("/sys/class/gpio/gpio17/direction") == "in\n" - - + + def test_can_set_direction_on_construction(self): pin = pins.pin(0, Out) - + assert pin.direction == Out assert not os.path.exists("/sys/class/gpio/gpio17/direction") - + with pin: assert content_of("/sys/class/gpio/gpio17/direction") == "out\n" assert pin.direction == Out - - + + def test_setting_value_of_output_pin_writes_to_device_file(self): with pins.pin(0) as pin: pin.direction = Out - + pin.value = 1 assert pin.value == 1 assert content_of('/sys/class/gpio/gpio17/value') == '1\n' - + pin.value = 0 assert pin.value == 0 assert content_of('/sys/class/gpio/gpio17/value') == '0\n' - - + + def test_direction_and_value_of_pin_is_reset_when_closed(self): with pins.pin(0, Out) as pin: pin.value = 1 - + gpio_admin("export", 17, PullDown) try: - assert content_of('/sys/class/gpio/gpio17/value') == '0\n' - assert content_of('/sys/class/gpio/gpio17/direction') == 'in\n' + assert content_of('/sys/class/gpio/gpio17/value') == '1\n' + assert content_of('/sys/class/gpio/gpio17/direction') == 'out\n' finally: gpio_admin("unexport", 17) def test_cannot_get_a_pin_with_an_invalid_index(self): with pytest.raises(IndexError): pins.pin(-1) - + with pytest.raises(IndexError): pins.pin(len(pins)) - + def content_of(filename): with open(filename, 'r') as f: return f.read()