Home Assistant Unofficial Reference 2024.12.1
wrong_silabs_firmware.py
Go to the documentation of this file.
1 """ZHA repairs for common environmental and device problems."""
2 
3 from __future__ import annotations
4 
5 import enum
6 import logging
7 
8 from universal_silabs_flasher.const import ApplicationType
9 from universal_silabs_flasher.flasher import Flasher
10 
12  hardware as skyconnect_hardware,
13 )
15  RADIO_DEVICE as YELLOW_RADIO_DEVICE,
16  hardware as yellow_hardware,
17 )
18 from homeassistant.core import HomeAssistant
19 from homeassistant.exceptions import HomeAssistantError
20 from homeassistant.helpers import issue_registry as ir
21 
22 from ..const import DOMAIN
23 
24 _LOGGER = logging.getLogger(__name__)
25 
26 
27 class AlreadyRunningEZSP(Exception):
28  """The device is already running EZSP firmware."""
29 
30 
31 class HardwareType(enum.StrEnum):
32  """Detected Zigbee hardware type."""
33 
34  SKYCONNECT = "skyconnect"
35  YELLOW = "yellow"
36  OTHER = "other"
37 
38 
39 DISABLE_MULTIPAN_URL = {
40  HardwareType.YELLOW: (
41  "https://yellow.home-assistant.io/guides/disable-multiprotocol/#flash-the-silicon-labs-radio-firmware"
42  ),
43  HardwareType.SKYCONNECT: (
44  "https://skyconnect.home-assistant.io/procedures/disable-multiprotocol/#step-flash-the-silicon-labs-radio-firmware"
45  ),
46  HardwareType.OTHER: None,
47 }
48 
49 ISSUE_WRONG_SILABS_FIRMWARE_INSTALLED = "wrong_silabs_firmware_installed"
50 
51 
52 def _detect_radio_hardware(hass: HomeAssistant, device: str) -> HardwareType:
53  """Identify the radio hardware with the given serial port."""
54  try:
55  yellow_hardware.async_info(hass)
56  except HomeAssistantError:
57  pass
58  else:
59  if device == YELLOW_RADIO_DEVICE:
60  return HardwareType.YELLOW
61 
62  try:
63  info = skyconnect_hardware.async_info(hass)
64  except HomeAssistantError:
65  pass
66  else:
67  for hardware_info in info:
68  for entry_id in hardware_info.config_entries or []:
69  entry = hass.config_entries.async_get_entry(entry_id)
70 
71  if entry is not None and entry.data["device"] == device:
72  return HardwareType.SKYCONNECT
73 
74  return HardwareType.OTHER
75 
76 
78  device: str, *, probe_methods: ApplicationType | None = None
79 ) -> ApplicationType | None:
80  """Probe the running firmware on a Silabs device."""
81  flasher = Flasher(
82  device=device,
83  **({"probe_methods": probe_methods} if probe_methods else {}),
84  )
85 
86  try:
87  await flasher.probe_app_type()
88  except Exception: # noqa: BLE001
89  _LOGGER.debug("Failed to probe application type", exc_info=True)
90 
91  return flasher.app_type
92 
93 
94 async def warn_on_wrong_silabs_firmware(hass: HomeAssistant, device: str) -> bool:
95  """Create a repair issue if the wrong type of SiLabs firmware is detected."""
96  # Only consider actual serial ports
97  if device.startswith("socket://"):
98  return False
99 
100  app_type = await probe_silabs_firmware_type(device)
101 
102  if app_type is None:
103  # Failed to probe, we can't tell if the wrong firmware is installed
104  return False
105 
106  if app_type == ApplicationType.EZSP:
107  # If connecting fails but we somehow probe EZSP (e.g. stuck in bootloader),
108  # reconnect, it should work
109  raise AlreadyRunningEZSP
110 
111  hardware_type = _detect_radio_hardware(hass, device)
112  ir.async_create_issue(
113  hass,
114  domain=DOMAIN,
115  issue_id=ISSUE_WRONG_SILABS_FIRMWARE_INSTALLED,
116  is_fixable=False,
117  is_persistent=True,
118  learn_more_url=DISABLE_MULTIPAN_URL[hardware_type],
119  severity=ir.IssueSeverity.ERROR,
120  translation_key=(
121  ISSUE_WRONG_SILABS_FIRMWARE_INSTALLED
122  + ("_nabucasa" if hardware_type != HardwareType.OTHER else "_other")
123  ),
124  translation_placeholders={"firmware_type": app_type.name},
125  )
126 
127  return True
bool warn_on_wrong_silabs_firmware(HomeAssistant hass, str device)
HardwareType _detect_radio_hardware(HomeAssistant hass, str device)
ApplicationType|None probe_silabs_firmware_type(str device, *ApplicationType|None probe_methods=None)