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.
193 lines
7.6 KiB
193 lines
7.6 KiB
import Foundation
|
|
import CoreBluetooth
|
|
|
|
#if canImport(JL_BLEKit)
|
|
import JL_BLEKit
|
|
#endif
|
|
|
|
/// Connect / disconnect via JL_BLEMultiple. Mirrors `feature/ConnectFeature.kt`.
|
|
///
|
|
/// Connection state events are published as `connectionState` payloads with
|
|
/// the same numeric coding as the Dart `ConnectionStateEvent` constants
|
|
/// (0=disconnect, 1=ok, 2=connecting). RCSP-init success surfaces as a
|
|
/// separate `rcspInit` event when the JieLi SDK confirms `cmdTargetFeatureResult`
|
|
/// returns successfully — same flow Android publishes.
|
|
class ConnectFeature: NSObject {
|
|
|
|
weak var server: JieliHomeServer?
|
|
init(server: JieliHomeServer) { self.server = server }
|
|
|
|
/// Cached id → entity for lookup by Dart-side `address` (peripheral UUID).
|
|
private var entityCache: [String: AnyObject] = [:]
|
|
private var connectedId: String?
|
|
private var connectedName: String?
|
|
|
|
func attach() {
|
|
#if canImport(JL_BLEKit)
|
|
// Hook into the JieLi notifications. The SDK posts named NSNotifications
|
|
// (kJL_BLE_M_ENTITY_CONNECTED / kJL_BLE_M_ENTITY_DISCONNECTED).
|
|
let nc = NotificationCenter.default
|
|
nc.addObserver(self,
|
|
selector: #selector(onConnected(_:)),
|
|
name: NSNotification.Name(kJL_BLE_M_ENTITY_CONNECTED), object: nil)
|
|
nc.addObserver(self,
|
|
selector: #selector(onDisconnected(_:)),
|
|
name: NSNotification.Name(kJL_BLE_M_ENTITY_DISCONNECTED), object: nil)
|
|
nc.addObserver(self,
|
|
selector: #selector(onAdapterOff(_:)),
|
|
name: NSNotification.Name(kJL_BLE_M_OFF), object: nil)
|
|
nc.addObserver(self,
|
|
selector: #selector(onAdapterOn(_:)),
|
|
name: NSNotification.Name(kJL_BLE_M_ON), object: nil)
|
|
#endif
|
|
}
|
|
|
|
func connect(
|
|
bleAddress: String,
|
|
edrAddress: String?,
|
|
deviceType: Int,
|
|
connectWay: Int
|
|
) throws {
|
|
NSLog("%@", "[JieliConnect] connect() start ble=\(bleAddress) edr=\(edrAddress ?? "nil") type=\(deviceType) way=\(connectWay)")
|
|
server?.dispatcher.send([
|
|
"type": "connectionState",
|
|
"address": bleAddress,
|
|
"state": 2, // connecting
|
|
])
|
|
#if canImport(JL_BLEKit)
|
|
guard let server = server else { throw PluginError("CONNECT_FAILED", "server not initialized") }
|
|
let mult = server.bleMultiple
|
|
NSLog("%@", "[JieliConnect] authEnable=\(mult.authEnable) ble_TIMEOUT=\(mult.ble_TIMEOUT) filter=\(mult.ble_FILTER_ENABLE)")
|
|
// Build / lookup entity. JL_BLEMultiple keeps a `blePeripheralArr` with
|
|
// discovered entities — we resolve by UUID, matching the iOS demo.
|
|
let entity: JL_EntityM
|
|
if let cached = entityCache[bleAddress] as? JL_EntityM {
|
|
entity = cached
|
|
NSLog("%@", "[JieliConnect] entity cache hit for \(bleAddress)")
|
|
} else if let made = mult.makeEntity(withUUID: bleAddress) {
|
|
entity = made
|
|
entityCache[bleAddress] = entity
|
|
NSLog("%@", "[JieliConnect] entity makeEntity ok for \(bleAddress)")
|
|
} else {
|
|
NSLog("%@", "[JieliConnect] entity makeEntity FAILED — peripheral not in JL_BLEMultiple cache (\(bleAddress))")
|
|
throw PluginError("CONNECT_FAILED", "remote device not found (UUID=\(bleAddress))")
|
|
}
|
|
mult.connectEntity(entity) { [weak self] status in
|
|
guard let self = self else { return }
|
|
NSLog("%@", "[JieliConnect] connectEntity callback status=\(status.rawValue) ble=\(bleAddress)")
|
|
switch status {
|
|
case .paired:
|
|
NSLog("%@", "[JieliConnect] PAIRED — issuing cmdTargetFeatureResult (RCSP init handshake) for \(bleAddress)")
|
|
self.connectedId = bleAddress
|
|
self.connectedName = entity.mPeripheral.name
|
|
// RCSP target-feature query — same role as Android's connect-success
|
|
// path that issues `cmdTargetFeatureResult` after handshake.
|
|
entity.mCmdManager.cmdTargetFeatureResult { result, response, error in
|
|
NSLog("%@", "[JieliConnect] cmdTargetFeatureResult result=\(result) response=\(String(describing: response)) error=\(String(describing: error)) for \(bleAddress)")
|
|
self.server?.dispatcher.send([
|
|
"type": "connectionState",
|
|
"address": bleAddress,
|
|
"state": 1, // ok / link connected
|
|
])
|
|
self.server?.dispatcher.send([
|
|
"type": "rcspInit",
|
|
"address": bleAddress,
|
|
"code": 0,
|
|
])
|
|
}
|
|
case .connecting:
|
|
NSLog("%@", "[JieliConnect] CONNECTING \(bleAddress)")
|
|
case .disconnectOk:
|
|
NSLog("%@", "[JieliConnect] DISCONNECT_OK \(bleAddress)")
|
|
self.server?.dispatcher.send([
|
|
"type": "connectionState",
|
|
"address": bleAddress,
|
|
"state": 0,
|
|
])
|
|
default:
|
|
// Any other terminal failure → expose as 0/disconnect with a code.
|
|
// 注意:authEnable=true 时,未授权设备会在 RCSP 握手阶段被 SDK 主动断开,
|
|
// 通常会以 .disconnect 或 .timeout 等非 .paired 状态走到这里。
|
|
NSLog("%@", "[JieliConnect] TERMINAL FAILURE status=\(status.rawValue) (authEnable=\(self.server?.bleMultiple.authEnable ?? false)) — surfacing as disconnect for \(bleAddress)")
|
|
self.server?.dispatcher.send([
|
|
"type": "connectionState",
|
|
"address": bleAddress,
|
|
"state": 0,
|
|
])
|
|
}
|
|
}
|
|
#else
|
|
NSLog("%@", "[JieliConnect] JL_BLEKit not linked — cannot connect \(bleAddress)")
|
|
throw PluginError("CONNECT_FAILED", "JL_BLEKit framework not linked")
|
|
#endif
|
|
}
|
|
|
|
func disconnect(address: String) throws {
|
|
#if canImport(JL_BLEKit)
|
|
guard let server = server else { return }
|
|
let mult = server.bleMultiple
|
|
if let entity = entityCache[address] as? JL_EntityM {
|
|
mult.disconnectEntity(entity) { _ in }
|
|
}
|
|
server.dispatcher.send([
|
|
"type": "connectionState",
|
|
"address": address,
|
|
"state": 0,
|
|
])
|
|
if connectedId == address {
|
|
connectedId = nil
|
|
connectedName = nil
|
|
}
|
|
#endif
|
|
}
|
|
|
|
func isConnected(address: String) -> Bool {
|
|
return connectedId == address
|
|
}
|
|
|
|
func connectedDeviceInfo() -> [String: Any?]? {
|
|
guard let id = connectedId else { return nil }
|
|
return ["address": id, "name": connectedName ?? ""]
|
|
}
|
|
|
|
// MARK: - Internal helpers used by sibling features
|
|
func currentEntity() -> AnyObject? {
|
|
guard let id = connectedId else { return nil }
|
|
return entityCache[id]
|
|
}
|
|
|
|
// MARK: - Notifications
|
|
|
|
@objc private func onConnected(_ note: Notification) {
|
|
// The SDK posts the CBPeripheral as the object on Custom-BLE flow. The
|
|
// BleMultiple flow publishes via the `paired` callback above, so the only
|
|
// job here is to ensure consistency for native subscribers.
|
|
if let cbp = note.object as? CBPeripheral {
|
|
NSLog("%@", "[JieliConnect] notif kJL_BLE_M_ENTITY_CONNECTED id=\(cbp.identifier.uuidString) name=\(cbp.name ?? "<nil>")")
|
|
connectedId = cbp.identifier.uuidString
|
|
connectedName = cbp.name
|
|
}
|
|
}
|
|
|
|
@objc private func onDisconnected(_ note: Notification) {
|
|
if let cbp = note.object as? CBPeripheral {
|
|
let id = cbp.identifier.uuidString
|
|
NSLog("%@", "[JieliConnect] notif kJL_BLE_M_ENTITY_DISCONNECTED id=\(id) name=\(cbp.name ?? "<nil>")")
|
|
server?.dispatcher.send([
|
|
"type": "connectionState",
|
|
"address": id,
|
|
"state": 0,
|
|
])
|
|
if connectedId == id { connectedId = nil; connectedName = nil }
|
|
}
|
|
}
|
|
|
|
@objc private func onAdapterOff(_ note: Notification) {
|
|
NSLog("%@", "[JieliConnect] notif kJL_BLE_M_OFF (adapter off)")
|
|
server?.dispatcher.send(["type": "adapterStatus", "enabled": false, "hasBle": true])
|
|
}
|
|
@objc private func onAdapterOn(_ note: Notification) {
|
|
NSLog("%@", "[JieliConnect] notif kJL_BLE_M_ON (adapter on)")
|
|
server?.dispatcher.send(["type": "adapterStatus", "enabled": true, "hasBle": true])
|
|
}
|
|
}
|
|
|