diff --git a/homeassistant/components/jvc_projector/coordinator.py b/homeassistant/components/jvc_projector/coordinator.py index cbde80b65bc..52d45acd33b 100644 --- a/homeassistant/components/jvc_projector/coordinator.py +++ b/homeassistant/components/jvc_projector/coordinator.py @@ -7,7 +7,12 @@ from datetime import timedelta import logging from typing import TYPE_CHECKING, Any -from jvcprojector import JvcProjector, JvcProjectorTimeoutError, command as cmd +from jvcprojector import ( + JvcProjector, + JvcProjectorCommandError, + JvcProjectorTimeoutError, + command as cmd, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -144,7 +149,16 @@ class JvcProjectorDataUpdateCoordinator(DataUpdateCoordinator[dict[str, str]]): self, command: type[Command], new_state: dict[type[Command], str] ) -> str | None: """Update state with the current value of a command.""" - value = await self.device.get(command) + try: + value = await self.device.get(command) + except JvcProjectorCommandError as err: + _LOGGER.warning("Command %s failed: %s", command.name, err) + cached = self.state.get(command) + if command is cmd.Power and cached is None: + raise UpdateFailed( + f"Failed to fetch {command.name} and no cached value is available" + ) from err + return cached if value != self.state.get(command): new_state[command] = value diff --git a/homeassistant/components/jvc_projector/manifest.json b/homeassistant/components/jvc_projector/manifest.json index 0d9fb766a3f..d2913b5dd90 100644 --- a/homeassistant/components/jvc_projector/manifest.json +++ b/homeassistant/components/jvc_projector/manifest.json @@ -7,5 +7,5 @@ "integration_type": "device", "iot_class": "local_polling", "loggers": ["jvcprojector"], - "requirements": ["pyjvcprojector==2.0.5"] + "requirements": ["pyjvcprojector==2.0.6"] } diff --git a/requirements_all.txt b/requirements_all.txt index 6ffc05d2f32..f2211a3709f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2222,7 +2222,7 @@ pyitachip2ir==0.0.7 pyituran==0.1.5 # homeassistant.components.jvc_projector -pyjvcprojector==2.0.5 +pyjvcprojector==2.0.6 # homeassistant.components.kaleidescape pykaleidescape==1.1.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a618e3c13a1..af20829d7ce 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1905,7 +1905,7 @@ pyisy==3.4.1 pyituran==0.1.5 # homeassistant.components.jvc_projector -pyjvcprojector==2.0.5 +pyjvcprojector==2.0.6 # homeassistant.components.kaleidescape pykaleidescape==1.1.5 diff --git a/tests/components/jvc_projector/test_coordinator.py b/tests/components/jvc_projector/test_coordinator.py index fd1eddb83d8..efe069c2cf3 100644 --- a/tests/components/jvc_projector/test_coordinator.py +++ b/tests/components/jvc_projector/test_coordinator.py @@ -3,7 +3,11 @@ from datetime import timedelta from unittest.mock import AsyncMock -from jvcprojector import JvcProjectorTimeoutError, command as cmd +from jvcprojector import ( + JvcProjectorCommandError, + JvcProjectorTimeoutError, + command as cmd, +) import pytest from homeassistant.components.jvc_projector.coordinator import ( @@ -11,6 +15,7 @@ from homeassistant.components.jvc_projector.coordinator import ( INTERVAL_SLOW, ) from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.util.dt import utcnow @@ -58,3 +63,42 @@ async def test_coordinator_setup_connect_error( ) -> None: """Test coordinator connect error.""" assert mock_integration.state is ConfigEntryState.SETUP_RETRY + + +@pytest.mark.parametrize( + "mock_device", + [{"fixture_override": {cmd.Power: JvcProjectorCommandError}}], + indirect=True, +) +async def test_coordinator_setup_power_command_error( + hass: HomeAssistant, + mock_device: AsyncMock, + mock_integration: MockConfigEntry, +) -> None: + """Test coordinator fails setup when Power command errors with no cached value.""" + assert mock_integration.state is ConfigEntryState.SETUP_RETRY + + +@pytest.mark.parametrize( + "mock_device", + [{"fixture_override": {cmd.Input: JvcProjectorCommandError}}], + indirect=True, +) +async def test_coordinator_command_error_keeps_other_entities_available( + hass: HomeAssistant, + mock_device: AsyncMock, + mock_integration: MockConfigEntry, +) -> None: + """Test a failing command does not take every entity offline.""" + assert mock_integration.state is ConfigEntryState.LOADED + + coordinator = mock_integration.runtime_data + assert coordinator.last_update_success is True + + power = hass.states.get("sensor.jvc_projector_status") + assert power is not None + assert power.state == "on" + + light_time = hass.states.get("sensor.jvc_projector_light_time") + assert light_time is not None + assert light_time.state != STATE_UNAVAILABLE