Support polling the MiFlora battery (#76342)
parent
7fc2a73c88
commit
6ad2708946
|
@ -11,8 +11,8 @@ from homeassistant.components.bluetooth import (
|
|||
BluetoothScanningMode,
|
||||
BluetoothServiceInfoBleak,
|
||||
)
|
||||
from homeassistant.components.bluetooth.passive_update_processor import (
|
||||
PassiveBluetoothProcessorCoordinator,
|
||||
from homeassistant.components.bluetooth.active_update_coordinator import (
|
||||
ActiveBluetoothProcessorCoordinator,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
|
@ -56,9 +56,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
kwargs["bindkey"] = bytes.fromhex(bindkey)
|
||||
data = XiaomiBluetoothDeviceData(**kwargs)
|
||||
|
||||
def _needs_poll(
|
||||
service_info: BluetoothServiceInfoBleak, last_poll: float | None
|
||||
) -> bool:
|
||||
return data.poll_needed(service_info, last_poll)
|
||||
|
||||
async def _async_poll(service_info: BluetoothServiceInfoBleak):
|
||||
# BluetoothServiceInfoBleak is defined in HA, otherwise would just pass it
|
||||
# directly to the Xiaomi code
|
||||
return await data.async_poll(service_info.device)
|
||||
|
||||
coordinator = hass.data.setdefault(DOMAIN, {})[
|
||||
entry.entry_id
|
||||
] = PassiveBluetoothProcessorCoordinator(
|
||||
] = ActiveBluetoothProcessorCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
address=address,
|
||||
|
@ -66,6 +76,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
update_method=lambda service_info: process_service_info(
|
||||
hass, entry, data, service_info
|
||||
),
|
||||
needs_poll_method=_needs_poll,
|
||||
poll_method=_async_poll,
|
||||
)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
entry.async_on_unload(
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
"service_data_uuid": "0000fe95-0000-1000-8000-00805f9b34fb"
|
||||
}
|
||||
],
|
||||
"requirements": ["xiaomi-ble==0.6.4"],
|
||||
"requirements": ["xiaomi-ble==0.8.1"],
|
||||
"dependencies": ["bluetooth"],
|
||||
"codeowners": ["@Jc2k", "@Ernst79"],
|
||||
"iot_class": "local_push"
|
||||
|
|
|
@ -2480,7 +2480,7 @@ xbox-webapi==2.0.11
|
|||
xboxapi==2.0.1
|
||||
|
||||
# homeassistant.components.xiaomi_ble
|
||||
xiaomi-ble==0.6.4
|
||||
xiaomi-ble==0.8.1
|
||||
|
||||
# homeassistant.components.knx
|
||||
xknx==0.22.1
|
||||
|
|
|
@ -1678,7 +1678,7 @@ wolf_smartset==0.1.11
|
|||
xbox-webapi==2.0.11
|
||||
|
||||
# homeassistant.components.xiaomi_ble
|
||||
xiaomi-ble==0.6.4
|
||||
xiaomi-ble==0.8.1
|
||||
|
||||
# homeassistant.components.knx
|
||||
xknx==0.22.1
|
||||
|
|
|
@ -1,21 +1,26 @@
|
|||
"""Tests for the SensorPush integration."""
|
||||
|
||||
from bleak.backends.device import BLEDevice
|
||||
from bleak.backends.scanner import AdvertisementData
|
||||
|
||||
from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo
|
||||
from homeassistant.components.bluetooth import BluetoothServiceInfoBleak
|
||||
|
||||
NOT_SENSOR_PUSH_SERVICE_INFO = BluetoothServiceInfo(
|
||||
NOT_SENSOR_PUSH_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||
name="Not it",
|
||||
address="00:00:00:00:00:00",
|
||||
device=BLEDevice("00:00:00:00:00:00", None),
|
||||
rssi=-63,
|
||||
manufacturer_data={3234: b"\x00\x01"},
|
||||
service_data={},
|
||||
service_uuids=[],
|
||||
source="local",
|
||||
advertisement=AdvertisementData(local_name="Not it"),
|
||||
)
|
||||
|
||||
LYWSDCGQ_SERVICE_INFO = BluetoothServiceInfo(
|
||||
LYWSDCGQ_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||
name="LYWSDCGQ",
|
||||
address="58:2D:34:35:93:21",
|
||||
device=BLEDevice("00:00:00:00:00:00", None),
|
||||
rssi=-63,
|
||||
manufacturer_data={},
|
||||
service_data={
|
||||
|
@ -23,11 +28,13 @@ LYWSDCGQ_SERVICE_INFO = BluetoothServiceInfo(
|
|||
},
|
||||
service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"],
|
||||
source="local",
|
||||
advertisement=AdvertisementData(local_name="Not it"),
|
||||
)
|
||||
|
||||
MMC_T201_1_SERVICE_INFO = BluetoothServiceInfo(
|
||||
MMC_T201_1_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||
name="MMC_T201_1",
|
||||
address="00:81:F9:DD:6F:C1",
|
||||
device=BLEDevice("00:00:00:00:00:00", None),
|
||||
rssi=-56,
|
||||
manufacturer_data={},
|
||||
service_data={
|
||||
|
@ -35,11 +42,13 @@ MMC_T201_1_SERVICE_INFO = BluetoothServiceInfo(
|
|||
},
|
||||
service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"],
|
||||
source="local",
|
||||
advertisement=AdvertisementData(local_name="Not it"),
|
||||
)
|
||||
|
||||
JTYJGD03MI_SERVICE_INFO = BluetoothServiceInfo(
|
||||
JTYJGD03MI_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||
name="JTYJGD03MI",
|
||||
address="54:EF:44:E3:9C:BC",
|
||||
device=BLEDevice("00:00:00:00:00:00", None),
|
||||
rssi=-56,
|
||||
manufacturer_data={},
|
||||
service_data={
|
||||
|
@ -47,11 +56,13 @@ JTYJGD03MI_SERVICE_INFO = BluetoothServiceInfo(
|
|||
},
|
||||
service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"],
|
||||
source="local",
|
||||
advertisement=AdvertisementData(local_name="Not it"),
|
||||
)
|
||||
|
||||
YLKG07YL_SERVICE_INFO = BluetoothServiceInfo(
|
||||
YLKG07YL_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||
name="YLKG07YL",
|
||||
address="F8:24:41:C5:98:8B",
|
||||
device=BLEDevice("00:00:00:00:00:00", None),
|
||||
rssi=-56,
|
||||
manufacturer_data={},
|
||||
service_data={
|
||||
|
@ -59,11 +70,13 @@ YLKG07YL_SERVICE_INFO = BluetoothServiceInfo(
|
|||
},
|
||||
service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"],
|
||||
source="local",
|
||||
advertisement=AdvertisementData(local_name="Not it"),
|
||||
)
|
||||
|
||||
MISSING_PAYLOAD_ENCRYPTED = BluetoothServiceInfo(
|
||||
MISSING_PAYLOAD_ENCRYPTED = BluetoothServiceInfoBleak(
|
||||
name="LYWSD02MMC",
|
||||
address="A4:C1:38:56:53:84",
|
||||
device=BLEDevice("00:00:00:00:00:00", None),
|
||||
rssi=-56,
|
||||
manufacturer_data={},
|
||||
service_data={
|
||||
|
@ -71,14 +84,16 @@ MISSING_PAYLOAD_ENCRYPTED = BluetoothServiceInfo(
|
|||
},
|
||||
service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"],
|
||||
source="local",
|
||||
advertisement=AdvertisementData(local_name="Not it"),
|
||||
)
|
||||
|
||||
|
||||
def make_advertisement(address: str, payload: bytes) -> BluetoothServiceInfo:
|
||||
def make_advertisement(address: str, payload: bytes) -> BluetoothServiceInfoBleak:
|
||||
"""Make a dummy advertisement."""
|
||||
return BluetoothServiceInfo(
|
||||
return BluetoothServiceInfoBleak(
|
||||
name="Test Device",
|
||||
address=address,
|
||||
device=BLEDevice(address, None),
|
||||
rssi=-56,
|
||||
manufacturer_data={},
|
||||
service_data={
|
||||
|
@ -86,4 +101,5 @@ def make_advertisement(address: str, payload: bytes) -> BluetoothServiceInfo:
|
|||
},
|
||||
service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"],
|
||||
source="local",
|
||||
advertisement=AdvertisementData(local_name="Test Device"),
|
||||
)
|
||||
|
|
|
@ -1,8 +1,55 @@
|
|||
"""Session fixtures."""
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class MockServices:
|
||||
"""Mock GATTServicesCollection."""
|
||||
|
||||
def get_characteristic(self, key: str) -> str:
|
||||
"""Mock GATTServicesCollection.get_characteristic."""
|
||||
return key
|
||||
|
||||
|
||||
class MockBleakClient:
|
||||
"""Mock BleakClient."""
|
||||
|
||||
services = MockServices()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Mock BleakClient."""
|
||||
pass
|
||||
|
||||
async def __aenter__(self, *args, **kwargs):
|
||||
"""Mock BleakClient.__aenter__."""
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *args, **kwargs):
|
||||
"""Mock BleakClient.__aexit__."""
|
||||
pass
|
||||
|
||||
async def connect(self, *args, **kwargs):
|
||||
"""Mock BleakClient.connect."""
|
||||
pass
|
||||
|
||||
async def disconnect(self, *args, **kwargs):
|
||||
"""Mock BleakClient.disconnect."""
|
||||
pass
|
||||
|
||||
|
||||
class MockBleakClientBattery5(MockBleakClient):
|
||||
"""Mock BleakClient that returns a battery level of 5."""
|
||||
|
||||
async def read_gatt_char(self, *args, **kwargs) -> bytes:
|
||||
"""Mock BleakClient.read_gatt_char."""
|
||||
return b"\x05\x001.2.3"
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_bluetooth(enable_bluetooth):
|
||||
"""Auto mock bluetooth."""
|
||||
|
||||
with mock.patch("xiaomi_ble.parser.BleakClient", MockBleakClientBattery5):
|
||||
yield
|
||||
|
|
|
@ -78,7 +78,7 @@ async def test_xiaomi_formaldeyhde(hass):
|
|||
# obj type is 0x1010, payload len is 0x2 and payload is 0xf400
|
||||
saved_callback(
|
||||
make_advertisement(
|
||||
"C4:7C:8D:6A:3E:7A", b"q \x98\x00iz>j\x8d|\xc4\r\x10\x10\x02\xf4\x00"
|
||||
"C4:7C:8D:6A:3E:7A", b"q \x5d\x01iz>j\x8d|\xc4\r\x10\x10\x02\xf4\x00"
|
||||
),
|
||||
BluetoothChange.ADVERTISEMENT,
|
||||
)
|
||||
|
@ -125,7 +125,7 @@ async def test_xiaomi_consumable(hass):
|
|||
# obj type is 0x1310, payload len is 0x2 and payload is 0x6000
|
||||
saved_callback(
|
||||
make_advertisement(
|
||||
"C4:7C:8D:6A:3E:7A", b"q \x98\x00iz>j\x8d|\xc4\r\x13\x10\x02\x60\x00"
|
||||
"C4:7C:8D:6A:3E:7A", b"q \x5d\x01iz>j\x8d|\xc4\r\x13\x10\x02\x60\x00"
|
||||
),
|
||||
BluetoothChange.ADVERTISEMENT,
|
||||
)
|
||||
|
@ -172,7 +172,7 @@ async def test_xiaomi_battery_voltage(hass):
|
|||
# obj type is 0x0a10, payload len is 0x2 and payload is 0x6400
|
||||
saved_callback(
|
||||
make_advertisement(
|
||||
"C4:7C:8D:6A:3E:7A", b"q \x98\x00iz>j\x8d|\xc4\r\x0a\x10\x02\x64\x00"
|
||||
"C4:7C:8D:6A:3E:7A", b"q \x5d\x01iz>j\x8d|\xc4\r\x0a\x10\x02\x64\x00"
|
||||
),
|
||||
BluetoothChange.ADVERTISEMENT,
|
||||
)
|
||||
|
@ -246,7 +246,7 @@ async def test_xiaomi_HHCCJCY01(hass):
|
|||
BluetoothChange.ADVERTISEMENT,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_all()) == 4
|
||||
assert len(hass.states.async_all()) == 5
|
||||
|
||||
illum_sensor = hass.states.get("sensor.test_device_illuminance")
|
||||
illum_sensor_attr = illum_sensor.attributes
|
||||
|
@ -276,6 +276,13 @@ async def test_xiaomi_HHCCJCY01(hass):
|
|||
assert temp_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "°C"
|
||||
assert temp_sensor_attribtes[ATTR_STATE_CLASS] == "measurement"
|
||||
|
||||
batt_sensor = hass.states.get("sensor.test_device_battery")
|
||||
batt_sensor_attribtes = batt_sensor.attributes
|
||||
assert batt_sensor.state == "5"
|
||||
assert batt_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Test Device Battery"
|
||||
assert batt_sensor_attribtes[ATTR_UNIT_OF_MEASUREMENT] == "%"
|
||||
assert batt_sensor_attribtes[ATTR_STATE_CLASS] == "measurement"
|
||||
|
||||
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
|
Loading…
Reference in New Issue