Skip to content
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
22 changes: 14 additions & 8 deletions homeassistant/components/blebox/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
"""The BleBox devices integration."""

import logging

from blebox_uniapi.box import Box
from blebox_uniapi.error import Error
from blebox_uniapi.error import ConnectionError, Error, HttpError, UnauthorizedRequest
from blebox_uniapi.session import ApiHost

from homeassistant.const import (
Expand All @@ -14,14 +12,16 @@
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryError,
ConfigEntryNotReady,
)

from .const import DEFAULT_SETUP_TIMEOUT
from .coordinator import BleBoxConfigEntry, BleBoxCoordinator
from .helpers import get_maybe_authenticated_session

_LOGGER = logging.getLogger(__name__)

PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.BUTTON,
Expand Down Expand Up @@ -50,9 +50,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: BleBoxConfigEntry) -> bo

try:
product = await Box.async_from_host(api_host)
except Error as ex:
_LOGGER.error("Identify failed at %s:%d (%s)", api_host.host, api_host.port, ex)
except UnauthorizedRequest as ex:
raise ConfigEntryAuthFailed from ex
except (
ConnectionError,
HttpError,
) as ex:
raise ConfigEntryNotReady from ex
except Error as ex:
raise ConfigEntryError from ex

coordinator = BleBoxCoordinator(hass, entry, product)
await coordinator.async_config_entry_first_refresh()
Expand Down
62 changes: 61 additions & 1 deletion homeassistant/components/blebox/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Config flow for BleBox devices integration."""

from collections.abc import Mapping
import logging
from typing import Any

Expand All @@ -26,6 +27,7 @@
DEFAULT_PORT,
DEFAULT_SETUP_TIMEOUT,
DOMAIN,
INVALID_AUTH,
UNKNOWN,
UNSUPPORTED_VERSION,
)
Expand All @@ -46,6 +48,7 @@
LOG_MSG = {
UNSUPPORTED_VERSION: "Outdated firmware",
CANNOT_CONNECT: "Failed to identify device",
INVALID_AUTH: "Authentication failed",
UNKNOWN: "Unknown error while identifying device",
}

Expand Down Expand Up @@ -87,7 +90,7 @@ async def _async_from_host_or_form(
)
except UnauthorizedRequest as ex:
return None, self.handle_step_exception(
ex, schema, host, port, CANNOT_CONNECT, _LOGGER.error, step_id
ex, schema, host, port, INVALID_AUTH, _LOGGER.error, step_id
)
except Error as ex:
return None, self.handle_step_exception(
Expand Down Expand Up @@ -115,6 +118,8 @@ async def async_step_zeroconf(

try:
product = await Box.async_from_host(api_host)
except UnauthorizedRequest:
return self.async_abort(reason="authorization_required")
except UnsupportedBoxVersion:
return self.async_abort(reason="unsupported_device_version")
except UnsupportedBoxResponse:
Expand Down Expand Up @@ -246,3 +251,58 @@ async def async_step_reconfigure(
reconfigure_entry,
data_updates=data_updates,
)

async def async_step_reauth(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle reauthentication upon an API authentication error."""
self.context["title_placeholders"] = {
"name": self._get_reauth_entry().title,
"host": entry_data[CONF_HOST],
}
return await self.async_step_reauth_confirm()

async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reauthentication confirmation."""
errors: dict[str, str] = {}
reauth_entry = self._get_reauth_entry()
host = reauth_entry.data[CONF_HOST]
port = reauth_entry.data[CONF_PORT]

if user_input is not None:
username = user_input.get(CONF_USERNAME)
password = user_input.get(CONF_PASSWORD)
websession = get_maybe_authenticated_session(self.hass, password, username)
api_host = ApiHost(
host, port, DEFAULT_SETUP_TIMEOUT, websession, self.hass.loop, _LOGGER
)
try:
await Box.async_from_host(api_host)
except UnauthorizedRequest:
errors["base"] = INVALID_AUTH
except Error:
errors["base"] = CANNOT_CONNECT
except RuntimeError:
errors["base"] = UNKNOWN
else:
return self.async_update_reload_and_abort(
reauth_entry,
data_updates={
CONF_USERNAME: username,
CONF_PASSWORD: password,
},
)

return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema(
{
vol.Inclusive(CONF_USERNAME, "auth"): str,
vol.Inclusive(CONF_PASSWORD, "auth"): str,
}
),
errors=errors,
description_placeholders={"address": f"{host}:{port}"},
)
1 change: 1 addition & 0 deletions homeassistant/components/blebox/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# translation strings
ADDRESS_ALREADY_CONFIGURED = "address_already_configured"
CANNOT_CONNECT = "cannot_connect"
INVALID_AUTH = "invalid_auth"
UNSUPPORTED_VERSION = "unsupported_version"
UNKNOWN = "unknown"

Expand Down
5 changes: 4 additions & 1 deletion homeassistant/components/blebox/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
import logging

from blebox_uniapi.box import Box
from blebox_uniapi.error import Error
from blebox_uniapi.error import Error, UnauthorizedRequest

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import DOMAIN
Expand Down Expand Up @@ -40,6 +41,8 @@ async def _async_update_data(self) -> None:
"""Fetch data from the BleBox device."""
try:
await self.box.async_update_data()
except UnauthorizedRequest as err:
raise ConfigEntryAuthFailed from err
except Error as err:
raise UpdateFailed(
translation_domain=DOMAIN,
Expand Down
15 changes: 15 additions & 0 deletions homeassistant/components/blebox/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
UnitOfEnergy,
UnitOfFrequency,
UnitOfPower,
UnitOfReactiveEnergy,
UnitOfReactivePower,
UnitOfSpeed,
UnitOfTemperature,
Expand Down Expand Up @@ -97,6 +98,20 @@ class BleBoxSensorEntityDescription(SensorEntityDescription):
native_unit_of_measurement=LIGHT_LUX,
state_class=SensorStateClass.MEASUREMENT,
),
BleBoxSensorEntityDescription(
key="forwardReactiveEnergy",
translation_key="forward_reactive_energy",
device_class=SensorDeviceClass.REACTIVE_ENERGY,
native_unit_of_measurement=UnitOfReactiveEnergy.KILO_VOLT_AMPERE_REACTIVE_HOUR,
state_class=SensorStateClass.TOTAL_INCREASING,
),
BleBoxSensorEntityDescription(
key="reverseReactiveEnergy",
translation_key="reverse_reactive_energy",
device_class=SensorDeviceClass.REACTIVE_ENERGY,
native_unit_of_measurement=UnitOfReactiveEnergy.KILO_VOLT_AMPERE_REACTIVE_HOUR,
state_class=SensorStateClass.TOTAL_INCREASING,
),
BleBoxSensorEntityDescription(
key="forwardActiveEnergy",
translation_key="forward_active_energy",
Expand Down
27 changes: 26 additions & 1 deletion homeassistant/components/blebox/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,33 @@
"abort": {
"address_already_configured": "A BleBox device is already configured at {address}.",
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"authorization_required": "The BleBox device requires authentication.",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"unique_id_mismatch": "The device identifier does not match the previously configured device."
"unique_id_mismatch": "The device identifier does not match the previously configured device.",
"unsupported_device_response": "The BleBox device returned an unrecognized response.",
"unsupported_device_version": "[%key:component::blebox::config::error::unsupported_version%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]",
"unsupported_version": "BleBox device has outdated firmware. Please upgrade it first."
},
"flow_title": "{name} ({host})",
"step": {
"reauth_confirm": {
"data": {
"password": "[%key:common::config_flow::data::password%]",
"username": "[%key:common::config_flow::data::username%]"
},
"data_description": {
"password": "The password for your BleBox device.",
"username": "The username for your BleBox device."
},
"description": "Enter credentials for the BleBox device at {address}.",
"title": "Reauthenticate your BleBox device"
},
"reconfigure": {
"data": {
"host": "[%key:common::config_flow::data::ip%]",
Expand Down Expand Up @@ -64,6 +81,10 @@
"current_n": { "name": "Current {index}" },
"forward_active_energy": { "name": "Forward active energy" },
"forward_active_energy_n": { "name": "Forward active energy {index}" },
"forward_reactive_energy": { "name": "Forward reactive energy" },
"forward_reactive_energy_n": {
"name": "Forward reactive energy {index}"
},
"frequency": { "name": "Frequency" },
"frequency_n": { "name": "Frequency {index}" },
"open_status": {
Expand All @@ -81,6 +102,10 @@
"reactive_power_n": { "name": "Reactive power {index}" },
"reverse_active_energy": { "name": "Reverse active energy" },
"reverse_active_energy_n": { "name": "Reverse active energy {index}" },
"reverse_reactive_energy": { "name": "Reverse reactive energy" },
"reverse_reactive_energy_n": {
"name": "Reverse reactive energy {index}"
},
"temperature": { "name": "Temperature" },
"temperature_n": { "name": "Temperature {index}" },
"voltage": { "name": "Voltage" },
Expand Down
14 changes: 13 additions & 1 deletion homeassistant/components/cloud/system_health.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,24 @@ async def system_health_info(hass: HomeAssistant) -> dict[str, Any]:
data["relayer_region"] = client.relayer_region
data["remote_enabled"] = client.prefs.remote_enabled
data["remote_connected"] = cloud.remote.is_connected
data["remote_server"] = cloud.remote.snitun_server
data["alexa_enabled"] = client.prefs.alexa_enabled
data["google_enabled"] = client.prefs.google_enabled
data["cloud_ice_servers_enabled"] = client.prefs.cloud_ice_servers_enabled
data["remote_server"] = cloud.remote.snitun_server
data["certificate_status"] = cloud.remote.certificate_status
data["instance_id"] = client.prefs.instance_id
data["iot_state"] = cloud.iot.state
data["iot_tries"] = cloud.iot.tries

if (cert := cloud.remote.certificate) is not None:
data["certificate_expire_date"] = cert.expire_date
data["certificate_fingerprint"] = cert.fingerprint
if cert.alternative_names:
data["certificate_alternative_names"] = cert.alternative_names

if (disconnect := cloud.iot.last_disconnect_reason) is not None:
data["iot_last_disconnect_clean"] = disconnect.clean
data["iot_last_disconnect_reason"] = disconnect.reason

data["can_reach_cert_server"] = system_health.async_check_can_reach_url(
hass, f"https://{cloud.acme_server}/directory"
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/hypontech/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .const import CONF_OEM, DEFAULT_OEM
from .coordinator import HypontechConfigEntry, HypontechDataCoordinator

_PLATFORMS: list[Platform] = [Platform.SENSOR]
Expand All @@ -19,6 +20,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: HypontechConfigEntry) ->
entry.data[CONF_USERNAME],
entry.data[CONF_PASSWORD],
session,
oem=int(entry.data.get(CONF_OEM, DEFAULT_OEM)),
)
try:
await hypontech_cloud.connect()
Expand Down
Loading
Loading