From af73afa2e2965a8d8b20461503c8111b31719cee Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Mon, 14 Nov 2022 18:18:51 +0100 Subject: [PATCH] Add support for HHCCJCY10 to xiaomi_ble (#82069) --- .../components/xiaomi_ble/manifest.json | 6 +- homeassistant/components/xiaomi_ble/sensor.py | 71 ++++++++++--------- homeassistant/generated/bluetooth.py | 5 ++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/xiaomi_ble/__init__.py | 14 ++++ tests/components/xiaomi_ble/test_sensor.py | 57 ++++++++++++++- 7 files changed, 121 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/xiaomi_ble/manifest.json b/homeassistant/components/xiaomi_ble/manifest.json index 56efd9e966a..3c66e8bc0ca 100644 --- a/homeassistant/components/xiaomi_ble/manifest.json +++ b/homeassistant/components/xiaomi_ble/manifest.json @@ -4,12 +4,16 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/xiaomi_ble", "bluetooth": [ + { + "connectable": false, + "service_data_uuid": "0000fd50-0000-1000-8000-00805f9b34fb" + }, { "connectable": false, "service_data_uuid": "0000fe95-0000-1000-8000-00805f9b34fb" } ], - "requirements": ["xiaomi-ble==0.10.0"], + "requirements": ["xiaomi-ble==0.12.1"], "dependencies": ["bluetooth"], "codeowners": ["@Jc2k", "@Ernst79"], "iot_class": "local_push" diff --git a/homeassistant/components/xiaomi_ble/sensor.py b/homeassistant/components/xiaomi_ble/sensor.py index 0e7e05a3a94..033d6b7daf0 100644 --- a/homeassistant/components/xiaomi_ble/sensor.py +++ b/homeassistant/components/xiaomi_ble/sensor.py @@ -36,10 +36,25 @@ from .const import DOMAIN from .device import device_key_to_bluetooth_entity_key, sensor_device_info_to_hass SENSOR_DESCRIPTIONS = { - (DeviceClass.TEMPERATURE, Units.TEMP_CELSIUS): SensorEntityDescription( - key=f"{DeviceClass.TEMPERATURE}_{Units.TEMP_CELSIUS}", - device_class=SensorDeviceClass.TEMPERATURE, - native_unit_of_measurement=TEMP_CELSIUS, + (DeviceClass.BATTERY, Units.PERCENTAGE): SensorEntityDescription( + key=f"{DeviceClass.BATTERY}_{Units.PERCENTAGE}", + device_class=SensorDeviceClass.BATTERY, + native_unit_of_measurement=PERCENTAGE, + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), + (DeviceClass.CONDUCTIVITY, Units.CONDUCTIVITY): SensorEntityDescription( + key=str(Units.CONDUCTIVITY), + device_class=None, + native_unit_of_measurement=CONDUCTIVITY, + state_class=SensorStateClass.MEASUREMENT, + ), + ( + DeviceClass.FORMALDEHYDE, + Units.CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, + ): SensorEntityDescription( + key=f"{DeviceClass.FORMALDEHYDE}_{Units.CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER}", + native_unit_of_measurement=CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, state_class=SensorStateClass.MEASUREMENT, ), (DeviceClass.HUMIDITY, Units.PERCENTAGE): SensorEntityDescription( @@ -54,26 +69,18 @@ SENSOR_DESCRIPTIONS = { native_unit_of_measurement=LIGHT_LUX, state_class=SensorStateClass.MEASUREMENT, ), + (DeviceClass.MOISTURE, Units.PERCENTAGE): SensorEntityDescription( + key=f"{DeviceClass.MOISTURE}_{Units.PERCENTAGE}", + device_class=SensorDeviceClass.MOISTURE, + native_unit_of_measurement=PERCENTAGE, + state_class=SensorStateClass.MEASUREMENT, + ), (DeviceClass.PRESSURE, Units.PRESSURE_MBAR): SensorEntityDescription( key=f"{DeviceClass.PRESSURE}_{Units.PRESSURE_MBAR}", device_class=SensorDeviceClass.PRESSURE, native_unit_of_measurement=PRESSURE_MBAR, state_class=SensorStateClass.MEASUREMENT, ), - (DeviceClass.BATTERY, Units.PERCENTAGE): SensorEntityDescription( - key=f"{DeviceClass.BATTERY}_{Units.PERCENTAGE}", - device_class=SensorDeviceClass.BATTERY, - native_unit_of_measurement=PERCENTAGE, - state_class=SensorStateClass.MEASUREMENT, - entity_category=EntityCategory.DIAGNOSTIC, - ), - (DeviceClass.VOLTAGE, Units.ELECTRIC_POTENTIAL_VOLT): SensorEntityDescription( - key=str(Units.ELECTRIC_POTENTIAL_VOLT), - device_class=SensorDeviceClass.VOLTAGE, - native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT, - state_class=SensorStateClass.MEASUREMENT, - entity_category=EntityCategory.DIAGNOSTIC, - ), ( DeviceClass.SIGNAL_STRENGTH, Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, @@ -85,26 +92,26 @@ SENSOR_DESCRIPTIONS = { entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, ), - # Used for e.g. moisture sensor on HHCCJCY01 + (DeviceClass.TEMPERATURE, Units.TEMP_CELSIUS): SensorEntityDescription( + key=f"{DeviceClass.TEMPERATURE}_{Units.TEMP_CELSIUS}", + device_class=SensorDeviceClass.TEMPERATURE, + native_unit_of_measurement=TEMP_CELSIUS, + state_class=SensorStateClass.MEASUREMENT, + ), + (DeviceClass.VOLTAGE, Units.ELECTRIC_POTENTIAL_VOLT): SensorEntityDescription( + key=f"{DeviceClass.VOLTAGE}_{Units.ELECTRIC_POTENTIAL_VOLT}", + device_class=SensorDeviceClass.VOLTAGE, + native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT, + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), + # Used for e.g. consumable sensor on WX08ZM (None, Units.PERCENTAGE): SensorEntityDescription( key=str(Units.PERCENTAGE), device_class=None, native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, ), - # Used for e.g. conductivity sensor on HHCCJCY01 - (None, Units.CONDUCTIVITY): SensorEntityDescription( - key=str(Units.CONDUCTIVITY), - device_class=None, - native_unit_of_measurement=CONDUCTIVITY, - state_class=SensorStateClass.MEASUREMENT, - ), - # Used for e.g. formaldehyde - (None, Units.CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER): SensorEntityDescription( - key=str(Units.CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER), - native_unit_of_measurement=CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, - state_class=SensorStateClass.MEASUREMENT, - ), } diff --git a/homeassistant/generated/bluetooth.py b/homeassistant/generated/bluetooth.py index 06ebbfbb515..017135e9d10 100644 --- a/homeassistant/generated/bluetooth.py +++ b/homeassistant/generated/bluetooth.py @@ -384,6 +384,11 @@ BLUETOOTH: list[dict[str, bool | str | int | list[int]]] = [ ], "manufacturer_id": 76, }, + { + "connectable": False, + "domain": "xiaomi_ble", + "service_data_uuid": "0000fd50-0000-1000-8000-00805f9b34fb", + }, { "connectable": False, "domain": "xiaomi_ble", diff --git a/requirements_all.txt b/requirements_all.txt index 1f26bef4f6a..f2060fa568d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2576,7 +2576,7 @@ xbox-webapi==2.0.11 xboxapi==2.0.1 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.10.0 +xiaomi-ble==0.12.1 # homeassistant.components.knx xknx==1.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 672785b69a2..367acc0eb86 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1789,7 +1789,7 @@ wolf_smartset==0.1.11 xbox-webapi==2.0.11 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.10.0 +xiaomi-ble==0.12.1 # homeassistant.components.knx xknx==1.2.0 diff --git a/tests/components/xiaomi_ble/__init__.py b/tests/components/xiaomi_ble/__init__.py index ab88cc559b7..a6d66ecf6df 100644 --- a/tests/components/xiaomi_ble/__init__.py +++ b/tests/components/xiaomi_ble/__init__.py @@ -84,6 +84,20 @@ YLKG07YL_SERVICE_INFO = BluetoothServiceInfoBleak( connectable=False, ) +HHCCJCY10_SERVICE_INFO = BluetoothServiceInfoBleak( + name="HHCCJCY10", + address="DC:23:4D:E5:5B:FC", + device=BLEDevice("00:00:00:00:00:00", None), + rssi=-56, + manufacturer_data={}, + service_data={"0000fd50-0000-1000-8000-00805f9b34fb": b"\x0e\x00n\x014\xa4(\x00["}, + service_uuids=["0000fd50-0000-1000-8000-00805f9b34fb"], + source="local", + advertisement=generate_advertisement_data(local_name="Not it"), + time=0, + connectable=False, +) + MISSING_PAYLOAD_ENCRYPTED = BluetoothServiceInfoBleak( name="LYWSD02MMC", address="A4:C1:38:56:53:84", diff --git a/tests/components/xiaomi_ble/test_sensor.py b/tests/components/xiaomi_ble/test_sensor.py index 17f9254b5ff..25f15938c6c 100644 --- a/tests/components/xiaomi_ble/test_sensor.py +++ b/tests/components/xiaomi_ble/test_sensor.py @@ -5,7 +5,7 @@ from homeassistant.components.sensor import ATTR_STATE_CLASS from homeassistant.components.xiaomi_ble.const import DOMAIN from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT -from . import MMC_T201_1_SERVICE_INFO, make_advertisement +from . import HHCCJCY10_SERVICE_INFO, MMC_T201_1_SERVICE_INFO, make_advertisement from tests.common import MockConfigEntry from tests.components.bluetooth import inject_bluetooth_service_info_bleak @@ -433,3 +433,58 @@ async def test_xiaomi_CGDK2(hass): assert await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() + + +async def test_hhcc_HHCCJCY10(hass): + """This device used a different UUID compared to the other Xiaomi sensors.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="DC:23:4D:E5:5B:FC", + ) + entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + inject_bluetooth_service_info_bleak(hass, HHCCJCY10_SERVICE_INFO) + + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 5 + + temp_sensor = hass.states.get("sensor.plant_sensor_5bfc_temperature") + temp_sensor_attr = temp_sensor.attributes + assert temp_sensor.state == "11.0" + assert temp_sensor_attr[ATTR_FRIENDLY_NAME] == "Plant Sensor 5BFC Temperature" + assert temp_sensor_attr[ATTR_UNIT_OF_MEASUREMENT] == "°C" + assert temp_sensor_attr[ATTR_STATE_CLASS] == "measurement" + + illu_sensor = hass.states.get("sensor.plant_sensor_5bfc_illuminance") + illu_sensor_attr = illu_sensor.attributes + assert illu_sensor.state == "79012" + assert illu_sensor_attr[ATTR_FRIENDLY_NAME] == "Plant Sensor 5BFC Illuminance" + assert illu_sensor_attr[ATTR_UNIT_OF_MEASUREMENT] == "lx" + assert illu_sensor_attr[ATTR_STATE_CLASS] == "measurement" + + cond_sensor = hass.states.get("sensor.plant_sensor_5bfc_conductivity") + cond_sensor_attr = cond_sensor.attributes + assert cond_sensor.state == "91" + assert cond_sensor_attr[ATTR_FRIENDLY_NAME] == "Plant Sensor 5BFC Conductivity" + assert cond_sensor_attr[ATTR_UNIT_OF_MEASUREMENT] == "µS/cm" + assert cond_sensor_attr[ATTR_STATE_CLASS] == "measurement" + + moist_sensor = hass.states.get("sensor.plant_sensor_5bfc_moisture") + moist_sensor_attr = moist_sensor.attributes + assert moist_sensor.state == "14" + assert moist_sensor_attr[ATTR_FRIENDLY_NAME] == "Plant Sensor 5BFC Moisture" + assert moist_sensor_attr[ATTR_UNIT_OF_MEASUREMENT] == "%" + assert moist_sensor_attr[ATTR_STATE_CLASS] == "measurement" + + bat_sensor = hass.states.get("sensor.plant_sensor_5bfc_battery") + bat_sensor_attr = bat_sensor.attributes + assert bat_sensor.state == "40" + assert bat_sensor_attr[ATTR_FRIENDLY_NAME] == "Plant Sensor 5BFC Battery" + assert bat_sensor_attr[ATTR_UNIT_OF_MEASUREMENT] == "%" + assert bat_sensor_attr[ATTR_STATE_CLASS] == "measurement" + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done()