__init__.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728
  1. """
  2. Platform for Tuya WiFi-connected devices.
  3. Based on nikrolls/homeassistant-goldair-climate for Goldair branded devices.
  4. Based on sean6541/tuya-homeassistant for service call logic, and TarxBoy's
  5. investigation into Goldair's tuyapi statuses
  6. https://github.com/codetheweb/tuyapi/issues/31.
  7. """
  8. import logging
  9. from homeassistant.config_entries import ConfigEntry
  10. from homeassistant.const import CONF_HOST
  11. from homeassistant.core import HomeAssistant, callback
  12. from homeassistant.exceptions import ConfigEntryNotReady
  13. from homeassistant.helpers.entity_registry import async_migrate_entries
  14. from homeassistant.util import slugify
  15. from .const import (
  16. CONF_DEVICE_ID,
  17. CONF_LOCAL_KEY,
  18. CONF_POLL_ONLY,
  19. CONF_PROTOCOL_VERSION,
  20. CONF_TYPE,
  21. DOMAIN,
  22. )
  23. from .device import async_delete_device, get_device_id, setup_device
  24. from .helpers.device_config import get_config
  25. _LOGGER = logging.getLogger(__name__)
  26. NOT_FOUND = "Configuration file for %s not found"
  27. def replace_unique_ids(entity_entry, device_id, conf_file, replacements):
  28. """Update the unique id of an entry based on a table of replacements."""
  29. old_id = entity_entry.unique_id
  30. platform = entity_entry.entity_id.split(".", 1)[0]
  31. for suffix, new_suffix in replacements.items():
  32. if old_id.endswith(suffix):
  33. for e in conf_file.all_entities():
  34. new_id = e.unique_id(device_id)
  35. if e.entity == platform and not e.name and new_id.endswith(new_suffix):
  36. break
  37. if e.entity == platform and not e.name and new_id.endswith(new_suffix):
  38. _LOGGER.info(
  39. "Migrating %s unique_id %s to %s",
  40. e.entity,
  41. old_id,
  42. new_id,
  43. )
  44. return {
  45. "new_unique_id": entity_entry.unique_id.replace(
  46. old_id,
  47. new_id,
  48. )
  49. }
  50. async def async_migrate_entry(hass, entry: ConfigEntry):
  51. """Migrate to latest config format."""
  52. CONF_TYPE_AUTO = "auto"
  53. if entry.version == 1:
  54. # Removal of Auto detection.
  55. config = {**entry.data, **entry.options, "name": entry.title}
  56. if config[CONF_TYPE] == CONF_TYPE_AUTO:
  57. device = await hass.async_add_executor_job(
  58. setup_device,
  59. hass,
  60. config,
  61. )
  62. config[CONF_TYPE] = await device.async_inferred_type()
  63. if config[CONF_TYPE] is None:
  64. _LOGGER.error(
  65. "Unable to determine type for device %s",
  66. config[CONF_DEVICE_ID],
  67. )
  68. return False
  69. hass.config_entries.async_update_entry(
  70. entry,
  71. data={
  72. CONF_DEVICE_ID: config[CONF_DEVICE_ID],
  73. CONF_LOCAL_KEY: config[CONF_LOCAL_KEY],
  74. CONF_HOST: config[CONF_HOST],
  75. },
  76. version=2,
  77. )
  78. if entry.version == 2:
  79. # CONF_TYPE is not configurable, move from options to main config.
  80. config = {**entry.data, **entry.options, "name": entry.title}
  81. opts = {**entry.options}
  82. # Ensure type has been migrated. Some users are reporting errors which
  83. # suggest it was removed completely. But that is probably due to
  84. # overwriting options without CONF_TYPE.
  85. if config.get(CONF_TYPE, CONF_TYPE_AUTO) == CONF_TYPE_AUTO:
  86. device = await hass.async_add_executor_job(
  87. setup_device,
  88. hass,
  89. config,
  90. )
  91. config[CONF_TYPE] = await device.async_inferred_type()
  92. if config[CONF_TYPE] is None:
  93. _LOGGER.error(
  94. "Unable to determine type for device %s",
  95. config[CONF_DEVICE_ID],
  96. )
  97. return False
  98. opts.pop(CONF_TYPE, None)
  99. hass.config_entries.async_update_entry(
  100. entry,
  101. data={
  102. CONF_DEVICE_ID: config[CONF_DEVICE_ID],
  103. CONF_LOCAL_KEY: config[CONF_LOCAL_KEY],
  104. CONF_HOST: config[CONF_HOST],
  105. CONF_TYPE: config[CONF_TYPE],
  106. },
  107. options={**opts},
  108. version=3,
  109. )
  110. if entry.version == 3:
  111. # Migrate to filename based config_type, to avoid needing to
  112. # parse config files to find the right one.
  113. config = {**entry.data, **entry.options, "name": entry.title}
  114. config_yaml = await hass.async_add_executor_job(
  115. get_config,
  116. config[CONF_TYPE],
  117. )
  118. config_type = config_yaml.config_type
  119. # Special case for kogan_switch. Consider also v2.
  120. if config_type == "smartplugv1":
  121. device = await hass.async_add_executor_job(
  122. setup_device,
  123. hass,
  124. config,
  125. )
  126. config_type = await device.async_inferred_type()
  127. if config_type != "smartplugv2":
  128. config_type = "smartplugv1"
  129. hass.config_entries.async_update_entry(
  130. entry,
  131. data={
  132. CONF_DEVICE_ID: config[CONF_DEVICE_ID],
  133. CONF_LOCAL_KEY: config[CONF_LOCAL_KEY],
  134. CONF_HOST: config[CONF_HOST],
  135. CONF_TYPE: config_type,
  136. },
  137. version=4,
  138. )
  139. if entry.version <= 5:
  140. # Migrate unique ids of existing entities to new format
  141. old_id = entry.unique_id
  142. conf_file = await hass.async_add_executor_job(
  143. get_config,
  144. entry.data[CONF_TYPE],
  145. )
  146. if conf_file is None:
  147. _LOGGER.error(NOT_FOUND, entry.data[CONF_TYPE])
  148. return False
  149. @callback
  150. def update_unique_id(entity_entry):
  151. """Update the unique id of an entity entry."""
  152. for e in conf_file.all_entities():
  153. if e.entity == entity_entry.platform:
  154. break
  155. if e.entity == entity_entry.platform:
  156. new_id = e.unique_id(old_id)
  157. if new_id != old_id:
  158. _LOGGER.info(
  159. "Migrating %s unique_id %s to %s",
  160. e.entity,
  161. old_id,
  162. new_id,
  163. )
  164. return {
  165. "new_unique_id": entity_entry.unique_id.replace(
  166. old_id,
  167. new_id,
  168. )
  169. }
  170. await async_migrate_entries(hass, entry.entry_id, update_unique_id)
  171. hass.config_entries.async_update_entry(entry, version=6)
  172. if entry.version <= 8:
  173. # Deprecated entities are removed, trim the config back to required
  174. # config only
  175. conf = {**entry.data, **entry.options}
  176. hass.config_entries.async_update_entry(
  177. entry,
  178. data={
  179. CONF_DEVICE_ID: conf[CONF_DEVICE_ID],
  180. CONF_LOCAL_KEY: conf[CONF_LOCAL_KEY],
  181. CONF_HOST: conf[CONF_HOST],
  182. CONF_TYPE: conf[CONF_TYPE],
  183. },
  184. options={},
  185. version=9,
  186. )
  187. if entry.version <= 9:
  188. # Added protocol_version, default to auto
  189. conf = {**entry.data, **entry.options}
  190. hass.config_entries.async_update_entry(
  191. entry,
  192. data={
  193. CONF_DEVICE_ID: conf[CONF_DEVICE_ID],
  194. CONF_LOCAL_KEY: conf[CONF_LOCAL_KEY],
  195. CONF_HOST: conf[CONF_HOST],
  196. CONF_TYPE: conf[CONF_TYPE],
  197. CONF_PROTOCOL_VERSION: "auto",
  198. },
  199. options={},
  200. version=10,
  201. )
  202. if entry.version <= 10:
  203. conf = entry.data | entry.options
  204. hass.config_entries.async_update_entry(
  205. entry,
  206. data={
  207. CONF_DEVICE_ID: conf[CONF_DEVICE_ID],
  208. CONF_LOCAL_KEY: conf[CONF_LOCAL_KEY],
  209. CONF_HOST: conf[CONF_HOST],
  210. CONF_TYPE: conf[CONF_TYPE],
  211. CONF_PROTOCOL_VERSION: conf[CONF_PROTOCOL_VERSION],
  212. CONF_POLL_ONLY: False,
  213. },
  214. options={},
  215. version=11,
  216. )
  217. if entry.version <= 11:
  218. # Migrate unique ids of existing entities to new format
  219. device_id = entry.unique_id
  220. conf_file = await hass.async_add_executor_job(
  221. get_config,
  222. entry.data[CONF_TYPE],
  223. )
  224. if conf_file is None:
  225. _LOGGER.error(
  226. NOT_FOUND,
  227. entry.data[CONF_TYPE],
  228. )
  229. return False
  230. @callback
  231. def update_unique_id12(entity_entry):
  232. """Update the unique id of an entity entry."""
  233. old_id = entity_entry.unique_id
  234. platform = entity_entry.entity_id.split(".", 1)[0]
  235. for e in conf_file.all_entities():
  236. if e.name:
  237. expect_id = f"{device_id}-{slugify(e.name)}"
  238. else:
  239. expect_id = device_id
  240. if e.entity == platform and expect_id == old_id:
  241. break
  242. if e.entity == platform and expect_id == old_id:
  243. new_id = e.unique_id(device_id)
  244. if new_id != old_id:
  245. _LOGGER.info(
  246. "Migrating %s unique_id %s to %s",
  247. e.entity,
  248. old_id,
  249. new_id,
  250. )
  251. return {
  252. "new_unique_id": entity_entry.unique_id.replace(
  253. old_id,
  254. new_id,
  255. )
  256. }
  257. await async_migrate_entries(hass, entry.entry_id, update_unique_id12)
  258. hass.config_entries.async_update_entry(entry, version=12)
  259. if entry.version <= 12:
  260. # Migrate unique ids of existing entities to new format taking into
  261. # account device_class if name is missing.
  262. device_id = entry.unique_id
  263. conf_file = await hass.async_add_executor_job(
  264. get_config,
  265. entry.data[CONF_TYPE],
  266. )
  267. if conf_file is None:
  268. _LOGGER.error(
  269. NOT_FOUND,
  270. entry.data[CONF_TYPE],
  271. )
  272. return False
  273. @callback
  274. def update_unique_id13(entity_entry):
  275. """Update the unique id of an entity entry."""
  276. old_id = entity_entry.unique_id
  277. platform = entity_entry.entity_id.split(".", 1)[0]
  278. # if unique_id ends with platform name, then this may have
  279. # changed with the addition of device_class.
  280. if old_id.endswith(platform):
  281. for e in conf_file.all_entities():
  282. if e.entity == platform and not e.name:
  283. break
  284. if e.entity == platform and not e.name:
  285. new_id = e.unique_id(device_id)
  286. if new_id != old_id:
  287. _LOGGER.info(
  288. "Migrating %s unique_id %s to %s",
  289. e.entity,
  290. old_id,
  291. new_id,
  292. )
  293. return {
  294. "new_unique_id": entity_entry.unique_id.replace(
  295. old_id,
  296. new_id,
  297. )
  298. }
  299. else:
  300. replacements = {
  301. "sensor_co2": "sensor_carbon_dioxide",
  302. "sensor_co": "sensor_carbon_monoxide",
  303. "sensor_pm2_5": "sensor_pm25",
  304. "sensor_pm_10": "sensor_pm10",
  305. "sensor_pm_1_0": "sensor_pm1",
  306. "sensor_pm_2_5": "sensor_pm25",
  307. "sensor_tvoc": "sensor_volatile_organic_compounds",
  308. "sensor_current_humidity": "sensor_humidity",
  309. "sensor_current_temperature": "sensor_temperature",
  310. }
  311. return replace_unique_ids(
  312. entity_entry, device_id, conf_file, replacements
  313. )
  314. await async_migrate_entries(hass, entry.entry_id, update_unique_id13)
  315. hass.config_entries.async_update_entry(entry, version=13)
  316. if entry.version == 13 and entry.minor_version < 2:
  317. # Migrate unique ids of existing entities to new id taking into
  318. # account translation_key, and standardising naming
  319. device_id = entry.unique_id
  320. conf_file = await hass.async_add_executor_job(
  321. get_config,
  322. entry.data[CONF_TYPE],
  323. )
  324. if conf_file is None:
  325. _LOGGER.error(
  326. NOT_FOUND,
  327. entry.data[CONF_TYPE],
  328. )
  329. return False
  330. @callback
  331. def update_unique_id13_2(entity_entry):
  332. """Update the unique id of an entity entry."""
  333. # Standardistion of entity naming to use translation_key
  334. replacements = {
  335. # special meaning of None to handle _full and _empty variants
  336. "binary_sensor_tank": None,
  337. "binary_sensor_tank_full_or_missing": "binary_sensor_tank_full",
  338. "binary_sensor_water_tank_full": "binary_sensor_tank_full",
  339. "binary_sensor_low_water": "binary_sensor_tank_empty",
  340. "binary_sensor_water_tank_empty": "binary_sensor_tank_empty",
  341. "binary_sensor_fault": "binary_sensor_problem",
  342. "binary_sensor_error": "binary_sensor_problem",
  343. "binary_sensor_fault_alarm": "binary_sensor_problem",
  344. "binary_sensor_errors": "binary_sensor_problem",
  345. "binary_sensor_defrosting": "binary_sensor_defrost",
  346. "binary_sensor_anti_frost": "binary_sensor_defrost",
  347. "binary_sensor_anti_freeze": "binary_sensor_defrost",
  348. "binary_sensor_low_battery": "binary_sensor_battery",
  349. "binary_sensor_low_battery_alarm": "binary_sensor_battery",
  350. "select_temperature_units": "select_temperature_unit",
  351. "select_display_temperature_unit": "select_temperature_unit",
  352. "select_display_unit": "select_temperature_unit",
  353. "select_display_units": "select_temperature_unit",
  354. "select_temperature_display_units": "select_temperature_unit",
  355. "switch_defrost": "switch_anti_frost",
  356. "switch_frost_protection": "switch_anti_frost",
  357. }
  358. return replace_unique_ids(entity_entry, device_id, conf_file, replacements)
  359. await async_migrate_entries(hass, entry.entry_id, update_unique_id13_2)
  360. hass.config_entries.async_update_entry(entry, minor_version=2)
  361. if entry.version == 13 and entry.minor_version < 3:
  362. # Migrate unique ids of existing entities to new id taking into
  363. # account translation_key, and standardising naming
  364. device_id = entry.unique_id
  365. conf_file = await hass.async_add_executor_job(
  366. get_config,
  367. entry.data[CONF_TYPE],
  368. )
  369. if conf_file is None:
  370. _LOGGER.error(
  371. NOT_FOUND,
  372. entry.data[CONF_TYPE],
  373. )
  374. return False
  375. @callback
  376. def update_unique_id13_3(entity_entry):
  377. """Update the unique id of an entity entry."""
  378. # Standardistion of entity naming to use translation_key
  379. replacements = {
  380. "light_front_display": "light_display",
  381. "light_lcd_brightness": "light_display",
  382. "light_coal_bed": "light_logs",
  383. "light_ember": "light_embers",
  384. "light_led_indicator": "light_indicator",
  385. "light_status_indicator": "light_indicator",
  386. "light_indicator_light": "light_indicator",
  387. "light_indicators": "light_indicator",
  388. "light_night_light": "light_nightlight",
  389. "number_tiemout_period": "number_timeout_period",
  390. "sensor_remaining_time": "sensor_time_remaining",
  391. "sensor_timer_remain": "sensor_time_remaining",
  392. "sensor_timer": "sensor_time_remaining",
  393. "sensor_timer_countdown": "sensor_time_remaining",
  394. "sensor_timer_remaining": "sensor_time_remaining",
  395. "sensor_time_left": "sensor_time_remaining",
  396. "sensor_timer_minutes_left": "sensor_time_remaining",
  397. "sensor_timer_time_left": "sensor_time_remaining",
  398. "sensor_auto_shutoff_time_remaining": "sensor_time_remaining",
  399. "sensor_warm_time_remaining": "sensor_time_remaining",
  400. "sensor_run_time_remaining": "sensor_time_remaining",
  401. "switch_ioniser": "switch_ionizer",
  402. "switch_run_uv_cycle": "switch_uv_sterilization",
  403. "switch_uv_light": "switch_uv_sterilization",
  404. "switch_ihealth": "switch_uv_sterilization",
  405. "switch_uv_lamp": "switch_uv_sterilization",
  406. "switch_anti_freeze": "switch_anti_frost",
  407. }
  408. return replace_unique_ids(entity_entry, device_id, conf_file, replacements)
  409. await async_migrate_entries(hass, entry.entry_id, update_unique_id13_3)
  410. hass.config_entries.async_update_entry(entry, minor_version=3)
  411. if entry.version == 13 and entry.minor_version < 4:
  412. conf = entry.data | entry.options
  413. conf_type = conf[CONF_TYPE]
  414. # Duolicate config removal - make sure the correct one is used
  415. if conf_type == "ble-yl01_waterquality_tester":
  416. conf_type = "ble_yl01_watertester"
  417. hass.config_entries.async_update_entry(
  418. entry,
  419. data={**entry.data, CONF_TYPE: conf_type},
  420. options={**entry.options},
  421. minor_version=4,
  422. )
  423. if entry.version == 13 and entry.minor_version < 5:
  424. # Migrate unique ids of existing entities to new id taking into
  425. # account translation_key, and standardising naming
  426. device_id = entry.unique_id
  427. conf_file = await hass.async_add_executor_job(
  428. get_config,
  429. entry.data[CONF_TYPE],
  430. )
  431. if conf_file is None:
  432. _LOGGER.error(
  433. NOT_FOUND,
  434. entry.data[CONF_TYPE],
  435. )
  436. return False
  437. @callback
  438. def update_unique_id13_5(entity_entry):
  439. """Update the unique id of an entity entry."""
  440. # Standardistion of entity naming to use translation_key
  441. replacements = {
  442. "number_countdown": "number_timer",
  443. "select_countdown": "select_timer",
  444. "sensor_countdown": "sensor_time_remaining",
  445. "sensor_countdown_timer": "sensor_time_remaining",
  446. "fan": "fan_aroma_diffuser",
  447. }
  448. return replace_unique_ids(entity_entry, device_id, conf_file, replacements)
  449. await async_migrate_entries(hass, entry.entry_id, update_unique_id13_5)
  450. if entry.version == 13 and entry.minor_version < 6:
  451. # Migrate unique ids of existing entities to new id taking into
  452. # account translation_key, and standardising naming
  453. device_id = entry.unique_id
  454. conf_file = await hass.async_add_executor_job(
  455. get_config,
  456. entry.data[CONF_TYPE],
  457. )
  458. if conf_file is None:
  459. _LOGGER.error(
  460. NOT_FOUND,
  461. entry.data[CONF_TYPE],
  462. )
  463. return False
  464. @callback
  465. def update_unique_id13_6(entity_entry):
  466. """Update the unique id of an entity entry."""
  467. # Standardistion of entity naming to use translation_key
  468. replacements = {
  469. "switch_sleep_mode": "switch_sleep",
  470. "switch_sleep_timer": "switch_sleep",
  471. "select_voice_language": "select_language",
  472. "select_restore_power_state": "select_initial_state",
  473. "select_power_on_state": "select_initial_state",
  474. "select_restart_status": "select_initial_state",
  475. "select_poweron_status": "select_initial_state",
  476. "select_mop_mode": "select_mopping",
  477. "select_mop_control": "select_mopping",
  478. "select_water_setting": "select_mopping",
  479. "select_water_mode": "select_mopping",
  480. "select_water_control": "select_mopping",
  481. "select_water_tank": "select_mopping",
  482. "light_light": "light",
  483. "light_lights": "light",
  484. }
  485. return replace_unique_ids(entity_entry, device_id, conf_file, replacements)
  486. await async_migrate_entries(hass, entry.entry_id, update_unique_id13_6)
  487. hass.config_entries.async_update_entry(entry, minor_version=6)
  488. if entry.version == 13 and entry.minor_version < 7:
  489. # Migrate unique ids of existing entities to new id taking into
  490. # account translation_key, and standardising naming
  491. device_id = entry.unique_id
  492. conf_file = await hass.async_add_executor_job(
  493. get_config,
  494. entry.data[CONF_TYPE],
  495. )
  496. if conf_file is None:
  497. _LOGGER.error(
  498. NOT_FOUND,
  499. entry.data[CONF_TYPE],
  500. )
  501. return False
  502. @callback
  503. def update_unique_id13_7(entity_entry):
  504. """Update the unique id of an entity entry."""
  505. # Standardistion of entity naming to use translation_key
  506. replacements = {
  507. "sensor_charger_state": "sensor_status",
  508. }
  509. return replace_unique_ids(entity_entry, device_id, conf_file, replacements)
  510. await async_migrate_entries(hass, entry.entry_id, update_unique_id13_7)
  511. hass.config_entries.async_update_entry(entry, minor_version=7)
  512. if entry.version == 13 and entry.minor_version < 8:
  513. # Migrate unique ids of existing entities to new id taking into
  514. # account translation_key, and standardising naming
  515. device_id = entry.unique_id
  516. conf_file = await hass.async_add_executor_job(
  517. get_config,
  518. entry.data[CONF_TYPE],
  519. )
  520. if conf_file is None:
  521. _LOGGER.error(
  522. NOT_FOUND,
  523. entry.data[CONF_TYPE],
  524. )
  525. return False
  526. @callback
  527. def update_unique_id13_8(entity_entry):
  528. """Update the unique id of an entity entry."""
  529. # Standardistion of entity naming to use translation_key
  530. replacements = {
  531. "sensor_forward_energy": "sensor_energy_consumed",
  532. "sensor_total_forward_energy": "sensor_energy_consumed",
  533. "sensor_energy_in": "sensor_energy_consumed",
  534. "sensor_reverse_energy": "sensor_energy_produced",
  535. "sensor_energy_out": "sensor_energy_produced",
  536. "sensor_forward_energy_a": "sensor_energy_consumed_a",
  537. "sensor_reverse_energy_a": "sensor_energy_produced_a",
  538. "sensor_forward_energy_b": "sensor_energy_consumed_b",
  539. "sensor_reverse_energy_b": "sensor_energy_produced_b",
  540. "sensor_energy_consumption_a": "sensor_energy_consumed_a",
  541. "sensor_energy_consumption_b": "sensor_energy_consumed_b",
  542. "number_timer_switch_1": "number_timer_1",
  543. "number_timer_switch_2": "number_timer_2",
  544. "number_timer_switch_3": "number_timer_3",
  545. "number_timer_switch_4": "number_timer_4",
  546. "number_timer_switch_5": "number_timer_5",
  547. "number_timer_socket_1": "number_timer_1",
  548. "number_timer_socket_2": "number_timer_2",
  549. "number_timer_socket_3": "number_timer_3",
  550. "number_timer_outlet_1": "number_timer_1",
  551. "number_timer_outlet_2": "number_timer_2",
  552. "number_timer_outlet_3": "number_timer_3",
  553. "number_timer_gang_1": "number_timer_1",
  554. "number_timer_gang_2": "number_timer_2",
  555. "number_timer_gang_3": "number_timer_3",
  556. }
  557. return replace_unique_ids(entity_entry, device_id, conf_file, replacements)
  558. await async_migrate_entries(hass, entry.entry_id, update_unique_id13_8)
  559. hass.config_entries.async_update_entry(entry, minor_version=8)
  560. if entry.version == 13 and entry.minor_version < 9:
  561. # Migrate unique ids of existing entities to new id taking into
  562. # account translation_key, and standardising naming
  563. device_id = entry.unique_id
  564. conf_file = await hass.async_add_executor_job(
  565. get_config,
  566. entry.data[CONF_TYPE],
  567. )
  568. if conf_file is None:
  569. _LOGGER.error(
  570. NOT_FOUND,
  571. entry.data[CONF_TYPE],
  572. )
  573. return False
  574. @callback
  575. def update_unique_id13_9(entity_entry):
  576. """Update the unique id of an entity entry."""
  577. # Standardistion of entity naming to use translation_key
  578. replacements = {
  579. "number_delay_time": "number_delay",
  580. }
  581. return replace_unique_ids(entity_entry, device_id, conf_file, replacements)
  582. await async_migrate_entries(hass, entry.entry_id, update_unique_id13_9)
  583. hass.config_entries.async_update_entry(entry, minor_version=9)
  584. if entry.version == 13 and entry.minor_version < 10:
  585. # Migrate unique ids of existing entities to new id taking into
  586. # account translation_key, and standardising naming
  587. device_id = entry.unique_id
  588. conf_file = await hass.async_add_executor_job(
  589. get_config,
  590. entry.data[CONF_TYPE],
  591. )
  592. if conf_file is None:
  593. _LOGGER.error(
  594. NOT_FOUND,
  595. entry.data[CONF_TYPE],
  596. )
  597. return False
  598. @callback
  599. def update_unique_id13_10(entity_entry):
  600. """Update the unique id of an entity entry."""
  601. # Standardistion of entity naming to use translation_key
  602. replacements = {
  603. "switch_disturb_switch": "switch_do_not_disturb",
  604. }
  605. return replace_unique_ids(entity_entry, device_id, conf_file, replacements)
  606. await async_migrate_entries(hass, entry.entry_id, update_unique_id13_10)
  607. hass.config_entries.async_update_entry(entry, minor_version=10)
  608. return True
  609. async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
  610. _LOGGER.debug(
  611. "Setting up entry for device: %s",
  612. get_device_id(entry.data),
  613. )
  614. config = {**entry.data, **entry.options, "name": entry.title}
  615. try:
  616. device = await hass.async_add_executor_job(setup_device, hass, config)
  617. await device.async_refresh()
  618. except Exception as e:
  619. raise ConfigEntryNotReady("tuya-local device not ready") from e
  620. if not device.has_returned_state:
  621. raise ConfigEntryNotReady("tuya-local device offline")
  622. device_conf = await hass.async_add_executor_job(
  623. get_config,
  624. entry.data[CONF_TYPE],
  625. )
  626. if device_conf is None:
  627. _LOGGER.error(NOT_FOUND, config[CONF_TYPE])
  628. return False
  629. entities = set()
  630. for e in device_conf.all_entities():
  631. entities.add(e.entity)
  632. await hass.config_entries.async_forward_entry_setups(entry, entities)
  633. entry.add_update_listener(async_update_entry)
  634. return True
  635. async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
  636. _LOGGER.debug("Unloading entry for device: %s", get_device_id(entry.data))
  637. config = entry.data
  638. data = hass.data[DOMAIN][get_device_id(config)]
  639. device_conf = await hass.async_add_executor_job(
  640. get_config,
  641. config[CONF_TYPE],
  642. )
  643. if device_conf is None:
  644. _LOGGER.error(NOT_FOUND, config[CONF_TYPE])
  645. return False
  646. entities = {}
  647. for e in device_conf.all_entities():
  648. if e.config_id in data:
  649. entities[e.entity] = True
  650. for e in entities:
  651. await hass.config_entries.async_forward_entry_unload(entry, e)
  652. await async_delete_device(hass, config)
  653. del hass.data[DOMAIN][get_device_id(config)]
  654. return True
  655. async def async_update_entry(hass: HomeAssistant, entry: ConfigEntry):
  656. _LOGGER.debug("Updating entry for device: %s", get_device_id(entry.data))
  657. await async_unload_entry(hass, entry)
  658. await async_setup_entry(hass, entry)