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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 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="?")