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.
374 lines
13 KiB
374 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
|
|
internal var dataEventSink: FlutterEventSink?
|
|
internal 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)
|
|
|
|
// 注册事件通道 - 为每个通道创建独立的handler
|
|
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())
|
|
|
|
// 创建独立的StreamHandler
|
|
let dataHandler = BleDataStreamHandler(instance)
|
|
let statusHandler = BleStatusStreamHandler(instance)
|
|
|
|
dataEventChannel.setStreamHandler(dataHandler)
|
|
statusEventChannel.setStreamHandler(statusHandler)
|
|
}
|
|
|
|
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":
|
|
#if canImport(AccessorySetupKit) && swift(>=5.9)
|
|
if #available(iOS 18.0, *) {
|
|
let pairedUUID = BleAccessorySetup.shared.getPairedMacAddress()
|
|
result(pairedUUID)
|
|
} else {
|
|
result(nil)
|
|
}
|
|
#else
|
|
result(nil)
|
|
#endif
|
|
|
|
|
|
|
|
case "isBleConnected":
|
|
result(BleService.shared.isConnected())
|
|
|
|
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: - BleService.Callback
|
|
@available(iOS 13.0, *)
|
|
extension SwiftBleServicePlugin: BleService.Callback {
|
|
public func onScanResult(devices: [[String: Any]]) {
|
|
if let eventSink = statusEventSink {
|
|
DispatchQueue.main.async {
|
|
eventSink([
|
|
"type": "scanResult",
|
|
"devices": devices
|
|
])
|
|
}
|
|
}
|
|
}
|
|
|
|
public func onConnectionStateChanged(state: Int) {
|
|
if let eventSink = statusEventSink {
|
|
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))"
|
|
}
|
|
|
|
DispatchQueue.main.async {
|
|
eventSink([
|
|
"type": "connectionStateChanged",
|
|
"state": state,
|
|
"stateStr": stateStr
|
|
])
|
|
}
|
|
}
|
|
}
|
|
|
|
public func onAudioDataReceived(data: Data) {
|
|
if let eventSink = dataEventSink {
|
|
DispatchQueue.main.async {
|
|
eventSink([
|
|
"type": "audioData",
|
|
"data": FlutterStandardTypedData(bytes: data)
|
|
])
|
|
}
|
|
}
|
|
}
|
|
|
|
public func onWakeupSignalReceived() {
|
|
if let eventSink = statusEventSink {
|
|
DispatchQueue.main.async {
|
|
eventSink([
|
|
"type": "wakeup"
|
|
])
|
|
}
|
|
}
|
|
}
|
|
|
|
public func onDeviceInfoReceived(infoType: Int, infoData: [String: Any]) {
|
|
if let eventSink = statusEventSink {
|
|
var resultMap = infoData
|
|
resultMap["type"] = "deviceInfo"
|
|
resultMap["result"] = infoType
|
|
|
|
DispatchQueue.main.async {
|
|
eventSink(resultMap)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 独立的StreamHandler类
|
|
@available(iOS 13.0, *)
|
|
class BleDataStreamHandler: NSObject, FlutterStreamHandler {
|
|
private weak var plugin: SwiftBleServicePlugin?
|
|
|
|
init(_ plugin: SwiftBleServicePlugin) {
|
|
self.plugin = plugin
|
|
}
|
|
|
|
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
|
|
plugin?.dataEventSink = events
|
|
return nil
|
|
}
|
|
|
|
func onCancel(withArguments arguments: Any?) -> FlutterError? {
|
|
plugin?.dataEventSink = nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
@available(iOS 13.0, *)
|
|
class BleStatusStreamHandler: NSObject, FlutterStreamHandler {
|
|
private weak var plugin: SwiftBleServicePlugin?
|
|
|
|
init(_ plugin: SwiftBleServicePlugin) {
|
|
self.plugin = plugin
|
|
}
|
|
|
|
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
|
|
plugin?.statusEventSink = events
|
|
return nil
|
|
}
|
|
|
|
func onCancel(withArguments arguments: Any?) -> FlutterError? {
|
|
plugin?.statusEventSink = nil
|
|
return nil
|
|
}
|
|
}
|