Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
4 changes: 2 additions & 2 deletions custom_components/plugwise/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
from .coordinator import PlugwiseDataUpdateCoordinator
from .entity import PlugwiseEntity

PARALLEL_UPDATES = 0 # Upstream
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0


@dataclass(frozen=True)
Expand All @@ -54,7 +55,6 @@ class PlugwiseBinarySensorEntityDescription(BinarySensorEntityDescription):
PLUGWISE_BINARY_SENSORS: tuple[PlugwiseBinarySensorEntityDescription, ...] = (
PlugwiseBinarySensorEntityDescription(
key=BATTERY_STATE,
translation_key=BATTERY_STATE,
device_class=BinarySensorDeviceClass.BATTERY,
entity_category=EntityCategory.DIAGNOSTIC,
),
Expand Down
2 changes: 1 addition & 1 deletion custom_components/plugwise/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from .entity import PlugwiseEntity
from .util import plugwise_command

PARALLEL_UPDATES = 0 # Upstream
PARALLEL_UPDATES = 0


async def async_setup_entry(
Expand Down
2 changes: 1 addition & 1 deletion custom_components/plugwise/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
from .entity import PlugwiseEntity
from .util import plugwise_command

PARALLEL_UPDATES = 0 # Upstream
PARALLEL_UPDATES = 0


async def async_setup_entry(
Expand Down
138 changes: 96 additions & 42 deletions custom_components/plugwise/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

from copy import deepcopy
import logging
from typing import Any, Self

from plugwise import Smile
Expand Down Expand Up @@ -69,18 +70,25 @@

type PlugwiseConfigEntry = ConfigEntry[PlugwiseDataUpdateCoordinator]

_LOGGER = logging.getLogger(__name__)

# Upstream basically the whole file (excluding the pw-beta options)

SMILE_RECONF_SCHEMA = vol.Schema(
{
vol.Required(CONF_HOST): str,
}
)


def base_schema(
cf_input: ZeroconfServiceInfo | dict[str, Any] | None,
) -> vol.Schema:
def smile_user_schema(cf_input: ZeroconfServiceInfo | dict[str, Any] | None) -> vol.Schema:
"""Generate base schema for gateways."""
if not cf_input: # no discovery- or user-input available
return vol.Schema(
{
vol.Required(CONF_HOST): str,
vol.Required(CONF_PASSWORD): str,
# Port under investigation for removal (hence not added in #132878)
vol.Optional(CONF_PORT, default=DEFAULT_PORT): int,
vol.Required(CONF_USERNAME, default=SMILE): vol.In(
{SMILE: FLOW_SMILE, STRETCH: FLOW_STRETCH}
Expand All @@ -106,7 +114,7 @@ def base_schema(
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> Smile:
"""Validate whether the user input allows us to connect to the gateway.

Data has the keys from base_schema() with values provided by the user.
Data has the keys from the schema with values provided by the user.
"""
websession = async_get_clientsession(hass, verify_ssl=False)
api = Smile(
Expand All @@ -120,6 +128,32 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> Smile:
return api


async def verify_connection(
hass: HomeAssistant, user_input: dict[str, Any]
) -> tuple[Smile | None, dict[str, str]]:
"""Verify and return the gateway connection or an error."""
errors: dict[str, str] = {}

try:
return (await validate_input(hass, user_input), errors)
except ConnectionFailedError:
errors[CONF_BASE] = "cannot_connect"
except InvalidAuthentication:
errors[CONF_BASE] = "invalid_auth"
except InvalidSetupError:
errors[CONF_BASE] = "invalid_setup"
except (InvalidXMLError, ResponseError):
errors[CONF_BASE] = "response_error"
except UnsupportedDeviceError:
errors[CONF_BASE] = "unsupported"
except Exception: # noqa: BLE001
_LOGGER.exception(
"Unknown exception while verifying connection with your Plugwise Smile"
)
errors[CONF_BASE] = "unknown"
return (None, errors)


class PlugwiseConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Plugwise Smile."""

Expand Down Expand Up @@ -197,51 +231,71 @@ def is_matching(self, other_flow: Self) -> bool:

return False


async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step when using network/gateway setups."""
errors: dict[str, str] = {}

if not user_input:
return self.async_show_form(
step_id=SOURCE_USER,
data_schema=base_schema(self.discovery_info),
errors=errors,
)

if self.discovery_info:
user_input[CONF_HOST] = self.discovery_info.host
user_input[CONF_PORT] = self.discovery_info.port
user_input[CONF_USERNAME] = self._username

try:
api = await validate_input(self.hass, user_input)
except ConnectionFailedError:
errors[CONF_BASE] = "cannot_connect"
except InvalidAuthentication:
errors[CONF_BASE] = "invalid_auth"
except InvalidSetupError:
errors[CONF_BASE] = "invalid_setup"
except (InvalidXMLError, ResponseError):
errors[CONF_BASE] = "response_error"
except UnsupportedDeviceError:
errors[CONF_BASE] = "unsupported"
except Exception: # noqa: BLE001
errors[CONF_BASE] = "unknown"

if errors:
return self.async_show_form(
step_id=SOURCE_USER,
data_schema=base_schema(user_input),
errors=errors,
)

await self.async_set_unique_id(
api.smile_hostname or api.gateway_id, raise_on_progress=False
if user_input is not None:
if self.discovery_info:
user_input[CONF_HOST] = self.discovery_info.host
user_input[CONF_PORT] = self.discovery_info.port
user_input[CONF_USERNAME] = self._username

api, errors = await verify_connection(self.hass, user_input)
if api:
await self.async_set_unique_id(
api.smile_hostname or api.gateway_id, raise_on_progress=False
)
self._abort_if_unique_id_configured()
return self.async_create_entry(title=api.smile_name, data=user_input)

return self.async_show_form(
step_id=SOURCE_USER,
data_schema=smile_user_schema(self.discovery_info),
errors=errors,
)
self._abort_if_unique_id_configured()
return self.async_create_entry(title=api.smile_name, data=user_input)

async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reconfiguration of the integration."""
errors: dict[str, str] = {}

reconfigure_entry = self._get_reconfigure_entry()

if user_input:
# Redefine ingest existing username and password
full_input = {
CONF_HOST: user_input.get(CONF_HOST),
CONF_PORT: reconfigure_entry.data.get(CONF_PORT),
CONF_USERNAME: reconfigure_entry.data.get(CONF_USERNAME),
CONF_PASSWORD: reconfigure_entry.data.get(CONF_PASSWORD),
}

api, errors = await verify_connection(self.hass, full_input)
if api:
await self.async_set_unique_id(
api.smile_hostname or api.gateway_id, raise_on_progress=False
)
self._abort_if_unique_id_mismatch(reason="not_the_same_smile")
return self.async_update_reload_and_abort(
reconfigure_entry,
data_updates=full_input,
)

return self.async_show_form(
step_id="reconfigure",
data_schema=self.add_suggested_values_to_schema(
data_schema=SMILE_RECONF_SCHEMA,
suggested_values=reconfigure_entry.data,
),
description_placeholders={"title": reconfigure_entry.title},
errors=errors,
)


@staticmethod
@callback
Expand Down
24 changes: 19 additions & 5 deletions custom_components/plugwise/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,31 @@ async def _async_update_data(self) -> PlugwiseData:
await self._connect()
data = await self.api.async_update()
except ConnectionFailedError as err:
raise UpdateFailed("Failed to connect") from err
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="failed_to_connect",
) from err
except InvalidAuthentication as err:
raise ConfigEntryError("Authentication failed") from err
raise ConfigEntryError(
translation_domain=DOMAIN,
translation_key="authentication_failed",
) from err
except (InvalidXMLError, ResponseError) as err:
# pwbeta TODO; we had {err} in the text, but not upstream, do we want this?
raise UpdateFailed(
f"Invalid XML data or error from Plugwise device: {err}"
translation_domain=DOMAIN,
translation_key="invalid_xml_data",
) from err
except PlugwiseError as err:
raise UpdateFailed("Data incomplete or missing") from err
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="data_incomplete_or_missing",
) from err
except UnsupportedDeviceError as err:
raise ConfigEntryError("Device with unsupported firmware") from err
raise ConfigEntryError(
translation_domain=DOMAIN,
translation_key="unsupported_firmware",
) from err
else:
LOGGER.debug(f"{self.api.smile_name} data: %s", data)
await self.async_add_remove_devices(data, self.config_entry)
Expand Down
5 changes: 0 additions & 5 deletions custom_components/plugwise/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,3 @@ def available(self) -> bool:
def device(self) -> GwEntityData:
"""Return data for this device."""
return self.coordinator.data.devices[self._dev_id]

async def async_added_to_hass(self) -> None:
"""Subscribe to updates."""
self._handle_coordinator_update()
await super().async_added_to_hass()
2 changes: 1 addition & 1 deletion custom_components/plugwise/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from .entity import PlugwiseEntity
from .util import plugwise_command

PARALLEL_UPDATES = 0 # Upstream
PARALLEL_UPDATES = 0


@dataclass(frozen=True, kw_only=True)
Expand Down
85 changes: 85 additions & 0 deletions custom_components/plugwise/quality_scale.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
rules:
## Bronze
config-flow: done
test-before-configure: done
unique-config-entry: done
config-flow-test-coverage: done
runtime-data: done
test-before-setup: done
appropriate-polling: done
entity-unique-id: done
has-entity-name: done
entity-event-setup: done
dependency-transparency: done
action-setup:
status: exempt
comment: Plugwise integration has no custom actions
common-modules: done
docs-high-level-description:
status: todo
comment: Rewrite top section, docs PR prepared waiting for 36087 merge
docs-installation-instructions:
status: todo
comment: Docs PR 36087
docs-removal-instructions: done
docs-actions: done
brands: done
## Silver
config-entry-unloading: done
log-when-unavailable: done
entity-unavailable: done
action-exceptions: done
reauthentication-flow:
status: exempt
comment: The hubs have a hardcoded `Smile ID` printed on the sticker used as password, it can not be changed
parallel-updates:
status: todo
comment: Using coordinator, but required due to mutable platform
test-coverage: done
integration-owner: done
docs-installation-parameters:
status: todo
comment: Docs PR 36087 (partial) + todo rewrite generically (PR prepared)
docs-configuration-parameters:
status: exempt
comment: Plugwise has no options flow
## Gold
entity-translations: done
entity-device-class: done
devices: done
entity-category: done
entity-disabled-by-default: done
discovery: done
stale-devices: done
diagnostics: done
exception-translations: done
icon-translations: done
reconfiguration-flow: done
dynamic-devices: done
discovery-update-info: done
repair-issues:
status: exempt
comment: This integration does not have repairs
docs-use-cases:
status: todo
comment: Check for completeness, PR prepared waiting for 36087 merge
docs-supported-devices:
status: todo
comment: The list is there but could be improved for readability, PR prepared waiting for 36087 merge
docs-supported-functions:
status: todo
comment: Check for completeness, PR prepared waiting for 36087 merge
docs-data-update: done
docs-known-limitations:
status: todo
comment: Partial in 36087 but could be more elaborate
docs-troubleshooting:
status: todo
comment: Check for completeness, PR prepared waiting for 36087 merge
docs-examples:
status: todo
comment: Check for completeness, PR prepared waiting for 36087 merge
## Platinum
async-dependency: done
inject-websession: done
strict-typing: done
2 changes: 1 addition & 1 deletion custom_components/plugwise/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from .entity import PlugwiseEntity
from .util import plugwise_command

PARALLEL_UPDATES = 0 # Upstream
PARALLEL_UPDATES = 0


@dataclass(frozen=True, kw_only=True)
Expand Down
1 change: 1 addition & 0 deletions custom_components/plugwise/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
from .coordinator import PlugwiseDataUpdateCoordinator
from .entity import PlugwiseEntity

# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0


Expand Down
Loading
Loading