diff --git a/homeassistant/components/gios/manifest.json b/homeassistant/components/gios/manifest.json index 3dfb2a168db..f13da0e3f33 100644 --- a/homeassistant/components/gios/manifest.json +++ b/homeassistant/components/gios/manifest.json @@ -3,7 +3,7 @@ "name": "GIO\u015a", "documentation": "https://www.home-assistant.io/integrations/gios", "codeowners": ["@bieniu"], - "requirements": ["gios==1.0.1"], + "requirements": ["gios==1.0.2"], "config_flow": true, "quality_scale": "platinum", "iot_class": "cloud_polling" diff --git a/homeassistant/components/gios/sensor.py b/homeassistant/components/gios/sensor.py index c0bda830f25..b9c1290dc00 100644 --- a/homeassistant/components/gios/sensor.py +++ b/homeassistant/components/gios/sensor.py @@ -13,6 +13,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import GiosDataUpdateCoordinator from .const import ( + ATTR_AQI, ATTR_INDEX, ATTR_STATION, ATTR_UNIT, @@ -33,10 +34,14 @@ async def async_setup_entry( coordinator = hass.data[DOMAIN][entry.entry_id] - sensors = [] + sensors: list[GiosSensor | GiosAqiSensor] = [] - for sensor in coordinator.data: - if sensor in SENSOR_TYPES: + for sensor, sensor_data in coordinator.data.items(): + if sensor not in SENSOR_TYPES or not sensor_data.get(ATTR_VALUE): + continue + if sensor == ATTR_AQI: + sensors.append(GiosAqiSensor(name, sensor, coordinator)) + else: sensors.append(GiosSensor(name, sensor, coordinator)) async_add_entities(sensors) @@ -84,6 +89,21 @@ class GiosSensor(CoordinatorEntity, SensorEntity): def state(self) -> StateType: """Return the state.""" self._state = self.coordinator.data[self._sensor_type][ATTR_VALUE] - if self._description.get(ATTR_VALUE): - return cast(StateType, self._description[ATTR_VALUE](self._state)) - return cast(StateType, self._state) + return cast(StateType, self._description[ATTR_VALUE](self._state)) + + +class GiosAqiSensor(GiosSensor): + """Define an GIOS AQI sensor.""" + + @property + def state(self) -> StateType: + """Return the state.""" + return cast(StateType, self.coordinator.data[self._sensor_type][ATTR_VALUE]) + + @property + def available(self) -> bool: + """Return if entity is available.""" + available = super().available + return available and bool( + self.coordinator.data[self._sensor_type].get(ATTR_VALUE) + ) diff --git a/requirements_all.txt b/requirements_all.txt index b9662549ab8..d1919653e57 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -677,7 +677,7 @@ georss_qld_bushfire_alert_client==0.5 getmac==0.8.2 # homeassistant.components.gios -gios==1.0.1 +gios==1.0.2 # homeassistant.components.gitter gitterpy==0.1.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5e2477db357..c5658bf9b5a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -383,7 +383,7 @@ georss_qld_bushfire_alert_client==0.5 getmac==0.8.2 # homeassistant.components.gios -gios==1.0.1 +gios==1.0.2 # homeassistant.components.glances glances_api==0.2.0 diff --git a/tests/components/gios/__init__.py b/tests/components/gios/__init__.py index 537d6265125..6c39ee35303 100644 --- a/tests/components/gios/__init__.py +++ b/tests/components/gios/__init__.py @@ -12,7 +12,9 @@ STATIONS = [ ] -async def init_integration(hass, incomplete_data=False) -> MockConfigEntry: +async def init_integration( + hass, incomplete_data=False, invalid_indexes=False +) -> MockConfigEntry: """Set up the GIOS integration in Home Assistant.""" entry = MockConfigEntry( domain=DOMAIN, @@ -28,6 +30,8 @@ async def init_integration(hass, incomplete_data=False) -> MockConfigEntry: indexes["stIndexLevel"]["indexLevelName"] = "foo" sensors["pm10"]["values"][0]["value"] = None sensors["pm10"]["values"][1]["value"] = None + if invalid_indexes: + indexes = {} with patch( "homeassistant.components.gios.Gios._get_stations", return_value=STATIONS diff --git a/tests/components/gios/test_sensor.py b/tests/components/gios/test_sensor.py index 4d43d90f9a2..8ce192b7e9c 100644 --- a/tests/components/gios/test_sensor.py +++ b/tests/components/gios/test_sensor.py @@ -5,7 +5,7 @@ from unittest.mock import patch from gios import ApiError -from homeassistant.components.gios.const import ATTR_STATION, ATTRIBUTION +from homeassistant.components.gios.const import ATTR_INDEX, ATTR_STATION, ATTRIBUTION from homeassistant.components.sensor import ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT from homeassistant.const import ( ATTR_ATTRIBUTION, @@ -37,6 +37,7 @@ async def test_sensor(hass): == CONCENTRATION_MICROGRAMS_PER_CUBIC_METER ) assert state.attributes.get(ATTR_ICON) == "mdi:blur" + assert state.attributes.get(ATTR_INDEX) == "bardzo dobry" entry = registry.async_get("sensor.home_c6h6") assert entry @@ -53,6 +54,7 @@ async def test_sensor(hass): == CONCENTRATION_MICROGRAMS_PER_CUBIC_METER ) assert state.attributes.get(ATTR_ICON) == "mdi:blur" + assert state.attributes.get(ATTR_INDEX) == "dobry" entry = registry.async_get("sensor.home_co") assert entry @@ -69,6 +71,7 @@ async def test_sensor(hass): == CONCENTRATION_MICROGRAMS_PER_CUBIC_METER ) assert state.attributes.get(ATTR_ICON) == "mdi:blur" + assert state.attributes.get(ATTR_INDEX) == "dobry" entry = registry.async_get("sensor.home_no2") assert entry @@ -85,6 +88,7 @@ async def test_sensor(hass): == CONCENTRATION_MICROGRAMS_PER_CUBIC_METER ) assert state.attributes.get(ATTR_ICON) == "mdi:blur" + assert state.attributes.get(ATTR_INDEX) == "dobry" entry = registry.async_get("sensor.home_o3") assert entry @@ -101,6 +105,7 @@ async def test_sensor(hass): == CONCENTRATION_MICROGRAMS_PER_CUBIC_METER ) assert state.attributes.get(ATTR_ICON) == "mdi:blur" + assert state.attributes.get(ATTR_INDEX) == "dobry" entry = registry.async_get("sensor.home_pm10") assert entry @@ -117,6 +122,7 @@ async def test_sensor(hass): == CONCENTRATION_MICROGRAMS_PER_CUBIC_METER ) assert state.attributes.get(ATTR_ICON) == "mdi:blur" + assert state.attributes.get(ATTR_INDEX) == "dobry" entry = registry.async_get("sensor.home_pm2_5") assert entry @@ -133,6 +139,7 @@ async def test_sensor(hass): == CONCENTRATION_MICROGRAMS_PER_CUBIC_METER ) assert state.attributes.get(ATTR_ICON) == "mdi:blur" + assert state.attributes.get(ATTR_INDEX) == "bardzo dobry" entry = registry.async_get("sensor.home_so2") assert entry @@ -179,7 +186,7 @@ async def test_availability(hass): return_value=json.loads(load_fixture("gios/sensors.json")), ), patch( "homeassistant.components.gios.Gios._get_indexes", - return_value=json.loads(load_fixture("gios/indexes.json")), + return_value={}, ): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -188,3 +195,160 @@ async def test_availability(hass): assert state assert state.state != STATE_UNAVAILABLE assert state.state == "4" + + state = hass.states.get("sensor.home_aqi") + assert state + assert state.state == STATE_UNAVAILABLE + + +async def test_invalid_indexes(hass): + """Test states of the sensor when API returns invalid indexes.""" + await init_integration(hass, invalid_indexes=True) + registry = er.async_get(hass) + + state = hass.states.get("sensor.home_c6h6") + assert state + assert state.state == "0" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_STATION) == "Test Name 1" + assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == CONCENTRATION_MICROGRAMS_PER_CUBIC_METER + ) + assert state.attributes.get(ATTR_ICON) == "mdi:blur" + assert state.attributes.get(ATTR_INDEX) is None + + entry = registry.async_get("sensor.home_c6h6") + assert entry + assert entry.unique_id == "123-c6h6" + + state = hass.states.get("sensor.home_co") + assert state + assert state.state == "252" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_STATION) == "Test Name 1" + assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == CONCENTRATION_MICROGRAMS_PER_CUBIC_METER + ) + assert state.attributes.get(ATTR_ICON) == "mdi:blur" + assert state.attributes.get(ATTR_INDEX) is None + + entry = registry.async_get("sensor.home_co") + assert entry + assert entry.unique_id == "123-co" + + state = hass.states.get("sensor.home_no2") + assert state + assert state.state == "7" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_STATION) == "Test Name 1" + assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == CONCENTRATION_MICROGRAMS_PER_CUBIC_METER + ) + assert state.attributes.get(ATTR_ICON) == "mdi:blur" + assert state.attributes.get(ATTR_INDEX) is None + + entry = registry.async_get("sensor.home_no2") + assert entry + assert entry.unique_id == "123-no2" + + state = hass.states.get("sensor.home_o3") + assert state + assert state.state == "96" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_STATION) == "Test Name 1" + assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == CONCENTRATION_MICROGRAMS_PER_CUBIC_METER + ) + assert state.attributes.get(ATTR_ICON) == "mdi:blur" + assert state.attributes.get(ATTR_INDEX) is None + + entry = registry.async_get("sensor.home_o3") + assert entry + assert entry.unique_id == "123-o3" + + state = hass.states.get("sensor.home_pm10") + assert state + assert state.state == "17" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_STATION) == "Test Name 1" + assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == CONCENTRATION_MICROGRAMS_PER_CUBIC_METER + ) + assert state.attributes.get(ATTR_ICON) == "mdi:blur" + assert state.attributes.get(ATTR_INDEX) is None + + entry = registry.async_get("sensor.home_pm10") + assert entry + assert entry.unique_id == "123-pm10" + + state = hass.states.get("sensor.home_pm2_5") + assert state + assert state.state == "4" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_STATION) == "Test Name 1" + assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == CONCENTRATION_MICROGRAMS_PER_CUBIC_METER + ) + assert state.attributes.get(ATTR_ICON) == "mdi:blur" + assert state.attributes.get(ATTR_INDEX) is None + + entry = registry.async_get("sensor.home_pm2_5") + assert entry + assert entry.unique_id == "123-pm2.5" + + state = hass.states.get("sensor.home_so2") + assert state + assert state.state == "4" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_STATION) == "Test Name 1" + assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == CONCENTRATION_MICROGRAMS_PER_CUBIC_METER + ) + assert state.attributes.get(ATTR_ICON) == "mdi:blur" + assert state.attributes.get(ATTR_INDEX) is None + + entry = registry.async_get("sensor.home_so2") + assert entry + assert entry.unique_id == "123-so2" + + state = hass.states.get("sensor.home_aqi") + assert state is None + + +async def test_aqi_sensor_availability(hass): + """Ensure that we mark the AQI sensor unavailable correctly when indexes are invalid.""" + await init_integration(hass) + + state = hass.states.get("sensor.home_aqi") + assert state + assert state.state != STATE_UNAVAILABLE + assert state.state == "dobry" + + future = utcnow() + timedelta(minutes=60) + with patch( + "homeassistant.components.gios.Gios._get_all_sensors", + return_value=json.loads(load_fixture("gios/sensors.json")), + ), patch( + "homeassistant.components.gios.Gios._get_indexes", + return_value={}, + ): + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + + state = hass.states.get("sensor.home_aqi") + assert state + assert state.state == STATE_UNAVAILABLE