Skip to content

Commit

Permalink
feat(core): bump to plugp100 5.0.0
Browse files Browse the repository at this point in the history
added more tests and diagnostic feature

bump to plugp100 5.0.0

feat(core): bump to plugp100 5.0.0
  • Loading branch information
petretiandrea committed Apr 8, 2024
1 parent 2bcc6ad commit e7b58ff
Show file tree
Hide file tree
Showing 53 changed files with 1,415 additions and 2,875 deletions.
11 changes: 6 additions & 5 deletions .devcontainer/start.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#!/usr/bin/env bash

function workspacePath {
if [[ -n "$WORKSPACE_DIRECTORY" ]]; then
echo "${WORKSPACE_DIRECTORY}/"
else
echo "$(find /workspaces -mindepth 1 -maxdepth 1 -type d | tail -1)/"
fi
# if [[ -n "$WORKSPACE_DIRECTORY" ]]; then
# echo "${WORKSPACE_DIRECTORY}/"
# else
# echo "$(find /workspaces -mindepth 1 -maxdepth 1 -type d | tail -1)/"
# fi
echo "$PWD/"
}

action="$1" # Get the first argument passed to the script
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
push:
branches:
- main
- dev

jobs:
release:
Expand All @@ -26,7 +27,7 @@ jobs:
with:
semantic_version: 23.0.2
branches: |
[ 'main' ]
[ 'main', {name: 'dev', channel: 'dev', prerelease: true} ]
extra_plugins: |
@semantic-release/commit-analyzer@11.1.0
@semantic-release/release-notes-generator@12.1.0
Expand Down
9 changes: 8 additions & 1 deletion .releaserc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
{
"branches": ["main"],
"branches": [
"main",
{
"name": "dev",
"channel": "dev",
"prerelease": true
}
],
"ci": true,
"tagFormat": "${version}",
"plugins": [
Expand Down
32 changes: 10 additions & 22 deletions custom_components/tapo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from custom_components.tapo.errors import DeviceNotSupported
from custom_components.tapo.hass_tapo import HassTapo
from custom_components.tapo.migrations import migrate_entry_to_v8
from custom_components.tapo.setup_helpers import create_api_from_config
from custom_components.tapo.setup_helpers import create_device_config
from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_SCAN_INTERVAL
Expand All @@ -28,7 +28,6 @@
from .const import DISCOVERY_FEATURE_FLAG
from .const import DISCOVERY_INTERVAL
from .const import DOMAIN
from .const import HUB_PLATFORMS
from .const import PLATFORMS

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -57,8 +56,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up tapo from a config entry."""
hass.data.setdefault(DOMAIN, {})
try:
api = await create_api_from_config(hass, entry)
device = HassTapo(entry, api)
device_config = create_device_config(entry)
device = HassTapo(entry, device_config)
return await device.initialize_device(hass)
except DeviceNotSupported as error:
raise error
Expand All @@ -79,25 +78,13 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry):

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Unload a config entry."""
platform_to_unload = (
PLATFORMS if not entry.data.get("is_hub", False) else HUB_PLATFORMS
)
unload_ok = all(
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_unload(entry, component)
for component in platform_to_unload
]
)
)
if unload_ok:
_LOGGER.info("Unloaded entry for %s", str(entry.entry_id))
data = cast(
Optional[HassTapoDeviceData], hass.data[DOMAIN].pop(entry.entry_id, None)
)
if data:
data.config_entry_update_unsub()
data = cast(HassTapoDeviceData, hass.data[DOMAIN][entry.entry_id])
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)

if data and unload_ok:
device = data.coordinator.device
data.config_entry_update_unsub()
return unload_ok


Expand All @@ -117,5 +104,6 @@ def async_create_discovery_flow(
CONF_HOST: device.ip,
CONF_MAC: dr.format_mac(mac),
CONF_SCAN_INTERVAL: DEFAULT_POLLING_RATE_S,
CONF_DISCOVERED_DEVICE_INFO: device.as_dict
},
)
28 changes: 14 additions & 14 deletions custom_components/tapo/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
from typing import cast

from custom_components.tapo.const import DOMAIN
from custom_components.tapo.coordinators import HassTapoDeviceData
from custom_components.tapo.coordinators import TapoDataCoordinator
from custom_components.tapo.entity import CoordinatedTapoEntity
from custom_components.tapo.hub.binary_sensor import (
async_setup_entry as async_setup_binary_sensors,
)
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from plugp100.responses.device_state import DeviceInfo
from plugp100.new.components.overheat_component import OverheatComponent
from plugp100.new.tapodevice import TapoDevice

from custom_components.tapo.const import DOMAIN
from custom_components.tapo.coordinators import HassTapoDeviceData
from custom_components.tapo.coordinators import TapoDataCoordinator
from custom_components.tapo.entity import CoordinatedTapoEntity
from custom_components.tapo.hub.binary_sensor import async_setup_entry as async_setup_binary_sensors


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_devices):
# get tapo helper
data = cast(HassTapoDeviceData, hass.data[DOMAIN][entry.entry_id])
sensors = [OverheatSensor(data.coordinator)]
async_add_devices(sensors, True)
if data.device.has_component(OverheatComponent):
async_add_devices([OverheatSensor(data.coordinator, data.device)], True)
if data.coordinator.is_hub:
await async_setup_binary_sensors(hass, entry, async_add_devices)


class OverheatSensor(CoordinatedTapoEntity[TapoDataCoordinator], BinarySensorEntity):
def __init__(self, coordinator: TapoDataCoordinator):
super().__init__(coordinator)
class OverheatSensor(CoordinatedTapoEntity, BinarySensorEntity):
def __init__(self, coordinator: TapoDataCoordinator, device: TapoDevice):
super().__init__(coordinator, device)
self._attr_name = "Overheat"
self._attr_icon = "mdi:fire-alert"
self._attr_entity_category = EntityCategory.DIAGNOSTIC
Expand All @@ -41,4 +41,4 @@ def device_class(self) -> BinarySensorDeviceClass:

@property
def is_on(self) -> bool | None:
return self.coordinator.get_state_of(DeviceInfo).overheated
return self.coordinator.device.overheated
105 changes: 57 additions & 48 deletions custom_components/tapo/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,22 @@

import aiohttp
import voluptuous as vol
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.typing import DiscoveryInfoType
from plugp100.common.credentials import AuthCredential
from plugp100.discovery.discovered_device import DiscoveredDevice
from plugp100.new.device_factory import DeviceConnectConfiguration, connect
from plugp100.new.tapodevice import TapoDevice
from plugp100.responses.tapo_exception import TapoError
from plugp100.responses.tapo_exception import TapoException

from custom_components.tapo.const import CONF_ADVANCED_SETTINGS
from custom_components.tapo.const import CONF_DISCOVERED_DEVICE_INFO
from custom_components.tapo.const import CONF_HOST
Expand All @@ -22,23 +38,7 @@
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
from plugp100.common.credentials import AuthCredential
from plugp100.discovery.discovered_device import DiscoveredDevice
from plugp100.responses.device_state import DeviceInfo
from plugp100.responses.tapo_exception import TapoError
from plugp100.responses.tapo_exception import TapoException
from custom_components.tapo.setup_helpers import get_host_port, create_aiohttp_session

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -94,7 +94,7 @@ def step_options(entry: config_entries.ConfigEntry) -> vol.Schema:

@dataclasses.dataclass(frozen=False)
class FirstStepData:
state: Optional[DeviceInfo]
device: Optional[TapoDevice]
user_input: Optional[dict[str, Any]]


Expand All @@ -115,7 +115,7 @@ async def async_step_dhcp(
) -> 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):
if discovered_device := await discover_tapo_device(discovery_info.ip):
return await self._async_handle_discovery(
discovery_info.ip, mac_address, discovered_device
)
Expand All @@ -124,10 +124,11 @@ async def async_step_integration_discovery(
self, discovery_info: DiscoveryInfoType
) -> data_entry_flow.FlowResult:
"""Handle integration discovery."""
discovered_device = DiscoveredDevice.from_dict(discovery_info[CONF_DISCOVERED_DEVICE_INFO])
return await self._async_handle_discovery(
discovery_info[CONF_HOST],
discovery_info[CONF_MAC],
self.context[CONF_DISCOVERED_DEVICE_INFO],
discovered_device,
)

async def async_step_user(
Expand All @@ -140,17 +141,17 @@ async def async_step_user(

if user_input is not None:
try:
device_info = await self._async_get_device_info(user_input)
await self.async_set_unique_id(dr.format_mac(device_info.mac))
device = await self._async_get_device(user_input)
await self.async_set_unique_id(dr.format_mac(device.mac))
self._abort_if_unique_id_configured()
self._async_abort_entries_match({CONF_HOST: device_info.ip})
self._async_abort_entries_match({CONF_HOST: device.host})

if user_input.get(CONF_ADVANCED_SETTINGS, False):
self.first_step_data = FirstStepData(device_info, user_input)
self.first_step_data = FirstStepData(device, user_input)
return await self.async_step_advanced_config()
else:
return await self._async_create_config_entry_from_device_info(
device_info, user_input
device, user_input
)
except InvalidAuth as error:
errors["base"] = "invalid_auth"
Expand Down Expand Up @@ -185,7 +186,7 @@ async def async_step_advanced_config(
if user_input is not None:
polling_rate = user_input.get(CONF_SCAN_INTERVAL, DEFAULT_POLLING_RATE_S)
return self.async_create_entry(
title=self.first_step_data.state.friendly_name,
title=self.first_step_data.device.nickname,
data=self.first_step_data.user_input
| {CONF_SCAN_INTERVAL: polling_rate},
)
Expand Down Expand Up @@ -228,10 +229,10 @@ async def async_step_discovery_auth_confirm(

if user_input:
try:
device_info = await self._async_get_device_info_from_discovered(
device = await self._async_get_device_from_discovered(
self._discovered_info, user_input
)
await self.async_set_unique_id(dr.format_mac(device_info.mac))
await self.async_set_unique_id(dr.format_mac(device.mac))
self._abort_if_unique_id_configured()
except InvalidAuth as error:
errors["base"] = "invalid_auth"
Expand All @@ -244,7 +245,7 @@ async def async_step_discovery_auth_confirm(
_LOGGER.exception("Failed to setup invalid host %s", str(error))
else:
return await self._async_create_config_entry_from_device_info(
device_info, user_input
device, user_input
)

discovery_data = {
Expand Down Expand Up @@ -276,38 +277,46 @@ def _recover_config_on_entry_error(
return None

async def _async_create_config_entry_from_device_info(
self, info: DeviceInfo, options: dict[str, Any]
self, device: TapoDevice, options: dict[str, Any]
):
return self.async_create_entry(
title=info.friendly_name,
title=device.nickname,
data=options
| {
CONF_HOST: info.ip,
CONF_MAC: info.mac,
CONF_HOST: device.host,
CONF_MAC: device.mac,
CONF_SCAN_INTERVAL: DEFAULT_POLLING_RATE_S,
CONF_DISCOVERED_DEVICE_INFO: self._discovered_info.as_dict if self._discovered_info is not None else None
},
)

async def _async_get_device_info_from_discovered(
async def _async_get_device_from_discovered(
self, discovered: DiscoveredDevice, config: dict[str, Any]
) -> DeviceInfo:
return await self._async_get_device_info(config | {CONF_HOST: discovered.ip})

async def _async_get_device_info(self, config: dict[str, Any]) -> DeviceInfo:
) -> TapoDevice:
return await self._async_get_device(config | {CONF_HOST: discovered.ip}, discovered)

async def _async_get_device(
self,
config: dict[str, Any],
discovered_device: DiscoveredDevice | None = None,
) -> TapoDevice:
if not config[CONF_HOST]:
raise InvalidHost
try:
session = async_create_clientsession(self.hass)
session = create_aiohttp_session(self.hass)
credential = AuthCredential(config[CONF_USERNAME], config[CONF_PASSWORD])
host, port = get_host_port(config[CONF_HOST])
client = TapoClient.create(
credential, address=host, port=port, http_session=session
)
return (
(await client.get_device_info())
.map(lambda x: DeviceInfo(**x))
.get_or_raise()
)
if discovered_device is None:
host, port = get_host_port(config[CONF_HOST])
config = DeviceConnectConfiguration(
credentials=credential,
host=host,
port=port,
)
device = await connect(config=config, session=session)
else:
device = await discovered_device.get_tapo_device(credential, session)
await device.update()
return device
except TapoException as error:
self._raise_from_tapo_exception(error)
except (aiohttp.ClientError, Exception) as error:
Expand Down

0 comments on commit e7b58ff

Please sign in to comment.