Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

您好,我是一名iOS开发者,我开发的功能很简单,蓝牙传输文件 #6

Open
xianengqi opened this issue Aug 18, 2023 · 1 comment

Comments

@xianengqi
Copy link

遇到的问题是,文件大小只有300kb,使用蓝牙传输到此IOT设备时,传输速度只有7kb/s ,MTU值我设置的是500字节,优化了好几天了传输速率始终上不来。
这是我的完整代码

import CoreBluetooth
import SwiftUI

struct ContentView: View {
  @State private var fileURL: URL? = nil
  @State private var status: String = "Not Connected"
  @StateObject private var bluetoothManager = BluetoothManager()

  var transferRateText: String {
    let transferRateKB = bluetoothManager.transferRate / 1024
    if transferRateKB < 1024 {
      return String(format: "%.2f KB/s", transferRateKB)
    } else {
      let transferRateMB = transferRateKB / 1024
      return String(format: "%.2f MB/s", transferRateMB)
    }
  }

  var body: some View {
    VStack {
      Button("Select File") {
        // TODO: Select file
        selectFile()
      }
      .padding()

      Text("Status: \(bluetoothManager.status)")
        .padding()
      Text("Transfer Rate: \(transferRateText)")
        .padding()

      Text("Total Time: \(bluetoothManager.totalTime, specifier: "%.2f") seconds")
        .padding()
      List(bluetoothManager.discoveredDevices, id: \.identifier) { device in
        HStack {
          Text(device.name ?? "Unknown")
          Spacer()
          Button("Connect") {
            bluetoothManager.connectToDevice(device)
          }
        }
      }
    }
  }

  private func selectFile() {
    bluetoothManager.presentFilePicker()
  }
}

#Preview {
  ContentView()
}

class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate {
  private var centralManager: CBCentralManager!
  private var peripheral: CBPeripheral?
  public var fileTransferCharacteristic: CBCharacteristic?
  @Published var status: String = "Not Connected"
  @Published var discoveredDevices: [CBPeripheral] = []
  private var startTime: Date?
  private var endTime: Date?
  var fileData: Data!
  @Published var transferRate: Double = 0.0
  @Published var totalTime: Double = 0.0
  private var connectionParameters: [String: Any]?

  override init() {
    super.init()
    centralManager = CBCentralManager(delegate: self, queue: nil)
  }

  func centralManagerDidUpdateState(_ central: CBCentralManager) {
    if central.state == .poweredOn {
      centralManager.scanForPeripherals(withServices: nil, options: nil)
    }
  }

  // TODO: Implement other delegate methods

  func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
    if !discoveredDevices.contains(where: { $0.identifier == peripheral.identifier }) {
      discoveredDevices.append(peripheral)
    }
  }

  func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
    print("Connected to peripheral: \(peripheral.name ?? "Unknown")")
    status = "Connected to \(peripheral.name ?? "Unknown")"
    peripheral.delegate = self
    peripheral.discoverServices(nil)
  }

  func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
    if let error = error {
      print("Error discovering services: \(error)")
      return
    }

    for service in peripheral.services ?? [] {
      print("Discovered service: \(service.uuid)")
      peripheral.discoverCharacteristics(nil, for: service)
    }
  }

  func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
    if let error = error {
      print("Error discovering characteristics: \(error)")
      return
    }

    for characteristic in service.characteristics ?? [] {
      print("Discovered characteristic: \(characteristic.uuid)")
      if characteristic.uuid == CBUUID(string: "0000ff01-0000-1000-8000-00805f9b34fb") { // Replace with your characteristic UUID
        fileTransferCharacteristic = characteristic
        // Check MTU size
        let mtu = peripheral.maximumWriteValueLength(for: .withResponse)
        print("MTU size: \(mtu) bytes")
      }
    }
  }

  public func sendFileData(_ data: Data, to characteristic: CBCharacteristic?) {
    guard let characteristic = characteristic else { return }
//    let mtu = 500
    let mtu = peripheral!.maximumWriteValueLength(for: .withResponse)
    var offset = 0

    DispatchQueue.global(qos: .background).async { // Move to a background thread
      self.startTime = Date() // Record the start time

      let writeType: CBCharacteristicWriteType = characteristic.properties.contains(.writeWithoutResponse) ? .withoutResponse : .withResponse

      while offset < data.count {
        let chunkSize = min(mtu, data.count - offset)
        let chunk = data.subdata(in: offset ..< offset + chunkSize)
        self.peripheral?.writeValue(chunk, for: characteristic, type: writeType)
        offset += chunkSize
      }
    }
  }

  func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
    if let error = error {
      print("Error writing value: \(error)")
      status = "Error: \(error)"
      return
    }

    print("Successfully wrote value for characteristic: \(characteristic.uuid)")

    endTime = Date() // Record the end time

    if let startTime = startTime, let endTime = endTime {
      let elapsedTime = endTime.timeIntervalSince(startTime)
      let fileSize = Double(fileData.count)
      let transferRateValue = fileSize / elapsedTime // bytes per second

      print("File transferred successfully")
      print("Transfer Rate: \(transferRateValue) B/s")
      print("Total Time: \(elapsedTime) seconds")

      // Update the published variables
      transferRate = transferRateValue
      totalTime = elapsedTime
    }
  }

  func presentFilePicker() {
    let filePicker = UIDocumentPickerViewController(documentTypes: ["public.data"], in: .import)
    filePicker.delegate = self
    UIApplication.shared.windows.first?.rootViewController?.present(filePicker, animated: true, completion: nil)
  }

  func connectToDevice(_ device: CBPeripheral) {
    peripheral = device
    peripheral?.delegate = self
    centralManager.connect(peripheral!, options: nil)
  }
}

extension BluetoothManager: UIDocumentPickerDelegate {
  func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
    guard let url = urls.first else { return }

    // Create a temporary directory for the file
    let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(url.lastPathComponent)

    do {
      // Check if file exists at temporary directory
      if FileManager.default.fileExists(atPath: tempURL.path) {
        // Remove the file if it exists
        try FileManager.default.removeItem(at: tempURL)
      }

      // Copy the file to the temporary directory
      try FileManager.default.copyItem(at: url, to: tempURL)

      // Read the file data
//      let fileData = try Data(contentsOf: tempURL)
      fileData = try Data(contentsOf: tempURL)

      // Send the file data
      sendFileData(fileData, to: fileTransferCharacteristic)
    } catch {
      print("Error reading file data: \(error)")
      status = "Error: \(error.localizedDescription)"
    }
  }
}

@xianengqi
Copy link
Author

IMG_2812

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant