__init__.py 30 KB

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