diff --git a/doc/configuration.rst b/doc/configuration.rst index 577b78810..72f4ef1bc 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -260,17 +260,18 @@ Used by: YKUSHPowerPort ++++++++++++++ -A YKUSHPowerPort describes a YEPKIT YKUSH USB (HID) switchable USB hub. +A YKUSHPowerPort describes a YEPKIT YKUSH family USB (HID) switchable USB hub. +Models supported include YKUSH, YKUSH 3, and YKUSH XS. .. code-block:: yaml YKUSHPowerPort: - serial: YK12345 + serial: Y3N12345 index: 1 -The example describes port 1 on the YKUSH USB hub with the -serial "YK12345". -(use "ykushcmd -l" to get your serial...) +The example describes port 1 on the YKUSH 3 USB hub with the +serial "Y3N12345". +(use "ykushcmd ykush3 -l" to get your serial...) Arguments: - serial (str): serial number of the YKUSH hub diff --git a/labgrid/driver/powerdriver.py b/labgrid/driver/powerdriver.py index 80c8377fb..fc9ac3dd5 100644 --- a/labgrid/driver/powerdriver.py +++ b/labgrid/driver/powerdriver.py @@ -13,6 +13,7 @@ from ..util.helper import processwrapper from .common import Driver from .exception import ExecutionError +from ..exceptions import InvalidConfigError @attr.s(eq=False) @@ -283,11 +284,31 @@ def __attrs_post_init__(self): else: self.tool = 'ykushcmd' + def on_activate(self): + # Product ID (PID): 0xF0CD + # https://www.yepkit.com/uploads/documents/41cce_YKUSHXS_ProductManual_v0.0.1.pdf + if self.port.model_id == 0xf0cd: + self.board_name = "ykushxs" + # "The board USB control device can be accessed by the USB host using + # the Vendor ID (VID) and Product ID (PID), which is 0x04D8 and + # 0xF11B respectively" + # https://www.yepkit.com/uploads/documents/9f39a_ykush3-datasheet.pdf + elif self.port.model_id == 0xf11b: + self.board_name = "ykush3" + # Product ID (PID): 0xF2F7 + # https://www.yepkit.com/uploads/documents/e9f11_YKUSH_ProductManual_v1.1.1.pdf + elif self.port.model_id == 0xf2f7: + self.board_name = "ykush" + else: + raise InvalidConfigError("Unknown YKUSH device {:04x}".format(self.port.model_id)) + + @Driver.check_active @step() def on(self): cmd = [ self.tool, + f"{self.board_name}", "-s", f"{self.port.serial}", "-u", f"{self.port.index}" ] @@ -298,6 +319,7 @@ def on(self): def off(self): cmd = [ self.tool, + f"{self.board_name}", "-s", f"{self.port.serial}", "-d", f"{self.port.index}" ] @@ -314,6 +336,7 @@ def cycle(self): def get(self): cmd = [ self.tool, + f"{self.board_name}", "-s", f"{self.port.serial}", "-g", f"{self.port.index}" ] diff --git a/labgrid/resource/ykushpowerport.py b/labgrid/resource/ykushpowerport.py index b7a967816..37c599e7f 100644 --- a/labgrid/resource/ykushpowerport.py +++ b/labgrid/resource/ykushpowerport.py @@ -1,29 +1,56 @@ import attr from ..factory import target_factory -from .common import NetworkResource, Resource +from .remote import RemoteUSBResource +from .udev import USBResource @target_factory.reg_resource @attr.s(eq=False) -class YKUSHPowerPort(Resource): +class YKUSHPowerPort(USBResource): """This resource describes a YEPKIT YKUSH switchable USB hub. Args: serial (str): serial of the YKUSH device index (int): port index""" - serial = attr.ib(validator=attr.validators.instance_of(str)) - index = attr.ib(validator=attr.validators.instance_of(int), + serial = attr.ib(default=None, validator=attr.validators.instance_of(str)) + index = attr.ib(default=None, validator=attr.validators.instance_of(int), converter=int) + def filter_match(self, device): + if device.properties.get('ID_VENDOR_ID') != "04d8": + return False + + # https://www.yepkit.com/uploads/documents/41cce_YKUSHXS_ProductManual_v0.0.1.pdf + # Vendor ID (VID): 0x04D8 + # Product ID (PID): 0xF0CD + + # https://www.yepkit.com/uploads/documents/e9f11_YKUSH_ProductManual_v1.1.1.pdf + # Vendor ID (VID): 0x04D8 + # Product ID (PID): 0xF2F7 + + # "The board USB control device can be accessed by the USB host using + # the Vendor ID (VID) and Product ID (PID), which is 0x04D8 and + # 0xF11B respectively" + # https://www.yepkit.com/uploads/documents/9f39a_ykush3-datasheet.pdf + + if device.properties.get('ID_MODEL_ID') not in ["f0cd", "f11b", "f2f7"]: + return False + + if device.properties.get('ID_SERIAL_SHORT') != self.serial: + return False + + return super().filter_match(device) + + @target_factory.reg_resource @attr.s(eq=False) -class NetworkYKUSHPowerPort(NetworkResource): +class NetworkYKUSHPowerPort(RemoteUSBResource): """"This resource describes a remote YEPKIT YKUSH switchable USB hub. Args: serial (str): serial of the YKUSH device index (int): port index""" - serial = attr.ib(validator=attr.validators.instance_of(str)) - index = attr.ib(validator=attr.validators.instance_of(int), + serial = attr.ib(default=None, validator=attr.validators.instance_of(str)) + index = attr.ib(default=None, validator=attr.validators.instance_of(int), converter=int) diff --git a/tests/test_powerdriver.py b/tests/test_powerdriver.py index 3c7b3dbab..f69d1b067 100644 --- a/tests/test_powerdriver.py +++ b/tests/test_powerdriver.py @@ -2,8 +2,14 @@ import pytest -from labgrid.resource import NetworkPowerPort -from labgrid.driver.powerdriver import ExternalPowerDriver, ManualPowerDriver, NetworkPowerDriver +from labgrid.resource import NetworkPowerPort, YKUSHPowerPort +from labgrid.driver.powerdriver import ( + ExternalPowerDriver, + ManualPowerDriver, + NetworkPowerDriver, + YKUSHPowerDriver, +) +from labgrid.util.helper import processwrapper class TestManualPowerDriver: @@ -264,3 +270,42 @@ def test_import_backend_tplink(self): def test_import_backend_siglent(self): pytest.importorskip("vxi11") import labgrid.driver.power.siglent + +class TestYKUSHPowerDriver: + FAKE_SERIAL = 'YK12345' + def test_create(self, target): + resource = YKUSHPowerPort(target, 'power', serial=self.FAKE_SERIAL, index=1) + device = YKUSHPowerDriver(target, 'power') + assert isinstance(device, YKUSHPowerDriver) + + def test_default_off(self, target, mocker): + check_call_mock = mocker.patch( + 'labgrid.util.helper.processwrapper.check_output' + ) + model_id_mock = mocker.patch('labgrid.resource.ykushpowerport.YKUSHPowerPort.model_id', new_callable=mocker.PropertyMock) + model_id_mock.return_value = 0xf2f7 + resource = YKUSHPowerPort(target, 'power', serial=self.FAKE_SERIAL, index=2) + resource.avail = True + device = YKUSHPowerDriver(target, 'power') + target.activate(device) + device.off() + + check_call_mock.assert_called_with( + ['ykushcmd', 'ykush', '-s', self.FAKE_SERIAL, '-d', '2'] + ) + + def test_ykush3_on(self, target, mocker): + check_call_mock = mocker.patch( + 'labgrid.util.helper.processwrapper.check_output' + ) + model_id_mock = mocker.patch('labgrid.resource.ykushpowerport.YKUSHPowerPort.model_id', new_callable=mocker.PropertyMock) + model_id_mock.return_value = 0xf11b + resource = YKUSHPowerPort(target, 'power', serial=self.FAKE_SERIAL, index=3) + resource.avail = True + device = YKUSHPowerDriver(target, 'power') + target.activate(device) + device.on() + + check_call_mock.assert_called_with( + ['ykushcmd', 'ykush3', '-s', self.FAKE_SERIAL, '-u', '3'] + )