diff --git a/Package.resolved b/Package.resolved index 6d120da..191a4f2 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,6 +1,15 @@ { "object": { "pins": [ + { + "package": "NordicDFU", + "repositoryURL": "https://github.com/NordicSemiconductor/IOS-DFU-Library", + "state": { + "branch": null, + "revision": "f73213f2de57c8c53700725ee2cff6cd35ef66fe", + "version": "4.11.1" + } + }, { "package": "swift-collections", "repositoryURL": "https://github.com/apple/swift-collections", @@ -9,6 +18,15 @@ "revision": "48254824bb4248676bf7ce56014ff57b142b77eb", "version": "1.0.2" } + }, + { + "package": "ZIPFoundation", + "repositoryURL": "https://github.com/weichsel/ZIPFoundation", + "state": { + "branch": null, + "revision": "ec32d62d412578542c0ffb7a6ce34d3e64b43b94", + "version": "0.9.11" + } } ] }, diff --git a/Package.swift b/Package.swift index 068ae41..1456723 100644 --- a/Package.swift +++ b/Package.swift @@ -14,7 +14,8 @@ let package = Package( ], dependencies: [ // Dependencies declare other packages that this package depends on. - .package(url: "https://github.com/apple/swift-collections", "1.0.0"..<"2.0.0") + .package(url: "https://github.com/apple/swift-collections", "1.0.0"..<"2.0.0"), + .package(url: "https://github.com/NordicSemiconductor/IOS-DFU-Library", .upToNextMajor(from: "4.11.1")) ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. @@ -22,7 +23,8 @@ let package = Package( .target( name: "CombustionBLE", dependencies: [ - .product(name: "Collections", package: "swift-collections", condition: nil) + .product(name: "Collections", package: "swift-collections"), + .product(name: "NordicDFU", package: "IOS-DFU-Library") ], path: "Sources/CombustionBLE"), /* diff --git a/Sources/CombustionBLE/BleManager.swift b/Sources/CombustionBLE/BleManager.swift index 0f2ff91..2985565 100644 --- a/Sources/CombustionBLE/BleManager.swift +++ b/Sources/CombustionBLE/BleManager.swift @@ -27,6 +27,8 @@ SOFTWARE. import Foundation import CoreBluetooth +import NordicDFU + protocol BleManagerDelegate: AnyObject { func didConnectTo(identifier: UUID) @@ -34,7 +36,7 @@ protocol BleManagerDelegate: AnyObject { func didDisconnectFrom(identifier: UUID) func handleSetIDResponse(identifier: UUID, success: Bool) func handleSetColorResponse(identifier: UUID, success: Bool) - func updateDeviceWithAdvertising(advertising: AdvertisingData, rssi: NSNumber, identifier: UUID) + func updateDeviceWithAdvertising(advertising: AdvertisingData, isConnectable: Bool, rssi: NSNumber, identifier: UUID) func updateDeviceWithLogResponse(identifier: UUID, logResponse: LogResponse) func updateDeviceWithSessionInformation(identifier: UUID, sessionInformation: SessionInformation) func updateDeviceWithStatus(identifier: UUID, status: DeviceStatus) @@ -87,6 +89,18 @@ class BleManager : NSObject { } } + func startFirmwareUpdate(device: Device, dfu: DFUFirmware) { + if let connectionPeripheral = getConnectedPeripheral(identifier: device.identifier) { + let initiator = DFUServiceInitiator().with(firmware: dfu) + + initiator.delegate = device + initiator.progressDelegate = device + + + initiator.start(target: connectionPeripheral) + } + } + private func getConnectedPeripheral(identifier: String) -> CBPeripheral? { let uuid = UUID(uuidString: identifier) let devicePeripherals = peripherals.filter { $0.identifier == uuid } @@ -123,6 +137,7 @@ extension BleManager: CBCentralManagerDelegate{ advertisementData: [String : Any], rssi RSSI: NSNumber) { let manufatureData: Data = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data ?? Data() + let isConnectable = advertisementData[CBAdvertisementDataIsConnectable] as? Bool ?? false if let advData = AdvertisingData(fromData: manufatureData) { // For now, only add probes. @@ -131,7 +146,10 @@ extension BleManager: CBCentralManagerDelegate{ // Store peripheral reference for later use peripherals.insert(peripheral) - delegate?.updateDeviceWithAdvertising(advertising: advData, rssi: RSSI, identifier: peripheral.identifier) + delegate?.updateDeviceWithAdvertising(advertising: advData, + isConnectable: isConnectable, + rssi: RSSI, + identifier: peripheral.identifier) } else { // print("Ignoring device with type \(advData.type)") } @@ -288,5 +306,4 @@ extension BleManager: CBPeripheralDelegate { } } } - } diff --git a/Sources/CombustionBLE/Device.swift b/Sources/CombustionBLE/Device.swift index 4c1d0dd..c0f10c3 100644 --- a/Sources/CombustionBLE/Device.swift +++ b/Sources/CombustionBLE/Device.swift @@ -26,6 +26,7 @@ SOFTWARE. --*/ import Foundation +import NordicDFU /// Struct containing info about a thermometer device. public class Device : ObservableObject { @@ -54,6 +55,9 @@ public class Device : ObservableObject { /// Current connection state of device @Published public internal(set) var connectionState: ConnectionState = .disconnected + /// Connectable flag set in advertising packet + @Published public internal(set) var isConnectable = false + /// Signal strength to device @Published public internal(set) var rssi: Int @@ -63,6 +67,26 @@ public class Device : ObservableObject { /// Tracks whether the data has gone stale (no new data in some time) @Published public internal(set) var stale = false + /// DFU state + @Published public private(set) var dfuState: DFUState? + + public struct DFUErrorMessage { + public let error: DFUError + public let message: String + } + + /// DFU error message + @Published public private(set) var dfuError: DFUErrorMessage? + + public struct DFUUploadProgress { + public let part: Int + public let totalParts: Int + public let progress: Int + } + + /// DFU Upload progress + @Published public private(set) var dfuUploadProgress: DFUUploadProgress? + /// Time at which device was last updated internal var lastUpdateTime = Date() @@ -82,6 +106,22 @@ public class Device : ObservableObject { func updateDeviceStale() { stale = Date().timeIntervalSince(lastUpdateTime) > Constants.STALE_TIMEOUT + + + // If device data is stale, assume its not longer connectable + if(stale) { + isConnectable = false + } + } + + public func isDFURunning() -> Bool { + guard let dfuState = dfuState else { return false } + + if(dfuState == .completed) { + return false + } + + return true } } @@ -95,7 +135,6 @@ extension Device { /// Attempt to connect to the device. public func connect() { // Mark that we should maintain a connection to this device. - // TODO - this doesn't seem to be propagating back to the UI?? maintainingConnection = true if(connectionState != .connected) { @@ -123,3 +162,23 @@ extension Device: Hashable { hasher.combine(identifier) } } + +extension Device: DFUServiceDelegate { + public func dfuStateDidChange(to state: DFUState) { + dfuState = state + } + + public func dfuError(_ error: DFUError, didOccurWithMessage message: String) { + dfuError = DFUErrorMessage(error: error, message: message) + } +} + +extension Device: DFUProgressDelegate { + public func dfuProgressDidChange(for part: Int, + outOf totalParts: Int, + to progress: Int, + currentSpeedBytesPerSecond: Double, + avgSpeedBytesPerSecond: Double) { + dfuUploadProgress = DFUUploadProgress(part: part, totalParts: totalParts, progress: progress) + } +} diff --git a/Sources/CombustionBLE/DeviceManager.swift b/Sources/CombustionBLE/DeviceManager.swift index c14d75d..fc51ef5 100644 --- a/Sources/CombustionBLE/DeviceManager.swift +++ b/Sources/CombustionBLE/DeviceManager.swift @@ -26,7 +26,7 @@ SOFTWARE. import Foundation import SwiftUI - +import NordicDFU /// Singleton that provides list of detected Devices /// (either via Bluetooth or from a list in the Cloud) @@ -162,11 +162,22 @@ public class DeviceManager : ObservableObject { /// - parameter ProbeColor: New Probe color public func setProbeColor(_ device: Device, color: ProbeColor, completionHandler: @escaping (Bool) -> Void) { setColorCompetionHandlers[device.identifier] = MessageHandler(timeSent: Date(), handler: completionHandler) - + let request = SetColorRequest(color: color) BleManager.shared.sendRequest(identifier: device.identifier, request: request) } + public func runSoftwareUpgrade(_ device: Device, otaFile: URL) -> Bool { + do { + let dfu = try DFUFirmware(urlToZipFile: otaFile) + BleManager.shared.startFirmwareUpdate(device: device, dfu: dfu) + return true + } + catch { + return false + } + } + private func checkForMessageTimeout(messageHandlers: inout [String: MessageHandler]) { let currentTime = Date() @@ -225,14 +236,14 @@ extension DeviceManager : BleManagerDelegate { } } - func updateDeviceWithAdvertising(advertising: AdvertisingData, rssi: NSNumber, identifier: UUID) { + func updateDeviceWithAdvertising(advertising: AdvertisingData, isConnectable: Bool, rssi: NSNumber, identifier: UUID) { if devices[identifier.uuidString] != nil { if let probe = devices[identifier.uuidString] as? Probe { - probe.updateWithAdvertising(advertising, RSSI: rssi) + probe.updateWithAdvertising(advertising, isConnectable: isConnectable, RSSI: rssi) } } else { - let device = Probe(advertising, RSSI: rssi, identifier: identifier) + let device = Probe(advertising, isConnectable: isConnectable, RSSI: rssi, identifier: identifier) addDevice(device: device) } } diff --git a/Sources/CombustionBLE/Probe.swift b/Sources/CombustionBLE/Probe.swift index 2c7b06b..6908dfc 100644 --- a/Sources/CombustionBLE/Probe.swift +++ b/Sources/CombustionBLE/Probe.swift @@ -70,14 +70,14 @@ public class Probe : Device { /// Time at which probe instant read was last updated internal var lastInstantRead: Date? - public init(_ advertising: AdvertisingData, RSSI: NSNumber, identifier: UUID) { + public init(_ advertising: AdvertisingData, isConnectable: Bool, RSSI: NSNumber, identifier: UUID) { serialNumber = advertising.serialNumber id = advertising.id color = advertising.color super.init(identifier: identifier, RSSI: RSSI) - updateWithAdvertising(advertising, RSSI: RSSI) + updateWithAdvertising(advertising, isConnectable: isConnectable, RSSI: RSSI) } override func updateConnectionState(_ state: ConnectionState) { @@ -108,21 +108,29 @@ extension Probe { } - func updateWithAdvertising(_ advertising: AdvertisingData, RSSI: NSNumber) { - if(advertising.mode == .Normal) { - currentTemperatures = advertising.temperatures - } - else if(advertising.mode == .InstantRead ){ - updateInstantRead(advertising.temperatures.values[0]) - } + func updateWithAdvertising(_ advertising: AdvertisingData, isConnectable: Bool, RSSI: NSNumber) { + // Always update probe RSSI and isConnectable flag + self.rssi = RSSI.intValue + self.isConnectable = isConnectable - rssi = RSSI.intValue - - id = advertising.id - color = advertising.color - batteryStatus = advertising.batteryStatus - - lastUpdateTime = Date() + // Only update rest of data if not connected to probe. Otherwise, rely on status + // notifications to update data + if(connectionState != .connected) + { + if(advertising.mode == .Normal) { + currentTemperatures = advertising.temperatures + } + else if(advertising.mode == .InstantRead ){ + updateInstantRead(advertising.temperatures.values[0]) + } + + + id = advertising.id + color = advertising.color + batteryStatus = advertising.batteryStatus + + lastUpdateTime = Date() + } } /// Updates the Device based on newly-received DeviceStatus message. Requests missing records. diff --git a/Sources/CombustionBLE/SimulatedProbe.swift b/Sources/CombustionBLE/SimulatedProbe.swift index f58e012..810e877 100644 --- a/Sources/CombustionBLE/SimulatedProbe.swift +++ b/Sources/CombustionBLE/SimulatedProbe.swift @@ -31,7 +31,7 @@ class SimulatedProbe: Probe { init() { let advertising = AdvertisingData(fakeSerial: UInt32.random(in: 0 ..< UINT32_MAX), fakeTemperatures: ProbeTemperatures.withRandomData()) - super.init(advertising, RSSI: SimulatedProbe.randomeRSSI(), identifier: UUID()) + super.init(advertising, isConnectable: true, RSSI: SimulatedProbe.randomeRSSI(), identifier: UUID()) firmareVersion = "v1.2.3" hardwareRevision = "v0.31-A1" @@ -64,7 +64,7 @@ class SimulatedProbe: Probe { private func updateFakeAdvertising() { let advertising = AdvertisingData(fakeSerial: UInt32.random(in: 0 ..< UINT32_MAX), fakeTemperatures: ProbeTemperatures.withRandomData()) - updateWithAdvertising(advertising, RSSI: SimulatedProbe.randomeRSSI()) + updateWithAdvertising(advertising, isConnectable: true, RSSI: SimulatedProbe.randomeRSSI()) } private func updateFakeStatus() { diff --git a/Sources/CombustionBLE/UART/LogRequest.swift b/Sources/CombustionBLE/UART/LogRequest.swift index b8e2f50..b77cec1 100644 --- a/Sources/CombustionBLE/UART/LogRequest.swift +++ b/Sources/CombustionBLE/UART/LogRequest.swift @@ -27,23 +27,15 @@ SOFTWARE. import Foundation class LogRequest: Request { - static let PAYLOAD_LENGTH: UInt8 = 8 - init(minSequence: UInt32, maxSequence: UInt32) { - super.init(payloadLength: LogRequest.PAYLOAD_LENGTH, type: .Log) + var payload = Data() var min = minSequence - let minData = Data(bytes: &min, count: MemoryLayout.size(ofValue: min)) - self.data[Request.HEADER_SIZE + 0] = minData[0] - self.data[Request.HEADER_SIZE + 1] = minData[1] - self.data[Request.HEADER_SIZE + 2] = minData[2] - self.data[Request.HEADER_SIZE + 3] = minData[3] + payload.append(Data(bytes: &min, count: MemoryLayout.size(ofValue: min))) var max = maxSequence - let maxData = Data(bytes: &max, count: MemoryLayout.size(ofValue: max)) - self.data[Request.HEADER_SIZE + 4] = maxData[0] - self.data[Request.HEADER_SIZE + 5] = maxData[1] - self.data[Request.HEADER_SIZE + 6] = maxData[2] - self.data[Request.HEADER_SIZE + 7] = maxData[3] + payload.append(Data(bytes: &max, count: MemoryLayout.size(ofValue: max))) + + super.init(payload: payload, type: .Log) } } diff --git a/Sources/CombustionBLE/UART/LogResponse.swift b/Sources/CombustionBLE/UART/LogResponse.swift index f7eea11..f88fe98 100644 --- a/Sources/CombustionBLE/UART/LogResponse.swift +++ b/Sources/CombustionBLE/UART/LogResponse.swift @@ -33,7 +33,7 @@ class LogResponse: Response { let sequenceNumber: UInt32 let temperatures: ProbeTemperatures - init(data: Data, success: Bool) { + init(data: Data, success: Bool, payloadLength: Int) { let sequenceByteIndex = Response.HEADER_LENGTH let sequenceRaw = data.subdata(in: sequenceByteIndex..<(sequenceByteIndex + 4)) sequenceNumber = sequenceRaw.withUnsafeBytes { @@ -47,6 +47,17 @@ class LogResponse: Response { // print("******** Received response!") // print("Sequence = \(sequenceNumber) : Temperature = \(temperatures)") - super.init(success: success, payLoadLength: LogResponse.PAYLOAD_LENGTH) + super.init(success: success, payLoadLength: payloadLength) + } +} + +extension LogResponse { + + static func fromRaw(data: Data, success: Bool, payloadLength: Int) -> LogResponse? { + if(payloadLength < PAYLOAD_LENGTH) { + return nil + } + + return LogResponse(data: data, success: success, payloadLength: payloadLength) } } diff --git a/Sources/CombustionBLE/UART/Request.swift b/Sources/CombustionBLE/UART/Request.swift index 9e0636a..faf3a9f 100644 --- a/Sources/CombustionBLE/UART/Request.swift +++ b/Sources/CombustionBLE/UART/Request.swift @@ -32,25 +32,35 @@ class Request { static let HEADER_SIZE = 6 /// Contains message data. - var data: Data + var data = Data() /// Constructor for Request object. /// - parameter payloadLength: Length of payload of message /// - parameter type: Type of message - init(payloadLength: UInt8, type: MessageType) { - let messageSize = Request.HEADER_SIZE + Int(payloadLength) - data = Data(repeating: 0, count: messageSize) + init(payload: Data, type: MessageType) { // Sync Bytes { 0xCA, 0xFE } - data[0] = 0xCA - data[1] = 0xFE + data.append(0xCA) + data.append(0xFE) - // CRC : TODO + // Calculate CRC over Message Type, payload length, payload + var crcData = Data() // Message type - data[4] = type.rawValue + crcData.append(type.rawValue) // Payload length - data[5] = payloadLength + crcData.append(UInt8(payload.count)) + + // Payload + crcData.append(payload) + + var crcValue = crcData.crc16ccitt() + + // CRC + data.append(Data(bytes: &crcValue, count: MemoryLayout.size(ofValue: crcValue))) + + // Message Type, payload length, payload + data.append(crcData) } } diff --git a/Sources/CombustionBLE/UART/Response.swift b/Sources/CombustionBLE/UART/Response.swift index 1769590..aa404f5 100644 --- a/Sources/CombustionBLE/UART/Response.swift +++ b/Sources/CombustionBLE/UART/Response.swift @@ -68,8 +68,6 @@ extension Response { return nil } - // CRC : TODO (bytes 2,3) - // Message type let typeByte = data.subdata(in: 4..<5) let typeRaw = typeByte.withUnsafeBytes { @@ -93,6 +91,23 @@ extension Response { $0.load(as: UInt8.self) } + // CRC + let crcBytes = data.subdata(in: 2..<4) + let crc = crcBytes.withUnsafeBytes { + $0.load(as: UInt16.self) + } + + let crcDataLength = 3 + Int(payloadLength) + var crcData = data.dropFirst(4) + crcData = crcData.dropLast(crcData.count - crcDataLength) + + let calculatedCRC = crcData.crc16ccitt() + + guard crc == calculatedCRC else { + print("Response::fromData(): Invalid CRC") + return nil + } + let responseLength = Int(payloadLength) + HEADER_LENGTH // Invalid number of bytes @@ -104,13 +119,13 @@ extension Response { switch messageType { case .Log: - return LogResponse(data: data, success: success) + return LogResponse.fromRaw(data: data, success: success, payloadLength: Int(payloadLength)) case .SetID: return SetIDResponse(success: success, payLoadLength: Int(payloadLength)) case .SetColor: return SetColorResponse(success: success, payLoadLength: Int(payloadLength)) case .SessionInfo: - return SessionInfoResponse(data: data, success: success) + return SessionInfoResponse.fromRaw(data: data, success: success, payloadLength: Int(payloadLength)) } } } diff --git a/Sources/CombustionBLE/UART/SessionInfo.swift b/Sources/CombustionBLE/UART/SessionInfo.swift index a565c61..d82eb82 100644 --- a/Sources/CombustionBLE/UART/SessionInfo.swift +++ b/Sources/CombustionBLE/UART/SessionInfo.swift @@ -32,10 +32,8 @@ public struct SessionInformation { } class SessionInfoRequest: Request { - static let PAYLOAD_LENGTH: UInt8 = 0 - init() { - super.init(payloadLength: SessionInfoRequest.PAYLOAD_LENGTH, type: .SessionInfo) + super.init(payload: Data(), type: .SessionInfo) } } @@ -44,7 +42,7 @@ class SessionInfoResponse: Response { let info: SessionInformation - init(data: Data, success: Bool) { + init(data: Data, success: Bool, payloadLength: Int) { let sequenceByteIndex = Response.HEADER_LENGTH let sessionIDRaw = data.subdata(in: sequenceByteIndex..<(sequenceByteIndex + 4)) let sessionID = sessionIDRaw.withUnsafeBytes { @@ -58,6 +56,17 @@ class SessionInfoResponse: Response { info = SessionInformation(sessionID: sessionID, samplePeriod: samplePeriod) - super.init(success: success, payLoadLength: SessionInfoResponse.PAYLOAD_LENGTH) + super.init(success: success, payLoadLength: payloadLength) + } +} + +extension SessionInfoResponse { + + static func fromRaw(data: Data, success: Bool, payloadLength: Int) -> SessionInfoResponse? { + if(payloadLength < PAYLOAD_LENGTH) { + return nil + } + + return SessionInfoResponse(data: data, success: success, payloadLength: payloadLength) } } diff --git a/Sources/CombustionBLE/UART/SetColor.swift b/Sources/CombustionBLE/UART/SetColor.swift index 8736099..411e71c 100644 --- a/Sources/CombustionBLE/UART/SetColor.swift +++ b/Sources/CombustionBLE/UART/SetColor.swift @@ -27,11 +27,11 @@ SOFTWARE. import Foundation class SetColorRequest: Request { - static let PAYLOAD_LENGTH: UInt8 = 1 - init(color: ProbeColor) { - super.init(payloadLength: SetColorRequest.PAYLOAD_LENGTH, type: .SetColor) - self.data[Request.HEADER_SIZE] = color.rawValue + var payload = Data() + payload.append(color.rawValue) + + super.init(payload: payload, type: .SetColor) } } diff --git a/Sources/CombustionBLE/UART/SetID.swift b/Sources/CombustionBLE/UART/SetID.swift index be56a89..0717a1e 100644 --- a/Sources/CombustionBLE/UART/SetID.swift +++ b/Sources/CombustionBLE/UART/SetID.swift @@ -27,11 +27,11 @@ SOFTWARE. import Foundation class SetIDRequest: Request { - static let PAYLOAD_LENGTH: UInt8 = 1 - init(id: ProbeID) { - super.init(payloadLength: SetIDRequest.PAYLOAD_LENGTH, type: .SetID) - self.data[Request.HEADER_SIZE] = id.rawValue + var payload = Data() + payload.append(id.rawValue) + + super.init(payload: payload, type: .SetID) } } diff --git a/Sources/CombustionBLE/Utilities/CSV.swift b/Sources/CombustionBLE/Utilities/CSV.swift index 474bb4f..979e4ef 100644 --- a/Sources/CombustionBLE/Utilities/CSV.swift +++ b/Sources/CombustionBLE/Utilities/CSV.swift @@ -38,12 +38,14 @@ public struct CSV { let dateString = dateFormatter.string(from: date) output.append("Combustion Inc. Probe Data") - output.append("iOS app: \(appVersion)") - output.append("CSV version: 2") + output.append("App: iOS \(appVersion)") + output.append("CSV version: 3") output.append("Probe S/N: \(String(format: "%4X", probe.serialNumber))") output.append("Probe FW version: \(probe.firmareVersion ?? "??")") output.append("Probe HW revision: \(probe.hardwareRevision ?? "??")") - output.append("\(dateString)") + output.append("Framework: iOS") + output.append("Sample Period: \(probe.temperatureLogs.first?.sessionInformation.samplePeriod ?? 0)") + output.append("Created: \(dateString)") output.append("") // Header @@ -55,20 +57,20 @@ public struct CSV { for dataPoint in session.dataPoints { // Calculate timestamp for current data point - var timeStamp = 0 + var timeStamp: TimeInterval = 0 if let currentSessionStart = session.startTime?.timeIntervalSince1970 { // Number of seconds between first session start time and current start time - let sessionStartTimeDiff = Int(currentSessionStart - firstSessionStart) + let sessionStartTimeDiff = currentSessionStart - firstSessionStart // Number of seconds beteen current data point and session start time - let dataPointSeconds = Int(dataPoint.sequenceNum) * Int(session.sessionInformation.samplePeriod) / 1000 + let dataPointSeconds = Double(dataPoint.sequenceNum) * Double(session.sessionInformation.samplePeriod) / 1000.0 // Number of seconds between current data point and first session start timeStamp = dataPointSeconds + sessionStartTimeDiff } - output.append(String(format: "%d,%u,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f", + output.append(String(format: "%.3f,%u,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f", timeStamp, session.id, dataPoint.sequenceNum, diff --git a/Sources/CombustionBLE/Utilities/Data+CRC.swift b/Sources/CombustionBLE/Utilities/Data+CRC.swift new file mode 100644 index 0000000..8caaa51 --- /dev/null +++ b/Sources/CombustionBLE/Utilities/Data+CRC.swift @@ -0,0 +1,46 @@ +// Data+CRC.swift + +/*-- +MIT License + +Copyright (c) 2022 Combustion Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +--*/ + +import Foundation + +extension Data { + func crc16ccitt() -> UInt16 { + var crc: UInt16 = 0xFFFF // initial value + let polynomial: UInt16 = 0x1021 // 0001 0000 0010 0001 (0, 5, 12) + + self.forEach { (byte) in + for i in 0...7 { + let bit = (byte >> (7 - i) & 1) == 1 + let c15 = (crc >> (15) & 1) == 1 + crc = crc << 1 + if (c15 != bit) { + crc = crc ^ polynomial + } + } + } + return crc & 0xffff + } +}