From 94b1ec8f3d4d277e3ae5979e076bfe404667d2e6 Mon Sep 17 00:00:00 2001 From: Wiktor Ogrodnik Date: Fri, 16 Jun 2023 13:38:10 +0200 Subject: [PATCH] Adds an option to pass devices with yaml (#47) Fixes an error with changing i2c parameters Updates documentation --- .github/workflows/pull-request.yml | 9 +- .github/workflows/release.yml | 9 +- .github/workflows/run-locally.yml | 9 +- .python-version | 1 + action/devices.py | 199 +++++++++++++++++++++-------- docs/Devices.md | 53 ++++++-- 6 files changed, 210 insertions(+), 70 deletions(-) create mode 100644 .python-version diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 7e1e54b..4013de2 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -114,9 +114,12 @@ jobs: python pyrav4l2/.github/save_examples.py pyrav4l2/README.md python examples/controls-enumeration.py devices: | - vivid - gpio 0 16 - i2c 0x1C + vivid: + gpio: + left-bound: 0 + right-bound: 16 + i2c: + chip-addr: 0x1C python-packages: | git+https://github.com/antmicro/pyrav4l2.git@3c071a7494b6b67263c4dddb87b47025338fd960 git+https://github.com/antmicro/tuttest.git@c44309e0365c54759fb36864fb77bf8b347bd647 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6a3cfa7..be8fc6d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -133,9 +133,12 @@ jobs: python pyrav4l2/.github/save_examples.py pyrav4l2/README.md python examples/controls-enumeration.py devices: | - vivid - gpio 0 16 - i2c 0x1C + vivid: + gpio: + left-bound: 0 + right-bound: 16 + i2c: + chip-addr: 0x1C python-packages: | git+https://github.com/antmicro/pyrav4l2.git@3c071a7494b6b67263c4dddb87b47025338fd960 git+https://github.com/antmicro/tuttest.git@c44309e0365c54759fb36864fb77bf8b347bd647 diff --git a/.github/workflows/run-locally.yml b/.github/workflows/run-locally.yml index 933975b..0a0b2d8 100644 --- a/.github/workflows/run-locally.yml +++ b/.github/workflows/run-locally.yml @@ -24,9 +24,12 @@ jobs: python pyrav4l2/.github/save_examples.py pyrav4l2/README.md python examples/controls-enumeration.py devices: | - vivid - gpio 0 16 - i2c 0x1C + vivid: + gpio: + left-bound: 0 + right-bound: 16 + i2c: + chip-addr: 0x1C python-packages: | git+https://github.com/antmicro/pyrav4l2.git@3c071a7494b6b67263c4dddb87b47025338fd960 git+https://github.com/antmicro/tuttest.git@c44309e0365c54759fb36864fb77bf8b347bd647 diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..afd844e --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +renode-linux-runner-action diff --git a/action/devices.py b/action/devices.py index a2c0da6..192f3b2 100644 --- a/action/devices.py +++ b/action/devices.py @@ -14,10 +14,12 @@ from common import error -from typing import Protocol, Any, Dict +from typing import Protocol, Any, Dict, Tuple, Iterator from dataclasses import dataclass from string import hexdigits +import yaml + class Action(Protocol): """ @@ -50,8 +52,8 @@ def __init__(self) -> None: def __call__(self, args: list[str]) -> list[str]: - assert len(args) >= 2, "not enough parameters passed" - assert args[0].isdecimal() and args[1].isdecimal() + assert len(args) == 2, "not enough parameters passed" + assert all(type(arg) is int for arg in args) or all(arg.isdecimal() for arg in args) l, r = int(args[0]), int(args[1]) gpio_ranges_params = [] @@ -65,11 +67,19 @@ def __call__(self, args: list[str]) -> list[str]: return [','.join([str(i) for i in gpio_ranges_params])] - def check_args(self, args: list[str]) -> bool: - return len(args) >= 2 and \ - args[0].isdecimal() and \ - args[1].isdecimal() and \ - int(args[0]) < int(args[1]) + def check_args(self, args: list[int | str]) -> bool: + + if len(args) != 2: + return False + + if type(args[0]) is int and type(args[1]) is int: + return args[0] < args[1] + elif type(args[0]) is str and type(args[1]) is str: + return args[0].isdecimal() and \ + args[1].isdecimal() and \ + int(args[0]) < int(args[1]) + else: + return False class I2C_SetDeviceAddress: @@ -84,47 +94,147 @@ def __call__(self, args: list[str]) -> list[str]: assert len(args) >= 1, "not enough parameters passed" - return [args[0]] + if type(args[0]) is int: + return [format(args[0], "#x")] + else: + return [args[0]] def check_args(self, args: list[str]) -> bool: - return len(args) == 1 and \ - len(args[0]) >= 3 and \ - args[0][0:2] == '0x' and \ - all(c in hexdigits for c in args[0][2:]) and \ - 3 <= int(args[0], 16) <= 119 + + if len(args) != 1: + return False + + if type(args[0]) is int: + return 3 <= args[0] <= 119 + elif type(args[0]) is str: + return len(args[0]) >= 3 and \ + args[0][0:2] == '0x' and \ + all(c in hexdigits for c in args[0][2:]) and \ + 3 <= int(args[0], 16) <= 119 + else: + return False @dataclass -class Device: +class DevicePrototype: """ Device Prototype: it stores available devices that can be added. Fields: ---------- params_list: list[str] - command_action: list[tuple[Action, int]] + command_action: list[Tuple[Action, list[str]]] defines number of parameters needed and the - Action itself + Action itself and their names (for yaml style list) """ params_list: list[str] - command_action: list[tuple[Action, int]] + command_action: list[Tuple[Action, list[str]]] + +@dataclass +class Device: + """ + Device with parameters selected by the user -available_devices = { - "vivid": Device( + Fields: + ---------- + name: name of the device + prototype: DevicePrototype for this device + args: dict with parameters (for yaml style) + or list with paramaters (for old multiline string style) + """ + name: str + prototype: DevicePrototype + args: Dict[str, str] + + +available_devices: Dict[str, DevicePrototype] = { + "vivid": DevicePrototype( [], - [(None, 0)], + [(None, [])], ), - "gpio": Device( - ["RANGES"], - [(GPIO_SplitDevice, 2)], + "gpio": DevicePrototype( + ["ranges"], + [(GPIO_SplitDevice, ["left-bound", "right-bound"])], ), - "i2c": Device( + "i2c": DevicePrototype( ["chip_addr"], - [(I2C_SetDeviceAddress, 1)], + [(I2C_SetDeviceAddress, ["chip-addr"])], ) } +def get_device(devices: str) -> Iterator[Device]: + """ + Returns the list of devices need to be added. It undersatds both yaml style list + and multiline string style list. + + Parameters + ---------- + devices: raw string from github action, syntax defined in README.md + """ + + def none_to_empty_dict(suspect: Dict[str, str] | None) -> Dict[str, str]: + return suspect if suspect is not None else {} + + def add_colon_if_no_params(line: str) -> str: + return line if ":" in line or len(line.split()) > 1 else f"{line}:" + + def device_available(device: str) -> bool: + if device not in available_devices: + print(f"WARNING: Device {device} not found") + + return device in available_devices + + devices_dict: Dict[str, Dict[str, str]] = {} + + try: + devices_dict = { + device: none_to_empty_dict(args) for device, args in yaml.load( + str.join('\n', [add_colon_if_no_params(line) for line in devices.splitlines()]), + Loader=yaml.FullLoader + ).items() if device_available(device) + } + + if any(type(args) is not dict for args in devices_dict.values()): + raise yaml.YAMLError + + except Exception: + + for device in devices.splitlines(): + device = device.split() + device_name = device[0] + device_args = device[1:] + + if not device_available(device_name): + continue + + device_prototype = available_devices[device_name] + + if len(device_args) != sum([len(i[1]) for i in device_prototype.command_action]): + print(f"WARNING: for device {device_name}, wrong number " + "of parameters, replaced with the default ones.") + + devices_dict[device_name] = {} + continue + + devices_dict[device_name] = { + param: value for param, value in zip( + sum([args[1] for args in available_devices[device_name].command_action], start=[]), + device_args + ) + } + + finally: + + for device_name, device_args in devices_dict.items(): + + yield Device( + device_name, + available_devices[device_name], + device_args, + ) + + def add_devices(devices: str) -> Dict[str, Dict[str, str]]: """ Parses arguments and commands, and adds devices to the @@ -136,41 +246,29 @@ def add_devices(devices: str) -> Dict[str, Dict[str, str]]: added_devices: Dict[str, Dict[str, str]] = {} - for device in devices.splitlines(): - device = device.split() - device_name = device[0] - - if device_name not in available_devices: - print(f"WARNING: Device {device_name} not found") - continue - - device_proto = available_devices[device_name] + for device in get_device(devices): new_device = {} - - args = device[1:] - args_pointer = 0 vars_pointer = 0 - if len(device[1:]) != sum([i[1] for i in device_proto.command_action]): - print(f"WARNING: for device {device_name}, wrong number " - "of parameters, replaced with the default ones.") + for params_hook in device.prototype.command_action: - added_devices[f"device-{device_name}"] = new_device - continue + params_action = params_hook[0] + params_list_len = len(params_hook[1]) + params = [device.args[arg] for arg in params_hook[1]] - for params_hook in device_proto.command_action: + if not all([arg in device.args.keys() for arg in params_hook[1]]): + print(f"WARNING: for device {device.name}, wrong number " + "of parameters. Some parameters replaced with the default ones.") - params_action = params_hook[0] - params_list_len = params_hook[1] - params = args[args_pointer:args_pointer + params_list_len] + continue if params_list_len > 0 and params_action: params_action: Action = params_action() if not params_action.check_args(params): - error(f"ERROR: for device {device_name} {params_action.error}.") + error(f"ERROR: for device {device.name} {params_action.error}.") variable_list = params_action(params) variable_list_len = len(variable_list) @@ -179,10 +277,9 @@ def add_devices(devices: str) -> Dict[str, Dict[str, str]]: variable_list = params variable_list_len = params_list_len - new_device |= {device_proto.params_list[i + vars_pointer]: var for i, var in enumerate(variable_list)} + new_device |= {device.prototype.params_list[i + vars_pointer].upper(): var for i, var in enumerate(variable_list)} vars_pointer += variable_list_len - args_pointer += params_list_len - added_devices[f"device-{device_name}"] = new_device + added_devices[f"device-{device.name}"] = new_device return added_devices diff --git a/docs/Devices.md b/docs/Devices.md index 83d5477..8926f48 100644 --- a/docs/Devices.md +++ b/docs/Devices.md @@ -5,7 +5,49 @@ This action offers several additional devices in the default images, which are n > **Warnings** > Adding devices may not work properly with your own kernel. If you want to use your own kernel, check that you have added the correct drivers. -## Devices syntax +## YAML style passing devices + +You can pass device configurations for inclusion with a simple YAML file. If you do not want to pass any parameters to the device, simply put its name on the next line. + +### Available devices + +- [`vivid`](https://www.kernel.org/doc/html/latest/admin-guide/media/vivid.html) - virtual device emulating a Video4Linux device +- [`gpio`](https://docs.kernel.org/admin-guide/gpio/gpio-mockup.html) - virtual device emulating GPIO lines. Optional parameters: + - left-bound: GPIO line numbers will start from this number + - right-bound: GPIO line numbers will end 1 before this number (for example, `gpio 0 64` will add 64 lines from 0 to 63) +- [`i2c`](https://www.kernel.org/doc/html/v5.10/i2c/i2c-stub.html) - virtual device emulating `I2C` bus. Optional parameter: + - chip-addr: 7 bit address 0x03 to 0x77 of the chip that simulates the EEPROM device and provides read and write commands to it. + +### Example + +```yaml +- uses: antmicro/renode-linux-runner-action@v0 + with: + renode-run: ls /dev + devices: | + gpio: + left-bound: 16 + right-bound: 32 + vivid +``` + +## Passing devices by multiline string + +Devices can also be written out simply as individual lines in a multiline string. Then all the parameters have to be listed one by one (without their names). + +For example: + +```yaml +- uses: antmicro/renode-linux-runner-action@v0 + with: + renode-run: ls /dev + devices: | + gpio 16 32 + i2c 0x1C + vivid +``` + +### Old devices syntax ```yaml - uses: antmicro/renode-linux-runner-action@v0 @@ -15,12 +57,3 @@ This action offers several additional devices in the default images, which are n device2 param1 param2 param3 ... ... ``` - -## Available devices - -- [`vivid`](https://www.kernel.org/doc/html/latest/admin-guide/media/vivid.html) - virtual device emulating a Video4Linux device -- [`gpio`](https://docs.kernel.org/admin-guide/gpio/gpio-mockup.html) - virtual device emulating GPIO lines. Optional parameters: - - left bound: GPIO line numbers will start from this number - - right bound: GPIO line numbers will end 1 before this number (for example, `gpio 0 64` will add 64 lines from 0 to 63) -- [`i2c`](https://www.kernel.org/doc/html/v5.10/i2c/i2c-stub.html) - virtual device emulating `I2C` bus. Optional parameter: - - chip_addr: 7 bit address 0x03 to 0x77 of the chip that simulates the EEPROM device and provides read and write commands to it.