import Foundation /// 协议服务内部回调 protocol BleProtocolServiceDelegate: AnyObject { func protocolService(_ service: BleProtocolService, didReceiveMessage commandType: UInt8, data: Data) } /// 协议通信服务:帧收发、分包重组 final class BleProtocolService: BleClientDelegate { weak var delegate: BleProtocolServiceDelegate? private let client: BleClient private let frameIntervalMs: Int private var isListening = false /// 分包重组会话 private struct FragmentSession { let total: Int var frames: [Int: Data] // curPage -> data } private var fragments: [String: FragmentSession] = [:] init(client: BleClient, frameIntervalMs: Int) { self.client = client self.frameIntervalMs = frameIntervalMs BleLog.i("Protocol service created (frameIntervalMs=\(frameIntervalMs))", "Protocol") } // MARK: - Start/Stop Listening func startListening() { isListening = true client.delegate = self fragments.removeAll() BleLog.i("Start listening for notifications", "Protocol") } func stopListening() { isListening = false fragments.removeAll() BleLog.i("Stop listening; fragments cleared", "Protocol") } // MARK: - Send /// 发送二进制数据(自动拆分为协议帧),使用回调。 func send( type: UInt8, data: Data, onProgress: ((Double) -> Void)? = nil, completion: @escaping (Result) -> Void ) { let maxWriteLen = client.maximumWriteLength let maxDataSize = min( maxWriteLen - FrameConstants.headerSize - FrameConstants.footerSize, FrameConstants.maxDataSize ) let safeMaxDataSize = max(1, maxDataSize) let frames = ProtocolManager.createFrames( type: type, data: data, head: .appToDevice, maxDataSize: safeMaxDataSize ) guard !frames.isEmpty else { BleLog.e("Payload too large; frames empty", "Protocol") completion(.failure(DuooomiBleError.transferFailed("Payload too large: exceeds 65535-page limit"))) return } let total = frames.count BleLog.d("Sending frames: total=\(total), maxChunk=\(safeMaxDataSize)", "Protocol") func sendFrame(at index: Int) { guard index < total else { onProgress?(1.0) BleLog.i("Frames sent: total=\(total)", "Protocol") completion(.success(())) return } do { try self.client.writeWithoutResponse(frames[index]) } catch { completion(.failure(error)) return } onProgress?(Double(index + 1) / Double(total)) if index + 1 < total { // Inter-frame delay from config (clamped to [0,500]ms) let delaySec = Double(min(max(self.frameIntervalMs, 0), 500)) / 1000.0 DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + delaySec) { sendFrame(at: index + 1) } } else { onProgress?(1.0) BleLog.i("Frames sent: total=\(total)", "Protocol") completion(.success(())) } } sendFrame(at: 0) } /// 发送 JSON 命令(带回调) func sendJSON(type: CommandType, payload: T, completion: @escaping (Result) -> Void) { do { let data = try JSONEncoder().encode(payload) send(type: type.rawValue, data: data, completion: completion) } catch { completion(.failure(error)) } } /// 发送 JSON 命令(同步,fire-and-forget,仅发第一帧,用于简单命令) func sendJSON(type: CommandType, payload: T) throws { let jsonData = try JSONEncoder().encode(payload) let maxWriteLen = client.maximumWriteLength let maxDataSize = min( maxWriteLen - FrameConstants.headerSize - FrameConstants.footerSize, FrameConstants.maxDataSize ) let safeMaxDataSize = max(1, maxDataSize) let frames = ProtocolManager.createFrames( type: type.rawValue, data: jsonData, head: .appToDevice, maxDataSize: safeMaxDataSize ) for frame in frames { try client.writeWithoutResponse(frame) } } // MARK: - BleClientDelegate func bleClient(_ client: BleClient, didDiscoverDevice device: DiscoveredDevice) { // Not handled here — scan results forwarded by SDK } func bleClient(_ client: BleClient, didReceiveData data: Data) { guard isListening else { return } handleRawData(data) } // MARK: - Receive & Reassemble private func handleRawData(_ data: Data) { guard let frame = ProtocolManager.parseFrame(data) else { BleLog.w("Drop invalid frame (len=\(data.count))", "Protocol") return } if frame.subpageTotal > 0 { handleFragment(frame) } else { BleLog.d("Single frame received: type=\(frame.type), len=\(frame.data.count)", "Protocol") delegate?.protocolService(self, didReceiveMessage: frame.type, data: frame.data) } } private func handleFragment(_ frame: ProtocolFrame) { let key = "\(frame.type)" if fragments[key] == nil { fragments[key] = FragmentSession(total: Int(frame.subpageTotal), frames: [:]) BleLog.d("New fragment session: key=\(key), total=\(frame.subpageTotal)", "Protocol") } guard var session = fragments[key] else { return } guard frame.curPage < session.total else { return } session.frames[Int(frame.curPage)] = frame.data fragments[key] = session // Check if all pages received guard session.frames.count == session.total else { return } // Reassemble: from high page to low page var combined = Data() for i in stride(from: session.total - 1, through: 0, by: -1) { if let part = session.frames[i] { combined.append(part) } } fragments.removeValue(forKey: key) BleLog.i("Fragment reassembled: key=\(key), size=\(combined.count)", "Protocol") delegate?.protocolService(self, didReceiveMessage: frame.type, data: combined) } }