Better handling for Dexcom G5 pairing
This commit is contained in:
parent
0c771af5d4
commit
bbdd6f138d
|
@ -28,6 +28,9 @@
|
|||
F81D6D4E22BFC762005EFAE2 /* TextsDexcomShareTestResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81D6D4D22BFC762005EFAE2 /* TextsDexcomShareTestResult.swift */; };
|
||||
F81D6D5022BFC7DC005EFAE2 /* DexcomShareTestResult.strings in Resources */ = {isa = PBXBuildFile; fileRef = F81D6D4F22BFC7DC005EFAE2 /* DexcomShareTestResult.strings */; };
|
||||
F81D6D5222C27F18005EFAE2 /* BgReading+DexcomShare.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81D6D5122C27F18005EFAE2 /* BgReading+DexcomShare.swift */; };
|
||||
F81D6D5622CAB8AC005EFAE2 /* KeepAliveTxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81D6D5522CAB8AB005EFAE2 /* KeepAliveTxMessage.swift */; };
|
||||
F81D6D5822CF487F005EFAE2 /* DexcomTransmitterOpCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81D6D5722CF487F005EFAE2 /* DexcomTransmitterOpCode.swift */; };
|
||||
F81D6D5A22CF947E005EFAE2 /* PairRequestTxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81D6D5922CF947E005EFAE2 /* PairRequestTxMessage.swift */; };
|
||||
F81F9FF822861E6D0028C70F /* KeyValueObserverTimeKeeper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81F9FF722861E6D0028C70F /* KeyValueObserverTimeKeeper.swift */; };
|
||||
F81F9FFC2288C7530028C70F /* NewAlertSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81F9FFB2288C7530028C70F /* NewAlertSettingsViewController.swift */; };
|
||||
F81FA0002289E4990028C70F /* AlertSettingsViewControllerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F81F9FFF2289E4990028C70F /* AlertSettingsViewControllerData.swift */; };
|
||||
|
@ -72,7 +75,6 @@
|
|||
F897AB1F22059EA000CDDD10 /* AuthRequestRxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897AB1922059E9F00CDDD10 /* AuthRequestRxMessage.swift */; };
|
||||
F897AB2022059EA000CDDD10 /* SensorDataTxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897AB1A22059EA000CDDD10 /* SensorDataTxMessage.swift */; };
|
||||
F897AB2122059EA000CDDD10 /* AuthRequestTxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897AB1B22059EA000CDDD10 /* AuthRequestTxMessage.swift */; };
|
||||
F897AB2222059EA000CDDD10 /* Opcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897AB1C22059EA000CDDD10 /* Opcode.swift */; };
|
||||
F897AB242206585F00CDDD10 /* AuthChallengeRxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897AB232206585F00CDDD10 /* AuthChallengeRxMessage.swift */; };
|
||||
F897AB2622073C4B00CDDD10 /* AuthChallengeTxMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897AB2522073C4A00CDDD10 /* AuthChallengeTxMessage.swift */; };
|
||||
F897AB2A220742E900CDDD10 /* AESCrypt.m in Sources */ = {isa = PBXBuildFile; fileRef = F897AB28220742E700CDDD10 /* AESCrypt.m */; };
|
||||
|
@ -188,6 +190,9 @@
|
|||
F81D6D4D22BFC762005EFAE2 /* TextsDexcomShareTestResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextsDexcomShareTestResult.swift; sourceTree = "<group>"; };
|
||||
F81D6D4F22BFC7DC005EFAE2 /* DexcomShareTestResult.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = DexcomShareTestResult.strings; sourceTree = "<group>"; };
|
||||
F81D6D5122C27F18005EFAE2 /* BgReading+DexcomShare.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BgReading+DexcomShare.swift"; sourceTree = "<group>"; };
|
||||
F81D6D5522CAB8AB005EFAE2 /* KeepAliveTxMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeepAliveTxMessage.swift; sourceTree = "<group>"; };
|
||||
F81D6D5722CF487F005EFAE2 /* DexcomTransmitterOpCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DexcomTransmitterOpCode.swift; sourceTree = "<group>"; };
|
||||
F81D6D5922CF947E005EFAE2 /* PairRequestTxMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PairRequestTxMessage.swift; sourceTree = "<group>"; };
|
||||
F81F9FF722861E6D0028C70F /* KeyValueObserverTimeKeeper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueObserverTimeKeeper.swift; sourceTree = "<group>"; };
|
||||
F81F9FFB2288C7530028C70F /* NewAlertSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewAlertSettingsViewController.swift; sourceTree = "<group>"; };
|
||||
F81F9FFF2289E4990028C70F /* AlertSettingsViewControllerData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertSettingsViewControllerData.swift; sourceTree = "<group>"; };
|
||||
|
@ -233,7 +238,6 @@
|
|||
F897AB1922059E9F00CDDD10 /* AuthRequestRxMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthRequestRxMessage.swift; sourceTree = "<group>"; };
|
||||
F897AB1A22059EA000CDDD10 /* SensorDataTxMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorDataTxMessage.swift; sourceTree = "<group>"; };
|
||||
F897AB1B22059EA000CDDD10 /* AuthRequestTxMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthRequestTxMessage.swift; sourceTree = "<group>"; };
|
||||
F897AB1C22059EA000CDDD10 /* Opcode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Opcode.swift; sourceTree = "<group>"; };
|
||||
F897AB232206585F00CDDD10 /* AuthChallengeRxMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthChallengeRxMessage.swift; sourceTree = "<group>"; };
|
||||
F897AB2522073C4A00CDDD10 /* AuthChallengeTxMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthChallengeTxMessage.swift; sourceTree = "<group>"; };
|
||||
F897AB28220742E700CDDD10 /* AESCrypt.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AESCrypt.m; sourceTree = "<group>"; };
|
||||
|
@ -745,6 +749,9 @@
|
|||
F897AAFF22036D4300CDDD10 /* G5Messages */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F81D6D5922CF947E005EFAE2 /* PairRequestTxMessage.swift */,
|
||||
F81D6D5722CF487F005EFAE2 /* DexcomTransmitterOpCode.swift */,
|
||||
F81D6D5522CAB8AB005EFAE2 /* KeepAliveTxMessage.swift */,
|
||||
F897AB29220742E800CDDD10 /* AESCrypt.h */,
|
||||
F897AB28220742E700CDDD10 /* AESCrypt.m */,
|
||||
F897AB232206585F00CDDD10 /* AuthChallengeRxMessage.swift */,
|
||||
|
@ -755,7 +762,6 @@
|
|||
F897AB38220775B100CDDD10 /* BatteryStatusTxMessage.swift */,
|
||||
F897AB302207716E00CDDD10 /* FirmwareVersionTxMessage.swift */,
|
||||
F897AB1522059E8500CDDD10 /* NSData+CRC.swift */,
|
||||
F897AB1C22059EA000CDDD10 /* Opcode.swift */,
|
||||
F897AB3C220A243300CDDD10 /* ResetMessage.swift */,
|
||||
F897AB2C220761F200CDDD10 /* SensorDataRxMessage.swift */,
|
||||
F897AB1A22059EA000CDDD10 /* SensorDataTxMessage.swift */,
|
||||
|
@ -1239,6 +1245,7 @@
|
|||
F897AB2D220761F200CDDD10 /* SensorDataRxMessage.swift in Sources */,
|
||||
F897AAF92200F2D200CDDD10 /* CBPeripheralState.swift in Sources */,
|
||||
F821CF57229BF43A005C1E43 /* SnoozeParameters.swift in Sources */,
|
||||
F81D6D5822CF487F005EFAE2 /* DexcomTransmitterOpCode.swift in Sources */,
|
||||
F8B3A79722635A25004BA588 /* AlertEntry+CoreDataProperties.swift in Sources */,
|
||||
F80610C4222D4E4D00D8F236 /* ActionClosureable-extension.swift in Sources */,
|
||||
F897AB242206585F00CDDD10 /* AuthChallengeRxMessage.swift in Sources */,
|
||||
|
@ -1276,7 +1283,8 @@
|
|||
F8B3A80A227A3D11004BA588 /* TextsAlertTypeSettings.swift in Sources */,
|
||||
F8B3A850227F26F8004BA588 /* AlertTypesSettingsViewController.swift in Sources */,
|
||||
F8EA6CAD21BC2CA40082976B /* BluetoothTransmitter.swift in Sources */,
|
||||
F897AB2222059EA000CDDD10 /* Opcode.swift in Sources */,
|
||||
F81D6D5622CAB8AC005EFAE2 /* KeepAliveTxMessage.swift in Sources */,
|
||||
F81D6D5A22CF947E005EFAE2 /* PairRequestTxMessage.swift in Sources */,
|
||||
F8B3A808227A2933004BA588 /* SettingsSelectedRowAction.swift in Sources */,
|
||||
F8E3C3AB21FE17B700907A04 /* StringProtocol.swift in Sources */,
|
||||
F8B3A78E22622954004BA588 /* AlertType+CoreDataClass.swift in Sources */,
|
||||
|
|
|
@ -57,6 +57,9 @@ struct Constants {
|
|||
enum DexcomG5 {
|
||||
/// how often to read battery level
|
||||
static let batteryReadPeriodInHours = 12.0
|
||||
|
||||
/// in case transmitter needs pairing, how long to keep connection up to give time to the user to accept the pairing request, inclusive opening the notification
|
||||
static let maxTimeToAcceptPairingInSeconds = 60
|
||||
}
|
||||
|
||||
/// for use in OSLog
|
||||
|
@ -162,6 +165,11 @@ struct Constants {
|
|||
/// sensor not detected notification
|
||||
static let sensorNotDetected = "sensorNotDetected"
|
||||
}
|
||||
|
||||
enum NotificationIdentifierForTransmitterNeedsPairing {
|
||||
/// transmitter needs pairing
|
||||
static let transmitterNeedsPairing = "transmitterNeedsPairing"
|
||||
}
|
||||
}
|
||||
|
||||
/// defines name of the Soundfile and name of the sound shown to the user with an extra function - both are defined in one case, seperated by a backslash - to be used for alerts - all these sounds will be shown
|
||||
|
|
|
@ -167,12 +167,12 @@ public class AlertManager:NSObject {
|
|||
}
|
||||
}
|
||||
|
||||
/// Function to be called that receives the notification actions. Will handle the response. completionHandler will not necessarily be called. Only if the identifier (response.notification.request.identifier) is one of the alert notification identifers, then it will handle the response and also call completionhandler.
|
||||
/// Function to be called that receives the notification actions. Will handle the response.
|
||||
///
|
||||
/// this function looks very similar to the UNUserNotificationCenterDelegate function, difference is that it returns an optional instance of PickerViewData. This will have the snooze data, ie title, actionHandler, cancelHandler, list of values, etc. Goal is not to have UI related stuff in AlertManager class. it's the caller that needs to decide how to present the data
|
||||
/// - returns:
|
||||
/// - PickerViewData : contains data that user needs to pick from, nil means nothing to pick from
|
||||
public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) -> PickerViewData? {
|
||||
public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) -> PickerViewData? {
|
||||
|
||||
// declare returnValue
|
||||
var returnValue:PickerViewData?
|
||||
|
@ -213,10 +213,6 @@ public class AlertManager:NSObject {
|
|||
|
||||
}
|
||||
|
||||
// it is possible to play the sound, show the content and/or set the badge counter as explained here https://developer.apple.com/documentation/usernotifications/unnotificationpresentationoptions
|
||||
// none of them seems useful here
|
||||
completionHandler()
|
||||
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,3 +24,6 @@
|
|||
"startsensorbeforecalibration" = "You can not calibrate as long as there's no sensor started.";
|
||||
"theremustbeareadingbeforecalibration" = "There must be at least two readings before you can calibrate. You will be requested to calibrate as soon as a reading arrives.";
|
||||
"sensornotdetected" = "Sensor not detected. Check if the MiaoMiao is well placed on the sensor.";
|
||||
"transmitternotpaired" = "Transmitter is not paired with this iOS device. Open the application.";
|
||||
"transmitterpairingtoolate" = "Too late, the transmitter has disconnected now. You should get a new pairing request in a few minutes.";
|
||||
"transmitterpairingattempttimeout" = "Transmitter did not reply to pairing request.";
|
||||
|
|
|
@ -107,4 +107,21 @@ enum Texts_HomeView {
|
|||
static let sensorNotDetected:String = {
|
||||
return NSLocalizedString("sensornotdetected", tableName: filename, bundle: Bundle.main, value: "Sensor not detected. Check if the MiaoMiao is well placed on the sensor.", comment: "for home view, miaomiao doesn't detect a sensor")
|
||||
}()
|
||||
|
||||
static let transmitterNotPaired:String = {
|
||||
return NSLocalizedString("transmitternotpaired", tableName: filename, bundle: Bundle.main, value: "Transmitter is not paired with this iOS device. Open the application.", comment: "If transmitter needs pairing, user needs to click the notification")
|
||||
}()
|
||||
|
||||
static let transmitterPairingTooLate:String = {
|
||||
return NSLocalizedString("transmitterpairingtoolate", tableName: filename, bundle: Bundle.main, value: "Too late, the transmitter has disconnected now. You should get a new pairing request in a few minutes.", comment: "If transmitter needs pairing, a notification was fired, user clicked it more than 60 seconds later, which is too late")
|
||||
}()
|
||||
|
||||
static let transmitterPairingSuccessful:String = {
|
||||
return NSLocalizedString("transmitterpairingsuccessful", tableName: filename, bundle: Bundle.main, value: "Transmitter successfully paired.", comment: "To give info to user that the transmitter is successfully paired")
|
||||
}()
|
||||
|
||||
static let transmitterPairingAttemptTimeout:String = {
|
||||
return NSLocalizedString("transmitterpairingattempttimeout", tableName: filename, bundle: Bundle.main, value: "Transmitter did not reply to pairing request.", comment: "To give info to user that the transmitter pairing requeset timed out")
|
||||
}()
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import Foundation
|
||||
import CoreBluetooth
|
||||
|
||||
/// defines functions that every transmitter should implement, mainly used by rootviewcontroller to get transmitter address, name, deterine status etc.
|
||||
///
|
||||
/// Most of the functions are already defined by BlueToothTransmitter.swift - so most of these functions don't need re-implementation in CGMTransmitter classes that conform to this protocol.
|
||||
///
|
||||
/// An exception is for example initiatePairing, which is implemented in CGMG5Transmitter.swift, because that transmitter needs to send a message to the transmitter that will cause the app to request the user to accept the pairing
|
||||
protocol CGMTransmitter {
|
||||
|
||||
/// get device address, cgmtransmitters should also derive from BlueToothTransmitter, hence no need to implement this function
|
||||
func address() -> String?
|
||||
|
||||
|
@ -15,10 +21,16 @@ protocol CGMTransmitter {
|
|||
|
||||
/// get connection status, nil if peripheral not yet known, ie never connected or discovered the transmitter
|
||||
func getConnectionStatus() -> CBPeripheralState?
|
||||
|
||||
/// to ask transmitter that it initiates pairing
|
||||
///
|
||||
/// for transmitter types that don't need pairing, or that don't need pairing initiated by user/view controller, this will be an empty function. Only G5 (and in future maybe G6) will use it. The others can define an empty body
|
||||
func initiatePairing()
|
||||
}
|
||||
|
||||
/// cgm transmitter types
|
||||
enum CGMTransmitterType:String, CaseIterable {
|
||||
|
||||
/// dexcom G4 using xdrip, xbridge, ...
|
||||
case dexcomG4 = "Dexcom G4"
|
||||
/// dexcom G5
|
||||
|
|
|
@ -36,6 +36,12 @@ protocol CGMTransmitterDelegate:AnyObject {
|
|||
|
||||
/// transmitter needs bluetooth pairing
|
||||
func cgmTransmitterNeedsPairing()
|
||||
|
||||
/// transmitter successfully paired
|
||||
func successfullyPaired()
|
||||
|
||||
/// transmitter pairing failed
|
||||
func pairingFailed()
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -154,7 +154,15 @@ final class CGMG4xDripTransmitter: BluetoothTransmitter, BluetoothTransmitterDel
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: helper functions
|
||||
// MARK: CGMTransmitter protocol functions
|
||||
|
||||
/// to ask pairing - empty function because G4 doesn't need pairing
|
||||
///
|
||||
/// this function is not implemented in BluetoothTransmitter.swift, otherwise it might be forgotten to look at in future CGMTransmitter developments
|
||||
func initiatePairing() {
|
||||
}
|
||||
|
||||
// MARK: helper functions
|
||||
|
||||
private func processxBridgeDataPacket(value:Data) -> (glucoseData:RawGlucoseData?, batteryLevel:Int?, transmitterID:String?) {
|
||||
guard value.count >= 10 else {
|
||||
|
|
|
@ -3,38 +3,62 @@ import CoreBluetooth
|
|||
import os
|
||||
|
||||
class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTransmitter {
|
||||
|
||||
// MARK: - properties
|
||||
|
||||
/// UUID's
|
||||
// MARK: UUID's
|
||||
|
||||
// advertisement
|
||||
let CBUUID_Advertisement_G5 = "0000FEBC-0000-1000-8000-00805F9B34FB"
|
||||
|
||||
// service
|
||||
let CBUUID_Service_G5 = "F8083532-849E-531C-C594-30F1F86A4EA5"
|
||||
|
||||
// characteristic uuids (created them in an enum as there's a lot of them, it's easy to switch through the list)
|
||||
private enum CBUUID_Characteristic_UUID:String, CustomStringConvertible {
|
||||
|
||||
// Read/Notify characteristic
|
||||
case CBUUID_Communication = "F8083533-849E-531C-C594-30F1F86A4EA5"
|
||||
|
||||
// Write/Indicate - write characteristic
|
||||
case CBUUID_Write_Control = "F8083534-849E-531C-C594-30F1F86A4EA5"
|
||||
|
||||
// Read/Write/Indicate - Read Characteristic
|
||||
case CBUUID_Receive_Authentication = "F8083535-849E-531C-C594-30F1F86A4EA5"
|
||||
|
||||
// Read/Write/Notify
|
||||
case CBUUID_Backfill = "F8083536-849E-531C-C594-30F1F86A4EA5"
|
||||
|
||||
var description: String {return self.rawValue}
|
||||
/// for logging, returns a readable name for the characteristic
|
||||
var description: String {
|
||||
switch self {
|
||||
|
||||
case .CBUUID_Communication:
|
||||
return "Communication"
|
||||
case .CBUUID_Write_Control:
|
||||
return "Write_Control"
|
||||
case .CBUUID_Receive_Authentication:
|
||||
return "Receive_Authentication"
|
||||
case .CBUUID_Backfill:
|
||||
return "Backfill"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stored characteristics
|
||||
// MARK: other
|
||||
|
||||
/// the write and control Characteristic
|
||||
private var writeControlCharacteristic:CBCharacteristic?
|
||||
|
||||
/// the receive and authentication Characteristic
|
||||
private var receiveAuthenticationCharacteristic:CBCharacteristic?
|
||||
|
||||
/// the communication Characteristic
|
||||
private var communicationCharacteristic:CBCharacteristic?
|
||||
|
||||
/// the backfill Characteristic
|
||||
private var backfillCharacteristic:CBCharacteristic?
|
||||
|
||||
|
||||
//timestamp of last reading
|
||||
private var timeStampOfLastG5Reading:Date
|
||||
|
||||
|
@ -68,6 +92,9 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
|
|||
// for creating testreadings
|
||||
private var testAmount:Double = 150000.0
|
||||
|
||||
/// true if pairing request was done, and waiting to see if pairing was done
|
||||
private var waitingPairingConfirmation = false
|
||||
|
||||
// MARK: - functions
|
||||
|
||||
/// - parameters:
|
||||
|
@ -157,6 +184,8 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
|
|||
}
|
||||
|
||||
override func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
|
||||
|
||||
// if last reading was less than a minute ago, then no need to continue, otherwise continue with process by calling super.centralManager(central, didConnect: peripheral)
|
||||
if Date() < Date(timeInterval: 60, since: timeStampOfLastG5Reading) {
|
||||
os_log("connected, but last reading was less than 1 minute ago, disconnecting", log: log, type: .info)
|
||||
//TODO: is it not better to keep connection open till it times out ? should be tested with new device, see if battery drains, if it does, try with removing the disconnect - Spike also disconnects
|
||||
|
@ -164,6 +193,9 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
|
|||
} else {
|
||||
super.centralManager(central, didConnect: peripheral)
|
||||
}
|
||||
|
||||
// to be sure waitingPairingConfirmation is reset to false
|
||||
waitingPairingConfirmation = false
|
||||
}
|
||||
|
||||
override func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
|
||||
|
@ -176,23 +208,29 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
|
|||
|
||||
if let characteristics = service.characteristics {
|
||||
for characteristic in characteristics {
|
||||
let ASCIIstring = characteristic.uuid.uuidString
|
||||
os_log("characteristic uuid: %{public}@", log: log, type: .info, ASCIIstring)
|
||||
if let characteristicValue = CBUUID_Characteristic_UUID(rawValue: ASCIIstring) {
|
||||
|
||||
if let characteristicValue = CBUUID_Characteristic_UUID(rawValue: characteristic.uuid.uuidString) {
|
||||
|
||||
os_log(" characteristic : %{public}@", log: log, type: .info, characteristicValue.description)
|
||||
|
||||
switch characteristicValue {
|
||||
case .CBUUID_Backfill:
|
||||
backfillCharacteristic = characteristic
|
||||
|
||||
case .CBUUID_Write_Control:
|
||||
writeControlCharacteristic = characteristic
|
||||
|
||||
case .CBUUID_Communication:
|
||||
communicationCharacteristic = characteristic
|
||||
|
||||
case .CBUUID_Receive_Authentication:
|
||||
receiveAuthenticationCharacteristic = characteristic
|
||||
os_log(" calling setNotifyValue true", log: log, type: .info)
|
||||
peripheral.setNotifyValue(true, for: characteristic)
|
||||
|
||||
}
|
||||
} else {
|
||||
os_log(" characteristic UUID unknown", log: log, type: .error)
|
||||
os_log(" characteristic UUID unknown : %{public}@", log: log, type: .error, characteristic.uuid.uuidString)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -200,6 +238,14 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: CGMTransmitter protocol functions
|
||||
|
||||
/// to ask pairing
|
||||
func initiatePairing() {
|
||||
// assuming that the transmitter is effectively awaiting the pairing, otherwise this obviously won't work
|
||||
sendPairingRequest()
|
||||
}
|
||||
|
||||
// MARK: BluetoothTransmitterDelegate functions
|
||||
|
||||
func centralManagerDidConnect(address:String?, name:String?) {
|
||||
|
@ -222,18 +268,32 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
|
|||
}
|
||||
|
||||
func centralManagerDidDisconnectPeripheral(error: Error?) {
|
||||
|
||||
if waitingPairingConfirmation {
|
||||
// device has requested a pairing request and is now in a status of verifying if pairing was successfull or not, this by doing setNotify to writeCharacteristic. If a disconnect occurs now, it means pairing has failed (probably because user didn't approve it
|
||||
waitingPairingConfirmation = false
|
||||
|
||||
// inform delegate
|
||||
cgmTransmitterDelegate?.pairingFailed()
|
||||
}
|
||||
|
||||
// inform delegate
|
||||
cgmTransmitterDelegate?.cgmTransmitterDidDisconnect()
|
||||
}
|
||||
|
||||
func peripheralDidUpdateNotificationStateFor(characteristic: CBCharacteristic, error: Error?) {
|
||||
os_log("in peripheralDidUpdateNotificationStateFor", log: log, type: .info)
|
||||
|
||||
if let error = error {
|
||||
os_log(" error: %{public}@", log: log, type: .error , error.localizedDescription)
|
||||
}
|
||||
let ASCIIstring = characteristic.uuid.uuidString
|
||||
os_log(" characteristic uuid: %{public}@", log: log, type: .info, ASCIIstring)
|
||||
if let characteristicValue = CBUUID_Characteristic_UUID(rawValue: ASCIIstring) {
|
||||
|
||||
if let characteristicValue = CBUUID_Characteristic_UUID(rawValue: characteristic.uuid.uuidString) {
|
||||
|
||||
os_log(" characteristic : %{public}@", log: log, type: .info, characteristicValue.description)
|
||||
|
||||
switch characteristicValue {
|
||||
|
||||
case .CBUUID_Write_Control:
|
||||
if (G5ResetRequested) {
|
||||
// send ResetTxMessage
|
||||
|
@ -242,9 +302,11 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
|
|||
// send SensorTxMessage to transmitter
|
||||
getSensorData()
|
||||
}
|
||||
|
||||
case .CBUUID_Receive_Authentication:
|
||||
//send AuthRequestTxMessage
|
||||
sendAuthRequestTxMessage()
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -254,7 +316,14 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
|
|||
}
|
||||
|
||||
func peripheralDidUpdateValueFor(characteristic: CBCharacteristic, error: Error?) {
|
||||
os_log("in peripheralDidUpdateValueFor", log: log, type: .info)
|
||||
|
||||
guard let characteristic_UUID = CBUUID_Characteristic_UUID(rawValue: characteristic.uuid.uuidString) else {
|
||||
os_log("in peripheralDidUpdateValueFor, unknown characteristic received with uuid = %{public}@", log: log, type: .error, characteristic.uuid.uuidString)
|
||||
return
|
||||
}
|
||||
|
||||
os_log("in peripheralDidUpdateValueFor, characteristic uuid = %{public}@", log: log, type: .info, characteristic_UUID.description)
|
||||
|
||||
if let error = error {
|
||||
os_log("error: %{public}@", log: log, type: .error , error.localizedDescription)
|
||||
}
|
||||
|
@ -267,24 +336,39 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
|
|||
|
||||
//check type of message and process according to type
|
||||
if let firstByte = value.first {
|
||||
if let opCode = Opcode(rawValue: firstByte) {
|
||||
if let opCode = DexcomTransmitterOpCode(rawValue: firstByte) {
|
||||
os_log(" opcode = %{public}@", log: log, type: .info, opCode.description)
|
||||
switch opCode {
|
||||
|
||||
case .authChallengeRx:
|
||||
if let authChallengeRxMessage = AuthChallengeRxMessage(data: value) {
|
||||
if !authChallengeRxMessage.bonded {
|
||||
|
||||
// if not paired, then send message to delegate
|
||||
if !authChallengeRxMessage.paired {
|
||||
|
||||
os_log(" transmitter needs pairing, calling sendKeepAliveMessage", log: log, type: .info)
|
||||
|
||||
// will send keep alive message
|
||||
sendKeepAliveMessage()
|
||||
|
||||
// delegate needs to be informed that pairing is needed
|
||||
cgmTransmitterDelegate?.cgmTransmitterNeedsPairing()
|
||||
os_log(" transmitter needs paring", log: log, type: .info)
|
||||
|
||||
} else {
|
||||
|
||||
// subscribe to writeControlCharacteristic
|
||||
if let writeControlCharacteristic = writeControlCharacteristic {
|
||||
setNotifyValue(true, for: writeControlCharacteristic)
|
||||
} else {
|
||||
os_log(" writeControlCharacteristic is nil, can not set notifyValue", log: log, type: .error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
os_log(" failed to create authChallengeRxMessage", log: log, type: .info)
|
||||
}
|
||||
|
||||
case .authRequestRx:
|
||||
if let authRequestRxMessage = AuthRequestRxMessage(data: value), let receiveAuthenticationCharacteristic = receiveAuthenticationCharacteristic {
|
||||
|
||||
|
@ -295,39 +379,55 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
|
|||
|
||||
let authChallengeTxMessage = AuthChallengeTxMessage(challengeHash: challengeHash)
|
||||
_ = writeDataToPeripheral(data: authChallengeTxMessage.data, characteristicToWriteTo: receiveAuthenticationCharacteristic, type: .withResponse)
|
||||
|
||||
} else {
|
||||
os_log(" writeControlCharacteristic is nil or authRequestRxMessage is nil", log: log, type: .error)
|
||||
}
|
||||
|
||||
case .sensorDataRx:
|
||||
|
||||
// if this is the first sensorDataRx after a successful pairing, then inform delegate that pairing is finished
|
||||
if waitingPairingConfirmation {
|
||||
waitingPairingConfirmation = false
|
||||
cgmTransmitterDelegate?.successfullyPaired()
|
||||
}
|
||||
|
||||
if let sensorDataRxMessage = SensorDataRxMessage(data: value) {
|
||||
|
||||
if transmitterVersion != nil {
|
||||
|
||||
// transmitterversion was already recceived, let's see if we need to get the batterystatus
|
||||
if Date() > Date(timeInterval: Constants.DexcomG5.batteryReadPeriodInHours * 60 * 60, since: timeStampOfLastBatteryReading) {
|
||||
os_log(" last battery reading was long time, ago requesting now", log: log, type: .info)
|
||||
os_log(" last battery reading was long time ago, requesting now", log: log, type: .info)
|
||||
if let writeControlCharacteristic = writeControlCharacteristic {
|
||||
_ = writeDataToPeripheral(data: BatteryStatusTxMessage().data, characteristicToWriteTo: writeControlCharacteristic, type: .withResponse)
|
||||
timeStampOfLastBatteryReading = Date()
|
||||
} else {
|
||||
os_log(" writeControlCharacteristic is nil, can not send BatteryStatusTxMessage", log: log, type: .error)
|
||||
}
|
||||
//TODO: strictly speaking a disconnect should be done after having written the data
|
||||
} else {
|
||||
disconnect()
|
||||
}
|
||||
} else {
|
||||
|
||||
if let writeControlCharacteristic = writeControlCharacteristic {
|
||||
_ = writeDataToPeripheral(data: TransmitterVersionTxMessage().data, characteristicToWriteTo: writeControlCharacteristic, type: .withResponse)
|
||||
} else {
|
||||
os_log(" writeControlCharacteristic is nil, can not send TransmitterVersionTxMessage", log: log, type: .error)
|
||||
}
|
||||
//TODO: strictly speaking a disconnect should be done after having written the data
|
||||
|
||||
}
|
||||
|
||||
//if reset was done recently, less than 5 minutes ago, then ignore the reading
|
||||
if Date() < Date(timeInterval: 5 * 60, since: timeStampTransmitterReset) {
|
||||
os_log(" last transmitterreset was less than 5 minutes ago, ignoring this reading", log: log, type: .info)
|
||||
os_log(" last transmitter reset was less than 5 minutes ago, ignoring this reading", log: log, type: .info)
|
||||
//} else if sensorDataRxMessage.unfiltered == 0.0 {
|
||||
// os_log(" sensorDataRxMessage.unfiltered = 0.0, ignoring this reading", log: log, type: .info)
|
||||
} else {
|
||||
if Date() < Date(timeInterval: 60, since: timeStampOfLastG5Reading) {
|
||||
os_log(" last reading was less than 1 minute ago, disconnecting", log: log, type: .info)
|
||||
// should probably never come here because this check is already done at connection time
|
||||
os_log(" last reading was less than 1 minute ago, ignoring", log: log, type: .info)
|
||||
} else {
|
||||
timeStampOfLastG5Reading = Date()
|
||||
let glucoseData = RawGlucoseData(timeStamp: sensorDataRxMessage.timestamp, glucoseLevelRaw: sensorDataRxMessage.unfiltered, glucoseLevelFiltered: sensorDataRxMessage.filtered)
|
||||
|
@ -335,17 +435,42 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
|
|||
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: nil, sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, serialNumber: nil, bootloader: nil)
|
||||
}
|
||||
}
|
||||
//start processing now the sensorDataRxMessage
|
||||
|
||||
} else {
|
||||
os_log(" sensorDataRxMessagee is nil", log: log, type: .error)
|
||||
}
|
||||
|
||||
case .resetRx:
|
||||
|
||||
processResetRxMessage(value: value)
|
||||
|
||||
case .batteryStatusRx:
|
||||
|
||||
processBatteryStatusRxMessage(value: value)
|
||||
|
||||
case .transmitterVersionRx:
|
||||
|
||||
processTransmitterVersionRxMessage(value: value)
|
||||
|
||||
case .keepAliveRx:
|
||||
|
||||
// seems no processing is necessary, now the user should get a pairing requeset
|
||||
break
|
||||
|
||||
case .paireRequestRx:
|
||||
|
||||
// don't know if the user accepted the pairing request or not, we can only know by trying to subscribe to writeControlCharacteristic - if the device is paired, we'll receive a sensorDataRx message, if not paired, then a disconnect will happen
|
||||
|
||||
// set status to waitingForPairingConfirmation
|
||||
waitingPairingConfirmation = true
|
||||
|
||||
// setNotifyValue
|
||||
if let writeControlCharacteristic = writeControlCharacteristic {
|
||||
setNotifyValue(true, for: writeControlCharacteristic)
|
||||
} else {
|
||||
os_log(" writeControlCharacteristic is nil, can not set notifyValue", log: log, type: .error)
|
||||
}
|
||||
|
||||
default:
|
||||
os_log(" unknown opcode received ", log: log, type: .error)
|
||||
break
|
||||
|
@ -442,4 +567,30 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
|
|||
return nil
|
||||
}
|
||||
|
||||
/// sends pairing request to transmitter, this will result in an iOS generated pairing request
|
||||
private func sendPairingRequest() {
|
||||
|
||||
if let receiveAuthenticationCharacteristic = receiveAuthenticationCharacteristic {
|
||||
|
||||
_ = writeDataToPeripheral(data: PairRequestTxMessage().data, characteristicToWriteTo: receiveAuthenticationCharacteristic, type: .withResponse)
|
||||
|
||||
} else {
|
||||
os_log(" in sendBondingRequest, receiveAuthenticationCharacteristic is nil, can not send KeepAliveTxMessage", log: log, type: .error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// sends a keepalive message, this will keep the connection with the transmitter open for 60 seconds
|
||||
private func sendKeepAliveMessage() {
|
||||
|
||||
if let receiveAuthenticationCharacteristic = receiveAuthenticationCharacteristic {
|
||||
|
||||
// to make sure the Dexcom doesn't disconnect the next 60 seconds, this gives the user sufficient time to accept the pairing request, which will come next
|
||||
_ = writeDataToPeripheral(data: KeepAliveTxMessage(time: UInt8(Constants.DexcomG5.maxTimeToAcceptPairingInSeconds)).data, characteristicToWriteTo: receiveAuthenticationCharacteristic, type: .withResponse)
|
||||
|
||||
} else {
|
||||
os_log(" in sendKeepAliveMessage, receiveAuthenticationCharacteristic is nil, can not send KeepAliveTxMessage", log: log, type: .error)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import Foundation
|
|||
|
||||
struct AuthChallengeRxMessage: TransmitterRxMessage {
|
||||
let authenticated: UInt8
|
||||
let bonded: Bool
|
||||
let paired: Bool
|
||||
|
||||
init?(data: Data) {
|
||||
guard data.count >= 3 else {
|
||||
|
@ -23,6 +23,6 @@ struct AuthChallengeRxMessage: TransmitterRxMessage {
|
|||
}
|
||||
|
||||
authenticated = data[1]
|
||||
bonded = data[2] != 2
|
||||
paired = data[2] != 2
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import Foundation
|
|||
struct BatteryStatusTxMessage {
|
||||
typealias Response = BatteryStatusRxMessage
|
||||
|
||||
let opcode: Opcode = .batteryStatusTx
|
||||
let opcode: DexcomTransmitterOpCode = .batteryStatusTx
|
||||
var data: Data {
|
||||
return Data(for: .batteryStatusTx).appendingCRC()
|
||||
}
|
||||
|
|
|
@ -7,15 +7,16 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
enum Opcode: UInt8 {
|
||||
enum DexcomTransmitterOpCode: UInt8 {
|
||||
// Auth
|
||||
case authRequestTx = 0x01
|
||||
|
||||
case authRequestRx = 0x03
|
||||
case authChallengeTx = 0x04
|
||||
case authChallengeRx = 0x05
|
||||
case keepAlive = 0x06 // auth; setAdvertisementParametersTx for control
|
||||
case bondRequest = 0x07
|
||||
case keepAliveTx = 0x06 // auth; setAdvertisementParametersTx for control
|
||||
case bondRequestTx = 0x07
|
||||
case paireRequestRx = 0x08 // comes in after having accepted the bluetooth pairing request
|
||||
|
||||
// Control
|
||||
case disconnectTx = 0x09
|
||||
|
@ -55,15 +56,17 @@ enum Opcode: UInt8 {
|
|||
|
||||
case glucoseBackfillTx = 0x50
|
||||
case glucoseBackfillRx = 0x51
|
||||
|
||||
case keepAliveRx = 0xFF // found during testing
|
||||
}
|
||||
|
||||
|
||||
extension Data {
|
||||
init(for opcode: Opcode) {
|
||||
init(for opcode: DexcomTransmitterOpCode) {
|
||||
self.init(bytes: [opcode.rawValue])
|
||||
}
|
||||
|
||||
func starts(with opcode: Opcode) -> Bool {
|
||||
func starts(with opcode: DexcomTransmitterOpCode) -> Bool {
|
||||
guard count > 0 else {
|
||||
return false
|
||||
}
|
||||
|
@ -72,7 +75,7 @@ extension Data {
|
|||
}
|
||||
}
|
||||
|
||||
extension Opcode: CustomStringConvertible {
|
||||
extension DexcomTransmitterOpCode: CustomStringConvertible {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .authRequestRx:
|
|
@ -10,5 +10,5 @@ import Foundation
|
|||
|
||||
|
||||
struct FirmwareVersionTxMessage {
|
||||
let opcode: Opcode = .firmwareVersionTx
|
||||
let opcode: DexcomTransmitterOpCode = .firmwareVersionTx
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// KeepAliveTxMessage.swift
|
||||
// xDrip5
|
||||
//
|
||||
// Created by Nathan Racklyeft on 11/23/15.
|
||||
// Copyright © 2015 Nathan Racklyeft. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
struct KeepAliveTxMessage: TransmitterTxMessage {
|
||||
let time: UInt8
|
||||
|
||||
var data: Data {
|
||||
var data = Data(for: .keepAliveTx)
|
||||
data.append(time)
|
||||
return data
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
//
|
||||
// BondRequestTxMessage.swift
|
||||
// xDrip5
|
||||
//
|
||||
// Created by Nathan Racklyeft on 11/23/15.
|
||||
// Copyright © 2015 Nathan Racklyeft. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
/// Initiates a bond with the central
|
||||
struct PairRequestTxMessage: TransmitterTxMessage {
|
||||
var data: Data {
|
||||
return Data(for: .bondRequestTx)
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import Foundation
|
|||
struct TransmitterVersionTxMessage {
|
||||
typealias Response = TransmitterVersionRxMessage
|
||||
|
||||
let opcode: Opcode = .transmitterVersionTx
|
||||
let opcode: DexcomTransmitterOpCode = .transmitterVersionTx
|
||||
var data: Data {
|
||||
return Data(for: .transmitterVersionTx).appendingCRC()
|
||||
}
|
||||
|
|
|
@ -254,6 +254,14 @@ class CGMGNSEntryTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate,
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: CGMTransmitter protocol functions
|
||||
|
||||
/// to ask pairing - empty function because G4 doesn't need pairing
|
||||
///
|
||||
/// this function is not implemented in BluetoothTransmitter.swift, otherwise it might be forgotten to look at in future CGMTransmitter developments
|
||||
func initiatePairing() {
|
||||
}
|
||||
|
||||
// MARK: CBCentralManager overriden functions
|
||||
|
||||
override func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
|
||||
|
|
|
@ -200,6 +200,14 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate,
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: CGMTransmitter protocol functions
|
||||
|
||||
/// to ask pairing - empty function because G4 doesn't need pairing
|
||||
///
|
||||
/// this function is not implemented in BluetoothTransmitter.swift, otherwise it might be forgotten to look at in future CGMTransmitter developments
|
||||
func initiatePairing() {
|
||||
}
|
||||
|
||||
// MARK: - helpers
|
||||
|
||||
/// reset rxBuffer, reset startDate, stop packetRxMonitorTimer, set resendPacketCounter to 0
|
||||
|
|
|
@ -48,6 +48,9 @@ final class RootViewController: UIViewController {
|
|||
|
||||
/// constant for key in ApplicationManager.shared.addClosureToRunWhenAppDidEnterBackground - updateLabels
|
||||
private let applicationManagerKeyUpdateLabels = "RootViewController-UpdateLabels"
|
||||
|
||||
/// constant for key in ApplicationManager.shared.addClosureToRunWhenAppWillEnterForeground - initiate pairing
|
||||
private let applicationManagerKeyInitiatePairing = "RootViewController-InitiatePairing"
|
||||
|
||||
// MARK: - Properties - other private properties
|
||||
|
||||
|
@ -87,6 +90,9 @@ final class RootViewController: UIViewController {
|
|||
/// dexcomShareUploadManager instance
|
||||
private var dexcomShareUploadManager:DexcomShareUploadManager?
|
||||
|
||||
/// timer used when asking the transmitter to initiate pairing. The user is waiting for the response, if the response from the transmitter doesn't come within a few seconds, then we'll inform the user
|
||||
private var transmitterPairingResponseTimer:Timer?
|
||||
|
||||
/// healthkit manager instance
|
||||
private var healthKitManager:HealthKitManager?
|
||||
|
||||
|
@ -401,6 +407,27 @@ final class RootViewController: UIViewController {
|
|||
|
||||
// MARK: - private helper functions
|
||||
|
||||
// inform user that pairing request timed out
|
||||
@objc private func informUserThatPairingTimedOut() {
|
||||
UIAlertController(title: Texts_Common.warning, message: "time out", actionHandler: nil).presentInOwnWindow(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
/// will call cgmTransmitter.initiatePairing() - also sets timer, if no successful pairing within a few seconds, then info will be given to user asking to wait another few minutes
|
||||
private func initiateTransmitterPairing() {
|
||||
|
||||
// initiate the pairing
|
||||
cgmTransmitter?.initiatePairing()
|
||||
|
||||
// invalide the timer, if it exists
|
||||
if let transmitterPairingResponseTimer = transmitterPairingResponseTimer {
|
||||
transmitterPairingResponseTimer.invalidate()
|
||||
}
|
||||
|
||||
// create and schedule timer
|
||||
transmitterPairingResponseTimer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(informUserThatPairingTimedOut), userInfo: nil, repeats: false)
|
||||
|
||||
}
|
||||
|
||||
/// launches timer that will do regular screen updates - and adds closure to ApplicationManager : when going to background, stop the timer, when coming to foreground, restart the timer
|
||||
///
|
||||
/// should be called only once immediately after app start, ie in viewdidload
|
||||
|
@ -918,10 +945,29 @@ final class RootViewController: UIViewController {
|
|||
|
||||
}
|
||||
|
||||
// MARK: - conform to CGMTransmitter protocol
|
||||
|
||||
/// conform to CGMTransmitterDelegate
|
||||
extension RootViewController:CGMTransmitterDelegate {
|
||||
|
||||
// MARK: - CGMTransmitter protocol functions
|
||||
func pairingFailed() {
|
||||
// this should be the consequence of the user not accepting the pairing request, there's no need to inform the user
|
||||
// invalidate transmitterPairingResponseTimer
|
||||
if let transmitterPairingResponseTimer = transmitterPairingResponseTimer {
|
||||
transmitterPairingResponseTimer.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
func successfullyPaired() {
|
||||
|
||||
// invalidate transmitterPairingResponseTimer
|
||||
if let transmitterPairingResponseTimer = transmitterPairingResponseTimer {
|
||||
transmitterPairingResponseTimer.invalidate()
|
||||
}
|
||||
|
||||
// inform user
|
||||
UIAlertController(title: Texts_HomeView.info, message: Texts_HomeView.transmitterPairingSuccessful, actionHandler: nil).presentInOwnWindow(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func cgmTransmitterDidConnect(address:String?, name:String?) {
|
||||
// store address and name, if this is the first connect to a specific device, then this address and name will be used in the future to reconnect to the same device, without having to scan
|
||||
|
@ -939,10 +985,8 @@ extension RootViewController:CGMTransmitterDelegate {
|
|||
}
|
||||
|
||||
func cgmTransmitterDidDisconnect() {
|
||||
|
||||
// set disconnect timestamp
|
||||
UserDefaults.standard.lastdisConnectTimestamp = Date()
|
||||
|
||||
}
|
||||
|
||||
func deviceDidUpdateBluetoothState(state: CBManagerState) {
|
||||
|
@ -973,9 +1017,64 @@ extension RootViewController:CGMTransmitterDelegate {
|
|||
|
||||
}
|
||||
|
||||
/// Transmitter is calling this delegate function to indicate that bluetooth pairing is needed. If the app is in the background, the user will be informed, after opening the app a pairing request will be initiated. if the app is in the foreground, the pairing request will be initiated immediately
|
||||
func cgmTransmitterNeedsPairing() {
|
||||
//TODO: needs implementation
|
||||
print("NEEDS IMPLEMENTATION")
|
||||
|
||||
os_log("transmitter needs pairing", log: log, type: .info)
|
||||
|
||||
// Create Notification Content
|
||||
let notificationContent = UNMutableNotificationContent()
|
||||
|
||||
// Configure NnotificationContent title
|
||||
notificationContent.title = Texts_Common.warning
|
||||
|
||||
notificationContent.body = Texts_HomeView.transmitterNotPaired
|
||||
|
||||
// add sound
|
||||
notificationContent.sound = UNNotificationSound.init(named: UNNotificationSoundName.init(""))
|
||||
|
||||
// Create Notification Request
|
||||
let notificationRequest = UNNotificationRequest(identifier: Constants.Notifications.NotificationIdentifierForTransmitterNeedsPairing.transmitterNeedsPairing, content: notificationContent, trigger: nil)
|
||||
|
||||
// Add Request to User Notification Center
|
||||
UNUserNotificationCenter.current().add(notificationRequest) { (error) in
|
||||
if let error = error {
|
||||
os_log("Unable to transmitter needs pairing Notification Request %{public}@", log: self.log, type: .error, error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
// vibrate
|
||||
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
|
||||
|
||||
// add closure to ApplicationManager.shared.addClosureToRunWhenAppWillEnterForeground so that if user opens the app, the pairing request will be initiated. This can be done only if the app is opened within 60 seconds.
|
||||
// If the app is already in the foreground, then userNotificationCenter willPresent will be called, in this function the closure will be removed immediately, and the pairing request will be called. As a result, if the app is in the foreground, the user will not see (or hear) any notification, but the pairing will be initiated
|
||||
|
||||
// max timestamp when notification was fired - connection stays open for 1 minute, taking 1 second as d
|
||||
let maxTimeUserCanOpenApp = Date(timeIntervalSinceNow: TimeInterval(Constants.DexcomG5.maxTimeToAcceptPairingInSeconds - 1))
|
||||
|
||||
// we will not just count on it that the user will click the notification to open the app (assuming the app is in the background, if the app is in the foreground, then we come in another flow)
|
||||
// we will
|
||||
// whenever app comes from-back to freground, updateLabels needs to be called
|
||||
ApplicationManager.shared.addClosureToRunWhenAppWillEnterForeground(key: applicationManagerKeyInitiatePairing, closure: {
|
||||
|
||||
// first of all reremove from application key manager
|
||||
ApplicationManager.shared.removeClosureToRunWhenAppWillEnterForeground(key: self.applicationManagerKeyInitiatePairing)
|
||||
|
||||
// first remove existing notification if any
|
||||
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [Constants.Notifications.NotificationIdentifierForTransmitterNeedsPairing.transmitterNeedsPairing])
|
||||
|
||||
// if it was too long since notification was fired, then forget about it
|
||||
if Date() > maxTimeUserCanOpenApp {
|
||||
os_log("in cgmTransmitterNeedsPairing, user opened the app too late", log: self.log, type: .error)
|
||||
UIAlertController(title: Texts_Common.warning, message: Texts_HomeView.transmitterPairingTooLate, actionHandler: nil).presentInOwnWindow(animated: true, completion: nil)
|
||||
return
|
||||
}
|
||||
|
||||
// initiate the pairing
|
||||
self.cgmTransmitter?.initiatePairing()
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// Only MioaMiao will call this
|
||||
|
@ -1010,6 +1109,7 @@ extension RootViewController:CGMTransmitterDelegate {
|
|||
/// - parameters:
|
||||
/// - readings: first entry is the most recent
|
||||
func cgmTransmitterInfoReceived(glucoseData: inout [RawGlucoseData], transmitterBatteryInfo: TransmitterBatteryInfo?, sensorState: SensorState?, sensorTimeInMinutes: Int?, firmware: String?, hardware: String?, serialNumber: String?, bootloader: String?) {
|
||||
|
||||
os_log("sensorstate %{public}@", log: log, type: .debug, sensorState?.description ?? "no sensor state found")
|
||||
os_log("firmware %{public}@", log: log, type: .debug, firmware ?? "no firmware version found")
|
||||
os_log("bootloader %{public}@", log: log, type: .debug, bootloader ?? "no bootloader found")
|
||||
|
@ -1017,12 +1117,15 @@ extension RootViewController:CGMTransmitterDelegate {
|
|||
os_log("hardware %{public}@", log: log, type: .debug, hardware ?? "no hardware version found")
|
||||
os_log("transmitterBatteryInfo %{public}@", log: log, type: .debug, transmitterBatteryInfo?.description ?? 0)
|
||||
os_log("sensor time in minutes %{public}@", log: log, type: .debug, sensorTimeInMinutes?.description ?? "not received")
|
||||
os_log("glucoseData size = %{public}@", log: log, type: .debug, glucoseData.count.description)
|
||||
|
||||
processNewCGMInfo(glucoseData: &glucoseData, sensorState: sensorState, firmware: firmware, hardware: hardware, transmitterBatteryInfo: transmitterBatteryInfo, sensorTimeInMinutes: sensorTimeInMinutes)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - conform to UITabBarControllerDelegate protocol
|
||||
|
||||
/// conform to UITabBarControllerDelegate, want to receive info when user clicks specific tabs
|
||||
extension RootViewController: UITabBarControllerDelegate {
|
||||
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
|
||||
|
@ -1034,23 +1137,38 @@ extension RootViewController: UITabBarControllerDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - conform to UNUserNotificationCenterDelegate protocol
|
||||
|
||||
/// conform to UNUserNotificationCenterDelegate, for notifications
|
||||
extension RootViewController:UNUserNotificationCenterDelegate {
|
||||
|
||||
//called when notification created while app is in foreground
|
||||
// called when notification created while app is in foreground
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
|
||||
|
||||
if notification.request.identifier == Constants.Notifications.NotificationIdentifiersForCalibration.initialCalibrationRequest {
|
||||
|
||||
// request calibration
|
||||
requestCalibration(userRequested: false)
|
||||
|
||||
// call completionhandler
|
||||
// call completionhandler to avoid that notification is shown to the user
|
||||
completionHandler([])
|
||||
|
||||
} else if notification.request.identifier == Constants.Notifications.NotificationIdentifierForSensorNotDetected.sensorNotDetected {
|
||||
|
||||
// call completionhandler to show the notification even though the app is in the foreground, without sound
|
||||
completionHandler([.alert])
|
||||
|
||||
} else if notification.request.identifier == Constants.Notifications.NotificationIdentifierForTransmitterNeedsPairing.transmitterNeedsPairing {
|
||||
|
||||
// so actually the app was in the foreground, at the moment the Transmitter Class called the cgmTransmitterNeedsPairing function, there's no need to show the notification, we can immediately call back the cgmTransmitter initiatePairing function
|
||||
completionHandler([])
|
||||
cgmTransmitter?.initiatePairing()
|
||||
|
||||
/// remove applicationManagerKeyInitiatePairing from application key manager - there's no need to initiate the pairing via this closure
|
||||
ApplicationManager.shared.removeClosureToRunWhenAppWillEnterForeground(key: self.applicationManagerKeyInitiatePairing)
|
||||
|
||||
} else {
|
||||
|
||||
// this will verify if it concerns an alert notification, if not pickerviewData will be nil
|
||||
if let pickerViewData = alertManager?.userNotificationCenter(center, willPresent: notification, withCompletionHandler: completionHandler) {
|
||||
|
||||
|
@ -1062,23 +1180,35 @@ extension RootViewController:UNUserNotificationCenterDelegate {
|
|||
|
||||
// called when user clicks a notification
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
||||
|
||||
os_log("userNotificationCenter didReceive", log: log, type: .info)
|
||||
|
||||
// call completionHandler when exiting function
|
||||
defer {
|
||||
// call completionhandler
|
||||
completionHandler()
|
||||
}
|
||||
|
||||
if response.notification.request.identifier == Constants.Notifications.NotificationIdentifiersForCalibration.initialCalibrationRequest {
|
||||
|
||||
os_log(" userNotificationCenter didReceive, user pressed calibration notification to open the app", log: log, type: .info)
|
||||
// request calibration
|
||||
requestCalibration(userRequested: false)
|
||||
|
||||
// call completionhandler
|
||||
completionHandler()
|
||||
} else if response.notification.request.identifier == Constants.Notifications.NotificationIdentifierForSensorNotDetected.sensorNotDetected {
|
||||
|
||||
// if user clicks notification "sensor not detected", then show uialert with title and body
|
||||
UIAlertController(title: Texts_Common.warning, message: Texts_HomeView.sensorNotDetected, actionHandler: nil).presentInOwnWindow(animated: true, completion: nil)
|
||||
|
||||
} else if response.notification.request.identifier == Constants.Notifications.NotificationIdentifierForTransmitterNeedsPairing.transmitterNeedsPairing {
|
||||
|
||||
// nothing required, the pairing function will be called as it's been added to ApplicationManager in function cgmTransmitterNeedsPairing
|
||||
|
||||
} else {
|
||||
|
||||
// it's not an initial calibration request notification that the user clicked, by calling alertManager?.userNotificationCenter, we check if it was an alert notification that was clicked and if yes pickerViewData will have the list of alert snooze values
|
||||
if let pickerViewData = alertManager?.userNotificationCenter(center, didReceive: response, withCompletionHandler: completionHandler) {
|
||||
if let pickerViewData = alertManager?.userNotificationCenter(center, didReceive: response) {
|
||||
|
||||
os_log(" userNotificationCenter didReceive, user pressed an alert notification to open the app", log: log, type: .info)
|
||||
PickerViewController.displayPickerViewController(pickerViewData: pickerViewData, parentController: self)
|
||||
|
||||
|
@ -1089,6 +1219,8 @@ extension RootViewController:UNUserNotificationCenterDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - conform to NightScoutFollowerDelegate protocol
|
||||
|
||||
extension RootViewController:NightScoutFollowerDelegate {
|
||||
|
||||
func nightScoutFollowerInfoReceived(followGlucoseDataArray: inout [FollowGlucoseData]) {
|
||||
|
@ -1148,7 +1280,5 @@ extension RootViewController:NightScoutFollowerDelegate {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue