diff --git a/Sources/DuooomiBleSDK/DuooomiBleConfig.swift b/Sources/DuooomiBleSDK/DuooomiBleConfig.swift index 91c10b5..cb4a87e 100644 --- a/Sources/DuooomiBleSDK/DuooomiBleConfig.swift +++ b/Sources/DuooomiBleSDK/DuooomiBleConfig.swift @@ -18,6 +18,9 @@ public struct DuooomiBleConfig { public let aniFps: String /// 扫描时按设备蓝牙广播名前缀过滤(大小写不敏感)。**必填**,由集成方约定固件广播名前缀。 public let scanNamePrefix: String + /// 协议帧之间的发送间隔(毫秒)。合法区间 `[0, 500]`,越界仅 warn,使用点会 clamp。 + /// 集成方一般无需调整;调高用于排查接收端丢帧/拥塞。默认 35。 + public let frameIntervalMs: Int public init( apiHost: URL = URL(string: "https://api.duooomi.com")!, @@ -27,7 +30,8 @@ public struct DuooomiBleConfig { aniWidth: Int = 360, aniHeight: Int = 360, aniFps: String = "24", - scanNamePrefix: String + scanNamePrefix: String, + frameIntervalMs: Int = 35 ) { self.apiHost = apiHost self.apiKey = apiKey @@ -37,5 +41,9 @@ public struct DuooomiBleConfig { self.aniHeight = aniHeight self.aniFps = aniFps self.scanNamePrefix = scanNamePrefix + if frameIntervalMs < 0 || frameIntervalMs > 500 { + BleLog.w("frameIntervalMs=\(frameIntervalMs) out of [0,500], will clamp at use site", "Config") + } + self.frameIntervalMs = frameIntervalMs } } diff --git a/Sources/DuooomiBleSDK/DuooomiBleSDK.swift b/Sources/DuooomiBleSDK/DuooomiBleSDK.swift index 573d147..e33a9ea 100644 --- a/Sources/DuooomiBleSDK/DuooomiBleSDK.swift +++ b/Sources/DuooomiBleSDK/DuooomiBleSDK.swift @@ -110,7 +110,7 @@ public final class DuooomiBleSDK: NSObject { public init(config: DuooomiBleConfig) { self.config = config bleClient = BleClient() - protocolService = BleProtocolService(client: bleClient) + protocolService = BleProtocolService(client: bleClient, frameIntervalMs: config.frameIntervalMs) timeProvider = BeijingTimeProvider() deviceInfoService = DeviceInfoService(protocolService: protocolService, timeProvider: timeProvider) fileTransferService = FileTransferService(protocolService: protocolService) diff --git a/Sources/DuooomiBleSDK/Protocol/ProtocolConstants.swift b/Sources/DuooomiBleSDK/Protocol/ProtocolConstants.swift index 3b7684c..4988935 100644 --- a/Sources/DuooomiBleSDK/Protocol/ProtocolConstants.swift +++ b/Sources/DuooomiBleSDK/Protocol/ProtocolConstants.swift @@ -16,8 +16,6 @@ public enum FrameConstants { public static let maxDataSize = 496 public static let headerSize = 8 public static let footerSize = 1 - /// Inter-frame delay in nanoseconds (35ms) - public static let frameIntervalNanos: UInt64 = 35_000_000 public static let screenSize = 360 } diff --git a/Sources/DuooomiBleSDK/Services/BleProtocolService.swift b/Sources/DuooomiBleSDK/Services/BleProtocolService.swift index b251271..c04c095 100644 --- a/Sources/DuooomiBleSDK/Services/BleProtocolService.swift +++ b/Sources/DuooomiBleSDK/Services/BleProtocolService.swift @@ -10,6 +10,7 @@ final class BleProtocolService: BleClientDelegate { weak var delegate: BleProtocolServiceDelegate? private let client: BleClient + private let frameIntervalMs: Int private var isListening = false /// 分包重组会话 @@ -20,9 +21,10 @@ final class BleProtocolService: BleClientDelegate { private var fragments: [String: FragmentSession] = [:] - init(client: BleClient) { + init(client: BleClient, frameIntervalMs: Int) { self.client = client - BleLog.i("Protocol service created", "Protocol") + self.frameIntervalMs = frameIntervalMs + BleLog.i("Protocol service created (frameIntervalMs=\(frameIntervalMs))", "Protocol") } // MARK: - Start/Stop Listening @@ -90,8 +92,9 @@ final class BleProtocolService: BleClientDelegate { onProgress?(Double(index + 1) / Double(total)) if index + 1 < total { - // Inter-frame delay 35ms - DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 0.035) { + // 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 { diff --git a/demo/Sources/DemoApp.swift b/demo/Sources/DemoApp.swift index 48829c4..7b01058 100644 --- a/demo/Sources/DemoApp.swift +++ b/demo/Sources/DemoApp.swift @@ -29,6 +29,9 @@ final class DemoViewModel: NSObject, ObservableObject, DuooomiBleSDKDelegate { @Published var configScanPrefix: String { didSet { defaults.set(configScanPrefix, forKey: Self.keyScanPrefix) } } + @Published var configFrameIntervalMs: String { + didSet { defaults.set(configFrameIntervalMs, forKey: Self.keyFrameIntervalMs) } + } // MARK: - SDK State (driven by delegate) @@ -50,26 +53,31 @@ final class DemoViewModel: NSObject, ObservableObject, DuooomiBleSDKDelegate { private static let keyBrand = "demo.config.brand" private static let keyApiKey = "demo.config.apiKey" private static let keyScanPrefix = "demo.config.scanPrefix" + private static let keyFrameIntervalMs = "demo.config.frameIntervalMs" /// demo 调试默认值(仅当 UserDefaults 没存过时使用)。秘钥从 DemoSecrets 读,避免泄露到仓库。 private static let defaultBrand = DemoSecrets.brand private static let defaultApiKey = DemoSecrets.apiKey private static let defaultScanPrefix = DemoSecrets.scanNamePrefix + private static let defaultFrameIntervalMs = "35" override init() { let d = UserDefaults.standard let brand = d.string(forKey: Self.keyBrand) ?? Self.defaultBrand let apiKey = d.string(forKey: Self.keyApiKey) ?? Self.defaultApiKey let scanPrefix = d.string(forKey: Self.keyScanPrefix) ?? Self.defaultScanPrefix + let frameIntervalMs = d.string(forKey: Self.keyFrameIntervalMs) ?? Self.defaultFrameIntervalMs self.configBrand = brand self.configApiKey = apiKey self.configScanPrefix = scanPrefix + self.configFrameIntervalMs = frameIntervalMs self.sdk = DuooomiBleSDK(config: .init( apiKey: apiKey, brand: brand, - scanNamePrefix: scanPrefix + scanNamePrefix: scanPrefix, + frameIntervalMs: Int(frameIntervalMs) ?? 35 )) super.init() @@ -83,7 +91,8 @@ final class DemoViewModel: NSObject, ObservableObject, DuooomiBleSDKDelegate { let new = DuooomiBleSDK(config: .init( apiKey: configApiKey, brand: configBrand, - scanNamePrefix: configScanPrefix + scanNamePrefix: configScanPrefix, + frameIntervalMs: Int(configFrameIntervalMs) ?? 35 )) new.delegate = self self.sdk = new diff --git a/demo/Sources/WrapperTestView.swift b/demo/Sources/WrapperTestView.swift index 4491562..a297bfe 100644 --- a/demo/Sources/WrapperTestView.swift +++ b/demo/Sources/WrapperTestView.swift @@ -88,9 +88,15 @@ struct WrapperTestView: View { .autocapitalization(.none) .disableAutocorrection(true) } + HStack { + Text("frameMs").frame(width: 90, alignment: .leading) + TextField("35", text: $viewModel.configFrameIntervalMs) + .textFieldStyle(.roundedBorder) + .keyboardType(.numberPad) + } Button("Reload SDK") { viewModel.reloadSDK() - log("SDK reloaded with brand=\(viewModel.configBrand), prefix=\(viewModel.configScanPrefix)", level: .success) + log("SDK reloaded with brand=\(viewModel.configBrand), prefix=\(viewModel.configScanPrefix), frameMs=\(viewModel.configFrameIntervalMs)", level: .success) } .buttonStyle(.borderedProminent) }