Home Assistant Unofficial Reference 2024.12.1
listeners.py
Go to the documentation of this file.
1 """Listeners for updating data in the Crownstone integration.
2 
3 For data updates, Cloud Push is used in form of an SSE server that sends out events.
4 For fast device switching Local Push is used in form of a USB dongle that hooks into a BLE mesh.
5 """
6 
7 from __future__ import annotations
8 
9 from functools import partial
10 from typing import TYPE_CHECKING, cast
11 
12 from crownstone_cloud.exceptions import CrownstoneNotFoundError
13 from crownstone_core.packets.serviceDataParsers.containers.AdvExternalCrownstoneState import (
14  AdvExternalCrownstoneState,
15 )
16 from crownstone_core.packets.serviceDataParsers.containers.elements.AdvTypes import (
17  AdvType,
18 )
19 from crownstone_core.protocol.SwitchState import SwitchState
20 from crownstone_sse.const import (
21  EVENT_ABILITY_CHANGE,
22  EVENT_ABILITY_CHANGE_DIMMING,
23  EVENT_SWITCH_STATE_UPDATE,
24 )
25 from crownstone_sse.events import AbilityChangeEvent, SwitchStateUpdateEvent
26 from crownstone_uart import UartEventBus, UartTopics
27 from crownstone_uart.topics.SystemTopics import SystemTopics
28 
29 from homeassistant.core import callback
31  async_dispatcher_connect,
32  async_dispatcher_send,
33  dispatcher_send,
34 )
35 
36 from .const import (
37  DOMAIN,
38  SIG_CROWNSTONE_STATE_UPDATE,
39  SIG_UART_STATE_CHANGE,
40  SSE_LISTENERS,
41  UART_LISTENERS,
42 )
43 
44 if TYPE_CHECKING:
45  from .entry_manager import CrownstoneEntryManager
46 
47 
48 @callback
50  manager: CrownstoneEntryManager, switch_event: SwitchStateUpdateEvent
51 ) -> None:
52  """Update the state of a Crownstone when switched externally."""
53  try:
54  updated_crownstone = manager.cloud.get_crownstone_by_id(switch_event.cloud_id)
55  except CrownstoneNotFoundError:
56  return
57 
58  # only update on change.
59  if updated_crownstone.state != switch_event.switch_state:
60  updated_crownstone.state = switch_event.switch_state
61  async_dispatcher_send(manager.hass, SIG_CROWNSTONE_STATE_UPDATE)
62 
63 
64 @callback
66  manager: CrownstoneEntryManager, ability_event: AbilityChangeEvent
67 ) -> None:
68  """Update the ability information of a Crownstone."""
69  try:
70  updated_crownstone = manager.cloud.get_crownstone_by_id(ability_event.cloud_id)
71  except CrownstoneNotFoundError:
72  return
73 
74  ability_type = ability_event.ability_type
75  ability_enabled = ability_event.ability_enabled
76  # only update on a change in state
77  if updated_crownstone.abilities[ability_type].is_enabled == ability_enabled:
78  return
79 
80  # write the change to the crownstone entity.
81  updated_crownstone.abilities[ability_type].is_enabled = ability_enabled
82 
83  if ability_event.sub_type == EVENT_ABILITY_CHANGE_DIMMING:
84  # reload the config entry because dimming is part of supported features
85  manager.hass.async_create_task(
86  manager.hass.config_entries.async_reload(manager.config_entry.entry_id)
87  )
88  else:
89  async_dispatcher_send(manager.hass, SIG_CROWNSTONE_STATE_UPDATE)
90 
91 
92 def update_uart_state(manager: CrownstoneEntryManager, _: bool | None) -> None:
93  """Update the uart ready state for entities that use USB."""
94  # update availability of power usage entities.
95  dispatcher_send(manager.hass, SIG_UART_STATE_CHANGE)
96 
97 
99  manager: CrownstoneEntryManager, data: AdvExternalCrownstoneState
100 ) -> None:
101  """Update the state of a Crownstone when switched externally."""
102  if data.type != AdvType.EXTERNAL_STATE:
103  return
104  try:
105  updated_crownstone = manager.cloud.get_crownstone_by_uid(
106  data.crownstoneId, manager.usb_sphere_id
107  )
108  except CrownstoneNotFoundError:
109  return
110 
111  if data.switchState is None:
112  return
113  # update on change
114  updated_state = cast(SwitchState, data.switchState)
115  if updated_crownstone.state != updated_state.intensity:
116  updated_crownstone.state = updated_state.intensity
117 
118  dispatcher_send(manager.hass, SIG_CROWNSTONE_STATE_UPDATE)
119 
120 
121 def setup_sse_listeners(manager: CrownstoneEntryManager) -> None:
122  """Set up SSE listeners."""
123  # save unsub function for when entry removed
124  manager.listeners[SSE_LISTENERS] = [
126  manager.hass,
127  f"{DOMAIN}_{EVENT_SWITCH_STATE_UPDATE}",
128  partial(async_update_crwn_state_sse, manager),
129  ),
131  manager.hass,
132  f"{DOMAIN}_{EVENT_ABILITY_CHANGE}",
133  partial(async_update_crwn_ability, manager),
134  ),
135  ]
136 
137 
138 def setup_uart_listeners(manager: CrownstoneEntryManager) -> None:
139  """Set up UART listeners."""
140  # save subscription id to unsub
141  manager.listeners[UART_LISTENERS] = [
142  UartEventBus.subscribe(
143  SystemTopics.connectionEstablished,
144  partial(update_uart_state, manager),
145  ),
146  UartEventBus.subscribe(
147  SystemTopics.connectionClosed,
148  partial(update_uart_state, manager),
149  ),
150  UartEventBus.subscribe(
151  UartTopics.newDataAvailable,
152  partial(update_crwn_state_uart, manager),
153  ),
154  ]
None async_update_crwn_state_sse(CrownstoneEntryManager manager, SwitchStateUpdateEvent switch_event)
Definition: listeners.py:51
None async_update_crwn_ability(CrownstoneEntryManager manager, AbilityChangeEvent ability_event)
Definition: listeners.py:67
None update_uart_state(CrownstoneEntryManager manager, bool|None _)
Definition: listeners.py:92
None setup_sse_listeners(CrownstoneEntryManager manager)
Definition: listeners.py:121
None setup_uart_listeners(CrownstoneEntryManager manager)
Definition: listeners.py:138
None update_crwn_state_uart(CrownstoneEntryManager manager, AdvExternalCrownstoneState data)
Definition: listeners.py:100
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103
None dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:137
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193