Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for LCN devices."""
2 
3 from __future__ import annotations
4 
5 from functools import partial
6 import logging
7 
8 import pypck
9 from pypck.connection import PchkConnectionManager
10 
11 from homeassistant.config_entries import ConfigEntry
12 from homeassistant.const import (
13  CONF_DEVICE_ID,
14  CONF_DOMAIN,
15  CONF_ENTITIES,
16  CONF_IP_ADDRESS,
17  CONF_PASSWORD,
18  CONF_PORT,
19  CONF_USERNAME,
20  Platform,
21 )
22 from homeassistant.core import HomeAssistant
23 from homeassistant.helpers import config_validation as cv, device_registry as dr
24 from homeassistant.helpers.typing import ConfigType
25 
26 from .const import (
27  ADD_ENTITIES_CALLBACKS,
28  CONF_ACKNOWLEDGE,
29  CONF_DIM_MODE,
30  CONF_DOMAIN_DATA,
31  CONF_SK_NUM_TRIES,
32  CONF_TRANSITION,
33  CONNECTION,
34  DOMAIN,
35  PLATFORMS,
36 )
37 from .helpers import (
38  AddressType,
39  InputType,
40  async_update_config_entry,
41  generate_unique_id,
42  register_lcn_address_devices,
43  register_lcn_host_device,
44 )
45 from .services import register_services
46 from .websocket import register_panel_and_ws_api
47 
48 _LOGGER = logging.getLogger(__name__)
49 
50 CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
51 
52 
53 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
54  """Set up the LCN component."""
55  hass.data.setdefault(DOMAIN, {})
56 
57  await register_services(hass)
58  await register_panel_and_ws_api(hass)
59 
60  return True
61 
62 
63 async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
64  """Set up a connection to PCHK host from a config entry."""
65  if config_entry.entry_id in hass.data[DOMAIN]:
66  return False
67 
68  settings = {
69  "SK_NUM_TRIES": config_entry.data[CONF_SK_NUM_TRIES],
70  "DIM_MODE": pypck.lcn_defs.OutputPortDimMode[config_entry.data[CONF_DIM_MODE]],
71  "ACKNOWLEDGE": config_entry.data[CONF_ACKNOWLEDGE],
72  }
73 
74  # connect to PCHK
75  lcn_connection = PchkConnectionManager(
76  config_entry.data[CONF_IP_ADDRESS],
77  config_entry.data[CONF_PORT],
78  config_entry.data[CONF_USERNAME],
79  config_entry.data[CONF_PASSWORD],
80  settings=settings,
81  connection_id=config_entry.entry_id,
82  )
83  try:
84  # establish connection to PCHK server
85  await lcn_connection.async_connect(timeout=15)
86  except pypck.connection.PchkAuthenticationError:
87  _LOGGER.warning('Authentication on PCHK "%s" failed', config_entry.title)
88  return False
89  except pypck.connection.PchkLicenseError:
90  _LOGGER.warning(
91  (
92  'Maximum number of connections on PCHK "%s" was '
93  "reached. An additional license key is required"
94  ),
95  config_entry.title,
96  )
97  return False
98  except TimeoutError:
99  _LOGGER.warning('Connection to PCHK "%s" failed', config_entry.title)
100  return False
101 
102  _LOGGER.debug('LCN connected to "%s"', config_entry.title)
103  hass.data[DOMAIN][config_entry.entry_id] = {
104  CONNECTION: lcn_connection,
105  ADD_ENTITIES_CALLBACKS: {},
106  }
107  # Update config_entry with LCN device serials
108  await async_update_config_entry(hass, config_entry)
109 
110  # register/update devices for host, modules and groups in device registry
111  register_lcn_host_device(hass, config_entry)
112  register_lcn_address_devices(hass, config_entry)
113 
114  # forward config_entry to components
115  await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
116 
117  # register for LCN bus messages
118  device_registry = dr.async_get(hass)
119  input_received = partial(
120  async_host_input_received, hass, config_entry, device_registry
121  )
122  lcn_connection.register_for_inputs(input_received)
123 
124  return True
125 
126 
127 async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
128  """Migrate old entry."""
129  _LOGGER.debug(
130  "Migrating configuration from version %s.%s",
131  config_entry.version,
132  config_entry.minor_version,
133  )
134 
135  new_data = {**config_entry.data}
136 
137  if config_entry.version == 1:
138  # update to 1.2 (add acknowledge flag)
139  if config_entry.minor_version < 2:
140  new_data[CONF_ACKNOWLEDGE] = False
141 
142  # update to 2.1 (fix transitions for lights and switches)
143  new_entities_data = [*new_data[CONF_ENTITIES]]
144  for entity in new_entities_data:
145  if entity[CONF_DOMAIN] in [Platform.LIGHT, Platform.SCENE]:
146  if entity[CONF_DOMAIN_DATA][CONF_TRANSITION] is None:
147  entity[CONF_DOMAIN_DATA][CONF_TRANSITION] = 0
148  entity[CONF_DOMAIN_DATA][CONF_TRANSITION] /= 1000.0
149  new_data[CONF_ENTITIES] = new_entities_data
150 
151  hass.config_entries.async_update_entry(
152  config_entry, data=new_data, minor_version=1, version=2
153  )
154 
155  _LOGGER.debug(
156  "Migration to configuration version %s.%s successful",
157  config_entry.version,
158  config_entry.minor_version,
159  )
160  return True
161 
162 
163 async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
164  """Close connection to PCHK host represented by config_entry."""
165  # forward unloading to platforms
166  unload_ok = await hass.config_entries.async_unload_platforms(
167  config_entry, PLATFORMS
168  )
169 
170  if unload_ok and config_entry.entry_id in hass.data[DOMAIN]:
171  host = hass.data[DOMAIN].pop(config_entry.entry_id)
172  await host[CONNECTION].async_close()
173 
174  return unload_ok
175 
176 
178  hass: HomeAssistant,
179  config_entry: ConfigEntry,
180  device_registry: dr.DeviceRegistry,
181  inp: pypck.inputs.Input,
182 ) -> None:
183  """Process received input object (command) from LCN bus."""
184  if not isinstance(inp, pypck.inputs.ModInput):
185  return
186 
187  lcn_connection = hass.data[DOMAIN][config_entry.entry_id][CONNECTION]
188  logical_address = lcn_connection.physical_to_logical(inp.physical_source_addr)
189  address = (
190  logical_address.seg_id,
191  logical_address.addr_id,
192  logical_address.is_group,
193  )
194  identifiers = {(DOMAIN, generate_unique_id(config_entry.entry_id, address))}
195  device = device_registry.async_get_device(identifiers=identifiers)
196  if device is None:
197  return
198 
199  if isinstance(inp, pypck.inputs.ModStatusAccessControl):
200  _async_fire_access_control_event(hass, device, address, inp)
201  elif isinstance(inp, pypck.inputs.ModSendKeysHost):
202  _async_fire_send_keys_event(hass, device, address, inp)
203 
204 
206  hass: HomeAssistant, device: dr.DeviceEntry, address: AddressType, inp: InputType
207 ) -> None:
208  """Fire access control event (transponder, transmitter, fingerprint, codelock)."""
209  event_data = {
210  "segment_id": address[0],
211  "module_id": address[1],
212  "code": inp.code,
213  }
214 
215  if device is not None:
216  event_data.update({CONF_DEVICE_ID: device.id})
217 
218  if inp.periphery == pypck.lcn_defs.AccessControlPeriphery.TRANSMITTER:
219  event_data.update(
220  {"level": inp.level, "key": inp.key, "action": inp.action.value}
221  )
222 
223  event_name = f"lcn_{inp.periphery.value.lower()}"
224  hass.bus.async_fire(event_name, event_data)
225 
226 
228  hass: HomeAssistant, device: dr.DeviceEntry, address: AddressType, inp: InputType
229 ) -> None:
230  """Fire send_keys event."""
231  for table, action in enumerate(inp.actions):
232  if action == pypck.lcn_defs.SendKeyCommand.DONTSEND:
233  continue
234 
235  for key, selected in enumerate(inp.keys):
236  if not selected:
237  continue
238  event_data = {
239  "segment_id": address[0],
240  "module_id": address[1],
241  "key": pypck.lcn_defs.Key(table * 8 + key).name.lower(),
242  "action": action.name.lower(),
243  }
244 
245  if device is not None:
246  event_data.update({CONF_DEVICE_ID: device.id})
247 
248  hass.bus.async_fire("lcn_send_keys", event_data)
str generate_unique_id(list[int] dev_id, int channel)
Definition: switch.py:35
None register_lcn_host_device(HomeAssistant hass, ConfigEntry config_entry)
Definition: helpers.py:209
None register_lcn_address_devices(HomeAssistant hass, ConfigEntry config_entry)
Definition: helpers.py:224
None async_update_config_entry(HomeAssistant hass, ConfigEntry config_entry)
Definition: helpers.py:295
None register_panel_and_ws_api(HomeAssistant hass)
Definition: websocket.py:69
None async_host_input_received(HomeAssistant hass, ConfigEntry config_entry, dr.DeviceRegistry device_registry, pypck.inputs.Input inp)
Definition: __init__.py:182
None _async_fire_send_keys_event(HomeAssistant hass, dr.DeviceEntry device, AddressType address, InputType inp)
Definition: __init__.py:229
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:53
bool async_migrate_entry(HomeAssistant hass, ConfigEntry config_entry)
Definition: __init__.py:127
bool async_unload_entry(HomeAssistant hass, ConfigEntry config_entry)
Definition: __init__.py:163
bool async_setup_entry(HomeAssistant hass, ConfigEntry config_entry)
Definition: __init__.py:63
None _async_fire_access_control_event(HomeAssistant hass, dr.DeviceEntry device, AddressType address, InputType inp)
Definition: __init__.py:207
None register_services(HomeAssistant hass)
Definition: __init__.py:192