NordicSemiconductor / IOS-DFU-Library

OTA DFU Library for Mac and iOS, compatible with nRF5x SoCs
http://www.nordicsemi.com
BSD 3-Clause "New" or "Revised" License
526 stars 215 forks source link

Peripheral Stuck in DFU Mode After Update #540

Closed brettohland-nuvoair closed 2 months ago

brettohland-nuvoair commented 2 months ago

Information

DFU Bootloader version (please complete the following information):

Device information (please complete the following information):

Describe the bug I have an interesting issue showing up while updating my peripheral. The original firmware isn't using bonding and has the DFU service exposed directly in the main application.

The update contains both a DFU and main application update inside of a zip file and initially updates the DFU partition and then reboots into the updated code, then updates the main application.

After updating the DFU partition the iOS DFU Library is unable to scan for the peripheral and will time out, which leaves the peripheral running in DFU mode. The data that it's advertising isn't what's expected:

(These values are from the console)

▿ 6 elements
  ▿ 0 : 2 elements
    - key : "kCBAdvDataRxPrimaryPHY"
    - value : 0
  ▿ 1 : 2 elements
    - key : "kCBAdvDataServiceData"
    ▿ value : 1 element
      ▿ 0 : 2 elements
        - key : 1530
        - value : <0a00>
  ▿ 2 : 2 elements
    - key : "kCBAdvDataRxSecondaryPHY"
    - value : 0
  ▿ 3 : 2 elements
    - key : "kCBAdvDataIsConnectable"
    - value : 1
  ▿ 4 : 2 elements
    - key : "kCBAdvDataLocalName"
    - value : AIR-DFU
  ▿ 5 : 2 elements
    - key : "kCBAdvDataTimestamp"
    - value : 748892861.775431

Other times, when the peripheral is running in DFU mode, it would be advertising the DFU service (00001530-1212-EFDE-1523-785FEABCD123), but my peripheral isn't advertising as having any available services. However, inside of the service data, you can see that it's referencing a shortened CBUUID identifier for the service and is providing the value of 0x0a00. This seems like this may be an error code provided by the DFU service, but haven't been able to find any documentation on what this might mean.

Through testing, the only way that I've had CoreBluetooth provide this peripheral to my code during a scan was either call scanForPeripherals(withServices:) with a nil array of CBUUID objects, or to include CBUUID(string; "1530") inside of that list. Since the iOS DFU Library only scans for the fully qualified CBUUID of the DFU service, it will not be found and will time out.

It's possible to "fix" the firmware update flow by modifying the switchToNewPeripheralAndConnect() method in DFUPeripheral.swift:

    func switchToNewPeripheralAndConnect() {
        // Release the previous peripheral.
        peripheral?.delegate = nil
        peripheral = nil
        cleanUp()

        guard !aborted else {
            resetDevice()
            return
        }

        // Set a scanner timer.
        connectionTimer = DispatchSource.makeTimerSource()
        connectionTimer?.setEventHandler { [weak self] in
            if let self = self {
                self.connectionTimer?.cancel()
                self.logger.w("Scanning timed out returning no matching peripherals!")
                self.logger.d("centralManager.stopScan()")
                self.centralManager.stopScan()
                self.delegate?.error(.failedToConnect, didOccurWithMessage: "No DFU device found.")
            }
        }
        connectionTimer?.schedule(deadline: .now() + connectionTimeout)
        connectionTimer?.resume()

        logger.v("Scanning for the DFU Bootloader...")

        // Changes below this line -------------
        var requiredServices = peripheralSelector.filterBy(hint: DFUServiceType.serviceUuid(from: uuidHelper))
        logger.d("centralManager.scanForPeripherals(withServices, \(requiredServices?.description ?? "nil")")
        requiredServices?.append(CBUUID(string: "1530"))
        centralManager.scanForPeripherals(withServices: requiredServices)
        // Changes above this line -------------
    }

When this change is made, the iOS DFU Library is able to scan for and re-connect with the peripheral and continue to update the main application (Logs are attached).

Interestingly enough, this is only happening when we update from this older version of our firmware which isn't bonded and has the DFU service exposed in the main application. Our newer firmware, which uses bonding, first reboots into DFU mode before initializing the update and it works as expected.

Any help or insight would be great. I'm unsure if this is a bug in the DFU update process or if this is solely an issue on our part.

Logs

Attached are logs for when it fails, and for when it works after implementing the fix.

Logs (failing).txt Logs (Working).txt

philips77 commented 2 months ago

Hello, First of all, let me tell you that I don't know what this advertised Service Data mean:

1 : 2 elements
    - key : "kCBAdvDataServiceData"
    ▿ value : 1 element
      ▿ 0 : 2 elements
        - key : 1530
        - value : <0a00>

Other times, when the peripheral is running in DFU mode, it would be advertising the DFU service (00001530-1212-EFDE-1523-785FEABCD123),

That is what it should advertise with.

but my peripheral isn't advertising as having any available services. However, inside of the service data, you can see that it's referencing a shortened CBUUID identifier for the service and is providing the value of 0x0a00. This seems like this may be an error code provided by the DFU service but haven't been able to find any documentation on what this might mean.

I believe this isn't something that was in the original DFU bootloader and in my opinion must have been custom made by the author of the new bootloader version. My reasoning is, that the 128-bit UUID should not be shortened to 16-bit UUID it it's not Bluetooth SIG assigned, so if it was in the bootloader, it would use Service Data with 128-bit UUID. I also never experienced that case. The unmodified bootloader should advertise with 128-bit Service UUID and address incremented by 1. It may also be, that iOS has shortened the UUID, but the device actually advertises with 128-bit, or that there's a bug in the bootloader. But then again, I don't know what 0x0A00 would mean.

It's possible to "fix" the firmware update flow by modifying the switchToNewPeripheralAndConnect() method in DFUPeripheral.swift:

You don't have to modify the code. The DfuPeripheralSelector, which you may customize in DfuServiceInitiator may return a list of CBUUID to scan for. By default, it returns the hint (DFU service), but you may add additional service there.

Use: https://github.com/NordicSemiconductor/IOS-DFU-Library/blob/2db0d4f26a4812dfb7553e50e2a10f53f7b95114/Library/Classes/Implementation/DFUServiceInitiator.swift#L110 and implement: https://github.com/NordicSemiconductor/IOS-DFU-Library/blob/2db0d4f26a4812dfb7553e50e2a10f53f7b95114/Library/Classes/Implementation/DFUPeripheralSelectorDelegate.swift#L108 similar to how the default selector does: https://github.com/NordicSemiconductor/IOS-DFU-Library/blob/2db0d4f26a4812dfb7553e50e2a10f53f7b95114/Library/Classes/Implementation/DFUPeripheralSelector.swift#L50-L52

When this change is made, the iOS DFU Library is able to scan for and re-connect with the peripheral and continue to update the main application (Logs are attached).

Your "fix" actually stops scanning for full DFU UUID and starts scanning to this 16-bit one, as a Central Manager can only perform a single scan at a moment.

Interestingly enough, this is only happening when we update from this older version of our firmware which isn't bonded and has the DFU service exposed in the main application. Our newer firmware, which uses bonding, first reboots into DFU mode before initializing the update and it works as expected.

That could indicate that the "new" bootloader when flashed onto the "old" devices is complaining about lack of bond information, for example.

...first reboots into DFU mode before initializing...

Every DFU reboots info DFU mode...?

brettohland-nuvoair commented 2 months ago

First of all, let me tell you that I don't know what this advertised Service Data mean:

That makes two of us, my original thought that the DFU bootloader was informing us of some sort of error code with the 0x0A00 data here.

The fact that it's leaving you as confused as we are tells me that it's some sort of issue on our side.

I really appreciate the fast response to this issue @philips77, I was able to use the DFUPeripheralSelector to have the SDK find the peripheral and complete the update.

Closing the issue as it's not a bug.