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
相关产品推荐
相关产品推荐

