"""
Pin manipulation class
Example:
Simple example with auto mode
>>> from fusion_hat.pin import Pin
>>> pin = Pin(17)
>>> pin.value() # read the pin input value
0
>>> pin.value(1) # output pin value to high
1
Output mode
>>> from fusion_hat.pin import Pin
>>> pin = Pin(17, mode=Pin.OUT)
>>> pin.value(1) # output high
1
>>> pin.value(0) # output low
0
>>> pin.value() # read current value, not as input
0
>>> pin.on() # output high
1
>>> pin.off() # output low
0
Input mode
>>> pin = Pin(17, mode=Pin.IN, pull=Pin.PULL_UP)
>>> pin.value() # read the pin input value
0
Active state, If your LED active LOW, you can set active_state make things easier.
>>> led = Pin(17, mode=Pin.OUT, active_state=Pin.ACTIVE_LOW)
>>> led.on() # output low but turn on the LED
0
>>> led.off() # output high but turn off the LED
1
>>> led.value() # read current value, not as input
1
>>> led.raw(1) # You can still set the raw value
1
And also if your button active LOW, you can set active_state to Pin.ACTIVE_LOW. you can also do the same.
>>> button = Pin(17, mode=Pin.IN, pull=Pin.PULL_UP, active_state=Pin.ACTIVE_LOW)
>>> button.value() # read's pin low but return 1 as pressed
1
>>> button.raw() # You can still read the raw value
0
You can also set interrupts on activated or deactivated state.
>>> button = Pin(17, mode=Pin.IN, pull=Pin.PULL_UP, active_state=Pin.ACTIVE_LOW, bouncetime=0.02)
>>> def on_press(pin: Pin) -> None:
... print("press")
>>> def on_release(pin: Pin) -> None:
... print("release")
>>> button.when_activated = on_press
>>> button.when_deactivated = on_release
pressed
release
"""
from RPi import GPIO
from typing import Callable
from enum import Enum
from ._base import _Base
[Doku]
class Mode(Enum):
""" Pin direction """
AUTO = None
"""Pin direction auto"""
IN = GPIO.IN
"""Pin direction input"""
OUT = GPIO.OUT
"""Pin direction output"""
[Doku]
class Pull(Enum):
""" Pin pull up/down """
UP = GPIO.PUD_UP
"""Pin internal pull up"""
DOWN = GPIO.PUD_DOWN
"""Pin internal pull down"""
NONE = GPIO.PUD_OFF
"""Pin internal pull none"""
[Doku]
class Active(Enum):
""" Pin active state """
HIGH = True
"""Pin active state high"""
LOW = False
"""Pin active state low"""
[Doku]
class Trigger(Enum):
""" Pin interrupt """
FALLING = GPIO.FALLING
"""Pin interrupt falling"""
RISING = GPIO.RISING
"""Pin interrupt rising"""
BOTH = GPIO.BOTH
"""Pin interrupt both rising and falling"""
[Doku]
class Pin(_Base):
""" Pin manipulation class
a pin wraping class of RPi.GPIO. you need to install rpi.lgpio instead of RPi.GPIO on Pi 5
Args:
pin (int): pin number of Raspberry Pi
mode (Mode, optional): pin mode(IN/OUT/AUTO). Defaults to Mode.AUTO.
pull (Pull, optional): pin pull (Pull.UP/Pull.DOWN/Pull.NONE). Defaults to Pull.NONE.
active_state (Active, optional): active state of pin,
If True, when the hardware pin state is HIGH, the software pin is HIGH.
If False, the input polarity is reversed. Defaults to Active.HIGH.
bounce_time (float, optional): bounce time of pin interrupt in seconds. Defaults to 0.02.
*args: Additional arguments for :class:`fusion_hat._base._Base`
**kwargs: Additional keyword arguments for :class:`fusion_hat._base._Base`
"""
OUT = Mode.OUT
"""Pin mode output"""
IN = Mode.IN
"""Pin mode input"""
AUTO = Mode.AUTO
"""Pin mode auto"""
PULL_UP = Pull.UP
"""Pin internal pull up"""
PULL_DOWN = Pull.DOWN
"""Pin internal pull down"""
PULL_NONE = Pull.NONE
"""Pin internal pull none"""
ACTIVE_HIGH = Active.HIGH
"""Pin active state high"""
ACTIVE_LOW = Active.LOW
"""Pin active state low"""
IRQ_FALLING = Trigger.FALLING
"""Pin interrupt falling"""
IRQ_RISING = Trigger.RISING
"""Pin interrupt rising"""
IRQ_RISING_FALLING = Trigger.BOTH
"""Pin interrupt both rising and falling"""
def __init__(self,
pin: int,
*args,
mode: Mode = Mode.AUTO,
pull: Pull = Pull.NONE,
active_state: Active = Active.HIGH,
bounce_time: float = 0.02,
**kwargs) -> None:
super().__init__(*args, **kwargs)
GPIO.setmode(GPIO.BCM)
self._pin_num = pin
self._value = 0
self._initialized = False
self._irq_inited = False
self._on_activated = None
self._on_deactivated = None
self.setup(mode, pull, active_state, bounce_time)
[Doku]
def close(self) -> None:
""" Close the pin """
self.log.debug("Close pin %d", self._pin_num)
GPIO.cleanup(self._pin_num)
[Doku]
def deinit(self) -> None:
"""Deinitialize the pin"""
self.log.debug("Deinitialize pin %d", self._pin_num)
GPIO.cleanup(self._pin_num)
[Doku]
def setup(self,
mode: Mode = Mode.AUTO,
pull: Pull = Pull.NONE,
active_state: Active = Active.HIGH,
bounce_time: float = 0.02) -> None:
""" Setup the pin
Args:
mode (Mode, optional): pin mode(IN/OUT/AUTO). Defaults to Mode.AUTO.
pull (Pull, optional): pin pull (Pull.UP/Pull.DOWN/Pull.NONE). Defaults to Pull.NONE.
active_state (Active, optional): active state of pin,
If True, when the hardware pin state is HIGH, the software pin is HIGH.
If False, the input polarity is reversed. Defaults to Active.HIGH.
bounce_time (float, optional): bounce time of pin interrupt in seconds. Defaults to 0.02.
"""
self.log.debug("Setup pin %d, mode %s, pull %s, active_state %s, bounce_time %s", self._pin_num, mode, pull, active_state, bounce_time)
self._mode = mode
self._pull = pull
self._active_state = active_state
self._bounce_time = bounce_time
if self._initialized:
self.log.warning("Pin %d already initialized, deinitializing", self._pin_num)
self.deinit()
if self._mode != Mode.AUTO:
GPIO.setup(self._pin_num, self._mode.value, pull_up_down=self._pull.value)
self._initialized = True
[Doku]
def __call__(self, value: [bool, int] = None) -> int:
""" Set/get the pin value
Args:
value (bool/int, optional): pin value, leave it empty to get the value(0/1). Defaults to None.
Returns:
int: pin value(0/1)
"""
return self.value(value)
[Doku]
def raw(self, value: [bool, int] = None) -> int:
""" Set/get the pin raw value
Args:
value (bool/int, optional): pin value, leave it empty to get the value(0/1). Defaults to None.
Returns:
int: pin value(0/1)
Raises:
ValueError: if pin mode is IN
"""
if value == None:
if self._mode == Mode.AUTO:
self.log.debug("Get pin %d raw value, mode is AUTO", self._pin_num)
GPIO.setup(self._pin_num, self.IN.value, pull_up_down=self._pull.value)
result = GPIO.input(self._pin_num)
elif self._mode == Mode.IN:
result = GPIO.input(self._pin_num)
elif self._mode == Mode.OUT:
result = self._value
return result
else:
if self._mode == Mode.IN:
raise ValueError("Input pin cannot set value")
elif self._mode == Mode.AUTO:
GPIO.setup(self._pin_num, self.OUT.value, pull_up_down=self._pull.value)
self._value = 1 if bool(value) else 0
GPIO.output(self._pin_num, self._value)
return self._value
[Doku]
def value(self, value: [bool, int] = None) -> int:
""" Set/get the pin value
Args:
value (bool/int, optional): pin value, leave it empty to get the value(0/1). Defaults to None.
Returns:
int: pin value(0/1)
Raises:
ValueError: if pin mode is IN
"""
if value == None:
value = self.raw()
if self._active_state == Active.HIGH:
return value
else:
return value + 1 & 1
else:
if self._active_state == Active.HIGH:
self.raw(value)
else:
self.raw(value + 1 & 1)
[Doku]
def on(self) -> int:
""" Set pin on(high)
Returns:
int: pin value(1)
"""
return self.value(1)
[Doku]
def off(self) -> int:
""" Set pin off(low)
Returns:
int: pin value(0)
"""
return self.value(0)
[Doku]
def high(self) -> int:
""" Set pin high(1)
Returns:
int: pin value(1)
"""
return self.raw(1)
[Doku]
def low(self) -> int:
""" Set pin low(0)
Returns:
int: pin value(0)
"""
return self.raw(0)
[Doku]
def irq(self, handler: Callable[[], None], trigger: Trigger = Trigger.BOTH) -> None:
""" Set the pin interrupt
Args:
handler (Callable[[], None]): interrupt handler callback function
trigger (Trigger, optional): interrupt trigger(RISING, FALLING, RISING_FALLING). Defaults to Trigger.BOTH.
Raises:
ValueError: if trigger is not valid
"""
GPIO.add_event_detect(self._pin_num, trigger.value, handler, bouncetime=self._bounce_time*1000)
self._irq_inited = True
[Doku]
def init_irq(self) -> None:
""" Initialize the pin interrupt
"""
if self._irq_inited:
return
self.log.debug("Setting up IRQ for pin %d", self._pin_num)
GPIO.add_event_detect(self._pin_num, Trigger.BOTH.value, self.irq_handler, bouncetime=int(self._bounce_time*1000))
self._irq_inited = True
[Doku]
def irq_handler(self, channel: int) -> None:
""" Handle the pin interrupt
Args:
channel (int): pin number
"""
value = GPIO.input(channel)
if self._on_activated and self.value() == 1:
self.log.debug("Pin %d activated", self._pin_num)
self._on_activated()
elif self._on_deactivated and self.value() == 0:
self.log.debug("Pin %d deactivated", self._pin_num)
self._on_deactivated()
@property
def when_activated(self) -> Callable[[], None]:
""" Get the pressed handler
Returns:
Callable[[], None]: pressed handler
"""
return self._on_activated
@when_activated.setter
def when_activated(self, handler: Callable[[], None]) -> None:
""" Set the pressed handler
Args:
handler (Callable[[], None]): pressed handler
"""
self.init_irq()
self._on_activated = handler
@property
def when_deactivated(self) -> Callable[[], None]:
""" Get the released handler
Returns:
Callable[[], None]: released handler
"""
return self._on_deactivated
@when_deactivated.setter
def when_deactivated(self, handler: Callable[[], None]) -> None:
""" Set the released handler
Args:
handler (Callable[[], None]): released handler
"""
self.init_irq()
self._on_deactivated = handler