Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move yaml configuration to integration key for command_line #92824

Merged
merged 24 commits into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ build.json @home-assistant/supervisor
/tests/components/color_extractor/ @GenericStudent
/homeassistant/components/comfoconnect/ @michaelarnauts
/tests/components/comfoconnect/ @michaelarnauts
/homeassistant/components/command_line/ @gjohansson-ST
/tests/components/command_line/ @gjohansson-ST
/homeassistant/components/compensation/ @Petro31
/tests/components/compensation/ @Petro31
/homeassistant/components/config/ @home-assistant/core
Expand Down
176 changes: 176 additions & 0 deletions homeassistant/components/command_line/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,177 @@
"""The command_line component."""
from __future__ import annotations

import asyncio
from collections.abc import Coroutine
import logging
from typing import Any

import voluptuous as vol

from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA as BINARY_SENSOR_DEVICE_CLASSES_SCHEMA,
DOMAIN as BINARY_SENSOR_DOMAIN,
)
from homeassistant.components.cover import DOMAIN as COVER_DOMAIN
from homeassistant.components.notify import DOMAIN as NOTIFY_DOMAIN
from homeassistant.components.sensor import (
CONF_STATE_CLASS,
DEVICE_CLASSES_SCHEMA as SENSOR_DEVICE_CLASSES_SCHEMA,
DOMAIN as SENSOR_DOMAIN,
STATE_CLASSES_SCHEMA as SENSOR_STATE_CLASSES_SCHEMA,
)
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.const import (
CONF_COMMAND,
CONF_COMMAND_CLOSE,
CONF_COMMAND_OFF,
CONF_COMMAND_ON,
CONF_COMMAND_OPEN,
CONF_COMMAND_STATE,
CONF_COMMAND_STOP,
CONF_DEVICE_CLASS,
CONF_ICON,
CONF_NAME,
CONF_PAYLOAD_OFF,
CONF_PAYLOAD_ON,
CONF_UNIQUE_ID,
CONF_UNIT_OF_MEASUREMENT,
CONF_VALUE_TEMPLATE,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType

from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN

BINARY_SENSOR_DEFAULT_NAME = "Binary Command Sensor"
DEFAULT_PAYLOAD_ON = "ON"
DEFAULT_PAYLOAD_OFF = "OFF"
CONF_JSON_ATTRIBUTES = "json_attributes"
SENSOR_DEFAULT_NAME = "Command Sensor"
CONF_NOTIFIERS = "notifiers"

PLATFORM_MAPPING = {
BINARY_SENSOR_DOMAIN: Platform.BINARY_SENSOR,
COVER_DOMAIN: Platform.COVER,
NOTIFY_DOMAIN: Platform.NOTIFY,
SENSOR_DOMAIN: Platform.SENSOR,
SWITCH_DOMAIN: Platform.SWITCH,
}

_LOGGER = logging.getLogger(__name__)

BINARY_SENSOR_SCHEMA = vol.Schema(
{
vol.Required(CONF_COMMAND): cv.string,
vol.Optional(CONF_NAME, default=BINARY_SENSOR_DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_DEVICE_CLASS): BINARY_SENSOR_DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_UNIQUE_ID): cv.string,
}
)
COVER_SCHEMA = vol.Schema(
{
vol.Optional(CONF_COMMAND_CLOSE, default="true"): cv.string,
vol.Optional(CONF_COMMAND_OPEN, default="true"): cv.string,
vol.Optional(CONF_COMMAND_STATE): cv.string,
vol.Optional(CONF_COMMAND_STOP, default="true"): cv.string,
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_UNIQUE_ID): cv.string,
}
)
NOTIFY_SCHEMA = vol.Schema(
{
vol.Required(CONF_COMMAND): cv.string,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
}
)
SENSOR_SCHEMA = vol.Schema(
{
vol.Required(CONF_COMMAND): cv.string,
vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_JSON_ATTRIBUTES): cv.ensure_list_csv,
vol.Optional(CONF_NAME, default=SENSOR_DEFAULT_NAME): cv.string,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_DEVICE_CLASS): SENSOR_DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_STATE_CLASS): SENSOR_STATE_CLASSES_SCHEMA,
}
)
SWITCH_SCHEMA = vol.Schema(
{
vol.Optional(CONF_COMMAND_OFF, default="true"): cv.string,
vol.Optional(CONF_COMMAND_ON, default="true"): cv.string,
vol.Optional(CONF_COMMAND_STATE): cv.string,
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_ICON): cv.template,
vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_UNIQUE_ID): cv.string,
}
)
COMBINED_SCHEMA = vol.Schema(
{
vol.Optional(BINARY_SENSOR_DOMAIN): BINARY_SENSOR_SCHEMA,
vol.Optional(COVER_DOMAIN): COVER_SCHEMA,
vol.Optional(NOTIFY_DOMAIN): NOTIFY_SCHEMA,
vol.Optional(SENSOR_DOMAIN): SENSOR_SCHEMA,
vol.Optional(SWITCH_DOMAIN): SWITCH_SCHEMA,
}
)
CONFIG_SCHEMA = vol.Schema(
{
vol.Optional(DOMAIN): vol.All(
cv.ensure_list,
[COMBINED_SCHEMA],
)
},
extra=vol.ALLOW_EXTRA,
)


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up Command Line from yaml config."""
command_line_config: list[dict[str, dict[str, Any]]] = config.get(DOMAIN, {})
gjohansson-ST marked this conversation as resolved.
Show resolved Hide resolved
if not command_line_config:
return True

_LOGGER.debug("Full config loaded: %s", command_line_config)

load_coroutines: list[Coroutine[Any, Any, None]] = []
platforms: list[Platform] = []
for platform_config in command_line_config:
for platform, _config in platform_config.items():
platforms.append(PLATFORM_MAPPING[platform])
_LOGGER.debug(
"Loading config %s for platform %s",
platform_config,
PLATFORM_MAPPING[platform],
)
load_coroutines.append(
discovery.async_load_platform(
hass,
PLATFORM_MAPPING[platform],
DOMAIN,
_config,
config,
)
)

await async_setup_reload_service(hass, DOMAIN, platforms)

if load_coroutines:
_LOGGER.debug("Loading platforms: %s", platforms)
await asyncio.gather(*load_coroutines)

return True
39 changes: 27 additions & 12 deletions homeassistant/components/command_line/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA,
DOMAIN as BINARY_SENSOR_DOMAIN,
PLATFORM_SCHEMA,
BinarySensorDeviceClass,
BinarySensorEntity,
Expand All @@ -23,11 +24,11 @@
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN, PLATFORMS
from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN
from .sensor import CommandSensorData

DEFAULT_NAME = "Binary Command Sensor"
Expand Down Expand Up @@ -59,16 +60,30 @@ async def async_setup_platform(
) -> None:
"""Set up the Command line Binary Sensor."""

await async_setup_reload_service(hass, DOMAIN, PLATFORMS)

name: str = config.get(CONF_NAME, DEFAULT_NAME)
command: str = config[CONF_COMMAND]
payload_off: str = config[CONF_PAYLOAD_OFF]
payload_on: str = config[CONF_PAYLOAD_ON]
device_class: BinarySensorDeviceClass | None = config.get(CONF_DEVICE_CLASS)
value_template: Template | None = config.get(CONF_VALUE_TEMPLATE)
command_timeout: int = config[CONF_COMMAND_TIMEOUT]
unique_id: str | None = config.get(CONF_UNIQUE_ID)
if binary_sensor_config := config:
async_create_issue(
hass,
DOMAIN,
"deprecated_yaml_binary_sensor",
breaks_in_ha_version="2023.8.0",
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key="deprecated_platform_yaml",
translation_placeholders={"platform": BINARY_SENSOR_DOMAIN},
)
if discovery_info:
binary_sensor_config = discovery_info

name: str = binary_sensor_config.get(CONF_NAME, DEFAULT_NAME)
command: str = binary_sensor_config[CONF_COMMAND]
payload_off: str = binary_sensor_config[CONF_PAYLOAD_OFF]
payload_on: str = binary_sensor_config[CONF_PAYLOAD_ON]
device_class: BinarySensorDeviceClass | None = binary_sensor_config.get(
CONF_DEVICE_CLASS
)
value_template: Template | None = binary_sensor_config.get(CONF_VALUE_TEMPLATE)
command_timeout: int = binary_sensor_config[CONF_COMMAND_TIMEOUT]
unique_id: str | None = binary_sensor_config.get(CONF_UNIQUE_ID)
if value_template is not None:
value_template.hass = hass
data = CommandSensorData(hass, command, command_timeout)
Expand Down
38 changes: 30 additions & 8 deletions homeassistant/components/command_line/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,31 @@

import voluptuous as vol

from homeassistant.components.cover import PLATFORM_SCHEMA, CoverEntity
from homeassistant.components.cover import (
DOMAIN as COVER_DOMAIN,
PLATFORM_SCHEMA,
CoverEntity,
)
from homeassistant.const import (
CONF_COMMAND_CLOSE,
CONF_COMMAND_OPEN,
CONF_COMMAND_STATE,
CONF_COMMAND_STOP,
CONF_COVERS,
CONF_FRIENDLY_NAME,
CONF_NAME,
CONF_UNIQUE_ID,
CONF_VALUE_TEMPLATE,
)
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import slugify

from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN, PLATFORMS
from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN
from .utils import call_shell_with_timeout, check_output_or_log

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -55,19 +61,35 @@ async def async_setup_platform(
) -> None:
"""Set up cover controlled by shell commands."""

await async_setup_reload_service(hass, DOMAIN, PLATFORMS)

devices: dict[str, Any] = config.get(CONF_COVERS, {})
covers = []
if discovery_info:
entities: dict[str, Any] = {slugify(discovery_info[CONF_NAME]): discovery_info}
else:
async_create_issue(
hass,
DOMAIN,
"deprecated_yaml_cover",
breaks_in_ha_version="2023.8.0",
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key="deprecated_platform_yaml",
translation_placeholders={"platform": COVER_DOMAIN},
)
entities = config.get(CONF_COVERS, {})

for device_name, device_config in devices.items():
for device_name, device_config in entities.items():
value_template: Template | None = device_config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass

if name := device_config.get(
CONF_FRIENDLY_NAME
): # Backward compatibility. Can be removed after deprecation
device_config[CONF_NAME] = name

covers.append(
CommandCover(
device_config.get(CONF_FRIENDLY_NAME, device_name),
device_config.get(CONF_NAME, device_name),
device_config[CONF_COMMAND_OPEN],
device_config[CONF_COMMAND_CLOSE],
device_config[CONF_COMMAND_STOP],
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/command_line/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"domain": "command_line",
"name": "Command Line",
"codeowners": [],
"codeowners": ["@gjohansson-ST"],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

"documentation": "https://www.home-assistant.io/integrations/command_line",
"iot_class": "local_polling"
}
26 changes: 22 additions & 4 deletions homeassistant/components/command_line/notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@

import voluptuous as vol

from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService
from homeassistant.components.notify import (
DOMAIN as NOTIFY_DOMAIN,
PLATFORM_SCHEMA,
BaseNotificationService,
)
from homeassistant.const import CONF_COMMAND, CONF_NAME
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.issue_registry import IssueSeverity, create_issue
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util.process import kill_subprocess

from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT
from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT, DOMAIN

_LOGGER = logging.getLogger(__name__)

Expand All @@ -33,8 +38,21 @@ def get_service(
discovery_info: DiscoveryInfoType | None = None,
) -> CommandLineNotificationService:
"""Get the Command Line notification service."""
command: str = config[CONF_COMMAND]
timeout: int = config[CONF_COMMAND_TIMEOUT]
if notify_config := config:
create_issue(
hass,
DOMAIN,
"deprecated_yaml_notify",
breaks_in_ha_version="2023.8.0",
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key="deprecated_platform_yaml",
translation_placeholders={"platform": NOTIFY_DOMAIN},
)
if discovery_info:
notify_config = discovery_info
command: str = notify_config[CONF_COMMAND]
timeout: int = notify_config[CONF_COMMAND_TIMEOUT]

return CommandLineNotificationService(command, timeout)

Expand Down