diff --git a/homeassistant/components/bthome/manifest.json b/homeassistant/components/bthome/manifest.json index d823d70dd39..bdb4b75bfa9 100644 --- a/homeassistant/components/bthome/manifest.json +++ b/homeassistant/components/bthome/manifest.json @@ -13,7 +13,7 @@ "service_data_uuid": "0000181e-0000-1000-8000-00805f9b34fb" } ], - "requirements": ["bthome-ble==0.4.0"], + "requirements": ["bthome-ble==0.5.2"], "dependencies": ["bluetooth"], "codeowners": ["@Ernst79"], "iot_class": "local_push" diff --git a/homeassistant/components/bthome/sensor.py b/homeassistant/components/bthome/sensor.py index 5cc3317ea82..71601fa24c0 100644 --- a/homeassistant/components/bthome/sensor.py +++ b/homeassistant/components/bthome/sensor.py @@ -25,6 +25,7 @@ from homeassistant.const import ( ENERGY_KILO_WATT_HOUR, LIGHT_LUX, MASS_KILOGRAMS, + MASS_POUNDS, PERCENTAGE, POWER_WATT, PRESSURE_MBAR, @@ -132,13 +133,41 @@ SENSOR_DESCRIPTIONS = { entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), - # Used for e.g. weight sensor + # Used for mass sensor with kg unit (None, Units.MASS_KILOGRAMS): SensorEntityDescription( - key=str(Units.MASS_KILOGRAMS), + key=f"{DeviceClass.MASS}_{Units.MASS_KILOGRAMS}", device_class=None, native_unit_of_measurement=MASS_KILOGRAMS, state_class=SensorStateClass.MEASUREMENT, ), + # Used for mass sensor with lb unit + (None, Units.MASS_POUNDS): SensorEntityDescription( + key=f"{DeviceClass.MASS}_{Units.MASS_POUNDS}", + device_class=None, + native_unit_of_measurement=MASS_POUNDS, + state_class=SensorStateClass.MEASUREMENT, + ), + # Used for moisture sensor + (None, Units.PERCENTAGE,): SensorEntityDescription( + key=f"{DeviceClass.MOISTURE}_{Units.PERCENTAGE}", + device_class=None, + native_unit_of_measurement=PERCENTAGE, + state_class=SensorStateClass.MEASUREMENT, + ), + # Used for dew point sensor + (None, Units.TEMP_CELSIUS): SensorEntityDescription( + key=f"{DeviceClass.DEW_POINT}_{Units.TEMP_CELSIUS}", + device_class=SensorDeviceClass.TEMPERATURE, + native_unit_of_measurement=TEMP_CELSIUS, + state_class=SensorStateClass.MEASUREMENT, + ), + # Used for count sensor + (None, None): SensorEntityDescription( + key=f"{DeviceClass.COUNT}", + device_class=None, + native_unit_of_measurement=None, + state_class=SensorStateClass.MEASUREMENT, + ), } @@ -156,7 +185,6 @@ def sensor_update_to_bluetooth_data_update( (description.device_class, description.native_unit_of_measurement) ] for device_key, description in sensor_update.entity_descriptions.items() - if description.native_unit_of_measurement }, entity_data={ device_key_to_bluetooth_entity_key(device_key): sensor_values.native_value diff --git a/requirements_all.txt b/requirements_all.txt index fca5683ce7a..b2fb4f2825c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -458,7 +458,7 @@ bsblan==0.5.0 bt_proximity==0.2.1 # homeassistant.components.bthome -bthome-ble==0.4.0 +bthome-ble==0.5.2 # homeassistant.components.bt_home_hub_5 bthomehub5-devicelist==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 777c97d1083..60c5bbead9a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -359,7 +359,7 @@ brunt==1.2.0 bsblan==0.5.0 # homeassistant.components.bthome -bthome-ble==0.4.0 +bthome-ble==0.5.2 # homeassistant.components.buienradar buienradar==1.0.5 diff --git a/tests/components/bthome/__init__.py b/tests/components/bthome/__init__.py index 7cb6496b5c5..be59cd7e8cb 100644 --- a/tests/components/bthome/__init__.py +++ b/tests/components/bthome/__init__.py @@ -37,18 +37,18 @@ TEMP_HUMI_ENCRYPTED_SERVICE_INFO = BluetoothServiceInfoBleak( connectable=False, ) -PM_SERVICE_INFO = BluetoothServiceInfoBleak( - name="TEST DEVICE 8F80A5", +PRST_SERVICE_INFO = BluetoothServiceInfoBleak( + name="prst 8F80A5", address="54:48:E6:8F:80:A5", device=BLEDevice("54:48:E6:8F:80:A5", None), rssi=-63, manufacturer_data={}, service_data={ - "0000181c-0000-1000-8000-00805f9b34fb": b"\x03\r\x12\x0c\x03\x0e\x02\x1c" + "0000181c-0000-1000-8000-00805f9b34fb": b'\x02\x14\x00\n"\x02\xdd\n\x02\x03{\x12\x02\x0c\n\x0b' }, service_uuids=["0000181c-0000-1000-8000-00805f9b34fb"], source="local", - advertisement=AdvertisementData(local_name="Not it"), + advertisement=AdvertisementData(local_name="prst"), time=0, connectable=False, ) diff --git a/tests/components/bthome/test_config_flow.py b/tests/components/bthome/test_config_flow.py index b1154ca9223..fd8f8dfaa35 100644 --- a/tests/components/bthome/test_config_flow.py +++ b/tests/components/bthome/test_config_flow.py @@ -11,7 +11,7 @@ from homeassistant.data_entry_flow import FlowResultType from . import ( NOT_BTHOME_SERVICE_INFO, - PM_SERVICE_INFO, + PRST_SERVICE_INFO, TEMP_HUMI_ENCRYPTED_SERVICE_INFO, TEMP_HUMI_SERVICE_INFO, ) @@ -185,7 +185,7 @@ async def test_async_step_user_with_found_devices(hass): """Test setup from service info cache with devices found.""" with patch( "homeassistant.components.bthome.config_flow.async_discovered_service_info", - return_value=[PM_SERVICE_INFO], + return_value=[PRST_SERVICE_INFO], ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -199,7 +199,7 @@ async def test_async_step_user_with_found_devices(hass): user_input={"address": "54:48:E6:8F:80:A5"}, ) assert result2["type"] == FlowResultType.CREATE_ENTRY - assert result2["title"] == "TEST DEVICE 80A5" + assert result2["title"] == "b-parasite 80A5" assert result2["data"] == {} assert result2["result"].unique_id == "54:48:E6:8F:80:A5" @@ -384,7 +384,7 @@ async def test_async_step_bluetooth_devices_already_setup(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_BLUETOOTH}, - data=PM_SERVICE_INFO, + data=PRST_SERVICE_INFO, ) assert result["type"] == FlowResultType.ABORT assert result["reason"] == "already_configured" @@ -395,7 +395,7 @@ async def test_async_step_bluetooth_already_in_progress(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_BLUETOOTH}, - data=PM_SERVICE_INFO, + data=PRST_SERVICE_INFO, ) assert result["type"] == FlowResultType.FORM assert result["step_id"] == "bluetooth_confirm" @@ -403,7 +403,7 @@ async def test_async_step_bluetooth_already_in_progress(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_BLUETOOTH}, - data=PM_SERVICE_INFO, + data=PRST_SERVICE_INFO, ) assert result["type"] == FlowResultType.ABORT assert result["reason"] == "already_in_progress" @@ -414,14 +414,14 @@ async def test_async_step_user_takes_precedence_over_discovery(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_BLUETOOTH}, - data=PM_SERVICE_INFO, + data=PRST_SERVICE_INFO, ) assert result["type"] == FlowResultType.FORM assert result["step_id"] == "bluetooth_confirm" with patch( "homeassistant.components.bthome.config_flow.async_discovered_service_info", - return_value=[PM_SERVICE_INFO], + return_value=[PRST_SERVICE_INFO], ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -435,7 +435,7 @@ async def test_async_step_user_takes_precedence_over_discovery(hass): user_input={"address": "54:48:E6:8F:80:A5"}, ) assert result2["type"] == FlowResultType.CREATE_ENTRY - assert result2["title"] == "TEST DEVICE 80A5" + assert result2["title"] == "b-parasite 80A5" assert result2["data"] == {} assert result2["result"].unique_id == "54:48:E6:8F:80:A5" diff --git a/tests/components/bthome/test_sensor.py b/tests/components/bthome/test_sensor.py index 07ccfe2288c..f73d3bf379c 100644 --- a/tests/components/bthome/test_sensor.py +++ b/tests/components/bthome/test_sensor.py @@ -106,6 +106,73 @@ from tests.common import MockConfigEntry }, ], ), + ( + "A4:C1:38:8D:18:B2", + make_advertisement( + "A4:C1:38:8D:18:B2", + b"\x03\x06\x5e\x1f", + ), + None, + [ + { + "sensor_entity": "sensor.test_device_18b2_mass", + "friendly_name": "Test Device 18B2 Mass", + "unit_of_measurement": "kg", + "state_class": "measurement", + "expected_state": "80.3", + }, + ], + ), + ( + "A4:C1:38:8D:18:B2", + make_advertisement( + "A4:C1:38:8D:18:B2", + b"\x03\x07\x3e\x1d", + ), + None, + [ + { + "sensor_entity": "sensor.test_device_18b2_mass", + "friendly_name": "Test Device 18B2 Mass", + "unit_of_measurement": "lb", + "state_class": "measurement", + "expected_state": "74.86", + }, + ], + ), + ( + "A4:C1:38:8D:18:B2", + make_advertisement( + "A4:C1:38:8D:18:B2", + b"\x23\x08\xCA\x06", + ), + None, + [ + { + "sensor_entity": "sensor.test_device_18b2_dew_point", + "friendly_name": "Test Device 18B2 Dew Point", + "unit_of_measurement": "°C", + "state_class": "measurement", + "expected_state": "17.38", + }, + ], + ), + ( + "A4:C1:38:8D:18:B2", + make_advertisement( + "A4:C1:38:8D:18:B2", + b"\x02\x09\x60", + ), + None, + [ + { + "sensor_entity": "sensor.test_device_18b2_count", + "friendly_name": "Test Device 18B2 Count", + "state_class": "measurement", + "expected_state": "96", + }, + ], + ), ( "A4:C1:38:8D:18:B2", make_advertisement( @@ -215,6 +282,23 @@ from tests.common import MockConfigEntry }, ], ), + ( + "A4:C1:38:8D:18:B2", + make_advertisement( + "A4:C1:38:8D:18:B2", + b"\x03\x14\x02\x0c", + ), + None, + [ + { + "sensor_entity": "sensor.test_device_18b2_moisture", + "friendly_name": "Test Device 18B2 Moisture", + "unit_of_measurement": "%", + "state_class": "measurement", + "expected_state": "30.74", + }, + ], + ), ( "54:48:E6:8F:80:A5", make_encrypted_advertisement( @@ -283,9 +367,10 @@ async def test_sensors( sensor = hass.states.get(meas["sensor_entity"]) sensor_attr = sensor.attributes assert sensor.state == meas["expected_state"] - assert sensor_attr[ATTR_FRIENDLY_NAME] == meas["friendly_name"] - assert sensor_attr[ATTR_UNIT_OF_MEASUREMENT] == meas["unit_of_measurement"] + if ATTR_UNIT_OF_MEASUREMENT in sensor_attr: + # Count sensor does not have a unit of measurement + assert sensor_attr[ATTR_UNIT_OF_MEASUREMENT] == meas["unit_of_measurement"] assert sensor_attr[ATTR_STATE_CLASS] == meas["state_class"] assert await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done()