mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 07:07:28 +00:00
Support polling the MiFlora battery (#76342)
This commit is contained in:
parent
7fc2a73c88
commit
6ad2708946
@ -11,8 +11,8 @@ from homeassistant.components.bluetooth import (
|
|||||||
BluetoothScanningMode,
|
BluetoothScanningMode,
|
||||||
BluetoothServiceInfoBleak,
|
BluetoothServiceInfoBleak,
|
||||||
)
|
)
|
||||||
from homeassistant.components.bluetooth.passive_update_processor import (
|
from homeassistant.components.bluetooth.active_update_coordinator import (
|
||||||
PassiveBluetoothProcessorCoordinator,
|
ActiveBluetoothProcessorCoordinator,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
@ -56,9 +56,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
kwargs["bindkey"] = bytes.fromhex(bindkey)
|
kwargs["bindkey"] = bytes.fromhex(bindkey)
|
||||||
data = XiaomiBluetoothDeviceData(**kwargs)
|
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, {})[
|
coordinator = hass.data.setdefault(DOMAIN, {})[
|
||||||
entry.entry_id
|
entry.entry_id
|
||||||
] = PassiveBluetoothProcessorCoordinator(
|
] = ActiveBluetoothProcessorCoordinator(
|
||||||
hass,
|
hass,
|
||||||
_LOGGER,
|
_LOGGER,
|
||||||
address=address,
|
address=address,
|
||||||
@ -66,6 +76,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
update_method=lambda service_info: process_service_info(
|
update_method=lambda service_info: process_service_info(
|
||||||
hass, entry, data, 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)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
entry.async_on_unload(
|
entry.async_on_unload(
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"service_data_uuid": "0000fe95-0000-1000-8000-00805f9b34fb"
|
"service_data_uuid": "0000fe95-0000-1000-8000-00805f9b34fb"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"requirements": ["xiaomi-ble==0.6.4"],
|
"requirements": ["xiaomi-ble==0.8.1"],
|
||||||
"dependencies": ["bluetooth"],
|
"dependencies": ["bluetooth"],
|
||||||
"codeowners": ["@Jc2k", "@Ernst79"],
|
"codeowners": ["@Jc2k", "@Ernst79"],
|
||||||
"iot_class": "local_push"
|
"iot_class": "local_push"
|
||||||
|
@ -2480,7 +2480,7 @@ xbox-webapi==2.0.11
|
|||||||
xboxapi==2.0.1
|
xboxapi==2.0.1
|
||||||
|
|
||||||
# homeassistant.components.xiaomi_ble
|
# homeassistant.components.xiaomi_ble
|
||||||
xiaomi-ble==0.6.4
|
xiaomi-ble==0.8.1
|
||||||
|
|
||||||
# homeassistant.components.knx
|
# homeassistant.components.knx
|
||||||
xknx==0.22.1
|
xknx==0.22.1
|
||||||
|
@ -1678,7 +1678,7 @@ wolf_smartset==0.1.11
|
|||||||
xbox-webapi==2.0.11
|
xbox-webapi==2.0.11
|
||||||
|
|
||||||
# homeassistant.components.xiaomi_ble
|
# homeassistant.components.xiaomi_ble
|
||||||
xiaomi-ble==0.6.4
|
xiaomi-ble==0.8.1
|
||||||
|
|
||||||
# homeassistant.components.knx
|
# homeassistant.components.knx
|
||||||
xknx==0.22.1
|
xknx==0.22.1
|
||||||
|
@ -1,21 +1,26 @@
|
|||||||
"""Tests for the SensorPush integration."""
|
"""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",
|
name="Not it",
|
||||||
address="00:00:00:00:00:00",
|
address="00:00:00:00:00:00",
|
||||||
|
device=BLEDevice("00:00:00:00:00:00", None),
|
||||||
rssi=-63,
|
rssi=-63,
|
||||||
manufacturer_data={3234: b"\x00\x01"},
|
manufacturer_data={3234: b"\x00\x01"},
|
||||||
service_data={},
|
service_data={},
|
||||||
service_uuids=[],
|
service_uuids=[],
|
||||||
source="local",
|
source="local",
|
||||||
|
advertisement=AdvertisementData(local_name="Not it"),
|
||||||
)
|
)
|
||||||
|
|
||||||
LYWSDCGQ_SERVICE_INFO = BluetoothServiceInfo(
|
LYWSDCGQ_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||||
name="LYWSDCGQ",
|
name="LYWSDCGQ",
|
||||||
address="58:2D:34:35:93:21",
|
address="58:2D:34:35:93:21",
|
||||||
|
device=BLEDevice("00:00:00:00:00:00", None),
|
||||||
rssi=-63,
|
rssi=-63,
|
||||||
manufacturer_data={},
|
manufacturer_data={},
|
||||||
service_data={
|
service_data={
|
||||||
@ -23,11 +28,13 @@ LYWSDCGQ_SERVICE_INFO = BluetoothServiceInfo(
|
|||||||
},
|
},
|
||||||
service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"],
|
service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"],
|
||||||
source="local",
|
source="local",
|
||||||
|
advertisement=AdvertisementData(local_name="Not it"),
|
||||||
)
|
)
|
||||||
|
|
||||||
MMC_T201_1_SERVICE_INFO = BluetoothServiceInfo(
|
MMC_T201_1_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||||
name="MMC_T201_1",
|
name="MMC_T201_1",
|
||||||
address="00:81:F9:DD:6F:C1",
|
address="00:81:F9:DD:6F:C1",
|
||||||
|
device=BLEDevice("00:00:00:00:00:00", None),
|
||||||
rssi=-56,
|
rssi=-56,
|
||||||
manufacturer_data={},
|
manufacturer_data={},
|
||||||
service_data={
|
service_data={
|
||||||
@ -35,11 +42,13 @@ MMC_T201_1_SERVICE_INFO = BluetoothServiceInfo(
|
|||||||
},
|
},
|
||||||
service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"],
|
service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"],
|
||||||
source="local",
|
source="local",
|
||||||
|
advertisement=AdvertisementData(local_name="Not it"),
|
||||||
)
|
)
|
||||||
|
|
||||||
JTYJGD03MI_SERVICE_INFO = BluetoothServiceInfo(
|
JTYJGD03MI_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||||
name="JTYJGD03MI",
|
name="JTYJGD03MI",
|
||||||
address="54:EF:44:E3:9C:BC",
|
address="54:EF:44:E3:9C:BC",
|
||||||
|
device=BLEDevice("00:00:00:00:00:00", None),
|
||||||
rssi=-56,
|
rssi=-56,
|
||||||
manufacturer_data={},
|
manufacturer_data={},
|
||||||
service_data={
|
service_data={
|
||||||
@ -47,11 +56,13 @@ JTYJGD03MI_SERVICE_INFO = BluetoothServiceInfo(
|
|||||||
},
|
},
|
||||||
service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"],
|
service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"],
|
||||||
source="local",
|
source="local",
|
||||||
|
advertisement=AdvertisementData(local_name="Not it"),
|
||||||
)
|
)
|
||||||
|
|
||||||
YLKG07YL_SERVICE_INFO = BluetoothServiceInfo(
|
YLKG07YL_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||||
name="YLKG07YL",
|
name="YLKG07YL",
|
||||||
address="F8:24:41:C5:98:8B",
|
address="F8:24:41:C5:98:8B",
|
||||||
|
device=BLEDevice("00:00:00:00:00:00", None),
|
||||||
rssi=-56,
|
rssi=-56,
|
||||||
manufacturer_data={},
|
manufacturer_data={},
|
||||||
service_data={
|
service_data={
|
||||||
@ -59,11 +70,13 @@ YLKG07YL_SERVICE_INFO = BluetoothServiceInfo(
|
|||||||
},
|
},
|
||||||
service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"],
|
service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"],
|
||||||
source="local",
|
source="local",
|
||||||
|
advertisement=AdvertisementData(local_name="Not it"),
|
||||||
)
|
)
|
||||||
|
|
||||||
MISSING_PAYLOAD_ENCRYPTED = BluetoothServiceInfo(
|
MISSING_PAYLOAD_ENCRYPTED = BluetoothServiceInfoBleak(
|
||||||
name="LYWSD02MMC",
|
name="LYWSD02MMC",
|
||||||
address="A4:C1:38:56:53:84",
|
address="A4:C1:38:56:53:84",
|
||||||
|
device=BLEDevice("00:00:00:00:00:00", None),
|
||||||
rssi=-56,
|
rssi=-56,
|
||||||
manufacturer_data={},
|
manufacturer_data={},
|
||||||
service_data={
|
service_data={
|
||||||
@ -71,14 +84,16 @@ MISSING_PAYLOAD_ENCRYPTED = BluetoothServiceInfo(
|
|||||||
},
|
},
|
||||||
service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"],
|
service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"],
|
||||||
source="local",
|
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."""
|
"""Make a dummy advertisement."""
|
||||||
return BluetoothServiceInfo(
|
return BluetoothServiceInfoBleak(
|
||||||
name="Test Device",
|
name="Test Device",
|
||||||
address=address,
|
address=address,
|
||||||
|
device=BLEDevice(address, None),
|
||||||
rssi=-56,
|
rssi=-56,
|
||||||
manufacturer_data={},
|
manufacturer_data={},
|
||||||
service_data={
|
service_data={
|
||||||
@ -86,4 +101,5 @@ def make_advertisement(address: str, payload: bytes) -> BluetoothServiceInfo:
|
|||||||
},
|
},
|
||||||
service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"],
|
service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"],
|
||||||
source="local",
|
source="local",
|
||||||
|
advertisement=AdvertisementData(local_name="Test Device"),
|
||||||
)
|
)
|
||||||
|
@ -1,8 +1,55 @@
|
|||||||
"""Session fixtures."""
|
"""Session fixtures."""
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
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)
|
@pytest.fixture(autouse=True)
|
||||||
def mock_bluetooth(enable_bluetooth):
|
def mock_bluetooth(enable_bluetooth):
|
||||||
"""Auto mock 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
|
# obj type is 0x1010, payload len is 0x2 and payload is 0xf400
|
||||||
saved_callback(
|
saved_callback(
|
||||||
make_advertisement(
|
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,
|
BluetoothChange.ADVERTISEMENT,
|
||||||
)
|
)
|
||||||
@ -125,7 +125,7 @@ async def test_xiaomi_consumable(hass):
|
|||||||
# obj type is 0x1310, payload len is 0x2 and payload is 0x6000
|
# obj type is 0x1310, payload len is 0x2 and payload is 0x6000
|
||||||
saved_callback(
|
saved_callback(
|
||||||
make_advertisement(
|
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,
|
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
|
# obj type is 0x0a10, payload len is 0x2 and payload is 0x6400
|
||||||
saved_callback(
|
saved_callback(
|
||||||
make_advertisement(
|
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,
|
BluetoothChange.ADVERTISEMENT,
|
||||||
)
|
)
|
||||||
@ -246,7 +246,7 @@ async def test_xiaomi_HHCCJCY01(hass):
|
|||||||
BluetoothChange.ADVERTISEMENT,
|
BluetoothChange.ADVERTISEMENT,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
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 = hass.states.get("sensor.test_device_illuminance")
|
||||||
illum_sensor_attr = illum_sensor.attributes
|
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_UNIT_OF_MEASUREMENT] == "°C"
|
||||||
assert temp_sensor_attribtes[ATTR_STATE_CLASS] == "measurement"
|
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)
|
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user