From 5eec425e2c54309ad4d385361769dace324aa8ed Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 24 Feb 2022 22:50:17 +0100 Subject: [PATCH] Remove deprecated MH-Z19 CO2 Sensor integration (#67186) --- homeassistant/components/mhz19/__init__.py | 1 - homeassistant/components/mhz19/manifest.json | 9 - homeassistant/components/mhz19/sensor.py | 169 ------------------- requirements_all.txt | 1 - requirements_test_all.txt | 4 - tests/components/mhz19/__init__.py | 1 - tests/components/mhz19/test_sensor.py | 125 -------------- 7 files changed, 310 deletions(-) delete mode 100644 homeassistant/components/mhz19/__init__.py delete mode 100644 homeassistant/components/mhz19/manifest.json delete mode 100644 homeassistant/components/mhz19/sensor.py delete mode 100644 tests/components/mhz19/__init__.py delete mode 100644 tests/components/mhz19/test_sensor.py diff --git a/homeassistant/components/mhz19/__init__.py b/homeassistant/components/mhz19/__init__.py deleted file mode 100644 index 5fa9bbb69e8..00000000000 --- a/homeassistant/components/mhz19/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""The mhz19 component.""" diff --git a/homeassistant/components/mhz19/manifest.json b/homeassistant/components/mhz19/manifest.json deleted file mode 100644 index 349fba8c7a2..00000000000 --- a/homeassistant/components/mhz19/manifest.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "domain": "mhz19", - "name": "MH-Z19 CO2 Sensor", - "documentation": "https://www.home-assistant.io/integrations/mhz19", - "requirements": ["pmsensor==0.4"], - "codeowners": [], - "iot_class": "local_polling", - "loggers": ["pmsensor"] -} diff --git a/homeassistant/components/mhz19/sensor.py b/homeassistant/components/mhz19/sensor.py deleted file mode 100644 index f9237fd2c5b..00000000000 --- a/homeassistant/components/mhz19/sensor.py +++ /dev/null @@ -1,169 +0,0 @@ -"""Support for CO2 sensor connected to a serial port.""" -from __future__ import annotations - -from datetime import timedelta -import logging - -from pmsensor import co2sensor -import voluptuous as vol - -from homeassistant.components.sensor import ( - PLATFORM_SCHEMA, - SensorDeviceClass, - SensorEntity, - SensorEntityDescription, -) -from homeassistant.const import ( - ATTR_TEMPERATURE, - CONCENTRATION_PARTS_PER_MILLION, - CONF_MONITORED_CONDITIONS, - CONF_NAME, - TEMP_CELSIUS, -) -from homeassistant.core import HomeAssistant -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from homeassistant.util import Throttle - -_LOGGER = logging.getLogger(__name__) - -CONF_SERIAL_DEVICE = "serial_device" -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10) - -DEFAULT_NAME = "CO2 Sensor" - -ATTR_CO2_CONCENTRATION = "co2_concentration" - -SENSOR_TEMPERATURE = "temperature" -SENSOR_CO2 = "co2" -SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( - SensorEntityDescription( - key=SENSOR_TEMPERATURE, - name="Temperature", - native_unit_of_measurement=TEMP_CELSIUS, - device_class=SensorDeviceClass.TEMPERATURE, - ), - SensorEntityDescription( - key=SENSOR_CO2, - name="CO2", - native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION, - device_class=SensorDeviceClass.CO2, - ), -) -SENSOR_KEYS: list[str] = [desc.key for desc in SENSOR_TYPES] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_SERIAL_DEVICE): cv.string, - vol.Optional(CONF_MONITORED_CONDITIONS, default=[SENSOR_CO2]): vol.All( - cv.ensure_list, [vol.In(SENSOR_KEYS)] - ), - } -) - - -def setup_platform( - hass: HomeAssistant, - config: ConfigType, - add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Set up the available CO2 sensors.""" - _LOGGER.warning( - "The MH-Z19 CO2 Sensor integration is deprecated and will be removed " - "in Home Assistant Core 2022.4; this integration is removed under " - "Architectural Decision Record 0019, more information can be found here: " - "https://github.com/home-assistant/architecture/blob/master/adr/0019-GPIO.md" - ) - - try: - co2sensor.read_mh_z19(config.get(CONF_SERIAL_DEVICE)) - except OSError as err: - _LOGGER.error( - "Could not open serial connection to %s (%s)", - config.get(CONF_SERIAL_DEVICE), - err, - ) - return - - data = MHZClient(co2sensor, config.get(CONF_SERIAL_DEVICE)) - name = config[CONF_NAME] - - monitored_conditions = config[CONF_MONITORED_CONDITIONS] - entities = [ - MHZ19Sensor(data, name, description) - for description in SENSOR_TYPES - if description.key in monitored_conditions - ] - - add_entities(entities, True) - - -class MHZ19Sensor(SensorEntity): - """Representation of an CO2 sensor.""" - - def __init__(self, mhz_client, name, description: SensorEntityDescription): - """Initialize a new PM sensor.""" - self.entity_description = description - self._mhz_client = mhz_client - self._ppm = None - self._temperature = None - - self._attr_name = f"{name}: {description.name}" - - @property - def native_value(self): - """Return the state of the sensor.""" - if self.entity_description.key == SENSOR_CO2: - return self._ppm - return self._temperature - - def update(self): - """Read from sensor and update the state.""" - self._mhz_client.update() - data = self._mhz_client.data - self._temperature = data.get(SENSOR_TEMPERATURE) - self._ppm = data.get(SENSOR_CO2) - - @property - def extra_state_attributes(self): - """Return the state attributes.""" - result = {} - sensor_type = self.entity_description.key - if sensor_type == SENSOR_TEMPERATURE and self._ppm is not None: - result[ATTR_CO2_CONCENTRATION] = self._ppm - elif sensor_type == SENSOR_CO2 and self._temperature is not None: - result[ATTR_TEMPERATURE] = self._temperature - return result - - -class MHZClient: - """Get the latest data from the MH-Z sensor.""" - - def __init__(self, co2sens, serial): - """Initialize the sensor.""" - self.co2sensor = co2sens - self._serial = serial - self.data = {} - - @Throttle(MIN_TIME_BETWEEN_UPDATES) - def update(self): - """Get the latest data the MH-Z19 sensor.""" - self.data = {} - try: - result = self.co2sensor.read_mh_z19_with_temperature(self._serial) - if result is None: - return - co2, temperature = result - - except OSError as err: - _LOGGER.error( - "Could not open serial connection to %s (%s)", self._serial, err - ) - return - - if temperature is not None: - self.data[SENSOR_TEMPERATURE] = temperature - if co2 is not None and 0 < co2 <= 5000: - self.data[SENSOR_CO2] = co2 diff --git a/requirements_all.txt b/requirements_all.txt index 1662428acef..8ec3e428c66 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1260,7 +1260,6 @@ plugwise==0.16.6 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 -# homeassistant.components.mhz19 # homeassistant.components.serial_pm pmsensor==0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 93dacff8619..ca17fbd8889 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -801,10 +801,6 @@ plugwise==0.16.6 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 -# homeassistant.components.mhz19 -# homeassistant.components.serial_pm -pmsensor==0.4 - # homeassistant.components.poolsense poolsense==0.0.8 diff --git a/tests/components/mhz19/__init__.py b/tests/components/mhz19/__init__.py deleted file mode 100644 index a35660a3726..00000000000 --- a/tests/components/mhz19/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the mhz19 component.""" diff --git a/tests/components/mhz19/test_sensor.py b/tests/components/mhz19/test_sensor.py deleted file mode 100644 index fd494d6c099..00000000000 --- a/tests/components/mhz19/test_sensor.py +++ /dev/null @@ -1,125 +0,0 @@ -"""Tests for MH-Z19 sensor.""" -from unittest.mock import DEFAULT, Mock, patch - -from pmsensor import co2sensor -from pmsensor.co2sensor import read_mh_z19_with_temperature - -import homeassistant.components.mhz19.sensor as mhz19 -from homeassistant.components.sensor import DOMAIN -from homeassistant.const import ( - CONCENTRATION_PARTS_PER_MILLION, - TEMP_CELSIUS, - TEMP_FAHRENHEIT, -) -from homeassistant.setup import async_setup_component - -from tests.common import assert_setup_component - - -async def test_setup_missing_config(hass): - """Test setup with configuration missing required entries.""" - with assert_setup_component(0): - assert await async_setup_component( - hass, DOMAIN, {"sensor": {"platform": "mhz19"}} - ) - - -@patch("pmsensor.co2sensor.read_mh_z19", side_effect=OSError("test error")) -async def test_setup_failed_connect(mock_co2, hass): - """Test setup when connection error occurs.""" - assert not mhz19.setup_platform( - hass, - {"platform": "mhz19", mhz19.CONF_SERIAL_DEVICE: "test.serial"}, - None, - ) - - -async def test_setup_connected(hass): - """Test setup when connection succeeds.""" - with patch.multiple( - "pmsensor.co2sensor", - read_mh_z19=DEFAULT, - read_mh_z19_with_temperature=DEFAULT, - ): - read_mh_z19_with_temperature.return_value = None - mock_add = Mock() - mhz19.setup_platform( - hass, - { - "platform": "mhz19", - "name": "name", - "monitored_conditions": ["co2", "temperature"], - mhz19.CONF_SERIAL_DEVICE: "test.serial", - }, - mock_add, - ) - assert mock_add.call_count == 1 - - -@patch( - "pmsensor.co2sensor.read_mh_z19_with_temperature", - side_effect=OSError("test error"), -) -async def aiohttp_client_update_oserror(mock_function): - """Test MHZClient when library throws OSError.""" - client = mhz19.MHZClient(co2sensor, "test.serial") - client.update() - assert {} == client.data - - -@patch("pmsensor.co2sensor.read_mh_z19_with_temperature", return_value=(5001, 24)) -async def aiohttp_client_update_ppm_overflow(mock_function): - """Test MHZClient when ppm is too high.""" - client = mhz19.MHZClient(co2sensor, "test.serial") - client.update() - assert client.data.get("co2") is None - - -@patch("pmsensor.co2sensor.read_mh_z19_with_temperature", return_value=(1000, 24)) -async def aiohttp_client_update_good_read(mock_function): - """Test MHZClient when ppm is too high.""" - client = mhz19.MHZClient(co2sensor, "test.serial") - client.update() - assert {"temperature": 24, "co2": 1000} == client.data - - -@patch("pmsensor.co2sensor.read_mh_z19_with_temperature", return_value=(1000, 24)) -async def test_co2_sensor(mock_function, hass): - """Test CO2 sensor.""" - client = mhz19.MHZClient(co2sensor, "test.serial") - sensor = mhz19.MHZ19Sensor(client, "name", mhz19.SENSOR_TYPES[1]) - sensor.hass = hass - sensor.update() - - assert sensor.name == "name: CO2" - assert sensor.state == 1000 - assert sensor.native_unit_of_measurement == CONCENTRATION_PARTS_PER_MILLION - assert sensor.should_poll - assert sensor.extra_state_attributes == {"temperature": 24} - - -@patch("pmsensor.co2sensor.read_mh_z19_with_temperature", return_value=(1000, 24)) -async def test_temperature_sensor(mock_function, hass): - """Test temperature sensor.""" - client = mhz19.MHZClient(co2sensor, "test.serial") - sensor = mhz19.MHZ19Sensor(client, "name", mhz19.SENSOR_TYPES[0]) - sensor.hass = hass - sensor.update() - - assert sensor.name == "name: Temperature" - assert sensor.state == 24 - assert sensor.native_unit_of_measurement == TEMP_CELSIUS - assert sensor.should_poll - assert sensor.extra_state_attributes == {"co2_concentration": 1000} - - -@patch("pmsensor.co2sensor.read_mh_z19_with_temperature", return_value=(1000, 24)) -async def test_temperature_sensor_f(mock_function, hass): - """Test temperature sensor.""" - with patch.object(hass.config.units, "temperature_unit", TEMP_FAHRENHEIT): - client = mhz19.MHZClient(co2sensor, "test.serial") - sensor = mhz19.MHZ19Sensor(client, "name", mhz19.SENSOR_TYPES[0]) - sensor.hass = hass - sensor.update() - - assert sensor.state == 75