    async def _async_update_data(self):
        """
        Update data for known devices by scanning bluetooth advert cache.

        This works only with local data, so should be cheap to run
        (no network requests made etc).

        """
        for service_info in bluetooth.async_discovered_service_info(self.hass, False):
            # Note that some of these entries are restored from storage,
            # so we won't necessarily find (immediately, or perhaps ever)
            # scanner entries for any given device.

            _got_new_name = False
            if service_info.address not in self.devices:
                _got_new_name = True  # Our first packet, we'll need fresh names

            # Get/Create a device entry
            device = self._get_or_create_device(service_info.address)

            # Check if it's broadcasting an Apple Inc manufacturing data (ID: 0x004C)
            for (
                company_code,
                man_data,
            ) in service_info.advertisement.manufacturer_data.items():
                # if company_code == 0x00E0:  # 224 Google
                #     _LOGGER.debug(
                #         "Found Google Device: %s %s", device.address, man_data.hex()
                #     )
                if company_code == 0x004C:  # 76 Apple Inc
                    # _LOGGER.debug(
                    #     "Found Apple Manufacturer data: %s %s",
                    #     device.address,
                    #     man_data.hex(),
                    # )
                    if man_data[:2] == b"\x02\x15":  # 0x0215:  # iBeacon packet
                        # iBeacon / UUID Support

                        # We provide simplistic support for iBeacons. The
                        # initial/primary use-case is the companion app
                        # for Android phones. We are working with these
                        # assumptions to start with:
                        # - UUID, Major and Minor are static
                        # - MAC address may or may not be static
                        # - We treat a given UUID/Major/Minor combination
                        #   as being unique. If a device sends multiple
                        #   ID's we treat it as *wanting* to be seen as multiple
                        #   devices.

                        # Internally, we still treat the MAC address as the primary
                        # "entity", but if a beacon payload is attached, we
                        # essentially create a duplicate BermudaDevice which uses
                        # the UUID as its "address", and we copy the most recently
                        # received device's details to it. This allows one to decide
                        # to track the MAC address or the UUID.

                        # Combining multiple Minor/Major's into one device isn't
                        # supported at this stage, and I'd suggest doing that sort
                        # of grouping at a higher level (eg using Groups in HA or
                        # matching automations on attributes or a subset of
                        # devices), but if there are prominent use-cases we can
                        # alter our approach.

                        # At least one(!) iBeacon out there sends only 22 bytes (it has no tx_power field)
                        # which is weird. So Let's just decode what we can that exists, and blindly proceed
                        # otherwise. We could reject it, but it can still be useful, so...
                        if len(man_data) >= 22:
                            # Proper iBeacon packet has 23 bytes.
                            device.metadevice_type.add(METADEVICE_TYPE_IBEACON_SOURCE)
                            device.beacon_uuid = man_data[2:18].hex().lower()
                            device.beacon_major = str(int.from_bytes(man_data[18:20], byteorder="big"))
                            device.beacon_minor = str(int.from_bytes(man_data[20:22], byteorder="big"))
                        if len(man_data) >= 23:
                            # There really is at least one out there that lacks this! See #466
                            device.beacon_power = int.from_bytes([man_data[22]], signed=True)

                        # So, the irony of having major/minor is that the
                        # UniversallyUniqueIDentifier is not even unique
                        # locally, so we need to make one :-)

                        device.beacon_unique_id = f"{device.beacon_uuid}_{device.beacon_major}_{device.beacon_minor}"  # pylint: disable=line-too-long
                        # Note: it's possible that a device sends multiple
                        # beacons. We are only going to process the latest
                        # one in any single update cycle, so we ignore that
                        # possibility for now. Given we re-process completely
                        # each cycle it should *just work*, for the most part.

                        # Update name in case it chooses to use the beacon_unique_id for it.
                        _got_new_name = True

                        # Create a metadevice for this beacon. Metadevices get updated
                        # after all adverts are processed and distances etc are calculated
                        # for the sources.
                        self.register_ibeacon_source(device)

            # We probably don't need to do all of this every time, but we
            # want to catch any changes, eg when the system learns the local
            # name etc.

            # Clean up names because it seems plenty of bluetooth device creators
            # don't seem to know that buffers !== strings.
            if device.name_bt_serviceinfo is None and service_info.device.name:
                device.name_bt_serviceinfo = clean_charbuf(service_info.device.name)
                _got_new_name = True
            if device.name_bt_local_name is None and service_info.advertisement.local_name:
                device.name_bt_local_name = clean_charbuf(service_info.advertisement.local_name)
                _got_new_name = True

            # If not already set, look up the manufacturer uuid and set name
            device.manufacturer = device.manufacturer or service_info.manufacturer
            if device.manufacturer is None:
                if (
                    service_info.service_uuids
                    and (member_uuid := service_info.service_uuids[0][4:8]) in self.member_uuids
                ):
                    # https://bitbucket.org/bluetooth-SIG/public/src/main/assigned_numbers/uuids/member_uuids.yaml
                    device.manufacturer = self.member_uuids[member_uuid]
                    _got_new_name = True

            if _got_new_name:
                device.make_name()  # re-generate the preferred name.

            device.connectable = service_info.connectable

            # Work through the scanner entries...
            matched_scanners = bluetooth.async_scanner_devices_by_address(self.hass, service_info.address, False)
            for discovered in matched_scanners:
                scanner_device = self._get_device(discovered.scanner.source)
                if scanner_device is None:
                    # The receiver doesn't have a device entry yet, let's refresh
                    # all of them in this batch...
                    self._do_full_scanner_init = True  # Flag that we need a full init
                    self._do_private_device_init = True
                    self._refresh_scanners()
                    scanner_device = self._get_device(discovered.scanner.source)

                if scanner_device is None:
                    # Highly unusual. If we can't find an entry for the scanner
                    # maybe it's from an integration that's not yet loaded, or
                    # perhaps it's an unexpected type that we don't know how to
                    # find.
                    _LOGGER_SPAM_LESS.error(
                        f"missing_scanner_entry_{discovered.scanner.source}",
                        "Failed to find config for scanner %s, this is probably a bug.",
                        discovered.scanner.source,
                    )
                    continue

                # Update the scanner entry on the current device
                device.update_scanner(scanner_device, discovered)

            # END of per-advertisement-by-device loop

        # If any *configured* devices have not yet been seen, create device
        # entries for them so they will claim the restored sensors in HA
        # (this prevents them from restoring at startup as "Unavailable" if they
        # are not currently visible, and will instead show as "Unknown" for
        # sensors and "Away" for device_trackers).
        if self.stamp_last_update == 0:
            # First run, let's do it.
            for _source_address in self.options.get(CONF_DEVICES, []):
                self._get_or_create_device(_source_address)

        # Scanner entries have been loaded up with latest data, now we can
        # process data for all devices over all scanners.
        for device in self.devices.values():
            # Recalculate smoothed distances, last_seen etc
            device.calculate_data()

        self._refresh_areas_by_min_distance()

        # We might need to freshen deliberately on first start if no new scanners
        # were discovered in the first scan update. This is likely if nothing has changed
        # since the last time we booted.
        if self._do_full_scanner_init:
            if not self._refresh_scanners():
                # _LOGGER.debug("Failed to refresh scanners, likely config entry not ready.")
                # don't fail the update, just try again next time.
                # self.last_update_success = False
                pass

        # set up any beacons and update their data. We do this after all the devices
        # have had their updates done since any beacon inherits data from its source
        # device(s). We do this *before* sensor creation, though.
        self.update_metadevices()

        # The devices are all updated now (and any new scanners and beacons seen have been added),
        # so let's ensure any devices that we create sensors for are set up ready to go.
        # We don't do this sooner because we need to ensure we have every active scanner
        # already loaded up.
        for address, device in self.devices.items():
            if device.create_sensor:
                if not device.create_all_done:
                    _LOGGER.debug("Firing device_new for %s (%s)", device.name, address)
                    # Note that the below should be OK thread-wise, debugger indicates this is being
                    # called by _run in events.py, so pretty sure we are "in the event loop".
                    async_dispatcher_send(self.hass, SIGNAL_DEVICE_NEW, address, self.scanner_list)

        if self.stamp_last_prune < MONOTONIC_TIME() - PRUNE_TIME_INTERVAL:
            # (periodically) prune any stale device entries...
            self.prune_devices()
            self.stamp_last_prune = MONOTONIC_TIME()

        # end of async update
        self.stamp_last_update = MONOTONIC_TIME()
        self.last_update_success = True
Update data for known devices by scanning bluetooth advert cache.

This works only with local data, so should be cheap to run
(no network requests made etc).
custom_components/bermuda/coordinator.py