mirror of
https://github.com/home-assistant/core.git
synced 2026-04-02 08:26:41 +01:00
Add support for switchbot keypad vision (#160484)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
@@ -127,8 +127,16 @@ PLATFORMS_BY_TYPE = {
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.BUTTON,
|
||||
],
|
||||
SupportedModels.KEYPAD_VISION.value: [Platform.SENSOR, Platform.BINARY_SENSOR],
|
||||
SupportedModels.KEYPAD_VISION_PRO.value: [Platform.SENSOR, Platform.BINARY_SENSOR],
|
||||
SupportedModels.KEYPAD_VISION.value: [
|
||||
Platform.SENSOR,
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.EVENT,
|
||||
],
|
||||
SupportedModels.KEYPAD_VISION_PRO.value: [
|
||||
Platform.SENSOR,
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.EVENT,
|
||||
],
|
||||
}
|
||||
CLASS_BY_DEVICE = {
|
||||
SupportedModels.CEILING_LIGHT.value: switchbot.SwitchbotCeilingLight,
|
||||
|
||||
@@ -85,10 +85,13 @@ BINARY_SENSOR_TYPES: dict[str, SwitchbotBinarySensorEntityDescription] = {
|
||||
),
|
||||
"battery_charging": SwitchbotBinarySensorEntityDescription(
|
||||
key="battery_charging",
|
||||
translation_key="battery_charging",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
device_class=BinarySensorDeviceClass.BATTERY_CHARGING,
|
||||
),
|
||||
"tamper_alarm": SwitchbotBinarySensorEntityDescription(
|
||||
key="tamper_alarm",
|
||||
device_class=BinarySensorDeviceClass.TAMPER,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
||||
63
homeassistant/components/switchbot/event.py
Normal file
63
homeassistant/components/switchbot/event.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""Support for SwitchBot event entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.event import (
|
||||
EventDeviceClass,
|
||||
EventEntity,
|
||||
EventEntityDescription,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import SwitchbotConfigEntry, SwitchbotDataUpdateCoordinator
|
||||
from .entity import SwitchbotEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
EVENT_TYPES = {
|
||||
"doorbell": EventEntityDescription(
|
||||
key="doorbell",
|
||||
device_class=EventDeviceClass.DOORBELL,
|
||||
event_types=["ring"],
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: SwitchbotConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the SwitchBot event platform."""
|
||||
coordinator = config_entry.runtime_data
|
||||
async_add_entities(
|
||||
SwitchbotEventEntity(coordinator, event, description)
|
||||
for event, description in EVENT_TYPES.items()
|
||||
if event in coordinator.device.parsed_data
|
||||
)
|
||||
|
||||
|
||||
class SwitchbotEventEntity(SwitchbotEntity, EventEntity):
|
||||
"""Representation of a SwitchBot event."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: SwitchbotDataUpdateCoordinator,
|
||||
event: str,
|
||||
description: EventEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the SwitchBot event."""
|
||||
super().__init__(coordinator)
|
||||
self._event = event
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{coordinator.base_unique_id}-{event}"
|
||||
self._previous_value = False
|
||||
|
||||
@callback
|
||||
def _async_update_attrs(self) -> None:
|
||||
"""Update the entity attributes."""
|
||||
value = bool(self.parsed_data.get(self._event, False))
|
||||
if value and not self._previous_value:
|
||||
self._trigger_event("ring")
|
||||
self._previous_value = value
|
||||
85
tests/components/switchbot/test_event.py
Normal file
85
tests/components/switchbot/test_event.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""Test the switchbot event entities."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.bluetooth import BluetoothServiceInfoBleak
|
||||
from homeassistant.components.switchbot.const import DOMAIN
|
||||
from homeassistant.const import STATE_UNKNOWN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import KEYPAD_VISION_PRO_INFO
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.components.bluetooth import (
|
||||
generate_advertisement_data,
|
||||
generate_ble_device,
|
||||
inject_bluetooth_service_info,
|
||||
)
|
||||
|
||||
|
||||
def _with_doorbell_event(
|
||||
info: BluetoothServiceInfoBleak,
|
||||
) -> BluetoothServiceInfoBleak:
|
||||
"""Return a BLE service info with the doorbell bit set."""
|
||||
mfr_data = bytearray(info.manufacturer_data[2409])
|
||||
mfr_data[12] |= 0b00001000
|
||||
updated_mfr_data = {2409: bytes(mfr_data)}
|
||||
return BluetoothServiceInfoBleak(
|
||||
name=info.name,
|
||||
manufacturer_data=updated_mfr_data,
|
||||
service_data=info.service_data,
|
||||
service_uuids=info.service_uuids,
|
||||
address=info.address,
|
||||
rssi=info.rssi,
|
||||
source=info.source,
|
||||
advertisement=generate_advertisement_data(
|
||||
local_name=info.name,
|
||||
manufacturer_data=updated_mfr_data,
|
||||
service_data=info.service_data,
|
||||
service_uuids=info.service_uuids,
|
||||
),
|
||||
device=generate_ble_device(info.address, info.name),
|
||||
time=info.time,
|
||||
connectable=info.connectable,
|
||||
tx_power=info.tx_power,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_keypad_vision_pro_doorbell_event(
|
||||
hass: HomeAssistant,
|
||||
mock_entry_encrypted_factory: Callable[[str], MockConfigEntry],
|
||||
) -> None:
|
||||
"""Test keypad vision pro doorbell event entity (encrypted device)."""
|
||||
await async_setup_component(hass, DOMAIN, {})
|
||||
inject_bluetooth_service_info(hass, KEYPAD_VISION_PRO_INFO)
|
||||
|
||||
entry = mock_entry_encrypted_factory(sensor_type="keypad_vision_pro")
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.switchbot.sensor.switchbot.SwitchbotKeypadVision.update",
|
||||
return_value=True,
|
||||
):
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity_id = "event.test_name_doorbell"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
inject_bluetooth_service_info(
|
||||
hass, _with_doorbell_event(KEYPAD_VISION_PRO_INFO)
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state != STATE_UNKNOWN
|
||||
assert state.attributes["event_type"] == "ring"
|
||||
assert state.attributes["event_types"] == ["ring"]
|
||||
@@ -4,6 +4,7 @@ from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.bluetooth import BluetoothServiceInfoBleak
|
||||
from homeassistant.components.sensor import ATTR_STATE_CLASS
|
||||
from homeassistant.components.switchbot.const import (
|
||||
CONF_ENCRYPTION_KEY,
|
||||
@@ -18,6 +19,7 @@ from homeassistant.const import (
|
||||
CONF_NAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_SENSOR_TYPE,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -29,6 +31,8 @@ from . import (
|
||||
EVAPORATIVE_HUMIDIFIER_SERVICE_INFO,
|
||||
HUB3_SERVICE_INFO,
|
||||
HUBMINI_MATTER_SERVICE_INFO,
|
||||
KEYPAD_VISION_INFO,
|
||||
KEYPAD_VISION_PRO_INFO,
|
||||
LEAK_SERVICE_INFO,
|
||||
PLUG_MINI_EU_SERVICE_INFO,
|
||||
PRESENCE_SENSOR_SERVICE_INFO,
|
||||
@@ -843,3 +847,72 @@ async def test_presence_sensor(hass: HomeAssistant) -> None:
|
||||
|
||||
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
@pytest.mark.parametrize(
|
||||
("adv_info", "sensor_type", "charging_state"),
|
||||
[
|
||||
(KEYPAD_VISION_INFO, "keypad_vision", STATE_ON),
|
||||
(KEYPAD_VISION_PRO_INFO, "keypad_vision_pro", STATE_OFF),
|
||||
],
|
||||
)
|
||||
async def test_keypad_vision_sensor(
|
||||
hass: HomeAssistant,
|
||||
adv_info: BluetoothServiceInfoBleak,
|
||||
sensor_type: str,
|
||||
charging_state: str,
|
||||
) -> None:
|
||||
"""Test setting up creates the sensors for Keypad Vision (Pro)."""
|
||||
await async_setup_component(hass, DOMAIN, {})
|
||||
inject_bluetooth_service_info(hass, adv_info)
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_ADDRESS: "AA:BB:CC:DD:EE:FF",
|
||||
CONF_NAME: "test-name",
|
||||
CONF_SENSOR_TYPE: sensor_type,
|
||||
CONF_KEY_ID: "ff",
|
||||
CONF_ENCRYPTION_KEY: "ffffffffffffffffffffffffffffffff",
|
||||
},
|
||||
unique_id="aabbccddeeff",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.switchbot.sensor.switchbot.SwitchbotKeypadVision.update",
|
||||
return_value=True,
|
||||
):
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(hass.states.async_all("sensor")) == 2
|
||||
assert len(hass.states.async_all("binary_sensor")) == 2
|
||||
|
||||
battery_sensor = hass.states.get("sensor.test_name_battery")
|
||||
battery_sensor_attrs = battery_sensor.attributes
|
||||
assert battery_sensor
|
||||
assert battery_sensor_attrs[ATTR_FRIENDLY_NAME] == "test-name Battery"
|
||||
assert battery_sensor_attrs[ATTR_STATE_CLASS] == "measurement"
|
||||
|
||||
rssi_sensor = hass.states.get("sensor.test_name_bluetooth_signal")
|
||||
rssi_sensor_attrs = rssi_sensor.attributes
|
||||
assert rssi_sensor
|
||||
assert rssi_sensor_attrs[ATTR_FRIENDLY_NAME] == "test-name Bluetooth signal"
|
||||
assert rssi_sensor_attrs[ATTR_UNIT_OF_MEASUREMENT] == "dBm"
|
||||
|
||||
tamper_sensor = hass.states.get("binary_sensor.test_name_tamper")
|
||||
tamper_sensor_attrs = tamper_sensor.attributes
|
||||
assert tamper_sensor
|
||||
assert tamper_sensor_attrs[ATTR_FRIENDLY_NAME] == "test-name Tamper"
|
||||
assert tamper_sensor.state == STATE_OFF
|
||||
|
||||
charging_sensor = hass.states.get("binary_sensor.test_name_charging")
|
||||
charging_sensor_attrs = charging_sensor.attributes
|
||||
assert charging_sensor
|
||||
assert charging_sensor_attrs[ATTR_FRIENDLY_NAME] == "test-name Charging"
|
||||
assert charging_sensor.state == charging_state
|
||||
|
||||
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
Reference in New Issue
Block a user