1 """Support to use flic buttons as a binary sensor."""
3 from __future__
import annotations
9 import voluptuous
as vol
12 PLATFORM_SCHEMA
as BINARY_SENSOR_PLATFORM_SCHEMA,
20 EVENT_HOMEASSISTANT_STOP,
28 _LOGGER = logging.getLogger(__name__)
32 CLICK_TYPE_SINGLE =
"single"
33 CLICK_TYPE_DOUBLE =
"double"
34 CLICK_TYPE_HOLD =
"hold"
35 CLICK_TYPES = [CLICK_TYPE_SINGLE, CLICK_TYPE_DOUBLE, CLICK_TYPE_HOLD]
37 CONF_IGNORED_CLICK_TYPES =
"ignored_click_types"
39 DEFAULT_HOST =
"localhost"
42 EVENT_NAME =
"flic_click"
43 EVENT_DATA_NAME =
"button_name"
44 EVENT_DATA_ADDRESS =
"button_address"
45 EVENT_DATA_TYPE =
"click_type"
46 EVENT_DATA_QUEUED_TIME =
"queued_time"
48 PLATFORM_SCHEMA = BINARY_SENSOR_PLATFORM_SCHEMA.extend(
50 vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
51 vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
52 vol.Optional(CONF_DISCOVERY, default=
True): cv.boolean,
53 vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
54 vol.Optional(CONF_IGNORED_CLICK_TYPES): vol.All(
55 cv.ensure_list, [vol.In(CLICK_TYPES)]
64 add_entities: AddEntitiesCallback,
65 discovery_info: DiscoveryInfoType |
None =
None,
67 """Set up the flic platform."""
71 host = config.get(CONF_HOST)
72 port = config.get(CONF_PORT)
73 discovery = config.get(CONF_DISCOVERY)
76 client = pyflic.FlicClient(host, port)
77 except ConnectionRefusedError:
78 _LOGGER.error(
"Failed to connect to flic server")
81 def new_button_callback(address):
82 """Set up newly verified button as device in Home Assistant."""
83 setup_button(hass, config, add_entities, client, address)
85 client.on_new_verified_button = new_button_callback
89 hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
lambda event: client.close())
92 threading.Thread(target=client.handle_events).start()
94 def get_info_callback(items):
95 """Add entities for already verified buttons."""
96 addresses = items[
"bd_addr_of_verified_buttons"]
or []
97 for address
in addresses:
98 setup_button(hass, config, add_entities, client, address)
101 client.get_info(get_info_callback)
105 """Start a new flic client for scanning and connecting to new buttons."""
106 scan_wizard = pyflic.ScanWizard()
108 def scan_completed_callback(scan_wizard, result, address, name):
109 """Restart scan wizard to constantly check for new buttons."""
110 if result == pyflic.ScanWizardResult.WizardSuccess:
111 _LOGGER.debug(
"Found new button %s", address)
112 elif result != pyflic.ScanWizardResult.WizardFailedTimeout:
114 "Failed to connect to button %s. Reason: %s", address, result
120 scan_wizard.on_completed = scan_completed_callback
121 client.add_scan_wizard(scan_wizard)
127 add_entities: AddEntitiesCallback,
131 """Set up a single button device."""
132 timeout: int = config[CONF_TIMEOUT]
133 ignored_click_types: list[str] |
None = config.get(CONF_IGNORED_CLICK_TYPES)
134 button =
FlicButton(hass, client, address, timeout, ignored_click_types)
135 _LOGGER.debug(
"Connected to button %s", address)
141 """Representation of a flic button."""
143 _attr_should_poll =
False
148 client: pyflic.FlicClient,
151 ignored_click_types: list[str] |
None,
153 """Initialize the flic button."""
156 self.
_attr_name_attr_name = f
"flic_{address.replace(':', '')}"
164 pyflic.ClickType.ButtonClick: CLICK_TYPE_SINGLE,
165 pyflic.ClickType.ButtonSingleClick: CLICK_TYPE_SINGLE,
166 pyflic.ClickType.ButtonDoubleClick: CLICK_TYPE_DOUBLE,
167 pyflic.ClickType.ButtonHold: CLICK_TYPE_HOLD,
171 client.add_connection_channel(self.
_channel_channel)
174 """Create a new connection channel to the button."""
175 channel = pyflic.ButtonConnectionChannel(self.
_address_address)
176 channel.on_button_up_or_down = self.
_on_up_down_on_up_down
184 channel.on_button_click_or_hold = self.
_on_click_on_click
187 channel.on_button_single_or_double_click = self.
_on_click_on_click
190 channel.on_button_single_or_double_click_or_hold = self.
_on_click_on_click
195 """Generate a log message and returns true if timeout exceeded."""
196 time_string = f
"{time_diff:d} {'second' if time_diff == 1 else 'seconds'}"
198 if time_diff > self.
_timeout_timeout:
200 "Queued %s dropped for %s. Time in queue was %s",
207 "Queued %s allowed for %s. Time in queue was %s",
214 def _on_up_down(self, channel, click_type, was_queued, time_diff):
215 """Update device state, if event was not queued."""
219 self.
_attr_is_on_attr_is_on = click_type != pyflic.ClickType.ButtonDown
222 def _on_click(self, channel, click_type, was_queued, time_diff):
223 """Fire click event, if event was not queued."""
233 self.
_hass_hass.bus.fire(
236 EVENT_DATA_NAME: self.
namename,
237 EVENT_DATA_ADDRESS: self.
_address_address,
238 EVENT_DATA_QUEUED_TIME: time_diff,
239 EVENT_DATA_TYPE: hass_click_type,
244 """Remove device, if button disconnects."""
245 if connection_status == pyflic.ConnectionStatus.Disconnected:
247 "Button (%s) disconnected. Reason: %s", self.
_address_address, disconnect_reason
None schedule_update_ha_state(self, bool force_refresh=False)
str|UndefinedType|None name(self)
None add_entities(AsusWrtRouter router, AddEntitiesCallback async_add_entities, set[str] tracked)
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)
def start_scanning(config, add_entities, client)
None setup_button(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, client, address)