diff --git a/README.md b/README.md index 98228ef..69806ae 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,48 @@ Button entities for each script defined on your Media Server: - Shutdown, restart, sleep, hibernate - Custom scripts +### Execute Script Service + +Call `remote_media_player.execute_script` to run any server-defined script with typed parameters: + +```yaml +service: remote_media_player.execute_script +data: + script_name: set_brightness + params: + level: 75 + monitor: primary +``` + +Parameters are validated against the script's schema on the server. Scripts define their parameters in `config.yaml`: + +```yaml +scripts: + set_brightness: + command: "python set_brightness.py" + label: "Set Brightness" + icon: "mdi:brightness-6" + timeout: 10 + parameters: + level: + type: integer + required: true + min: 0 + max: 100 + description: "Brightness level (0-100)" + monitor: + type: select + options: ["primary", "secondary", "all"] + default: "primary" + description: "Target monitor" +``` + +Supported parameter types: `string`, `integer`, `float`, `boolean`, `select`. + +Parameters are passed to scripts as environment variables prefixed with `SCRIPT_PARAM_` (e.g., `SCRIPT_PARAM_LEVEL=75`, `SCRIPT_PARAM_MONITOR=primary`). + +Scripts without parameters work as before — just omit `params`. + ## Example Lovelace Card ```yaml diff --git a/custom_components/remote_media_player/__init__.py b/custom_components/remote_media_player/__init__.py index a587bb9..e260b29 100644 --- a/custom_components/remote_media_player/__init__.py +++ b/custom_components/remote_media_player/__init__.py @@ -15,8 +15,8 @@ from homeassistant.helpers import config_validation as cv from .api_client import MediaServerClient, MediaServerError from .const import ( ATTR_FILE_PATH, - ATTR_SCRIPT_ARGS, ATTR_SCRIPT_NAME, + ATTR_SCRIPT_PARAMS, CONF_HOST, CONF_PORT, CONF_TOKEN, @@ -33,9 +33,7 @@ PLATFORMS: list[Platform] = [Platform.MEDIA_PLAYER, Platform.BUTTON, Platform.NU SERVICE_EXECUTE_SCRIPT_SCHEMA = vol.Schema( { vol.Required(ATTR_SCRIPT_NAME): cv.string, - vol.Optional(ATTR_SCRIPT_ARGS, default=[]): vol.All( - cv.ensure_list, [cv.string] - ), + vol.Optional(ATTR_SCRIPT_PARAMS, default={}): dict, } ) @@ -83,10 +81,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_execute_script(call: ServiceCall) -> dict[str, Any]: """Execute a script on the media server.""" script_name = call.data[ATTR_SCRIPT_NAME] - script_args = call.data.get(ATTR_SCRIPT_ARGS, []) + script_params = call.data.get(ATTR_SCRIPT_PARAMS, {}) _LOGGER.debug( - "Executing script '%s' with args: %s", script_name, script_args + "Executing script '%s' with params: %s", script_name, script_params ) # Get all clients and execute on all of them @@ -94,7 +92,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: for entry_id, data in hass.data[DOMAIN].items(): client: MediaServerClient = data["client"] try: - result = await client.execute_script(script_name, script_args) + result = await client.execute_script(script_name, script_params) results[entry_id] = result _LOGGER.info( "Script '%s' executed on %s: success=%s", diff --git a/custom_components/remote_media_player/api_client.py b/custom_components/remote_media_player/api_client.py index 5bbe0f7..beb2d82 100644 --- a/custom_components/remote_media_player/api_client.py +++ b/custom_components/remote_media_player/api_client.py @@ -287,19 +287,21 @@ class MediaServerClient: return await self._request("GET", API_SCRIPTS_LIST) async def execute_script( - self, script_name: str, args: list[str] | None = None + self, + script_name: str, + params: dict[str, str | int | float | bool] | None = None, ) -> dict[str, Any]: """Execute a script on the server. Args: script_name: Name of the script to execute - args: Optional list of arguments to pass to the script + params: Optional named parameters (validated against script schema) Returns: Execution result with success, exit_code, stdout, stderr """ endpoint = f"{API_SCRIPTS_EXECUTE}/{script_name}" - json_data = {"args": args or []} + json_data = {"params": params or {}} return await self._request("POST", endpoint, json_data) async def get_media_folders(self) -> dict[str, dict[str, Any]]: diff --git a/custom_components/remote_media_player/const.py b/custom_components/remote_media_player/const.py index 485fb2f..24252dc 100644 --- a/custom_components/remote_media_player/const.py +++ b/custom_components/remote_media_player/const.py @@ -47,5 +47,5 @@ SERVICE_PLAY_MEDIA_FILE = "play_media_file" # Service attributes ATTR_SCRIPT_NAME = "script_name" -ATTR_SCRIPT_ARGS = "args" +ATTR_SCRIPT_PARAMS = "params" ATTR_FILE_PATH = "file_path" diff --git a/custom_components/remote_media_player/services.yaml b/custom_components/remote_media_player/services.yaml index 3f22882..fbd0dd0 100644 --- a/custom_components/remote_media_player/services.yaml +++ b/custom_components/remote_media_player/services.yaml @@ -9,10 +9,10 @@ execute_script: example: "launch_spotify" selector: text: - args: - name: Arguments - description: Optional list of arguments to pass to the script + params: + name: Parameters + description: Optional named parameters to pass to the script (validated against script schema) required: false - example: '["arg1", "arg2"]' + example: '{"level": 75, "monitor": "primary"}' selector: object: