Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

breaking(core): dhcp discovery step and cleanup tracking feature #711

Merged
merged 5 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ tapo:
This will enable tapo device discovery. Not all tapo devices supports tapo discovery, so if you not find it, try adding manually.
Also tapo integration discovery filters out not supported devices!

#### Device IP Tracking
By using DHCP home assistant discovery the feature of mac tracking is now disabled, cause HA can track it automatically now, please be sure
to have DHCP discovery not disable on your `configuration.yaml` (by default is active).

[BREAKING] Tracking mac address feature is now disabled cause not recommended by HA. The tracking is now performed by HA itself.

### Supported devices

- [x] pure async home assistant's method
Expand All @@ -65,8 +71,6 @@ Also tapo integration discovery filters out not supported devices!

### Additional features

- [x] tracking of ip address. Cause Tapo local discovery isn't working for a lot of Tapo devices, this integration can try to track ip address changes by reling on MAC address.
This requires Home Assistant to runs on the same devices network and the capability to send ARP and broadcast packets.
- [x] manually change ip address. Now you can change the ip address of a tapo device wihtout removing and re-adding it.

# How to install
Expand Down
11 changes: 4 additions & 7 deletions custom_components/tapo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from custom_components.tapo.discovery import discovery_tapo_devices
from custom_components.tapo.errors import DeviceNotSupported
from custom_components.tapo.hass_tapo import HassTapo
from custom_components.tapo.migrations import migrate_entry_to_v6
from custom_components.tapo.migrations import migrate_entry_to_v7
from custom_components.tapo.setup_helpers import create_api_from_config
from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry
Expand All @@ -23,7 +23,6 @@
from .const import CONF_DISCOVERED_DEVICE_INFO
from .const import CONF_HOST
from .const import CONF_MAC
from .const import CONF_TRACK_DEVICE
from .const import DEFAULT_POLLING_RATE_S
from .const import DISCOVERY_INTERVAL
from .const import DOMAIN
Expand Down Expand Up @@ -65,10 +64,9 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry):
"""Migrate old entry."""
_LOGGER.debug("Migrating from version %s", config_entry.version)

if config_entry.version != 6:
await migrate_entry_to_v6(hass, config_entry)

_LOGGER.info("Migration to version %s successful", config_entry.version)
if config_entry.version != 7:
await migrate_entry_to_v7(hass, config_entry)
_LOGGER.info("Migration to version %s successful", config_entry.version)

return True

Expand Down Expand Up @@ -113,6 +111,5 @@ def async_create_discovery_flow(
CONF_HOST: device.ip,
CONF_MAC: mac,
CONF_SCAN_INTERVAL: DEFAULT_POLLING_RATE_S,
CONF_TRACK_DEVICE: False,
},
)
26 changes: 13 additions & 13 deletions custom_components/tapo/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,26 @@
from custom_components.tapo.const import CONF_HOST
from custom_components.tapo.const import CONF_MAC
from custom_components.tapo.const import CONF_PASSWORD
from custom_components.tapo.const import CONF_TRACK_DEVICE
from custom_components.tapo.const import CONF_USERNAME
from custom_components.tapo.const import DEFAULT_POLLING_RATE_S
from custom_components.tapo.const import DOMAIN
from custom_components.tapo.const import STEP_ADVANCED_SETTINGS
from custom_components.tapo.const import STEP_DISCOVERY_REQUIRE_AUTH
from custom_components.tapo.const import STEP_INIT
from custom_components.tapo.const import SUPPORTED_DEVICES
from custom_components.tapo.discovery import discover_tapo_device
from custom_components.tapo.errors import CannotConnect
from custom_components.tapo.errors import InvalidAuth
from custom_components.tapo.errors import InvalidHost
from custom_components.tapo.setup_helpers import get_host_port
from homeassistant import config_entries
from homeassistant import data_entry_flow
from homeassistant.components.dhcp import DhcpServiceInfo
from homeassistant.config_entries import ConfigEntry
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_SCAN_INTERVAL
from homeassistant.core import callback
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from homeassistant.helpers.typing import DiscoveryInfoType
from plugp100.api.tapo_client import TapoClient
Expand All @@ -50,12 +52,6 @@
CONF_USERNAME, description="The username used with Tapo App, so your email"
): str,
vol.Required(CONF_PASSWORD, description="The password used with Tapo App"): str,
vol.Optional(
CONF_TRACK_DEVICE,
description="Try to track device dynamic ip using MAC address. (Your HA must be able to access to same network of device)",
default=False,
): bool,
vol.Optional(CONF_ADVANCED_SETTINGS, description="Advanced settings"): bool,
}
)

Expand Down Expand Up @@ -92,11 +88,6 @@ def step_options(entry: config_entries.ConfigEntry) -> vol.Schema:
description="Polling rate in seconds (e.g. 0.5 seconds means 500ms)",
default=entry.data.get(CONF_SCAN_INTERVAL, DEFAULT_POLLING_RATE_S),
): vol.All(vol.Coerce(float), vol.Clamp(min=1)),
vol.Optional(
CONF_TRACK_DEVICE,
description="Try to track device dynamic ip using MAC address. (Your HA must be able to access to same network of device)",
default=entry.data.get(CONF_TRACK_DEVICE, False),
): bool,
}
)

Expand All @@ -119,6 +110,16 @@ def __init__(self) -> None:
self.first_step_data: Optional[FirstStepData] = None
self._discovered_info: DiscoveredDevice | None = None

async def async_step_dhcp(
self, discovery_info: DhcpServiceInfo
) -> data_entry_flow.FlowResult:
"""Handle discovery via dhcp."""
mac_address = dr.format_mac(discovery_info.macaddress)
if discovered_device := await discover_tapo_device(self.hass, mac_address):
return await self._async_handle_discovery(
discovery_info.ip, discovered_device.mac, discovered_device
)

async def async_step_integration_discovery(
self, discovery_info: DiscoveryInfoType
) -> data_entry_flow.FlowResult:
Expand Down Expand Up @@ -285,7 +286,6 @@ async def _async_create_config_entry_from_device_info(
CONF_HOST: info.ip,
CONF_MAC: info.mac,
CONF_SCAN_INTERVAL: DEFAULT_POLLING_RATE_S,
CONF_TRACK_DEVICE: options.pop(CONF_TRACK_DEVICE, False),
},
)

Expand Down
13 changes: 7 additions & 6 deletions custom_components/tapo/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,13 @@
SUPPORTED_DEVICE_AS_LED_STRIP = ["l930", "l920", "l900"]


SUPPORTED_DEVICES = SUPPORTED_DEVICE_AS_LED_STRIP\
+ SUPPORTED_DEVICE_AS_LIGHT\
+ SUPPORTED_HUB_DEVICE_MODEL\
+ SUPPORTED_DEVICE_AS_SWITCH\
+ [SUPPORTED_POWER_STRIP_DEVICE_MODEL]
SUPPORTED_DEVICES = (
SUPPORTED_DEVICE_AS_LED_STRIP
+ SUPPORTED_DEVICE_AS_LIGHT
+ SUPPORTED_HUB_DEVICE_MODEL
+ SUPPORTED_DEVICE_AS_SWITCH
+ [SUPPORTED_POWER_STRIP_DEVICE_MODEL]
)

ISSUE_URL = "https://github.com/petretiandrea/home-assistant-tapo-p100/issues"

Expand All @@ -80,7 +82,6 @@
CONF_USERNAME = "username"
CONF_PASSWORD = "password"
CONF_ADVANCED_SETTINGS = "advanced_settings"
CONF_TRACK_DEVICE = "track_device_mac"

CONF_DISCOVERED_DEVICE_INFO = "discovered_device_info"

Expand Down
12 changes: 10 additions & 2 deletions custom_components/tapo/discovery.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import asyncio
from itertools import chain
from typing import Optional

from custom_components.tapo.const import DISCOVERY_TIMEOUT
from homeassistant.components import network
Expand All @@ -13,9 +14,7 @@

async def discovery_tapo_devices(hass: HomeAssistant) -> dict[str, DiscoveredDevice]:
loop = asyncio.get_event_loop()

broadcast_addresses = await network.async_get_ipv4_broadcast_addresses(hass)
print("Addresses ", broadcast_addresses)
discovery_tasks = [
loop.run_in_executor(
None,
Expand All @@ -29,3 +28,12 @@ async def discovery_tapo_devices(hass: HomeAssistant) -> dict[str, DiscoveredDev
dr.format_mac(device.mac): device
for device in chain(*await asyncio.gather(*discovery_tasks))
}


async def discover_tapo_device(
hass: HomeAssistant, mac: str
) -> Optional[DiscoveredDevice]:
found_devices = await discovery_tapo_devices(hass)
return next(
filter(lambda x: dr.format_mac(x.mac) == mac, found_devices.values()), None
)
4 changes: 4 additions & 0 deletions custom_components/tapo/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from custom_components.tapo.const import DOMAIN
from custom_components.tapo.coordinators import TapoDataCoordinator
from homeassistant.core import callback
from homeassistant.helpers import device_registry
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from plugp100.responses.device_state import DeviceInfo as TapoDeviceInfo
Expand Down Expand Up @@ -31,6 +32,9 @@ def device_info(self) -> DeviceInfo:
"manufacturer": "TP-Link",
"sw_version": self._base_data.firmware_version,
"hw_version": self._base_data.hardware_version,
"connections": {
(device_registry.CONNECTION_NETWORK_MAC, self._base_data.mac)
},
}

@property
Expand Down
35 changes: 0 additions & 35 deletions custom_components/tapo/helpers.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import ipaddress
from typing import Optional
from typing import TypeVar

from homeassistant.components.network.models import Adapter
from homeassistant.util.color import (
color_temperature_kelvin_to_mired as kelvin_to_mired,
)
Expand Down Expand Up @@ -62,36 +60,3 @@ def tapo_to_hass_color_temperature(
max_value=mireds[1],
)
return None


async def find_adapter_for(
adapters: list[Adapter], ip: Optional[str]
) -> Optional[Adapter]:
default_enabled = next(
iter(
[
adapter
for adapter in adapters
if adapter.get("enabled") and adapter.get("default")
]
),
None,
)
if ip is None: # search for adapter enabled and default
return default_enabled
else:
for adapter in adapters:
if adapter.get("enabled") and len(adapter.get("ipv4")) > 0:
adapter_network = get_network_of(adapter)
if ipaddress.ip_address(ip) in ipaddress.IPv4Network(
adapter_network, strict=False
):
return adapter

return default_enabled


def get_network_of(adapter: Adapter) -> Optional[str]:
if len(adapter.get("ipv4")) > 0:
return f"{adapter.get('ipv4')[0].get('address')}/{adapter.get('ipv4')[0].get('network_prefix')}"
return None
Empty file.
10 changes: 0 additions & 10 deletions custom_components/tapo/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,6 @@
_LOGGER = logging.getLogger(__name__)


# async def async_setup_platform(
# hass: HomeAssistant,
# config: Dict[str, Any],
# async_add_entities: AddEntitiesCallback,
# discovery_info=None,
# ) -> None:
# coordinator = await setup_from_platform_config(hass, config)
# _setup_from_coordinator(hass, coordinator, async_add_entities)


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
):
Expand Down
6 changes: 6 additions & 0 deletions custom_components/tapo/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
"documentation": "https://github.com/petretiandrea/home-assistant-tapo-p100",
"issue_tracker": "https://github.com/petretiandrea/home-assistant-tapo-p100/issues",
"requirements": ["plugp100==4.0.3"],
"dependencies": ["network"],
"dhcp": [
{
"registered_devices": true
}
],
"integration_type": "device",
"codeowners": ["@petretiandrea"]
}
8 changes: 3 additions & 5 deletions custom_components/tapo/migrations.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
from custom_components.tapo.const import CONF_MAC
from custom_components.tapo.const import CONF_TRACK_DEVICE
from custom_components.tapo.const import DEFAULT_POLLING_RATE_S
from custom_components.tapo.setup_helpers import create_api_from_config
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_SCAN_INTERVAL
from homeassistant.core import HomeAssistant


async def migrate_entry_to_v6(hass: HomeAssistant, config_entry: ConfigEntry):
api = create_api_from_config(hass, config_entry)
async def migrate_entry_to_v7(hass: HomeAssistant, config_entry: ConfigEntry):
api = await create_api_from_config(hass, config_entry)
new_data = {**config_entry.data}
scan_interval = new_data.pop(CONF_SCAN_INTERVAL, DEFAULT_POLLING_RATE_S)
mac = (await api.get_device_info()).map(lambda j: j["mac"]).get_or_else(None)
config_entry.version = 6
config_entry.version = 7
hass.config_entries.async_update_entry(
config_entry,
data={
**new_data,
CONF_MAC: mac,
CONF_TRACK_DEVICE: False,
CONF_SCAN_INTERVAL: scan_interval,
},
)
10 changes: 0 additions & 10 deletions custom_components/tapo/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,6 @@
]


# async def async_setup_platform(
# hass: HomeAssistant,
# config: Dict[str, Any],
# async_add_entities: AddEntitiesCallback,
# discovery_info=None,
# ) -> None:
# coordinator = await setup_from_platform_config(hass, config)
# _setup_from_coordinator(hass, coordinator, async_add_entities)


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
):
Expand Down
Loading