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.

359 lines
13 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?
private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleServicePlugin")
// 标记是否已初始化所有服务
private var servicesInitialized = false
// MARK: - 初始化方法
init(_ channel: FlutterMethodChannel) {
self.methodChannel = channel
super.init()
#if canImport(AccessorySetupKit) && swift(>=5.9)
if #available(iOS 18.0, *) {
// iOS 18环境下,只设置回调但不主动初始化会话
let bleAccessorySetup = BleAccessorySetup.shared
// 在accessoryAdded回调中延迟设置BleService
bleAccessorySetup.setDeviceDiscoveredCallback { [weak self] uuid in
// 确保服务已初始化
self?.ensureServicesInitialized()
// 然后处理发现的设备
self?.handleDiscoveredDevice(uuid: uuid)
}
// 注释掉自动初始化,改为等待用户主动调用startDeviceAssociation
// bleAccessorySetup.initializeSession()
os_log("iOS 18环境:已设置设备发现回调,等待用户主动调用关联", log: logger, type: .info)
} else {
// 低于iOS 18,直接初始化所有服务
initializeAllServices()
}
#else
// 非AccessorySetupKit环境,直接初始化所有服务
initializeAllServices()
#endif
}
// 确保所有服务都已初始化
private func ensureServicesInitialized() {
if !servicesInitialized {
// 设置BLE服务回调
BleService.shared.addCallback(self)
// 设置BleService到AccessorySetup
BleAccessorySetup.shared.setBleService(BleService.shared)
servicesInitialized = true
}
}
// 初始化所有服务(iOS 17及以下版本的标准行为)
private func initializeAllServices() {
// 设置BLE服务回调
BleService.shared.addCallback(self)
// 初始化AccessorySetup,但不自动开始会话
BleAccessorySetup.shared.setBleService(BleService.shared)
BleAccessorySetup.shared.setDeviceDiscoveredCallback { [weak self] uuid in
self?.handleDiscoveredDevice(uuid: uuid)
}
// 移除自动初始化,统一由startDeviceAssociation方法触发
// BleAccessorySetup.shared.initializeSession()
os_log("非iOS 18环境:已设置BLE服务但不自动初始化会话", log: logger, type: .info)
servicesInitialized = true
}
// 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) {
// 检查是否需要初始化服务
checkAndInitializeServices()
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":
#if canImport(AccessorySetupKit) && swift(>=5.9)
if #available(iOS 18.0, *) {
result(true)
} else {
result(false)
}
#else
result(false)
#endif
case "getPairedMacAddress":
// 在新的实现中我们不再直接提供已配对设备
os_log("iOS版本使用AccessorySetupKit自动管理配对", log: logger, type: .debug)
result(nil)
case "registerBackgroundScan", "unregisterBackgroundScan":
// iOS上禁用,直接返回成功以避免Flutter端报错
result(true)
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 "clearAssociations":
// 清除设备配对关系
#if canImport(AccessorySetupKit) && swift(>=5.9)
if #available(iOS 18.0, *) {
os_log("清除设备配对关系", log: logger, type: .info)
BleAccessorySetup.shared.clearAssociations()
result(true)
} else {
result(true)
}
#else
result(true)
#endif
case "startDeviceAssociation":
// 用户主动触发设备关联流程
#if canImport(AccessorySetupKit) && swift(>=5.9)
if #available(iOS 18.0, *) {
os_log("用户主动触发设备关联流程", log: logger, type: .info)
// 确保服务已初始化
ensureServicesInitialized()
// 重新初始化会话
BleAccessorySetup.shared.initializeSession()
// 返回成功,实际配对结果会通过回调通知
result(true)
} else {
os_log("当前iOS版本不支持AccessorySetupKit", log: logger, type: .info)
result(false)
}
#else
os_log("当前环境不支持AccessorySetupKit", log: logger, type: .info)
result(false)
#endif
default:
result(FlutterMethodNotImplemented)
}
}
// MARK: - 私有方法
// 检查并按需初始化服务
private func checkAndInitializeServices() {
#if canImport(AccessorySetupKit) && swift(>=5.9)
if #available(iOS 18.0, *) {
ensureServicesInitialized()
}
#endif
}
// AccessorySetupKit发现设备的处理
private func handleDiscoveredDevice(uuid: UUID) {
// BleAccessorySetup内部已进行连接,此处不再重复调用
// os_log("收到设备发现回调: %{public}@", log: logger, type: .info, uuid.uuidString)
// 可以在这里添加设备记录或其他操作
}
private func sendEvent<T>(_ eventSink: FlutterEventSink?, data: T, errorPrefix: String) {
if let eventSink = eventSink {
DispatchQueue.main.async {
eventSink(data)
}
}
}
// 获取顶层视图控制器的辅助方法
private func getTopViewController(_ viewController: UIViewController) -> UIViewController {
if let presented = viewController.presentedViewController {
return getTopViewController(presented)
}
if let tabBarController = viewController as? UITabBarController,
let selected = tabBarController.selectedViewController {
return getTopViewController(selected)
}
if let navigationController = viewController as? UINavigationController,
let visibleViewController = navigationController.visibleViewController {
return getTopViewController(visibleViewController)
}
return viewController
}
}
// 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 {
public func onScanResult(devices: [[String: Any]]) {
if let eventSink = statusEventSink {
DispatchQueue.main.async {
eventSink([
"event": "scanResult",
"devices": devices
])
}
}
}
public func onConnectionStateChanged(state: Int) {
if let eventSink = statusEventSink {
var stateDesc = "未知"
switch state {
case BleConst.STATE_DISCONNECTED:
stateDesc = "已断开"
case BleConst.STATE_CONNECTING:
stateDesc = "连接中"
case BleConst.STATE_CONNECTED:
stateDesc = "已连接"
case BleConst.STATE_DISCONNECTING:
stateDesc = "断开中"
case BleConst.STATE_ERROR:
stateDesc = "错误"
default:
break
}
DispatchQueue.main.async {
eventSink([
"event": "connectionStateChanged",
"state": state,
"stateDesc": stateDesc
])
}
}
}
public func onAudioDataReceived(data: Data) {
if let eventSink = dataEventSink {
DispatchQueue.main.async {
eventSink([
"event": "audioDataReceived",
"data": FlutterStandardTypedData(bytes: data)
])
}
}
}
public func onWakeupSignalReceived() {
if let eventSink = statusEventSink {
DispatchQueue.main.async {
eventSink([
"event": "wakeupSignalReceived"
])
}
}
}
public func onDeviceInfoReceived(infoType: Int, infoData: [String: Any]) {
if let eventSink = statusEventSink {
DispatchQueue.main.async {
eventSink([
"event": "deviceInfoReceived",
"infoType": infoType,
"infoData": infoData
])
}
}
}
}