From 48f98b8406152263a81dadfce1d5fb3ca1328aee Mon Sep 17 00:00:00 2001 From: Daniel McKnight Date: Fri, 29 Mar 2024 10:13:06 -0700 Subject: [PATCH] Refactor to use `gpiozero` instead of `RPi.GPIO` for Kernel 6.6 Compat. --- neon_phal_plugin_switches/__init__.py | 126 ++++++++++++-------------- requirements/requirements.txt | 3 +- 2 files changed, 61 insertions(+), 68 deletions(-) diff --git a/neon_phal_plugin_switches/__init__.py b/neon_phal_plugin_switches/__init__.py index 1ab7eb2..ccf6432 100644 --- a/neon_phal_plugin_switches/__init__.py +++ b/neon_phal_plugin_switches/__init__.py @@ -26,21 +26,22 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import RPi.GPIO as GPIO - +from os.path import exists from abc import ABC -from time import sleep +from typing import Optional + from ovos_plugin_manager.phal import PHALPlugin from ovos_plugin_manager.hardware.switches import AbstractSwitches from ovos_utils.log import LOG from ovos_bus_client.message import Message from sj201_interface.revisions import detect_sj201_revision +from gpiozero import Button class SwitchValidator: @staticmethod def validate(_=None): - # TODO: More generic validation + # TODO: More generic validation that GPIO exists return detect_sj201_revision() is not None @@ -62,13 +63,26 @@ def __init__(self, bus=None, config=None): self.switches.on_vol_up = self.on_button_volup_press self.switches.on_vol_down = self.on_button_voldown_press - if GPIO.input(self.switches.mute_pin) == self.switches.muted: + if self.switches.mute_switch.is_active: + LOG.debug(f"Mute switch active") self.bus.emit(Message('mycroft.mic.mute')) self.bus.on('mycroft.mic.status', self.on_mic_status) + @property + def gpio_device(self): + if self.config.get("gpio_device"): + LOG.debug(f"GIPIO device from config: {self.config['gpio_device']}") + return self.config.get("gpio_device") + if exists("/dev/gpiochip4"): + LOG.info("Selecting gpiochip4") + return "gpiochip4" + if exists("/dev/gpiochip0"): + return "gpiochip0" + raise RuntimeError(f"No GPIO device available") + def on_mic_status(self, message): - if GPIO.input(self.switches.mute_pin) == self.switches.muted: + if self.switches.mute_switch.is_active: msg_type = 'mycroft.mic.mute' else: msg_type = 'mycroft.mic.unmute' @@ -76,7 +90,7 @@ def on_mic_status(self, message): def on_button_press(self): LOG.info("Listen button pressed") - if GPIO.input(self.switches.mute_pin) != self.switches.muted: + if not self.switches.mute_switch.is_active: self.bus.emit(Message("mycroft.mic.listen")) else: self.bus.emit(Message("mycroft.mic.error", @@ -100,25 +114,42 @@ def on_hardware_unmute(self): class GPIOSwitches(AbstractSwitches, ABC): - def __init__(self, action_callback, volup_callback, voldown_callback, - mute_callback, unmute_callback, volup_pin: int = 22, - voldown_pin: int = 23, action_pin: int = 24, - mute_pin: int = 25, sw_active_state: int = 0, - sw_muted_state: int = 1): + def __init__(self, action_callback: callable, + volup_callback: callable, voldown_callback: callable, + mute_callback: callable, unmute_callback: callable, + volup_pin: int = 22, voldown_pin: int = 23, + action_pin: int = 24, mute_pin: int = 25, + sw_muted_state: int = 1, sw_pullup: bool = True): + """ + Creates an object to manage GPIO switches and callbacks on switch + activity. + @param action_callback: Called when the "Action" switch is activated + @param volup_callback: Called when the volume up switch is activated + @param voldown_callback: Called when the volume down switch is activated + @param mute_callback: Called when the mute switch is activated + @param unmute_callback: Called when the mute switch is de-activated + @param volup_pin: GPIO pin of the volume up switch + @param voldown_pin: GPIO pin of the volume down switch + @param action_pin: GPIO pin of the action switch + @param mute_pin: GPIO pin of the mute slider + @param sw_muted_state: mute pin state associated with muted + @param sw_pullup: if True, pull up switches, else pull down + """ self.on_action = action_callback self.on_vol_up = volup_callback self.on_vol_down = voldown_callback self.on_mute = mute_callback self.on_unmute = unmute_callback + self.mute_switch: Optional[Button] = None + self.vol_up_pin = volup_pin self.vol_dn_pin = voldown_pin self.action_pin = action_pin self.mute_pin = mute_pin - self._active = sw_active_state self._muted = sw_muted_state - self.setup_gpio() + self.setup_gpio(pull_up=sw_pullup) @property def muted(self): @@ -127,63 +158,24 @@ def muted(self): """ return self._muted - def setup_gpio(self, debounce=100): + def setup_gpio(self, debounce: int = 0.1, pull_up: bool = True): """ Do GPIO setup. + @param debounce: bounce time in seconds for button presses + @param pull_up: If true, pull up switches, else pull down """ - # use BCM GPIO pin numbering - GPIO.setmode(GPIO.BCM) - GPIO.setwarnings(False) - if self._active == 0: - pull_up_down = GPIO.PUD_UP - else: - pull_up_down = GPIO.PUD_DOWN - # we need to pull up the 3 buttons and mute switch - GPIO.setup(self.action_pin, GPIO.IN, pull_up_down=pull_up_down) - GPIO.setup(self.vol_up_pin, GPIO.IN, pull_up_down=pull_up_down) - GPIO.setup(self.vol_dn_pin, GPIO.IN, pull_up_down=pull_up_down) - GPIO.setup(self.mute_pin, GPIO.IN, pull_up_down=pull_up_down) - - # attach callbacks - GPIO.add_event_detect(self.action_pin, - GPIO.BOTH, - callback=self.handle_action, - bouncetime=debounce) - - GPIO.add_event_detect(self.vol_up_pin, - GPIO.BOTH, - callback=self.handle_vol_up, - bouncetime=debounce) - - GPIO.add_event_detect(self.vol_dn_pin, - GPIO.BOTH, - callback=self.handle_vol_down, - bouncetime=debounce) - - GPIO.add_event_detect(self.mute_pin, - GPIO.BOTH, - callback=self.handle_mute, - bouncetime=debounce) - - def handle_action(self, _): - if GPIO.input(self.action_pin) == self._active: - self.on_action() - - def handle_vol_up(self, _): - if GPIO.input(self.vol_up_pin) == self._active: - self.on_vol_up() - - def handle_vol_down(self, _): - if GPIO.input(self.vol_dn_pin) == self._active: - self.on_vol_down() - - def handle_mute(self, _): - sleep(0.05) - if GPIO.input(self.mute_pin) == self._muted: - self.on_mute() - else: - self.on_unmute() + Button(self.action_pin, pull_up=pull_up, + bounce_time=debounce).when_activated = self.on_action + Button(self.vol_up_pin, pull_up=pull_up, + bounce_time=debounce).when_activated = self.on_vol_up + Button(self.vol_dn_pin, pull_up=pull_up, + bounce_time=debounce).when_activated = self.on_vol_down + + self.mute_switch = Button(self.mute_pin, pull_up=pull_up, + bounce_time=debounce) + self.mute_switch.when_deactivated = self.on_unmute + self.mute_switch.when_activated = self.on_mute @property def capabilities(self) -> dict: diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 453d2bf..8e95e9c 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,4 +1,5 @@ sj201-interface~=0.0.2 ovos-plugin-manager~=0.0.20 ovos-utils~=0.0.26 -ovos-bus-client~=0.0.3 \ No newline at end of file +ovos-bus-client~=0.0.3 +gpiozero~=2.0 \ No newline at end of file