1 """Expose regular shell commands as services."""
3 from __future__
import annotations
6 from contextlib
import suppress
10 import voluptuous
as vol
23 DOMAIN =
"shell_command"
27 _LOGGER = logging.getLogger(__name__)
29 CONFIG_SCHEMA = vol.Schema(
30 {DOMAIN: cv.schema_with_slug_keys(cv.string)}, extra=vol.ALLOW_EXTRA
34 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
35 """Set up the shell_command component."""
36 conf = config.get(DOMAIN, {})
38 cache: dict[str, tuple[str, str |
None, template.Template |
None]] = {}
40 async
def async_service_handler(service: ServiceCall) -> ServiceResponse:
41 """Execute a shell command service."""
42 cmd = conf[service.service]
45 prog, args, args_compiled = cache[cmd]
50 cache[cmd] = prog, args, args_compiled
52 prog, args = cmd.split(
" ", 1)
53 args_compiled = template.Template(
str(args), hass)
54 cache[cmd] = prog, args, args_compiled
58 rendered_args = args_compiled.async_render(
59 variables=service.data, parse_result=
False
62 _LOGGER.exception(
"Error rendering command template")
67 if rendered_args == args:
70 create_process = asyncio.create_subprocess_shell(
73 stdout=asyncio.subprocess.PIPE,
74 stderr=asyncio.subprocess.PIPE,
80 shlexed_cmd = [prog, *shlex.split(rendered_args)]
82 create_process = asyncio.create_subprocess_exec(
85 stdout=asyncio.subprocess.PIPE,
86 stderr=asyncio.subprocess.PIPE,
90 process = await create_process
92 async
with asyncio.timeout(COMMAND_TIMEOUT):
93 stdout_data, stderr_data = await process.communicate()
94 except TimeoutError
as err:
96 "Timed out running command: `%s`, after: %ss", cmd, COMMAND_TIMEOUT
99 with suppress(TypeError):
102 process._transport.close()
106 translation_domain=DOMAIN,
107 translation_key=
"timeout",
108 translation_placeholders={
110 "timeout":
str(COMMAND_TIMEOUT),
116 "Stdout of command: `%s`, return code: %s:\n%s",
123 "Stderr of command: `%s`, return code: %s:\n%s",
128 if process.returncode != 0:
130 "Error running command: `%s`, return code: %s", cmd, process.returncode
133 if service.return_response:
134 service_response: JsonObjectType = {
137 "returncode": process.returncode,
141 service_response[
"stdout"] = stdout_data.decode(
"utf-8").strip()
143 service_response[
"stderr"] = stderr_data.decode(
"utf-8").strip()
144 except UnicodeDecodeError
as err:
146 "Unable to handle non-utf8 output of command: `%s`", cmd
149 translation_domain=DOMAIN,
150 translation_key=
"non_utf8_output",
151 translation_placeholders={
"command": cmd},
153 return service_response
157 hass.services.async_register(
160 async_service_handler,
161 supports_response=SupportsResponse.OPTIONAL,
bool async_setup(HomeAssistant hass, ConfigType config)