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:
Tom Hennigan 2022-01-05 16:51:01 +00:00 committed by GitHub
parent 09456925e9
commit ce138dd30e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 81 additions and 19 deletions

View File

@ -4,14 +4,7 @@ from __future__ import annotations
import logging
import threading
from pyflic import (
ButtonConnectionChannel,
ClickType,
ConnectionStatus,
FlicClient,
ScanWizard,
ScanWizardResult,
)
import pyflic
import voluptuous as vol
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity
@ -24,6 +17,7 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant
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.typing import ConfigType, DiscoveryInfoType
@ -75,7 +69,7 @@ def setup_platform(
discovery = config.get(CONF_DISCOVERY)
try:
client = FlicClient(host, port)
client = pyflic.FlicClient(host, port)
except ConnectionRefusedError:
_LOGGER.error("Failed to connect to flic server")
return
@ -105,13 +99,13 @@ def setup_platform(
def start_scanning(config, add_entities, client):
"""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):
"""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)
elif result != ScanWizardResult.WizardFailedTimeout:
elif result != pyflic.ScanWizardResult.WizardFailedTimeout:
_LOGGER.warning(
"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):
"""Initialize the flic button."""
self._attr_unique_id = format_mac(address)
self._hass = hass
self._address = address
self._timeout = timeout
self._is_down = False
self._ignored_click_types = ignored_click_types or []
self._hass_click_types = {
ClickType.ButtonClick: CLICK_TYPE_SINGLE,
ClickType.ButtonSingleClick: CLICK_TYPE_SINGLE,
ClickType.ButtonDoubleClick: CLICK_TYPE_DOUBLE,
ClickType.ButtonHold: CLICK_TYPE_HOLD,
pyflic.ClickType.ButtonClick: CLICK_TYPE_SINGLE,
pyflic.ClickType.ButtonSingleClick: CLICK_TYPE_SINGLE,
pyflic.ClickType.ButtonDoubleClick: CLICK_TYPE_DOUBLE,
pyflic.ClickType.ButtonHold: CLICK_TYPE_HOLD,
}
self._channel = self._create_channel()
@ -156,7 +151,7 @@ class FlicButton(BinarySensorEntity):
def _create_channel(self):
"""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
# 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):
return
self._is_down = click_type == ClickType.ButtonDown
self._is_down = click_type == pyflic.ClickType.ButtonDown
self.schedule_update_ha_state()
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):
"""Remove device, if button disconnects."""
if connection_status == ConnectionStatus.Disconnected:
if connection_status == pyflic.ConnectionStatus.Disconnected:
_LOGGER.warning(
"Button (%s) disconnected. Reason: %s", self.address, disconnect_reason
)

View File

@ -916,6 +916,9 @@ pyfido==2.1.1
# homeassistant.components.fireservicerota
pyfireservicerota==0.0.43
# homeassistant.components.flic
pyflic==2.0.3
# homeassistant.components.flume
pyflume==0.6.5

View File

@ -0,0 +1 @@
"""Tests for the flic integration."""

View 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()