From 4cc07b2e17e3118c8ce4a8e12626209caa1d935c Mon Sep 17 00:00:00 2001 From: Perry Melange Date: Sun, 19 Jan 2025 14:39:56 +0100 Subject: [PATCH 1/3] button protocol and driver: Add a new protocol and driver for a Button A button can be used to simulate pressing a physical button on a device. For example a DigitalOutputButtonDriver can be used when a reset button is connected via a relay to a DigitalOutput. In addition there are the ManualButtonDriver and ExternalButtonDriver drivers analogous to the ManualPowerDriver and ExternalPowerDriver drivers. Operations on a button include "press", "release", "press_for", and "get". Signed-off-by: Perry Melange --- labgrid/driver/__init__.py | 2 + labgrid/driver/buttondriver.py | 103 +++++++++++++++++++++++++++++ labgrid/protocol/__init__.py | 1 + labgrid/protocol/buttonprotocol.py | 25 +++++++ 4 files changed, 131 insertions(+) create mode 100644 labgrid/driver/buttondriver.py create mode 100644 labgrid/protocol/buttonprotocol.py diff --git a/labgrid/driver/__init__.py b/labgrid/driver/__init__.py index edf1ad2b1..43fb750a6 100644 --- a/labgrid/driver/__init__.py +++ b/labgrid/driver/__init__.py @@ -16,6 +16,8 @@ DigitalOutputPowerDriver, YKUSHPowerDriver, \ USBPowerDriver, SiSPMPowerDriver, NetworkPowerDriver, \ PDUDaemonDriver +from .buttondriver import ManualButtonDriver, ExternalButtonDriver, \ + DigitalOutputButtonDriver from .usbloader import MXSUSBDriver, IMXUSBDriver, BDIMXUSBDriver, RKUSBDriver, UUUDriver from .usbsdmuxdriver import USBSDMuxDriver from .usbsdwiredriver import USBSDWireDriver diff --git a/labgrid/driver/buttondriver.py b/labgrid/driver/buttondriver.py new file mode 100644 index 000000000..60f6288ed --- /dev/null +++ b/labgrid/driver/buttondriver.py @@ -0,0 +1,103 @@ +import shlex +import time + +import attr + +from ..factory import target_factory +from ..protocol import ButtonProtocol, DigitalOutputProtocol +from ..step import step +from ..util.helper import processwrapper +from .common import Driver + + +@target_factory.reg_driver +@attr.s(eq=False) +class ManualButtonDriver(Driver, ButtonProtocol): + """ManualButtonDriver - Driver to tell the user to control a target's button""" + + @Driver.check_active + @step() + def press(self): + self.target.interact( + f"Press and hold the button on target {self.target.name} and press enter" + ) + + @Driver.check_active + @step() + def release(self): + self.target.interact( + f"Release the button on the target {self.target.name} press enter" + ) + + @Driver.check_active + @step() + def press_for(self): + self.target.interact( + f"Press and then Release the button on target {self.target.name} for {self.delay} seconds and press enter" + ) + +@target_factory.reg_driver +@attr.s(eq=False) +class ExternalButtonDriver(Driver, ButtonProtocol): + """ExternalButtonDriver - Driver using an external command to control a target's button""" + cmd_press = attr.ib(validator=attr.validators.instance_of(str)) + cmd_release = attr.ib(validator=attr.validators.instance_of(str)) + cmd_press_for = attr.ib(validator=attr.validators.instance_of(str)) + delay = attr.ib(default=1.0, validator=attr.validators.instance_of(float)) + + @Driver.check_active + @step() + def press(self): + cmd = shlex.split(self.cmd_press) + processwrapper.check_output(cmd) + + @Driver.check_active + @step() + def release(self): + cmd = shlex.split(self.cmd_release) + processwrapper.check_output(cmd) + + @Driver.check_active + @step() + def press_for(self): + if self.cmd_press_for is not None: + cmd = shlex.split(self.cmd_press_for) + processwrapper.check_output(cmd) + else: + self.press() + time.sleep(self.delay) + self.release() + +@target_factory.reg_driver +@attr.s(eq=False) +class DigitalOutputButtonDriver(Driver, ButtonProtocol): + """ + DigitalOutputButtonDriver uses a DigitalOutput to control a button + """ + bindings = {"output": DigitalOutputProtocol, } + delay = attr.ib(default=1.0, validator=attr.validators.instance_of(float)) + + def __attrs_post_init__(self): + super().__attrs_post_init__() + + @Driver.check_active + @step() + def press(self): + self.output.set(True) + + @Driver.check_active + @step() + def release(self): + self.output.set(False) + + @Driver.check_active + @step() + def press_for(self): + self.press() + time.sleep(self.delay) + self.release() + + @Driver.check_active + @step() + def get(self): + return self.output.get() diff --git a/labgrid/protocol/__init__.py b/labgrid/protocol/__init__.py index 0ac225622..749539c81 100644 --- a/labgrid/protocol/__init__.py +++ b/labgrid/protocol/__init__.py @@ -3,6 +3,7 @@ from .consoleprotocol import ConsoleProtocol from .linuxbootprotocol import LinuxBootProtocol from .powerprotocol import PowerProtocol +from .buttonprotocol import ButtonProtocol from .filetransferprotocol import FileTransferProtocol from .infoprotocol import InfoProtocol from .digitaloutputprotocol import DigitalOutputProtocol diff --git a/labgrid/protocol/buttonprotocol.py b/labgrid/protocol/buttonprotocol.py new file mode 100644 index 000000000..abc4a292e --- /dev/null +++ b/labgrid/protocol/buttonprotocol.py @@ -0,0 +1,25 @@ +import abc + + +class ButtonProtocol(abc.ABC): + """Abstract class providing the ButtonProtocol interface""" + + @abc.abstractmethod + def press(self): + """Implementations should "press and hold" the button.""" + raise NotImplementedError + + @abc.abstractmethod + def release(self): + """Implementations should "release" the button""" + raise NotImplementedError + + @abc.abstractmethod + def press_for(self, time: float): + """Implementations should "press" the button for time seconds and then "release" the button again""" + raise NotImplementedError + + @abc.abstractmethod + def get(self): + """Implementations should return the status of the button""" + raise NotImplementedError From ea9f1d83f2c1746934d79bfe530ea1e24c73bfcd Mon Sep 17 00:00:00 2001 From: Perry Melange Date: Fri, 14 Feb 2025 12:47:46 +0100 Subject: [PATCH 2/3] client: add client commands to SysfsGPIO Add support for the button protocol for SysfsGPIO to labgrid-client Signed-off-by: Perry Melange --- labgrid/remote/client.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/labgrid/remote/client.py b/labgrid/remote/client.py index 27108a7b4..293628d89 100755 --- a/labgrid/remote/client.py +++ b/labgrid/remote/client.py @@ -919,6 +919,33 @@ def power(self): if action == "get": print(f"power{' ' + name if name else ''} for place {place.name} is {'on' if res else 'off'}") + def button(self): + place = self.get_acquired_place() + action = self.args.action + delay = self.args.delay + name = self.args.name + target = self._get_target(place) + from ..resource.remote import NetworkSysfsGPIO + + drv = None + try: + drv = target.get_driver("ButtonProtocol", name=name) + except NoDriverFoundError: + for resource in target.resources: + if isinstance(resource, NetworkSysfsGPIO): + self._get_driver_or_new(target, "GpioDigitalOutputDriver", name=name) + drv = self._get_driver_or_new(target, "DigitalOutputButtonDriver", name=name) + if drv: + break + + if not drv: + raise UserError("target has no compatible resource available") + if delay is not None: + drv.delay = delay + res = getattr(drv, action)() + if action == "get": + print(f"button{' ' + name if name else ''} for place {place.name} is {'pressed' if res else 'released'}") + def digital_io(self): place = self.get_acquired_place() action = self.args.action @@ -1868,6 +1895,14 @@ def main(): subparser.add_argument("--name", "-n", help="optional resource name") subparser.set_defaults(func=ClientSession.power) + subparser = subparsers.add_parser("button", help="change (or get) a place's button status") + subparser.add_argument("action", choices=["press", "release", "press_for", "get"]) + subparser.add_argument( + "-t", "--delay", type=float, default=None, help="wait time in seconds between the press and release during press_for" + ) + subparser.add_argument("--name", "-n", help="optional resource name") + subparser.set_defaults(func=ClientSession.button) + subparser = subparsers.add_parser("io", help="change (or get) a digital IO status") subparser.add_argument("action", choices=["high", "low", "get"], help="action") subparser.add_argument("name", help="optional resource name", nargs="?") From 1f55372839b069521b509adff547c6735f2ded38 Mon Sep 17 00:00:00 2001 From: Perry Melange Date: Fri, 14 Feb 2025 12:52:12 +0100 Subject: [PATCH 3/3] doc/configuration.rst: Add new Button drivers Add documentation for ManualButtonDriver, ExternalButtonDriver, DigitalOutputButtonDriver. Signed-off-by: Perry Melange --- doc/configuration.rst | 71 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/doc/configuration.rst b/doc/configuration.rst index d93e58c90..640a2a347 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -2313,6 +2313,72 @@ Implements: Arguments: - delay (float, default=2.0): delay in seconds between off and on +ManualButtonDriver +~~~~~~~~~~~~~~~~~~ +A :any:`ManualButtonDriver` requires the user to control the taget button. +This is required if a strategy is used with the target, but no automatic +button control is available. + +The driver's name will be displayed during interaction. + +Binds to: + - None + +Implements: + - :any:`ButtonProtocol` + +.. code-block:: yaml + + ManualButtonDriver: + name: 'example-board' + +Arguments: + - None + +ExternalButtonDriver +~~~~~~~~~~~~~~~~~~~~ +An :any:`ExternalButtonDriver` is used to control a target button via an +external command. + +Binds to: + - None + +Implements: + - :any:`ButtonProtocol` + +.. code-block:: yaml + + ExternalButtonDriver: + cmd_press: 'example_command press and hold' + cmd_release: 'example_command release' + cmd_press_for: 'example_command press_for' + delay: 2.0 + +Arguments: + - cmd_press (str): command to press and hold the button on the board + - cmd_release (str): command to release the button on the board + - cmd_press_for (str): command to press, pause, and release the button on the board + - delay (float, default=1.0): delay in seconds when calling press_for + +DigitalOutputButtonDriver +~~~~~~~~~~~~~~~~~~~~~~~~~ +A :any:`DigitalOutputButtonDriver` is used to control a target button via a +DigitalOutputDriver + +Binds to: + - :any:`DigitalOutputProtocol` + +Implements: + - :any:`ButtonProtocol` + +.. code-block:: yaml + + DigitalOutputButtonDriver: + delay: 2.0 + +Arguments: + - delay (float, default=1.0): delay in seconds when calling press_for + GpioDigitalOutputDriver ~~~~~~~~~~~~~~~~~~~~~~~ The :any:`GpioDigitalOutputDriver` writes a digital signal to a GPIO line. @@ -2333,10 +2399,11 @@ Implements: .. code-block:: yaml - GpioDigitalOutputDriver: {} + GpioDigitalOutputDriver: + delay: 2.0 Arguments: - - None + - delay (float, default=1.0): delay in seconds between off and on for a power cycle or between states for button press_for SerialPortDigitalOutputDriver ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~