From 4b8aafd50b5a56d93cf9fca5c8655b12632f6a80 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 1 Mar 2018 23:42:59 -0600 Subject: [PATCH] Data/BLE Modernization Updating the uses of Data to take advantage of its Collection semantics Making the PeripheralManager convenience methods to work with Message generics --- CGMBLEKit Example/Data.swift | 22 ----- .../NSData.swift => Common/Data.swift | 39 +++++++++ xDripG5.xcodeproj/project.pbxproj | 46 ++++++---- xDripG5/BluetoothManager.swift | 1 + xDripG5/Messages/AuthChallengeRxMessage.swift | 13 ++- xDripG5/Messages/AuthChallengeTxMessage.swift | 7 +- xDripG5/Messages/AuthRequestRxMessage.swift | 28 ++++++ xDripG5/Messages/AuthRequestTxMessage.swift | 8 +- xDripG5/Messages/AuthStatusRxMessage.swift | 29 ------- xDripG5/Messages/BatteryStatusTxMessage.swift | 2 +- xDripG5/Messages/BondRequestTxMessage.swift | 9 +- .../Messages/CalibrateGlucoseTxMessage.swift | 4 +- .../Messages/CalibrationDataRxMessage.swift | 6 +- xDripG5/Messages/DisconnectTxMessage.swift | 6 +- .../Messages/FirmwareVersionTxMessage.swift | 2 +- .../Messages/GlucoseHistoryTxMessage.swift | 2 +- xDripG5/Messages/GlucoseRxMessage.swift | 11 ++- xDripG5/Messages/GlucoseTxMessage.swift | 8 +- xDripG5/Messages/KeepAliveTxMessage.swift | 9 +- xDripG5/Messages/SessionStartRxMessage.swift | 13 ++- xDripG5/Messages/SessionStartTxMessage.swift | 11 ++- xDripG5/Messages/SessionStopRxMessage.swift | 13 ++- xDripG5/Messages/SessionStopTxMessage.swift | 11 ++- xDripG5/Messages/TransmitterMessage.swift | 39 ++------- .../Messages/TransmitterTimeRxMessage.swift | 9 +- .../Messages/TransmitterTimeTxMessage.swift | 8 +- .../TransmitterVersionRxMessage.swift | 7 +- .../TransmitterVersionTxMessage.swift | 4 +- xDripG5/NSData+CRC.swift | 44 ++++++---- xDripG5/Opcode.swift | 55 ++++++++++++ xDripG5/PeripheralManager+G5.swift | 85 ++++++++++++------- xDripG5/Transmitter.swift | 79 ++++++++--------- 32 files changed, 356 insertions(+), 274 deletions(-) delete mode 100644 CGMBLEKit Example/Data.swift rename xDripG5Tests/NSData.swift => Common/Data.swift (56%) create mode 100644 xDripG5/Messages/AuthRequestRxMessage.swift delete mode 100644 xDripG5/Messages/AuthStatusRxMessage.swift create mode 100644 xDripG5/Opcode.swift diff --git a/CGMBLEKit Example/Data.swift b/CGMBLEKit Example/Data.swift deleted file mode 100644 index 81d92e53..00000000 --- a/CGMBLEKit Example/Data.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// Data.swift -// xDripG5 -// -// Created by Nate Racklyeft on 9/18/16. -// Copyright © 2016 Nathan Racklyeft. All rights reserved. -// - -import Foundation - - -extension Data { - var hexadecimalString: String { - let string = NSMutableString(capacity: count * 2) - - for byte in self { - string.appendFormat("%02x", byte) - } - - return string as String - } -} diff --git a/xDripG5Tests/NSData.swift b/Common/Data.swift similarity index 56% rename from xDripG5Tests/NSData.swift rename to Common/Data.swift index c05da11d..7f0e771c 100644 --- a/xDripG5Tests/NSData.swift +++ b/Common/Data.swift @@ -9,6 +9,45 @@ import Foundation +extension Data { + func to(_: T.Type) -> T { + return self.withUnsafeBytes { (bytes: UnsafePointer) in + return T(littleEndian: bytes.pointee) + } + } + + func toInt() -> T { + return to(T.self) + } + + func toBigEndian(_: T.Type) -> T { + return self.withUnsafeBytes { + return T(bigEndian: $0.pointee) + } + } + + mutating func append(_ newElement: T) { + var element = newElement.littleEndian + append(UnsafeBufferPointer(start: &element, count: 1)) + } + + mutating func appendBigEndian(_ newElement: T) { + var element = newElement.bigEndian + append(UnsafeBufferPointer(start: &element, count: 1)) + } + + init(_ value: T) { + var value = value.littleEndian + self.init(buffer: UnsafeBufferPointer(start: &value, count: 1)) + } + + init(bigEndian value: T) { + var value = value.bigEndian + self.init(buffer: UnsafeBufferPointer(start: &value, count: 1)) + } +} + + // String conversion methods, adapted from https://stackoverflow.com/questions/40276322/hex-binary-string-conversion-in-swift/40278391#40278391 extension Data { init?(hexadecimalString: String) { diff --git a/xDripG5.xcodeproj/project.pbxproj b/xDripG5.xcodeproj/project.pbxproj index cc4fceeb..bf0d6f7f 100644 --- a/xDripG5.xcodeproj/project.pbxproj +++ b/xDripG5.xcodeproj/project.pbxproj @@ -30,10 +30,10 @@ 43CABE121C350B2800005705 /* BluetoothManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CABE0E1C350B2800005705 /* BluetoothManager.swift */; }; 43CABE131C350B2800005705 /* BluetoothServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CABE0F1C350B2800005705 /* BluetoothServices.swift */; }; 43CABE151C350B2800005705 /* Transmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CABE111C350B2800005705 /* Transmitter.swift */; }; - 43CABE231C350B3D00005705 /* AuthChallengeRxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CABE171C350B3D00005705 /* AuthChallengeRxMessage.swift */; }; + 43CABE231C350B3D00005705 /* AuthRequestRxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CABE171C350B3D00005705 /* AuthRequestRxMessage.swift */; }; 43CABE241C350B3D00005705 /* AuthChallengeTxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CABE181C350B3D00005705 /* AuthChallengeTxMessage.swift */; }; 43CABE251C350B3D00005705 /* AuthRequestTxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CABE191C350B3D00005705 /* AuthRequestTxMessage.swift */; }; - 43CABE261C350B3D00005705 /* AuthStatusRxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CABE1A1C350B3D00005705 /* AuthStatusRxMessage.swift */; }; + 43CABE261C350B3D00005705 /* AuthChallengeRxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CABE1A1C350B3D00005705 /* AuthChallengeRxMessage.swift */; }; 43CABE271C350B3D00005705 /* BondRequestTxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CABE1B1C350B3D00005705 /* BondRequestTxMessage.swift */; }; 43CABE281C350B3D00005705 /* DisconnectTxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CABE1C1C350B3D00005705 /* DisconnectTxMessage.swift */; }; 43CABE291C350B3D00005705 /* GlucoseRxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CABE1D1C350B3D00005705 /* GlucoseRxMessage.swift */; }; @@ -50,7 +50,10 @@ 43CE7CD21CA73CBC003CC1B0 /* CalibrateGlucoseTxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CE7CD11CA73CBC003CC1B0 /* CalibrateGlucoseTxMessage.swift */; }; 43CE7CD41CA73CE8003CC1B0 /* GlucoseHistoryTxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CE7CD31CA73CE8003CC1B0 /* GlucoseHistoryTxMessage.swift */; }; 43CE7CDC1CA77468003CC1B0 /* TransmitterStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CE7CDB1CA77468003CC1B0 /* TransmitterStatus.swift */; }; - 43DC87C01C8B509B005BC30D /* NSData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DC87BF1C8B509B005BC30D /* NSData.swift */; }; + 43D140CE2047AA930032346D /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DC87BF1C8B509B005BC30D /* Data.swift */; }; + 43D140CF2047AA940032346D /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DC87BF1C8B509B005BC30D /* Data.swift */; }; + 43D140D12047BD930032346D /* Opcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D140D02047BD930032346D /* Opcode.swift */; }; + 43DC87C01C8B509B005BC30D /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DC87BF1C8B509B005BC30D /* Data.swift */; }; 43DC87C21C8B520F005BC30D /* GlucoseRxMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DC87C11C8B520F005BC30D /* GlucoseRxMessageTests.swift */; }; 43E3978B1D5668BD0028E321 /* CalibrationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43E3978A1D5668BD0028E321 /* CalibrationState.swift */; }; 43E3978D1D566AEA0028E321 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43E3978C1D566AEA0028E321 /* HealthKit.framework */; }; @@ -66,7 +69,6 @@ 43F82BD21D037040006F5DD7 /* SessionStopRxMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F82BD11D037040006F5DD7 /* SessionStopRxMessageTests.swift */; }; 43F82BD41D037227006F5DD7 /* SessionStartRxMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F82BD31D037227006F5DD7 /* SessionStartRxMessageTests.swift */; }; C19084BB203932BD00AA47F3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C19084B9203932BD00AA47F3 /* Main.storyboard */; }; - C1CD8B0B203931AD00A8F498 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CD8B09203931AD00A8F498 /* Data.swift */; }; C1CD8B0C203931AD00A8F498 /* NSUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CD8B0A203931AD00A8F498 /* NSUserDefaults.swift */; }; /* End PBXBuildFile section */ @@ -136,10 +138,10 @@ 43CABE0E1C350B2800005705 /* BluetoothManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothManager.swift; sourceTree = ""; }; 43CABE0F1C350B2800005705 /* BluetoothServices.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothServices.swift; sourceTree = ""; }; 43CABE111C350B2800005705 /* Transmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Transmitter.swift; sourceTree = ""; }; - 43CABE171C350B3D00005705 /* AuthChallengeRxMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthChallengeRxMessage.swift; sourceTree = ""; }; + 43CABE171C350B3D00005705 /* AuthRequestRxMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthRequestRxMessage.swift; sourceTree = ""; }; 43CABE181C350B3D00005705 /* AuthChallengeTxMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthChallengeTxMessage.swift; sourceTree = ""; }; 43CABE191C350B3D00005705 /* AuthRequestTxMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthRequestTxMessage.swift; sourceTree = ""; }; - 43CABE1A1C350B3D00005705 /* AuthStatusRxMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthStatusRxMessage.swift; sourceTree = ""; }; + 43CABE1A1C350B3D00005705 /* AuthChallengeRxMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthChallengeRxMessage.swift; sourceTree = ""; }; 43CABE1B1C350B3D00005705 /* BondRequestTxMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BondRequestTxMessage.swift; sourceTree = ""; }; 43CABE1C1C350B3D00005705 /* DisconnectTxMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisconnectTxMessage.swift; sourceTree = ""; }; 43CABE1D1C350B3D00005705 /* GlucoseRxMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseRxMessage.swift; sourceTree = ""; }; @@ -156,7 +158,8 @@ 43CE7CD11CA73CBC003CC1B0 /* CalibrateGlucoseTxMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalibrateGlucoseTxMessage.swift; sourceTree = ""; }; 43CE7CD31CA73CE8003CC1B0 /* GlucoseHistoryTxMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseHistoryTxMessage.swift; sourceTree = ""; }; 43CE7CDB1CA77468003CC1B0 /* TransmitterStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransmitterStatus.swift; sourceTree = ""; }; - 43DC87BF1C8B509B005BC30D /* NSData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSData.swift; sourceTree = ""; }; + 43D140D02047BD930032346D /* Opcode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Opcode.swift; sourceTree = ""; }; + 43DC87BF1C8B509B005BC30D /* Data.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = ""; }; 43DC87C11C8B520F005BC30D /* GlucoseRxMessageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseRxMessageTests.swift; sourceTree = ""; }; 43E3978A1D5668BD0028E321 /* CalibrationState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalibrationState.swift; sourceTree = ""; }; 43E3978C1D566AEA0028E321 /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = System/Library/Frameworks/HealthKit.framework; sourceTree = SDKROOT; }; @@ -172,7 +175,6 @@ 43F82BD11D037040006F5DD7 /* SessionStopRxMessageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionStopRxMessageTests.swift; sourceTree = ""; }; 43F82BD31D037227006F5DD7 /* SessionStartRxMessageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionStartRxMessageTests.swift; sourceTree = ""; }; C19084BA203932BD00AA47F3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - C1CD8B09203931AD00A8F498 /* Data.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = ""; }; C1CD8B0A203931AD00A8F498 /* NSUserDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSUserDefaults.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -208,7 +210,6 @@ 437AFEF82038EC43008C4892 /* CGMBLEKit Example */ = { isa = PBXGroup; children = ( - C1CD8B09203931AD00A8F498 /* Data.swift */, C1CD8B0A203931AD00A8F498 /* NSUserDefaults.swift */, 437AFEF92038EC43008C4892 /* AppDelegate.swift */, 437AFF0E2038ED71008C4892 /* HKUnit.swift */, @@ -235,6 +236,7 @@ 43CABDF51C3506F100005705 /* xDripG5 */, 43CABE011C3506F100005705 /* xDripG5Tests */, 437AFEF82038EC43008C4892 /* CGMBLEKit Example */, + 43D140CD2047AA530032346D /* Common */, 43CABDF41C3506F100005705 /* Products */, 437AFF172038EDF9008C4892 /* Frameworks */, ); @@ -264,6 +266,7 @@ 43CABDF81C3506F100005705 /* Info.plist */, 430D64C41CB7846A00FCA750 /* NSData+CRC.swift */, 4323115E1EFC870300B95E62 /* OSLog.swift */, + 43D140D02047BD930032346D /* Opcode.swift */, 43E4B1F11F8AF9790038823E /* PeripheralManager.swift */, 435535D31FB2C1B000CE5A23 /* PeripheralManagerError.swift */, 431CE7661F91D0B300255374 /* PeripheralManager+G5.swift */, @@ -282,7 +285,6 @@ 43DC87C11C8B520F005BC30D /* GlucoseRxMessageTests.swift */, 43E397901D5692080028E321 /* GlucoseTests.swift */, 43CABE041C3506F100005705 /* Info.plist */, - 43DC87BF1C8B509B005BC30D /* NSData.swift */, 43F82BD31D037227006F5DD7 /* SessionStartRxMessageTests.swift */, 43F82BD11D037040006F5DD7 /* SessionStopRxMessageTests.swift */, 43460F87200B30D10030C0E3 /* TransmitterIDTests.swift */, @@ -295,10 +297,10 @@ 43CABE161C350B2E00005705 /* Messages */ = { isa = PBXGroup; children = ( - 43CABE171C350B3D00005705 /* AuthChallengeRxMessage.swift */, - 43CABE181C350B3D00005705 /* AuthChallengeTxMessage.swift */, 43CABE191C350B3D00005705 /* AuthRequestTxMessage.swift */, - 43CABE1A1C350B3D00005705 /* AuthStatusRxMessage.swift */, + 43CABE171C350B3D00005705 /* AuthRequestRxMessage.swift */, + 43CABE181C350B3D00005705 /* AuthChallengeTxMessage.swift */, + 43CABE1A1C350B3D00005705 /* AuthChallengeRxMessage.swift */, 43CE7CCB1CA73BCC003CC1B0 /* BatteryStatusTxMessage.swift */, 43CABE1B1C350B3D00005705 /* BondRequestTxMessage.swift */, 43CE7CD11CA73CBC003CC1B0 /* CalibrateGlucoseTxMessage.swift */, @@ -322,6 +324,14 @@ path = Messages; sourceTree = ""; }; + 43D140CD2047AA530032346D /* Common */ = { + isa = PBXGroup; + children = ( + 43DC87BF1C8B509B005BC30D /* Data.swift */, + ); + path = Common; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -475,10 +485,10 @@ buildActionMask = 2147483647; files = ( C1CD8B0C203931AD00A8F498 /* NSUserDefaults.swift in Sources */, + 43D140CE2047AA930032346D /* Data.swift in Sources */, 437AFEFC2038EC43008C4892 /* ViewController.swift in Sources */, 437AFEFA2038EC43008C4892 /* AppDelegate.swift in Sources */, 437AFF0F2038ED71008C4892 /* HKUnit.swift in Sources */, - C1CD8B0B203931AD00A8F498 /* Data.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -495,8 +505,8 @@ 43CABE271C350B3D00005705 /* BondRequestTxMessage.swift in Sources */, 4323115F1EFC870300B95E62 /* OSLog.swift in Sources */, 43E4B1F21F8AF9790038823E /* PeripheralManager.swift in Sources */, - 43CABE231C350B3D00005705 /* AuthChallengeRxMessage.swift in Sources */, - 43CABE261C350B3D00005705 /* AuthStatusRxMessage.swift in Sources */, + 43CABE231C350B3D00005705 /* AuthRequestRxMessage.swift in Sources */, + 43CABE261C350B3D00005705 /* AuthChallengeRxMessage.swift in Sources */, 43CE7CD41CA73CE8003CC1B0 /* GlucoseHistoryTxMessage.swift in Sources */, 43E397931D56950C0028E321 /* HKUnit.swift in Sources */, 43846AC61D8F896C00799272 /* CalibrationDataRxMessage.swift in Sources */, @@ -504,6 +514,7 @@ 431CE7671F91D0B300255374 /* PeripheralManager+G5.swift in Sources */, 43CABE2A1C350B3D00005705 /* GlucoseTxMessage.swift in Sources */, 43CE7CC81CA73AEB003CC1B0 /* FirmwareVersionTxMessage.swift in Sources */, + 43D140D12047BD930032346D /* Opcode.swift in Sources */, 43CE7CCC1CA73BCC003CC1B0 /* BatteryStatusTxMessage.swift in Sources */, 43EEA7121D14DC0800CBBDA0 /* AESCrypt.m in Sources */, 43CE7CCE1CA73C22003CC1B0 /* SessionStartTxMessage.swift in Sources */, @@ -521,6 +532,7 @@ 43F82BD01D035D68006F5DD7 /* SessionStopRxMessage.swift in Sources */, 43F82BCE1D035D5C006F5DD7 /* SessionStartRxMessage.swift in Sources */, 435535D41FB2C1B000CE5A23 /* PeripheralManagerError.swift in Sources */, + 43D140CF2047AA940032346D /* Data.swift in Sources */, 43CABE251C350B3D00005705 /* AuthRequestTxMessage.swift in Sources */, 430D64C51CB7846A00FCA750 /* NSData+CRC.swift in Sources */, ); @@ -534,7 +546,7 @@ 43F82BCC1D035AA4006F5DD7 /* TransmitterTimeRxMessageTests.swift in Sources */, 43F82BD41D037227006F5DD7 /* SessionStartRxMessageTests.swift in Sources */, 43846AC81D8F89BE00799272 /* CalibrationDataRxMessageTests.swift in Sources */, - 43DC87C01C8B509B005BC30D /* NSData.swift in Sources */, + 43DC87C01C8B509B005BC30D /* Data.swift in Sources */, 43F82BD21D037040006F5DD7 /* SessionStopRxMessageTests.swift in Sources */, 43E397911D5692080028E321 /* GlucoseTests.swift in Sources */, 43880F9A1D9E1BD7009061A8 /* TransmitterVersionRxMessageTests.swift in Sources */, diff --git a/xDripG5/BluetoothManager.swift b/xDripG5/BluetoothManager.swift index f807dae3..d65bddad 100644 --- a/xDripG5/BluetoothManager.swift +++ b/xDripG5/BluetoothManager.swift @@ -167,6 +167,7 @@ class BluetoothManager: NSObject { extension BluetoothManager: CBCentralManagerDelegate { func centralManagerDidUpdateState(_ central: CBCentralManager) { peripheralManager?.centralManagerDidUpdateState(central) + log.info("%{public}@: %{public}@", #function, String(describing: central.state)) switch central.state { case .poweredOn: diff --git a/xDripG5/Messages/AuthChallengeRxMessage.swift b/xDripG5/Messages/AuthChallengeRxMessage.swift index 90e00643..5ebce938 100644 --- a/xDripG5/Messages/AuthChallengeRxMessage.swift +++ b/xDripG5/Messages/AuthChallengeRxMessage.swift @@ -10,20 +10,19 @@ import Foundation struct AuthChallengeRxMessage: TransmitterRxMessage { - static let opcode: UInt8 = 0x3 - let tokenHash: Data - let challenge: Data + let authenticated: UInt8 + let bonded: UInt8 init?(data: Data) { - guard data.count >= 17 else { + guard data.count >= 3 else { return nil } - guard data[0] == type(of: self).opcode else { + guard data.starts(with: .authChallengeRx) else { return nil } - tokenHash = data.subdata(in: 1..<9) - challenge = data.subdata(in: 9..<17) + authenticated = data[1] + bonded = data[2] } } diff --git a/xDripG5/Messages/AuthChallengeTxMessage.swift b/xDripG5/Messages/AuthChallengeTxMessage.swift index 1143f16f..6ce551cc 100644 --- a/xDripG5/Messages/AuthChallengeTxMessage.swift +++ b/xDripG5/Messages/AuthChallengeTxMessage.swift @@ -10,10 +10,11 @@ import Foundation struct AuthChallengeTxMessage: TransmitterTxMessage { - let opcode: UInt8 = 0x4 let challengeHash: Data - var byteSequence: [Any] { - return [opcode, challengeHash] + var data: Data { + var data = Data(for: .authChallengeTx) + data.append(challengeHash) + return data } } diff --git a/xDripG5/Messages/AuthRequestRxMessage.swift b/xDripG5/Messages/AuthRequestRxMessage.swift new file mode 100644 index 00000000..3843891a --- /dev/null +++ b/xDripG5/Messages/AuthRequestRxMessage.swift @@ -0,0 +1,28 @@ +// +// AuthRequestRxMessage.swift +// xDrip5 +// +// Created by Nathan Racklyeft on 11/22/15. +// Copyright © 2015 Nathan Racklyeft. All rights reserved. +// + +import Foundation + + +struct AuthRequestRxMessage: TransmitterRxMessage { + let tokenHash: Data + let challenge: Data + + init?(data: Data) { + guard data.count >= 17 else { + return nil + } + + guard data.starts(with: .authRequestRx) else { + return nil + } + + tokenHash = data.subdata(in: 1..<9) + challenge = data.subdata(in: 9..<17) + } +} diff --git a/xDripG5/Messages/AuthRequestTxMessage.swift b/xDripG5/Messages/AuthRequestTxMessage.swift index d6bc73d6..3cac18d2 100644 --- a/xDripG5/Messages/AuthRequestTxMessage.swift +++ b/xDripG5/Messages/AuthRequestTxMessage.swift @@ -10,7 +10,6 @@ import Foundation struct AuthRequestTxMessage: TransmitterTxMessage { - let opcode: UInt8 = 0x1 let singleUseToken: Data let endByte: UInt8 = 0x2 @@ -21,7 +20,10 @@ struct AuthRequestTxMessage: TransmitterTxMessage { uuid.4, uuid.5, uuid.6, uuid.7]) } - var byteSequence: [Any] { - return [opcode, singleUseToken, endByte] + var data: Data { + var data = Data(for: .authRequestTx) + data.append(singleUseToken) + data.append(endByte) + return data } } diff --git a/xDripG5/Messages/AuthStatusRxMessage.swift b/xDripG5/Messages/AuthStatusRxMessage.swift deleted file mode 100644 index 473bd158..00000000 --- a/xDripG5/Messages/AuthStatusRxMessage.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// AuthStatusRxMessage.swift -// xDrip5 -// -// Created by Nathan Racklyeft on 11/22/15. -// Copyright © 2015 Nathan Racklyeft. All rights reserved. -// - -import Foundation - - -struct AuthStatusRxMessage: TransmitterRxMessage { - static let opcode: UInt8 = 0x5 - let authenticated: UInt8 - let bonded: UInt8 - - init?(data: Data) { - guard data.count >= 3 else { - return nil - } - - guard data[0] == type(of: self).opcode else { - return nil - } - - authenticated = data[1] - bonded = data[2] - } -} diff --git a/xDripG5/Messages/BatteryStatusTxMessage.swift b/xDripG5/Messages/BatteryStatusTxMessage.swift index 81e48dcd..8cabbe89 100644 --- a/xDripG5/Messages/BatteryStatusTxMessage.swift +++ b/xDripG5/Messages/BatteryStatusTxMessage.swift @@ -10,7 +10,7 @@ import Foundation struct BatteryStatusTxMessage { - let opcode: UInt8 = 0x22 + let opcode: Opcode = .batteryStatusTx // Response: 23003c012f01cd021f247bae } diff --git a/xDripG5/Messages/BondRequestTxMessage.swift b/xDripG5/Messages/BondRequestTxMessage.swift index 8abec472..70601d97 100644 --- a/xDripG5/Messages/BondRequestTxMessage.swift +++ b/xDripG5/Messages/BondRequestTxMessage.swift @@ -9,10 +9,9 @@ import Foundation +/// Initiates a bond with the central struct BondRequestTxMessage: TransmitterTxMessage { - let opcode: UInt8 = 0x7 - - var byteSequence: [Any] { - return [opcode] + var data: Data { + return Data(for: .bondRequest) } -} \ No newline at end of file +} diff --git a/xDripG5/Messages/CalibrateGlucoseTxMessage.swift b/xDripG5/Messages/CalibrateGlucoseTxMessage.swift index 2a85089e..77299703 100644 --- a/xDripG5/Messages/CalibrateGlucoseTxMessage.swift +++ b/xDripG5/Messages/CalibrateGlucoseTxMessage.swift @@ -10,5 +10,5 @@ import Foundation struct CalibrateGlucoseTxMessage { - let opcode: UInt8 = 0x34 -} \ No newline at end of file + let opcode: Opcode = .calibrateGlucoseTx +} diff --git a/xDripG5/Messages/CalibrationDataRxMessage.swift b/xDripG5/Messages/CalibrationDataRxMessage.swift index 25032634..938c067f 100644 --- a/xDripG5/Messages/CalibrationDataRxMessage.swift +++ b/xDripG5/Messages/CalibrationDataRxMessage.swift @@ -10,14 +10,12 @@ import Foundation struct CalibrationDataRxMessage: TransmitterRxMessage { - static let opcode: UInt8 = 0x33 - init?(data: Data) { - guard data.count == 19 && data.crcValid() else { + guard data.count == 19 && data.isCRCValid else { return nil } - guard data[0] == type(of: self).opcode else { + guard data.starts(with: .calibrationDataRx) else { return nil } } diff --git a/xDripG5/Messages/DisconnectTxMessage.swift b/xDripG5/Messages/DisconnectTxMessage.swift index 84b81f01..856a7319 100644 --- a/xDripG5/Messages/DisconnectTxMessage.swift +++ b/xDripG5/Messages/DisconnectTxMessage.swift @@ -10,9 +10,7 @@ import Foundation struct DisconnectTxMessage: TransmitterTxMessage { - let opcode: UInt8 = 0x09 - - var byteSequence: [Any] { - return [opcode] + var data: Data { + return Data(for: .disconnectTx) } } diff --git a/xDripG5/Messages/FirmwareVersionTxMessage.swift b/xDripG5/Messages/FirmwareVersionTxMessage.swift index 1f131b2c..7578177d 100644 --- a/xDripG5/Messages/FirmwareVersionTxMessage.swift +++ b/xDripG5/Messages/FirmwareVersionTxMessage.swift @@ -10,5 +10,5 @@ import Foundation struct FirmwareVersionTxMessage { - let opcode: UInt8 = 0x20 + let opcode: Opcode = .firmwareVersionTx } diff --git a/xDripG5/Messages/GlucoseHistoryTxMessage.swift b/xDripG5/Messages/GlucoseHistoryTxMessage.swift index 465be3df..44abfdd0 100644 --- a/xDripG5/Messages/GlucoseHistoryTxMessage.swift +++ b/xDripG5/Messages/GlucoseHistoryTxMessage.swift @@ -10,5 +10,5 @@ import Foundation struct GlucoseHistoryTxMessage { - let opcode: UInt8 = 0x3e + let opcode: Opcode = .glucoseHistoryTx } diff --git a/xDripG5/Messages/GlucoseRxMessage.swift b/xDripG5/Messages/GlucoseRxMessage.swift index ae984ca6..c10a72ba 100644 --- a/xDripG5/Messages/GlucoseRxMessage.swift +++ b/xDripG5/Messages/GlucoseRxMessage.swift @@ -10,7 +10,6 @@ import Foundation public struct GlucoseRxMessage: TransmitterRxMessage { - static let opcode: UInt8 = 0x31 public let status: UInt8 public let sequence: UInt32 public let timestamp: UInt32 @@ -20,19 +19,19 @@ public struct GlucoseRxMessage: TransmitterRxMessage { public let trend: Int8 init?(data: Data) { - guard data.count == 16 && data.crcValid() else { + guard data.count == 16 && data.isCRCValid else { return nil } - guard data[0] == type(of: self).opcode else { + guard data.starts(with: .glucoseRx) else { return nil } status = data[1] - sequence = data.subdata(in: 2..<6).withUnsafeBytes { $0.pointee } - timestamp = data.subdata(in: 6..<10).withUnsafeBytes { $0.pointee } + sequence = data[2..<6].toInt() + timestamp = data[6..<10].toInt() - let glucoseBytes: UInt16 = data.subdata(in: 10..<12).withUnsafeBytes { $0.pointee } + let glucoseBytes = data[10..<12].to(UInt16.self) glucoseIsDisplayOnly = (glucoseBytes & 0xf000) > 0 glucose = glucoseBytes & 0xfff diff --git a/xDripG5/Messages/GlucoseTxMessage.swift b/xDripG5/Messages/GlucoseTxMessage.swift index f92c64f9..f20f6fa6 100644 --- a/xDripG5/Messages/GlucoseTxMessage.swift +++ b/xDripG5/Messages/GlucoseTxMessage.swift @@ -9,10 +9,10 @@ import Foundation -struct GlucoseTxMessage: TransmitterTxMessage { - let opcode: UInt8 = 0x30 +struct GlucoseTxMessage: RespondableMessage { + typealias Response = GlucoseRxMessage - var byteSequence: [Any] { - return [opcode, opcode.crc16()] + var data: Data { + return Data(for: .glucoseTx).appendingCRC() } } diff --git a/xDripG5/Messages/KeepAliveTxMessage.swift b/xDripG5/Messages/KeepAliveTxMessage.swift index 198efadd..67e33c55 100644 --- a/xDripG5/Messages/KeepAliveTxMessage.swift +++ b/xDripG5/Messages/KeepAliveTxMessage.swift @@ -10,10 +10,11 @@ import Foundation struct KeepAliveTxMessage: TransmitterTxMessage { - let opcode: UInt8 = 0x6 let time: UInt8 - var byteSequence: [Any] { - return [opcode, time] + var data: Data { + var data = Data(for: .keepAlive) + data.append(time) + return data } -} \ No newline at end of file +} diff --git a/xDripG5/Messages/SessionStartRxMessage.swift b/xDripG5/Messages/SessionStartRxMessage.swift index 71960dba..f38ca5f0 100644 --- a/xDripG5/Messages/SessionStartRxMessage.swift +++ b/xDripG5/Messages/SessionStartRxMessage.swift @@ -9,8 +9,7 @@ import Foundation -struct SessionStartRxMessage { - static let opcode: UInt8 = 0x27 +struct SessionStartRxMessage: TransmitterRxMessage { let status: UInt8 let received: UInt8 @@ -21,18 +20,18 @@ struct SessionStartRxMessage { let transmitterTime: UInt32 init?(data: Data) { - guard data.count == 17 && data.crcValid() else { + guard data.count == 17 && data.isCRCValid else { return nil } - guard data[0] == type(of: self).opcode else { + guard data.starts(with: .sessionStartRx) else { return nil } status = data[1] received = data[2] - requestedStartTime = data.subdata(in: 3..<7).withUnsafeBytes { $0.pointee } - sessionStartTime = data.subdata(in: 7..<11).withUnsafeBytes { $0.pointee } - transmitterTime = data.subdata(in: 11..<15).withUnsafeBytes { $0.pointee } + requestedStartTime = data[3..<7].toInt() + sessionStartTime = data[7..<11].toInt() + transmitterTime = data[11..<15].toInt() } } diff --git a/xDripG5/Messages/SessionStartTxMessage.swift b/xDripG5/Messages/SessionStartTxMessage.swift index 05101a90..8e14e66e 100644 --- a/xDripG5/Messages/SessionStartTxMessage.swift +++ b/xDripG5/Messages/SessionStartTxMessage.swift @@ -9,11 +9,14 @@ import Foundation -struct SessionStartTxMessage: TransmitterTxMessage { - let opcode: UInt8 = 0x26 +struct SessionStartTxMessage: RespondableMessage { + typealias Response = SessionStartRxMessage + let startTime: UInt32 - var byteSequence: [Any] { - return [opcode, startTime] + var data: Data { + var data = Data(for: .sessionStartTx) + data.append(startTime) + return data } } diff --git a/xDripG5/Messages/SessionStopRxMessage.swift b/xDripG5/Messages/SessionStopRxMessage.swift index 36f41bc5..a2f86792 100644 --- a/xDripG5/Messages/SessionStopRxMessage.swift +++ b/xDripG5/Messages/SessionStopRxMessage.swift @@ -9,8 +9,7 @@ import Foundation -struct SessionStopRxMessage { - static let opcode: UInt8 = 0x29 +struct SessionStopRxMessage: TransmitterRxMessage { let status: UInt8 let received: UInt8 let sessionStopTime: UInt32 @@ -18,18 +17,18 @@ struct SessionStopRxMessage { let transmitterTime: UInt32 init?(data: Data) { - guard data.count == 17 && data.crcValid() else { + guard data.count == 17 && data.isCRCValid else { return nil } - guard data[0] == type(of: self).opcode else { + guard data.starts(with: .sessionStopRx) else { return nil } status = data[1] received = data[2] - sessionStopTime = data.subdata(in: 3..<7).withUnsafeBytes { $0.pointee } - sessionStartTime = data.subdata(in: 7..<11).withUnsafeBytes { $0.pointee } - transmitterTime = data.subdata(in: 11..<15).withUnsafeBytes { $0.pointee } + sessionStopTime = data[3..<7].toInt() + sessionStartTime = data[7..<11].toInt() + transmitterTime = data[11..<15].toInt() } } diff --git a/xDripG5/Messages/SessionStopTxMessage.swift b/xDripG5/Messages/SessionStopTxMessage.swift index 8b4526b7..49051fd5 100644 --- a/xDripG5/Messages/SessionStopTxMessage.swift +++ b/xDripG5/Messages/SessionStopTxMessage.swift @@ -9,11 +9,14 @@ import Foundation -struct SessionStopTxMessage: TransmitterTxMessage { - let opcode: UInt8 = 0x28 +struct SessionStopTxMessage: RespondableMessage { + typealias Response = SessionStopRxMessage + let stopTime: UInt32 - var byteSequence: [Any] { - return [opcode, stopTime] + var data: Data { + var data = Data(for: .sessionStopTx) + data.append(stopTime) + return data } } diff --git a/xDripG5/Messages/TransmitterMessage.swift b/xDripG5/Messages/TransmitterMessage.swift index 340b8a45..f6c9908f 100644 --- a/xDripG5/Messages/TransmitterMessage.swift +++ b/xDripG5/Messages/TransmitterMessage.swift @@ -9,51 +9,24 @@ import Foundation +/// A data sequence written to the transmitter protocol TransmitterTxMessage { - var byteSequence: [Any] { get } - + /// The data to write var data: Data { get } } -extension TransmitterTxMessage { - var data: Data { - let data = NSMutableData() - - for item in byteSequence { - switch item { - case let i as Int8: - var value = i - - data.append(&value, length: 1) - case let i as UInt8: - var value = i - - data.append(&value, length: 1) - case let i as UInt16: - var value = i - - data.append(&value, length: 2) - case let i as UInt32: - var value = i - - data.append(&value, length: 4) - case let i as Data: - data.append(i) - default: - fatalError("\(item) not supported") - } - } - - return data as Data - } +protocol RespondableMessage: TransmitterTxMessage { + associatedtype Response: TransmitterRxMessage } +/// A data sequence received by the transmitter protocol TransmitterRxMessage { + init?(data: Data) } diff --git a/xDripG5/Messages/TransmitterTimeRxMessage.swift b/xDripG5/Messages/TransmitterTimeRxMessage.swift index 6e9e3467..b05fe8b2 100644 --- a/xDripG5/Messages/TransmitterTimeRxMessage.swift +++ b/xDripG5/Messages/TransmitterTimeRxMessage.swift @@ -10,23 +10,22 @@ import Foundation struct TransmitterTimeRxMessage: TransmitterRxMessage { - static let opcode: UInt8 = 0x25 let status: UInt8 let currentTime: UInt32 let sessionStartTime: UInt32 init?(data: Data) { - guard data.count == 16 && data.crcValid() else { + guard data.count == 16 && data.isCRCValid else { return nil } - guard data[0] == type(of: self).opcode else { + guard data.starts(with: .transmitterTimeRx) else { return nil } status = data[1] - currentTime = data.subdata(in: 2..<6).withUnsafeBytes { $0.pointee } - sessionStartTime = data.subdata(in: 6..<10).withUnsafeBytes { $0.pointee } + currentTime = data[2..<6].toInt() + sessionStartTime = data[6..<10].toInt() } } diff --git a/xDripG5/Messages/TransmitterTimeTxMessage.swift b/xDripG5/Messages/TransmitterTimeTxMessage.swift index 6288dd78..5f2e29e6 100644 --- a/xDripG5/Messages/TransmitterTimeTxMessage.swift +++ b/xDripG5/Messages/TransmitterTimeTxMessage.swift @@ -9,10 +9,10 @@ import Foundation -struct TransmitterTimeTxMessage: TransmitterTxMessage { - let opcode: UInt8 = 0x24 +struct TransmitterTimeTxMessage: RespondableMessage { + typealias Response = TransmitterTimeRxMessage - var byteSequence: [Any] { - return [opcode, opcode.crc16()] + var data: Data { + return Data(for: .transmitterTimeTx).appendingCRC() } } diff --git a/xDripG5/Messages/TransmitterVersionRxMessage.swift b/xDripG5/Messages/TransmitterVersionRxMessage.swift index 0b6e5784..4854385b 100644 --- a/xDripG5/Messages/TransmitterVersionRxMessage.swift +++ b/xDripG5/Messages/TransmitterVersionRxMessage.swift @@ -10,21 +10,20 @@ import Foundation struct TransmitterVersionRxMessage: TransmitterRxMessage { - static let opcode: UInt8 = 0x4b let status: UInt8 let firmwareVersion: [UInt8] init?(data: Data) { - guard data.count == 19 && data.crcValid() else { + guard data.count == 19 && data.isCRCValid else { return nil } - guard data[0] == type(of: self).opcode else { + guard data.starts(with: .transmitterVersionRx) else { return nil } status = data[1] - firmwareVersion = data.subdata(in: 2..<6).map({ $0 }) + firmwareVersion = data[2..<6].map { $0 } } } diff --git a/xDripG5/Messages/TransmitterVersionTxMessage.swift b/xDripG5/Messages/TransmitterVersionTxMessage.swift index 466a94fa..60df2c7f 100644 --- a/xDripG5/Messages/TransmitterVersionTxMessage.swift +++ b/xDripG5/Messages/TransmitterVersionTxMessage.swift @@ -10,5 +10,7 @@ import Foundation struct TransmitterVersionTxMessage { - let opcode: UInt8 = 0x4a + typealias Response = TransmitterVersionRxMessage + + let opcode: Opcode = .transmitterVersionTx } diff --git a/xDripG5/NSData+CRC.swift b/xDripG5/NSData+CRC.swift index 51cdf649..b635a116 100644 --- a/xDripG5/NSData+CRC.swift +++ b/xDripG5/NSData+CRC.swift @@ -16,38 +16,46 @@ import Foundation [http://web.mit.edu/6.115/www/amulet/xmodem.htm]() */ -private func CRCCCITTXModem(_ bytes: Data) -> UInt16 { - var crc: UInt16 = 0 - - for byte in bytes { - crc ^= UInt16(byte) << 8 - - for _ in 0..<8 { - if crc & 0x8000 != 0 { - crc = crc << 1 ^ 0x1021 - } else { - crc = crc << 1 +extension Collection where Element == UInt8 { + private var crcCCITTXModem: UInt16 { + var crc: UInt16 = 0 + + for byte in self { + crc ^= UInt16(byte) << 8 + + for _ in 0..<8 { + if crc & 0x8000 != 0 { + crc = crc << 1 ^ 0x1021 + } else { + crc = crc << 1 + } } } + + return crc } - return crc + var crc16: UInt16 { + return crcCCITTXModem + } } extension UInt8 { - func crc16() -> UInt16 { - return CRCCCITTXModem(Data(bytes: [self])) + var crc16: UInt16 { + return [self].crc16 } } extension Data { - func crc16() -> UInt16 { - return CRCCCITTXModem(self) + var isCRCValid: Bool { + return dropLast(2).crc16 == suffix(2).toInt() } - func crcValid() -> Bool { - return CRCCCITTXModem(subdata(in: 0.. Data { + var data = self + data.append(crc16) + return data } } diff --git a/xDripG5/Opcode.swift b/xDripG5/Opcode.swift new file mode 100644 index 00000000..0e4a4084 --- /dev/null +++ b/xDripG5/Opcode.swift @@ -0,0 +1,55 @@ +// +// Opcode.swift +// xDripG5 +// +// Copyright © 2018 LoopKit Authors. All rights reserved. +// + + +enum Opcode: UInt8 { + case authRequestTx = 0x01 + case authRequestRx = 0x03 + case authChallengeTx = 0x04 + case authChallengeRx = 0x05 + case keepAlive = 0x06 + case bondRequest = 0x07 + + case disconnectTx = 0x09 + + case firmwareVersionTx = 0x20 + + case batteryStatusTx = 0x22 + + case transmitterTimeTx = 0x24 + case transmitterTimeRx = 0x25 + case sessionStartTx = 0x26 + case sessionStartRx = 0x27 + + case sessionStopTx = 0x28 + case sessionStopRx = 0x29 + case glucoseTx = 0x30 + case glucoseRx = 0x31 + + case calibrationDataRx = 0x33 + case calibrateGlucoseTx = 0x34 + + case glucoseHistoryTx = 0x3e + + case transmitterVersionTx = 0x4a + case transmitterVersionRx = 0x4b +} + + +extension Data { + init(for opcode: Opcode) { + self.init(bytes: [opcode.rawValue]) + } + + func starts(with opcode: Opcode) -> Bool { + guard count > 0 else { + return false + } + + return self[startIndex] == opcode.rawValue + } +} diff --git a/xDripG5/PeripheralManager+G5.swift b/xDripG5/PeripheralManager+G5.swift index 75a84df8..489304dc 100644 --- a/xDripG5/PeripheralManager+G5.swift +++ b/xDripG5/PeripheralManager+G5.swift @@ -6,6 +6,10 @@ // import CoreBluetooth +import os.log + + +private let log = OSLog(category: "PeripheralManager+G5") extension PeripheralManager { @@ -20,62 +24,96 @@ extension PeripheralManager { try setNotifyValue(enabled, for: characteristic, timeout: timeout) } - func readValue( + func readMessage( for characteristicUUID: CGMServiceCharacteristicUUID, - timeout: TimeInterval = 2, - expectingFirstByte firstByte: UInt8? = nil) throws -> Data + timeout: TimeInterval = 2 + ) throws -> R { guard let characteristic = peripheral.getCharacteristicWithUUID(characteristicUUID) else { throw PeripheralManagerError.unknownCharacteristic } + var capturedResponse: R? + try runCommand(timeout: timeout) { - addCondition(.makeValueUpdate(characteristic: characteristic, matchingFirstByte: firstByte)) + addCondition(.valueUpdate(characteristic: characteristic, matching: { (data) -> Bool in + guard let value = data else { + return false + } + + guard let response = R(data: value) else { + // We don't recognize the contents. Keep listening. + return false + } + + capturedResponse = response + return true + })) peripheral.readValue(for: characteristic) } - guard let value = characteristic.value else { + guard let response = capturedResponse else { // TODO: This is an "unknown value" issue, not a timeout + if let value = characteristic.value { + log.error("Unknown response data: %{public}@", value.hexadecimalString) + } throw PeripheralManagerError.timeout } - return value + return response } /// - Throws: PeripheralManagerError - func writeValue(_ value: Data, + func writeMessage(_ message: T, for characteristicUUID: CGMServiceCharacteristicUUID, type: CBCharacteristicWriteType = .withResponse, - timeout: TimeInterval = 2, - expectingFirstByte firstByte: UInt8?) throws -> Data + timeout: TimeInterval = 2 + ) throws -> T.Response { guard let characteristic = peripheral.getCharacteristicWithUUID(characteristicUUID) else { throw PeripheralManagerError.unknownCharacteristic } + var capturedResponse: T.Response? + try runCommand(timeout: timeout) { if case .withResponse = type { addCondition(.write(characteristic: characteristic)) } if characteristic.isNotifying { - addCondition(.makeValueUpdate(characteristic: characteristic, matchingFirstByte: firstByte)) + addCondition(.valueUpdate(characteristic: characteristic, matching: { (data) -> Bool in + guard let value = data else { + return false + } + + guard let response = T.Response(data: value) else { + // We don't recognize the contents. Keep listening. + return false + } + + capturedResponse = response + return true + })) } - peripheral.writeValue(value, for: characteristic, type: type) + peripheral.writeValue(message.data, for: characteristic, type: type) } - guard let value = characteristic.value else { + guard let response = capturedResponse else { // TODO: This is an "unknown value" issue, not a timeout + if let value = characteristic.value { + log.error("Unknown response data: %{public}@", value.hexadecimalString) + } throw PeripheralManagerError.timeout } - return value + return response } /// - Throws: PeripheralManagerError - func writeValue(_ value: Data, + func writeMessage(_ message: TransmitterTxMessage, for characteristicUUID: CGMServiceCharacteristicUUID, type: CBCharacteristicWriteType = .withResponse, timeout: TimeInterval = 2) throws @@ -84,24 +122,7 @@ extension PeripheralManager { throw PeripheralManagerError.unknownCharacteristic } - try writeValue(value, for: characteristic, type: type, timeout: timeout) - } -} - - -fileprivate extension PeripheralManager.CommandCondition { - static func makeValueUpdate(characteristic: CBCharacteristic, matchingFirstByte firstByte: UInt8?) -> PeripheralManager.CommandCondition { - return .valueUpdate(characteristic: characteristic, matching: { value in - if let firstByte = firstByte { - if let value = value, value.count > 0, value[0] == firstByte { - return true - } else { - return false - } - } else { // No condition on response - return true - } - }) + try writeValue(message.data, for: characteristic, type: type, timeout: timeout) } } diff --git a/xDripG5/Transmitter.swift b/xDripG5/Transmitter.swift index 0e3417a1..d5497c55 100644 --- a/xDripG5/Transmitter.swift +++ b/xDripG5/Transmitter.swift @@ -126,12 +126,12 @@ public final class Transmitter: BluetoothManagerDelegate { } func bluetoothManager(_ manager: BluetoothManager, shouldConnectPeripheral peripheral: CBPeripheral) -> Bool { - /// The Dexcom G5 advertises a peripheral name of "DexcomXX" /// where "XX" is the last-two characters of the transmitter ID. if let name = peripheral.name, name.suffix(2) == id.id.suffix(2) { return true } else { + self.log.info("Not connecting to peripheral: %{public}@", peripheral.name ?? String(describing: peripheral)) return false } } @@ -141,8 +141,8 @@ public final class Transmitter: BluetoothManagerDelegate { guard response.count > 0 else { return } - switch response[0] { - case GlucoseRxMessage.opcode: + switch Opcode(rawValue: response[0]) { + case .glucoseRx?: if let glucoseMessage = GlucoseRxMessage(data: response), let timeMessage = lastTimeMessage, let activationDate = activationDate @@ -150,21 +150,21 @@ public final class Transmitter: BluetoothManagerDelegate { delegateQueue.async { self.delegate?.transmitter(self, didRead: Glucose(glucoseMessage: glucoseMessage, timeMessage: timeMessage, activationDate: activationDate)) } - return } - case CalibrationDataRxMessage.opcode, SessionStartRxMessage.opcode, SessionStopRxMessage.opcode: - return // Ignore these messages - case TransmitterTimeRxMessage.opcode: + case .transmitterTimeRx?: if let timeMessage = TransmitterTimeRxMessage(data: response) { self.activationDate = Date(timeIntervalSinceNow: -TimeInterval(timeMessage.currentTime)) self.lastTimeMessage = timeMessage return } + case .none: + delegateQueue.async { + self.delegate?.transmitter(self, didReadUnknownData: response) + } default: + // We ignore all other known opcodes break } - - delegate?.transmitter(self, didReadUnknownData: response) } } @@ -193,64 +193,67 @@ struct TransmitterID { return nil } - return outData.subdata(in: 0..<8) + return outData[0..<8] } } // MARK: - Helpers fileprivate extension PeripheralManager { - fileprivate func authenticate(id: TransmitterID) throws -> AuthStatusRxMessage { + fileprivate func authenticate(id: TransmitterID) throws -> AuthChallengeRxMessage { let authMessage = AuthRequestTxMessage() - let authRequestRx: Data do { - try writeValue(authMessage.data, for: .authentication) - authRequestRx = try readValue(for: .authentication, expectingFirstByte: AuthChallengeRxMessage.opcode) + try writeMessage(authMessage, for: .authentication) } catch let error { throw TransmitterError.authenticationError("Error writing transmitter challenge: \(error)") } - guard let challengeRx = AuthChallengeRxMessage(data: authRequestRx) else { - throw TransmitterError.authenticationError("Unable to parse auth challenge: \(authRequestRx)") + let authResponse: AuthRequestRxMessage + do { + authResponse = try readMessage(for: .authentication) + } catch let error { + throw TransmitterError.authenticationError("Unable to parse auth challenge: \(error)") } - guard challengeRx.tokenHash == id.computeHash(of: authMessage.singleUseToken) else { + guard authResponse.tokenHash == id.computeHash(of: authMessage.singleUseToken) else { throw TransmitterError.authenticationError("Transmitter failed auth challenge") } - guard let challengeHash = id.computeHash(of: challengeRx.challenge) else { + guard let challengeHash = id.computeHash(of: authResponse.challenge) else { throw TransmitterError.authenticationError("Failed to compute challenge hash for transmitter ID") } - let statusData: Data + do { - try writeValue(AuthChallengeTxMessage(challengeHash: challengeHash).data, for: .authentication) - statusData = try readValue(for: .authentication, expectingFirstByte: AuthStatusRxMessage.opcode) + try writeMessage(AuthChallengeTxMessage(challengeHash: challengeHash), for: .authentication) } catch let error { throw TransmitterError.authenticationError("Error writing challenge response: \(error)") } - guard let status = AuthStatusRxMessage(data: statusData) else { - throw TransmitterError.authenticationError("Unable to parse auth status: \(statusData)") + let challengeResponse: AuthChallengeRxMessage + do { + challengeResponse = try readMessage(for: .authentication) + } catch let error { + throw TransmitterError.authenticationError("Unable to parse auth status: \(error)") } - guard status.authenticated == 1 else { + guard challengeResponse.authenticated == 1 else { throw TransmitterError.authenticationError("Transmitter rejected auth challenge") } - return status + return challengeResponse } fileprivate func requestBond() throws { do { - try writeValue(KeepAliveTxMessage(time: 25).data, for: .authentication) + try writeMessage(KeepAliveTxMessage(time: 25), for: .authentication) } catch let error { throw TransmitterError.authenticationError("Error writing keep-alive for bond: \(error)") } do { - try writeValue(BondRequestTxMessage().data, for: .authentication) + try writeMessage(BondRequestTxMessage(), for: .authentication) } catch let error { throw TransmitterError.authenticationError("Error writing bond request: \(error)") } @@ -267,34 +270,26 @@ fileprivate extension PeripheralManager { throw TransmitterError.controlError("Error enabling notification: \(error)") } - let timeData: Data + let timeMessage: TransmitterTimeRxMessage do { - timeData = try writeValue(TransmitterTimeTxMessage().data, for: .control, expectingFirstByte: TransmitterTimeRxMessage.opcode) + timeMessage = try writeMessage(TransmitterTimeTxMessage(), for: .control) } catch let error { - throw TransmitterError.controlError("Error writing time request: \(error)") - } - - guard let timeMessage = TransmitterTimeRxMessage(data: timeData) else { - throw TransmitterError.controlError("Unable to parse time response: \(timeData)") + throw TransmitterError.controlError("Error getting time: \(error)") } let activationDate = Date(timeIntervalSinceNow: -TimeInterval(timeMessage.currentTime)) - let glucoseData: Data + let glucoseMessage: GlucoseRxMessage do { - glucoseData = try writeValue(GlucoseTxMessage().data, for: .control, expectingFirstByte: GlucoseRxMessage.opcode) + glucoseMessage = try writeMessage(GlucoseTxMessage(), for: .control) } catch let error { - throw TransmitterError.controlError("Error writing glucose request: \(error)") - } - - guard let glucoseMessage = GlucoseRxMessage(data: glucoseData) else { - throw TransmitterError.controlError("Unable to parse glucose response: \(glucoseData)") + throw TransmitterError.controlError("Error getting glucose: \(error)") } defer { do { try setNotifyValue(false, for: .control) - try writeValue(DisconnectTxMessage().data, for: .control) + try writeMessage(DisconnectTxMessage(), for: .control) } catch { } }