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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ on:
type: boolean

env:
CACHE_VERSION: 8
CACHE_VERSION: 9
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 1
HA_SHORT_VERSION: "2025.11"
Expand Down Expand Up @@ -525,7 +525,7 @@ jobs:
. venv/bin/activate
python --version
pip install "$(grep '^uv' < requirements.txt)"
uv pip install -U "pip>=21.3.1" setuptools wheel
uv pip install -U "pip>=25.2"
uv pip install -r requirements.txt
python -m script.gen_requirements_all ci
uv pip install -r requirements_all_pytest.txt -r requirements_test.txt
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/frontend/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20251001.2"]
"requirements": ["home-assistant-frontend==20251001.4"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/kegtron/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/kegtron",
"iot_class": "local_push",
"requirements": ["kegtron-ble==0.4.0"]
"requirements": ["kegtron-ble==1.0.2"]
}
62 changes: 37 additions & 25 deletions homeassistant/components/shelly/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,15 @@
)
from .coordinator import ShellyBlockCoordinator, ShellyConfigEntry, ShellyRpcCoordinator
from .entity import (
BlockEntityDescription,
RpcEntityDescription,
ShellyBlockEntity,
ShellyBlockAttributeEntity,
ShellyRpcAttributeEntity,
async_setup_entry_attribute_entities,
async_setup_entry_rpc,
)
from .utils import (
async_remove_orphaned_entities,
async_remove_shelly_entity,
brightness_to_percentage,
get_device_entry_gen,
is_block_channel_type_light,
Expand All @@ -58,6 +59,24 @@
PARALLEL_UPDATES = 0


@dataclass(frozen=True, kw_only=True)
class BlockLightDescription(BlockEntityDescription, LightEntityDescription):
"""Description for a Shelly BLOCK light entity."""


BLOCK_LIGHTS = {
("light", "output"): BlockLightDescription(
key="light|output",
),
("relay", "output"): BlockLightDescription(
key="relay|output",
removal_condition=lambda settings, block: not is_block_channel_type_light(
settings, block
),
),
}


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ShellyConfigEntry,
Expand All @@ -79,36 +98,29 @@ def async_setup_block_entry(
"""Set up entities for block device."""
coordinator = config_entry.runtime_data.block
assert coordinator
blocks = []
assert coordinator.device.blocks
for block in coordinator.device.blocks:
if block.type == "light":
blocks.append(block)
elif block.type == "relay" and block.channel is not None:
if not is_block_channel_type_light(
coordinator.device.settings, int(block.channel)
):
continue

blocks.append(block)
unique_id = f"{coordinator.mac}-{block.type}_{block.channel}"
async_remove_shelly_entity(hass, "switch", unique_id)

if not blocks:
return

async_add_entities(BlockShellyLight(coordinator, block) for block in blocks)
async_setup_entry_attribute_entities(
hass, config_entry, async_add_entities, BLOCK_LIGHTS, BlockShellyLight
)


class BlockShellyLight(ShellyBlockEntity, LightEntity):
class BlockShellyLight(ShellyBlockAttributeEntity, LightEntity):
"""Entity that controls a light on block based Shelly devices."""

entity_description: BlockLightDescription
_attr_supported_color_modes: set[str]

def __init__(self, coordinator: ShellyBlockCoordinator, block: Block) -> None:
"""Initialize light."""
super().__init__(coordinator, block)
def __init__(
self,
coordinator: ShellyBlockCoordinator,
block: Block,
attribute: str,
description: BlockLightDescription,
) -> None:
"""Initialize block light."""
super().__init__(coordinator, block, attribute, description)
self.control_result: dict[str, Any] | None = None
self._attr_unique_id: str = f"{coordinator.mac}-{block.description}"
self._attr_supported_color_modes = set()
self._attr_min_color_temp_kelvin = KELVIN_MIN_VALUE_WHITE
self._attr_max_color_temp_kelvin = KELVIN_MAX_VALUE
Expand Down Expand Up @@ -347,7 +359,7 @@ def _update_callback(self) -> None:

@dataclass(frozen=True, kw_only=True)
class RpcLightDescription(RpcEntityDescription, LightEntityDescription):
"""Description for a Shelly RPC number entity."""
"""Description for a Shelly RPC light entity."""


class RpcShellyLightBase(ShellyRpcAttributeEntity, LightEntity):
Expand Down
12 changes: 6 additions & 6 deletions homeassistant/components/shelly/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,7 @@ def is_block_exclude_from_relay(settings: dict[str, Any], block: Block) -> bool:
if settings.get("mode") == "roller":
return True

if TYPE_CHECKING:
assert block.channel is not None

return is_block_channel_type_light(settings, int(block.channel))
return is_block_channel_type_light(settings, block)


def get_device_uptime(uptime: float, last_uptime: datetime | None) -> datetime:
Expand Down Expand Up @@ -501,9 +498,12 @@ def is_rpc_momentary_input(
return cast(bool, config[key]["type"] == "button")


def is_block_channel_type_light(settings: dict[str, Any], channel: int) -> bool:
def is_block_channel_type_light(settings: dict[str, Any], block: Block) -> bool:
"""Return true if block channel appliance type is set to light."""
app_type = settings["relays"][channel].get("appliance_type")
if TYPE_CHECKING:
assert block.channel is not None

app_type = settings["relays"][int(block.channel)].get("appliance_type")
return app_type is not None and app_type.lower().startswith("light")


Expand Down
2 changes: 1 addition & 1 deletion homeassistant/package_constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ habluetooth==5.7.0
hass-nabucasa==1.2.0
hassil==3.2.0
home-assistant-bluetooth==1.13.1
home-assistant-frontend==20251001.2
home-assistant-frontend==20251001.4
home-assistant-intents==2025.10.1
httpx==0.28.1
ifaddr==0.2.0
Expand Down
4 changes: 2 additions & 2 deletions requirements_all.txt

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

4 changes: 2 additions & 2 deletions requirements_test_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 tests/components/matter/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ async def integration_fixture(
"silabs_refrigerator",
"silabs_water_heater",
"smoke_detector",
"solar_power",
"solar_inverter",
"speaker",
"switch_unit",
"tado_smart_radiator_thermostat_x",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"0/40/0": 19,
"0/40/1": "TEST_VENDOR",
"0/40/2": 65521,
"0/40/3": "SolarPower",
"0/40/3": "Mock solar inverter",
"0/40/4": 32768,
"0/40/5": "",
"0/40/6": "**REDACTED**",
Expand Down
40 changes: 20 additions & 20 deletions tests/components/matter/snapshots/test_sensor.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -6555,7 +6555,7 @@
'state': '0.0',
})
# ---
# name: test_sensors[solar_power][sensor.solarpower_active_current-entry]
# name: test_sensors[solar_inverter][sensor.mock_solar_inverter_active_current-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
Expand All @@ -6570,7 +6570,7 @@
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.solarpower_active_current',
'entity_id': 'sensor.mock_solar_inverter_active_current',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
Expand Down Expand Up @@ -6598,23 +6598,23 @@
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
})
# ---
# name: test_sensors[solar_power][sensor.solarpower_active_current-state]
# name: test_sensors[solar_inverter][sensor.mock_solar_inverter_active_current-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'current',
'friendly_name': 'SolarPower Active current',
'friendly_name': 'Mock solar inverter Active current',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
}),
'context': <ANY>,
'entity_id': 'sensor.solarpower_active_current',
'entity_id': 'sensor.mock_solar_inverter_active_current',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '-3.62',
})
# ---
# name: test_sensors[solar_power][sensor.solarpower_energy_exported-entry]
# name: test_sensors[solar_inverter][sensor.mock_solar_inverter_energy_exported-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
Expand All @@ -6629,7 +6629,7 @@
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.solarpower_energy_exported',
'entity_id': 'sensor.mock_solar_inverter_energy_exported',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
Expand Down Expand Up @@ -6657,23 +6657,23 @@
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
})
# ---
# name: test_sensors[solar_power][sensor.solarpower_energy_exported-state]
# name: test_sensors[solar_inverter][sensor.mock_solar_inverter_energy_exported-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'energy',
'friendly_name': 'SolarPower Energy exported',
'friendly_name': 'Mock solar inverter Energy exported',
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
}),
'context': <ANY>,
'entity_id': 'sensor.solarpower_energy_exported',
'entity_id': 'sensor.mock_solar_inverter_energy_exported',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '42.279',
})
# ---
# name: test_sensors[solar_power][sensor.solarpower_power-entry]
# name: test_sensors[solar_inverter][sensor.mock_solar_inverter_power-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
Expand All @@ -6688,7 +6688,7 @@
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.solarpower_power',
'entity_id': 'sensor.mock_solar_inverter_power',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
Expand Down Expand Up @@ -6716,23 +6716,23 @@
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
})
# ---
# name: test_sensors[solar_power][sensor.solarpower_power-state]
# name: test_sensors[solar_inverter][sensor.mock_solar_inverter_power-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'power',
'friendly_name': 'SolarPower Power',
'friendly_name': 'Mock solar inverter Power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
'entity_id': 'sensor.solarpower_power',
'entity_id': 'sensor.mock_solar_inverter_power',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '-850.0',
})
# ---
# name: test_sensors[solar_power][sensor.solarpower_voltage-entry]
# name: test_sensors[solar_inverter][sensor.mock_solar_inverter_voltage-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
Expand All @@ -6747,7 +6747,7 @@
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.solarpower_voltage',
'entity_id': 'sensor.mock_solar_inverter_voltage',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
Expand Down Expand Up @@ -6775,16 +6775,16 @@
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
})
# ---
# name: test_sensors[solar_power][sensor.solarpower_voltage-state]
# name: test_sensors[solar_inverter][sensor.mock_solar_inverter_voltage-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'voltage',
'friendly_name': 'SolarPower Voltage',
'friendly_name': 'Mock solar inverter Voltage',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricPotential.VOLT: 'V'>,
}),
'context': <ANY>,
'entity_id': 'sensor.solarpower_voltage',
'entity_id': 'sensor.mock_solar_inverter_voltage',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
Expand Down
Loading
Loading