Add new sensors to BThome (#77561)

This commit is contained in:
Ernst Klamer 2022-08-30 23:03:41 +02:00 committed by GitHub
parent 7c5a5f86ee
commit f43f440739
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 134 additions and 21 deletions

View file

@ -13,7 +13,7 @@
"service_data_uuid": "0000181e-0000-1000-8000-00805f9b34fb" "service_data_uuid": "0000181e-0000-1000-8000-00805f9b34fb"
} }
], ],
"requirements": ["bthome-ble==0.4.0"], "requirements": ["bthome-ble==0.5.2"],
"dependencies": ["bluetooth"], "dependencies": ["bluetooth"],
"codeowners": ["@Ernst79"], "codeowners": ["@Ernst79"],
"iot_class": "local_push" "iot_class": "local_push"

View file

@ -25,6 +25,7 @@ from homeassistant.const import (
ENERGY_KILO_WATT_HOUR, ENERGY_KILO_WATT_HOUR,
LIGHT_LUX, LIGHT_LUX,
MASS_KILOGRAMS, MASS_KILOGRAMS,
MASS_POUNDS,
PERCENTAGE, PERCENTAGE,
POWER_WATT, POWER_WATT,
PRESSURE_MBAR, PRESSURE_MBAR,
@ -132,13 +133,41 @@ SENSOR_DESCRIPTIONS = {
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
# Used for e.g. weight sensor # Used for mass sensor with kg unit
(None, Units.MASS_KILOGRAMS): SensorEntityDescription( (None, Units.MASS_KILOGRAMS): SensorEntityDescription(
key=str(Units.MASS_KILOGRAMS), key=f"{DeviceClass.MASS}_{Units.MASS_KILOGRAMS}",
device_class=None, device_class=None,
native_unit_of_measurement=MASS_KILOGRAMS, native_unit_of_measurement=MASS_KILOGRAMS,
state_class=SensorStateClass.MEASUREMENT, 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) (description.device_class, description.native_unit_of_measurement)
] ]
for device_key, description in sensor_update.entity_descriptions.items() for device_key, description in sensor_update.entity_descriptions.items()
if description.native_unit_of_measurement
}, },
entity_data={ entity_data={
device_key_to_bluetooth_entity_key(device_key): sensor_values.native_value device_key_to_bluetooth_entity_key(device_key): sensor_values.native_value

View file

@ -458,7 +458,7 @@ bsblan==0.5.0
bt_proximity==0.2.1 bt_proximity==0.2.1
# homeassistant.components.bthome # homeassistant.components.bthome
bthome-ble==0.4.0 bthome-ble==0.5.2
# homeassistant.components.bt_home_hub_5 # homeassistant.components.bt_home_hub_5
bthomehub5-devicelist==0.1.1 bthomehub5-devicelist==0.1.1

View file

@ -359,7 +359,7 @@ brunt==1.2.0
bsblan==0.5.0 bsblan==0.5.0
# homeassistant.components.bthome # homeassistant.components.bthome
bthome-ble==0.4.0 bthome-ble==0.5.2
# homeassistant.components.buienradar # homeassistant.components.buienradar
buienradar==1.0.5 buienradar==1.0.5

View file

@ -37,18 +37,18 @@ TEMP_HUMI_ENCRYPTED_SERVICE_INFO = BluetoothServiceInfoBleak(
connectable=False, connectable=False,
) )
PM_SERVICE_INFO = BluetoothServiceInfoBleak( PRST_SERVICE_INFO = BluetoothServiceInfoBleak(
name="TEST DEVICE 8F80A5", name="prst 8F80A5",
address="54:48:E6:8F:80:A5", address="54:48:E6:8F:80:A5",
device=BLEDevice("54:48:E6:8F:80:A5", None), device=BLEDevice("54:48:E6:8F:80:A5", None),
rssi=-63, rssi=-63,
manufacturer_data={}, manufacturer_data={},
service_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"], service_uuids=["0000181c-0000-1000-8000-00805f9b34fb"],
source="local", source="local",
advertisement=AdvertisementData(local_name="Not it"), advertisement=AdvertisementData(local_name="prst"),
time=0, time=0,
connectable=False, connectable=False,
) )

View file

@ -11,7 +11,7 @@ from homeassistant.data_entry_flow import FlowResultType
from . import ( from . import (
NOT_BTHOME_SERVICE_INFO, NOT_BTHOME_SERVICE_INFO,
PM_SERVICE_INFO, PRST_SERVICE_INFO,
TEMP_HUMI_ENCRYPTED_SERVICE_INFO, TEMP_HUMI_ENCRYPTED_SERVICE_INFO,
TEMP_HUMI_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.""" """Test setup from service info cache with devices found."""
with patch( with patch(
"homeassistant.components.bthome.config_flow.async_discovered_service_info", "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( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
@ -199,7 +199,7 @@ async def test_async_step_user_with_found_devices(hass):
user_input={"address": "54:48:E6:8F:80:A5"}, user_input={"address": "54:48:E6:8F:80:A5"},
) )
assert result2["type"] == FlowResultType.CREATE_ENTRY assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "TEST DEVICE 80A5" assert result2["title"] == "b-parasite 80A5"
assert result2["data"] == {} assert result2["data"] == {}
assert result2["result"].unique_id == "54:48:E6:8F:80:A5" 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( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_BLUETOOTH}, context={"source": config_entries.SOURCE_BLUETOOTH},
data=PM_SERVICE_INFO, data=PRST_SERVICE_INFO,
) )
assert result["type"] == FlowResultType.ABORT assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "already_configured" 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( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_BLUETOOTH}, context={"source": config_entries.SOURCE_BLUETOOTH},
data=PM_SERVICE_INFO, data=PRST_SERVICE_INFO,
) )
assert result["type"] == FlowResultType.FORM assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "bluetooth_confirm" 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( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_BLUETOOTH}, context={"source": config_entries.SOURCE_BLUETOOTH},
data=PM_SERVICE_INFO, data=PRST_SERVICE_INFO,
) )
assert result["type"] == FlowResultType.ABORT assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "already_in_progress" 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( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_BLUETOOTH}, context={"source": config_entries.SOURCE_BLUETOOTH},
data=PM_SERVICE_INFO, data=PRST_SERVICE_INFO,
) )
assert result["type"] == FlowResultType.FORM assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "bluetooth_confirm" assert result["step_id"] == "bluetooth_confirm"
with patch( with patch(
"homeassistant.components.bthome.config_flow.async_discovered_service_info", "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( result = await hass.config_entries.flow.async_init(
DOMAIN, 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"}, user_input={"address": "54:48:E6:8F:80:A5"},
) )
assert result2["type"] == FlowResultType.CREATE_ENTRY assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "TEST DEVICE 80A5" assert result2["title"] == "b-parasite 80A5"
assert result2["data"] == {} assert result2["data"] == {}
assert result2["result"].unique_id == "54:48:E6:8F:80:A5" assert result2["result"].unique_id == "54:48:E6:8F:80:A5"

View file

@ -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", "A4:C1:38:8D:18:B2",
make_advertisement( 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", "54:48:E6:8F:80:A5",
make_encrypted_advertisement( make_encrypted_advertisement(
@ -283,8 +367,9 @@ async def test_sensors(
sensor = hass.states.get(meas["sensor_entity"]) sensor = hass.states.get(meas["sensor_entity"])
sensor_attr = sensor.attributes sensor_attr = sensor.attributes
assert sensor.state == meas["expected_state"] assert sensor.state == meas["expected_state"]
assert sensor_attr[ATTR_FRIENDLY_NAME] == meas["friendly_name"] assert sensor_attr[ATTR_FRIENDLY_NAME] == meas["friendly_name"]
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_UNIT_OF_MEASUREMENT] == meas["unit_of_measurement"]
assert sensor_attr[ATTR_STATE_CLASS] == meas["state_class"] assert sensor_attr[ATTR_STATE_CLASS] == meas["state_class"]
assert await hass.config_entries.async_unload(entry.entry_id) assert await hass.config_entries.async_unload(entry.entry_id)