import Foundation #if canImport(JL_BLEKit) import JL_BLEKit #endif #if canImport(JL_OTALib) import JL_OTALib #endif /// OTA via JL_OTALib + `JL_BLEMultiple.otaFunc...`. Mirrors Android /// `feature/ota/OtaFeature.kt`. /// /// Drives `JL_BLEMultiple.otaFunc(withEntityM:withFilePath:Result:)` (the /// "simple" path used by the iOS demo) and remaps the `JL_OTAResult` / /// progress callback into the same `otaState` / `otaError` event shapes /// the Android side publishes — Dart `_parseOtaState(...)` keeps working /// unchanged. public class OtaFeature { weak var server: JieliHomeServer? init(server: JieliHomeServer) { self.server = server } private var running: Bool = false public func isRunning() -> Bool { running } public func start( address: String?, firmwareFilePath: String, blockSize: Int, fileFlag: Data? ) { if running { return } #if canImport(JL_BLEKit) guard let server = server, let entity = server.connectFeature.currentEntity() as? JL_EntityM else { server?.dispatcher.send([ "type": "otaError", "code": -1, "message": "no active session", ]) return } running = true server.dispatcher.send([ "type": "otaState", "state": "INQUIRING", "sent": 0, "total": 0, "percent": -1, "tsMs": Self.tsMs(), ]) server.bleMultiple.otaFunc(withEntityM: entity, withFilePath: firmwareFilePath) { [weak self] result, progress in self?.onOtaCallback(result: result, progress: progress) } #else server?.dispatcher.send([ "type": "otaError", "code": -1, "message": "JL_OTALib not linked", ]) #endif } public func cancel() { if !running { return } running = false server?.dispatcher.send([ "type": "otaState", "state": "CANCELLED", "sent": 0, "total": 0, "percent": -1, "tsMs": Self.tsMs(), ]) } #if canImport(JL_OTALib) /// Map `JL_OTAResult` (~30 distinct cases) to the smaller Android-style /// state machine used by Dart. We funnel "in-progress / preparing / /// upgrading" into TRANSFERRING with the float `progress` (0..1) scaled /// to 0..100, terminal success → DONE, anything else → FAILED. private func onOtaCallback(result: JL_OTAResult, progress: Float) { guard let server = server else { return } switch result { case .preparing, .prepared: server.dispatcher.send([ "type": "otaState", "state": "ENTERING", "sent": 0, "total": 0, "percent": -1, "tsMs": Self.tsMs(), ]) case .upgrading: let percent = max(0, min(100, Int(progress * 100))) server.dispatcher.send([ "type": "otaState", "state": "TRANSFERRING", "sent": 0, "total": 0, "percent": percent, "tsMs": Self.tsMs(), ]) case .reboot, .reconnect, .reconnectWithMacAddr, .reconnectUpdateSource: server.dispatcher.send([ "type": "otaState", "state": "REBOOTING", "sent": 0, "total": 0, "percent": 100, "tsMs": Self.tsMs(), ]) case .success: running = false server.dispatcher.send([ "type": "otaState", "state": "DONE", "sent": 0, "total": 0, "percent": 100, "tsMs": Self.tsMs(), ]) case .cancel: running = false server.dispatcher.send([ "type": "otaState", "state": "CANCELLED", "sent": 0, "total": 0, "percent": -1, "tsMs": Self.tsMs(), ]) default: // Anything else from the JL_OTAResult enum is a terminal failure. running = false server.dispatcher.send([ "type": "otaState", "state": "FAILED", "sent": 0, "total": 0, "percent": -1, "tsMs": Self.tsMs(), ]) server.dispatcher.send([ "type": "otaError", "code": Int(result.rawValue), "message": "JL_OTAResult=\(result.rawValue)", ]) } } #endif private static func tsMs() -> Int { Int(Date().timeIntervalSince1970 * 1000) } }