vcc-gnd / YD-ESP32-S3

YD-ESP32-S3 Micropython VCC-GND Studio
122 stars 23 forks source link

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

Open xianengqi opened 1 year ago

xianengqi commented 1 year ago

遇到的问题是,文件大小只有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 commented 1 year ago

IMG_2812