This commit is contained in:
Johan Degraeve 2019-07-07 19:11:44 +02:00
parent a861e68127
commit f1f9c0f01d
17 changed files with 207 additions and 28 deletions

View File

@ -170,6 +170,11 @@ struct Constants {
/// transmitter needs pairing
static let transmitterNeedsPairing = "transmitterNeedsPairing"
}
enum NotificationIdentifierForResetResult {
/// transmitter reset result
static let transmitterResetResult = "transmitterResetResult"
}
}
/// 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

@ -8,7 +8,7 @@ extension UserDefaults {
// General
/// bloodglucose unit
case bloodGlucoseUnit = "bloodGlucoseUnit"
case bloodGlucoseUnitIsMgDl = "bloodGlucoseUnit"
/// low value
case lowMarkValue = "lowMarkValue"
/// high value
@ -90,6 +90,8 @@ extension UserDefaults {
// Transmitter
/// Transmitter Battery Level
case transmitterBatteryInfo = "transmitterbatteryinfo"
/// Dexcom transmitter reset required
case transmitterResetRequired = "transmitterResetRequired"
// HealthKit
/// did user authorize the storage of readings in healthkit or not
@ -111,10 +113,10 @@ extension UserDefaults {
@objc dynamic var bloodGlucoseUnitIsMgDl: Bool {
//default value for bool in userdefaults is false, false is for mgdl, true is for mmol
get {
return !bool(forKey: Key.bloodGlucoseUnit.rawValue)
return !bool(forKey: Key.bloodGlucoseUnitIsMgDl.rawValue)
}
set {
set(!newValue, forKey: Key.bloodGlucoseUnit.rawValue)
set(!newValue, forKey: Key.bloodGlucoseUnitIsMgDl.rawValue)
}
}
@ -482,6 +484,18 @@ extension UserDefaults {
}
}
}
/// is transmitter reset required or not
@objc dynamic var transmitterResetRequired: Bool {
get {
return bool(forKey: Key.transmitterResetRequired.rawValue)
}
set {
set(newValue, forKey: Key.transmitterResetRequired.rawValue)
}
}
/// did user authorize the storage of readings in healthkit or not - this setting is actually only used to allow the HealthKitManager to listen for changes in the authorization status
var storeReadingsInHealthkitAuthorized:Bool {

View File

@ -18,3 +18,5 @@
"update" = "Update";
"add" = "Add";
"settingsviews_speakIntervalMessage" = "Minimum interval between two readings, in minutes";
"yes" = "yes";
"no" = "no";

View File

@ -27,3 +27,6 @@
"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.";
"transmitterResultResult" = "Transmitter reset result";
"success" = "success";
"failed" = "failed";

View File

@ -35,3 +35,4 @@
"settingsviews_follower" = "Follower";
"settingsviews_speakreadingslanguageselection" = "Select Language";
"settingsviews_speakBgReadingslanguage" = "Language";
"settingsviews_resettransmitter" = "Reset Transmitter";

View File

@ -81,4 +81,11 @@ class Texts_Common {
return NSLocalizedString("add", tableName: filename, bundle: Bundle.main, value: "Add", comment: "literally add")
}()
static let yes = {
return NSLocalizedString("yes", tableName: filename, bundle: Bundle.main, value: "yes", comment: "literally yes, without capital")
}()
static let no = {
return NSLocalizedString("no", tableName: filename, bundle: Bundle.main, value: "no", comment: "literally no, without capital")
}()
}

View File

@ -124,4 +124,16 @@ enum Texts_HomeView {
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")
}()
static let transmitterResetResult:String = {
return NSLocalizedString("transmitterResultResult", tableName: filename, bundle: Bundle.main, value: "Transmitter reset result", comment: "To give result about transitter result in notification body")
}()
static let success:String = {
return NSLocalizedString("success", tableName: filename, bundle: Bundle.main, value: "success", comment: "To give result about transitter result in notification body, successful")
}()
static let failed:String = {
return NSLocalizedString("failed", tableName: filename, bundle: Bundle.main, value: "failed", comment: "To give result about transitter result in notification body, failed")
}()
}

View File

@ -58,6 +58,10 @@ class Texts_SettingsView {
return NSLocalizedString("settingsviews_givetransmitterid", tableName: filename, bundle: Bundle.main, value: "Enter Transmitter Id", comment: "transmitter settings, pop up that asks user to inter transmitter id")
}()
static let labelResetTransmitter:String = {
return NSLocalizedString("settingsviews_resettransmitter", tableName: filename, bundle: Bundle.main, value: "Reset Transmitter", comment: "transmitter settings, to explain that settings is about resetting the transmitter")
}()
// MARK: - Section Alerts
static let sectionTitleAlerting: String = {

View File

@ -1,7 +1,7 @@
import Foundation
import CoreBluetooth
/// defines functions that every transmitter should implement, mainly used by rootviewcontroller to get transmitter address, name, deterine status etc.
/// defines functions that every cgm transmitter should conform to, 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.
///
@ -26,6 +26,13 @@ protocol CGMTransmitter {
///
/// 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()
/// to reset the transmitter
/// - parameters:
/// - requested : if true then transmitter must be reset
/// for transmitter types that don't support resetting, this will be an empty function. Only G5 (and in future maybe G6) will use it. The others can define an empty body
func reset(requested:Bool)
}
/// cgm transmitter types
@ -40,6 +47,9 @@ enum CGMTransmitterType:String, CaseIterable {
/// GNSentry
case GNSentry = "GNSentry"
/// does the transmitter need a transmitter id ?
///
/// can be used in UI stuff, if reset not possible then there's no need to show that option in the settings UI
func needsTransmitterId() -> Bool {
switch self {
case .dexcomG4:
@ -69,7 +79,7 @@ enum CGMTransmitterType:String, CaseIterable {
}
}
/// returns nil if idtovalidate has expected length and type of characters etc.
/// returns nil if id to validate has expected length and type of characters etc.
func validateTransimtterId(idtovalidate:String) -> String? {
switch self {
case .dexcomG5:
@ -146,4 +156,21 @@ enum CGMTransmitterType:String, CaseIterable {
return ""
}
}
/// can a transmitter be reset ? For example Dexcom G5 (and G6) can be reset
///
/// can be used in UI stuff, if reset not possible then there's no need to show that option in the settings UI
func resetPossible() -> Bool {
switch self {
case .dexcomG4:
return false
case .dexcomG5:
return true
case .miaomiao:
return false
case .GNSentry:
return false
}
}
}

View File

@ -42,6 +42,9 @@ protocol CGMTransmitterDelegate:AnyObject {
/// transmitter pairing failed
func pairingFailed()
/// transmitter reset result
func reset(successful: Bool)
}

View File

@ -159,8 +159,12 @@ final class CGMG4xDripTransmitter: BluetoothTransmitter, BluetoothTransmitterDel
/// 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() {
}
func initiatePairing() {}
/// to ask transmitter reset - empty function because G4 doesn't support reset
///
/// this function is not implemented in BluetoothTransmitter.swift, otherwise it might be forgotten to look at in future CGMTransmitter developments
func reset(requested:Bool) {}
// MARK: helper functions

View File

@ -168,10 +168,6 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
// MARK: public functions
func doG5Reset() {
G5ResetRequested = true
}
// MARK: CBCentralManager overriden functions
override func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
@ -245,6 +241,11 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
sendPairingRequest()
}
/// to ask transmitter reset
func reset(requested:Bool) {
G5ResetRequested = requested
}
// MARK: BluetoothTransmitterDelegate functions
func centralManagerDidConnect(address:String?, name:String?) {
@ -297,6 +298,10 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
if (G5ResetRequested) {
// send ResetTxMessage
sendG5Reset()
//reset G5ResetRequested to false
G5ResetRequested = false
} else {
// send SensorTxMessage to transmitter
getSensorData()
@ -518,8 +523,13 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
private func processResetRxMessage(value:Data) {
if let resetRxMessage = ResetRxMessage(data: value) {
os_log("resetRxMessage status is %{public}d", log: log, type: .info, resetRxMessage.status)
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: nil, sensorState: SensorState.G5Reset, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, serialNumber: nil, bootloader: nil)
if resetRxMessage.status == 0 {
os_log("resetRxMessage status is 0, considering reset successful", log: log, type: .info)
cgmTransmitterDelegate?.reset(successful: true)
} else {
os_log("resetRxMessage status is %{public}d, considering reset failed", log: log, type: .info, resetRxMessage.status)
cgmTransmitterDelegate?.reset(successful: false)
}
} else {
os_log("resetRxMessage is nil", log: log, type: .error)
}

View File

@ -256,11 +256,15 @@ class CGMGNSEntryTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate,
// MARK: CGMTransmitter protocol functions
/// to ask pairing - empty function because G4 doesn't need pairing
/// to ask pairing - empty function because GNSEntry 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() {
}
func initiatePairing() {}
/// to ask transmitter reset - empty function because GNSEntry doesn't support reset
///
/// this function is not implemented in BluetoothTransmitter.swift, otherwise it might be forgotten to look at in future CGMTransmitter developments
func reset(requested:Bool) {}
// MARK: CBCentralManager overriden functions

View File

@ -205,8 +205,12 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate,
/// 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() {
}
func initiatePairing() {}
/// to ask transmitter reset - empty function because MiaoMiao doesn't support reset
///
/// this function is not implemented in BluetoothTransmitter.swift, otherwise it might be forgotten to look at in future CGMTransmitter developments
func reset(requested:Bool) {}
// MARK: - helpers

View File

@ -29,7 +29,6 @@ enum SensorState {
case shutdown
case failure
case unknown
case G5Reset//TODO: should be done better, probably define other state enum for dexcom
init(){
self = .unknown

View File

@ -141,6 +141,8 @@ final class RootViewController: UIViewController {
// changing from follower to master or vice versa also requires transmitter setup
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.isMaster.rawValue, options: .new
, context: nil)
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.transmitterResetRequired.rawValue, options: .new
, context: nil)
// setup delegate for UNUserNotificationCenter
UNUserNotificationCenter.current().delegate = self
@ -228,6 +230,7 @@ final class RootViewController: UIViewController {
// setup FollowManager
guard let soundPlayer = soundPlayer else { fatalError("In setupApplicationData, this looks very in appropriate, shame")}
// setup nightscoutmanager
nightScoutFollowManager = NightScoutFollowManager(coreDataManager: coreDataManager, nightScoutFollowerDelegate: self)
// setup alertmanager
@ -358,7 +361,6 @@ final class RootViewController: UIViewController {
// MARK:- observe function
// when user changes transmitter type or transmitter id, then new transmitter needs to be setup. That's why observer for these settings is required
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let keyPath = keyPath {
@ -386,6 +388,12 @@ final class RootViewController: UIViewController {
initializeCGMTransmitter()
}
case UserDefaults.Key.transmitterResetRequired :
if (keyValueObserverTimeKeeper.verifyKey(forKey: keyPathEnum.rawValue, withMinimumDelayMilliSeconds: 200)) {
cgmTransmitter?.reset(requested: UserDefaults.standard.transmitterResetRequired)
}
default:
break
}
@ -583,6 +591,9 @@ final class RootViewController: UIViewController {
}
}
//reset UserDefaults.standard.transmitterResetRequired to false, might have been set to true.
UserDefaults.standard.transmitterResetRequired = false
}
/// for debug purposes
@ -621,7 +632,7 @@ final class RootViewController: UIViewController {
// Add Request to User Notification Center
UNUserNotificationCenter.current().add(notificationRequest) { (error) in
if let error = error {
os_log("Unable to Add Notification Request %{public}@", log: self.log, type: .error, error.localizedDescription)
os_log("Unable to Add Notification Request : %{public}@", log: self.log, type: .error, error.localizedDescription)
}
}
}
@ -950,6 +961,31 @@ final class RootViewController: UIViewController {
/// conform to CGMTransmitterDelegate
extension RootViewController:CGMTransmitterDelegate {
func reset(successful: Bool) {
// reset setting to false
UserDefaults.standard.transmitterResetRequired = false
// Create Notification Content to give info about reset result of reset attempt
let notificationContent = UNMutableNotificationContent()
// Configure NnotificationContent title
notificationContent.title = successful ? Texts_HomeView.info : Texts_Common.warning
notificationContent.body = Texts_HomeView.transmitterResetResult + " : " + (successful ? Texts_HomeView.success : Texts_HomeView.failed)
// Create Notification Request
let notificationRequest = UNNotificationRequest(identifier: Constants.Notifications.NotificationIdentifierForResetResult.transmitterResetResult, content: notificationContent, trigger: nil)
// Add Request to User Notification Center
UNUserNotificationCenter.current().add(notificationRequest) { (error) in
if let error = error {
os_log("Unable add notification request : transmitter reset result, error: %{public}@", log: self.log, type: .error, error.localizedDescription)
}
}
}
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
@ -1039,7 +1075,7 @@ extension RootViewController:CGMTransmitterDelegate {
// 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)
os_log("Unable add notification request : transmitter needs pairing Notification Request, error : %{public}@", log: self.log, type: .error, error.localizedDescription)
}
}
@ -1158,6 +1194,10 @@ extension RootViewController:UNUserNotificationCenterDelegate {
// 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.NotificationIdentifierForResetResult.transmitterResetResult {
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
@ -1203,9 +1243,13 @@ extension RootViewController:UNUserNotificationCenterDelegate {
} 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 if response.notification.request.identifier == Constants.Notifications.NotificationIdentifierForResetResult.transmitterResetResult {
// nothing required
} 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) {

View File

@ -1,10 +1,12 @@
import UIKit
fileprivate enum Setting:Int, CaseIterable {
//transmittertype
/// transmittertype
case transmitterType = 0
//transmitterid
/// transmitterid
case transmitterId = 1
/// is transmitter reset required or not (only applicable to Dexcom G5 and later also G6)
case resetRequired = 2
}
/// conforms to SettingsViewModelProtocol for all transmitter settings in the first sections screen
@ -62,6 +64,10 @@ struct SettingsViewTransmitterSettingsViewModel:SettingsViewModelProtocol {
UserDefaults.standard.transmitterTypeAsString = data[index]
}
}, cancelHandler: nil, didSelectRowHandler: nil)
case .resetRequired:
return SettingsSelectedRowAction.callFunction(function: {UserDefaults.standard.transmitterResetRequired ? (UserDefaults.standard.transmitterResetRequired) = false : (UserDefaults.standard.transmitterResetRequired = true)})
}
}
@ -71,8 +77,17 @@ struct SettingsViewTransmitterSettingsViewModel:SettingsViewModelProtocol {
func numberOfRows() -> Int {
if let transmitterType = UserDefaults.standard.transmitterType {
// if transmitter doesn't need transmitterid (like MiaoMiao) then the settings row that asks for transmitterid doesn't need to be shown. That rows is the second row.
return transmitterType.needsTransmitterId() ? 2:1
// if transmitter doesn't need transmitterid (like MiaoMiao) then the settings row that asks for transmitterid doesn't need to be shown. That row is the second row - also reset transmitter not necessary in that case
// if ever there would be a transmitter that doesn't need a transmitter id but that supports reset transmitter, then some recoding will be necessary here
if transmitterType.needsTransmitterId() {
if transmitterType.resetPossible() {
return 3
} else {
return 2
}
} else {
return 1
}
} else {
// transmitterType nil, means this is initial setup, no need to show transmitter id field
return 1
@ -83,25 +98,46 @@ struct SettingsViewTransmitterSettingsViewModel:SettingsViewModelProtocol {
guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") }
switch (setting) {
case .transmitterId:
return Texts_SettingsView.labelTransmitterId
case .transmitterType:
return Texts_SettingsView.labelTransmitterType
case .resetRequired:
return Texts_SettingsView.labelResetTransmitter
}
}
func accessoryType(index: Int) -> UITableViewCell.AccessoryType {
return UITableViewCell.AccessoryType.disclosureIndicator
guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") }
switch setting {
case .transmitterType:
return UITableViewCell.AccessoryType.disclosureIndicator
case .transmitterId:
return UITableViewCell.AccessoryType.disclosureIndicator
case .resetRequired:
return UITableViewCell.AccessoryType.none
}
}
func detailedText(index: Int) -> String? {
guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") }
switch (setting) {
case .transmitterId:
return UserDefaults.standard.transmitterId
case .transmitterType:
return UserDefaults.standard.transmitterType?.rawValue
case .resetRequired:
return UserDefaults.standard.transmitterResetRequired ? Texts_Common.yes:Texts_Common.no
}
}