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
29 changes: 28 additions & 1 deletion homeassistant/components/actron_air/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@

from actron_neo_api import ActronAirAPI, ActronAirAuthError

from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow, ConfigFlowResult
from homeassistant.config_entries import (
SOURCE_REAUTH,
SOURCE_RECONFIGURE,
ConfigFlow,
ConfigFlowResult,
)
from homeassistant.const import CONF_API_TOKEN
from homeassistant.exceptions import HomeAssistantError

Expand Down Expand Up @@ -105,6 +110,14 @@ async def async_step_finish_login(
data_updates={CONF_API_TOKEN: self._api.refresh_token_value},
)

# Check if this is a reconfigure flow
if self.source == SOURCE_RECONFIGURE:
self._abort_if_unique_id_mismatch(reason="wrong_account")
return self.async_update_reload_and_abort(
self._get_reconfigure_entry(),
data_updates={CONF_API_TOKEN: self._api.refresh_token_value},
)

self._abort_if_unique_id_configured()
return self.async_create_entry(
title=user_data.email,
Expand Down Expand Up @@ -138,6 +151,20 @@ async def async_step_reauth_confirm(

return self.async_show_form(step_id="reauth_confirm")

async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reconfiguration request."""
return await self.async_step_reconfigure_confirm()

async def async_step_reconfigure_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm reconfiguration dialog."""
if user_input is not None:
return await self.async_step_user()
return self.async_show_form(step_id="reconfigure_confirm")

async def async_step_connection_error(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/actron_air/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ rules:
entity-translations: done
exception-translations: done
icon-translations: done
reconfiguration-flow: todo
reconfiguration-flow: done
repair-issues:
status: exempt
comment: This integration does not have any known issues that require repair.
Expand Down
7 changes: 6 additions & 1 deletion homeassistant/components/actron_air/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
"oauth2_error": "Failed to start authentication flow",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"wrong_account": "You must reauthenticate with the same Actron Air account that was originally configured."
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"wrong_account": "You must authenticate with the same Actron Air account that was originally configured."
},
"error": {
"oauth2_error": "Failed to start authentication flow. Please try again later."
Expand All @@ -22,6 +23,10 @@
"description": "Your Actron Air authentication has expired. Select continue to reauthenticate with your Actron Air account. You will be prompted to log in again to restore the connection.",
"title": "Authentication expired"
},
"reconfigure_confirm": {
"description": "Reconfigure your Actron Air account. You will be prompted to log in again. Note: you must use the same account that was originally configured.",
"title": "Reconfigure Actron Air"
},
"timeout": {
"data": {},
"description": "The authentication process timed out. Please try again.",
Expand Down
12 changes: 8 additions & 4 deletions homeassistant/components/blebox/sensor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""BleBox sensor entities."""

from datetime import datetime
from datetime import datetime, timedelta

import blebox_uniapi.sensor

Expand Down Expand Up @@ -30,6 +30,9 @@
from . import BleBoxConfigEntry
from .entity import BleBoxEntity

SCAN_INTERVAL = timedelta(seconds=5)


SENSOR_TYPES = (
SensorEntityDescription(
key="pm1",
Expand All @@ -53,9 +56,9 @@
),
SensorEntityDescription(
key="powerConsumption",
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
state_class=SensorStateClass.TOTAL,
suggested_display_precision=2,
icon="mdi:lightning-bolt",
),
SensorEntityDescription(
key="humidity",
Expand Down Expand Up @@ -150,6 +153,7 @@ def native_value(self):
@property
def last_reset(self) -> datetime | None:
"""Return the time when the sensor was last reset, if implemented."""
if self.state_class != SensorStateClass.TOTAL:
return None
native_implementation = getattr(self._feature, "last_reset", None)

return native_implementation or super().last_reset
2 changes: 1 addition & 1 deletion homeassistant/components/bring/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["bring_api"],
"quality_scale": "platinum",
"requirements": ["bring-api==1.1.1"]
"requirements": ["bring-api==1.1.2"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/casper_glow/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["pycasperglow"],
"quality_scale": "silver",
"quality_scale": "platinum",
"requirements": ["pycasperglow==1.2.0"]
}
4 changes: 2 additions & 2 deletions homeassistant/components/casper_glow/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ rules:
comment: No network discovery.
discovery: done
docs-data-update: done
docs-examples: todo
docs-examples: done
docs-known-limitations: done
docs-supported-devices: done
docs-supported-functions: done
docs-troubleshooting: done
docs-use-cases: todo
docs-use-cases: done
dynamic-devices:
status: exempt
comment: Each config entry represents a single device.
Expand Down
23 changes: 18 additions & 5 deletions homeassistant/components/dnsip/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,18 @@
"step": {
"user": {
"data": {
"hostname": "The hostname for which to perform the DNS query",
"port": "Port for IPV4 lookup",
"port_ipv6": "Port for IPV6 lookup",
"resolver": "Resolver for IPV4 lookup",
"resolver_ipv6": "Resolver for IPV6 lookup"
"hostname": "Hostname",
"port": "IPv4 port",
"port_ipv6": "IPv6 port",
"resolver": "IPv4 resolver",
"resolver_ipv6": "IPv6 resolver"
},
"data_description": {
"hostname": "The hostname for which to perform the DNS query.",
"port": "Port used for the IPv4 lookup.",
"port_ipv6": "Port used for the IPv6 lookup.",
"resolver": "Resolver used for the IPv4 lookup.",
"resolver_ipv6": "Resolver used for the IPv6 lookup."
}
}
}
Expand Down Expand Up @@ -50,6 +57,12 @@
"port_ipv6": "[%key:component::dnsip::config::step::user::data::port_ipv6%]",
"resolver": "[%key:component::dnsip::config::step::user::data::resolver%]",
"resolver_ipv6": "[%key:component::dnsip::config::step::user::data::resolver_ipv6%]"
},
"data_description": {
"port": "[%key:component::dnsip::config::step::user::data_description::port%]",
"port_ipv6": "[%key:component::dnsip::config::step::user::data_description::port_ipv6%]",
"resolver": "[%key:component::dnsip::config::step::user::data_description::resolver%]",
"resolver_ipv6": "[%key:component::dnsip::config::step::user::data_description::resolver_ipv6%]"
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/fluss/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ class FlussButton(FlussEntity, ButtonEntity):

_attr_name = None

@property
def available(self) -> bool:
"""Return True only when the device is online."""
return super().available and self.device["internetConnected"]

async def async_press(self) -> None:
"""Handle the button press."""
try:
Expand Down
3 changes: 1 addition & 2 deletions homeassistant/components/fluss/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@

DOMAIN = "fluss"
LOGGER = logging.getLogger(__name__)
UPDATE_INTERVAL = 60 # seconds
UPDATE_INTERVAL_TIMEDELTA = timedelta(seconds=UPDATE_INTERVAL)
UPDATE_INTERVAL = timedelta(minutes=30)
32 changes: 27 additions & 5 deletions homeassistant/components/fluss/coordinator.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
"""DataUpdateCoordinator for Fluss+ integration."""

from __future__ import annotations

import asyncio
from typing import Any

from fluss_api import (
Expand All @@ -15,12 +18,12 @@
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util import slugify

from .const import LOGGER, UPDATE_INTERVAL_TIMEDELTA
from .const import LOGGER, UPDATE_INTERVAL

type FlussConfigEntry = ConfigEntry[FlussDataUpdateCoordinator]


class FlussDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
class FlussDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]]):
"""Manages fetching Fluss device data on a schedule."""

def __init__(
Expand All @@ -33,16 +36,35 @@ def __init__(
LOGGER,
name=f"Fluss+ ({slugify(api_key[:8])})",
config_entry=config_entry,
update_interval=UPDATE_INTERVAL_TIMEDELTA,
update_interval=UPDATE_INTERVAL,
)

async def _async_get_connectivity(self, device_id: str) -> bool:
"""Return connectivity for a device; False if the status call fails."""
try:
status = await self.api.async_get_device_status(device_id)
except FlussApiClientError:
return False
return status["status"]["internetConnected"]

async def _async_update_data(self) -> dict[str, dict[str, Any]]:
"""Fetch data from the Fluss API and return as a dictionary keyed by deviceId."""
"""Fetch Fluss+ devices and merge per-device connectivity status."""
try:
devices = await self.api.async_get_devices()
except FlussApiClientAuthenticationError as err:
raise ConfigEntryError(f"Authentication failed: {err}") from err
except FlussApiClientError as err:
raise UpdateFailed(f"Error fetching Fluss devices: {err}") from err

return {device["deviceId"]: device for device in devices.get("devices", [])}
device_list = [
device
for device in devices["devices"]
if device["userPermissions"]["canUseWiFi"]
]
connectivity = await asyncio.gather(
*(self._async_get_connectivity(d["deviceId"]) for d in device_list)
)
return {
device["deviceId"]: {**device, "internetConnected": connected}
for device, connected in zip(device_list, connectivity, strict=False)
}
3 changes: 2 additions & 1 deletion homeassistant/components/fritz/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from datetime import datetime, timedelta
import logging

from fritzconnection.core.exceptions import FritzConnectionException
from fritzconnection.lib.fritzstatus import FritzStatus
from requests.exceptions import RequestException

Expand Down Expand Up @@ -143,7 +144,7 @@ def _is_suitable_cpu_temperature(status: FritzStatus) -> bool:
"""Return whether the CPU temperature sensor is suitable."""
try:
cpu_temp = status.get_cpu_temperatures()[0]
except RequestException, IndexError:
except RequestException, IndexError, FritzConnectionException:
_LOGGER.debug("CPU temperature not supported by the device")
return False
if cpu_temp == 0:
Expand Down
20 changes: 17 additions & 3 deletions homeassistant/components/hive/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,22 @@ async def async_step_2fa(
if not errors:
_LOGGER.debug("2FA successful")
if self.source == SOURCE_REAUTH:
return await self.async_setup_hive_entry()
self.device_registration = True
return await self.async_step_configuration()
try:
device_registered = await self.hive_auth.is_device_registered()
except HiveApiError as err:
_LOGGER.debug(
"Failed to check whether the Hive device is registered during reauthentication: %s",
err,
)
errors["base"] = "no_internet_available"
else:
if device_registered:
return await self.async_setup_hive_entry()
self.device_registration = True
return await self.async_step_configuration()
else:
self.device_registration = True
return await self.async_step_configuration()

schema = vol.Schema({vol.Required(CONF_CODE): str})
return self.async_show_form(step_id="2fa", data_schema=schema, errors=errors)
Expand Down Expand Up @@ -171,6 +184,7 @@ async def async_step_reauth(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Re Authenticate a user."""
self.data = dict(entry_data)
data = {
CONF_USERNAME: entry_data[CONF_USERNAME],
CONF_PASSWORD: entry_data[CONF_PASSWORD],
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/indevolt/button.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Button platform for Indevolt integration."""

from dataclasses import dataclass, field
from dataclasses import dataclass
from typing import Final

from indevolt_api import IndevoltRealtimeAction
Expand All @@ -20,7 +20,7 @@
class IndevoltButtonEntityDescription(ButtonEntityDescription):
"""Custom entity description class for Indevolt button entities."""

generation: list[int] = field(default_factory=lambda: [1, 2])
generation: tuple[int, ...] = (1, 2)


BUTTONS: Final = (
Expand Down
12 changes: 6 additions & 6 deletions homeassistant/components/indevolt/number.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Number platform for Indevolt integration."""

from dataclasses import dataclass, field
from dataclasses import dataclass
from typing import Final

from indevolt_api import IndevoltConfig
Expand All @@ -27,15 +27,15 @@
class IndevoltNumberEntityDescription(NumberEntityDescription):
"""Custom entity description class for Indevolt number entities."""

generation: list[int] = field(default_factory=lambda: [1, 2])
read_key: str
write_key: str
generation: tuple[int, ...] = (1, 2)


NUMBERS: Final = (
IndevoltNumberEntityDescription(
key="discharge_limit",
generation=[2],
generation=(2,),
translation_key="discharge_limit",
read_key=IndevoltConfig.READ_DISCHARGE_LIMIT,
write_key=IndevoltConfig.WRITE_DISCHARGE_LIMIT,
Expand All @@ -46,7 +46,7 @@ class IndevoltNumberEntityDescription(NumberEntityDescription):
),
IndevoltNumberEntityDescription(
key="max_ac_output_power",
generation=[2],
generation=(2,),
translation_key="max_ac_output_power",
read_key=IndevoltConfig.READ_MAX_AC_OUTPUT_POWER,
write_key=IndevoltConfig.WRITE_MAX_AC_OUTPUT_POWER,
Expand All @@ -58,7 +58,7 @@ class IndevoltNumberEntityDescription(NumberEntityDescription):
),
IndevoltNumberEntityDescription(
key="inverter_input_limit",
generation=[2],
generation=(2,),
translation_key="inverter_input_limit",
read_key=IndevoltConfig.READ_INVERTER_INPUT_LIMIT,
write_key=IndevoltConfig.WRITE_INVERTER_INPUT_LIMIT,
Expand All @@ -70,7 +70,7 @@ class IndevoltNumberEntityDescription(NumberEntityDescription):
),
IndevoltNumberEntityDescription(
key="feedin_power_limit",
generation=[2],
generation=(2,),
translation_key="feedin_power_limit",
read_key=IndevoltConfig.READ_FEEDIN_POWER_LIMIT,
write_key=IndevoltConfig.WRITE_FEEDIN_POWER_LIMIT,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/indevolt/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class IndevoltSelectEntityDescription(SelectEntityDescription):
write_key: str
value_to_option: dict[IndevoltEnergyMode, str]
unavailable_values: list[IndevoltEnergyMode] = field(default_factory=list)
generation: list[int] = field(default_factory=lambda: [1, 2])
generation: tuple[int, ...] = (1, 2)


SELECTS: Final = (
Expand Down
Loading
Loading