You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Arduino HM-10蓝牙与iOS连接成功但无法传输数据及导航异常

SwiftUI iOS应用与HM-10蓝牙模块通信问题

我正在开发基于SwiftUI的iOS应用,实现与Arduino的HM-10蓝牙模块连接,并将iOS的当前纬度值发送给Arduino。目前已完成设备选择界面,可正常显示设备UUID与名称;Arduino端能通过LiquidCrystal检测到连接(显示OK+CONN)与断开状态(显示OK+LOST),但点击“Fetch Location”按钮后,Arduino端无任何响应。此外,点击选择设备时会自动导航至MainView,原因暂未查明(此问题优先级较低)。曾尝试移植UIKit示例项目但未成功。


代码文件

ConnectView.swift

import SwiftUI
import CoreBluetooth
import Awesome

struct ConnectView: View {
    @State private var isDeviceSelectionPresented = false
    @State private var selectedDevice: CBPeripheral?
    @State private var isSendingData = false
    private var writeType: CBCharacteristicWriteType = .withoutResponse
    
    @StateObject private var bleManager = BLEManager()
    @StateObject private var locationDelegate = LocationDelegate()
    
    private var peripherals: [CBPeripheral] { bleManager.peripherals }
    private var txCharacteristic: CBCharacteristic?
    
    weak var writeCharacteristic: CBCharacteristic?
    
    var body: some View {
        NavigationView {
            VStack(spacing: 20) {
                HeadView(headTitle: "Cydial 장치 연결하기")
                Button(action: {
                    isDeviceSelectionPresented = true
                }) {
                    ZStack{
                        RoundedRectangle(cornerRadius: 8)
                            .stroke(Color.accentColor, lineWidth:2)
                            .frame(height:40)
                        HStack(spacing:10){
                            Image(systemName:"checkmark")
                            Text("Select Device")
                                .fontWeight(.semibold)
                        }
                    }
                }
                .sheet(isPresented: $isDeviceSelectionPresented) {
                    DeviceSelectionView(selectedDevice: $selectedDevice, isPresented: $isDeviceSelectionPresented)
                        .environmentObject(bleManager)
                }
                
                if let connectedDevice = bleManager.connectedPeripheral {
                    VStack(alignment:.leading ,spacing: 5) {
                        HStack{
                            Image(systemName:"checkmark.circle")
                            Text("Device Connected")
                        }
                        .font(.title3)
                        .fontWeight(.semibold)
                        Text("Device Name: \(connectedDevice.name ?? "Unknown")")
                        Text("UUID: \(connectedDevice.identifier.uuidString)")
                    }
                    .padding(.top, 10)
                }
                HStack{
                    Image(systemName:"location.circle")
                    Text("Current Latitude: \(locationDelegate.latitudeValue)")
                    Spacer()
                }
                
                Button(action: {
//                    sendMessageToDevice("\(locationDelegate.latitudeValue)")
                    sendDataToArduino()
                }) {
                    ZStack{
                        RoundedRectangle(cornerRadius: 8)
                            .stroke(Color.accentColor, lineWidth:2)
                            .frame(height:40)
                        HStack(spacing:10){
                            Image(systemName:"rectangle.portrait.and.arrow.forward")
                            Text("Fetch Location")
                                .fontWeight(.semibold)
                        }
                    }
                }
                Spacer()
            }
            .padding()
        }
    }
    
    func sendDataToArduino() {
        guard let peripheral = selectedDevice,
              let txCharacteristic = txCharacteristic,
              let dataToSend = "\(locationDelegate.latitudeValue)".data(using: .utf8) else {
            print("Error: Bluetooth is not ready.")
            return
        }
        
        isSendingData = true
        selectedDevice?.writeValue(dataToSend, for: txCharacteristic, type: writeType)
    }
    
    func sendMessageToDevice(_ message: String) {
        if let data = message.data(using: String.Encoding.utf8) {
            selectedDevice!.writeValue(data, for: writeCharacteristic!, type: writeType)
        }
    }
    
//    func sendBytesToDevice(_ bytes: [UInt8]) {
//        let data = Data(bytes: UnsafePointer<UInt8>(bytes), count: bytes.count)
//        selectedDevice!.writeValue(data, for: writeCharacteristic!, type: writeType)
//    }
//    func floatToUInt8(_ value: Float) -> UInt8? {
//        // Check if the value is within the valid range for UInt8
//        let max = (UInt32(UInt16.max) + 1) * UInt32(UInt32(UInt8.max) + 1) - 1
//                let int = UInt32(((self / maxRange + 1.0) / 2.0 * Float(max)).rounded())
//                let a = int.quotientAndRemainder(dividingBy: UInt32(UInt16.max) + 1)
//                let b = a.remainder.quotientAndRemainder(dividingBy: UInt32(UInt8.max) + 1)
//                return [UInt8(a.quotient), UInt8(b.quotient), UInt8(b.remainder), 0]
//    }

}

struct ConnectView_Previews: PreviewProvider {
    static var previews: some View {
        ConnectView()
    }
}

DeviceSelectionView.swift

import SwiftUI
import CoreBluetooth

struct DeviceSelectionView: View {
    @Binding var selectedDevice: CBPeripheral?
    @EnvironmentObject var bleManager: BLEManager
    @Binding var isPresented: Bool
    
    var body: some View {
        List(bleManager.peripherals, id: \.self) { peripheral in
            Button(action: {
                bleManager.connectToDevice(peripheral)
                isPresented = false
            }) {
                Text(peripheral.name ?? "Unknown Device")
            }
        }
        .onAppear {
            bleManager.startScanning()
        }
        .onDisappear {
            bleManager.stopScanning()
        }
        .onChange(of: bleManager.connectedPeripheral) { connectedPeripheral in
            if let selectedDevice = selectedDevice, selectedDevice == connectedPeripheral {
                isPresented = false
            }
        }
    }
}

struct DeviceSelectionView_Previews: PreviewProvider {
    static var previews: some View {
        DeviceSelectionView(selectedDevice: .constant(nil), isPresented: .constant(true))
            .environmentObject(BLEManager())
    }
}

BLEManager.swift

import Foundation
import CoreBluetooth
import CoreLocation

class LocationDelegate: NSObject, ObservableObject, CLLocationManagerDelegate {
    @Published var latitudeValue: Float = 0.0
    private let locationManager = CLLocationManager()
    
    override init() {
        super.init()
        locationManager.delegate = self
        locationManager.requestWhenInUseAuthorization()
        locationManager.startUpdatingLocation()
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let location = locations.last {
            latitudeValue = Float(location.coordinate.latitude)
        }
    }
}

class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate {
    var centralManager: CBCentralManager!
    @Published var peripherals: [CBPeripheral] = []
    @Published var connectedPeripheral: CBPeripheral?
    @Published var txCharacteristic: CBCharacteristic?
    
    override init() {
        super.init()
        centralManager = CBCentralManager(delegate: self, queue: nil)
    }
    
    func startScanning() {
        centralManager.scanForPeripherals(withServices: nil, options: nil)
    }
    
    func stopScanning() {
        centralManager.stopScan()
    }
    
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        if central.state == .poweredOn {
            startScanning()
        } else {
            print("Error: Bluetooth is not available.")
        }
    }
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        if !peripherals.contains(peripheral) {
            peripherals.append(peripheral)
        }
    }
    
    func connectToDevice(_ peripheral: CBPeripheral) {
        connectedPeripheral = peripheral
        centralManager.connect(peripheral, options: nil)
    }
    
    func disconnectFromDevice() {
        if let peripheral = connectedPeripheral {
            centralManager.cancelPeripheralConnection(peripheral)
            connectedPeripheral = nil
        }
    }
    
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        peripheral.delegate = self
        peripheral.discoverServices(nil)
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if let services = peripheral.services {
            for service in services {
                peripheral.discoverCharacteristics(nil, for: service)
            }
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if let characteristics = service.characteristics {
            for characteristic in characteristics {
                if characteristic.properties.contains(.write) {
                    txCharacteristic = characteristic
                }
            }
        }
    }
}

问题修复方案

1. 核心问题:数据发送无响应

关键原因
  • ConnectView本地txCharacteristic未关联BLEManager中已发现的可写特征值
  • selectedDevice未在设备连接时同步更新,始终为nil
  • 未针对HM-10模块的标准串口服务做精准匹配,可能匹配到无效特征
  • 发送数据未添加换行符,Arduino端无法识别数据结束标记
修复步骤
(1)修改ConnectView,复用BLEManager的属性

删除ConnectView中本地定义的txCharacteristic和writeCharacteristic,直接使用bleManager的已发现特征:

// 删除以下两行
// private var txCharacteristic: CBCharacteristic?
// weak var writeCharacteristic: CBCharacteristic?
(2)重构sendDataToArduino函数

使用bleManager的连接外设和特征值,添加换行符与日志:

func sendDataToArduino() {
    guard let peripheral = bleManager.connectedPeripheral,
          let txCharacteristic = bleManager.txCharacteristic,
          let dataToSend = "\(locationDelegate.latitudeValue)\n".data(using: .utf8) else {
        print("发送失败:外设未连接/特征值未找到/数据转换失败")
        print("当前连接外设:\(bleManager.connectedPeripheral?.name ?? "无")")
        print("可写特征值:\(bleManager.txCharacteristic ?? "无")")
        return
    }
    
    isSendingData = true
    peripheral.writeValue(dataToSend, for: txCharacteristic, type: writeType)
    print("已发送数据:\(locationDelegate.latitudeValue)")
}
(3)更新DeviceSelectionView的设备选择逻辑

点击设备时同步更新selectedDevice:

Button(action: {
    selectedDevice = peripheral // 新增此行
    bleManager.connectToDevice(peripheral)
    isPresented = false
}) {
    Text(peripheral.name ?? "Unknown Device")
}
(4)优化BLEManager的特征发现逻辑

精准匹配HM-10的标准串口服务(UUID: FFE0)和特征值(UUID: FFE1):

// 在BLEManager中添加UUID常量
private let hm10ServiceUUID = CBUUID(string: "FFE0")
private let hm10TxCharacteristicUUID = CBUUID(string: "FFE1")

// 修改didConnect方法
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
    peripheral.delegate = self
    peripheral.discoverServices([hm10ServiceUUID]) // 只扫描指定服务
}

// 修改didDiscoverServices方法
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
    if let error = error {
        print("发现服务失败:\(error.localizedDescription)")
        return
    }
    guard let services = peripheral.services else { return }
    for service in services {
        if service.uuid == hm10ServiceUUID {
            peripheral.discoverCharacteristics([hm10TxCharacteristicUUID], for: service)
        }
    }
}

// 修改didDiscoverCharacteristicsFor方法
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
    if let error = error {
        print("发现特征值失败:\(error.localizedDescription)")
        return
    }
    guard let characteristics = service.characteristics else { return }
    for characteristic in characteristics {
        if characteristic.uuid == hm10TxCharacteristicUUID && characteristic.properties.contains(.write) {
            txCharacteristic = characteristic
            print("已找到可写特征值:\(characteristic.uuid.uuidString)")
        }
    }
}

2. 自动导航至MainView问题排查(低优先级)

  • 检查ConnectView是否嵌套在NavigationStack/NavigationView中,是否存在未处理的NavigationLink
  • 排查DeviceSelectionView的onDisappear/onChange逻辑是否触发了导航动作
  • 检查App路由逻辑,是否在设备连接状态变化时自动跳转页面

内容的提问来源于stack exchange,提问作者Cyan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 04:44:59