Skip to content
Merged
2 changes: 1 addition & 1 deletion homeassistant/components/accuweather/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@
}
UPDATE_INTERVAL_OBSERVATION = timedelta(minutes=10)
UPDATE_INTERVAL_DAILY_FORECAST = timedelta(hours=6)
UPDATE_INTERVAL_HOURLY_FORECAST = timedelta(hours=30)
UPDATE_INTERVAL_HOURLY_FORECAST = timedelta(minutes=30)
17 changes: 17 additions & 0 deletions homeassistant/components/airthings_ble/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
from homeassistant.const import (
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
EntityCategory,
Platform,
UnitOfPressure,
UnitOfSoundPressure,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant, callback
Expand Down Expand Up @@ -112,6 +114,21 @@
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"lux": SensorEntityDescription(
key="lux",
device_class=SensorDeviceClass.ILLUMINANCE,
native_unit_of_measurement=LIGHT_LUX,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"noise": SensorEntityDescription(
key="noise",
translation_key="ambient_noise",
device_class=SensorDeviceClass.SOUND_PRESSURE,
native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
}

PARALLEL_UPDATES = 0
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/airthings_ble/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
},
"illuminance": {
"name": "[%key:component::sensor::entity_component::illuminance::name%]"
},
"ambient_noise": {
"name": "Ambient noise"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/holiday/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/holiday",
"iot_class": "local_polling",
"requirements": ["holidays==0.81", "babel==2.15.0"]
"requirements": ["holidays==0.82", "babel==2.15.0"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/lamarzocco/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@
"iot_class": "cloud_push",
"loggers": ["pylamarzocco"],
"quality_scale": "platinum",
"requirements": ["pylamarzocco==2.1.1"]
"requirements": ["pylamarzocco==2.1.2"]
}
36 changes: 22 additions & 14 deletions homeassistant/components/motion_blinds/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ class MotionBaseDevice(MotionCoordinatorEntity, CoverEntity):

_restore_tilt = False

def __init__(self, coordinator, blind, device_class):
def __init__(self, coordinator, blind, device_class) -> None:
"""Initialize the blind."""
super().__init__(coordinator, blind)

Expand Down Expand Up @@ -275,7 +275,7 @@ def current_cover_tilt_position(self) -> int | None:
"""
if self._blind.angle is None:
return None
return self._blind.angle * 100 / 180
return 100 - (self._blind.angle * 100 / 180)

@property
def is_closed(self) -> bool | None:
Expand All @@ -287,22 +287,22 @@ def is_closed(self) -> bool | None:
async def async_open_cover_tilt(self, **kwargs: Any) -> None:
"""Open the cover tilt."""
async with self._api_lock:
await self.hass.async_add_executor_job(self._blind.Set_angle, 180)
await self.hass.async_add_executor_job(self._blind.Set_angle, 0)

await self.async_request_position_till_stop()

async def async_close_cover_tilt(self, **kwargs: Any) -> None:
"""Close the cover tilt."""
async with self._api_lock:
await self.hass.async_add_executor_job(self._blind.Set_angle, 0)
await self.hass.async_add_executor_job(self._blind.Set_angle, 180)

await self.async_request_position_till_stop()

async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
"""Move the cover tilt to a specific position."""
angle = kwargs[ATTR_TILT_POSITION] * 180 / 100
async with self._api_lock:
await self.hass.async_add_executor_job(self._blind.Set_angle, angle)
await self.hass.async_add_executor_job(self._blind.Set_angle, 180 - angle)

await self.async_request_position_till_stop()

Expand Down Expand Up @@ -347,19 +347,19 @@ def current_cover_tilt_position(self) -> int | None:
if self._blind.position is None:
if self._blind.angle is None:
return None
return self._blind.angle * 100 / 180
return 100 - (self._blind.angle * 100 / 180)

return self._blind.position
return 100 - self._blind.position

@property
def is_closed(self) -> bool | None:
"""Return if the cover is closed or not."""
if self._blind.position is None:
if self._blind.angle is None:
return None
return self._blind.angle == 0
return self._blind.angle == 180

return self._blind.position == 0
return self._blind.position == 100

async def async_open_cover_tilt(self, **kwargs: Any) -> None:
"""Open the cover tilt."""
Expand All @@ -381,10 +381,14 @@ async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
if self._blind.position is None:
angle = angle * 180 / 100
async with self._api_lock:
await self.hass.async_add_executor_job(self._blind.Set_angle, angle)
await self.hass.async_add_executor_job(
self._blind.Set_angle, 180 - angle
)
else:
async with self._api_lock:
await self.hass.async_add_executor_job(self._blind.Set_position, angle)
await self.hass.async_add_executor_job(
self._blind.Set_position, 100 - angle
)

await self.async_request_position_till_stop()

Expand All @@ -397,18 +401,22 @@ async def async_set_absolute_position(self, **kwargs):
if self._blind.position is None:
angle = angle * 180 / 100
async with self._api_lock:
await self.hass.async_add_executor_job(self._blind.Set_angle, angle)
await self.hass.async_add_executor_job(
self._blind.Set_angle, 180 - angle
)
else:
async with self._api_lock:
await self.hass.async_add_executor_job(self._blind.Set_position, angle)
await self.hass.async_add_executor_job(
self._blind.Set_position, 100 - angle
)

await self.async_request_position_till_stop()


class MotionTDBUDevice(MotionBaseDevice):
"""Representation of a Motion Top Down Bottom Up blind Device."""

def __init__(self, coordinator, blind, device_class, motor):
def __init__(self, coordinator, blind, device_class, motor) -> None:
"""Initialize the blind."""
super().__init__(coordinator, blind, device_class)
self._motor = motor
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/open_router/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"quality_scale": "bronze",
"requirements": ["openai==1.99.5", "python-open-router==0.3.1"]
"requirements": ["openai==2.2.0", "python-open-router==0.3.1"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/openai_conversation/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ async def _async_handle_chat_log(

if options.get(CONF_WEB_SEARCH):
web_search = WebSearchToolParam(
type="web_search_preview",
type="web_search",
search_context_size=options.get(
CONF_WEB_SEARCH_CONTEXT_SIZE, RECOMMENDED_WEB_SEARCH_CONTEXT_SIZE
),
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/openai_conversation/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"documentation": "https://www.home-assistant.io/integrations/openai_conversation",
"integration_type": "service",
"iot_class": "cloud_polling",
"requirements": ["openai==1.99.5"]
"requirements": ["openai==2.2.0"]
}
107 changes: 68 additions & 39 deletions homeassistant/components/shelly/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

from aioshelly.const import BLU_TRV_IDENTIFIER, MODEL_BLU_GATEWAY_G3, RPC_GENERATIONS
from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError, RpcCallError
from aioshelly.rpc_device import RpcDevice

from homeassistant.components.button import (
DOMAIN as BUTTON_PLATFORM,
Expand All @@ -24,16 +23,24 @@
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import DOMAIN, LOGGER, SHELLY_GAS_MODELS
from .const import DOMAIN, LOGGER, MODEL_FRANKEVER_WATER_VALVE, SHELLY_GAS_MODELS
from .coordinator import ShellyBlockCoordinator, ShellyConfigEntry, ShellyRpcCoordinator
from .entity import get_entity_block_device_info, get_entity_rpc_device_info
from .entity import (
RpcEntityDescription,
ShellyRpcAttributeEntity,
async_setup_entry_rpc,
get_entity_block_device_info,
get_entity_rpc_device_info,
rpc_call,
)
from .utils import (
async_remove_orphaned_entities,
format_ble_addr,
get_blu_trv_device_info,
get_device_entry_gen,
get_rpc_entity_name,
get_rpc_key_ids,
get_rpc_key_instances,
get_rpc_role_by_key,
get_virtual_component_ids,
)

Expand All @@ -51,6 +58,11 @@ class ShellyButtonDescription[
supported: Callable[[_ShellyCoordinatorT], bool] = lambda _: True


@dataclass(frozen=True, kw_only=True)
class RpcButtonDescription(RpcEntityDescription, ButtonEntityDescription):
"""Class to describe a RPC button."""


BUTTONS: Final[list[ShellyButtonDescription[Any]]] = [
ShellyButtonDescription[ShellyBlockCoordinator | ShellyRpcCoordinator](
key="reboot",
Expand Down Expand Up @@ -96,12 +108,24 @@ class ShellyButtonDescription[
),
]

VIRTUAL_BUTTONS: Final[list[ShellyButtonDescription]] = [
ShellyButtonDescription[ShellyRpcCoordinator](
RPC_VIRTUAL_BUTTONS = {
"button_generic": RpcButtonDescription(
key="button",
press_action="single_push",
)
]
role="generic",
),
"button_open": RpcButtonDescription(
key="button",
entity_registry_enabled_default=False,
role="open",
models={MODEL_FRANKEVER_WATER_VALVE},
),
"button_close": RpcButtonDescription(
key="button",
entity_registry_enabled_default=False,
role="close",
models={MODEL_FRANKEVER_WATER_VALVE},
),
}


@callback
Expand Down Expand Up @@ -129,8 +153,10 @@ def async_migrate_unique_ids(
)
}

if not isinstance(coordinator, ShellyRpcCoordinator):
return None

if blutrv_key_ids := get_rpc_key_ids(coordinator.device.status, BLU_TRV_IDENTIFIER):
assert isinstance(coordinator.device, RpcDevice)
for _id in blutrv_key_ids:
key = f"{BLU_TRV_IDENTIFIER}:{_id}"
ble_addr: str = coordinator.device.config[key]["addr"]
Expand All @@ -149,6 +175,26 @@ def async_migrate_unique_ids(
)
}

if virtual_button_keys := get_rpc_key_instances(
coordinator.device.config, "button"
):
for key in virtual_button_keys:
old_unique_id = f"{coordinator.mac}-{key}"
if entity_entry.unique_id == old_unique_id:
role = get_rpc_role_by_key(coordinator.device.config, key)
new_unique_id = f"{coordinator.mac}-{key}-button_{role}"
LOGGER.debug(
"Migrating unique_id for %s entity from [%s] to [%s]",
entity_entry.entity_id,
old_unique_id,
new_unique_id,
)
return {
"new_unique_id": entity_entry.unique_id.replace(
old_unique_id, new_unique_id
)
}

return None


Expand All @@ -172,7 +218,7 @@ async def async_setup_entry(
hass, config_entry.entry_id, partial(async_migrate_unique_ids, coordinator)
)

entities: list[ShellyButton | ShellyBluTrvButton | ShellyVirtualButton] = []
entities: list[ShellyButton | ShellyBluTrvButton] = []

entities.extend(
ShellyButton(coordinator, button)
Expand All @@ -185,12 +231,9 @@ async def async_setup_entry(
return

# add virtual buttons
if virtual_button_ids := get_rpc_key_ids(coordinator.device.status, "button"):
entities.extend(
ShellyVirtualButton(coordinator, button, id_)
for id_ in virtual_button_ids
for button in VIRTUAL_BUTTONS
)
async_setup_entry_rpc(
hass, config_entry, async_add_entities, RPC_VIRTUAL_BUTTONS, RpcVirtualButton
)

# add BLU TRV buttons
if blutrv_key_ids := get_rpc_key_ids(coordinator.device.status, BLU_TRV_IDENTIFIER):
Expand Down Expand Up @@ -332,30 +375,16 @@ async def _press_method(self) -> None:
await method(self._id)


class ShellyVirtualButton(ShellyBaseButton):
"""Defines a Shelly virtual component button."""

def __init__(
self,
coordinator: ShellyRpcCoordinator,
description: ShellyButtonDescription,
_id: int,
) -> None:
"""Initialize Shelly virtual component button."""
super().__init__(coordinator, description)
class RpcVirtualButton(ShellyRpcAttributeEntity, ButtonEntity):
"""Defines a Shelly RPC virtual component button."""

self._attr_unique_id = f"{coordinator.mac}-{description.key}:{_id}"
self._attr_device_info = get_entity_rpc_device_info(coordinator)
self._attr_name = get_rpc_entity_name(
coordinator.device, f"{description.key}:{_id}"
)
self._id = _id
entity_description: RpcButtonDescription
_id: int

async def _press_method(self) -> None:
"""Press method."""
@rpc_call
async def async_press(self) -> None:
"""Triggers the Shelly button press service."""
if TYPE_CHECKING:
assert isinstance(self.coordinator, ShellyRpcCoordinator)

await self.coordinator.device.button_trigger(
self._id, self.entity_description.press_action
)
await self.coordinator.device.button_trigger(self._id, "single_push")
Loading
Loading