|
33 | 33 | from homeassistant.helpers.aiohttp_client import async_get_clientsession |
34 | 34 | from homeassistant.helpers.hassio import is_hassio |
35 | 35 |
|
36 | | -from .const import OTBR_DOMAIN, Z2M_EMBER_DOCS_URL, ZHA_DOMAIN |
| 36 | +from .const import DOMAIN, OTBR_DOMAIN, Z2M_EMBER_DOCS_URL, ZHA_DOMAIN |
37 | 37 | from .util import ( |
38 | 38 | ApplicationType, |
39 | 39 | FirmwareInfo, |
40 | 40 | OwningAddon, |
41 | 41 | OwningIntegration, |
42 | 42 | ResetTarget, |
| 43 | + async_firmware_flashing_context, |
43 | 44 | async_flash_silabs_firmware, |
44 | 45 | get_otbr_addon_manager, |
45 | 46 | guess_firmware_info, |
@@ -228,83 +229,95 @@ async def _install_firmware( |
228 | 229 | # Keep track of the firmware we're working with, for error messages |
229 | 230 | self.installing_firmware_name = firmware_name |
230 | 231 |
|
231 | | - # Installing new firmware is only truly required if the wrong type is |
232 | | - # installed: upgrading to the latest release of the current firmware type |
233 | | - # isn't strictly necessary for functionality. |
234 | | - self._probed_firmware_info = await probe_silabs_firmware_info( |
235 | | - self._device, |
236 | | - bootloader_reset_methods=self.BOOTLOADER_RESET_METHODS, |
237 | | - application_probe_methods=self.APPLICATION_PROBE_METHODS, |
238 | | - ) |
239 | | - |
240 | | - firmware_install_required = self._probed_firmware_info is None or ( |
241 | | - self._probed_firmware_info.firmware_type != expected_installed_firmware_type |
242 | | - ) |
243 | | - |
244 | | - session = async_get_clientsession(self.hass) |
245 | | - client = FirmwareUpdateClient(fw_update_url, session) |
246 | | - |
247 | | - try: |
248 | | - manifest = await client.async_update_data() |
249 | | - fw_manifest = next( |
250 | | - fw for fw in manifest.firmwares if fw.filename.startswith(fw_type) |
| 232 | + # For the duration of firmware flashing, hint to other integrations (i.e. ZHA) |
| 233 | + # that the hardware is in use and should not be accessed. This is separate from |
| 234 | + # locking the serial port itself, since a momentary release of the port may |
| 235 | + # still allow for ZHA to reclaim the device. |
| 236 | + async with async_firmware_flashing_context(self.hass, self._device, DOMAIN): |
| 237 | + # Installing new firmware is only truly required if the wrong type is |
| 238 | + # installed: upgrading to the latest release of the current firmware type |
| 239 | + # isn't strictly necessary for functionality. |
| 240 | + self._probed_firmware_info = await probe_silabs_firmware_info( |
| 241 | + self._device, |
| 242 | + bootloader_reset_methods=self.BOOTLOADER_RESET_METHODS, |
| 243 | + application_probe_methods=self.APPLICATION_PROBE_METHODS, |
251 | 244 | ) |
252 | | - except (StopIteration, TimeoutError, ClientError, ManifestMissing) as err: |
253 | | - _LOGGER.warning("Failed to fetch firmware update manifest", exc_info=True) |
254 | 245 |
|
255 | | - # Not having internet access should not prevent setup |
256 | | - if not firmware_install_required: |
257 | | - _LOGGER.debug("Skipping firmware upgrade due to index download failure") |
258 | | - return |
259 | | - |
260 | | - raise AbortFlow( |
261 | | - reason="fw_download_failed", |
262 | | - description_placeholders=self._get_translation_placeholders(), |
263 | | - ) from err |
264 | | - |
265 | | - if not firmware_install_required: |
266 | | - assert self._probed_firmware_info is not None |
| 246 | + firmware_install_required = self._probed_firmware_info is None or ( |
| 247 | + self._probed_firmware_info.firmware_type |
| 248 | + != expected_installed_firmware_type |
| 249 | + ) |
267 | 250 |
|
268 | | - # Make sure we do not downgrade the firmware |
269 | | - fw_metadata = NabuCasaMetadata.from_json(fw_manifest.metadata) |
270 | | - fw_version = fw_metadata.get_public_version() |
271 | | - probed_fw_version = Version(self._probed_firmware_info.firmware_version) |
| 251 | + session = async_get_clientsession(self.hass) |
| 252 | + client = FirmwareUpdateClient(fw_update_url, session) |
272 | 253 |
|
273 | | - if probed_fw_version >= fw_version: |
274 | | - _LOGGER.debug( |
275 | | - "Not downgrading firmware, installed %s is newer than available %s", |
276 | | - probed_fw_version, |
277 | | - fw_version, |
| 254 | + try: |
| 255 | + manifest = await client.async_update_data() |
| 256 | + fw_manifest = next( |
| 257 | + fw for fw in manifest.firmwares if fw.filename.startswith(fw_type) |
| 258 | + ) |
| 259 | + except (StopIteration, TimeoutError, ClientError, ManifestMissing) as err: |
| 260 | + _LOGGER.warning( |
| 261 | + "Failed to fetch firmware update manifest", exc_info=True |
278 | 262 | ) |
279 | | - return |
280 | 263 |
|
281 | | - try: |
282 | | - fw_data = await client.async_fetch_firmware(fw_manifest) |
283 | | - except (TimeoutError, ClientError, ValueError) as err: |
284 | | - _LOGGER.warning("Failed to fetch firmware update", exc_info=True) |
| 264 | + # Not having internet access should not prevent setup |
| 265 | + if not firmware_install_required: |
| 266 | + _LOGGER.debug( |
| 267 | + "Skipping firmware upgrade due to index download failure" |
| 268 | + ) |
| 269 | + return |
| 270 | + |
| 271 | + raise AbortFlow( |
| 272 | + reason="fw_download_failed", |
| 273 | + description_placeholders=self._get_translation_placeholders(), |
| 274 | + ) from err |
285 | 275 |
|
286 | | - # If we cannot download new firmware, we shouldn't block setup |
287 | 276 | if not firmware_install_required: |
288 | | - _LOGGER.debug("Skipping firmware upgrade due to image download failure") |
289 | | - return |
| 277 | + assert self._probed_firmware_info is not None |
| 278 | + |
| 279 | + # Make sure we do not downgrade the firmware |
| 280 | + fw_metadata = NabuCasaMetadata.from_json(fw_manifest.metadata) |
| 281 | + fw_version = fw_metadata.get_public_version() |
| 282 | + probed_fw_version = Version(self._probed_firmware_info.firmware_version) |
| 283 | + |
| 284 | + if probed_fw_version >= fw_version: |
| 285 | + _LOGGER.debug( |
| 286 | + "Not downgrading firmware, installed %s is newer than available %s", |
| 287 | + probed_fw_version, |
| 288 | + fw_version, |
| 289 | + ) |
| 290 | + return |
290 | 291 |
|
291 | | - # Otherwise, fail |
292 | | - raise AbortFlow( |
293 | | - reason="fw_download_failed", |
294 | | - description_placeholders=self._get_translation_placeholders(), |
295 | | - ) from err |
| 292 | + try: |
| 293 | + fw_data = await client.async_fetch_firmware(fw_manifest) |
| 294 | + except (TimeoutError, ClientError, ValueError) as err: |
| 295 | + _LOGGER.warning("Failed to fetch firmware update", exc_info=True) |
296 | 296 |
|
297 | | - self._probed_firmware_info = await async_flash_silabs_firmware( |
298 | | - hass=self.hass, |
299 | | - device=self._device, |
300 | | - fw_data=fw_data, |
301 | | - expected_installed_firmware_type=expected_installed_firmware_type, |
302 | | - bootloader_reset_methods=self.BOOTLOADER_RESET_METHODS, |
303 | | - application_probe_methods=self.APPLICATION_PROBE_METHODS, |
304 | | - progress_callback=lambda offset, total: self.async_update_progress( |
305 | | - offset / total |
306 | | - ), |
307 | | - ) |
| 297 | + # If we cannot download new firmware, we shouldn't block setup |
| 298 | + if not firmware_install_required: |
| 299 | + _LOGGER.debug( |
| 300 | + "Skipping firmware upgrade due to image download failure" |
| 301 | + ) |
| 302 | + return |
| 303 | + |
| 304 | + # Otherwise, fail |
| 305 | + raise AbortFlow( |
| 306 | + reason="fw_download_failed", |
| 307 | + description_placeholders=self._get_translation_placeholders(), |
| 308 | + ) from err |
| 309 | + |
| 310 | + self._probed_firmware_info = await async_flash_silabs_firmware( |
| 311 | + hass=self.hass, |
| 312 | + device=self._device, |
| 313 | + fw_data=fw_data, |
| 314 | + expected_installed_firmware_type=expected_installed_firmware_type, |
| 315 | + bootloader_reset_methods=self.BOOTLOADER_RESET_METHODS, |
| 316 | + application_probe_methods=self.APPLICATION_PROBE_METHODS, |
| 317 | + progress_callback=lambda offset, total: self.async_update_progress( |
| 318 | + offset / total |
| 319 | + ), |
| 320 | + ) |
308 | 321 |
|
309 | 322 | async def _configure_and_start_otbr_addon(self) -> None: |
310 | 323 | """Configure and start the OTBR addon.""" |
|
0 commit comments