Home Assistant Unofficial Reference 2024.12.1
cover.py
Go to the documentation of this file.
1 """Platform for the Garadget cover component."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import Any
7 
8 import requests
9 import voluptuous as vol
10 
12  PLATFORM_SCHEMA as COVER_PLATFORM_SCHEMA,
13  CoverDeviceClass,
14  CoverEntity,
15  CoverState,
16 )
17 from homeassistant.const import (
18  CONF_ACCESS_TOKEN,
19  CONF_COVERS,
20  CONF_DEVICE,
21  CONF_NAME,
22  CONF_PASSWORD,
23  CONF_USERNAME,
24 )
25 from homeassistant.core import HomeAssistant
27 from homeassistant.helpers.entity_platform import AddEntitiesCallback
28 from homeassistant.helpers.event import track_utc_time_change
29 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
30 
31 _LOGGER = logging.getLogger(__name__)
32 
33 ATTR_AVAILABLE = "available"
34 ATTR_SENSOR_STRENGTH = "sensor_reflection_rate"
35 ATTR_SIGNAL_STRENGTH = "wifi_signal_strength"
36 ATTR_TIME_IN_STATE = "time_in_state"
37 
38 DEFAULT_NAME = "Garadget"
39 
40 STATE_OFFLINE = "offline"
41 STATE_STOPPED = "stopped"
42 
43 STATES_MAP = {
44  "open": CoverState.OPEN,
45  "opening": CoverState.OPENING,
46  "closed": CoverState.CLOSED,
47  "closing": CoverState.CLOSING,
48  "stopped": STATE_STOPPED,
49 }
50 
51 COVER_SCHEMA = vol.Schema(
52  {
53  vol.Optional(CONF_ACCESS_TOKEN): cv.string,
54  vol.Optional(CONF_DEVICE): cv.string,
55  vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
56  vol.Optional(CONF_PASSWORD): cv.string,
57  vol.Optional(CONF_USERNAME): cv.string,
58  }
59 )
60 
61 PLATFORM_SCHEMA = COVER_PLATFORM_SCHEMA.extend(
62  {vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA)}
63 )
64 
65 
67  hass: HomeAssistant,
68  config: ConfigType,
69  add_entities: AddEntitiesCallback,
70  discovery_info: DiscoveryInfoType | None = None,
71 ) -> None:
72  """Set up the Garadget covers."""
73  covers = []
74  devices = config[CONF_COVERS]
75 
76  for device_id, device_config in devices.items():
77  args = {
78  "name": device_config.get(CONF_NAME),
79  "device_id": device_config.get(CONF_DEVICE, device_id),
80  "username": device_config.get(CONF_USERNAME),
81  "password": device_config.get(CONF_PASSWORD),
82  "access_token": device_config.get(CONF_ACCESS_TOKEN),
83  }
84 
85  covers.append(GaradgetCover(hass, args))
86 
87  add_entities(covers)
88 
89 
91  """Representation of a Garadget cover."""
92 
93  _attr_device_class = CoverDeviceClass.GARAGE
94 
95  def __init__(self, hass, args):
96  """Initialize the cover."""
97  self.particle_urlparticle_url = "https://api.particle.io"
98  self.hasshasshass = hass
99  self._name_name = args["name"]
100  self.device_iddevice_id = args["device_id"]
101  self.access_tokenaccess_token = args["access_token"]
102  self.obtained_tokenobtained_token = False
103  self._username_username = args["username"]
104  self._password_password = args["password"]
105  self._state_state = None
106  self.time_in_statetime_in_state = None
107  self.signalsignal = None
108  self.sensorsensor = None
109  self._unsub_listener_cover_unsub_listener_cover = None
110  self._available_available = True
111 
112  if self.access_tokenaccess_token is None:
113  self.access_tokenaccess_token = self.get_tokenget_token()
114  self._obtained_token_obtained_token = True
115 
116  try:
117  if self._name_name is None:
118  doorconfig = self._get_variable_get_variable("doorConfig")
119  if doorconfig["nme"] is not None:
120  self._name_name = doorconfig["nme"]
121  self.updateupdate()
122  except requests.exceptions.ConnectionError as ex:
123  _LOGGER.error("Unable to connect to server: %(reason)s", {"reason": ex})
124  self._state_state = STATE_OFFLINE
125  self._available_available = False
126  self._name_name = DEFAULT_NAME
127  except KeyError:
128  _LOGGER.warning(
129  "Garadget device %(device)s seems to be offline",
130  {"device": self.device_iddevice_id},
131  )
132  self._name_name = DEFAULT_NAME
133  self._state_state = STATE_OFFLINE
134  self._available_available = False
135 
136  def __del__(self):
137  """Try to remove token."""
138  if self._obtained_token_obtained_token is True and self.access_tokenaccess_token is not None:
139  self.remove_tokenremove_token()
140 
141  @property
142  def name(self) -> str:
143  """Return the name of the cover."""
144  return self._name_name
145 
146  @property
147  def available(self) -> bool:
148  """Return True if entity is available."""
149  return self._available_available
150 
151  @property
152  def extra_state_attributes(self) -> dict[str, Any]:
153  """Return the device state attributes."""
154  data = {}
155 
156  if self.signalsignal is not None:
157  data[ATTR_SIGNAL_STRENGTH] = self.signalsignal
158 
159  if self.time_in_statetime_in_state is not None:
160  data[ATTR_TIME_IN_STATE] = self.time_in_statetime_in_state
161 
162  if self.sensorsensor is not None:
163  data[ATTR_SENSOR_STRENGTH] = self.sensorsensor
164 
165  if self.access_tokenaccess_token is not None:
166  data[CONF_ACCESS_TOKEN] = self.access_tokenaccess_token
167 
168  return data
169 
170  @property
171  def is_closed(self) -> bool | None:
172  """Return if the cover is closed."""
173  if self._state_state is None:
174  return None
175  return self._state_state == CoverState.CLOSED
176 
177  def get_token(self):
178  """Get new token for usage during this session."""
179  args = {
180  "grant_type": "password",
181  "username": self._username_username,
182  "password": self._password_password,
183  }
184  url = f"{self.particle_url}/oauth/token"
185  ret = requests.post(url, auth=("particle", "particle"), data=args, timeout=10)
186 
187  try:
188  return ret.json()["access_token"]
189  except KeyError:
190  _LOGGER.error("Unable to retrieve access token")
191 
192  def remove_token(self):
193  """Remove authorization token from API."""
194  url = f"{self.particle_url}/v1/access_tokens/{self.access_token}"
195  ret = requests.delete(url, auth=(self._username_username, self._password_password), timeout=10)
196  return ret.text
197 
198  def _start_watcher(self, command):
199  """Start watcher."""
200  _LOGGER.debug("Starting Watcher for command: %s ", command)
201  if self._unsub_listener_cover_unsub_listener_cover is None:
202  self._unsub_listener_cover_unsub_listener_cover = track_utc_time_change(
203  self.hasshasshass, self._check_state_check_state
204  )
205 
206  def _check_state(self, now):
207  """Check the state of the service during an operation."""
208  self.schedule_update_ha_stateschedule_update_ha_state(True)
209 
210  def close_cover(self, **kwargs: Any) -> None:
211  """Close the cover."""
212  if self._state_state not in ["close", "closing"]:
213  self._put_command_put_command("setState", "close")
214  self._start_watcher_start_watcher("close")
215 
216  def open_cover(self, **kwargs: Any) -> None:
217  """Open the cover."""
218  if self._state_state not in ["open", "opening"]:
219  self._put_command_put_command("setState", "open")
220  self._start_watcher_start_watcher("open")
221 
222  def stop_cover(self, **kwargs: Any) -> None:
223  """Stop the door where it is."""
224  if self._state_state not in ["stopped"]:
225  self._put_command_put_command("setState", "stop")
226  self._start_watcher_start_watcher("stop")
227 
228  def update(self) -> None:
229  """Get updated status from API."""
230  try:
231  status = self._get_variable_get_variable("doorStatus")
232  _LOGGER.debug("Current Status: %s", status["status"])
233  self._state_state = STATES_MAP.get(status["status"])
234  self.time_in_statetime_in_state = status["time"]
235  self.signalsignal = status["signal"]
236  self.sensorsensor = status["sensor"]
237  self._available_available = True
238  except requests.exceptions.ConnectionError as ex:
239  _LOGGER.error("Unable to connect to server: %(reason)s", {"reason": ex})
240  self._state_state = STATE_OFFLINE
241  except KeyError:
242  _LOGGER.warning(
243  "Garadget device %(device)s seems to be offline",
244  {"device": self.device_iddevice_id},
245  )
246  self._state_state = STATE_OFFLINE
247 
248  if (
249  self._state_state not in [CoverState.CLOSING, CoverState.OPENING]
250  and self._unsub_listener_cover_unsub_listener_cover is not None
251  ):
252  self._unsub_listener_cover_unsub_listener_cover()
253  self._unsub_listener_cover_unsub_listener_cover = None
254 
255  def _get_variable(self, var):
256  """Get latest status."""
257  url = f"{self.particle_url}/v1/devices/{self.device_id}/{var}?access_token={self.access_token}"
258  ret = requests.get(url, timeout=10)
259  result = {}
260  for pairs in ret.json()["result"].split("|"):
261  key = pairs.split("=")
262  result[key[0]] = key[1]
263  return result
264 
265  def _put_command(self, func, arg=None):
266  """Send commands to API."""
267  params = {"access_token": self.access_tokenaccess_token}
268  if arg:
269  params["command"] = arg
270  url = f"{self.particle_url}/v1/devices/{self.device_id}/{func}"
271  ret = requests.post(url, data=params, timeout=10)
272  return ret.json()
None schedule_update_ha_state(self, bool force_refresh=False)
Definition: entity.py:1244
None add_entities(AsusWrtRouter router, AddEntitiesCallback async_add_entities, set[str] tracked)
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: cover.py:71