Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for SleepIQ from SleepNumber."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import Any
7 
8 from asyncsleepiq import (
9  AsyncSleepIQ,
10  SleepIQAPIException,
11  SleepIQLoginException,
12  SleepIQTimeoutException,
13 )
14 import voluptuous as vol
15 
16 from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
17 from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, PRESSURE, Platform
18 from homeassistant.core import HomeAssistant, callback
19 from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
20 from homeassistant.helpers import entity_registry as er
21 from homeassistant.helpers.aiohttp_client import async_get_clientsession
23 from homeassistant.helpers.typing import ConfigType
24 
25 from .const import DOMAIN, IS_IN_BED, SLEEP_NUMBER
26 from .coordinator import (
27  SleepIQData,
28  SleepIQDataUpdateCoordinator,
29  SleepIQPauseUpdateCoordinator,
30 )
31 
32 _LOGGER = logging.getLogger(__name__)
33 
34 PLATFORMS = [
35  Platform.BINARY_SENSOR,
36  Platform.BUTTON,
37  Platform.LIGHT,
38  Platform.NUMBER,
39  Platform.SELECT,
40  Platform.SENSOR,
41  Platform.SWITCH,
42 ]
43 
44 CONFIG_SCHEMA = vol.Schema(
45  {
46  DOMAIN: {
47  vol.Required(CONF_USERNAME): cv.string,
48  vol.Required(CONF_PASSWORD): cv.string,
49  }
50  },
51  extra=vol.ALLOW_EXTRA,
52 )
53 
54 
55 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
56  """Set up sleepiq component."""
57  if DOMAIN in config:
58  hass.async_create_task(
59  hass.config_entries.flow.async_init(
60  DOMAIN, context={"source": SOURCE_IMPORT}, data=config[DOMAIN]
61  )
62  )
63 
64  return True
65 
66 
67 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
68  """Set up the SleepIQ config entry."""
69  conf = entry.data
70  email = conf[CONF_USERNAME]
71  password = conf[CONF_PASSWORD]
72 
73  client_session = async_get_clientsession(hass)
74 
75  gateway = AsyncSleepIQ(client_session=client_session)
76 
77  try:
78  await gateway.login(email, password)
79  except SleepIQLoginException as err:
80  _LOGGER.error("Could not authenticate with SleepIQ server")
81  raise ConfigEntryAuthFailed(err) from err
82  except SleepIQTimeoutException as err:
83  raise ConfigEntryNotReady(
84  str(err) or "Timed out during authentication"
85  ) from err
86 
87  try:
88  await gateway.init_beds()
89  except SleepIQTimeoutException as err:
90  raise ConfigEntryNotReady(
91  str(err) or "Timed out during initialization"
92  ) from err
93  except SleepIQAPIException as err:
94  raise ConfigEntryNotReady(str(err) or "Error reading from SleepIQ API") from err
95 
96  await _async_migrate_unique_ids(hass, entry, gateway)
97 
98  coordinator = SleepIQDataUpdateCoordinator(hass, gateway, email)
99  pause_coordinator = SleepIQPauseUpdateCoordinator(hass, gateway, email)
100 
101  # Call the SleepIQ API to refresh data
102  await coordinator.async_config_entry_first_refresh()
103  await pause_coordinator.async_config_entry_first_refresh()
104 
105  hass.data.setdefault(DOMAIN, {})[entry.entry_id] = SleepIQData(
106  data_coordinator=coordinator,
107  pause_coordinator=pause_coordinator,
108  client=gateway,
109  )
110 
111  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
112 
113  return True
114 
115 
116 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
117  """Unload the config entry."""
118  if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
119  hass.data[DOMAIN].pop(entry.entry_id)
120  return unload_ok
121 
122 
124  hass: HomeAssistant, entry: ConfigEntry, gateway: AsyncSleepIQ
125 ) -> None:
126  """Migrate old unique ids."""
127  names_to_ids = {
128  sleeper.name: sleeper.sleeper_id
129  for bed in gateway.beds.values()
130  for sleeper in bed.sleepers
131  }
132 
133  bed_ids = {bed.id for bed in gateway.beds.values()}
134 
135  @callback
136  def _async_migrator(entity_entry: er.RegistryEntry) -> dict[str, Any] | None:
137  # Old format for sleeper entities was {bed_id}_{sleeper.name}_{sensor_type}.....
138  # New format is {sleeper.sleeper_id}_{sensor_type}....
139  sensor_types = [IS_IN_BED, PRESSURE, SLEEP_NUMBER]
140 
141  old_unique_id = entity_entry.unique_id
142  parts = old_unique_id.split("_")
143 
144  # If it doesn't begin with a bed id or end with one of the sensor types,
145  # it doesn't need to be migrated
146  if parts[0] not in bed_ids or not old_unique_id.endswith(tuple(sensor_types)):
147  return None
148 
149  sensor_type = next(filter(old_unique_id.endswith, sensor_types))
150  sleeper_name = "_".join(parts[1:]).removesuffix(f"_{sensor_type}")
151  sleeper_id = names_to_ids.get(sleeper_name)
152 
153  if not sleeper_id:
154  return None
155 
156  new_unique_id = f"{sleeper_id}_{sensor_type}"
157 
158  _LOGGER.debug(
159  "Migrating unique_id from [%s] to [%s]",
160  old_unique_id,
161  new_unique_id,
162  )
163  return {"new_unique_id": new_unique_id}
164 
165  await er.async_migrate_entries(hass, entry.entry_id, _async_migrator)
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:67
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:55
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:116
None _async_migrate_unique_ids(HomeAssistant hass, ConfigEntry entry, AsyncSleepIQ gateway)
Definition: __init__.py:125
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)