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
2 changes: 1 addition & 1 deletion homeassistant/components/airthings_ble/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/airthings_ble",
"iot_class": "local_polling",
"requirements": ["airthings-ble==0.9.2"]
"requirements": ["airthings-ble==1.1.1"]
}
8 changes: 5 additions & 3 deletions homeassistant/components/awair/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,18 @@ async def async_setup_entry(

if CONF_HOST in config_entry.data:
coordinator = AwairLocalDataUpdateCoordinator(hass, config_entry, session)
config_entry.async_on_unload(
config_entry.add_update_listener(_async_update_listener)
)
else:
coordinator = AwairCloudDataUpdateCoordinator(hass, config_entry, session)

await coordinator.async_config_entry_first_refresh()

config_entry.runtime_data = coordinator

if CONF_HOST in config_entry.data:
config_entry.async_on_unload(
config_entry.add_update_listener(_async_update_listener)
)

await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)

return True
Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/shelly/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,7 @@ class BLEScannerMode(StrEnum):
MAX_SCRIPT_SIZE = 5120

All_LIGHT_TYPES = ("cct", "light", "rgb", "rgbw")

# Shelly-X specific models
MODEL_NEO_WATER_VALVE = "NeoWaterValve"
MODEL_FRANKEVER_WATER_VALVE = "WaterValve"
4 changes: 4 additions & 0 deletions homeassistant/components/shelly/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ def async_setup_rpc_attribute_entities(

for key in key_instances:
# Filter non-existing sensors
if description.models and coordinator.model not in description.models:
continue

if description.role and description.role != coordinator.device.config[
key
].get("role", "generic"):
Expand Down Expand Up @@ -316,6 +319,7 @@ class RpcEntityDescription(EntityDescription):
options_fn: Callable[[dict], list[str]] | None = None
entity_class: Callable | None = None
role: str | None = None
models: set[str] | None = None


@dataclass(frozen=True)
Expand Down
107 changes: 105 additions & 2 deletions homeassistant/components/shelly/valve.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .coordinator import ShellyBlockCoordinator, ShellyConfigEntry
from .const import MODEL_FRANKEVER_WATER_VALVE, MODEL_NEO_WATER_VALVE
from .coordinator import ShellyBlockCoordinator, ShellyConfigEntry, ShellyRpcCoordinator
from .entity import (
BlockEntityDescription,
RpcEntityDescription,
ShellyBlockAttributeEntity,
ShellyRpcAttributeEntity,
async_setup_block_attribute_entities,
async_setup_entry_rpc,
)
from .utils import async_remove_shelly_entity, get_device_entry_gen

Expand All @@ -33,6 +37,11 @@ class BlockValveDescription(BlockEntityDescription, ValveEntityDescription):
"""Class to describe a BLOCK valve."""


@dataclass(kw_only=True, frozen=True)
class RpcValveDescription(RpcEntityDescription, ValveEntityDescription):
"""Class to describe a RPC virtual valve."""


GAS_VALVE = BlockValveDescription(
key="valve|valve",
name="Valve",
Expand All @@ -41,14 +50,108 @@ class BlockValveDescription(BlockEntityDescription, ValveEntityDescription):
)


class RpcShellyBaseWaterValve(ShellyRpcAttributeEntity, ValveEntity):
"""Base Entity for RPC Shelly Water Valves."""

entity_description: RpcValveDescription
_attr_device_class = ValveDeviceClass.WATER
_id: int

def __init__(
self,
coordinator: ShellyRpcCoordinator,
key: str,
attribute: str,
description: RpcEntityDescription,
) -> None:
"""Initialize RPC water valve."""
super().__init__(coordinator, key, attribute, description)
self._attr_name = None # Main device entity


class RpcShellyWaterValve(RpcShellyBaseWaterValve):
"""Entity that controls a valve on RPC Shelly Water Valve."""

_attr_supported_features = (
ValveEntityFeature.OPEN
| ValveEntityFeature.CLOSE
| ValveEntityFeature.SET_POSITION
)
_attr_reports_position = True

@property
def current_valve_position(self) -> int:
"""Return current position of valve."""
return cast(int, self.attribute_value)

async def async_set_valve_position(self, position: int) -> None:
"""Move the valve to a specific position."""
await self.coordinator.device.number_set(self._id, position)


class RpcShellyNeoWaterValve(RpcShellyBaseWaterValve):
"""Entity that controls a valve on RPC Shelly NEO Water Valve."""

_attr_supported_features = ValveEntityFeature.OPEN | ValveEntityFeature.CLOSE
_attr_reports_position = False

@property
def is_closed(self) -> bool | None:
"""Return if the valve is closed or not."""
return not self.attribute_value

async def async_open_valve(self, **kwargs: Any) -> None:
"""Open valve."""
await self.coordinator.device.boolean_set(self._id, True)

async def async_close_valve(self, **kwargs: Any) -> None:
"""Close valve."""
await self.coordinator.device.boolean_set(self._id, False)


RPC_VALVES: dict[str, RpcValveDescription] = {
"water_valve": RpcValveDescription(
key="number",
sub_key="value",
role="position",
entity_class=RpcShellyWaterValve,
models={MODEL_FRANKEVER_WATER_VALVE},
),
"neo_water_valve": RpcValveDescription(
key="boolean",
sub_key="value",
role="state",
entity_class=RpcShellyNeoWaterValve,
models={MODEL_NEO_WATER_VALVE},
),
}


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ShellyConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up valves for device."""
if get_device_entry_gen(config_entry) in BLOCK_GENERATIONS:
async_setup_block_entry(hass, config_entry, async_add_entities)
return async_setup_block_entry(hass, config_entry, async_add_entities)

return async_setup_rpc_entry(hass, config_entry, async_add_entities)


@callback
def async_setup_rpc_entry(
hass: HomeAssistant,
config_entry: ShellyConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up entities for RPC device."""
coordinator = config_entry.runtime_data.rpc
assert coordinator

async_setup_entry_rpc(
hass, config_entry, async_add_entities, RPC_VALVES, RpcShellyWaterValve
)


@callback
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/vesync/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,9 @@ def percentage(self) -> int | None:
"""Return the currently set speed."""

current_level = self.device.state.fan_level

if self.device.state.mode == VS_FAN_MODE_MANUAL and current_level is not None:
if current_level == 0:
return 0
return ordered_list_item_to_percentage(
self.device.fan_levels, current_level
)
Expand Down
16 changes: 14 additions & 2 deletions homeassistant/components/zwave_js/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -918,7 +918,7 @@ async def async_step_finish_addon_setup_user(
discovery_info = await self._async_get_addon_discovery_info()
self.ws_address = f"ws://{discovery_info['host']}:{discovery_info['port']}"

if not self.unique_id or self.source in (SOURCE_USB, SOURCE_ESPHOME):
if not self.unique_id or self.source == SOURCE_USB:
if not self.version_info:
try:
self.version_info = await async_get_version_info(
Expand All @@ -942,7 +942,12 @@ async def async_step_finish_addon_setup_user(
CONF_S2_UNAUTHENTICATED_KEY: self.s2_unauthenticated_key,
CONF_LR_S2_ACCESS_CONTROL_KEY: self.lr_s2_access_control_key,
CONF_LR_S2_AUTHENTICATED_KEY: self.lr_s2_authenticated_key,
}
},
error=(
"migration_successful"
if self.source in (SOURCE_USB, SOURCE_ESPHOME)
else "already_configured"
),
)
return self._async_create_entry_from_vars()

Expand Down Expand Up @@ -1490,6 +1495,8 @@ async def async_step_esphome(
)
# Only update existing entries that are configured via sockets
and existing_entry.data.get(CONF_SOCKET_PATH)
# And use the add-on
and existing_entry.data.get(CONF_USE_ADDON)
):
await self._async_set_addon_config(
{CONF_ADDON_SOCKET: discovery_info.socket_path}
Expand All @@ -1498,6 +1505,11 @@ async def async_step_esphome(
self.hass.config_entries.async_schedule_reload(existing_entry.entry_id)
return self.async_abort(reason="already_configured")

# We are not aborting if home ID configured here, we just want to make sure that it's set
# We will update a USB based config entry automatically in `async_step_finish_addon_setup_user`
await self.async_set_unique_id(
str(discovery_info.zwave_home_id), raise_on_progress=False
)
self.socket_path = discovery_info.socket_path
self.context["title_placeholders"] = {
CONF_NAME: f"{discovery_info.name} via ESPHome"
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion requirements_test_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading