5 changed files with 244 additions and 12 deletions
@ -0,0 +1,165 @@ |
|||
import Foundation |
|||
|
|||
#if canImport(JL_BLEKit) |
|||
import JL_BLEKit |
|||
#endif |
|||
|
|||
/// 把 JL_BLEKit 的 NSNotification(设备主动推送)翻译成 EventDispatcher |
|||
/// 事件。对齐 Android `event/DeviceInfoEventForwarder` + `CustomEventForwarder`: |
|||
/// |
|||
/// - `kJL_MANAGER_SYSTEM_INFO` / `kJL_MANAGER_TARGET_INFO` → `battery` |
|||
/// (耳机端电量变化、放回仓、设备主动同步系统信息时都会触发;这里只在 |
|||
/// `outputDeviceModel().battery` 真的变化时才往 Dart 抛,避免高频空包) |
|||
/// - `kJL_MANAGER_CALL_STATUS` → `phoneCallStatus` |
|||
/// (通话状态变化;BleManager._isCall 跟 InternalCleanupListener 都依赖它) |
|||
/// - `kJL_MANAGER_CUSTOM_DATA` → `expandFunction` |
|||
/// (耳机厂商私有协议主动上报,opCode=0x16/0x17 用来发起/停止通话翻译; |
|||
/// Android 的 `CustomEventForwarder.onExpandFunction` 等价路径) |
|||
/// |
|||
/// 通知 payload 格式来自 SDK 头注释: |
|||
/// `@{ kJL_MANAGER_KEY_UUID: uuid, kJL_MANAGER_KEY_OBJECT: object }` |
|||
/// 实际可能放在 `note.object` 也可能在 `note.userInfo`,所以读取做了兜底。 |
|||
final class JieliEventForwarders: NSObject { |
|||
|
|||
weak var server: JieliHomeServer? |
|||
|
|||
private var lastBatteryLevel: Int = -1 |
|||
private var lastCallStatus: Int = -1 |
|||
|
|||
init(server: JieliHomeServer) { |
|||
self.server = server |
|||
super.init() |
|||
} |
|||
|
|||
func attach() { |
|||
#if canImport(JL_BLEKit) |
|||
let nc = NotificationCenter.default |
|||
nc.addObserver(self, selector: #selector(onSystemInfo(_:)), |
|||
name: NSNotification.Name(kJL_MANAGER_SYSTEM_INFO), object: nil) |
|||
nc.addObserver(self, selector: #selector(onTargetInfo(_:)), |
|||
name: NSNotification.Name(kJL_MANAGER_TARGET_INFO), object: nil) |
|||
nc.addObserver(self, selector: #selector(onCallStatus(_:)), |
|||
name: NSNotification.Name(kJL_MANAGER_CALL_STATUS), object: nil) |
|||
nc.addObserver(self, selector: #selector(onCustomData(_:)), |
|||
name: NSNotification.Name(kJL_MANAGER_CUSTOM_DATA), object: nil) |
|||
// 设备断开时清空去重缓存,避免下一次重连首次电量/通话状态变化被 |
|||
// "和上次一样" 误判而吞掉。 |
|||
nc.addObserver(self, selector: #selector(onEntityDisconnected(_:)), |
|||
name: NSNotification.Name(kJL_BLE_M_ENTITY_DISCONNECTED), object: nil) |
|||
#endif |
|||
} |
|||
|
|||
func detach() { |
|||
NotificationCenter.default.removeObserver(self) |
|||
lastBatteryLevel = -1 |
|||
lastCallStatus = -1 |
|||
} |
|||
|
|||
// MARK: - Helpers |
|||
|
|||
/// SDK 把 `@{UUID, OBJECT}` 字典塞在 NSNotification 的哪个字段并不一致 |
|||
/// (`object` 或 `userInfo`),这里两个位置都查一遍。 |
|||
private func payloadDict(_ note: Notification) -> [AnyHashable: Any]? { |
|||
if let dict = note.object as? [AnyHashable: Any] { return dict } |
|||
if let info = note.userInfo, !info.isEmpty { return info } |
|||
return nil |
|||
} |
|||
|
|||
#if canImport(JL_BLEKit) |
|||
private func uuid(from note: Notification) -> String { |
|||
let dict = payloadDict(note) ?? [:] |
|||
if let s = dict[kJL_MANAGER_KEY_UUID] as? String { return s } |
|||
return "" |
|||
} |
|||
|
|||
private func obj(from note: Notification) -> Any? { |
|||
payloadDict(note)?[kJL_MANAGER_KEY_OBJECT] |
|||
} |
|||
#endif |
|||
|
|||
// MARK: - Battery |
|||
|
|||
@objc private func onSystemInfo(_ note: Notification) { |
|||
publishBatteryIfChanged(note: note) |
|||
} |
|||
|
|||
@objc private func onTargetInfo(_ note: Notification) { |
|||
publishBatteryIfChanged(note: note) |
|||
} |
|||
|
|||
private func publishBatteryIfChanged(note: Notification) { |
|||
#if canImport(JL_BLEKit) |
|||
guard let server = server, server.initialized else { return } |
|||
let address = uuid(from: note) |
|||
// 当前 session 只跟踪一个已连接设备;UUID 对不上就放弃,避免抢 |
|||
// 别的 entity 的状态。 |
|||
guard let entity = server.connectFeature.currentEntity() as? JL_EntityM, |
|||
!address.isEmpty, entity.mUUID == address else { return } |
|||
let battery = Int(entity.mCmdManager.outputDeviceModel().battery) |
|||
// 0 是 SDK 没拿到信息时的占位;忽略 0 → 非 0 之外的"覆盖性 0", |
|||
// 防止短暂的握手抖动把已经显示出来的电量重置回 0。 |
|||
if battery == 0 && lastBatteryLevel > 0 { return } |
|||
if battery == lastBatteryLevel { return } |
|||
lastBatteryLevel = battery |
|||
server.dispatcher.send([ |
|||
"type": "battery", |
|||
"address": address, |
|||
"level": battery, |
|||
]) |
|||
#endif |
|||
} |
|||
|
|||
// MARK: - Phone call status |
|||
|
|||
@objc private func onCallStatus(_ note: Notification) { |
|||
guard let server = server, server.initialized else { return } |
|||
#if canImport(JL_BLEKit) |
|||
let address = uuid(from: note) |
|||
// 杰理 iOS SDK 的 call status 通过 NSNumber 抛上来;解不出来就当 0 |
|||
// (= idle),整段链路语义都是 "status != 0 表示通话中"。 |
|||
let status: Int |
|||
if let n = obj(from: note) as? NSNumber { |
|||
status = n.intValue |
|||
} else if let n = obj(from: note) as? Int { |
|||
status = n |
|||
} else { |
|||
status = 0 |
|||
} |
|||
if status == lastCallStatus { return } |
|||
lastCallStatus = status |
|||
server.dispatcher.send([ |
|||
"type": "phoneCallStatus", |
|||
"address": address, |
|||
"status": status, |
|||
]) |
|||
#endif |
|||
} |
|||
|
|||
// MARK: - Disconnect reset |
|||
|
|||
@objc private func onEntityDisconnected(_ note: Notification) { |
|||
lastBatteryLevel = -1 |
|||
lastCallStatus = -1 |
|||
} |
|||
|
|||
// MARK: - Expand function (vendor custom command, device-initiated) |
|||
|
|||
@objc private func onCustomData(_ note: Notification) { |
|||
guard let server = server, server.initialized else { return } |
|||
#if canImport(JL_BLEKit) |
|||
let address = uuid(from: note) |
|||
guard let data = obj(from: note) as? Data, !data.isEmpty else { return } |
|||
// Android 的 onExpandFunction(opCode, payload) 把首字节当 opCode, |
|||
// 其余作为 payload。iOS `cmdCustomData` 出站方向也是这么拼的 |
|||
// (见 CustomCmdFeature.send),按对称把入站拆回去。 |
|||
let opCode = Int(data[0]) |
|||
let payload = data.count > 1 ? data.subdata(in: 1..<data.count) : Data() |
|||
server.dispatcher.send([ |
|||
"type": "expandFunction", |
|||
"address": address, |
|||
"opCode": opCode, |
|||
"payloadBase64": payload.base64EncodedString(), |
|||
]) |
|||
#endif |
|||
} |
|||
} |
|||
Loading…
Reference in new issue