From 7bddee43e85bc13ea3218be8b34848c77ade4969 Mon Sep 17 00:00:00 2001 From: Trond Elde Date: Tue, 1 Sep 2026 19:56:09 +0200 Subject: [PATCH] Add Flexit binary sensors and change default baud rate (#180952) --- homeassistant/components/flexit/__init__.py | 2 +- .../components/flexit/binary_sensor.py | 80 ++++++++++++++ homeassistant/components/flexit/const.py | 2 +- homeassistant/components/flexit/strings.json | 10 ++ .../flexit/snapshots/test_binary_sensor.ambr | 102 ++++++++++++++++++ tests/components/flexit/test_binary_sensor.py | 43 ++++++++ tests/components/flexit/test_climate.py | 7 +- tests/components/flexit/test_config_flow.py | 13 ++- 8 files changed, 254 insertions(+), 5 deletions(-) create mode 100644 homeassistant/components/flexit/binary_sensor.py create mode 100644 tests/components/flexit/snapshots/test_binary_sensor.ambr create mode 100644 tests/components/flexit/test_binary_sensor.py diff --git a/homeassistant/components/flexit/__init__.py b/homeassistant/components/flexit/__init__.py index cdab796b93b8..dd8ba1ca7ae4 100644 --- a/homeassistant/components/flexit/__init__.py +++ b/homeassistant/components/flexit/__init__.py @@ -12,7 +12,7 @@ from homeassistant.core import HomeAssistant from .const import CONF_BAUDRATE, CONF_UNIT, DEFAULT_PORT, TYPE_SERIAL from .coordinator import FlexitConfigEntry, FlexitDataCoordinator -_PLATFORMS: list[Platform] = [Platform.CLIMATE] +_PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.CLIMATE] def create_modbus_params( diff --git a/homeassistant/components/flexit/binary_sensor.py b/homeassistant/components/flexit/binary_sensor.py new file mode 100644 index 000000000000..290a2a2b536a --- /dev/null +++ b/homeassistant/components/flexit/binary_sensor.py @@ -0,0 +1,80 @@ +"""Binary sensor platform for the Flexit integration.""" + +from collections.abc import Callable +from dataclasses import dataclass +from typing import override + +from flexit_modbus import Measurements + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback + +from .coordinator import FlexitConfigEntry, FlexitDataCoordinator +from .entity import FlexitEntity + + +@dataclass(kw_only=True, frozen=True) +class FlexitBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describe a Flexit binary sensor entity.""" + + value_fn: Callable[[Measurements], bool | None] + + +BINARY_SENSORS: tuple[FlexitBinarySensorEntityDescription, ...] = ( + FlexitBinarySensorEntityDescription( + key="filter_alarm", + translation_key="filter_alarm", + device_class=BinarySensorDeviceClass.PROBLEM, + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda measurements: measurements.filter_alarm, + ), + FlexitBinarySensorEntityDescription( + key="electric_heater_enabled", + translation_key="electric_heater_enabled", + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda measurements: measurements.electric_heater_enabled, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: FlexitConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Set up Flexit binary sensor entities.""" + coordinator = entry.runtime_data + async_add_entities( + FlexitBinarySensor(coordinator, description) for description in BINARY_SENSORS + ) + + +class FlexitBinarySensor(FlexitEntity, BinarySensorEntity): + """Representation of a Flexit binary sensor.""" + + entity_description: FlexitBinarySensorEntityDescription + + def __init__( + self, + coordinator: FlexitDataCoordinator, + entity_description: FlexitBinarySensorEntityDescription, + ) -> None: + """Initialize the binary sensor.""" + assert coordinator.config_entry is not None + super().__init__(coordinator) + self.entity_description = entity_description + self._attr_unique_id = ( + f"{coordinator.config_entry.entry_id}-{entity_description.key}" + ) + + @property + @override + def is_on(self) -> bool | None: + """Return the binary sensor state.""" + return self.entity_description.value_fn(self.coordinator.device.measurements) diff --git a/homeassistant/components/flexit/const.py b/homeassistant/components/flexit/const.py index 27197757754b..59c0ad4cf0e5 100644 --- a/homeassistant/components/flexit/const.py +++ b/homeassistant/components/flexit/const.py @@ -11,4 +11,4 @@ CONF_UNIT = "unit" TYPE_TCP = "tcp" TYPE_SERIAL = "serial" -DEFAULT_BAUDRATE = 57600 +DEFAULT_BAUDRATE = 9600 diff --git a/homeassistant/components/flexit/strings.json b/homeassistant/components/flexit/strings.json index 3f9b2b1f8656..787df090dc1b 100644 --- a/homeassistant/components/flexit/strings.json +++ b/homeassistant/components/flexit/strings.json @@ -57,6 +57,16 @@ } } }, + "entity": { + "binary_sensor": { + "electric_heater_enabled": { + "name": "Electric heater enabled" + }, + "filter_alarm": { + "name": "Filter alarm" + } + } + }, "issues": { "deprecated_yaml_no_import": { "description": "Configuring Flexit using YAML is being removed.\n\nYour existing YAML configuration could not be automatically imported because the Modbus connection details are configured separately, in a `modbus:` hub, which is not accessible from the `climate` platform configuration.\n\nRemove the `flexit` configuration from your configuration.yaml file, then add the integration again from the Home Assistant UI, providing the Modbus connection details and unit ID of your Flexit unit.", diff --git a/tests/components/flexit/snapshots/test_binary_sensor.ambr b/tests/components/flexit/snapshots/test_binary_sensor.ambr new file mode 100644 index 000000000000..be212fa19e59 --- /dev/null +++ b/tests/components/flexit/snapshots/test_binary_sensor.ambr @@ -0,0 +1,102 @@ +# serializer version: 1 +# name: test_binary_sensors[binary_sensor.flexit_electric_heater_enabled-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.flexit_electric_heater_enabled', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Electric heater enabled', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Electric heater enabled', + 'platform': 'flexit', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'electric_heater_enabled', + 'unique_id': 'flexit_001-electric_heater_enabled', + 'unit_of_measurement': None, + }) +# --- +# name: test_binary_sensors[binary_sensor.flexit_electric_heater_enabled-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Flexit Electric heater enabled', + }), + 'context': , + 'entity_id': 'binary_sensor.flexit_electric_heater_enabled', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_binary_sensors[binary_sensor.flexit_filter_alarm-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.flexit_filter_alarm', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Filter alarm', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Filter alarm', + 'platform': 'flexit', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'filter_alarm', + 'unique_id': 'flexit_001-filter_alarm', + 'unit_of_measurement': None, + }) +# --- +# name: test_binary_sensors[binary_sensor.flexit_filter_alarm-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'problem', + : 'Flexit Filter alarm', + }), + 'context': , + 'entity_id': 'binary_sensor.flexit_filter_alarm', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- diff --git a/tests/components/flexit/test_binary_sensor.py b/tests/components/flexit/test_binary_sensor.py new file mode 100644 index 000000000000..e445a97bf48e --- /dev/null +++ b/tests/components/flexit/test_binary_sensor.py @@ -0,0 +1,43 @@ +"""Test the Flexit binary sensor platform.""" + +from unittest.mock import patch + +from modbus_connection.mock import MockModbusUnit +from syrupy.assertion import SnapshotAssertion + +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from tests.common import MockConfigEntry, snapshot_platform + + +async def test_binary_sensors( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + entity_registry: er.EntityRegistry, + mock_config_entry: MockConfigEntry, + mock_modbus_unit: MockModbusUnit, +) -> None: + """Test Flexit binary sensor states.""" + mock_modbus_unit.holding.update({8: 215, 17: 2}) + mock_modbus_unit.input.update( + { + 8: 120, + 9: 200, + 11: 50, + 13: 0, + 14: 0, + 15: 0, + 27: 1, + 28: 1, + 48: 0, + } + ) + mock_config_entry.add_to_hass(hass) + + with patch("homeassistant.components.flexit._PLATFORMS", [Platform.BINARY_SENSOR]): + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) diff --git a/tests/components/flexit/test_climate.py b/tests/components/flexit/test_climate.py index 2118e1d794f5..24f20c8762ab 100644 --- a/tests/components/flexit/test_climate.py +++ b/tests/components/flexit/test_climate.py @@ -15,7 +15,7 @@ from homeassistant.components.climate import ( ) from homeassistant.components.flexit.climate import async_setup_platform from homeassistant.components.flexit.const import DOMAIN -from homeassistant.const import ATTR_TEMPERATURE +from homeassistant.const import ATTR_TEMPERATURE, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import ( @@ -90,7 +90,10 @@ async def test_climate_entity( mock_config_entry: MockConfigEntry, ) -> None: """Test climate entity setup and state.""" - await _setup_integration(hass, mock_config_entry) + mock_config_entry.add_to_hass(hass) + with patch("homeassistant.components.flexit._PLATFORMS", [Platform.CLIMATE]): + assert await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) diff --git a/tests/components/flexit/test_config_flow.py b/tests/components/flexit/test_config_flow.py index 9382db2a47a2..73b85fbef778 100644 --- a/tests/components/flexit/test_config_flow.py +++ b/tests/components/flexit/test_config_flow.py @@ -6,7 +6,12 @@ from modbus_connection import ModbusError, ModbusTcpParams from modbus_connection.mock import MockModbusUnit import pytest -from homeassistant.components.flexit.const import CONF_UNIT, DOMAIN, TYPE_TCP +from homeassistant.components.flexit.const import ( + CONF_BAUDRATE, + CONF_UNIT, + DOMAIN, + TYPE_TCP, +) from homeassistant.config_entries import ( SOURCE_RECONFIGURE, SOURCE_USER, @@ -126,6 +131,12 @@ async def test_full_flow_serial( assert result["type"] is FlowResultType.FORM assert result["step_id"] == "serial" assert result["errors"] == {} + assert ( + result["data_schema"]({CONF_DEVICE: "/dev/ttyUSB0", CONF_UNIT: 1})[ + CONF_BAUDRATE + ] + == 9600 + ) result = await hass.config_entries.flow.async_configure( result["flow_id"],