CBP Периферийные услуги не переоткрыты должным образом после изменения - PullRequest
1 голос
/ 13 января 2020

У меня есть проект iOS Swift 5, в котором CBCentralManager используется для подключения к устройству BLE. Существует запись CBCharacteristic, которая должна использоваться для записи определенного значения с устройства iOS, и после успешной записи устройство меняет свои службы, добавляя новую службу в свою таблицу GATT. CoreBluetooth, похоже, не распознает обновленные сервисы. Устройство BLE сопряжено с iOS.

Что пыталось и что случилось:

  • Если вы не внедрили didModifyServices из CBPeripheralDelegate, CoreBluetooth жалуется, что службы изменен, но у вас нет метода для обработки этих изменений
  • Если вы реализуете didModifyServices, он никогда не будет вызван, и вышеупомянутое сообщение никогда не будет показано
  • Попытка ручного повторного обнаружения служб после успешного завершения пишите, но CBPerpiheral возвращает только старую службу, несмотря на то, что устройство BLE фактически имеет еще одну в таблице GATT, как будто CoreBluetooth кэширует старые службы и фактически не открывает их заново
  • Попытка отсоединения и отбрасывания экземпляра CBPeripheral для его повторного обнаружения и повторного подключения - в этом случае периферийное устройство никогда не переоткрывается снова после отключения.

Любые идеи, которые можно было бы сделать, чтобы иметь возможность восстановить новый сервис?

Соответствующий код CoreBluetooth:

enum BluetoothStateChange
{
    case unknown
    case unsupported
    case unauthorized
    case poweredOff
}

class BluetoothCentralService: NSObject
{
    var onStateChange: ((BluetoothStateChange) -> Void)?
    var onServicesScannedPeripheralsUpdate: ((_ peripherals: [UUID]) -> Void)?
    var onCharacteristicsDiscovered: ((_ peripheral: UUID, _ service: CBUUID, _ characteristics: [CBUUID]) -> Void)?
    var onCharacteristicWriteFinished: ((_ peripheral: UUID, _ characteristic: CBUUID, _ isSuccessful: Bool) -> Void)?
    var onCharacteristicReadFinished: ((_ peripheral: UUID, _ characteristic: CBUUID, _ value: Data?) -> Void)?

    private var connectingPeripherals = Set<CBPeripheral>()
    private var connectedPeripherals = Set<CBPeripheral>()
    private var servicesDiscoveredPeripherals = Set<CBPeripheral>()
    private var ignoredPeripherals = Set<CBPeripheral>()

    private let central: CBCentralManager
    private let logger: LoggerProtocol

    var rememberedDeviceUUID: UUID?

    init(central: CBCentralManager, logger: LoggerProtocol, rememberedDeviceUUID: UUID? = nil)
    {
        self.central = central
        self.logger = logger
        self.rememberedDeviceUUID = rememberedDeviceUUID

        super.init()

        central.delegate = self
    }

    func ignore(_ uuid: UUID)
    {
        guard
            let peripheral = connectedPeripherals.first(where: { $0.identifier == uuid })
                ?? servicesDiscoveredPeripherals.first(where: { $0.identifier == uuid })
        else {
            return
        }

        ignoredPeripherals.insert(peripheral)
        servicesDiscoveredPeripherals.remove(peripheral)
        central.cancelPeripheralConnection(peripheral)
    }

    func services(for uuid: UUID) -> [CBUUID]
    {
        return servicesDiscoveredPeripherals.first { $0.identifier == uuid }?.services?.map { $0.uuid } ?? []
    }

    func discoverCharacteristics(for peripheralUuid: UUID, service uuid: CBUUID)
    {
        guard
            let peripheral = servicesDiscoveredPeripherals.first(where: { $0.identifier == peripheralUuid }),
            let service = peripheral.service(with: uuid)
        else {
            return
        }

        peripheral.discoverCharacteristics(nil, for: service)
    }

    func write(
        to peripheralUuid: UUID,
        service serviceUuid: CBUUID,
        characteristic characteristicUuid: CBUUID,
        value: Data,
        type: CBCharacteristicWriteType
    ){
        guard
            let peripheral = servicesDiscoveredPeripherals.first(where: { $0.identifier == peripheralUuid }),
            let service = peripheral.service(with: serviceUuid),
            let characteristic = service.characteristics?.first(where: { $0.uuid == characteristicUuid })
        else {
            return
        }

        peripheral.writeValue(value, for: characteristic, type: type)
    }

    func read(from peripheralUuid: UUID, service serviceUuid: CBUUID, characteristic characteristicUuid: CBUUID)
    {
        guard
            let peripheral = servicesDiscoveredPeripherals.first(where: { $0.identifier == peripheralUuid }),
            let service = peripheral.service(with: serviceUuid),
            let characteristic = service.characteristics?.first(where: { $0.uuid == characteristicUuid })
        else {
            return
        }

        peripheral.readValue(for: characteristic)
    }
}

extension BluetoothCentralService: CBCentralManagerDelegate
{
    func centralManagerDidUpdateState(_ central: CBCentralManager)
    {
        switch central.state
        {
        case .resetting:
            break
        case .unknown:
            onStateChange?(.unknown)
        case .unsupported:
            onStateChange?(.unsupported)
        case .unauthorized:
            onStateChange?(.unauthorized)
        case .poweredOff:
            onStateChange?(.poweredOff)
            connectedPeripherals = []
            servicesDiscoveredPeripherals = []
            ignoredPeripherals = []
            onServicesScannedPeripheralsUpdate?([])
        case .poweredOn:
            scanRemembered()
        default:
            logger.log(.warning, "\(#function): Unhandled state type.")
        }
    }

    private func scanRemembered()
    {
        guard
            let uuid = rememberedDeviceUUID,
            let peripheral = central.retrievePeripherals(withIdentifiers: [uuid]).first
        else {
            scanConnected(); return
        }

        connect(peripheral)
    }

    private func scanConnected()
    {
        let connected = central.retrieveConnectedPeripherals(withServices: [])

        connectedPeripherals.formUnion(connected)
        connectedPeripherals.forEach { $0.discoverServices(nil) }

        scanNew()
    }

    private func scanNew()
    {
        central.scanForPeripherals(withServices: nil)
    }

    private func connect(_ peripheral: CBPeripheral)
    {
        guard
            !ignoredPeripherals.contains(peripheral),
            !connectingPeripherals.contains(peripheral),
            !connectedPeripherals.contains(peripheral),
            !servicesDiscoveredPeripherals.contains(peripheral)
        else {
            return
        }

        connectingPeripherals.insert(peripheral)
        peripheral.delegate = self

        central.connect(peripheral)
    }

    func centralManager(
        _ central: CBCentralManager,
        didDiscover peripheral: CBPeripheral,
        advertisementData: [String : Any],
        rssi RSSI: NSNumber
    ){
        guard
            !ignoredPeripherals.contains(peripheral),
            !connectingPeripherals.contains(peripheral),
            !connectedPeripherals.contains(peripheral),
            !servicesDiscoveredPeripherals.contains(peripheral)
        else {
            return
        }

        connect(peripheral)
    }

    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral)
    {
        connectingPeripherals.remove(peripheral)
        connectedPeripherals.insert(peripheral)

        peripheral.discoverServices(nil)
    }

    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?)
    {
        connectedPeripherals.remove(peripheral)
    }
}

extension BluetoothCentralService: CBPeripheralDelegate
{
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?)
    {
        guard error == nil else { return }

        servicesDiscoveredPeripherals.insert(peripheral)
        connectedPeripherals.remove(peripheral)

        onServicesScannedPeripheralsUpdate?(servicesDiscoveredPeripherals.map { $0.identifier })
    }

    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)
    {
        onCharacteristicsDiscovered?(
            peripheral.identifier,
            service.uuid,
            service.characteristics?.compactMap { $0.uuid } ?? []
        )
    }

    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?)
    {
        onCharacteristicWriteFinished?(peripheral.identifier, characteristic.uuid, error == nil)
    }

    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)
    {
        onCharacteristicReadFinished?(peripheral.identifier, characteristic.uuid, characteristic.value)
    }

    func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService])
    {
        // This never gets called if it's actually implemented here!
    }
}
...