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