|
| 1 | +// |
| 2 | +// DFUUpdaterCustom.swift |
| 3 | +// InfiniLink |
| 4 | +// |
| 5 | +// Created by Liam Willey on 1/4/25. |
| 6 | +// |
| 7 | + |
| 8 | +import Foundation |
| 9 | +import Zip |
| 10 | + |
| 11 | +struct DFUManifest: Codable { |
| 12 | + struct InitPacketData: Codable { |
| 13 | + let application_version: Int |
| 14 | + let device_revision: Int |
| 15 | + let device_type: Int |
| 16 | + let firmware_crc16: Int |
| 17 | + let softdevice_req: [Int] |
| 18 | + } |
| 19 | + struct FirmwareApplication: Codable { |
| 20 | + let bin_file: String |
| 21 | + let dat_file: String |
| 22 | + let init_packet_data: InitPacketData |
| 23 | + } |
| 24 | + struct Manifest: Codable { |
| 25 | + let application: FirmwareApplication |
| 26 | + let dfu_version: Double |
| 27 | + } |
| 28 | + |
| 29 | + let manifest: Manifest |
| 30 | +} |
| 31 | + |
| 32 | +class DFUUpdaterCustom: ObservableObject { |
| 33 | + static let shared = DFUUpdaterCustom() |
| 34 | + |
| 35 | + let bleManager = BLEManager.shared |
| 36 | + let dfuUpdater = DFUUpdater.shared |
| 37 | + |
| 38 | + func startDFU() { |
| 39 | + let start = Data([0x01, 0x04]) |
| 40 | + let initPacket = Data([0x02, 0x00]) |
| 41 | + let datPacketSent = Data([0x02, 0x01]) |
| 42 | + let packetReceiptInterval = Data([0x08, 0x0A]) |
| 43 | + |
| 44 | + if let infiniTime = bleManager.infiniTime { |
| 45 | + /* |
| 46 | + MARK: Step One |
| 47 | + For the first step, write `0x01`, `0x04` to the control point characteristic. This will signal InfiniTime that a DFU upgrade is to be started. |
| 48 | + */ |
| 49 | + infiniTime.writeValue(start, for: bleManager.dfuControlPointCharacteristic, type: .withResponse) |
| 50 | + |
| 51 | + do { |
| 52 | + let unzipDirectory = try Zip.quickUnzipFile(dfuUpdater.firmwareURL) |
| 53 | + let manifest = unzipDirectory.appendingPathComponent("manifest.json") |
| 54 | + let jsonData = try Data(contentsOf: manifest) |
| 55 | + |
| 56 | + let decoder = JSONDecoder() |
| 57 | + let decodedManifest = try decoder.decode(DFUManifest.self, from: jsonData) |
| 58 | + |
| 59 | + /* |
| 60 | + MARK: Step Two |
| 61 | + In step two, send the total size in bytes of the firmware file to the packet characteristic. This value should be an unsigned 32-bit integer encoded as little-endian. In front of this integer should be 8 null bytes. This is because there are three items that can be updated and each 4 bytes is for one of those. The last four are for the InfiniTime application, so those are the ones that need to be set. |
| 62 | + */ |
| 63 | + let fileSize = try Data(contentsOf: unzipDirectory.appendingPathComponent(decodedManifest.manifest.application.bin_file)).count |
| 64 | + print("Firmware size: \(fileSize) bytes") |
| 65 | + |
| 66 | + var data = Data(repeating: 0, count: 8) |
| 67 | + let sizeBytes = withUnsafeBytes(of: UInt32(fileSize).littleEndian) { Data($0) } |
| 68 | + data.append(sizeBytes) |
| 69 | + |
| 70 | + print("Data to send: \(data.map { String(format: "%02X", $0) }.joined(separator: " "))") |
| 71 | + |
| 72 | + infiniTime.writeValue(data, for: bleManager.dfuPacketCharacteristic, type: .withResponse) |
| 73 | + |
| 74 | + /* |
| 75 | + MARK: Step Three |
| 76 | + Before running step three, wait for a response from the control point. This response should be `0x10`, `0x01`, `0x01` which indicates a successful DFU start. In step three, send `0x02`, `0x00` to the control point. This will signal InfiniTime to expect the init packet on the packet characteristic. |
| 77 | + */ |
| 78 | + // TODO: wait for response |
| 79 | + infiniTime.writeValue(initPacket, for: bleManager.dfuControlPointCharacteristic, type: .withResponse) |
| 80 | + |
| 81 | + /* |
| 82 | + MARK: Step Four |
| 83 | + The previous step prepared InfiniTime for this one. In this step, send the contents of the .dat init packet file to the packet characteristic. |
| 84 | + */ |
| 85 | + let datData = try Data(contentsOf: unzipDirectory.appendingPathComponent(decodedManifest.manifest.application.dat_file)) |
| 86 | + infiniTime.writeValue(datData, for: bleManager.dfuPacketCharacteristic, type: .withResponse) |
| 87 | + /* |
| 88 | + After this, send `0x02`, `0x01` indicating that the packet has been sent. |
| 89 | + */ |
| 90 | + infiniTime.writeValue(datPacketSent, for: bleManager.dfuControlPointCharacteristic, type: .withResponse) |
| 91 | + |
| 92 | + /* |
| 93 | + MARK: Step Five |
| 94 | + Before running this step, wait to receive `0x10`, `0x02`, `0x01` which indicates that the packet has been received. During this step, send the packet receipt interval to the control point. The firmware file will be sent in segments of 20 bytes each. The packet receipt interval indicates how many segments should be received before sending a receipt containing the amount of bytes received so that it can be confirmed to be the same as the amount sent. This is very useful for detecting packet loss. `itd` uses `0x08`, `0x0A` which indicates 10 segments. |
| 95 | + */ |
| 96 | + // TODO: wait for response |
| 97 | + infiniTime.writeValue(packetReceiptInterval, for: bleManager.dfuControlPointCharacteristic, type: .withResponse) |
| 98 | + |
| 99 | + /* |
| 100 | + MARK: Step Six |
| 101 | + Write `0x03` to the control point, indicating that the firmware will be sent next on the packet characteristic. |
| 102 | + */ |
| 103 | + infiniTime.writeValue(Data([0x03]), for: bleManager.dfuControlPointCharacteristic, type: .withResponse) |
| 104 | + |
| 105 | + /* |
| 106 | + MARK: Step Seven |
| 107 | + This step is the most difficult. Here, the actual firmware is sent to InfiniTime. |
| 108 | + |
| 109 | + As mentioned before, the firmware file must be split up into segments of 20 bytes each and sent to the packet characteristic one by one. Every 10 segments (or whatever you have set the interval to), check for a response starting with `0x11`. The rest of the response will be the amount of bytes received encoded as a little-endian unsigned 32-bit integer. Confirm that this matches the amount of bytes sent, and then continue sending more segments. |
| 110 | + */ |
| 111 | + let firmwareData = try Data(contentsOf: unzipDirectory.appending(path: decodedManifest.manifest.application.bin_file)) |
| 112 | + let firmwareSegments = firmwareData.split(separator: Data([0x08, 0x0A])) |
| 113 | + |
| 114 | + for (_, segment) in firmwareSegments.enumerated() { |
| 115 | + infiniTime.writeValue(segment, for: bleManager.dfuPacketCharacteristic, type: .withResponse) |
| 116 | + // TODO: wait for response |
| 117 | + } |
| 118 | + |
| 119 | + /* |
| 120 | + MARK: Step Eight |
| 121 | + Before running this step, wait to receive `0x10`, `0x03`, `0x01` which indicates a successful receipt of the firmware image. In this step, write `0x04` to the control point to signal InfiniTime to validate the image it has received. |
| 122 | + */ |
| 123 | + infiniTime.writeValue(Data([0x04]), for: bleManager.dfuControlPointCharacteristic, type: .withResponse) |
| 124 | + |
| 125 | + /* |
| 126 | + MARK: Step Nine |
| 127 | + Before running this step, wait to receive `0x10`, `0x04`, `0x01` which indicates that the image has been validated. In this step, send `0x05` to the control point as a command with no response. This signals InfiniTime to activate the new firmware and reboot. |
| 128 | + */ |
| 129 | + // TODO: wait for response |
| 130 | + infiniTime.writeValue(Data([0x05]), for: bleManager.dfuControlPointCharacteristic, type: .withResponse) |
| 131 | + } catch { |
| 132 | + log("\(error.localizedDescription)", caller: "DFUUpdaterCustom") |
| 133 | + } |
| 134 | + } |
| 135 | + } |
| 136 | +} |
0 commit comments