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
1 change: 1 addition & 0 deletions .strict-typing
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ homeassistant.components.imap.*
homeassistant.components.imgw_pib.*
homeassistant.components.immich.*
homeassistant.components.incomfort.*
homeassistant.components.indevolt.*
homeassistant.components.inels.*
homeassistant.components.infrared.*
homeassistant.components.input_button.*
Expand Down
42 changes: 38 additions & 4 deletions homeassistant/components/bsblan/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,14 @@
)
from homeassistant.helpers.typing import ConfigType

from .const import CONF_HEATING_CIRCUITS, CONF_PASSKEY, DEFAULT_PORT, DOMAIN, LOGGER
from .const import (
CONF_HEATING_CIRCUITS,
CONF_PASSKEY,
DEFAULT_HEATING_CIRCUITS,
DEFAULT_PORT,
DOMAIN,
LOGGER,
)
from .coordinator import BSBLanFastCoordinator, BSBLanSlowCoordinator
from .services import async_setup_services

Expand Down Expand Up @@ -118,7 +125,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: BSBLanConfigEntry) -> bo

# Read available heating circuits from config entry data
# (populated by config flow or migration)
circuits: list[int] = entry.data[CONF_HEATING_CIRCUITS]
circuits: list[int] = entry.data[CONF_HEATING_CIRCUITS] or list(
DEFAULT_HEATING_CIRCUITS
)

# Fetch required device metadata in parallel for faster startup
device, info = await asyncio.gather(
Expand Down Expand Up @@ -229,7 +238,7 @@ async def async_migrate_entry(hass: HomeAssistant, entry: BSBLanConfigEntry) ->
# heating circuits from the device; fall back to [1] (pre-multi-circuit
# default) if the device is unreachable or the endpoint is unsupported.
if entry.version == 1 and entry.minor_version < 2:
circuits: list[int] = [1]
circuits: list[int] = list(DEFAULT_HEATING_CIRCUITS)
config = BSBLANConfig(
host=entry.data[CONF_HOST],
passkey=entry.data[CONF_PASSKEY],
Expand All @@ -245,11 +254,18 @@ async def async_migrate_entry(hass: HomeAssistant, entry: BSBLanConfigEntry) ->
except (BSBLANError, TimeoutError) as err:
LOGGER.warning(
"Circuit discovery during migration failed for %s (%s); "
"defaulting to single circuit [1]. Use Reconfigure to "
"defaulting to a single circuit. Use Reconfigure to "
"rediscover additional circuits later",
entry.data[CONF_HOST],
err,
)
if not circuits:
LOGGER.warning(
"Circuit discovery during migration returned no heating circuits "
"for %s; defaulting to a single circuit",
entry.data[CONF_HOST],
)
circuits = list(DEFAULT_HEATING_CIRCUITS)

hass.config_entries.async_update_entry(
entry,
Expand All @@ -263,4 +279,22 @@ async def async_migrate_entry(hass: HomeAssistant, entry: BSBLanConfigEntry) ->
circuits,
)

# 1.2 -> 1.3: Repair entries that stored an empty circuit list during
# discovery. Every BSB-LAN setup has at least one heating circuit.
if entry.version == 1 and entry.minor_version < 3:
if not entry.data[CONF_HEATING_CIRCUITS]:
LOGGER.warning(
"Stored heating circuits for %s are empty; defaulting to a "
"single circuit",
entry.data[CONF_HOST],
)
data = {
**entry.data,
CONF_HEATING_CIRCUITS: list(DEFAULT_HEATING_CIRCUITS),
}
else:
data = {**entry.data}

hass.config_entries.async_update_entry(entry, data=data, minor_version=3)

return True
22 changes: 18 additions & 4 deletions homeassistant/components/bsblan/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,28 @@
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo

from .const import CONF_HEATING_CIRCUITS, CONF_PASSKEY, DEFAULT_PORT, DOMAIN, LOGGER
from .const import (
CONF_HEATING_CIRCUITS,
CONF_PASSKEY,
DEFAULT_HEATING_CIRCUITS,
DEFAULT_PORT,
DOMAIN,
LOGGER,
)


class BSBLANFlowHandler(ConfigFlow, domain=DOMAIN):
"""Handle a BSBLAN config flow."""

VERSION = 1
MINOR_VERSION = 2
MINOR_VERSION = 3

def __init__(self) -> None:
"""Initialize BSBLan flow."""
self.host: str = ""
self.port: int = DEFAULT_PORT
self.mac: str | None = None
self.circuits: list[int] = [1]
self.circuits: list[int] = list(DEFAULT_HEATING_CIRCUITS)
self.passkey: str | None = None
self.username: str | None = None
self.password: str | None = None
Expand Down Expand Up @@ -384,6 +391,13 @@ async def _discover_circuits(self) -> None:
try:
await bsblan.initialize()
self.circuits = await bsblan.get_available_circuits()
if not self.circuits:
LOGGER.debug(
"Circuit discovery returned no heating circuits for %s, "
"defaulting to single circuit",
self.host,
)
self.circuits = list(DEFAULT_HEATING_CIRCUITS)
except (
BSBLANError,
TimeoutError,
Expand All @@ -392,4 +406,4 @@ async def _discover_circuits(self) -> None:
"Circuit discovery not available for %s, defaulting to single circuit",
self.host,
)
self.circuits = [1]
self.circuits = list(DEFAULT_HEATING_CIRCUITS)
1 change: 1 addition & 0 deletions homeassistant/components/bsblan/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@
CONF_PASSKEY: Final = "passkey"
CONF_HEATING_CIRCUITS: Final = "heating_circuits"

DEFAULT_HEATING_CIRCUITS: Final = (1,)
DEFAULT_PORT: Final = 80
27 changes: 27 additions & 0 deletions homeassistant/components/data_grand_lyon/diagnostics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Diagnostics support for the Data Grand Lyon integration."""

from dataclasses import asdict
from typing import Any

from homeassistant.components.diagnostics import async_redact_data
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant

from .coordinator import DataGrandLyonConfigEntry

TO_REDACT = {CONF_USERNAME, CONF_PASSWORD}


async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: DataGrandLyonConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator = entry.runtime_data

return {
"config_entry": async_redact_data(entry.as_dict(), TO_REDACT),
"coordinator_data": {
subentry_id: [asdict(passage) for passage in passages]
for subentry_id, passages in coordinator.data.items()
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ rules:

# Gold
devices: done
diagnostics: todo
diagnostics: done
discovery-update-info:
status: exempt
comment: This is a service integration; there are no discoverable devices.
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/hdmi_cec/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from pycec.commands import CecCommand, KeyPressCommand, KeyReleaseCommand
from pycec.const import (
CMD_STANDBY,
KEY_BACKWARD,
KEY_FORWARD,
KEY_MUTE_TOGGLE,
Expand Down Expand Up @@ -93,7 +94,7 @@ async def async_turn_on(self) -> None:

async def async_turn_off(self) -> None:
"""Turn device off."""
self._device.turn_off()
self._device.send_command(CecCommand(CMD_STANDBY, dst=self._logical_address))
self._attr_state = MediaPlayerState.OFF
self.async_write_ha_state()

Expand Down
5 changes: 3 additions & 2 deletions homeassistant/components/hdmi_cec/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import logging
from typing import Any

from pycec.const import POWER_OFF, POWER_ON
from pycec.commands import CecCommand
from pycec.const import CMD_STANDBY, POWER_OFF, POWER_ON

from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN, SwitchEntity
from homeassistant.core import HomeAssistant
Expand Down Expand Up @@ -50,7 +51,7 @@ async def async_turn_on(self, **kwargs: Any) -> None:

async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn device off."""
self._device.turn_off()
self._device.send_command(CecCommand(CMD_STANDBY, dst=self._logical_address))
self._attr_is_on = False
self.async_write_ha_state()

Expand Down
7 changes: 6 additions & 1 deletion homeassistant/components/iaqualink/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,9 @@
from datetime import timedelta

DOMAIN = "iaqualink"
UPDATE_INTERVAL = timedelta(seconds=15)

UPDATE_INTERVAL_BY_SYSTEM_TYPE: dict[str, timedelta] = {
"iaqua": timedelta(seconds=15),
"exo": timedelta(seconds=60),
}
UPDATE_INTERVAL_DEFAULT = timedelta(seconds=30)
7 changes: 5 additions & 2 deletions homeassistant/components/iaqualink/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import DOMAIN, UPDATE_INTERVAL
from .const import DOMAIN, UPDATE_INTERVAL_BY_SYSTEM_TYPE, UPDATE_INTERVAL_DEFAULT

_LOGGER = logging.getLogger(__name__)

Expand All @@ -26,12 +26,15 @@ def __init__(
self, hass: HomeAssistant, config_entry: ConfigEntry, system: Any
) -> None:
"""Initialize the coordinator."""
update_interval = UPDATE_INTERVAL_BY_SYSTEM_TYPE.get(
system.NAME, UPDATE_INTERVAL_DEFAULT
)
super().__init__(
hass,
_LOGGER,
config_entry=config_entry,
name=f"{DOMAIN}_{system.serial}",
update_interval=UPDATE_INTERVAL,
update_interval=update_interval,
)
self.system = system

Expand Down
12 changes: 12 additions & 0 deletions homeassistant/components/indevolt/icons.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
{
"entity": {
"button": {
"stop": {
"default": "mdi:stop"
}
},
"select": {
"energy_mode": {
"default": "mdi:home-lightning-bolt"
}
}
},
"services": {
"charge": {
"service": "mdi:battery-arrow-up"
Expand Down
19 changes: 9 additions & 10 deletions homeassistant/components/indevolt/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ rules:
discovery:
status: exempt
comment: Integration does not support network discovery
docs-data-update: todo
docs-examples: todo
docs-known-limitations: todo
docs-supported-devices: todo
docs-supported-functions: todo
docs-troubleshooting: todo
docs-use-cases: todo
docs-data-update: done
docs-examples: done
docs-known-limitations: done
docs-supported-devices: done
docs-supported-functions: done
docs-troubleshooting: done
docs-use-cases: done
dynamic-devices:
status: exempt
comment: Integration represents a single device, not a hub with multiple devices
Expand All @@ -61,16 +61,15 @@ rules:
entity-disabled-by-default: done
entity-translations: done
exception-translations: todo
icon-translations: todo
icon-translations: done
reconfiguration-flow: done
repair-issues:
status: exempt
comment: No repair issues needed for current functionality
stale-devices:
status: exempt
comment: Integration represents a single device, not a hub with multiple devices

# Platinum
async-dependency: done
inject-websession: done
strict-typing: todo
strict-typing: done
4 changes: 2 additions & 2 deletions homeassistant/components/indevolt/sensor.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Sensor platform for Indevolt integration."""

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

from indevolt_api import (
IndevoltBattery,
Expand Down Expand Up @@ -768,4 +768,4 @@ def native_value(self) -> str | int | float | None:
if self.entity_description.device_class == SensorDeviceClass.ENUM:
return self.entity_description.state_mapping.get(raw_value)

return raw_value
return cast(str | int | float, raw_value)
2 changes: 1 addition & 1 deletion homeassistant/components/infrared/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/infrared",
"integration_type": "entity",
"quality_scale": "internal",
"requirements": ["infrared-protocols==3.5.0"]
"requirements": ["infrared-protocols==4.0.0"]
}
8 changes: 8 additions & 0 deletions homeassistant/components/prusalink/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ class PrusaLinkButtonEntityDescription(
bool, data["printer"]["state"] == PrinterState.PAUSED.value
),
),
PrusaLinkButtonEntityDescription[PrinterStatus](
key="job.continue_job",
translation_key="continue_job",
press_fn=lambda api: api.continue_job,
available_fn=lambda data: cast(
bool, data["printer"]["state"] == PrinterState.ATTENTION.value
),
),
),
}

Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/prusalink/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"cancel_job": {
"default": "mdi:cancel"
},
"continue_job": {
"default": "mdi:play-circle"
},
"pause_job": {
"default": "mdi:pause"
},
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/prusalink/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
"cancel_job": {
"name": "Cancel job"
},
"continue_job": {
"name": "Continue job"
},
"pause_job": {
"name": "Pause job"
},
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/usb/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def usb_device_from_port(port: SerialPortInfo) -> USBDevice:
pid=f"{hex(port.pid)[2:]:0>4}".upper(),
serial_number=port.serial_number,
manufacturer=port.manufacturer,
description=port.product,
description=port.description,
bcd_device=port.bcd_device,
interface_description=port.interface_description,
interface_num=port.interface_num,
Expand All @@ -36,7 +36,7 @@ def serial_device_from_port(port: SerialPortInfo) -> SerialDevice:
device=port.device,
serial_number=port.serial_number,
manufacturer=port.manufacturer,
description=port.product,
description=port.description,
interface_description=port.interface_description,
interface_num=port.interface_num,
)
Expand Down
Loading
Loading