__init__.py 29 KB

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