Add support for BTHome V2 to bthome (#81811)
* Add BTHome v2 support * Add new sensor types * Add new sensor typespull/81868/head
parent
4b4bf54994
commit
b72876d369
|
@ -11,9 +11,13 @@
|
||||||
{
|
{
|
||||||
"connectable": false,
|
"connectable": false,
|
||||||
"service_data_uuid": "0000181e-0000-1000-8000-00805f9b34fb"
|
"service_data_uuid": "0000181e-0000-1000-8000-00805f9b34fb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"connectable": false,
|
||||||
|
"service_data_uuid": "0000fcd2-0000-1000-8000-00805f9b34fb"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"requirements": ["bthome-ble==1.2.2"],
|
"requirements": ["bthome-ble==2.2.1"],
|
||||||
"dependencies": ["bluetooth"],
|
"dependencies": ["bluetooth"],
|
||||||
"codeowners": ["@Ernst79"],
|
"codeowners": ["@Ernst79"],
|
||||||
"iot_class": "local_push"
|
"iot_class": "local_push"
|
||||||
|
|
|
@ -21,16 +21,20 @@ from homeassistant.components.sensor import (
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
CONCENTRATION_PARTS_PER_MILLION,
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
|
DEGREE,
|
||||||
|
ELECTRIC_CURRENT_AMPERE,
|
||||||
ELECTRIC_POTENTIAL_VOLT,
|
ELECTRIC_POTENTIAL_VOLT,
|
||||||
ENERGY_KILO_WATT_HOUR,
|
|
||||||
LIGHT_LUX,
|
LIGHT_LUX,
|
||||||
MASS_KILOGRAMS,
|
|
||||||
MASS_POUNDS,
|
|
||||||
PERCENTAGE,
|
PERCENTAGE,
|
||||||
POWER_WATT,
|
|
||||||
PRESSURE_MBAR,
|
|
||||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||||
TEMP_CELSIUS,
|
TIME_SECONDS,
|
||||||
|
UnitOfEnergy,
|
||||||
|
UnitOfLength,
|
||||||
|
UnitOfMass,
|
||||||
|
UnitOfPower,
|
||||||
|
UnitOfPressure,
|
||||||
|
UnitOfSpeed,
|
||||||
|
UnitOfTemperature,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity import EntityCategory
|
from homeassistant.helpers.entity import EntityCategory
|
||||||
|
@ -43,7 +47,7 @@ SENSOR_DESCRIPTIONS = {
|
||||||
(BTHomeSensorDeviceClass.TEMPERATURE, Units.TEMP_CELSIUS): SensorEntityDescription(
|
(BTHomeSensorDeviceClass.TEMPERATURE, Units.TEMP_CELSIUS): SensorEntityDescription(
|
||||||
key=f"{BTHomeSensorDeviceClass.TEMPERATURE}_{Units.TEMP_CELSIUS}",
|
key=f"{BTHomeSensorDeviceClass.TEMPERATURE}_{Units.TEMP_CELSIUS}",
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
native_unit_of_measurement=TEMP_CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
(BTHomeSensorDeviceClass.HUMIDITY, Units.PERCENTAGE): SensorEntityDescription(
|
(BTHomeSensorDeviceClass.HUMIDITY, Units.PERCENTAGE): SensorEntityDescription(
|
||||||
|
@ -61,7 +65,7 @@ SENSOR_DESCRIPTIONS = {
|
||||||
(BTHomeSensorDeviceClass.PRESSURE, Units.PRESSURE_MBAR): SensorEntityDescription(
|
(BTHomeSensorDeviceClass.PRESSURE, Units.PRESSURE_MBAR): SensorEntityDescription(
|
||||||
key=f"{BTHomeSensorDeviceClass.PRESSURE}_{Units.PRESSURE_MBAR}",
|
key=f"{BTHomeSensorDeviceClass.PRESSURE}_{Units.PRESSURE_MBAR}",
|
||||||
device_class=SensorDeviceClass.PRESSURE,
|
device_class=SensorDeviceClass.PRESSURE,
|
||||||
native_unit_of_measurement=PRESSURE_MBAR,
|
native_unit_of_measurement=UnitOfPressure.MBAR,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
(BTHomeSensorDeviceClass.BATTERY, Units.PERCENTAGE): SensorEntityDescription(
|
(BTHomeSensorDeviceClass.BATTERY, Units.PERCENTAGE): SensorEntityDescription(
|
||||||
|
@ -86,13 +90,13 @@ SENSOR_DESCRIPTIONS = {
|
||||||
): SensorEntityDescription(
|
): SensorEntityDescription(
|
||||||
key=f"{BTHomeSensorDeviceClass.ENERGY}_{Units.ENERGY_KILO_WATT_HOUR}",
|
key=f"{BTHomeSensorDeviceClass.ENERGY}_{Units.ENERGY_KILO_WATT_HOUR}",
|
||||||
device_class=SensorDeviceClass.ENERGY,
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
),
|
),
|
||||||
(BTHomeSensorDeviceClass.POWER, Units.POWER_WATT): SensorEntityDescription(
|
(BTHomeSensorDeviceClass.POWER, Units.POWER_WATT): SensorEntityDescription(
|
||||||
key=f"{BTHomeSensorDeviceClass.POWER}_{Units.POWER_WATT}",
|
key=f"{BTHomeSensorDeviceClass.POWER}_{Units.POWER_WATT}",
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
native_unit_of_measurement=POWER_WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -146,14 +150,14 @@ SENSOR_DESCRIPTIONS = {
|
||||||
(BTHomeSensorDeviceClass.MASS, Units.MASS_KILOGRAMS): SensorEntityDescription(
|
(BTHomeSensorDeviceClass.MASS, Units.MASS_KILOGRAMS): SensorEntityDescription(
|
||||||
key=f"{BTHomeSensorDeviceClass.MASS}_{Units.MASS_KILOGRAMS}",
|
key=f"{BTHomeSensorDeviceClass.MASS}_{Units.MASS_KILOGRAMS}",
|
||||||
device_class=SensorDeviceClass.WEIGHT,
|
device_class=SensorDeviceClass.WEIGHT,
|
||||||
native_unit_of_measurement=MASS_KILOGRAMS,
|
native_unit_of_measurement=UnitOfMass.KILOGRAMS,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
# Used for mass sensor with lb unit
|
# Used for mass sensor with lb unit
|
||||||
(BTHomeSensorDeviceClass.MASS, Units.MASS_POUNDS): SensorEntityDescription(
|
(BTHomeSensorDeviceClass.MASS, Units.MASS_POUNDS): SensorEntityDescription(
|
||||||
key=f"{BTHomeSensorDeviceClass.MASS}_{Units.MASS_POUNDS}",
|
key=f"{BTHomeSensorDeviceClass.MASS}_{Units.MASS_POUNDS}",
|
||||||
device_class=SensorDeviceClass.WEIGHT,
|
device_class=SensorDeviceClass.WEIGHT,
|
||||||
native_unit_of_measurement=MASS_POUNDS,
|
native_unit_of_measurement=UnitOfMass.POUNDS,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
# Used for moisture sensor
|
# Used for moisture sensor
|
||||||
|
@ -167,7 +171,7 @@ SENSOR_DESCRIPTIONS = {
|
||||||
(BTHomeSensorDeviceClass.DEW_POINT, Units.TEMP_CELSIUS): SensorEntityDescription(
|
(BTHomeSensorDeviceClass.DEW_POINT, Units.TEMP_CELSIUS): SensorEntityDescription(
|
||||||
key=f"{BTHomeSensorDeviceClass.DEW_POINT}_{Units.TEMP_CELSIUS}",
|
key=f"{BTHomeSensorDeviceClass.DEW_POINT}_{Units.TEMP_CELSIUS}",
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
native_unit_of_measurement=TEMP_CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
# Used for count sensor
|
# Used for count sensor
|
||||||
|
@ -176,6 +180,64 @@ SENSOR_DESCRIPTIONS = {
|
||||||
device_class=None,
|
device_class=None,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
|
# Used for rotation sensor
|
||||||
|
(BTHomeSensorDeviceClass.ROTATION, Units.DEGREE): SensorEntityDescription(
|
||||||
|
key=f"{BTHomeSensorDeviceClass.ROTATION}_{Units.DEGREE}",
|
||||||
|
device_class=None,
|
||||||
|
native_unit_of_measurement=DEGREE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
# Used for distance sensor in mm
|
||||||
|
(
|
||||||
|
BTHomeSensorDeviceClass.DISTANCE,
|
||||||
|
Units.LENGTH_MILLIMETERS,
|
||||||
|
): SensorEntityDescription(
|
||||||
|
key=f"{BTHomeSensorDeviceClass.DISTANCE}_{Units.LENGTH_MILLIMETERS}",
|
||||||
|
device_class=SensorDeviceClass.DISTANCE,
|
||||||
|
native_unit_of_measurement=UnitOfLength.MILLIMETERS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
# Used for distance sensor in m
|
||||||
|
(BTHomeSensorDeviceClass.DISTANCE, Units.LENGTH_METERS): SensorEntityDescription(
|
||||||
|
key=f"{BTHomeSensorDeviceClass.DISTANCE}_{Units.LENGTH_METERS}",
|
||||||
|
device_class=SensorDeviceClass.DISTANCE,
|
||||||
|
native_unit_of_measurement=UnitOfLength.METERS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
# Used for duration sensor
|
||||||
|
(BTHomeSensorDeviceClass.DURATION, Units.TIME_SECONDS): SensorEntityDescription(
|
||||||
|
key=f"{BTHomeSensorDeviceClass.DURATION}_{Units.TIME_SECONDS}",
|
||||||
|
device_class=SensorDeviceClass.DURATION,
|
||||||
|
native_unit_of_measurement=TIME_SECONDS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
# Used for current sensor
|
||||||
|
(
|
||||||
|
BTHomeSensorDeviceClass.CURRENT,
|
||||||
|
Units.ELECTRIC_CURRENT_AMPERE,
|
||||||
|
): SensorEntityDescription(
|
||||||
|
key=f"{BTHomeSensorDeviceClass.CURRENT}_{Units.ELECTRIC_CURRENT_AMPERE}",
|
||||||
|
device_class=SensorDeviceClass.CURRENT,
|
||||||
|
native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
# Used for speed sensor
|
||||||
|
(
|
||||||
|
BTHomeSensorDeviceClass.SPEED,
|
||||||
|
Units.SPEED_METERS_PER_SECOND,
|
||||||
|
): SensorEntityDescription(
|
||||||
|
key=f"{BTHomeSensorDeviceClass.SPEED}_{Units.SPEED_METERS_PER_SECOND}",
|
||||||
|
device_class=SensorDeviceClass.SPEED,
|
||||||
|
native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
# Used for UV index sensor
|
||||||
|
(BTHomeSensorDeviceClass.UV_INDEX, None,): SensorEntityDescription(
|
||||||
|
key=f"{BTHomeSensorDeviceClass.UV_INDEX}",
|
||||||
|
device_class=None,
|
||||||
|
native_unit_of_measurement=None,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,11 @@ BLUETOOTH: list[dict[str, bool | str | int | list[int]]] = [
|
||||||
"connectable": False,
|
"connectable": False,
|
||||||
"service_data_uuid": "0000181e-0000-1000-8000-00805f9b34fb",
|
"service_data_uuid": "0000181e-0000-1000-8000-00805f9b34fb",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"domain": "bthome",
|
||||||
|
"connectable": False,
|
||||||
|
"service_data_uuid": "0000fcd2-0000-1000-8000-00805f9b34fb",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"domain": "fjaraskupan",
|
"domain": "fjaraskupan",
|
||||||
"connectable": False,
|
"connectable": False,
|
||||||
|
|
|
@ -482,7 +482,7 @@ brunt==1.2.0
|
||||||
bt_proximity==0.2.1
|
bt_proximity==0.2.1
|
||||||
|
|
||||||
# homeassistant.components.bthome
|
# homeassistant.components.bthome
|
||||||
bthome-ble==1.2.2
|
bthome-ble==2.2.1
|
||||||
|
|
||||||
# homeassistant.components.bt_home_hub_5
|
# homeassistant.components.bt_home_hub_5
|
||||||
bthomehub5-devicelist==0.1.1
|
bthomehub5-devicelist==0.1.1
|
||||||
|
|
|
@ -386,7 +386,7 @@ brother==2.0.0
|
||||||
brunt==1.2.0
|
brunt==1.2.0
|
||||||
|
|
||||||
# homeassistant.components.bthome
|
# homeassistant.components.bthome
|
||||||
bthome-ble==1.2.2
|
bthome-ble==2.2.1
|
||||||
|
|
||||||
# homeassistant.components.buienradar
|
# homeassistant.components.buienradar
|
||||||
buienradar==1.0.5
|
buienradar==1.0.5
|
||||||
|
|
|
@ -85,7 +85,7 @@ NOT_BTHOME_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def make_advertisement(address: str, payload: bytes) -> BluetoothServiceInfoBleak:
|
def make_bthome_v1_adv(address: str, payload: bytes) -> BluetoothServiceInfoBleak:
|
||||||
"""Make a dummy advertisement."""
|
"""Make a dummy advertisement."""
|
||||||
return BluetoothServiceInfoBleak(
|
return BluetoothServiceInfoBleak(
|
||||||
name="Test Device",
|
name="Test Device",
|
||||||
|
@ -104,7 +104,7 @@ def make_advertisement(address: str, payload: bytes) -> BluetoothServiceInfoBlea
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def make_encrypted_advertisement(
|
def make_encrypted_bthome_v1_adv(
|
||||||
address: str, payload: bytes
|
address: str, payload: bytes
|
||||||
) -> BluetoothServiceInfoBleak:
|
) -> BluetoothServiceInfoBleak:
|
||||||
"""Make a dummy encrypted advertisement."""
|
"""Make a dummy encrypted advertisement."""
|
||||||
|
@ -123,3 +123,22 @@ def make_encrypted_advertisement(
|
||||||
time=0,
|
time=0,
|
||||||
connectable=False,
|
connectable=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def make_bthome_v2_adv(address: str, payload: bytes) -> BluetoothServiceInfoBleak:
|
||||||
|
"""Make a dummy advertisement."""
|
||||||
|
return BluetoothServiceInfoBleak(
|
||||||
|
name="Test Device",
|
||||||
|
address=address,
|
||||||
|
device=BLEDevice(address, None),
|
||||||
|
rssi=-56,
|
||||||
|
manufacturer_data={},
|
||||||
|
service_data={
|
||||||
|
"0000fcd2-0000-1000-8000-00805f9b34fb": payload,
|
||||||
|
},
|
||||||
|
service_uuids=["0000fcd2-0000-1000-8000-00805f9b34fb"],
|
||||||
|
source="local",
|
||||||
|
advertisement=generate_advertisement_data(local_name="Test Device"),
|
||||||
|
time=0,
|
||||||
|
connectable=False,
|
||||||
|
)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import pytest
|
||||||
from homeassistant.components.bthome.const import DOMAIN
|
from homeassistant.components.bthome.const import DOMAIN
|
||||||
from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_OFF, STATE_ON
|
from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_OFF, STATE_ON
|
||||||
|
|
||||||
from . import make_advertisement
|
from . import make_bthome_v1_adv, make_bthome_v2_adv
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
from tests.components.bluetooth import inject_bluetooth_service_info
|
from tests.components.bluetooth import inject_bluetooth_service_info
|
||||||
|
@ -20,7 +20,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
make_advertisement(
|
make_bthome_v1_adv(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
b"\x02\x10\x01",
|
b"\x02\x10\x01",
|
||||||
),
|
),
|
||||||
|
@ -35,7 +35,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
make_advertisement(
|
make_bthome_v1_adv(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
b"\x02\x11\x00",
|
b"\x02\x11\x00",
|
||||||
),
|
),
|
||||||
|
@ -50,7 +50,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
make_advertisement(
|
make_bthome_v1_adv(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
b"\x02\x0F\x01",
|
b"\x02\x0F\x01",
|
||||||
),
|
),
|
||||||
|
@ -65,14 +65,100 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_binary_sensors(
|
async def test_v1_binary_sensors(
|
||||||
hass,
|
hass,
|
||||||
mac_address,
|
mac_address,
|
||||||
advertisement,
|
advertisement,
|
||||||
bind_key,
|
bind_key,
|
||||||
result,
|
result,
|
||||||
):
|
):
|
||||||
"""Test the different binary sensors."""
|
"""Test the different BTHome v1 binary sensors."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id=mac_address,
|
||||||
|
data={"bindkey": bind_key},
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(hass.states.async_all()) == 0
|
||||||
|
|
||||||
|
inject_bluetooth_service_info(
|
||||||
|
hass,
|
||||||
|
advertisement,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(hass.states.async_all()) == len(result)
|
||||||
|
for meas in result:
|
||||||
|
binary_sensor = hass.states.get(meas["binary_sensor_entity"])
|
||||||
|
binary_sensor_attr = binary_sensor.attributes
|
||||||
|
assert binary_sensor.state == meas["expected_state"]
|
||||||
|
assert binary_sensor_attr[ATTR_FRIENDLY_NAME] == meas["friendly_name"]
|
||||||
|
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"mac_address, advertisement, bind_key, result",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x10\x01",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"binary_sensor_entity": "binary_sensor.test_device_18b2_power",
|
||||||
|
"friendly_name": "Test Device 18B2 Power",
|
||||||
|
"expected_state": STATE_ON,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x11\x00",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"binary_sensor_entity": "binary_sensor.test_device_18b2_opening",
|
||||||
|
"friendly_name": "Test Device 18B2 Opening",
|
||||||
|
"expected_state": STATE_OFF,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x0F\x01",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"binary_sensor_entity": "binary_sensor.test_device_18b2_generic",
|
||||||
|
"friendly_name": "Test Device 18B2 Generic",
|
||||||
|
"expected_state": STATE_ON,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_v2_binary_sensors(
|
||||||
|
hass,
|
||||||
|
mac_address,
|
||||||
|
advertisement,
|
||||||
|
bind_key,
|
||||||
|
result,
|
||||||
|
):
|
||||||
|
"""Test the different BTHome v2 binary sensors."""
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
unique_id=mac_address,
|
unique_id=mac_address,
|
||||||
|
|
|
@ -1,24 +1,29 @@
|
||||||
"""Test the BTHome sensors."""
|
"""Test the BTHome sensors."""
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.bthome.const import DOMAIN
|
from homeassistant.components.bthome.const import DOMAIN
|
||||||
from homeassistant.components.sensor import ATTR_STATE_CLASS
|
from homeassistant.components.sensor import ATTR_STATE_CLASS
|
||||||
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
|
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT
|
||||||
|
|
||||||
from . import make_advertisement, make_encrypted_advertisement
|
from . import make_bthome_v1_adv, make_bthome_v2_adv, make_encrypted_bthome_v1_adv
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
from tests.components.bluetooth import inject_bluetooth_service_info
|
from tests.components.bluetooth import inject_bluetooth_service_info
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# Tests for BTHome v1
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"mac_address, advertisement, bind_key, result",
|
"mac_address, advertisement, bind_key, result",
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
make_advertisement(
|
make_bthome_v1_adv(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
b"#\x02\xca\t\x03\x03\xbf\x13",
|
b"#\x02\xca\t\x03\x03\xbf\x13",
|
||||||
),
|
),
|
||||||
|
@ -42,7 +47,7 @@ from tests.components.bluetooth import inject_bluetooth_service_info
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
make_advertisement(
|
make_bthome_v1_adv(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
b"\x02\x00\xa8#\x02]\t\x03\x03\xb7\x18\x02\x01]",
|
b"\x02\x00\xa8#\x02]\t\x03\x03\xb7\x18\x02\x01]",
|
||||||
),
|
),
|
||||||
|
@ -73,7 +78,7 @@ from tests.components.bluetooth import inject_bluetooth_service_info
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
make_advertisement(
|
make_bthome_v1_adv(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
b"\x02\x00\x0c\x04\x04\x13\x8a\x01",
|
b"\x02\x00\x0c\x04\x04\x13\x8a\x01",
|
||||||
),
|
),
|
||||||
|
@ -90,7 +95,7 @@ from tests.components.bluetooth import inject_bluetooth_service_info
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"AA:BB:CC:DD:EE:FF",
|
"AA:BB:CC:DD:EE:FF",
|
||||||
make_advertisement(
|
make_bthome_v1_adv(
|
||||||
"AA:BB:CC:DD:EE:FF",
|
"AA:BB:CC:DD:EE:FF",
|
||||||
b"\x04\x05\x13\x8a\x14",
|
b"\x04\x05\x13\x8a\x14",
|
||||||
),
|
),
|
||||||
|
@ -107,7 +112,7 @@ from tests.components.bluetooth import inject_bluetooth_service_info
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
make_advertisement(
|
make_bthome_v1_adv(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
b"\x03\x06\x5e\x1f",
|
b"\x03\x06\x5e\x1f",
|
||||||
),
|
),
|
||||||
|
@ -124,7 +129,7 @@ from tests.components.bluetooth import inject_bluetooth_service_info
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
make_advertisement(
|
make_bthome_v1_adv(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
b"\x03\x07\x3e\x1d",
|
b"\x03\x07\x3e\x1d",
|
||||||
),
|
),
|
||||||
|
@ -141,7 +146,7 @@ from tests.components.bluetooth import inject_bluetooth_service_info
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
make_advertisement(
|
make_bthome_v1_adv(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
b"\x23\x08\xCA\x06",
|
b"\x23\x08\xCA\x06",
|
||||||
),
|
),
|
||||||
|
@ -158,7 +163,7 @@ from tests.components.bluetooth import inject_bluetooth_service_info
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
make_advertisement(
|
make_bthome_v1_adv(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
b"\x02\x09\x60",
|
b"\x02\x09\x60",
|
||||||
),
|
),
|
||||||
|
@ -174,7 +179,7 @@ from tests.components.bluetooth import inject_bluetooth_service_info
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
make_advertisement(
|
make_bthome_v1_adv(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
b"\x04\n\x13\x8a\x14",
|
b"\x04\n\x13\x8a\x14",
|
||||||
),
|
),
|
||||||
|
@ -191,7 +196,7 @@ from tests.components.bluetooth import inject_bluetooth_service_info
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
make_advertisement(
|
make_bthome_v1_adv(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
b"\x04\x0b\x02\x1b\x00",
|
b"\x04\x0b\x02\x1b\x00",
|
||||||
),
|
),
|
||||||
|
@ -208,7 +213,7 @@ from tests.components.bluetooth import inject_bluetooth_service_info
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
make_advertisement(
|
make_bthome_v1_adv(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
b"\x03\x0c\x02\x0c",
|
b"\x03\x0c\x02\x0c",
|
||||||
),
|
),
|
||||||
|
@ -225,7 +230,7 @@ from tests.components.bluetooth import inject_bluetooth_service_info
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
make_advertisement(
|
make_bthome_v1_adv(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
b"\x03\r\x12\x0c\x03\x0e\x02\x1c",
|
b"\x03\r\x12\x0c\x03\x0e\x02\x1c",
|
||||||
),
|
),
|
||||||
|
@ -249,7 +254,7 @@ from tests.components.bluetooth import inject_bluetooth_service_info
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
make_advertisement(
|
make_bthome_v1_adv(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
b"\x03\x12\xe2\x04",
|
b"\x03\x12\xe2\x04",
|
||||||
),
|
),
|
||||||
|
@ -266,7 +271,7 @@ from tests.components.bluetooth import inject_bluetooth_service_info
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
make_advertisement(
|
make_bthome_v1_adv(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
b"\x03\x133\x01",
|
b"\x03\x133\x01",
|
||||||
),
|
),
|
||||||
|
@ -283,7 +288,7 @@ from tests.components.bluetooth import inject_bluetooth_service_info
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
make_advertisement(
|
make_bthome_v1_adv(
|
||||||
"A4:C1:38:8D:18:B2",
|
"A4:C1:38:8D:18:B2",
|
||||||
b"\x03\x14\x02\x0c",
|
b"\x03\x14\x02\x0c",
|
||||||
),
|
),
|
||||||
|
@ -300,7 +305,7 @@ from tests.components.bluetooth import inject_bluetooth_service_info
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"54:48:E6:8F:80:A5",
|
"54:48:E6:8F:80:A5",
|
||||||
make_encrypted_advertisement(
|
make_encrypted_bthome_v1_adv(
|
||||||
"54:48:E6:8F:80:A5",
|
"54:48:E6:8F:80:A5",
|
||||||
b'\xfb\xa45\xe4\xd3\xc3\x12\xfb\x00\x11"3W\xd9\n\x99',
|
b'\xfb\xa45\xe4\xd3\xc3\x12\xfb\x00\x11"3W\xd9\n\x99',
|
||||||
),
|
),
|
||||||
|
@ -324,14 +329,14 @@ from tests.components.bluetooth import inject_bluetooth_service_info
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_sensors(
|
async def test_v1_sensors(
|
||||||
hass,
|
hass,
|
||||||
mac_address,
|
mac_address,
|
||||||
advertisement,
|
advertisement,
|
||||||
bind_key,
|
bind_key,
|
||||||
result,
|
result,
|
||||||
):
|
):
|
||||||
"""Test the different measurement sensors."""
|
"""Test the different BTHome V1 sensors."""
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
unique_id=mac_address,
|
unique_id=mac_address,
|
||||||
|
@ -357,7 +362,572 @@ async def test_sensors(
|
||||||
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:
|
if ATTR_UNIT_OF_MEASUREMENT in sensor_attr:
|
||||||
# Count sensor does not have a unit of measurement
|
# Some sensors don't 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()
|
||||||
|
|
||||||
|
|
||||||
|
# Tests for BTHome V2
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"mac_address, advertisement, bind_key, result",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x02\xca\x09\x03\xbf\x13",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_temperature",
|
||||||
|
"friendly_name": "Test Device 18B2 Temperature",
|
||||||
|
"unit_of_measurement": "°C",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "25.06",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_humidity",
|
||||||
|
"friendly_name": "Test Device 18B2 Humidity",
|
||||||
|
"unit_of_measurement": "%",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "50.55",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x01\x5d\x02\x5d\x09\x03\xb7\x18",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_temperature",
|
||||||
|
"friendly_name": "Test Device 18B2 Temperature",
|
||||||
|
"unit_of_measurement": "°C",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "23.97",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_humidity",
|
||||||
|
"friendly_name": "Test Device 18B2 Humidity",
|
||||||
|
"unit_of_measurement": "%",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "63.27",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_battery",
|
||||||
|
"friendly_name": "Test Device 18B2 Battery",
|
||||||
|
"unit_of_measurement": "%",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "93",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x04\x13\x8a\x01",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_pressure",
|
||||||
|
"friendly_name": "Test Device 18B2 Pressure",
|
||||||
|
"unit_of_measurement": "mbar",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "1008.83",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"AA:BB:CC:DD:EE:FF",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"AA:BB:CC:DD:EE:FF",
|
||||||
|
b"\x40\x05\x13\x8a\x14",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_eeff_illuminance",
|
||||||
|
"friendly_name": "Test Device EEFF Illuminance",
|
||||||
|
"unit_of_measurement": "lx",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "13460.67",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\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_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\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_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\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_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\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_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x0a\x13\x8a\x14",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_energy",
|
||||||
|
"friendly_name": "Test Device 18B2 Energy",
|
||||||
|
"unit_of_measurement": "kWh",
|
||||||
|
"state_class": "total_increasing",
|
||||||
|
"expected_state": "1346.067",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x0b\x02\x1b\x00",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_power",
|
||||||
|
"friendly_name": "Test Device 18B2 Power",
|
||||||
|
"unit_of_measurement": "W",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "69.14",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x0c\x02\x0c",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_voltage",
|
||||||
|
"friendly_name": "Test Device 18B2 Voltage",
|
||||||
|
"unit_of_measurement": "V",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "3.074",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x0d\x12\x0c\x0e\x02\x1c",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_pm10",
|
||||||
|
"friendly_name": "Test Device 18B2 Pm10",
|
||||||
|
"unit_of_measurement": "µg/m³",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "7170",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_pm25",
|
||||||
|
"friendly_name": "Test Device 18B2 Pm25",
|
||||||
|
"unit_of_measurement": "µg/m³",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "3090",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x12\xe2\x04",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_carbon_dioxide",
|
||||||
|
"friendly_name": "Test Device 18B2 Carbon Dioxide",
|
||||||
|
"unit_of_measurement": "ppm",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "1250",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x133\x01",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_volatile_organic_compounds",
|
||||||
|
"friendly_name": "Test Device 18B2 Volatile Organic Compounds",
|
||||||
|
"unit_of_measurement": "µg/m³",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "307",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\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",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x3F\x02\x0c",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_rotation",
|
||||||
|
"friendly_name": "Test Device 18B2 Rotation",
|
||||||
|
"unit_of_measurement": "°",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "307.4",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x40\x0C\x00",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_distance",
|
||||||
|
"friendly_name": "Test Device 18B2 Distance",
|
||||||
|
"unit_of_measurement": "mm",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "12",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x41\x4E\x00",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_distance",
|
||||||
|
"friendly_name": "Test Device 18B2 Distance",
|
||||||
|
"unit_of_measurement": "m",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "7.8",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x42\x4E\x34\x00",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_duration",
|
||||||
|
"friendly_name": "Test Device 18B2 Duration",
|
||||||
|
"unit_of_measurement": "s",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "13.39",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x43\x4E\x34",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_current",
|
||||||
|
"friendly_name": "Test Device 18B2 Current",
|
||||||
|
"unit_of_measurement": "A",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "13.39",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x44\x4E\x34",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_speed",
|
||||||
|
"friendly_name": "Test Device 18B2 Speed",
|
||||||
|
"unit_of_measurement": "m/s",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "133.9",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x45\x11\x01",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_temperature",
|
||||||
|
"friendly_name": "Test Device 18B2 Temperature",
|
||||||
|
"unit_of_measurement": "°C",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "27.3",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x46\x32",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_uv_index",
|
||||||
|
"friendly_name": "Test Device 18B2 Uv Index",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "5.0",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x02\xca\x09\x02\xcf\x09",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_temperature_1",
|
||||||
|
"friendly_name": "Test Device 18B2 Temperature 1",
|
||||||
|
"unit_of_measurement": "°C",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "25.06",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_temperature_2",
|
||||||
|
"friendly_name": "Test Device 18B2 Temperature 2",
|
||||||
|
"unit_of_measurement": "°C",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "25.11",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"A4:C1:38:8D:18:B2",
|
||||||
|
b"\x40\x02\xca\x09\x02\xcf\x09\x02\xcf\x08\x03\xb7\x18\x03\xb7\x17\x01\x5d",
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_temperature_1",
|
||||||
|
"friendly_name": "Test Device 18B2 Temperature 1",
|
||||||
|
"unit_of_measurement": "°C",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "25.06",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_temperature_2",
|
||||||
|
"friendly_name": "Test Device 18B2 Temperature 2",
|
||||||
|
"unit_of_measurement": "°C",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "25.11",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_temperature_3",
|
||||||
|
"friendly_name": "Test Device 18B2 Temperature 3",
|
||||||
|
"unit_of_measurement": "°C",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "22.55",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_humidity_1",
|
||||||
|
"friendly_name": "Test Device 18B2 Humidity 1",
|
||||||
|
"unit_of_measurement": "%",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "63.27",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_humidity_2",
|
||||||
|
"friendly_name": "Test Device 18B2 Humidity 2",
|
||||||
|
"unit_of_measurement": "%",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "60.71",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_18b2_battery",
|
||||||
|
"friendly_name": "Test Device 18B2 Battery",
|
||||||
|
"unit_of_measurement": "%",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "93",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"54:48:E6:8F:80:A5",
|
||||||
|
make_bthome_v2_adv(
|
||||||
|
"54:48:E6:8F:80:A5",
|
||||||
|
b"\x41\xa4\x72\x66\xc9\x5f\x73\x00\x11\x22\x33\xb7\xce\xd8\xe5",
|
||||||
|
),
|
||||||
|
"231d39c1d7cc1ab1aee224cd096db932",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_80a5_temperature",
|
||||||
|
"friendly_name": "Test Device 80A5 Temperature",
|
||||||
|
"unit_of_measurement": "°C",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "25.06",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sensor_entity": "sensor.test_device_80a5_humidity",
|
||||||
|
"friendly_name": "Test Device 80A5 Humidity",
|
||||||
|
"unit_of_measurement": "%",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"expected_state": "50.55",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_v2_sensors(
|
||||||
|
hass,
|
||||||
|
mac_address,
|
||||||
|
advertisement,
|
||||||
|
bind_key,
|
||||||
|
result,
|
||||||
|
):
|
||||||
|
"""Test the different BTHome V2 sensors."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id=mac_address,
|
||||||
|
data={"bindkey": bind_key},
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(hass.states.async_all()) == 0
|
||||||
|
|
||||||
|
inject_bluetooth_service_info(
|
||||||
|
hass,
|
||||||
|
advertisement,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(hass.states.async_all()) == len(result)
|
||||||
|
|
||||||
|
for meas in result:
|
||||||
|
_LOGGER.error(meas)
|
||||||
|
sensor = hass.states.get(meas["sensor_entity"])
|
||||||
|
_LOGGER.error(hass.states)
|
||||||
|
sensor_attr = sensor.attributes
|
||||||
|
assert sensor.state == meas["expected_state"]
|
||||||
|
assert sensor_attr[ATTR_FRIENDLY_NAME] == meas["friendly_name"]
|
||||||
|
if ATTR_UNIT_OF_MEASUREMENT in sensor_attr:
|
||||||
|
# Some sensors don't 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)
|
||||||
|
|
Loading…
Reference in New Issue