Home Assistant Unofficial Reference 2024.12.1
cover.py
Go to the documentation of this file.
1 """Support for command line covers."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from datetime import datetime, timedelta
7 from typing import TYPE_CHECKING, Any, cast
8 
9 from homeassistant.components.cover import CoverEntity
10 from homeassistant.const import (
11  CONF_COMMAND_CLOSE,
12  CONF_COMMAND_OPEN,
13  CONF_COMMAND_STATE,
14  CONF_COMMAND_STOP,
15  CONF_NAME,
16  CONF_SCAN_INTERVAL,
17  CONF_VALUE_TEMPLATE,
18 )
19 from homeassistant.core import HomeAssistant
20 from homeassistant.helpers.entity_platform import AddEntitiesCallback
21 from homeassistant.helpers.event import async_track_time_interval
22 from homeassistant.helpers.template import Template
23 from homeassistant.helpers.trigger_template_entity import ManualTriggerEntity
24 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
25 from homeassistant.util import dt as dt_util, slugify
26 
27 from .const import CONF_COMMAND_TIMEOUT, LOGGER, TRIGGER_ENTITY_OPTIONS
28 from .utils import async_call_shell_with_timeout, async_check_output_or_log
29 
30 SCAN_INTERVAL = timedelta(seconds=15)
31 
32 
34  hass: HomeAssistant,
35  config: ConfigType,
36  async_add_entities: AddEntitiesCallback,
37  discovery_info: DiscoveryInfoType | None = None,
38 ) -> None:
39  """Set up cover controlled by shell commands."""
40  if not discovery_info:
41  return
42 
43  covers = []
44  discovery_info = cast(DiscoveryInfoType, discovery_info)
45  entities: dict[str, dict[str, Any]] = {
46  slugify(discovery_info[CONF_NAME]): discovery_info
47  }
48 
49  for device_name, cover_config in entities.items():
50  trigger_entity_config = {
51  CONF_NAME: Template(cover_config.get(CONF_NAME, device_name), hass),
52  **{k: v for k, v in cover_config.items() if k in TRIGGER_ENTITY_OPTIONS},
53  }
54 
55  covers.append(
57  trigger_entity_config,
58  cover_config[CONF_COMMAND_OPEN],
59  cover_config[CONF_COMMAND_CLOSE],
60  cover_config[CONF_COMMAND_STOP],
61  cover_config.get(CONF_COMMAND_STATE),
62  cover_config.get(CONF_VALUE_TEMPLATE),
63  cover_config[CONF_COMMAND_TIMEOUT],
64  cover_config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL),
65  )
66  )
67 
68  async_add_entities(covers)
69 
70 
72  """Representation a command line cover."""
73 
74  _attr_should_poll = False
75 
76  def __init__(
77  self,
78  config: ConfigType,
79  command_open: str,
80  command_close: str,
81  command_stop: str,
82  command_state: str | None,
83  value_template: Template | None,
84  timeout: int,
85  scan_interval: timedelta,
86  ) -> None:
87  """Initialize the cover."""
88  super().__init__(self.hasshasshass, config)
89  self._state_state: int | None = None
90  self._command_open_command_open = command_open
91  self._command_close_command_close = command_close
92  self._command_stop_command_stop = command_stop
93  self._command_state_command_state = command_state
94  self._value_template_value_template = value_template
95  self._timeout_timeout = timeout
96  self._scan_interval_scan_interval = scan_interval
97  self._process_updates_process_updates: asyncio.Lock | None = None
98 
99  async def async_added_to_hass(self) -> None:
100  """Call when entity about to be added to hass."""
101  await super().async_added_to_hass()
102  if self._command_state_command_state:
103  self.async_on_removeasync_on_remove(
105  self.hasshasshass,
106  self._update_entity_state_update_entity_state,
107  self._scan_interval_scan_interval,
108  name=f"Command Line Cover - {self.name}",
109  cancel_on_shutdown=True,
110  ),
111  )
112 
113  async def _async_move_cover(self, command: str) -> bool:
114  """Execute the actual commands."""
115  LOGGER.debug("Running command: %s", command)
116 
117  returncode = await async_call_shell_with_timeout(command, self._timeout_timeout)
118  success = returncode == 0
119 
120  if not success:
121  LOGGER.error(
122  "Command failed (with return code %s): %s", returncode, command
123  )
124 
125  return success
126 
127  @property
128  def is_closed(self) -> bool | None:
129  """Return if the cover is closed."""
132  return None
133 
134  @property
135  def current_cover_position(self) -> int | None:
136  """Return current position of cover.
137 
138  None is unknown, 0 is closed, 100 is fully open.
139  """
140  return self._state_state
141 
142  async def _async_query_state(self) -> str | None:
143  """Query for the state."""
144  if TYPE_CHECKING:
145  assert self._command_state_command_state
146  LOGGER.debug("Running state value command: %s", self._command_state_command_state)
147  return await async_check_output_or_log(self._command_state_command_state, self._timeout_timeout)
148 
149  async def _update_entity_state(self, now: datetime | None = None) -> None:
150  """Update the state of the entity."""
151  if self._process_updates_process_updates is None:
152  self._process_updates_process_updates = asyncio.Lock()
153  if self._process_updates_process_updates.locked():
154  LOGGER.warning(
155  "Updating Command Line Cover %s took longer than the scheduled update interval %s",
156  self.namenamename,
157  self._scan_interval_scan_interval,
158  )
159  return
160 
161  async with self._process_updates_process_updates:
162  await self._async_update_async_update()
163 
164  async def _async_update(self) -> None:
165  """Update device state."""
166  if self._command_state_command_state:
167  payload = str(await self._async_query_state_async_query_state())
168  if self._value_template_value_template:
169  payload = self._value_template_value_template.async_render_with_possible_json_value(
170  payload, None
171  )
172  self._state_state = None
173  if payload:
174  self._state_state = int(payload)
175  self._process_manual_data_process_manual_data(payload)
176  self.async_write_ha_stateasync_write_ha_state()
177 
178  async def async_update(self) -> None:
179  """Update the entity.
180 
181  Only used by the generic entity update service.
182  """
183  await self._update_entity_state_update_entity_state(dt_util.now())
184 
185  async def async_open_cover(self, **kwargs: Any) -> None:
186  """Open the cover."""
187  await self._async_move_cover_async_move_cover(self._command_open_command_open)
188  await self._update_entity_state_update_entity_state()
189 
190  async def async_close_cover(self, **kwargs: Any) -> None:
191  """Close the cover."""
192  await self._async_move_cover_async_move_cover(self._command_close_command_close)
193  await self._update_entity_state_update_entity_state()
194 
195  async def async_stop_cover(self, **kwargs: Any) -> None:
196  """Stop the cover."""
197  await self._async_move_cover_async_move_cover(self._command_stop_command_stop)
198  await self._update_entity_state_update_entity_state()
None __init__(self, ConfigType config, str command_open, str command_close, str command_stop, str|None command_state, Template|None value_template, int timeout, timedelta scan_interval)
Definition: cover.py:86
None _update_entity_state(self, datetime|None now=None)
Definition: cover.py:149
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
str|UndefinedType|None name(self)
Definition: entity.py:738
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: cover.py:38
int async_call_shell_with_timeout(str command, int timeout, *bool log_return_code=True)
Definition: utils.py:14
str|None async_check_output_or_log(str command, int timeout)
Definition: utils.py:44
CALLBACK_TYPE async_track_time_interval(HomeAssistant hass, Callable[[datetime], Coroutine[Any, Any, None]|None] action, timedelta interval, *str|None name=None, bool|None cancel_on_shutdown=None)
Definition: event.py:1679