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 19 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
189 changes: 189 additions & 0 deletions homeassistant/components/command_line/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,190 @@
"""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,
)
from homeassistant.components.sensor import (
CONF_STATE_CLASS,
DEVICE_CLASSES_SCHEMA as SENSOR_DEVICE_CLASSES_SCHEMA,
STATE_CLASSES_SCHEMA as SENSOR_STATE_CLASSES_SCHEMA,
)
from homeassistant.const import (
CONF_BINARY_SENSORS,
CONF_COMMAND,
CONF_COMMAND_CLOSE,
CONF_COMMAND_OFF,
CONF_COMMAND_ON,
CONF_COMMAND_OPEN,
CONF_COMMAND_STATE,
CONF_COMMAND_STOP,
CONF_COVERS,
CONF_DEVICE_CLASS,
CONF_FRIENDLY_NAME,
CONF_ICON_TEMPLATE,
CONF_NAME,
CONF_PAYLOAD_OFF,
CONF_PAYLOAD_ON,
CONF_SENSORS,
CONF_SWITCHES,
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 = {
CONF_BINARY_SENSORS: Platform.BINARY_SENSOR,
CONF_COVERS: Platform.COVER,
CONF_NOTIFIERS: Platform.NOTIFY,
CONF_SENSORS: Platform.SENSOR,
CONF_SWITCHES: 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.Optional(CONF_FRIENDLY_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.Optional(CONF_FRIENDLY_NAME): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_ICON_TEMPLATE): 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(CONF_BINARY_SENSORS): cv.schema_with_slug_keys(
BINARY_SENSOR_SCHEMA
),
vol.Optional(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA),
vol.Optional(CONF_NOTIFIERS): cv.schema_with_slug_keys(NOTIFY_SCHEMA),
vol.Optional(CONF_SENSORS): cv.schema_with_slug_keys(SENSOR_SCHEMA),
vol.Optional(CONF_SWITCHES): cv.schema_with_slug_keys(SWITCH_SCHEMA),
}
)
CONFIG_SCHEMA = vol.Schema(
{
vol.Optional(DOMAIN): vol.All(
COMBINED_SCHEMA,
cv.has_at_least_one_key(
CONF_BINARY_SENSORS,
CONF_COVERS,
CONF_NOTIFIERS,
CONF_SENSORS,
CONF_SWITCHES,
),
)
},
extra=vol.ALLOW_EXTRA,
)


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up Command Line from yaml config."""
command_line_config: dict[str, dict[str, Any]] = config.get(DOMAIN, {})
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, configs in command_line_config.items():
platforms.append(PLATFORM_MAPPING[platform])
for object_id, object_config in configs.items():
platform_config = {"object_id": object_id, "config": object_config}
if PLATFORM_MAPPING[platform] == Platform.NOTIFY and (
add_name := object_config.get(CONF_NAME)
):
platform_config[CONF_NAME] = add_name
_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,
platform_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
37 changes: 25 additions & 12 deletions homeassistant/components/command_line/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,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 +59,29 @@ 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_yaml_binary_sensor",
)
gjohansson-ST marked this conversation as resolved.
Show resolved Hide resolved
if discovery_info:
binary_sensor_config = discovery_info["config"]

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
22 changes: 17 additions & 5 deletions homeassistant/components/command_line/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,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 .utils import call_shell_with_timeout, check_output_or_log

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -55,10 +55,22 @@ 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:
devices: dict[str, Any] = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's not keep using "devices" here, either call the dict "entities" or "covers"

discovery_info["object_id"]: discovery_info["config"]
}
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_yaml_cover",
)
devices = config.get(CONF_COVERS, {})

for device_name, device_config in devices.items():
value_template: Template | None = device_config.get(CONF_VALUE_TEMPLATE)
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"
}
19 changes: 16 additions & 3 deletions homeassistant/components/command_line/notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
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 +34,20 @@ 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_yaml_notify",
)
if discovery_info:
notify_config = discovery_info["config"]
command: str = notify_config[CONF_COMMAND]
timeout: int = notify_config[CONF_COMMAND_TIMEOUT]

return CommandLineNotificationService(command, timeout)

Expand Down
34 changes: 22 additions & 12 deletions homeassistant/components/command_line/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@
from homeassistant.exceptions import TemplateError
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 .utils import check_output_or_log

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -64,18 +64,28 @@ async def async_setup_platform(
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Command Sensor."""

await async_setup_reload_service(hass, DOMAIN, PLATFORMS)

name: str = config[CONF_NAME]
command: str = config[CONF_COMMAND]
unit: str | None = config.get(CONF_UNIT_OF_MEASUREMENT)
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 sensor_config := config:
async_create_issue(
hass,
DOMAIN,
"deprecated_yaml_sensor",
breaks_in_ha_version="2023.8.0",
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml_sensor",
)
if discovery_info:
sensor_config = discovery_info["config"]

name: str = sensor_config[CONF_NAME]
command: str = sensor_config[CONF_COMMAND]
unit: str | None = sensor_config.get(CONF_UNIT_OF_MEASUREMENT)
value_template: Template | None = sensor_config.get(CONF_VALUE_TEMPLATE)
command_timeout: int = sensor_config[CONF_COMMAND_TIMEOUT]
unique_id: str | None = sensor_config.get(CONF_UNIQUE_ID)
if value_template is not None:
value_template.hass = hass
json_attributes: list[str] | None = config.get(CONF_JSON_ATTRIBUTES)
json_attributes: list[str] | None = sensor_config.get(CONF_JSON_ATTRIBUTES)
data = CommandSensorData(hass, command, command_timeout)

async_add_entities(
Expand Down