mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 10:47:10 +00:00
Add unique id to flic buttons (#61496)
* Use bluetooth address as unique id for flic buttons. * Always lower case address for uid and add tests. * Update test to set up component. * Use format_mac(addr) as unique id. * Only patch pyflic objects and use query entity registry for buttons. * Replace ExitStack with patch.multiple, remove assert_setup_component. * Test binary sensor is present in state machine.
This commit is contained in:
parent
09456925e9
commit
ce138dd30e
@ -4,14 +4,7 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from pyflic import (
|
import pyflic
|
||||||
ButtonConnectionChannel,
|
|
||||||
ClickType,
|
|
||||||
ConnectionStatus,
|
|
||||||
FlicClient,
|
|
||||||
ScanWizard,
|
|
||||||
ScanWizardResult,
|
|
||||||
)
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity
|
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity
|
||||||
@ -24,6 +17,7 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.helpers.device_registry import format_mac
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
|
|
||||||
@ -75,7 +69,7 @@ def setup_platform(
|
|||||||
discovery = config.get(CONF_DISCOVERY)
|
discovery = config.get(CONF_DISCOVERY)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client = FlicClient(host, port)
|
client = pyflic.FlicClient(host, port)
|
||||||
except ConnectionRefusedError:
|
except ConnectionRefusedError:
|
||||||
_LOGGER.error("Failed to connect to flic server")
|
_LOGGER.error("Failed to connect to flic server")
|
||||||
return
|
return
|
||||||
@ -105,13 +99,13 @@ def setup_platform(
|
|||||||
|
|
||||||
def start_scanning(config, add_entities, client):
|
def start_scanning(config, add_entities, client):
|
||||||
"""Start a new flic client for scanning and connecting to new buttons."""
|
"""Start a new flic client for scanning and connecting to new buttons."""
|
||||||
scan_wizard = ScanWizard()
|
scan_wizard = pyflic.ScanWizard()
|
||||||
|
|
||||||
def scan_completed_callback(scan_wizard, result, address, name):
|
def scan_completed_callback(scan_wizard, result, address, name):
|
||||||
"""Restart scan wizard to constantly check for new buttons."""
|
"""Restart scan wizard to constantly check for new buttons."""
|
||||||
if result == ScanWizardResult.WizardSuccess:
|
if result == pyflic.ScanWizardResult.WizardSuccess:
|
||||||
_LOGGER.info("Found new button %s", address)
|
_LOGGER.info("Found new button %s", address)
|
||||||
elif result != ScanWizardResult.WizardFailedTimeout:
|
elif result != pyflic.ScanWizardResult.WizardFailedTimeout:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Failed to connect to button %s. Reason: %s", address, result
|
"Failed to connect to button %s. Reason: %s", address, result
|
||||||
)
|
)
|
||||||
@ -139,16 +133,17 @@ class FlicButton(BinarySensorEntity):
|
|||||||
def __init__(self, hass, client, address, timeout, ignored_click_types):
|
def __init__(self, hass, client, address, timeout, ignored_click_types):
|
||||||
"""Initialize the flic button."""
|
"""Initialize the flic button."""
|
||||||
|
|
||||||
|
self._attr_unique_id = format_mac(address)
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
self._address = address
|
self._address = address
|
||||||
self._timeout = timeout
|
self._timeout = timeout
|
||||||
self._is_down = False
|
self._is_down = False
|
||||||
self._ignored_click_types = ignored_click_types or []
|
self._ignored_click_types = ignored_click_types or []
|
||||||
self._hass_click_types = {
|
self._hass_click_types = {
|
||||||
ClickType.ButtonClick: CLICK_TYPE_SINGLE,
|
pyflic.ClickType.ButtonClick: CLICK_TYPE_SINGLE,
|
||||||
ClickType.ButtonSingleClick: CLICK_TYPE_SINGLE,
|
pyflic.ClickType.ButtonSingleClick: CLICK_TYPE_SINGLE,
|
||||||
ClickType.ButtonDoubleClick: CLICK_TYPE_DOUBLE,
|
pyflic.ClickType.ButtonDoubleClick: CLICK_TYPE_DOUBLE,
|
||||||
ClickType.ButtonHold: CLICK_TYPE_HOLD,
|
pyflic.ClickType.ButtonHold: CLICK_TYPE_HOLD,
|
||||||
}
|
}
|
||||||
|
|
||||||
self._channel = self._create_channel()
|
self._channel = self._create_channel()
|
||||||
@ -156,7 +151,7 @@ class FlicButton(BinarySensorEntity):
|
|||||||
|
|
||||||
def _create_channel(self):
|
def _create_channel(self):
|
||||||
"""Create a new connection channel to the button."""
|
"""Create a new connection channel to the button."""
|
||||||
channel = ButtonConnectionChannel(self._address)
|
channel = pyflic.ButtonConnectionChannel(self._address)
|
||||||
channel.on_button_up_or_down = self._on_up_down
|
channel.on_button_up_or_down = self._on_up_down
|
||||||
|
|
||||||
# If all types of clicks should be ignored, skip registering callbacks
|
# If all types of clicks should be ignored, skip registering callbacks
|
||||||
@ -225,7 +220,7 @@ class FlicButton(BinarySensorEntity):
|
|||||||
if was_queued and self._queued_event_check(click_type, time_diff):
|
if was_queued and self._queued_event_check(click_type, time_diff):
|
||||||
return
|
return
|
||||||
|
|
||||||
self._is_down = click_type == ClickType.ButtonDown
|
self._is_down = click_type == pyflic.ClickType.ButtonDown
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
def _on_click(self, channel, click_type, was_queued, time_diff):
|
def _on_click(self, channel, click_type, was_queued, time_diff):
|
||||||
@ -251,7 +246,7 @@ class FlicButton(BinarySensorEntity):
|
|||||||
|
|
||||||
def _connection_status_changed(self, channel, connection_status, disconnect_reason):
|
def _connection_status_changed(self, channel, connection_status, disconnect_reason):
|
||||||
"""Remove device, if button disconnects."""
|
"""Remove device, if button disconnects."""
|
||||||
if connection_status == ConnectionStatus.Disconnected:
|
if connection_status == pyflic.ConnectionStatus.Disconnected:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Button (%s) disconnected. Reason: %s", self.address, disconnect_reason
|
"Button (%s) disconnected. Reason: %s", self.address, disconnect_reason
|
||||||
)
|
)
|
||||||
|
@ -916,6 +916,9 @@ pyfido==2.1.1
|
|||||||
# homeassistant.components.fireservicerota
|
# homeassistant.components.fireservicerota
|
||||||
pyfireservicerota==0.0.43
|
pyfireservicerota==0.0.43
|
||||||
|
|
||||||
|
# homeassistant.components.flic
|
||||||
|
pyflic==2.0.3
|
||||||
|
|
||||||
# homeassistant.components.flume
|
# homeassistant.components.flume
|
||||||
pyflume==0.6.5
|
pyflume==0.6.5
|
||||||
|
|
||||||
|
1
tests/components/flic/__init__.py
Normal file
1
tests/components/flic/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the flic integration."""
|
63
tests/components/flic/test_binary_sensor.py
Normal file
63
tests/components/flic/test_binary_sensor.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
"""Tests for Flic button integration."""
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
|
||||||
|
class _MockFlicClient:
|
||||||
|
def __init__(self, button_addresses):
|
||||||
|
self.addresses = button_addresses
|
||||||
|
self.get_info_callback = None
|
||||||
|
self.scan_wizard = None
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_info(self, callback):
|
||||||
|
self.get_info_callback = callback
|
||||||
|
callback({"bd_addr_of_verified_buttons": self.addresses})
|
||||||
|
|
||||||
|
def handle_events(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_scan_wizard(self, wizard):
|
||||||
|
self.scan_wizard = wizard
|
||||||
|
|
||||||
|
def add_connection_channel(self, channel):
|
||||||
|
self.channel = channel
|
||||||
|
|
||||||
|
|
||||||
|
async def test_button_uid(hass):
|
||||||
|
"""Test UID assignment for Flic buttons."""
|
||||||
|
address_to_name = {
|
||||||
|
"80:e4:da:78:6e:11": "binary_sensor.flic_80e4da786e11",
|
||||||
|
# Uppercase address should not change uid.
|
||||||
|
"80:E4:DA:78:6E:12": "binary_sensor.flic_80e4da786e12",
|
||||||
|
}
|
||||||
|
|
||||||
|
flic_client = _MockFlicClient(tuple(address_to_name))
|
||||||
|
|
||||||
|
with mock.patch.multiple(
|
||||||
|
"pyflic",
|
||||||
|
FlicClient=lambda _, __: flic_client,
|
||||||
|
ButtonConnectionChannel=mock.DEFAULT,
|
||||||
|
ScanWizard=mock.DEFAULT,
|
||||||
|
):
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"binary_sensor",
|
||||||
|
{"binary_sensor": [{"platform": "flic"}]},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
for address, name in address_to_name.items():
|
||||||
|
state = hass.states.get(name)
|
||||||
|
assert state
|
||||||
|
assert state.attributes.get("address") == address
|
||||||
|
|
||||||
|
entry = entity_registry.async_get(name)
|
||||||
|
assert entry
|
||||||
|
assert entry.unique_id == address.lower()
|
Loading…
x
Reference in New Issue
Block a user