1 """Support for Zigbee Home Automation devices."""
5 from zoneinfo
import ZoneInfo
7 import voluptuous
as vol
8 from zha.application.const
import BAUD_RATES, RadioType
9 from zha.application.gateway
import Gateway
10 from zha.application.helpers
import ZHAData
11 from zha.zigbee.device
import get_device_automation_triggers
12 from zigpy.config
import CONF_DATABASE, CONF_DEVICE, CONF_DEVICE_PATH
13 from zigpy.exceptions
import NetworkSettingsInconsistent, TransientConnectionError
18 EVENT_CORE_CONFIG_UPDATE,
19 EVENT_HOMEASSISTANT_STOP,
29 from .
import repairs, websocket_api
32 CONF_CUSTOM_QUIRKS_PATH,
42 from .helpers
import (
49 from .radio_manager
import ZhaRadioManager
50 from .repairs.network_settings_inconsistent
import warn_on_inconsistent_network_settings
51 from .repairs.wrong_silabs_firmware
import (
53 warn_on_wrong_silabs_firmware,
56 DEVICE_CONFIG_SCHEMA_ENTRY = vol.Schema({vol.Optional(CONF_TYPE): cv.string})
58 vol.Optional(CONF_BAUDRATE): cv.positive_int,
59 vol.Optional(CONF_DATABASE): cv.string,
60 vol.Optional(CONF_DEVICE_CONFIG, default={}): vol.Schema(
61 {cv.string: DEVICE_CONFIG_SCHEMA_ENTRY}
63 vol.Optional(CONF_ENABLE_QUIRKS, default=
True): cv.boolean,
64 vol.Optional(CONF_ZIGPY): dict,
65 vol.Optional(CONF_RADIO_TYPE): cv.enum(RadioType),
66 vol.Optional(CONF_USB_PATH): cv.string,
67 vol.Optional(CONF_CUSTOM_QUIRKS_PATH): cv.isdir,
69 CONFIG_SCHEMA = vol.Schema(
73 cv.deprecated(CONF_USB_PATH),
74 cv.deprecated(CONF_BAUDRATE),
75 cv.deprecated(CONF_RADIO_TYPE),
80 extra=vol.ALLOW_EXTRA,
84 Platform.ALARM_CONTROL_PANEL,
85 Platform.BINARY_SENSOR,
89 Platform.DEVICE_TRACKER,
103 CENTICELSIUS =
"C-100"
106 _LOGGER = logging.getLogger(__name__)
109 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
110 """Set up ZHA from config."""
111 ha_zha_data =
HAZHAData(yaml_config=config.get(DOMAIN, {}))
112 hass.data[DATA_ZHA] = ha_zha_data
120 Will automatically load components to support devices found on the network.
123 ha_zha_data.config_entry = config_entry
126 zha_gateway = await Gateway.async_from_config(zha_lib_data)
129 device_registry = dr.async_get(hass)
130 radio_mgr = ZhaRadioManager.from_config_entry(hass, config_entry)
132 async
with radio_mgr.connect_zigpy_app()
as app:
133 for dev
in app.devices.values():
134 dev_entry = device_registry.async_get_device(
135 identifiers={(DOMAIN,
str(dev.ieee))},
136 connections={(dr.CONNECTION_ZIGBEE,
str(dev.ieee))},
139 if dev_entry
is None:
142 zha_lib_data.device_trigger_cache[dev_entry.id] = (
144 get_device_automation_triggers(dev),
146 ha_zha_data.device_trigger_cache = zha_lib_data.device_trigger_cache
148 _LOGGER.debug(
"Trigger cache: %s", zha_lib_data.device_trigger_cache)
151 await zha_gateway.async_initialize()
152 except NetworkSettingsInconsistent
as exc:
155 config_entry=config_entry,
156 old_state=exc.old_state,
157 new_state=exc.new_state,
160 "Network settings do not match most recent backup"
162 except TransientConnectionError
as exc:
163 raise ConfigEntryNotReady
from exc
164 except Exception
as exc:
165 _LOGGER.debug(
"Failed to set up ZHA", exc_info=exc)
166 device_path = config_entry.data[CONF_DEVICE][CONF_DEVICE_PATH]
169 not device_path.startswith(
"socket://")
170 and RadioType[config_entry.data[CONF_RADIO_TYPE]] == RadioType.ezsp
176 except AlreadyRunningEZSP
as ezsp_exc:
177 raise ConfigEntryNotReady
from ezsp_exc
179 raise ConfigEntryNotReady
from exc
181 repairs.async_delete_blocking_issues(hass)
183 ha_zha_data.gateway_proxy =
ZHAGatewayProxy(hass, config_entry, zha_gateway)
185 manufacturer = zha_gateway.state.node_info.manufacturer
186 model = zha_gateway.state.node_info.model
188 if manufacturer
is None and model
is None:
189 manufacturer =
"Unknown"
192 device_registry.async_get_or_create(
193 config_entry_id=config_entry.entry_id,
194 connections={(dr.CONNECTION_ZIGBEE,
str(zha_gateway.state.node_info.ieee))},
195 identifiers={(DOMAIN,
str(zha_gateway.state.node_info.ieee))},
196 name=
"Zigbee Coordinator",
197 manufacturer=manufacturer,
199 sw_version=zha_gateway.state.node_info.version,
202 websocket_api.async_load_api(hass)
204 async
def async_shutdown(_: Event) ->
None:
205 """Handle shutdown tasks."""
206 assert ha_zha_data.gateway_proxy
is not None
207 await ha_zha_data.gateway_proxy.shutdown()
209 config_entry.async_on_unload(
210 hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_shutdown)
215 """Handle Core config update."""
216 zha_gateway.config.local_timezone = ZoneInfo(hass.config.time_zone)
218 config_entry.async_on_unload(
219 hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, update_config)
222 await ha_zha_data.gateway_proxy.async_initialize_devices_and_entities()
223 await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
229 """Unload ZHA config entry."""
231 ha_zha_data.config_entry =
None
233 if ha_zha_data.gateway_proxy
is not None:
234 await ha_zha_data.gateway_proxy.shutdown()
235 ha_zha_data.gateway_proxy =
None
241 with contextlib.suppress(KeyError):
242 for platform
in PLATFORMS:
243 del ha_zha_data.platforms[platform]
245 websocket_api.async_unload_api(hass)
247 return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
251 """Migrate old entry."""
252 _LOGGER.debug(
"Migrating from version %s", config_entry.version)
254 if config_entry.version == 1:
256 CONF_RADIO_TYPE: config_entry.data[CONF_RADIO_TYPE],
257 CONF_DEVICE: {CONF_DEVICE_PATH: config_entry.data[CONF_USB_PATH]},
260 baudrate =
get_zha_data(hass).yaml_config.get(CONF_BAUDRATE)
261 if data[CONF_RADIO_TYPE] != RadioType.deconz
and baudrate
in BAUD_RATES:
262 data[CONF_DEVICE][CONF_BAUDRATE] = baudrate
264 hass.config_entries.async_update_entry(config_entry, data=data, version=2)
266 if config_entry.version == 2:
267 data = {**config_entry.data}
269 if data[CONF_RADIO_TYPE] ==
"ti_cc":
270 data[CONF_RADIO_TYPE] =
"znp"
272 hass.config_entries.async_update_entry(config_entry, data=data, version=3)
274 if config_entry.version == 3:
275 data = {**config_entry.data}
277 if not data[CONF_DEVICE].
get(CONF_BAUDRATE):
278 data[CONF_DEVICE][CONF_BAUDRATE] = {
284 }[data[CONF_RADIO_TYPE]]
286 if not data[CONF_DEVICE].
get(CONF_FLOW_CONTROL):
287 data[CONF_DEVICE][CONF_FLOW_CONTROL] =
None
289 hass.config_entries.async_update_entry(config_entry, data=data, version=4)
291 _LOGGER.info(
"Migration to version %s successful", config_entry.version)
web.Response get(self, web.Request request, str config_key)
None update_config(str path, dict[str, Any] calendar)
ZHAData create_zha_config(HomeAssistant hass, HAZHAData ha_zha_data)
HAZHAData get_zha_data(HomeAssistant hass)
None warn_on_inconsistent_network_settings(HomeAssistant hass, ConfigEntry config_entry, NetworkBackup old_state, NetworkBackup new_state)
bool warn_on_wrong_silabs_firmware(HomeAssistant hass, str device)
bool async_unload_entry(HomeAssistant hass, ConfigEntry config_entry)
bool async_setup_entry(HomeAssistant hass, ConfigEntry config_entry)
bool async_setup(HomeAssistant hass, ConfigType config)
bool async_migrate_entry(HomeAssistant hass, ConfigEntry config_entry)
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)