Compare commits

..

7 Commits

Author SHA1 Message Date
Franck Nijhof
2f9faa53a1 2026.3.0 (#164757) 2026-03-04 20:17:05 +01:00
Joost Lekkerkerker
718607a758 Revert "Add diagnostics platform to AWS S3 (#164118)" (#164759) 2026-03-04 19:01:47 +01:00
Franck Nijhof
3789156559 Revert "Add diagnostics platform to AWS S3 (#164118)"
This reverts commit 37d2c946e8.
2026-03-04 17:53:29 +00:00
Franck Nijhof
042ce6f2de Bump version to 2026.3.0 2026-03-04 17:30:58 +00:00
Franck Nijhof
0a5908002f Bump version to 2026.3.0b4 2026-03-04 17:09:32 +00:00
Petro31
3a5f71e10a Fix this variable preview issue with template entities from the UI (#164740) 2026-03-04 17:09:18 +00:00
rappenze
04e4b05ab0 Fix handling of several thermostat QuickApp's in fibaro (#164344)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-04 17:09:17 +00:00
11 changed files with 177 additions and 168 deletions

View File

@@ -1,55 +0,0 @@
"""Diagnostics support for AWS S3."""
from __future__ import annotations
import dataclasses
from typing import Any
from homeassistant.components.backup import (
DATA_MANAGER as BACKUP_DATA_MANAGER,
BackupManager,
)
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.core import HomeAssistant
from .const import (
CONF_ACCESS_KEY_ID,
CONF_BUCKET,
CONF_PREFIX,
CONF_SECRET_ACCESS_KEY,
DOMAIN,
)
from .coordinator import S3ConfigEntry
from .helpers import async_list_backups_from_s3
TO_REDACT = (CONF_ACCESS_KEY_ID, CONF_SECRET_ACCESS_KEY)
async def async_get_config_entry_diagnostics(
hass: HomeAssistant,
entry: S3ConfigEntry,
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator = entry.runtime_data
backup_manager: BackupManager = hass.data[BACKUP_DATA_MANAGER]
backups = await async_list_backups_from_s3(
coordinator.client,
bucket=entry.data[CONF_BUCKET],
prefix=entry.data.get(CONF_PREFIX, ""),
)
data = {
"coordinator_data": dataclasses.asdict(coordinator.data),
"config": {
**entry.data,
**entry.options,
},
"backup_agents": [
{"name": agent.name}
for agent in backup_manager.backup_agents.values()
if agent.domain == DOMAIN
],
"backup": [backup.as_dict() for backup in backups],
}
return async_redact_data(data, TO_REDACT)

View File

@@ -43,7 +43,7 @@ rules:
# Gold
devices: done
diagnostics: done
diagnostics: todo
discovery-update-info:
status: exempt
comment: S3 is a cloud service that is not discovered on the network.

View File

@@ -275,8 +275,11 @@ class FibaroController:
# otherwise add the first visible device in the group
# which is a hack, but solves a problem with FGT having
# hidden compatibility devices before the real device
if last_climate_parent != device.parent_fibaro_id or (
device.has_endpoint_id and last_endpoint != device.endpoint_id
# Second hack is for quickapps which have parent id 0 and no children
if (
last_climate_parent != device.parent_fibaro_id
or (device.has_endpoint_id and last_endpoint != device.endpoint_id)
or device.parent_fibaro_id == 0
):
_LOGGER.debug("Handle separately")
self.fibaro_devices[platform].append(device)

View File

@@ -29,7 +29,7 @@ from homeassistant.core import (
)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity import Entity, async_generate_entity_id
from homeassistant.helpers.event import (
TrackTemplate,
TrackTemplateResult,
@@ -264,16 +264,23 @@ class TemplateEntity(AbstractTemplateEntity):
return None
return cast(str, self._blueprint_inputs[CONF_USE_BLUEPRINT][CONF_PATH])
def _get_this_variable(self) -> TemplateStateFromEntityId:
"""Create a this variable for the entity."""
if self._preview_callback:
preview_entity_id = async_generate_entity_id(
self._entity_id_format, self._attr_name or "preview", hass=self.hass
)
return TemplateStateFromEntityId(self.hass, preview_entity_id)
return TemplateStateFromEntityId(self.hass, self.entity_id)
def _render_script_variables(self) -> dict[str, Any]:
"""Render configured variables."""
if isinstance(self._run_variables, dict):
return self._run_variables
return self._run_variables.async_render(
self.hass,
{
"this": TemplateStateFromEntityId(self.hass, self.entity_id),
},
self.hass, {"this": self._get_this_variable()}
)
def setup_state_template(
@@ -451,7 +458,7 @@ class TemplateEntity(AbstractTemplateEntity):
has_availability_template = False
variables = {
"this": TemplateStateFromEntityId(self.hass, self.entity_id),
"this": self._get_this_variable(),
**self._render_script_variables(),
}

View File

@@ -17,7 +17,7 @@ if TYPE_CHECKING:
APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2026
MINOR_VERSION: Final = 3
PATCH_VERSION: Final = "0b3"
PATCH_VERSION: Final = "0"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 14, 2)

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "homeassistant"
version = "2026.3.0b3"
version = "2026.3.0"
license = "Apache-2.0"
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
description = "Open-source home automation platform running on Python 3."

View File

@@ -1,73 +0,0 @@
# serializer version: 1
# name: test_entry_diagnostics[large]
dict({
'backup': list([
dict({
'addons': list([
]),
'backup_id': '23e64aec',
'database_included': True,
'date': '2024-11-22T11:48:48.727189+01:00',
'extra_metadata': dict({
}),
'folders': list([
]),
'homeassistant_included': True,
'homeassistant_version': '2024.12.0.dev0',
'name': 'Core 2024.12.0.dev0',
'protected': False,
'size': 20971520,
}),
]),
'backup_agents': list([
dict({
'name': 'test',
}),
]),
'config': dict({
'access_key_id': '**REDACTED**',
'bucket': 'test',
'endpoint_url': 'https://s3.eu-south-1.amazonaws.com',
'secret_access_key': '**REDACTED**',
}),
'coordinator_data': dict({
'all_backups_size': 20971520,
}),
})
# ---
# name: test_entry_diagnostics[small]
dict({
'backup': list([
dict({
'addons': list([
]),
'backup_id': '23e64aec',
'database_included': True,
'date': '2024-11-22T11:48:48.727189+01:00',
'extra_metadata': dict({
}),
'folders': list([
]),
'homeassistant_included': True,
'homeassistant_version': '2024.12.0.dev0',
'name': 'Core 2024.12.0.dev0',
'protected': False,
'size': 1048576,
}),
]),
'backup_agents': list([
dict({
'name': 'test',
}),
]),
'config': dict({
'access_key_id': '**REDACTED**',
'bucket': 'test',
'endpoint_url': 'https://s3.eu-south-1.amazonaws.com',
'secret_access_key': '**REDACTED**',
}),
'coordinator_data': dict({
'all_backups_size': 1048576,
}),
})
# ---

View File

@@ -1,29 +0,0 @@
"""Tests for AWS S3 diagnostics."""
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.backup import DOMAIN as BACKUP_DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
from tests.components.diagnostics import get_diagnostics_for_config_entry
from tests.typing import ClientSessionGenerator
async def test_entry_diagnostics(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
) -> None:
"""Test config entry diagnostics."""
mock_config_entry.add_to_hass(hass)
assert await async_setup_component(hass, BACKUP_DOMAIN, {BACKUP_DOMAIN: {}})
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert (
await get_diagnostics_for_config_entry(hass, hass_client, mock_config_entry)
== snapshot
)

View File

@@ -286,6 +286,68 @@ def mock_thermostat_with_operating_mode() -> Mock:
return climate
@pytest.fixture
def mock_thermostat_quickapp_1() -> Mock:
"""Fixture for a thermostat."""
climate = Mock()
climate.fibaro_id = 6
climate.parent_fibaro_id = 0
climate.has_endpoint_id = False
climate.name = "Test climate"
climate.room_id = 1
climate.dead = False
climate.visible = True
climate.enabled = True
climate.type = "com.fibaro.hvacSystemHeat"
climate.base_type = "com.fibaro.hvacSystem"
climate.properties = {"manufacturer": ""}
climate.actions = {"setHeatingThermostatSetpoint": 1, "setThermostatMode": 1}
climate.supported_features = {}
climate.has_supported_operating_modes = False
climate.has_supported_thermostat_modes = True
climate.supported_thermostat_modes = ["Off", "Heat"]
climate.has_thermostat_mode = True
climate.thermostat_mode = "Heat"
climate.has_unit = False
climate.has_heating_thermostat_setpoint = False
climate.has_heating_thermostat_setpoint_future = False
value_mock = Mock()
value_mock.has_value = False
climate.value = value_mock
return climate
@pytest.fixture
def mock_thermostat_quickapp_2() -> Mock:
"""Fixture for a thermostat."""
climate = Mock()
climate.fibaro_id = 7
climate.parent_fibaro_id = 0
climate.has_endpoint_id = False
climate.name = "Test climate 2"
climate.room_id = 1
climate.dead = False
climate.visible = True
climate.enabled = True
climate.type = "com.fibaro.hvacSystemHeat"
climate.base_type = "com.fibaro.hvacSystem"
climate.properties = {"manufacturer": ""}
climate.actions = {"setHeatingThermostatSetpoint": 1, "setThermostatMode": 1}
climate.supported_features = {}
climate.has_supported_operating_modes = False
climate.has_supported_thermostat_modes = True
climate.supported_thermostat_modes = ["Off", "Heat"]
climate.has_thermostat_mode = True
climate.thermostat_mode = "Heat"
climate.has_unit = False
climate.has_heating_thermostat_setpoint = False
climate.has_heating_thermostat_setpoint_future = False
value_mock = Mock()
value_mock.has_value = False
climate.value = value_mock
return climate
@pytest.fixture
def mock_fan_device() -> Mock:
"""Fixture for a fan endpoint of a thermostat device."""

View File

@@ -41,6 +41,34 @@ async def test_climate_setup(
)
async def test_climate_setup_2_quickapps(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_fibaro_client: Mock,
mock_config_entry: MockConfigEntry,
mock_thermostat_quickapp_1: Mock,
mock_thermostat_quickapp_2: Mock,
mock_room: Mock,
) -> None:
"""Test that the climate creates entities for more than one QuickApp."""
# Arrange
mock_fibaro_client.read_rooms.return_value = [mock_room]
mock_fibaro_client.read_devices.return_value = [
mock_thermostat_quickapp_1,
mock_thermostat_quickapp_2,
]
with patch("homeassistant.components.fibaro.PLATFORMS", [Platform.CLIMATE]):
# Act
await init_integration(hass, mock_config_entry)
# Assert
entry1 = entity_registry.async_get("climate.room_1_test_climate_6")
assert entry1
entry2 = entity_registry.async_get("climate.room_1_test_climate_2_7")
assert entry2
async def test_hvac_mode_preset(
hass: HomeAssistant,
mock_fibaro_client: Mock,

View File

@@ -1853,3 +1853,69 @@ async def test_preview_error(
# Test No preview is created
with pytest.raises(TimeoutError):
await client.receive_json(timeout=0.01)
@pytest.mark.parametrize(
("step_id", "user_input", "expected_state"),
[
(
"sensor",
{
"name": "",
"state": "{{ this.state }}",
},
"unknown",
),
(
"binary_sensor",
{
"name": "",
"state": "{{ this.state }}",
},
"off",
),
],
)
async def test_preview_this_variable(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
step_id: str,
user_input: dict,
expected_state: str,
) -> None:
"""Test 'this' variable will not produce an error when rendering a template."""
client = await hass_ws_client(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.MENU
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"next_step_id": step_id},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == step_id
assert result["errors"] is None
assert result["preview"] == "template"
await client.send_json_auto_id(
{
"type": "template/start_preview",
"flow_id": result["flow_id"],
"flow_type": "config_flow",
"user_input": user_input,
}
)
msg = await client.receive_json()
assert msg["success"]
assert msg["result"] is None
# Verify we do not get an error and that we receive a preview state.
msg = await client.receive_json()
assert "error" not in msg["event"]
assert msg["event"]["state"] == expected_state