Skip to content

Commit 2e32d7b

Browse files
authored
Merge pull request #917 from JurajNyiri/6.1.4
Fix #887: Integration keeps redetecting already configured devices when using hostname
2 parents 738cf5e + 3cbc998 commit 2e32d7b

File tree

5 files changed

+169
-17
lines changed

5 files changed

+169
-17
lines changed

custom_components/tapo_control/__init__.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from homeassistant.core import HomeAssistant, callback
66
from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS
77
from homeassistant.config_entries import ConfigEntry
8+
from homeassistant.helpers.device_registry import async_get as device_registry_async_get
89
from homeassistant.const import (
910
CONF_IP_ADDRESS,
1011
CONF_USERNAME,
@@ -41,6 +42,7 @@
4142
MEDIA_SYNC_HOURS,
4243
MEDIA_VIEW_DAYS_ORDER,
4344
MEDIA_VIEW_RECORDINGS_ORDER,
45+
REPORTED_IP_ADDRESS,
4446
RTSP_TRANS_PROTOCOLS,
4547
SOUND_DETECTION_DURATION,
4648
SOUND_DETECTION_PEAK,
@@ -60,6 +62,7 @@
6062
getDataForController,
6163
getEntryStorageFile,
6264
getHotDirPathForEntry,
65+
getIP,
6366
isUsingHTTPS,
6467
mediaCleanup,
6568
registerController,
@@ -291,6 +294,70 @@ def update_unique_id(entity_entry):
291294

292295
hass.config_entries.async_update_entry(config_entry, data=new, version=20)
293296

297+
if config_entry.version == 20:
298+
new = {**config_entry.data}
299+
try:
300+
host = config_entry.data.get(CONF_IP_ADDRESS)
301+
controlPort = config_entry.data.get(CONTROL_PORT)
302+
isKlapDevice = config_entry.data.get(IS_KLAP_DEVICE)
303+
cloud_password = config_entry.data.get(CLOUD_PASSWORD)
304+
username = config_entry.data.get(CONF_USERNAME)
305+
password = config_entry.data.get(CONF_PASSWORD)
306+
if cloud_password != "":
307+
LOGGER.debug("Setting up controller using cloud password.")
308+
tapoController = await hass.async_add_executor_job(
309+
registerController,
310+
host,
311+
controlPort,
312+
"admin",
313+
cloud_password,
314+
cloud_password,
315+
"",
316+
None,
317+
isKlapDevice,
318+
hass,
319+
)
320+
else:
321+
LOGGER.debug("Setting up controller using username and password.")
322+
tapoController = await hass.async_add_executor_job(
323+
registerController,
324+
host,
325+
controlPort,
326+
username,
327+
password,
328+
"",
329+
"",
330+
None,
331+
isKlapDevice,
332+
hass,
333+
)
334+
camData = await getCamData(hass, tapoController)
335+
reported_ip_address = getIP(camData)
336+
LOGGER.debug(f"Detected IP: {reported_ip_address}")
337+
new[REPORTED_IP_ADDRESS] = reported_ip_address
338+
339+
hass.config_entries.async_update_entry(
340+
config_entry,
341+
data=new,
342+
version=21,
343+
unique_id=DOMAIN
344+
+ (reported_ip_address if reported_ip_address else host),
345+
)
346+
347+
except Exception as e:
348+
LOGGER.error(
349+
"Unable to connect to Tapo: Cameras Control controller: %s", str(e)
350+
)
351+
if "Invalid authentication data" in str(e):
352+
raise ConfigEntryAuthFailed(e)
353+
elif "Temporary Suspension:" in str(
354+
e
355+
): # keep retrying to authenticate eventually, or throw
356+
# ConfigEntryAuthFailed on invalid auth eventually
357+
raise ConfigEntryNotReady
358+
# Retry for anything else
359+
raise ConfigEntryNotReady
360+
294361
LOGGER.info("Migration to version %s successful", config_entry.version)
295362

296363
return True

custom_components/tapo_control/config_flow.py

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
from homeassistant.helpers.selector import selector
1717

1818
from .utils import (
19+
getCamData,
20+
getIP,
1921
registerController,
2022
isRtspStreamWorking,
2123
areCameraPortsOpened,
@@ -40,6 +42,7 @@
4042
MEDIA_VIEW_DAYS_ORDER_OPTIONS,
4143
MEDIA_VIEW_RECORDINGS_ORDER,
4244
MEDIA_VIEW_RECORDINGS_ORDER_OPTIONS,
45+
REPORTED_IP_ADDRESS,
4346
SOUND_DETECTION_DURATION,
4447
SOUND_DETECTION_PEAK,
4548
SOUND_DETECTION_RESET,
@@ -58,7 +61,7 @@
5861
class FlowHandler(ConfigFlow):
5962
"""Handle a config flow."""
6063

61-
VERSION = 20
64+
VERSION = 21
6265

6366
@staticmethod
6467
def async_get_options_flow(config_entry):
@@ -129,7 +132,6 @@ async def async_step_reauth_confirm_stream(self, user_input=None):
129132
self.hass.config_entries.async_update_entry(
130133
self.reauth_entry,
131134
data=allConfigData,
132-
unique_id=DOMAIN + tapoHost,
133135
)
134136
try:
135137
LOGGER.debug(
@@ -249,9 +251,7 @@ async def async_step_reauth_confirm_cloud(self, user_input=None):
249251
allConfigData = {**self.reauth_entry.data}
250252
allConfigData[CLOUD_PASSWORD] = cloudPassword
251253
self.hass.config_entries.async_update_entry(
252-
self.reauth_entry,
253-
data=allConfigData,
254-
unique_id=DOMAIN + tapoHost,
254+
self.reauth_entry, data=allConfigData
255255
)
256256
await self.hass.config_entries.async_reload(self.reauth_entry.entry_id)
257257
return self.async_abort(reason="reauth_successful")
@@ -348,6 +348,8 @@ def _async_host_already_configured(self, host):
348348
for entry in self._async_current_entries():
349349
if entry.data.get(CONF_IP_ADDRESS) == host:
350350
return True
351+
elif entry.data.get(REPORTED_IP_ADDRESS) == host:
352+
return True
351353
return False
352354

353355
async def async_step_other_options(self, user_input=None):
@@ -422,7 +424,9 @@ async def async_step_other_options(self, user_input=None):
422424
"[ADD DEVICE][%s] Saving entry.",
423425
self.tapoHost,
424426
)
425-
await self.async_set_unique_id(DOMAIN + host)
427+
await self.async_set_unique_id(
428+
DOMAIN + (self.reportedIPAddress if self.reportedIPAddress else host)
429+
)
426430
return self.async_create_entry(
427431
title=host,
428432
data={
@@ -439,6 +443,7 @@ async def async_step_other_options(self, user_input=None):
439443
CONF_USERNAME: username,
440444
CONF_PASSWORD: password,
441445
CLOUD_PASSWORD: cloud_password,
446+
REPORTED_IP_ADDRESS: self.reportedIPAddress,
442447
ENABLE_SOUND_DETECTION: enable_sound_detection,
443448
SOUND_DETECTION_PEAK: sound_detection_peak,
444449
SOUND_DETECTION_DURATION: sound_detection_duration,
@@ -521,13 +526,15 @@ async def async_step_auth_cloud_password(self, user_input=None):
521526
self.tapoHost,
522527
)
523528
cloud_password = user_input[CLOUD_PASSWORD]
524-
await self.hass.async_add_executor_job(
529+
tapoController = await self.hass.async_add_executor_job(
525530
registerController,
526531
self.tapoHost,
527532
self.tapoControlPort,
528533
"admin",
529534
cloud_password,
530535
)
536+
camData = await getCamData(self.hass, tapoController)
537+
self.reportedIPAddress = getIP(camData)
531538
LOGGER.debug(
532539
"[ADD DEVICE][%s] Cloud password works for control.",
533540
self.tapoHost,
@@ -587,19 +594,22 @@ async def async_step_auth_klap(self, user_input=None):
587594
password = user_input[CONF_PASSWORD]
588595
self.tapoUsername = email
589596
self.tapoPassword = password
597+
reported_ip_address = False
590598

591599
try:
592600
LOGGER.debug(
593601
"[ADD DEVICE][%s] Testing control of camera using KLAP Account.",
594602
host,
595603
)
596-
await self.hass.async_add_executor_job(
604+
tapoController = await self.hass.async_add_executor_job(
597605
registerController,
598606
host,
599607
controlPort,
600608
email,
601609
password,
602610
)
611+
camData = await getCamData(self.hass, tapoController)
612+
reported_ip_address = getIP(camData)
603613
LOGGER.warning(
604614
"[ADD DEVICE][%s] KLAP Account works for control.",
605615
host,
@@ -617,7 +627,9 @@ async def async_step_auth_klap(self, user_input=None):
617627
LOGGER.error(e)
618628
raise Exception(e)
619629

620-
await self.async_set_unique_id(DOMAIN + host)
630+
await self.async_set_unique_id(
631+
DOMAIN + (reported_ip_address if reported_ip_address else host)
632+
)
621633
return self.async_create_entry(
622634
title=host,
623635
data={
@@ -630,6 +642,7 @@ async def async_step_auth_klap(self, user_input=None):
630642
ENABLE_STREAM: False,
631643
ENABLE_TIME_SYNC: False,
632644
CONF_IP_ADDRESS: host,
645+
REPORTED_IP_ADDRESS: reported_ip_address,
633646
CONTROL_PORT: controlPort,
634647
CONF_USERNAME: email,
635648
CONF_PASSWORD: password,
@@ -800,18 +813,20 @@ async def async_step_auth_optional_cloud(self, user_input=None):
800813
self.tapoHost,
801814
)
802815
cloud_password = user_input[CLOUD_PASSWORD]
803-
await self.hass.async_add_executor_job(
816+
tapoController = await self.hass.async_add_executor_job(
804817
registerController,
805818
self.tapoHost,
806819
self.tapoControlPort,
807820
"admin",
808821
cloud_password,
809822
)
823+
camData = await getCamData(self.hass, tapoController)
810824
LOGGER.debug(
811825
"[ADD DEVICE][%s] Cloud password works for control.",
812826
self.tapoHost,
813827
)
814828
self.tapoCloudPassword = cloud_password
829+
self.reportedIPAddress = getIP(camData)
815830
return await self.async_step_other_options()
816831
except Exception as e:
817832
if "Failed to establish a new connection" in str(e):
@@ -1455,7 +1470,7 @@ async def async_step_auth(self, user_input=None):
14551470
ip_address,
14561471
)
14571472
try:
1458-
await self.hass.async_add_executor_job(
1473+
tapoController = await self.hass.async_add_executor_job(
14591474
registerController,
14601475
ip_address,
14611476
controlPort,
@@ -1482,18 +1497,59 @@ async def async_step_auth(self, user_input=None):
14821497

14831498
ipChanged = self.config_entry.data[CONF_IP_ADDRESS] != ip_address
14841499

1500+
reported_ip_address = False
1501+
14851502
if ipChanged:
1503+
if tapoController is None:
1504+
isKLAPResult = await self.hass.async_add_executor_job(
1505+
isKLAP, ip_address, 80, 5
1506+
)
1507+
if cloud_password != "":
1508+
LOGGER.debug("Setting up controller using cloud password.")
1509+
tapoController = await self.hass.async_add_executor_job(
1510+
registerController,
1511+
ip_address,
1512+
controlPort,
1513+
"admin",
1514+
cloud_password,
1515+
cloud_password,
1516+
"",
1517+
None,
1518+
isKLAPResult,
1519+
self.hass,
1520+
)
1521+
else:
1522+
LOGGER.debug(
1523+
"Setting up controller using username and password."
1524+
)
1525+
tapoController = await self.hass.async_add_executor_job(
1526+
registerController,
1527+
ip_address,
1528+
controlPort,
1529+
username,
1530+
password,
1531+
"",
1532+
"",
1533+
None,
1534+
isKLAPResult,
1535+
self.hass,
1536+
)
14861537
LOGGER.debug("[%s] IP Changed, cleaning up devices...", ip_address)
1538+
camData = await getCamData(self.hass, tapoController)
1539+
reported_ip_address = getIP(camData)
14871540
device_registry = device_registry_async_get(self.hass)
1541+
devices_to_remove = []
14881542
for deviceID in device_registry.devices:
14891543
device = device_registry.devices[deviceID]
1490-
LOGGER.debug("[%s] Removing device %s.", ip_address, deviceID)
14911544
if (
14921545
len(device.config_entries)
14931546
and list(device.config_entries)[0]
14941547
== self.config_entry.entry_id
14951548
):
1496-
device_registry.async_remove_device(device.id)
1549+
devices_to_remove.append(device.id)
1550+
for deviceID in devices_to_remove:
1551+
LOGGER.debug("[%s] Removing device %s.", ip_address, deviceID)
1552+
device_registry.async_remove_device(deviceID)
14971553
else:
14981554
LOGGER.debug(
14991555
"[%s] Skipping removal of devices since IP address did not change.",
@@ -1533,6 +1589,7 @@ async def async_step_auth(self, user_input=None):
15331589
allConfigData[CONF_IP_ADDRESS] = ip_address
15341590
allConfigData[CONF_USERNAME] = username
15351591
allConfigData[CONF_PASSWORD] = password
1592+
allConfigData[REPORTED_IP_ADDRESS] = reported_ip_address
15361593
allConfigData[CLOUD_PASSWORD] = cloud_password
15371594
allConfigData[ENABLE_TIME_SYNC] = enable_time_sync
15381595
allConfigData[CONF_EXTRA_ARGUMENTS] = extra_arguments
@@ -1542,7 +1599,8 @@ async def async_step_auth(self, user_input=None):
15421599
self.hass.config_entries.async_update_entry(
15431600
self.config_entry,
15441601
data=allConfigData,
1545-
unique_id=DOMAIN + ip_address,
1602+
unique_id=DOMAIN
1603+
+ (reported_ip_address if reported_ip_address else ip_address),
15461604
)
15471605

15481606
if ipChanged or rtspEnablementChanged:

custom_components/tapo_control/const.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from homeassistant.helpers import config_validation as cv
77

88
CONTROL_PORT = "control_port"
9-
PYTAPO_REQUIRED_VERSION = "3.3.41"
9+
PYTAPO_REQUIRED_VERSION = "3.3.44"
1010
DOMAIN = "tapo_control"
1111
BRAND = "TP-Link"
1212
ALARM_MODE = "alarm_mode"
@@ -32,6 +32,7 @@
3232
ENABLE_MEDIA_SYNC = "enable_media_sync"
3333

3434
IS_KLAP_DEVICE = "is_klap_device"
35+
REPORTED_IP_ADDRESS = "reported_ip_address"
3536
UPDATE_INTERVAL_MAIN = "update_interval_main"
3637
UPDATE_INTERVAL_BATTERY = "update_interval_battery"
3738

custom_components/tapo_control/manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
"codeowners": [
77
"@JurajNyiri"
88
],
9-
"version": "6.1.3",
9+
"version": "6.1.4",
1010
"requirements": [
11-
"pytapo==3.3.41",
11+
"pytapo==3.3.44",
1212
"python-kasa[speedups]==0.10.2"
1313
],
1414
"dependencies": [

0 commit comments

Comments
 (0)