You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
301 lines
11 KiB
301 lines
11 KiB
import Flutter
|
|
import UIKit
|
|
import CoreBluetooth
|
|
import os
|
|
|
|
@available(iOS 13.0, *)
|
|
public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
|
|
// MARK: - 属性
|
|
private var methodChannel: FlutterMethodChannel
|
|
private var dataEventSink: FlutterEventSink?
|
|
private var statusEventSink: FlutterEventSink?
|
|
|
|
// MARK: - 初始化方法
|
|
init(_ channel: FlutterMethodChannel) {
|
|
self.methodChannel = channel
|
|
super.init()
|
|
|
|
// 设置BLE服务回调
|
|
BleService.shared.setDelegate(self)
|
|
|
|
// 初始化AccessorySetup
|
|
BleAccessorySetup.shared.setBleService(BleService.shared)
|
|
BleAccessorySetup.shared.setDeviceDiscoveredCallback { [weak self] uuid in
|
|
self?.handleDiscoveredDevice(uuid: uuid)
|
|
}
|
|
BleAccessorySetup.shared.initializeDiscoverySession()
|
|
}
|
|
|
|
// MARK: - FlutterPlugin Protocol
|
|
public static func register(with registrar: FlutterPluginRegistrar) {
|
|
let channel = FlutterMethodChannel(name: "com.yunqiinnovation.ble_service", binaryMessenger: registrar.messenger())
|
|
let instance = SwiftBleServicePlugin(channel)
|
|
registrar.addMethodCallDelegate(instance, channel: channel)
|
|
|
|
// 注册事件通道
|
|
let dataEventChannel = FlutterEventChannel(name: "com.yunqiinnovation.ble_service/data_events", binaryMessenger: registrar.messenger())
|
|
let statusEventChannel = FlutterEventChannel(name: "com.yunqiinnovation.ble_service/status_events", binaryMessenger: registrar.messenger())
|
|
|
|
dataEventChannel.setStreamHandler(instance)
|
|
statusEventChannel.setStreamHandler(instance)
|
|
}
|
|
|
|
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
|
switch call.method {
|
|
case "isConnected":
|
|
result(BleService.shared.isConnected())
|
|
|
|
case "getBatteryLevel":
|
|
// 获取电池信息,通过回调返回结果
|
|
if BleService.shared.getBatteryInfo() {
|
|
result(0) // 通过回调返回实际值
|
|
} else {
|
|
result(FlutterError(code: "UNAVAILABLE",
|
|
message: "无法获取电池信息",
|
|
details: nil))
|
|
}
|
|
|
|
case "isCompanionSupported":
|
|
// 检查设备是否支持iOS 18的AccessorySetupKit功能
|
|
#if canImport(AccessorySetupKit)
|
|
if #available(iOS 18.0, *) {
|
|
// 设备运行iOS 18+,支持AccessorySetupKit
|
|
result(true)
|
|
} else {
|
|
// 设备运行iOS 17或更低版本
|
|
result(false)
|
|
}
|
|
#else
|
|
// 编译时没有AccessorySetupKit框架
|
|
result(false)
|
|
#endif
|
|
|
|
case "getPairedMacAddress":
|
|
// 获取已配对设备的标识符
|
|
#if canImport(AccessorySetupKit)
|
|
if #available(iOS 18.0, *) {
|
|
if let wrapper = BleAccessorySetup.shared.accessorySessionWrapper as? BleAccessorySetup.AccessorySessionWrapper {
|
|
// 检查是否有配对设备
|
|
if let firstAccessory = wrapper.session.accessories.first,
|
|
let btId = firstAccessory.bluetoothIdentifier {
|
|
// 在iOS中,我们返回UUID字符串作为"MAC地址"的等价物
|
|
result(btId.uuidString)
|
|
} else {
|
|
// 无配对设备
|
|
result(nil)
|
|
}
|
|
} else {
|
|
// 会话未初始化
|
|
result(nil)
|
|
}
|
|
} else {
|
|
// iOS版本太低
|
|
result(nil)
|
|
}
|
|
#else
|
|
// 编译环境不支持
|
|
result(nil)
|
|
#endif
|
|
|
|
case "registerBackgroundScan":
|
|
// iOS通过AccessorySetupKit自动处理后台扫描,不需要单独注册
|
|
#if canImport(AccessorySetupKit)
|
|
if #available(iOS 18.0, *) {
|
|
// 在iOS 18+上,不需要额外操作
|
|
result(true)
|
|
} else {
|
|
// iOS版本太低
|
|
result(false)
|
|
}
|
|
#else
|
|
result(false)
|
|
#endif
|
|
|
|
case "unregisterBackgroundScan":
|
|
// iOS通过AccessorySetupKit自动处理后台扫描,不需要单独注销
|
|
result(true)
|
|
|
|
case "clearAssociations":
|
|
// 清除设备关联
|
|
#if canImport(AccessorySetupKit)
|
|
if #available(iOS 18.0, *) {
|
|
if let wrapper = BleAccessorySetup.shared.accessorySessionWrapper as? BleAccessorySetup.AccessorySessionWrapper {
|
|
// 遍历所有配对设备
|
|
for accessory in wrapper.session.accessories {
|
|
if let btId = accessory.bluetoothIdentifier {
|
|
// 移除设备的系统处理已经集成在设置中,这里什么都不做
|
|
os_log("设备需要在系统设置中手动移除: %{public}@",
|
|
log: OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService"),
|
|
type: .info,
|
|
btId.uuidString)
|
|
}
|
|
}
|
|
}
|
|
// 返回成功
|
|
result(true)
|
|
} else {
|
|
// iOS版本太低
|
|
result(false)
|
|
}
|
|
#else
|
|
result(false)
|
|
#endif
|
|
|
|
case "getDeviceVersion":
|
|
result(BleService.shared.getDeviceVersion())
|
|
|
|
case "getProductId":
|
|
result(BleService.shared.getProductId())
|
|
|
|
case "getBatteryInfo":
|
|
result(BleService.shared.getBatteryInfo())
|
|
|
|
case "openEncoder":
|
|
result(BleService.shared.openEncoder())
|
|
|
|
case "openDecoder":
|
|
result(BleService.shared.openDecoder())
|
|
|
|
case "openA2DPDecoder":
|
|
result(BleService.shared.openA2DPDecoder())
|
|
|
|
case "closeCodec":
|
|
result(BleService.shared.closeCodec())
|
|
|
|
case "playA2DP":
|
|
result(BleService.shared.playA2DP())
|
|
|
|
// iOS 18+ AccessorySetupKit相关方法
|
|
case "startDeviceAssociation":
|
|
#if canImport(AccessorySetupKit)
|
|
if #available(iOS 18.0, *) {
|
|
BleAccessorySetup.shared.showDevicePicker()
|
|
result(true)
|
|
} else {
|
|
result(FlutterError(code: "UNAVAILABLE",
|
|
message: "AccessorySetupKit需要iOS 18或更高版本",
|
|
details: nil))
|
|
}
|
|
#else
|
|
result(FlutterError(code: "UNAVAILABLE",
|
|
message: "编译环境不支持AccessorySetupKit",
|
|
details: nil))
|
|
#endif
|
|
|
|
default:
|
|
result(FlutterMethodNotImplemented)
|
|
}
|
|
}
|
|
|
|
// MARK: - 私有方法
|
|
private func handleDiscoveredDevice(uuid: UUID) {
|
|
BleAccessorySetup.shared.handleDiscoveredAccessory(uuid: uuid)
|
|
}
|
|
|
|
private func sendEvent<T>(_ eventSink: FlutterEventSink?, data: T, errorPrefix: String) {
|
|
if let eventSink = eventSink {
|
|
DispatchQueue.main.async {
|
|
eventSink(data)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - FlutterStreamHandler
|
|
@available(iOS 13.0, *)
|
|
extension SwiftBleServicePlugin: FlutterStreamHandler {
|
|
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
|
|
if let args = arguments as? [String: Any], let type = args["type"] as? String {
|
|
if type == "data" {
|
|
dataEventSink = events
|
|
} else if type == "status" {
|
|
statusEventSink = events
|
|
}
|
|
} else {
|
|
// 默认行为:根据注册的通道来确定事件类型
|
|
if arguments == nil {
|
|
// 如果无参数,根据函数被调用的顺序确定
|
|
if dataEventSink == nil {
|
|
dataEventSink = events
|
|
} else {
|
|
statusEventSink = events
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
|
|
if let args = arguments as? [String: Any], let type = args["type"] as? String {
|
|
if type == "data" {
|
|
dataEventSink = nil
|
|
} else if type == "status" {
|
|
statusEventSink = nil
|
|
}
|
|
} else {
|
|
// 同时清除两个sink
|
|
dataEventSink = nil
|
|
statusEventSink = nil
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// MARK: - BleService.Callback
|
|
@available(iOS 13.0, *)
|
|
extension SwiftBleServicePlugin: BleService.Callback {
|
|
func onScanResult(devices: [[String : Any]]) {
|
|
sendEvent(statusEventSink,
|
|
data: ["type": "scanResult", "devices": devices],
|
|
errorPrefix: "发送扫描结果异常")
|
|
}
|
|
|
|
func onConnectionStateChanged(state: Int) {
|
|
var stateStr = "Unknown"
|
|
switch state {
|
|
case BleConst.STATE_DISCONNECTED:
|
|
stateStr = "Disconnected"
|
|
case BleConst.STATE_CONNECTING:
|
|
stateStr = "Connecting"
|
|
case BleConst.STATE_CONNECTED:
|
|
stateStr = "Connected"
|
|
case BleConst.STATE_DISCONNECTING:
|
|
stateStr = "Disconnecting"
|
|
case BleConst.STATE_ERROR:
|
|
stateStr = "Connection Error"
|
|
default:
|
|
stateStr = "Unknown State(\(state))"
|
|
}
|
|
|
|
let stateMap: [String: Any] = [
|
|
"type": "connectionStateChanged",
|
|
"state": state,
|
|
"stateStr": stateStr
|
|
]
|
|
|
|
sendEvent(statusEventSink, data: stateMap, errorPrefix: "发送连接状态异常")
|
|
}
|
|
|
|
func onAudioDataReceived(data: Data) {
|
|
let dataMap: [String: Any] = [
|
|
"type": "audioData",
|
|
"data": data
|
|
]
|
|
|
|
sendEvent(dataEventSink, data: dataMap, errorPrefix: "发送音频数据异常")
|
|
}
|
|
|
|
func onDeviceInfoReceived(infoType: Int, infoData: [String : Any]) {
|
|
var dataMap: [String: Any] = [
|
|
"type": "deviceInfo",
|
|
"infoType": infoType
|
|
]
|
|
|
|
// 合并infoData
|
|
for (key, value) in infoData {
|
|
dataMap[key] = value
|
|
}
|
|
|
|
sendEvent(statusEventSink, data: dataMap, errorPrefix: "发送设备信息异常")
|
|
}
|
|
}
|