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
11 changes: 6 additions & 5 deletions homeassistant/components/accuweather/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_PARTS_PER_CUBIC_METER,
PERCENTAGE,
UV_INDEX,
UnitOfIrradiance,
Expand Down Expand Up @@ -47,6 +46,8 @@

PARALLEL_UPDATES = 1

PARTS_PER_CUBIC_METER = "p/m³"


@dataclass(frozen=True, kw_only=True)
class AccuWeatherSensorDescription(SensorEntityDescription):
Expand Down Expand Up @@ -81,7 +82,7 @@ class AccuWeatherSensorDescription(SensorEntityDescription):
AccuWeatherSensorDescription(
key="Grass",
entity_registry_enabled_default=False,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=PARTS_PER_CUBIC_METER,
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
attr_fn=lambda data: {
ATTR_LEVEL: POLLEN_CATEGORY_MAP[data[ATTR_CATEGORY_VALUE]]
Expand All @@ -107,7 +108,7 @@ class AccuWeatherSensorDescription(SensorEntityDescription):
AccuWeatherSensorDescription(
key="Mold",
entity_registry_enabled_default=False,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=PARTS_PER_CUBIC_METER,
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
attr_fn=lambda data: {
ATTR_LEVEL: POLLEN_CATEGORY_MAP[data[ATTR_CATEGORY_VALUE]]
Expand All @@ -116,7 +117,7 @@ class AccuWeatherSensorDescription(SensorEntityDescription):
),
AccuWeatherSensorDescription(
key="Ragweed",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
attr_fn=lambda data: {
Expand Down Expand Up @@ -184,7 +185,7 @@ class AccuWeatherSensorDescription(SensorEntityDescription):
),
AccuWeatherSensorDescription(
key="Tree",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
attr_fn=lambda data: {
Expand Down
31 changes: 9 additions & 22 deletions homeassistant/components/alexa_devices/media_player.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
"""Media player platform for Alexa Devices."""

from dataclasses import dataclass
from datetime import datetime
from typing import Any, Final
from typing import Any

from aioamazondevices.structures import (
AmazonMediaControls,
Expand Down Expand Up @@ -38,18 +37,6 @@
)


@dataclass(frozen=True, kw_only=True)
class AmazonDevicesMediaPlayerEntityDescription(MediaPlayerEntityDescription):
"""Describes an Alexa Devices media player entity."""


MEDIA_PLAYERS: Final = (
AmazonDevicesMediaPlayerEntityDescription(
key="media",
),
)


async def async_setup_entry(
hass: HomeAssistant,
entry: AmazonConfigEntry,
Expand All @@ -69,9 +56,10 @@ def _check_device() -> None:
continue

known_devices.add(serial_num)
new_entities.extend(
AlexaDevicesMediaPlayer(coordinator, serial_num, description)
for description in MEDIA_PLAYERS
new_entities.append(
AlexaDevicesMediaPlayer(
coordinator, serial_num, MediaPlayerEntityDescription(key="media")
)
)

if new_entities:
Expand All @@ -85,8 +73,6 @@ def _check_device() -> None:
class AlexaDevicesMediaPlayer(AmazonEntity, MediaPlayerEntity):
"""Representation of an Alexa device media player."""

entity_description: AmazonDevicesMediaPlayerEntityDescription

_attr_name = None # Uses the device name
_attr_device_class = MediaPlayerDeviceClass.SPEAKER
_attr_volume_step = 0.05
Expand All @@ -95,7 +81,7 @@ def __init__(
self,
coordinator: AmazonDevicesCoordinator,
serial_num: str,
description: AmazonDevicesMediaPlayerEntityDescription,
description: MediaPlayerEntityDescription,
) -> None:
"""Initialize."""
self._prev_volume: int | None = None
Expand Down Expand Up @@ -214,7 +200,7 @@ def media_position_updated_at(self) -> datetime | None:
@property
def media_content_type(self) -> MediaType | None:
"""Content type — tells HA what kind of media is playing."""
if self.state in [MediaPlayerState.PLAYING, MediaPlayerState.PAUSED]:
if self.state in (MediaPlayerState.PLAYING, MediaPlayerState.PAUSED):
return MediaType.MUSIC
return None

Expand All @@ -227,7 +213,8 @@ async def async_play_media(
**kwargs: Any,
) -> None:
"""Play a piece of media."""
await self.async_call_alexa_music(media_id, media_type)
provider = media_type.value if isinstance(media_type, MediaType) else media_type
await self.async_call_alexa_music(media_id, provider)

@alexa_api_call
async def async_call_alexa_music(
Expand Down
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==5.6.1"]
"requirements": ["infrared-protocols==5.8.0"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/knx/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"requirements": [
"xknx==3.15.0",
"xknxproject==3.9.0",
"knx-frontend==2026.4.30.60856"
"knx-frontend==2026.6.1.213802"
],
"single_config_entry": true
}
18 changes: 8 additions & 10 deletions homeassistant/components/switchbot_cloud/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,15 @@ class SwitchbotCloudDeviceConfig:
True, entity_config=(Platform.BINARY_SENSOR, Platform.SENSOR)
),
"Home Climate Panel": SwitchbotCloudDeviceConfig(
False, entity_config=(Platform.BINARY_SENSOR, Platform.SENSOR)
True, entity_config=(Platform.BINARY_SENSOR, Platform.SENSOR)
),
"WeatherStation": SwitchbotCloudDeviceConfig(
False, entity_config=(Platform.SENSOR,)
),
"Meter": SwitchbotCloudDeviceConfig(False, entity_config=(Platform.SENSOR,)),
"MeterPlus": SwitchbotCloudDeviceConfig(False, entity_config=(Platform.SENSOR,)),
"WoIOSensor": SwitchbotCloudDeviceConfig(False, entity_config=(Platform.SENSOR,)),
"Hub 2": SwitchbotCloudDeviceConfig(False, entity_config=(Platform.SENSOR,)),
"MeterPro": SwitchbotCloudDeviceConfig(False, entity_config=(Platform.SENSOR,)),
"MeterPro(CO2)": SwitchbotCloudDeviceConfig(
False, entity_config=(Platform.SENSOR,)
True, entity_config=(Platform.SENSOR,)
),
"Meter": SwitchbotCloudDeviceConfig(True, entity_config=(Platform.SENSOR,)),
"MeterPlus": SwitchbotCloudDeviceConfig(True, entity_config=(Platform.SENSOR,)),
"WoIOSensor": SwitchbotCloudDeviceConfig(True, entity_config=(Platform.SENSOR,)),
"Hub 2": SwitchbotCloudDeviceConfig(True, entity_config=(Platform.SENSOR,)),
"MeterPro": SwitchbotCloudDeviceConfig(True, entity_config=(Platform.SENSOR,)),
"MeterPro(CO2)": SwitchbotCloudDeviceConfig(True, entity_config=(Platform.SENSOR,)),
}
2 changes: 1 addition & 1 deletion homeassistant/components/vistapool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

_LOGGER = logging.getLogger(__name__)

PLATFORMS: list[Platform] = [Platform.SENSOR]
PLATFORMS: list[Platform] = [Platform.BUTTON, Platform.SENSOR]


@dataclass
Expand Down
73 changes: 73 additions & 0 deletions homeassistant/components/vistapool/button.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Vistapool Button entities."""

import asyncio

from aioaquarite import AquariteError

from homeassistant.components.button import ButtonEntity
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from . import VistapoolConfigEntry
from .const import DOMAIN
from .coordinator import VistapoolDataUpdateCoordinator
from .entity import VistapoolEntity

PARALLEL_UPDATES = 1

_HASLED_PATH = "main.hasLED"
_LIGHT_STATUS_PATH = "light.status"
_LED_PULSE_DELAY_SECONDS = 1.0


async def async_setup_entry(
hass: HomeAssistant,
entry: VistapoolConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Vistapool buttons for every pool that has an LED fixture."""
async_add_entities(
VistapoolLEDPulseButton(coordinator)
for coordinator in entry.runtime_data.coordinators.values()
if coordinator.get_value(_HASLED_PATH)
)


class VistapoolLEDPulseButton(VistapoolEntity, ButtonEntity):
"""Power-cycle the pool light to advance the LED fixture's color.

Mirrors the "Next" button under LED Color in the Vistapool app's
Illumination screen. If the light is on, sends light.status=0, waits a
moment, then light.status=1; the physical LED fixture advances to the
next color on power-on. If the light is off, just turns it on.
"""

_attr_translation_key = "led_pulse"

def __init__(self, coordinator: VistapoolDataUpdateCoordinator) -> None:
"""Initialize the LED pulse button."""
super().__init__(coordinator)
self._attr_unique_id = self.build_unique_id("led_pulse")

async def async_press(self) -> None:
"""Send a color-advance pulse to the pool LED fixture."""
try:
if self.coordinator.get_value(_LIGHT_STATUS_PATH) in (True, "1"):
await self.coordinator.api.set_value(
self.coordinator.pool_id, _LIGHT_STATUS_PATH, 0
)
await asyncio.sleep(_LED_PULSE_DELAY_SECONDS)
await self.coordinator.api.set_value(
self.coordinator.pool_id, _LIGHT_STATUS_PATH, 1
)
except AquariteError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_failed",
translation_placeholders={"entity": self.entity_id},
) from err
# Optimistically reflect the just-written value so a rapid second press
# doesn't read the stale off-state before the Firestore push round-trips.
self.coordinator.data.setdefault("light", {})["status"] = 1
self.coordinator.async_set_updated_data(self.coordinator.data)
8 changes: 3 additions & 5 deletions homeassistant/components/vistapool/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ rules:
# Bronze
action-setup:
status: exempt
comment: No service actions in initial sensor-only platform
comment: No integration-specific service actions; entities use platform-standard actions only
appropriate-polling: done
brands: done
common-modules: done
Expand All @@ -11,7 +11,7 @@ rules:
dependency-transparency: done
docs-actions:
status: exempt
comment: No service actions in initial sensor-only platform
comment: No integration-specific service actions to document
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
Expand All @@ -24,9 +24,7 @@ rules:
unique-config-entry: done

# Silver
action-exceptions:
status: exempt
comment: No user actions (sensor-only platform)
action-exceptions: done
config-entry-unloading: done
docs-configuration-parameters:
status: exempt
Expand Down
8 changes: 8 additions & 0 deletions homeassistant/components/vistapool/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
}
},
"entity": {
"button": {
"led_pulse": {
"name": "LED next color"
}
},
"sensor": {
"chlorine": {
"name": "Chlorine"
Expand Down Expand Up @@ -59,6 +64,9 @@
"no_pools": {
"message": "No pools were found on this account."
},
"set_failed": {
"message": "Failed to set {entity}."
},
"update_failed": {
"message": "Error fetching data from Vistapool."
}
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/zone/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
},
"left": {
"trigger": "mdi:map-marker-minus"
},
"occupancy_cleared": {
"trigger": "mdi:account-off"
},
"occupancy_detected": {
"trigger": "mdi:account-group"
}
}
}
26 changes: 26 additions & 0 deletions homeassistant/components/zone/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,32 @@
}
},
"name": "Left zone"
},
"occupancy_cleared": {
"description": "Triggers when a zone transitions from occupied to unoccupied.",
"fields": {
"for": {
"name": "[%key:component::zone::common::trigger_for_name%]"
},
"zone": {
"description": "[%key:component::zone::triggers::occupancy_detected::fields::zone::description%]",
"name": "[%key:component::zone::triggers::occupancy_detected::fields::zone::name%]"
}
},
"name": "Zone occupancy cleared"
},
"occupancy_detected": {
"description": "Triggers when a zone transitions to an occupied state.",
"fields": {
"for": {
"name": "[%key:component::zone::common::trigger_for_name%]"
},
"zone": {
"description": "The zone to monitor.",
"name": "Zone"
}
},
"name": "Zone occupancy detected"
}
}
}
Loading
Loading