Better handling for Dexcom G5 pairing

This commit is contained in:
Johan Degraeve 2019-07-05 16:30:18 +02:00
parent 0c771af5d4
commit bbdd6f138d
19 changed files with 449 additions and 54 deletions

View File

@ -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 */,

View File

@ -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

View File

@ -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
}
}

View File

@ -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.";

View File

@ -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")
}()
}

View File

@ -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

View File

@ -36,6 +36,12 @@ protocol CGMTransmitterDelegate:AnyObject {
/// transmitter needs bluetooth pairing
func cgmTransmitterNeedsPairing()
/// transmitter successfully paired
func successfullyPaired()
/// transmitter pairing failed
func pairingFailed()
}

View File

@ -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 {

View File

@ -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)
}
}
}

View File

@ -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
}
}

View File

@ -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()
}

View File

@ -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:

View File

@ -10,5 +10,5 @@ import Foundation
struct FirmwareVersionTxMessage {
let opcode: Opcode = .firmwareVersionTx
let opcode: DexcomTransmitterOpCode = .firmwareVersionTx
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -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?) {

View File

@ -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

View File

@ -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 {
}
}
}
}