diff --git a/homeassistant/components/version/binary_sensor.py b/homeassistant/components/version/binary_sensor.py new file mode 100644 index 00000000000..0e60b1b856c --- /dev/null +++ b/homeassistant/components/version/binary_sensor.py @@ -0,0 +1,61 @@ +"""Binary sensor platform for Version.""" +from __future__ import annotations + +from awesomeversion import AwesomeVersion + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_NAME, __version__ as HA_VERSION +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import CONF_SOURCE, DEFAULT_NAME, DOMAIN +from .coordinator import VersionDataUpdateCoordinator +from .entity import VersionEntity + +HA_VERSION_OBJECT = AwesomeVersion(HA_VERSION) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up version binary_sensors.""" + coordinator: VersionDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] + if (source := config_entry.data[CONF_SOURCE]) == "local": + return + + if (entity_name := config_entry.data[CONF_NAME]) == DEFAULT_NAME: + entity_name = config_entry.title + + entities: list[VersionBinarySensor] = [ + VersionBinarySensor( + coordinator=coordinator, + entity_description=BinarySensorEntityDescription( + key=str(source), + name=f"{entity_name} Update Available", + device_class=BinarySensorDeviceClass.UPDATE, + entity_category=EntityCategory.DIAGNOSTIC, + ), + ) + ] + + async_add_entities(entities) + + +class VersionBinarySensor(VersionEntity, BinarySensorEntity): + """Binary sensor for version entities.""" + + entity_description: BinarySensorEntityDescription + + @property + def is_on(self) -> bool: + """Return true if the binary sensor is on.""" + version = self.coordinator.version + return version is not None and (version > HA_VERSION_OBJECT) diff --git a/homeassistant/components/version/const.py b/homeassistant/components/version/const.py index 8f1005961e8..9ee556c6b7f 100644 --- a/homeassistant/components/version/const.py +++ b/homeassistant/components/version/const.py @@ -11,7 +11,7 @@ from homeassistant.const import CONF_NAME, Platform DOMAIN: Final = "version" LOGGER: Final[Logger] = getLogger(__package__) -PLATFORMS: Final[list[Platform]] = [Platform.SENSOR] +PLATFORMS: Final[list[Platform]] = [Platform.BINARY_SENSOR, Platform.SENSOR] UPDATE_COORDINATOR_UPDATE_INTERVAL: Final[timedelta] = timedelta(minutes=5) ENTRY_TYPE_SERVICE: Final = "service" diff --git a/homeassistant/components/version/entity.py b/homeassistant/components/version/entity.py new file mode 100644 index 00000000000..1dcdc23fa9f --- /dev/null +++ b/homeassistant/components/version/entity.py @@ -0,0 +1,33 @@ +"""Common entity class for Version integration.""" + +from homeassistant.helpers.device_registry import DeviceEntryType +from homeassistant.helpers.entity import DeviceInfo, EntityDescription +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN, HOME_ASSISTANT +from .coordinator import VersionDataUpdateCoordinator + + +class VersionEntity(CoordinatorEntity): + """Common entity class for Version integration.""" + + _attr_device_info = DeviceInfo( + name=f"{HOME_ASSISTANT} {DOMAIN.title()}", + identifiers={(HOME_ASSISTANT, DOMAIN)}, + manufacturer=HOME_ASSISTANT, + entry_type=DeviceEntryType.SERVICE, + ) + + coordinator: VersionDataUpdateCoordinator + + def __init__( + self, + coordinator: VersionDataUpdateCoordinator, + entity_description: EntityDescription, + ) -> None: + """Initialize version entities.""" + super().__init__(coordinator) + self.entity_description = entity_description + self._attr_unique_id = ( + f"{coordinator.config_entry.entry_id}_{entity_description.key}" + ) diff --git a/homeassistant/components/version/sensor.py b/homeassistant/components/version/sensor.py index 8b09d893afd..f0583a19068 100644 --- a/homeassistant/components/version/sensor.py +++ b/homeassistant/components/version/sensor.py @@ -15,11 +15,8 @@ from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_NAME from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.device_registry import DeviceEntryType -from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType -from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ( ATTR_SOURCE, @@ -31,12 +28,12 @@ from .const import ( DEFAULT_NAME, DEFAULT_SOURCE, DOMAIN, - HOME_ASSISTANT, LOGGER, VALID_IMAGES, VALID_SOURCES, ) from .coordinator import VersionDataUpdateCoordinator +from .entity import VersionEntity PLATFORM_SCHEMA: Final[Schema] = SENSOR_PLATFORM_SCHEMA.extend( { @@ -91,30 +88,10 @@ async def async_setup_entry( async_add_entities(version_sensor_entities) -class VersionSensorEntity(CoordinatorEntity, SensorEntity): +class VersionSensorEntity(VersionEntity, SensorEntity): """Version sensor entity class.""" _attr_icon = "mdi:package-up" - _attr_device_info = DeviceInfo( - name=f"{HOME_ASSISTANT} {DOMAIN.title()}", - identifiers={(HOME_ASSISTANT, DOMAIN)}, - manufacturer=HOME_ASSISTANT, - entry_type=DeviceEntryType.SERVICE, - ) - - coordinator: VersionDataUpdateCoordinator - - def __init__( - self, - coordinator: VersionDataUpdateCoordinator, - entity_description: SensorEntityDescription, - ) -> None: - """Initialize version sensor entities.""" - super().__init__(coordinator) - self.entity_description = entity_description - self._attr_unique_id = ( - f"{coordinator.config_entry.entry_id}_{entity_description.key}" - ) @property def native_value(self) -> StateType: diff --git a/tests/components/version/common.py b/tests/components/version/common.py index 17d72d6de72..b210a8600b8 100644 --- a/tests/components/version/common.py +++ b/tests/components/version/common.py @@ -52,9 +52,17 @@ async def mock_get_version_update( await hass.async_block_till_done() -async def setup_version_integration(hass: HomeAssistant) -> MockConfigEntry: +async def setup_version_integration( + hass: HomeAssistant, + entry_data: dict[str, Any] | None = None, +) -> MockConfigEntry: """Set up the Version integration.""" - mock_entry = MockConfigEntry(**MOCK_VERSION_CONFIG_ENTRY_DATA) + mock_entry = MockConfigEntry( + **{ + **MOCK_VERSION_CONFIG_ENTRY_DATA, + "data": entry_data or MOCK_VERSION_CONFIG_ENTRY_DATA["data"], + } + ) mock_entry.add_to_hass(hass) with patch( @@ -65,7 +73,6 @@ async def setup_version_integration(hass: HomeAssistant) -> MockConfigEntry: assert await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() - assert hass.states.get("sensor.local_installation").state == MOCK_VERSION assert mock_entry.state == config_entries.ConfigEntryState.LOADED return mock_entry diff --git a/tests/components/version/test_binary_sensor.py b/tests/components/version/test_binary_sensor.py new file mode 100644 index 00000000000..c9551ad4415 --- /dev/null +++ b/tests/components/version/test_binary_sensor.py @@ -0,0 +1,23 @@ +"""The test for the version binary sensor platform.""" +from __future__ import annotations + +from homeassistant.components.version.const import DEFAULT_CONFIGURATION +from homeassistant.core import HomeAssistant + +from .common import setup_version_integration + + +async def test_version_binary_sensor_local_source(hass: HomeAssistant): + """Test the Version binary sensor with local source.""" + await setup_version_integration(hass) + + state = hass.states.get("binary_sensor.local_installation_update_available") + assert not state + + +async def test_version_binary_sensor(hass: HomeAssistant): + """Test the Version binary sensor.""" + await setup_version_integration(hass, {**DEFAULT_CONFIGURATION, "source": "pypi"}) + + state = hass.states.get("binary_sensor.local_installation_update_available") + assert state