diff --git a/README.md b/README.md index 12093a3e..0ba50f3f 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Current Status - G5 and G6 - MiaoMiao - G4 with xBridge + - Blucon - Alerting - Upload to Nightscout - Follower mode, with NightScout @@ -19,7 +20,4 @@ Not yet supported - sidiary - graph on the homescreen -Other restrictions -- sensor change detection not fully working, you might have to restart the app after placing the new sensor, maybe two times. I need more testing for this (next sensor replacement) - For developers : please go to the Wiki : https://github.com/JohanDegraeve/xdripswift/wiki diff --git a/xdrip.xcodeproj/project.pbxproj b/xdrip.xcodeproj/project.pbxproj index 7798ff53..689b70cc 100644 --- a/xdrip.xcodeproj/project.pbxproj +++ b/xdrip.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ F821CF9522ADB0D7005C1E43 /* HealthKitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F821CF9422ADB0D7005C1E43 /* HealthKitManager.swift */; }; F821CF9722AE589E005C1E43 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F821CF9622AE589E005C1E43 /* HealthKit.framework */; }; F821CF9D22AEF483005C1E43 /* BGReadingSpeaker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F821CF9C22AEF483005C1E43 /* BGReadingSpeaker.swift */; }; + F856CE5B22EDC8E50083E436 /* ConstantsBluetoothPairing.swift in Sources */ = {isa = PBXBuildFile; fileRef = F856CE5A22EDC8E50083E436 /* ConstantsBluetoothPairing.swift */; }; F85DC2ED21CFE2F500B9F74A /* BgReading+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85DC2E721CFE2F500B9F74A /* BgReading+CoreDataProperties.swift */; }; F85DC2EF21CFE2F500B9F74A /* Sensor+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85DC2E921CFE2F500B9F74A /* Sensor+CoreDataProperties.swift */; }; F85DC2F321CFE3D400B9F74A /* Calibration+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85DC2F021CFE3D400B9F74A /* Calibration+CoreDataClass.swift */; }; @@ -55,6 +56,24 @@ F867E2612252ADAB000FD265 /* Calibration+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F867E25D2252ADAB000FD265 /* Calibration+CoreDataProperties.swift */; }; F897AAF92200F2D200CDDD10 /* CBPeripheralState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897AAF82200F2D200CDDD10 /* CBPeripheralState.swift */; }; F897AAFB2201018800CDDD10 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897AAFA2201018800CDDD10 /* String.swift */; }; + F8A1584D22ECA445007F5B5D /* SettingsViewDevelopmentSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1584C22ECA445007F5B5D /* SettingsViewDevelopmentSettingsViewModel.swift */; }; + F8A1584F22ECB281007F5B5D /* SettingsViewInfoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1584E22ECB281007F5B5D /* SettingsViewInfoViewModel.swift */; }; + F8A1585122EDB597007F5B5D /* ConstantsBGGraphBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1585022EDB597007F5B5D /* ConstantsBGGraphBuilder.swift */; }; + F8A1585322EDB602007F5B5D /* ConstantsBloodGlucose.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1585222EDB602007F5B5D /* ConstantsBloodGlucose.swift */; }; + F8A1585522EDB706007F5B5D /* ConstantsCalibrationAlgorithms.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1585422EDB706007F5B5D /* ConstantsCalibrationAlgorithms.swift */; }; + F8A1585722EDB754007F5B5D /* ConstantsCoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1585622EDB754007F5B5D /* ConstantsCoreData.swift */; }; + F8A1585922EDB7C6007F5B5D /* ConstantsDefaultAlertLevels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1585822EDB7C6007F5B5D /* ConstantsDefaultAlertLevels.swift */; }; + F8A1585B22EDB7EA007F5B5D /* ConstantsDexcomG5.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1585A22EDB7EA007F5B5D /* ConstantsDexcomG5.swift */; }; + F8A1585F22EDB81E007F5B5D /* ConstantsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1585E22EDB81E007F5B5D /* ConstantsLog.swift */; }; + F8A1586122EDB844007F5B5D /* ConstantsNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1586022EDB844007F5B5D /* ConstantsNotifications.swift */; }; + F8A1586322EDB86E007F5B5D /* ConstantsSounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1586222EDB86E007F5B5D /* ConstantsSounds.swift */; }; + F8A1586522EDB89D007F5B5D /* ConstantsDefaultAlertTypeSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1586422EDB89D007F5B5D /* ConstantsDefaultAlertTypeSettings.swift */; }; + F8A1586722EDB8BF007F5B5D /* ConstantsHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1586622EDB8BF007F5B5D /* ConstantsHomeView.swift */; }; + F8A1586B22EDB967007F5B5D /* ConstantsMaster.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1586A22EDB967007F5B5D /* ConstantsMaster.swift */; }; + F8A1586D22EDB9BE007F5B5D /* ConstantsDexcomFollower.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1586C22EDB9BE007F5B5D /* ConstantsDexcomFollower.swift */; }; + F8A1586F22EDC7EE007F5B5D /* ConstantsSuspensionPrevention.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1586E22EDC7EE007F5B5D /* ConstantsSuspensionPrevention.swift */; }; + F8A1587122EDC865007F5B5D /* ConstantsSpeakReadingLanguages.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1587022EDC865007F5B5D /* ConstantsSpeakReadingLanguages.swift */; }; + F8A1587322EDC893007F5B5D /* ConstantsDexcomShare.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A1587222EDC893007F5B5D /* ConstantsDexcomShare.swift */; }; F8A54AAD22D6859200934E7A /* SlopeParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AAC22D6859200934E7A /* SlopeParameters.swift */; }; F8A54AAF22D686CD00934E7A /* NightScoutBgReading.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AAE22D686CD00934E7A /* NightScoutBgReading.swift */; }; F8A54AB722D9111900934E7A /* CGMTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A54AB322D9111900934E7A /* CGMTransmitter.swift */; }; @@ -162,7 +181,6 @@ F8BDD455221DEF22006EAB84 /* SettingsViews.strings in Resources */ = {isa = PBXBuildFile; fileRef = F8BDD457221DEF22006EAB84 /* SettingsViews.strings */; }; F8E3C3AB21FE17B700907A04 /* StringProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E3C3AA21FE17B700907A04 /* StringProtocol.swift */; }; F8E3C3AD21FE551C00907A04 /* DexcomCalibrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E3C3AC21FE551C00907A04 /* DexcomCalibrator.swift */; }; - F8EA6C7F21B70E390082976B /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EA6C7E21B70E390082976B /* Constants.swift */; }; F8EA6C8221B723BC0082976B /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EA6C8121B723BC0082976B /* Date.swift */; }; F8EA6CA921BBE3010082976B /* UniqueId.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EA6CA821BBE3010082976B /* UniqueId.swift */; }; F8EA6CAD21BC2CA40082976B /* BluetoothTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EA6CAC21BC2CA40082976B /* BluetoothTransmitter.swift */; }; @@ -213,6 +231,7 @@ F821CF9622AE589E005C1E43 /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = System/Library/Frameworks/HealthKit.framework; sourceTree = SDKROOT; }; F821CF9822AE589E005C1E43 /* xdrip.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = xdrip.entitlements; sourceTree = ""; }; F821CF9C22AEF483005C1E43 /* BGReadingSpeaker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BGReadingSpeaker.swift; sourceTree = ""; }; + F856CE5A22EDC8E50083E436 /* ConstantsBluetoothPairing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsBluetoothPairing.swift; sourceTree = ""; }; F85DC2E721CFE2F500B9F74A /* BgReading+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "BgReading+CoreDataProperties.swift"; path = "../Extensions/BgReading+CoreDataProperties.swift"; sourceTree = ""; }; F85DC2E921CFE2F500B9F74A /* Sensor+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Sensor+CoreDataProperties.swift"; path = "../Extensions/Sensor+CoreDataProperties.swift"; sourceTree = ""; }; F85DC2F021CFE3D400B9F74A /* Calibration+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Calibration+CoreDataClass.swift"; sourceTree = ""; }; @@ -221,6 +240,24 @@ F867E25D2252ADAB000FD265 /* Calibration+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Calibration+CoreDataProperties.swift"; path = "xdrip/Core Data/extensions/Calibration+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; }; F897AAF82200F2D200CDDD10 /* CBPeripheralState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBPeripheralState.swift; sourceTree = ""; }; F897AAFA2201018800CDDD10 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; + F8A1584C22ECA445007F5B5D /* SettingsViewDevelopmentSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewDevelopmentSettingsViewModel.swift; sourceTree = ""; }; + F8A1584E22ECB281007F5B5D /* SettingsViewInfoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewInfoViewModel.swift; sourceTree = ""; }; + F8A1585022EDB597007F5B5D /* ConstantsBGGraphBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsBGGraphBuilder.swift; sourceTree = ""; }; + F8A1585222EDB602007F5B5D /* ConstantsBloodGlucose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsBloodGlucose.swift; sourceTree = ""; }; + F8A1585422EDB706007F5B5D /* ConstantsCalibrationAlgorithms.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsCalibrationAlgorithms.swift; sourceTree = ""; }; + F8A1585622EDB754007F5B5D /* ConstantsCoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsCoreData.swift; sourceTree = ""; }; + F8A1585822EDB7C6007F5B5D /* ConstantsDefaultAlertLevels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsDefaultAlertLevels.swift; sourceTree = ""; }; + F8A1585A22EDB7EA007F5B5D /* ConstantsDexcomG5.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsDexcomG5.swift; sourceTree = ""; }; + F8A1585E22EDB81E007F5B5D /* ConstantsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsLog.swift; sourceTree = ""; }; + F8A1586022EDB844007F5B5D /* ConstantsNotifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsNotifications.swift; sourceTree = ""; }; + F8A1586222EDB86E007F5B5D /* ConstantsSounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsSounds.swift; sourceTree = ""; }; + F8A1586422EDB89D007F5B5D /* ConstantsDefaultAlertTypeSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsDefaultAlertTypeSettings.swift; sourceTree = ""; }; + F8A1586622EDB8BF007F5B5D /* ConstantsHomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsHomeView.swift; sourceTree = ""; }; + F8A1586A22EDB967007F5B5D /* ConstantsMaster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsMaster.swift; sourceTree = ""; }; + F8A1586C22EDB9BE007F5B5D /* ConstantsDexcomFollower.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConstantsDexcomFollower.swift; sourceTree = ""; }; + F8A1586E22EDC7EE007F5B5D /* ConstantsSuspensionPrevention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsSuspensionPrevention.swift; sourceTree = ""; }; + F8A1587022EDC865007F5B5D /* ConstantsSpeakReadingLanguages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsSpeakReadingLanguages.swift; sourceTree = ""; }; + F8A1587222EDC893007F5B5D /* ConstantsDexcomShare.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsDexcomShare.swift; sourceTree = ""; }; F8A54AAC22D6859200934E7A /* SlopeParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SlopeParameters.swift; sourceTree = ""; }; F8A54AAE22D686CD00934E7A /* NightScoutBgReading.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightScoutBgReading.swift; sourceTree = ""; }; F8A54AB322D9111900934E7A /* CGMTransmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMTransmitter.swift; sourceTree = ""; }; @@ -442,7 +479,6 @@ F8BDD458221DEF24006EAB84 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/SettingsViews.strings; sourceTree = ""; }; F8E3C3AA21FE17B700907A04 /* StringProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringProtocol.swift; sourceTree = ""; }; F8E3C3AC21FE551C00907A04 /* DexcomCalibrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomCalibrator.swift; sourceTree = ""; }; - F8EA6C7E21B70E390082976B /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; F8EA6C8121B723BC0082976B /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; F8EA6CA821BBE3010082976B /* UniqueId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniqueId.swift; sourceTree = ""; }; F8EA6CAC21BC2CA40082976B /* BluetoothTransmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothTransmitter.swift; sourceTree = ""; }; @@ -976,6 +1012,8 @@ F8B3A83C227F090D004BA588 /* SettingsViewHealthKitSettingsViewModel.swift */, F8B3A83D227F090D004BA588 /* SettingsViewSpeakSettingsViewModel.swift */, F8B3A83E227F090D004BA588 /* SettingsViewGeneralSettingsViewModel.swift */, + F8A1584C22ECA445007F5B5D /* SettingsViewDevelopmentSettingsViewModel.swift */, + F8A1584E22ECB281007F5B5D /* SettingsViewInfoViewModel.swift */, ); path = SettingsViewModels; sourceTree = ""; @@ -1036,7 +1074,23 @@ F8EA6C7D21B70DEA0082976B /* Constants */ = { isa = PBXGroup; children = ( - F8EA6C7E21B70E390082976B /* Constants.swift */, + F8A1585022EDB597007F5B5D /* ConstantsBGGraphBuilder.swift */, + F8A1585222EDB602007F5B5D /* ConstantsBloodGlucose.swift */, + F8A1585422EDB706007F5B5D /* ConstantsCalibrationAlgorithms.swift */, + F8A1585622EDB754007F5B5D /* ConstantsCoreData.swift */, + F8A1585822EDB7C6007F5B5D /* ConstantsDefaultAlertLevels.swift */, + F8A1586422EDB89D007F5B5D /* ConstantsDefaultAlertTypeSettings.swift */, + F8A1586C22EDB9BE007F5B5D /* ConstantsDexcomFollower.swift */, + F8A1585A22EDB7EA007F5B5D /* ConstantsDexcomG5.swift */, + F8A1586622EDB8BF007F5B5D /* ConstantsHomeView.swift */, + F8A1585E22EDB81E007F5B5D /* ConstantsLog.swift */, + F8A1586A22EDB967007F5B5D /* ConstantsMaster.swift */, + F8A1586022EDB844007F5B5D /* ConstantsNotifications.swift */, + F8A1586222EDB86E007F5B5D /* ConstantsSounds.swift */, + F8A1586E22EDC7EE007F5B5D /* ConstantsSuspensionPrevention.swift */, + F8A1587022EDC865007F5B5D /* ConstantsSpeakReadingLanguages.swift */, + F8A1587222EDC893007F5B5D /* ConstantsDexcomShare.swift */, + F856CE5A22EDC8E50083E436 /* ConstantsBluetoothPairing.swift */, ); name = Constants; path = xdrip/Constants; @@ -1117,7 +1171,7 @@ TargetAttributes = { F8AC425921ADEBD60078C348 = { CreatedOnToolsVersion = 10.1; - LastSwiftMigration = 1010; + LastSwiftMigration = 1030; SystemCapabilities = { com.apple.BackgroundModes = { enabled = 1; @@ -1265,6 +1319,7 @@ F8B3A81B227DEC92004BA588 /* SensorsAccessor.swift in Sources */, F8B3A85D22821BB6004BA588 /* Int.swift in Sources */, F8A54AAF22D686CD00934E7A /* NightScoutBgReading.swift in Sources */, + F8A1585722EDB754007F5B5D /* ConstantsCoreData.swift in Sources */, F821CF9022AB1068005C1E43 /* DatePickerViewData.swift in Sources */, F8025E4E21ED450300ECF0C0 /* Double.swift in Sources */, F8B3A853227F2743004BA588 /* AlertsSettingsViewController.swift in Sources */, @@ -1286,15 +1341,19 @@ F8AC42A121B31F170078C348 /* xdrip.xcdatamodeld in Sources */, F8A54ADB22D911BA00934E7A /* AuthRequestTxMessage.swift in Sources */, F8EA6CA921BBE3010082976B /* UniqueId.swift in Sources */, + F8A1585122EDB597007F5B5D /* ConstantsBGGraphBuilder.swift in Sources */, F81D6D4822BD5F62005EFAE2 /* DexcomShareUploadManager.swift in Sources */, F8A7406E22D9C0E700967CFC /* CGMBluconTransmitter.swift in Sources */, + F8A1586B22EDB967007F5B5D /* ConstantsMaster.swift in Sources */, F8B3A7B2226A0878004BA588 /* TextsAlerts.swift in Sources */, F8A54B0022D9179100934E7A /* ParseLibreData.swift in Sources */, F8025E5421EE8D2100ECF0C0 /* Libre1Calibrator.swift in Sources */, F81FA00A228F53680028C70F /* TextsHomeView.swift in Sources */, F8E3C3AD21FE551C00907A04 /* DexcomCalibrator.swift in Sources */, F821CF61229BF4A2005C1E43 /* NightScoutUploadManager.swift in Sources */, + F8A1587122EDC865007F5B5D /* ConstantsSpeakReadingLanguages.swift in Sources */, F8A54ADD22D911BA00934E7A /* BatteryStatusTxMessage.swift in Sources */, + F8A1585522EDB706007F5B5D /* ConstantsCalibrationAlgorithms.swift in Sources */, F8A54ADA22D911BA00934E7A /* TransmitterMessage.swift in Sources */, F897AAF92200F2D200CDDD10 /* CBPeripheralState.swift in Sources */, F8A54AE522D911BA00934E7A /* TransmitterVersionRxMessage.swift in Sources */, @@ -1314,25 +1373,34 @@ F85DC2F421CFE3D400B9F74A /* Sensor+CoreDataClass.swift in Sources */, F8B3A844227F090E004BA588 /* SettingsViewAlertSettingsViewModel.swift in Sources */, F8A54AD822D911BA00934E7A /* CGMG5Transmitter.swift in Sources */, + F8A1586322EDB86E007F5B5D /* ConstantsSounds.swift in Sources */, + F8A1587322EDC893007F5B5D /* ConstantsDexcomShare.swift in Sources */, + F8A1586F22EDC7EE007F5B5D /* ConstantsSuspensionPrevention.swift in Sources */, F8A54AB822D9111900934E7A /* TransmitterBatteryInfo.swift in Sources */, F8B3A82D227F07D6004BA588 /* SettingsNavigationController.swift in Sources */, F8A54AB722D9111900934E7A /* CGMTransmitter.swift in Sources */, F8B3A830227F085A004BA588 /* SettingsTableViewCell.swift in Sources */, + F8A1586122EDB844007F5B5D /* ConstantsNotifications.swift in Sources */, F8B3A81C227DEC92004BA588 /* AlertEntriesAccessor.swift in Sources */, + F8A1585B22EDB7EA007F5B5D /* ConstantsDexcomG5.swift in Sources */, F8BDD452221DEAB2006EAB84 /* TextsSettingsView.swift in Sources */, F897AAFB2201018800CDDD10 /* String.swift in Sources */, F8B3A847227F090E004BA588 /* SettingsViewNightScoutSettingsViewModel.swift in Sources */, F8B3A79622635A25004BA588 /* AlertEntry+CoreDataClass.swift in Sources */, F8AC425E21ADEBD60078C348 /* AppDelegate.swift in Sources */, F821CF8E22AB090C005C1E43 /* DatePickerViewController.swift in Sources */, + F8A1585322EDB602007F5B5D /* ConstantsBloodGlucose.swift in Sources */, F8A54AE322D911BA00934E7A /* AuthRequestRxMessage.swift in Sources */, F8A54AD722D911BA00934E7A /* CGMG6Transmitter.swift in Sources */, F81F9FF822861E6D0028C70F /* KeyValueObserverTimeKeeper.swift in Sources */, F8B3A858227F6971004BA588 /* UISwitch.swift in Sources */, + F8A1586722EDB8BF007F5B5D /* ConstantsHomeView.swift in Sources */, + F8A1585922EDB7C6007F5B5D /* ConstantsDefaultAlertLevels.swift in Sources */, F8A54AAD22D6859200934E7A /* SlopeParameters.swift in Sources */, F8B3A783225D37F2004BA588 /* TextsNightScoutTestResult.swift in Sources */, F8025C0A21D94FD700ECF0C0 /* CBManagerState.swift in Sources */, F8B3A80A227A3D11004BA588 /* TextsAlertTypeSettings.swift in Sources */, + F8A1586D22EDB9BE007F5B5D /* ConstantsDexcomFollower.swift in Sources */, F8B3A850227F26F8004BA588 /* AlertTypesSettingsViewController.swift in Sources */, F8A7407022DBB24800967CFC /* BluconTransmitterOpCode.swift in Sources */, F8EA6CAD21BC2CA40082976B /* BluetoothTransmitter.swift in Sources */, @@ -1345,6 +1413,7 @@ F81F9FFC2288C7530028C70F /* NewAlertSettingsViewController.swift in Sources */, F81FA0002289E4990028C70F /* AlertSettingsViewControllerData.swift in Sources */, F8A7407222DCDA3E00967CFC /* BluconUtilities.swift in Sources */, + F856CE5B22EDC8E50083E436 /* ConstantsBluetoothPairing.swift in Sources */, F8B48A9422B2A705009BCC01 /* TextsSpeakReading.swift in Sources */, F8A54AE222D911BA00934E7A /* ResetMessage.swift in Sources */, F821CF5F229BF43A005C1E43 /* ApplicationManager.swift in Sources */, @@ -1367,16 +1436,19 @@ F8025C1121DA5E8F00ECF0C0 /* BluetoothTransmitterDelegate.swift in Sources */, F8A54AD922D911BA00934E7A /* TransmitterVersionTxMessage.swift in Sources */, F821CF5E229BF43A005C1E43 /* BgReading+NightScout.swift in Sources */, - F8EA6C7F21B70E390082976B /* Constants.swift in Sources */, F8A54ADE22D911BA00934E7A /* AESCrypt.m in Sources */, F8A54ADC22D911BA00934E7A /* AuthChallengeTxMessage.swift in Sources */, F8025C1321DA683400ECF0C0 /* Data.swift in Sources */, F85DC2EF21CFE2F500B9F74A /* Sensor+CoreDataProperties.swift in Sources */, F8A54AFA22D9156600934E7A /* CGMMiaoMiaoTransmitter.swift in Sources */, + F8A1584D22ECA445007F5B5D /* SettingsViewDevelopmentSettingsViewModel.swift in Sources */, F8A54AB922D9111900934E7A /* CGMTransmitterDelegate.swift in Sources */, F8B3A856227F28DC004BA588 /* AlertTypeSettingsViewController.swift in Sources */, F8A54AE822D911BA00934E7A /* BatteryStatusRxMessage.swift in Sources */, + F8A1584F22ECB281007F5B5D /* SettingsViewInfoViewModel.swift in Sources */, F8B3A845227F090E004BA588 /* SettingsViewDexcomSettingsViewModel.swift in Sources */, + F8A1585F22EDB81E007F5B5D /* ConstantsLog.swift in Sources */, + F8A1586522EDB89D007F5B5D /* ConstantsDefaultAlertTypeSettings.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1730,7 +1802,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "xdrip/xdrip-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; name = Debug; @@ -1753,7 +1825,7 @@ PRODUCT_BUNDLE_IDENTIFIER = net.johandegraeve.iosxdripreader; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "xdrip/xdrip-Bridging-Header.h"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; name = Release; diff --git a/xdrip/Calibration/Protocol/Calibrator.swift b/xdrip/Calibration/Protocol/Calibrator.swift index a8f670c4..d0f415f4 100644 --- a/xdrip/Calibration/Protocol/Calibrator.swift +++ b/xdrip/Calibration/Protocol/Calibrator.swift @@ -495,10 +495,10 @@ extension Calibrator { /// - bgReading : reading that needs to be updated - inout parameter to improve performance private func updateCalculatedValue(for bgReading:BgReading) { if (bgReading.calculatedValue < 10) { - bgReading.calculatedValue = Constants.CalibrationAlgorithms.bgReadingErrorValue + bgReading.calculatedValue = ConstantsCalibrationAlgorithms.bgReadingErrorValue bgReading.hideSlope = true } else { - bgReading.calculatedValue = min(Constants.CalibrationAlgorithms.maximumBgReadingCalculatedValue, max(Constants.CalibrationAlgorithms.minimumBgReadingCalculatedValue, bgReading.calculatedValue)) + bgReading.calculatedValue = min(ConstantsCalibrationAlgorithms.maximumBgReadingCalculatedValue, max(ConstantsCalibrationAlgorithms.minimumBgReadingCalculatedValue, bgReading.calculatedValue)) } } @@ -517,11 +517,11 @@ extension Calibrator { /// - withSensor : the currently active sensor, optional private func calculateAgeAdjustedRawValue(for bgReading:BgReading, withSensor sensor:Sensor?) { if let sensor = sensor { - let adjustfor:Double = Constants.CalibrationAlgorithms.ageAdjustmentTime - (bgReading.timeStamp.toMillisecondsAsDouble() - sensor.startDate.toMillisecondsAsDouble()) + let adjustfor:Double = ConstantsCalibrationAlgorithms.ageAdjustmentTime - (bgReading.timeStamp.toMillisecondsAsDouble() - sensor.startDate.toMillisecondsAsDouble()) if (adjustfor <= 0 || !ageAdjustMentNeeded) { bgReading.ageAdjustedRawValue = bgReading.rawData } else { - bgReading.ageAdjustedRawValue = ((Constants.CalibrationAlgorithms.ageAdjustmentFactor * (adjustfor / Constants.CalibrationAlgorithms.ageAdjustmentTime)) * bgReading.rawData) + bgReading.rawData + bgReading.ageAdjustedRawValue = ((ConstantsCalibrationAlgorithms.ageAdjustmentFactor * (adjustfor / ConstantsCalibrationAlgorithms.ageAdjustmentTime)) * bgReading.rawData) + bgReading.rawData } } else { bgReading.ageAdjustedRawValue = bgReading.rawData diff --git a/xdrip/Constants/Constants.swift b/xdrip/Constants/Constants.swift deleted file mode 100644 index 8fc1daaf..00000000 --- a/xdrip/Constants/Constants.swift +++ /dev/null @@ -1,392 +0,0 @@ -//Application level constants -import Foundation - -struct Constants { - - enum BGGraphBuilder { - static let maxSlopeInMinutes = 21 - static let defaultLowMarkInMgdl = 70.0 - static let defaultHighMmarkInMgdl = 170.0 - } - - enum BloodGlucose { - static let mmollToMgdl = 18.01801801801802 - static let mgDlToMmoll = 0.0555 - static let libreMultiplier = 117.64705 - } - - /// constants used in calibration algorithm - enum CalibrationAlgorithms { - // age adjustment constants, only for non Libre - static let ageAdjustmentTime = 86400000 * 1.9 - static let ageAdjustmentFactor = 0.45 - - // minimum and maxium values for a reading - static let minimumBgReadingCalculatedValue = 39.0 - static let maximumBgReadingCalculatedValue = 400.0 - static let bgReadingErrorValue = 38.0 - } - - /// coredata specific constants - enum CoreData { - static let modelName = "xdrip" - } - - /// default alert levels to be used when creating defalt alert entries - enum DefaultAlertLevels { - // default battery alert level, below this level an alert should be generated - this default value will be used when changing transmittertype - static let defaultBatteryAlertLevelDexcomG5 = 300 - static let defaultBatteryAlertLevelDexcomG4 = 210 - static let defaultBatteryAlertLevelMiaoMiao = 20 - static let defaultBatteryAlertLevelGNSEntry = 20 - static let defaultBatteryAlertLevelBlucon = 20 - - // blood glucose level alert values in mgdl - static let veryHigh = 250 - static let veryLow = 50 - static let high = 170 - static let low = 70 - - // in minutes, after how many minutes of now reading should alert be raised - static let missedReading = 30 - - // in hours, after how many hours alert to request a new calibration - static let calibration = 24 - } - - /// dexcom G5 specific 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 - enum Log { - /// for use in OSLog - static let subSystem = "xDrip" - /// for use in OSLog - static let categoryBlueTooth = "bluetooth" - /// for use in cgm transmitter miaomiao - static let categoryCGMMiaoMiao = "cgmmiaomiao" - /// for use in cgm xdripg4 - static let categoryCGMxDripG4 = "cgmxdripg4" - /// for use in firstview - static let categoryFirstView = "firstview" - /// calibration - static let calibration = "Calibration" - /// debuglogging - static let debuglogging = "xdripdebuglogging" - /// G5 - static let categoryCGMG5 = "categoryCGMG5" - /// GNSEntry - static let categoryCGMGNSEntry = "categoryCGMGNSEntry" - /// Blucon - static let categoryBlucon = "categoryBlucon" - /// core data manager - static let categoryCoreDataManager = "categoryCoreDataManager" - /// application data bgreadings - static let categoryApplicationDataBgReadings = "categoryApplicationDataBgReadings" - /// application data calibrations - static let categoryApplicationDataCalibrations = "categoryApplicationDataCalibrations" - /// application data sensors - static let categoryApplicationDataSensors = "categoryApplicationDataSensors" - /// application data alerttypes - static let categoryApplicationDataAlertTypes = "categoryApplicationDataAlertTypes" - /// application data alertentries - static let categoryApplicationDataAlertEntries = "categoryApplicationDataAlertEntries" - /// nightscout uploader - static let categoryNightScoutUploadManager = "categoryNightScoutUploadManager" - /// nightscout follow - static let categoryNightScoutFollowManager = "categoryNightScoutFollowManager" - /// alertmanager - static let categoryAlertManager = "categoryAlertManager" - /// playsound - static let categoryPlaySound = "categoryPlaySound" - /// healthkit manager - static let categoryHealthKitManager = "categoryHealthKitManager" - /// SettingsViewHealthKitSettingsViewModel - static let categorySettingsViewHealthKitSettingsViewModel = "categorySettingsViewHealthKitSettingsViewModel" - /// dexcom share upload manager - static let categoryDexcomShareUploadManager = "categoryDexcomShareUploadManager" - } - - enum Notifications { - - /// identifiers for alert categories - enum CategoryIdentifiersForAlerts { - /// for initial calibration - static let initialCalibrationRequest = "InititalCalibrationRequest" - /// subsequent calibration request - static let subsequentCalibrationRequest = "SubsequentCalibrationRequest" - /// high alert - static let highAlert = "highAlert" - /// low alert - static let lowAlert = "lowAlert" - /// very high alert - static let veryHighAlert = "veryHighAlert" - /// very low alert - static let veryLowAlert = "veryLowAlert" - /// missed reading alert - static let missedReadingAlert = "missedReadingAlert" - /// battery low - static let batteryLow = "batteryLow" - } - - /// identifiers for alert notifications - enum NotificationIdentifiersForAlerts { - /// high alert - static let highAlert = "highAlert" - /// low alert - static let lowAlert = "lowAlert" - /// very high alert - static let veryHighAlert = "veryHighAlert" - /// very low alert - static let veryLowAlert = "veryLowAlert" - /// missed reading alert - static let missedReadingAlert = "missedReadingAlert" - /// battery low - static let batteryLow = "batteryLow" - } - - /// identifiers for calibration requests - enum NotificationIdentifiersForCalibration { - /// for initial calibration - static let initialCalibrationRequest = "initialCalibrationRequest" - /// subsequent calibration request - static let subsequentCalibrationRequest = "subsequentCalibrationRequest" - } - - enum NotificationIdentifierForBgReading { - /// bgreading notification - static let bgReadingNotificationRequest = "bgReadingNotificationRequest" - } - - enum NotificationIdentifierForSensorNotDetected { - /// sensor not detected notification - static let sensorNotDetected = "sensorNotDetected" - } - - enum NotificationIdentifierForTransmitterNeedsPairing { - /// 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 - enum Sounds: String, CaseIterable { - - // here using case iso properties because we want to iterate through them - /// name of the sound as shown to the user, and also stored in the alerttype - case batterwakeup = "Better Wake Up/betterwakeup.mp3" - case bruteforce = "Brute Force/bruteforce.mp3" - case modernalarm2 = "Modern Alert 2/modern2.mp3" - case modernalarm = "Modern Alert/modernalarm.mp3" - case shorthigh1 = "Short High 1/shorthigh1.mp3" - case shorthigh2 = "Short High 2/shorthigh2.mp3" - case shorthigh3 = "Short High 3/shorthigh3.mp3" - case shorthigh4 = "Short High 4/shorthigh4.mp3" - case shortlow1 = "Short Low 1/shortlow1.mp3" - case shortlow2 = "Short Low 2/shortlow2.mp3" - case shortlow3 = "Short Low 3/shortlow3.mp3" - case shortlow4 = "Short Low 4/shortlow4.mp3" - case spaceship = "Space Ship/spaceship.mp3" - case xdripalert = "xDrip Alert/xdripalert.aif" - - /// gets all sound names in array, ie part of the case before the / - static func allSoundsBySoundNameAndFileName() -> (soundNames:[String], fileNames:[String]) { - var soundNames = [String]() - var soundFileNames = [String]() - - soundloop: for sound in Constants.Sounds.allCases { - - // Constants.Sounds defines available sounds. Per case there a string which is the soundname as shown in the UI and the filename of the sound in the Resources folder, seperated by backslash - // get array of indexes, of location of "/" - let indexOfBackSlash = sound.rawValue.indexes(of: "/") - - // define range to get the soundname (as shown in UI) - let soundNameRange = sound.rawValue.startIndex.. String { - let indexOfBackSlash = forSound.rawValue.indexes(of: "/") - let soundNameRange = forSound.rawValue.startIndex.. String { - let indexOfBackSlash = forSound.rawValue.indexes(of: "/") - let soundNameRange = forSound.rawValue.index(after: indexOfBackSlash[0]).. String { - - if let forLanguageCode = forLanguageCode { - for (index, languageCode) in allLanguageNamesAndCodes.codes.enumerated() { - if languageCode == forLanguageCode { - return allLanguageNamesAndCodes.names[index] - } - } - } - return Texts_SpeakReading.defaultLanguageCode - } - - } - - /// constants for Dexcom Share - enum DexcomShare { - - /// applicationId to use in Dexcom Share protocol - static let applicationId = "d8665ade-9673-4e27-9ff6-92db4ce13d13" - - /// us share base url - static let usBaseShareUrl = "https://share2.dexcom.com/ShareWebServices/Services" - - /// non us share base url - static let nonUsBaseShareUrl = "https://shareous1.dexcom.com/ShareWebServices/Services" - - } - - /// constants related to Bluetooth pairing - enum BluetoothPairing { - - /// minimum time in seconds between two pairing notifications - static let minimumTimeBetweenTwoPairingNotificationsInSeconds = 30 - - } -} diff --git a/xdrip/Constants/ConstantsBGGraphBuilder.swift b/xdrip/Constants/ConstantsBGGraphBuilder.swift new file mode 100644 index 00000000..d9134cfb --- /dev/null +++ b/xdrip/Constants/ConstantsBGGraphBuilder.swift @@ -0,0 +1,5 @@ +enum ConstantsBGGraphBuilder { + static let maxSlopeInMinutes = 21 + static let defaultLowMarkInMgdl = 70.0 + static let defaultHighMmarkInMgdl = 170.0 +} diff --git a/xdrip/Constants/ConstantsBloodGlucose.swift b/xdrip/Constants/ConstantsBloodGlucose.swift new file mode 100644 index 00000000..6edc8bb4 --- /dev/null +++ b/xdrip/Constants/ConstantsBloodGlucose.swift @@ -0,0 +1,7 @@ +enum ConstantsBloodGlucose { + static let mmollToMgdl = 18.01801801801802 + static let mgDlToMmoll = 0.0555 + static let libreMultiplier = 117.64705 +} + + diff --git a/xdrip/Constants/ConstantsBluetoothPairing.swift b/xdrip/Constants/ConstantsBluetoothPairing.swift new file mode 100644 index 00000000..6093c70a --- /dev/null +++ b/xdrip/Constants/ConstantsBluetoothPairing.swift @@ -0,0 +1,7 @@ +/// constants related to Bluetooth pairing +enum ConstantsBluetoothPairing { + + /// minimum time in seconds between two pairing notifications + static let minimumTimeBetweenTwoPairingNotificationsInSeconds = 30 + +} diff --git a/xdrip/Constants/ConstantsCalibrationAlgorithms.swift b/xdrip/Constants/ConstantsCalibrationAlgorithms.swift new file mode 100644 index 00000000..20813651 --- /dev/null +++ b/xdrip/Constants/ConstantsCalibrationAlgorithms.swift @@ -0,0 +1,11 @@ +/// constants used in calibration algorithm +enum ConstantsCalibrationAlgorithms { + // age adjustment constants, only for non Libre + static let ageAdjustmentTime = 86400000 * 1.9 + static let ageAdjustmentFactor = 0.45 + + // minimum and maxium values for a reading + static let minimumBgReadingCalculatedValue = 39.0 + static let maximumBgReadingCalculatedValue = 400.0 + static let bgReadingErrorValue = 38.0 +} diff --git a/xdrip/Constants/ConstantsCoreData.swift b/xdrip/Constants/ConstantsCoreData.swift new file mode 100644 index 00000000..30358716 --- /dev/null +++ b/xdrip/Constants/ConstantsCoreData.swift @@ -0,0 +1,4 @@ +/// coredata specific constants +enum ConstantsCoreData { + static let modelName = "xdrip" +} diff --git a/xdrip/Constants/ConstantsDefaultAlertLevels.swift b/xdrip/Constants/ConstantsDefaultAlertLevels.swift new file mode 100644 index 00000000..ecdc7bbe --- /dev/null +++ b/xdrip/Constants/ConstantsDefaultAlertLevels.swift @@ -0,0 +1,21 @@ +/// default alert levels to be used when creating defalt alert entries +enum ConstantsDefaultAlertLevels { + // default battery alert level, below this level an alert should be generated - this default value will be used when changing transmittertype + static let defaultBatteryAlertLevelDexcomG5 = 300 + static let defaultBatteryAlertLevelDexcomG4 = 210 + static let defaultBatteryAlertLevelMiaoMiao = 20 + static let defaultBatteryAlertLevelGNSEntry = 20 + static let defaultBatteryAlertLevelBlucon = 20 + + // blood glucose level alert values in mgdl + static let veryHigh = 250 + static let veryLow = 50 + static let high = 170 + static let low = 70 + + // in minutes, after how many minutes of now reading should alert be raised + static let missedReading = 30 + + // in hours, after how many hours alert to request a new calibration + static let calibration = 24 +} diff --git a/xdrip/Constants/ConstantsDefaultAlertTypeSettings.swift b/xdrip/Constants/ConstantsDefaultAlertTypeSettings.swift new file mode 100644 index 00000000..ff9b657a --- /dev/null +++ b/xdrip/Constants/ConstantsDefaultAlertTypeSettings.swift @@ -0,0 +1,12 @@ +/// default values to be used when creating a new AlertType +enum ConstantsDefaultAlertTypeSettings { + + static let enabled = true + static let name = Texts_Common.default0 + static let overrideMute = false + static let snooze = true + static let snoozePeriod = Int16(60) + static let vibrate = true + static let soundName:String? = nil +} + diff --git a/xdrip/Constants/ConstantsDexcomFollower.swift b/xdrip/Constants/ConstantsDexcomFollower.swift new file mode 100644 index 00000000..bbdd92de --- /dev/null +++ b/xdrip/Constants/ConstantsDexcomFollower.swift @@ -0,0 +1,9 @@ +/// constants for follower mode +enum ConstantsFollower { + + /// maximum days of readings to download + static let maxiumDaysOfReadingsToDownload = 1 + + /// maximum age in seconds, of reading in alert flow. If age of latest reading is more than this number, then no alert check will be done + static let maximumBgReadingAgeForAlertsInSeconds = 240.0 +} diff --git a/xdrip/Constants/ConstantsDexcomG5.swift b/xdrip/Constants/ConstantsDexcomG5.swift new file mode 100644 index 00000000..f12c0329 --- /dev/null +++ b/xdrip/Constants/ConstantsDexcomG5.swift @@ -0,0 +1,8 @@ +/// dexcom G5 specific constants +enum ConstantsDexcomG5 { + /// 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 +} diff --git a/xdrip/Constants/ConstantsDexcomShare.swift b/xdrip/Constants/ConstantsDexcomShare.swift new file mode 100644 index 00000000..7c043e77 --- /dev/null +++ b/xdrip/Constants/ConstantsDexcomShare.swift @@ -0,0 +1,15 @@ +/// constants for Dexcom Share +enum ConstantsDexcomShare { + + /// applicationId to use in Dexcom Share protocol + static let applicationId = "d8665ade-9673-4e27-9ff6-92db4ce13d13" + + /// us share base url + static let usBaseShareUrl = "https://share2.dexcom.com/ShareWebServices/Services" + + /// non us share base url + static let nonUsBaseShareUrl = "https://shareous1.dexcom.com/ShareWebServices/Services" + +} + + diff --git a/xdrip/Constants/ConstantsHomeView.swift b/xdrip/Constants/ConstantsHomeView.swift new file mode 100644 index 00000000..e880ea6a --- /dev/null +++ b/xdrip/Constants/ConstantsHomeView.swift @@ -0,0 +1,12 @@ +/// constants for home view, ie first view +enum ConstantsHomeView { + + /// how often to update the labels in the homeview (ie label with latest reading, minutes ago, etc..) + static let updateHomeViewIntervalInSeconds = 15.0 + + /// info email adres, appears in licenseInfo + static let infoEmailAddress = "xdrip@proximus.be" + + /// application name, appears in licenseInfo as title + static let applicationName = "xDrip" +} diff --git a/xdrip/Constants/ConstantsLog.swift b/xdrip/Constants/ConstantsLog.swift new file mode 100644 index 00000000..9601fb91 --- /dev/null +++ b/xdrip/Constants/ConstantsLog.swift @@ -0,0 +1,50 @@ +/// for use in OSLog +enum ConstantsLog { + /// for use in OSLog + static let subSystem = "xDrip" + /// for use in OSLog + static let categoryBlueTooth = "bluetooth" + /// for use in cgm transmitter miaomiao + static let categoryCGMMiaoMiao = "cgmmiaomiao" + /// for use in cgm xdripg4 + static let categoryCGMxDripG4 = "cgmxdripg4" + /// for use in firstview + static let categoryFirstView = "firstview" + /// calibration + static let calibration = "Calibration" + /// debuglogging + static let debuglogging = "xdripdebuglogging" + /// G5 + static let categoryCGMG5 = "categoryCGMG5" + /// GNSEntry + static let categoryCGMGNSEntry = "categoryCGMGNSEntry" + /// Blucon + static let categoryBlucon = "categoryBlucon" + /// core data manager + static let categoryCoreDataManager = "categoryCoreDataManager" + /// application data bgreadings + static let categoryApplicationDataBgReadings = "categoryApplicationDataBgReadings" + /// application data calibrations + static let categoryApplicationDataCalibrations = "categoryApplicationDataCalibrations" + /// application data sensors + static let categoryApplicationDataSensors = "categoryApplicationDataSensors" + /// application data alerttypes + static let categoryApplicationDataAlertTypes = "categoryApplicationDataAlertTypes" + /// application data alertentries + static let categoryApplicationDataAlertEntries = "categoryApplicationDataAlertEntries" + /// nightscout uploader + static let categoryNightScoutUploadManager = "categoryNightScoutUploadManager" + /// nightscout follow + static let categoryNightScoutFollowManager = "categoryNightScoutFollowManager" + /// alertmanager + static let categoryAlertManager = "categoryAlertManager" + /// playsound + static let categoryPlaySound = "categoryPlaySound" + /// healthkit manager + static let categoryHealthKitManager = "categoryHealthKitManager" + /// SettingsViewHealthKitSettingsViewModel + static let categorySettingsViewHealthKitSettingsViewModel = "categorySettingsViewHealthKitSettingsViewModel" + /// dexcom share upload manager + static let categoryDexcomShareUploadManager = "categoryDexcomShareUploadManager" +} + diff --git a/xdrip/Constants/ConstantsMaster.swift b/xdrip/Constants/ConstantsMaster.swift new file mode 100644 index 00000000..909fee14 --- /dev/null +++ b/xdrip/Constants/ConstantsMaster.swift @@ -0,0 +1,6 @@ +/// constants typically for master mode +enum ConstantsMaster { + + /// maximum age in seconds, of reading in alert flow. If age of latest reading is more than this number, then no alert check will be done + static let maximumBgReadingAgeForAlertsInSeconds = 60.0 +} diff --git a/xdrip/Constants/ConstantsNotifications.swift b/xdrip/Constants/ConstantsNotifications.swift new file mode 100644 index 00000000..81015538 --- /dev/null +++ b/xdrip/Constants/ConstantsNotifications.swift @@ -0,0 +1,66 @@ +enum ConstantsNotifications { + + /// identifiers for alert categories + enum CategoryIdentifiersForAlerts { + /// for initial calibration + static let initialCalibrationRequest = "InititalCalibrationRequest" + /// subsequent calibration request + static let subsequentCalibrationRequest = "SubsequentCalibrationRequest" + /// high alert + static let highAlert = "highAlert" + /// low alert + static let lowAlert = "lowAlert" + /// very high alert + static let veryHighAlert = "veryHighAlert" + /// very low alert + static let veryLowAlert = "veryLowAlert" + /// missed reading alert + static let missedReadingAlert = "missedReadingAlert" + /// battery low + static let batteryLow = "batteryLow" + } + + /// identifiers for alert notifications + enum NotificationIdentifiersForAlerts { + /// high alert + static let highAlert = "highAlert" + /// low alert + static let lowAlert = "lowAlert" + /// very high alert + static let veryHighAlert = "veryHighAlert" + /// very low alert + static let veryLowAlert = "veryLowAlert" + /// missed reading alert + static let missedReadingAlert = "missedReadingAlert" + /// battery low + static let batteryLow = "batteryLow" + } + + /// identifiers for calibration requests + enum NotificationIdentifiersForCalibration { + /// for initial calibration + static let initialCalibrationRequest = "initialCalibrationRequest" + /// subsequent calibration request + static let subsequentCalibrationRequest = "subsequentCalibrationRequest" + } + + enum NotificationIdentifierForBgReading { + /// bgreading notification + static let bgReadingNotificationRequest = "bgReadingNotificationRequest" + } + + enum NotificationIdentifierForSensorNotDetected { + /// sensor not detected notification + static let sensorNotDetected = "sensorNotDetected" + } + + enum NotificationIdentifierForTransmitterNeedsPairing { + /// transmitter needs pairing + static let transmitterNeedsPairing = "transmitterNeedsPairing" + } + + enum NotificationIdentifierForResetResult { + /// transmitter reset result + static let transmitterResetResult = "transmitterResetResult" + } +} diff --git a/xdrip/Constants/ConstantsSounds.swift b/xdrip/Constants/ConstantsSounds.swift new file mode 100644 index 00000000..75603d88 --- /dev/null +++ b/xdrip/Constants/ConstantsSounds.swift @@ -0,0 +1,67 @@ +/// 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 +enum ConstantsSounds: String, CaseIterable { + + // here using case iso properties because we want to iterate through them + /// name of the sound as shown to the user, and also stored in the alerttype + case batterwakeup = "Better Wake Up/betterwakeup.mp3" + case bruteforce = "Brute Force/bruteforce.mp3" + case modernalarm2 = "Modern Alert 2/modern2.mp3" + case modernalarm = "Modern Alert/modernalarm.mp3" + case shorthigh1 = "Short High 1/shorthigh1.mp3" + case shorthigh2 = "Short High 2/shorthigh2.mp3" + case shorthigh3 = "Short High 3/shorthigh3.mp3" + case shorthigh4 = "Short High 4/shorthigh4.mp3" + case shortlow1 = "Short Low 1/shortlow1.mp3" + case shortlow2 = "Short Low 2/shortlow2.mp3" + case shortlow3 = "Short Low 3/shortlow3.mp3" + case shortlow4 = "Short Low 4/shortlow4.mp3" + case spaceship = "Space Ship/spaceship.mp3" + case xdripalert = "xDrip Alert/xdripalert.aif" + + /// gets all sound names in array, ie part of the case before the / + static func allSoundsBySoundNameAndFileName() -> (soundNames:[String], fileNames:[String]) { + var soundNames = [String]() + var soundFileNames = [String]() + + soundloop: for sound in ConstantsSounds.allCases { + + // ConstantsSounds defines available sounds. Per case there a string which is the soundname as shown in the UI and the filename of the sound in the Resources folder, seperated by backslash + // get array of indexes, of location of "/" + let indexOfBackSlash = sound.rawValue.indexes(of: "/") + + // define range to get the soundname (as shown in UI) + let soundNameRange = sound.rawValue.startIndex.. String { + let indexOfBackSlash = forSound.rawValue.indexes(of: "/") + let soundNameRange = forSound.rawValue.startIndex.. String { + let indexOfBackSlash = forSound.rawValue.indexes(of: "/") + let soundNameRange = forSound.rawValue.index(after: indexOfBackSlash[0]).. String { + + if let forLanguageCode = forLanguageCode { + for (index, languageCode) in allLanguageNamesAndCodes.codes.enumerated() { + if languageCode == forLanguageCode { + return allLanguageNamesAndCodes.names[index] + } + } + } + return Texts_SpeakReading.defaultLanguageCode + } + +} diff --git a/xdrip/Constants/ConstantsSuspensionPrevention.swift b/xdrip/Constants/ConstantsSuspensionPrevention.swift new file mode 100644 index 00000000..a4ab1b9f --- /dev/null +++ b/xdrip/Constants/ConstantsSuspensionPrevention.swift @@ -0,0 +1,11 @@ +/// suspension prevention +enum ConstantsSuspensionPrevention { + + /// name of the file that has the sound to play + static let soundFileName = "1-millisecond-of-silence.mp3"//20ms-of-silence.caf" + + /// how often to play the sound, in seconds + static let interval = 5 +} + + diff --git a/xdrip/Core Data/accessors/AlertEntriesAccessor.swift b/xdrip/Core Data/accessors/AlertEntriesAccessor.swift index 5d13a9ad..a1f9ca79 100644 --- a/xdrip/Core Data/accessors/AlertEntriesAccessor.swift +++ b/xdrip/Core Data/accessors/AlertEntriesAccessor.swift @@ -7,7 +7,7 @@ class AlertEntriesAccessor { // MARK: - Properties /// for logging - private var log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryApplicationDataAlertEntries) + private var log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryApplicationDataAlertEntries) /// CoreDataManager to use private let coreDataManager:CoreDataManager diff --git a/xdrip/Core Data/accessors/AlertTypesAccessor.swift b/xdrip/Core Data/accessors/AlertTypesAccessor.swift index 7c3ff9bd..ccd08279 100644 --- a/xdrip/Core Data/accessors/AlertTypesAccessor.swift +++ b/xdrip/Core Data/accessors/AlertTypesAccessor.swift @@ -7,7 +7,7 @@ class AlertTypesAccessor { // MARK: - Properties /// for logging - private var log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryApplicationDataAlertTypes) + private var log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryApplicationDataAlertTypes) /// CoreDataManager to use private let coreDataManager:CoreDataManager @@ -48,7 +48,7 @@ class AlertTypesAccessor { if let alertType = alertTypes.first { return alertType } else { - let defaultAlertType = AlertType(enabled: Constants.DefaultAlertTypeSettings.enabled, name: Texts_Common.default0, overrideMute: Constants.DefaultAlertTypeSettings.overrideMute, snooze: Constants.DefaultAlertTypeSettings.snooze, snoozePeriod: Int(Constants.DefaultAlertTypeSettings.snoozePeriod), vibrate: Constants.DefaultAlertTypeSettings.vibrate, soundName: Constants.Sounds.getSoundName(forSound: .xdripalert), alertEntries: nil, nsManagedObjectContext: coreDataManager.mainManagedObjectContext) + let defaultAlertType = AlertType(enabled: ConstantsDefaultAlertTypeSettings.enabled, name: Texts_Common.default0, overrideMute: ConstantsDefaultAlertTypeSettings.overrideMute, snooze: ConstantsDefaultAlertTypeSettings.snooze, snoozePeriod: Int(ConstantsDefaultAlertTypeSettings.snoozePeriod), vibrate: ConstantsDefaultAlertTypeSettings.vibrate, soundName: ConstantsSounds.getSoundName(forSound: .xdripalert), alertEntries: nil, nsManagedObjectContext: coreDataManager.mainManagedObjectContext) coreDataManager.saveChanges() return defaultAlertType } diff --git a/xdrip/Core Data/accessors/BgReadingsAccessor.swift b/xdrip/Core Data/accessors/BgReadingsAccessor.swift index 7379c9dc..50f30952 100644 --- a/xdrip/Core Data/accessors/BgReadingsAccessor.swift +++ b/xdrip/Core Data/accessors/BgReadingsAccessor.swift @@ -7,7 +7,7 @@ class BgReadingsAccessor { // MARK: - Properties /// for logging - private var log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryApplicationDataBgReadings) + private var log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryApplicationDataBgReadings) /// CoreDataManager to use private let coreDataManager:CoreDataManager diff --git a/xdrip/Core Data/accessors/CalibrationsAccessor.swift b/xdrip/Core Data/accessors/CalibrationsAccessor.swift index a3331731..4c87a288 100644 --- a/xdrip/Core Data/accessors/CalibrationsAccessor.swift +++ b/xdrip/Core Data/accessors/CalibrationsAccessor.swift @@ -7,7 +7,7 @@ class CalibrationsAccessor { // MARK: - Properties /// for logging - private var log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryApplicationDataCalibrations) + private var log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryApplicationDataCalibrations) /// CoreDataManager to use private let coreDataManager:CoreDataManager diff --git a/xdrip/Core Data/accessors/SensorsAccessor.swift b/xdrip/Core Data/accessors/SensorsAccessor.swift index 0bfb2e13..1b6fa3ab 100644 --- a/xdrip/Core Data/accessors/SensorsAccessor.swift +++ b/xdrip/Core Data/accessors/SensorsAccessor.swift @@ -7,7 +7,7 @@ class SensorsAccessor { // MARK: - Properties /// for logging - private var log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryApplicationDataSensors) + private var log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryApplicationDataSensors) /// CoreDataManager to use private let coreDataManager:CoreDataManager diff --git a/xdrip/Core Data/classes/AlertType+CoreDataClass.swift b/xdrip/Core Data/classes/AlertType+CoreDataClass.swift index 9c2a5931..7fd1da37 100644 --- a/xdrip/Core Data/classes/AlertType+CoreDataClass.swift +++ b/xdrip/Core Data/classes/AlertType+CoreDataClass.swift @@ -10,7 +10,7 @@ import CoreData /// - overrideMute : should alert make sound if the alert is muted /// - snooze : can the alert be snoozed from the home screen notification /// - vibrate : should the phone vibrate when the alert fires -/// - soundname : the name of the sound as shown in the UI. Constants.Sounds defines available sounds. Per case there a string which is the soundname as shown in the UI and the filename of the sound in the Resources folder, seperated by backslash. Some special cases : an empty string means no sound needed. A nil value means default iOS sound. Any other value should be in the list defined in Constants.Sounds, otherwise the default xDrip sound will be used (see AlertManager.swift) +/// - soundname : the name of the sound as shown in the UI. ConstantsSounds defines available sounds. Per case there a string which is the soundname as shown in the UI and the filename of the sound in the Resources folder, seperated by backslash. Some special cases : an empty string means no sound needed. A nil value means default iOS sound. Any other value should be in the list defined in ConstantsSounds, otherwise the default xDrip sound will be used (see AlertManager.swift) /// - alertEntries : the alertEntries in which this AlertType is used, optional public class AlertType: NSManagedObject { init( diff --git a/xdrip/Core Data/classes/BgReading+CoreDataClass.swift b/xdrip/Core Data/classes/BgReading+CoreDataClass.swift index 62190b15..3f55e821 100644 --- a/xdrip/Core Data/classes/BgReading+CoreDataClass.swift +++ b/xdrip/Core Data/classes/BgReading+CoreDataClass.swift @@ -164,7 +164,7 @@ public class BgReading: NSManagedObject { return "???" } - if timeStamp.timeIntervalSince(previousBgReading.timeStamp) > Double(Constants.BGGraphBuilder.maxSlopeInMinutes * 60) { + if timeStamp.timeIntervalSince(previousBgReading.timeStamp) > Double(ConstantsBGGraphBuilder.maxSlopeInMinutes * 60) { // don't show delta if there are not enough values or the values are more than 20 mintes apart return "???"; } @@ -208,7 +208,7 @@ public class BgReading: NSManagedObject { func calculateSlope(lastBgReading:BgReading) -> (Double, Bool) { if timeStamp == lastBgReading.timeStamp || - timeStamp.toMillisecondsAsDouble() - lastBgReading.timeStamp.toMillisecondsAsDouble() > Double(Constants.BGGraphBuilder.maxSlopeInMinutes * 60 * 1000) { + timeStamp.toMillisecondsAsDouble() - lastBgReading.timeStamp.toMillisecondsAsDouble() > Double(ConstantsBGGraphBuilder.maxSlopeInMinutes * 60 * 1000) { return (0,true) } return ((lastBgReading.calculatedValue - calculatedValue) / (lastBgReading.timeStamp.toMillisecondsAsDouble() - timeStamp.toMillisecondsAsDouble()), false) diff --git a/xdrip/Extensions/CBManagerState.swift b/xdrip/Extensions/CBManagerState.swift index 5d0b2ac0..929e1c85 100644 --- a/xdrip/Extensions/CBManagerState.swift +++ b/xdrip/Extensions/CBManagerState.swift @@ -16,6 +16,8 @@ extension CBManagerState { return "unknown" case .unsupported: return "unsupported" + @unknown default: + return "unknown state" } } } diff --git a/xdrip/Extensions/Double.swift b/xdrip/Extensions/Double.swift index e9a6d01e..bfc4bd54 100644 --- a/xdrip/Extensions/Double.swift +++ b/xdrip/Extensions/Double.swift @@ -3,7 +3,7 @@ import Foundation extension Double { /// converts mgdl to mmol func mgdlToMmol() -> Double { - return self * Constants.BloodGlucose.mgDlToMmoll + return self * ConstantsBloodGlucose.mgDlToMmoll } /// converts mgdl to mmol if parameter mgdl = false. If mgdl = true then just returns self @@ -11,7 +11,7 @@ extension Double { if mgdl { return self } else { - return self * Constants.BloodGlucose.mgDlToMmoll + return self * ConstantsBloodGlucose.mgDlToMmoll } } @@ -26,7 +26,7 @@ extension Double { /// converts mmol to mgdl func mmolToMgdl() -> Double { - return self * Constants.BloodGlucose.mmollToMgdl + return self * ConstantsBloodGlucose.mmollToMgdl } /// returns the value rounded to fractionDigits diff --git a/xdrip/Extensions/UserDefaults.swift b/xdrip/Extensions/UserDefaults.swift index 793be34a..bf423d04 100644 --- a/xdrip/Extensions/UserDefaults.swift +++ b/xdrip/Extensions/UserDefaults.swift @@ -100,9 +100,21 @@ extension UserDefaults { /// timestamp of last bgreading that was stored in healthkit case timeStampLatestHealthKitStoreBgReading = "timeStampLatestHealthKitStoreBgReading" + // Dexcom Share /// timestamp of latest reading uploaded to Dexcom Share case timeStampLatestDexcomShareUploadedBgReading = "timeStampLatestDexcomShareUploadedBgReading" + // Sensor + /// sensor Serial Number, for now only applicable to Libre + case sensorSerialNumber = "sensorSerialNumber" + + // development settings to test G6 scaling + /// G6 factor1 - for testing G6 scaling + case G6v2ScalingFactor1 = "G6v2ScalingFactor1" + + /// G6 factor2 - for testing G6 scaling + case G6v2ScalingFactor2 = "G6v2ScalingFactor2" + } // MARK: - ===== User Configurable Settings ====== @@ -127,7 +139,7 @@ extension UserDefaults { var returnValue = double(forKey: Key.lowMarkValue.rawValue) // if 0 set to defaultvalue if returnValue == 0.0 { - returnValue = Constants.BGGraphBuilder.defaultLowMarkInMgdl + returnValue = ConstantsBGGraphBuilder.defaultLowMarkInMgdl } if !bloodGlucoseUnitIsMgDl { returnValue = returnValue.mgdlToMmol() @@ -147,7 +159,7 @@ extension UserDefaults { var returnValue = double(forKey: Key.highMarkValue.rawValue) // if 0 set to defaultvalue if returnValue == 0.0 { - returnValue = Constants.BGGraphBuilder.defaultHighMmarkInMgdl + returnValue = ConstantsBGGraphBuilder.defaultHighMmarkInMgdl } if !bloodGlucoseUnitIsMgDl { returnValue = returnValue.mgdlToMmol() @@ -527,6 +539,38 @@ extension UserDefaults { } } + /// sensor serial number, for now only useful for Libre sensor + var sensorSerialNumber:String? { + get { + return string(forKey: Key.sensorSerialNumber.rawValue) + } + set { + set(newValue, forKey: Key.sensorSerialNumber.rawValue) + } + } + + // MARK: - ===== technical settings for testing ====== + + /// G6 factor 1 + @objc dynamic var G6v2ScalingFactor1:String? { + get { + return string(forKey: Key.G6v2ScalingFactor1.rawValue) + } + set { + set(newValue, forKey: Key.G6v2ScalingFactor1.rawValue) + } + } + + /// G6 factor 2 + @objc dynamic var G6v2ScalingFactor2:String? { + get { + return string(forKey: Key.G6v2ScalingFactor2.rawValue) + } + set { + set(newValue, forKey: Key.G6v2ScalingFactor2.rawValue) + } + } + } diff --git a/xdrip/Managers/Alerts/AlertKind.swift b/xdrip/Managers/Alerts/AlertKind.swift index a71fa869..4f961baf 100644 --- a/xdrip/Managers/Alerts/AlertKind.swift +++ b/xdrip/Managers/Alerts/AlertKind.swift @@ -42,22 +42,22 @@ public enum AlertKind:Int, CaseIterable { switch self { case .low: - return Constants.DefaultAlertLevels.low + return ConstantsDefaultAlertLevels.low case .high: - return Constants.DefaultAlertLevels.high + return ConstantsDefaultAlertLevels.high case .verylow: - return Constants.DefaultAlertLevels.veryLow + return ConstantsDefaultAlertLevels.veryLow case .veryhigh: - return Constants.DefaultAlertLevels.veryHigh + return ConstantsDefaultAlertLevels.veryHigh case .missedreading: - return Constants.DefaultAlertLevels.missedReading + return ConstantsDefaultAlertLevels.missedReading case .calibration: - return Constants.DefaultAlertLevels.calibration + return ConstantsDefaultAlertLevels.calibration case .batterylow: if let transmitterType = UserDefaults.standard.transmitterType { return transmitterType.defaultBatteryAlertLevel() } else { - return Constants.DefaultAlertLevels.defaultBatteryAlertLevelMiaoMiao + return ConstantsDefaultAlertLevels.defaultBatteryAlertLevelMiaoMiao } } } @@ -253,19 +253,19 @@ public enum AlertKind:Int, CaseIterable { switch self { case .low: - return Constants.Notifications.NotificationIdentifiersForAlerts.lowAlert + return ConstantsNotifications.NotificationIdentifiersForAlerts.lowAlert case .high: - return Constants.Notifications.NotificationIdentifiersForAlerts.highAlert + return ConstantsNotifications.NotificationIdentifiersForAlerts.highAlert case .verylow: - return Constants.Notifications.NotificationIdentifiersForAlerts.veryLowAlert + return ConstantsNotifications.NotificationIdentifiersForAlerts.veryLowAlert case .veryhigh: - return Constants.Notifications.NotificationIdentifiersForAlerts.veryHighAlert + return ConstantsNotifications.NotificationIdentifiersForAlerts.veryHighAlert case .missedreading: - return Constants.Notifications.NotificationIdentifiersForAlerts.missedReadingAlert + return ConstantsNotifications.NotificationIdentifiersForAlerts.missedReadingAlert case .calibration: - return Constants.Notifications.NotificationIdentifiersForCalibration.subsequentCalibrationRequest + return ConstantsNotifications.NotificationIdentifiersForCalibration.subsequentCalibrationRequest case .batterylow: - return Constants.Notifications.NotificationIdentifiersForAlerts.batteryLow + return ConstantsNotifications.NotificationIdentifiersForAlerts.batteryLow } } diff --git a/xdrip/Managers/Alerts/AlertManager.swift b/xdrip/Managers/Alerts/AlertManager.swift index 8f4f75e7..ddffa70c 100644 --- a/xdrip/Managers/Alerts/AlertManager.swift +++ b/xdrip/Managers/Alerts/AlertManager.swift @@ -18,7 +18,7 @@ public class AlertManager:NSObject { private let snoozeCategoryIdentifier = "snoozeCategoryIdentifier" /// for logging - private var log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryAlertManager) + private var log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryAlertManager) /// BgReadings instance private let bgReadingsAccessor:BgReadingsAccessor @@ -345,11 +345,11 @@ public class AlertManager:NSObject { soundToSet = "" } else { // a sound name has been found in the alertType different from empty string (ie a sound must be played and it's not the default iOS sound) - // need to find the corresponding sound file name in Constants.Sounds + // need to find the corresponding sound file name in ConstantsSounds // start by setting it to to xdripalert, because the soundname found in the alert type might not be found in the list of sounds stored in the resources (although that shouldn't happen) soundToSet = "xdripalert.aif" - soundloop: for sound in Constants.Sounds.allCases { - // Constants.Sounds defines available sounds. Per case there a string which is the soundname as shown in the UI and the filename of the sound in the Resources folder, seperated by backslash + soundloop: for sound in ConstantsSounds.allCases { + // ConstantsSounds defines available sounds. Per case there a string which is the soundname as shown in the UI and the filename of the sound in the Resources folder, seperated by backslash // get array of indexes, of location of "/" let indexOfBackSlash = sound.rawValue.indexes(of: "/") // define range to get the soundname (as shown in UI) diff --git a/xdrip/Managers/CoreData/CoreDataManager.swift b/xdrip/Managers/CoreData/CoreDataManager.swift index 5a15188f..38b8dab6 100644 --- a/xdrip/Managers/CoreData/CoreDataManager.swift +++ b/xdrip/Managers/CoreData/CoreDataManager.swift @@ -13,7 +13,7 @@ final class CoreDataManager { private let modelName: String - private var log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryCoreDataManager) + private var log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryCoreDataManager) /// constant for key in ApplicationManager.shared.addClosureToRunWhenAppWillTerminate private let applicationManagerKeySaveChanges = "coredatamanagersavechanges" diff --git a/xdrip/Managers/DexcomShare/DexcomShareUploadManager.swift b/xdrip/Managers/DexcomShare/DexcomShareUploadManager.swift index c9ce7dd3..d6913e0e 100644 --- a/xdrip/Managers/DexcomShare/DexcomShareUploadManager.swift +++ b/xdrip/Managers/DexcomShare/DexcomShareUploadManager.swift @@ -24,14 +24,14 @@ class DexcomShareUploadManager:NSObject { private let messageHandler:((String, String) -> Void)? /// for logging - private var log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryDexcomShareUploadManager) + private var log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryDexcomShareUploadManager) /// dexcom share url to use, calculated property private var dexcomShareUrl:String { if UserDefaults.standard.useUSDexcomShareurl { - return Constants.DexcomShare.usBaseShareUrl + return ConstantsDexcomShare.usBaseShareUrl } else { - return Constants.DexcomShare.nonUsBaseShareUrl + return ConstantsDexcomShare.nonUsBaseShareUrl } } @@ -521,7 +521,7 @@ class DexcomShareUploadManager:NSObject { let uploadData = try JSONSerialization.data(withJSONObject: [ "accountName": dexcomShareAccountName, "password": dexcomSharePassword, - "applicationId": Constants.DexcomShare.applicationId + "applicationId": ConstantsDexcomShare.applicationId ], options: []) // get shared URLSession diff --git a/xdrip/Managers/HealthKit/HealthKitManager.swift b/xdrip/Managers/HealthKit/HealthKitManager.swift index c66c6347..9240fea5 100644 --- a/xdrip/Managers/HealthKit/HealthKitManager.swift +++ b/xdrip/Managers/HealthKit/HealthKitManager.swift @@ -11,7 +11,7 @@ public class HealthKitManager:NSObject { private let keyValueObserverTimeKeeper:KeyValueObserverTimeKeeper = KeyValueObserverTimeKeeper() /// for logging - private var log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryHealthKitManager) + private var log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryHealthKitManager) /// reference to coredatamanager private var coreDataManager:CoreDataManager @@ -86,6 +86,8 @@ public class HealthKitManager:NSObject { return false case .sharingAuthorized: break + @unknown default: + os_log("unknown authorizationstatus for healthkit - HealthKitManager.swift", log: self.log, type: .error) } // all checks ok , return true diff --git a/xdrip/Managers/NightScout/NightScoutFollowManager.swift b/xdrip/Managers/NightScout/NightScoutFollowManager.swift index 7947b69b..770f774c 100644 --- a/xdrip/Managers/NightScout/NightScoutFollowManager.swift +++ b/xdrip/Managers/NightScout/NightScoutFollowManager.swift @@ -14,7 +14,7 @@ class NightScoutFollowManager:NSObject { private let keyValueObserverTimeKeeper:KeyValueObserverTimeKeeper = KeyValueObserverTimeKeeper() /// for logging - private var log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryNightScoutFollowManager) + private var log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryNightScoutFollowManager) /// when to do next download private var nextFollowDownloadTimeStamp:Date @@ -59,7 +59,7 @@ class NightScoutFollowManager:NSObject { // creat audioplayer do { // set up url to create audioplayer - let soundFileName = Constants.SuspensionPrevention.soundFileName + let soundFileName = ConstantsSuspensionPrevention.soundFileName if let url = Bundle.main.url(forResource: soundFileName, withExtension: "") { try audioPlayer = AVAudioPlayer(contentsOf: url) @@ -144,7 +144,7 @@ class NightScoutFollowManager:NSObject { guard let nightScoutUrl = UserDefaults.standard.nightScoutUrl else {return} // maximum timeStamp to download initially set to 1 day back - var timeStampOfFirstBgReadingToDowload = Date(timeIntervalSinceNow: TimeInterval(-Constants.Follower.maxiumDaysOfReadingsToDownload * 24 * 3600)) + var timeStampOfFirstBgReadingToDowload = Date(timeIntervalSinceNow: TimeInterval(-ConstantsFollower.maxiumDaysOfReadingsToDownload * 24 * 3600)) // check timestamp of lastest stored bgreading with calculated value, if more recent then use this as timeStampOfFirstBgReadingToDowload let latestBgReadings = bgReadingsAccessor.getLatestBgReadings(limit: nil, howOld: 1, forSensor: nil, ignoreRawData: true, ignoreCalculatedValue: false) @@ -325,7 +325,7 @@ class NightScoutFollowManager:NSObject { private func enableSuspensionPrevention() { // create playSoundTimer - playSoundTimer = RepeatingTimer(timeInterval: TimeInterval(Constants.SuspensionPrevention.interval), eventHandler: { + playSoundTimer = RepeatingTimer(timeInterval: TimeInterval(ConstantsSuspensionPrevention.interval), eventHandler: { // play the sound if let audioPlayer = self.audioPlayer, !audioPlayer.isPlaying { audioPlayer.play() diff --git a/xdrip/Managers/NightScout/NightScoutUploadManager.swift b/xdrip/Managers/NightScout/NightScoutUploadManager.swift index 94c18518..b0a29f49 100644 --- a/xdrip/Managers/NightScout/NightScoutUploadManager.swift +++ b/xdrip/Managers/NightScout/NightScoutUploadManager.swift @@ -19,7 +19,7 @@ public class NightScoutUploadManager:NSObject { private let nightScoutAuthTestPath = "/api/v1/experiments/test" /// for logging - private var log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryNightScoutUploadManager) + private var log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryNightScoutUploadManager) /// BgReadingsAccessor instance private let bgReadingsAccessor:BgReadingsAccessor diff --git a/xdrip/Storyboards/en.lproj/SettingsViews.strings b/xdrip/Storyboards/en.lproj/SettingsViews.strings index 782dfc5c..b8de8984 100644 --- a/xdrip/Storyboards/en.lproj/SettingsViews.strings +++ b/xdrip/Storyboards/en.lproj/SettingsViews.strings @@ -36,3 +36,5 @@ "settingsviews_speakreadingslanguageselection" = "Select Language"; "settingsviews_speakBgReadingslanguage" = "Language"; "settingsviews_resettransmitter" = "Reset Transmitter"; +"settingsviews_Version" = "Version"; +"settingsviews_license" = "License"; diff --git a/xdrip/Supporting Files/Info.plist b/xdrip/Supporting Files/Info.plist index 1948976b..7380c1e4 100644 --- a/xdrip/Supporting Files/Info.plist +++ b/xdrip/Supporting Files/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.3.1 + 2.4.2 CFBundleVersion - 2.3.1 + 2.4.2 LSRequiresIPhoneOS NSBluetoothPeripheralUsageDescription diff --git a/xdrip/Texts/TextsCommon.swift b/xdrip/Texts/TextsCommon.swift index 72622380..b05aface 100644 --- a/xdrip/Texts/TextsCommon.swift +++ b/xdrip/Texts/TextsCommon.swift @@ -88,4 +88,5 @@ class Texts_Common { static let no = { return NSLocalizedString("no", tableName: filename, bundle: Bundle.main, value: "no", comment: "literally no, without capital") }() + } diff --git a/xdrip/Texts/TextsSettingsView.swift b/xdrip/Texts/TextsSettingsView.swift index a2e91ed2..53d858f4 100644 --- a/xdrip/Texts/TextsSettingsView.swift +++ b/xdrip/Texts/TextsSettingsView.swift @@ -187,4 +187,14 @@ class Texts_SettingsView { return NSLocalizedString("settingsviews_speakRateMessage", tableName: filename, bundle: Bundle.main, value: "Value between 0 and 1", comment: "When clicking the rate setting, a pop up asks for the rate, this is the message displayed in the pop up") }() + // MARK: - Section Info + + static let version = { + return NSLocalizedString("settingsviews_Version", tableName: filename, bundle: Bundle.main, value: "Version", comment: "used in settings, section Info, title of the version setting") + }() + + static let license = { + return NSLocalizedString("settingsviews_license", tableName: filename, bundle: Bundle.main, value: "License", comment: "used in settings, section Info, title of the license setting") + }() + } diff --git a/xdrip/Texts/TextsSpeakReading.swift b/xdrip/Texts/TextsSpeakReading.swift index 8a38a9c2..fb46a03d 100644 --- a/xdrip/Texts/TextsSpeakReading.swift +++ b/xdrip/Texts/TextsSpeakReading.swift @@ -13,13 +13,13 @@ enum Texts_SpeakReading { /// the language for speak reading texts, default en /// - /// Must be a valid language code, example "en-EN" or "en-US" but also "en" is allowed - should be a language code that exists in Constants.SpeakReadingLanguages - and the corresponding strings file must exist. Example there's only "en" for the moment, not en-GB or en-US + /// Must be a valid language code, example "en-EN" or "en-US" but also "en" is allowed - should be a language code that exists in ConstantsSpeakReadingLanguages - and the corresponding strings file must exist. Example there's only "en" for the moment, not en-GB or en-US /// /// if there's no folder languageCode.lproj (example fr.lproj if languageCode would be assigned to "fr") then the default language will be used ie en private(set) static var languageCode = defaultLanguageCode - /// name of currently selected language, should be matching value currently stored in user defaults - it can be used for performance reasons, to avoid that when needed the whole enum in Constants.SpeakReadingLanguages needs to be iterated through each time again - private(set) static var languageName = Constants.SpeakReadingLanguages.languageName(forLanguageCode: languageCode) + /// name of currently selected language, should be matching value currently stored in user defaults - it can be used for performance reasons, to avoid that when needed the whole enum in ConstantsSpeakReadingLanguages needs to be iterated through each time again + private(set) static var languageName = ConstantsSpeakReadingLanguages.languageName(forLanguageCode: languageCode) /// bundle to use, will be reassigned if user changes language for speak reading texts private static var bundle = Bundle(path: Bundle.main.path(forResource: defaultLanguageCode, ofType: "lproj")!) @@ -33,7 +33,7 @@ enum Texts_SpeakReading { /// set the language for speak reading texts, default en /// - /// Must be a valid language code, example "en-EN" or "en-US" but also "en" is allowed - should be a language code that exists in Constants.SpeakReadingLanguages - and the corresponding strings file must exist. Example there's only "en" for the moment + /// Must be a valid language code, example "en-EN" or "en-US" but also "en" is allowed - should be a language code that exists in ConstantsSpeakReadingLanguages - and the corresponding strings file must exist. Example there's only "en" for the moment /// /// if there's no folder languageCode.lproj (example fr.lproj if languageCode would be assigned to "fr") then the default language will be used ie en public static func setLanguageCode(code:String?) { @@ -47,7 +47,7 @@ public static func setLanguageCode(code:String?) { bundle = Bundle(path: path) } else { // full languageCode doesn't work, try now to split by - and use the first part only - // should never be in this branch if Constants.SpeakReadingLanguages is aligned with actual .lproj folders + // should never be in this branch if ConstantsSpeakReadingLanguages is aligned with actual .lproj folders if languageCode.contains(find: "-") { let indexOfHyphen = languageCode.indexes(of: "-") let languageRange = languageCode.startIndex..= 12 && data.isCRCValid else { + guard data.count >= 10 && data.isCRCValid else { return nil } @@ -22,8 +22,12 @@ struct BatteryStatusRxMessage: TransmitterRxMessage { voltageA = Int(data.uint16(position: 2)) voltageB = Int(data.uint16(position: 4)) resist = Int(data.uint16(position: 6)) - runtime = Int(data.uint16(position: 8)) - temperature = Int(data.uint8(position: 10)) + if data.count == 10 {// see https://github.com/NightscoutFoundation/xDrip/commit/b1fb0835a765a89ccc1bb8b216b0d6b2d21d66bb#diff-564e59f90a64b2928799ea4e30d81920 + runtime = -1 + } else { + runtime = Int(data[8]) + } + temperature = Int(data.uint8(position: 9)) } } diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Dexcom/G5/G5Messages/DexcomTransmitterOpCode.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Dexcom/G5/G5Messages/DexcomTransmitterOpCode.swift index cfb17ab0..65c6cbfe 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Dexcom/G5/G5Messages/DexcomTransmitterOpCode.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Dexcom/G5/G5Messages/DexcomTransmitterOpCode.swift @@ -63,7 +63,7 @@ enum DexcomTransmitterOpCode: UInt8 { extension Data { init(for opcode: DexcomTransmitterOpCode) { - self.init(bytes: [opcode.rawValue]) + self.init([opcode.rawValue]) } func starts(with opcode: DexcomTransmitterOpCode) -> Bool { diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Dexcom/G6/CGMG6Transmitter.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Dexcom/G6/CGMG6Transmitter.swift index aca08c26..45f48f75 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Dexcom/G6/CGMG6Transmitter.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Dexcom/G6/CGMG6Transmitter.swift @@ -2,6 +2,14 @@ import Foundation class CGMG6Transmitter: CGMG5Transmitter { + /// scaling factor for G6 firmware version 1 + private let G6v1ScalingFactor = 34.0 + + /// scaling factor 1 for G6 firmware version 2 + static let G6v2DefaultScalingFactor1 = 1151500000.0 + + static let G6v2DefaultScalingFactor2 = 110000.0 + /// - parameters: /// - address: if already connected before, then give here the address that was received during previous connect, if not give nil /// - transmitterID: expected transmitterID, 6 characters @@ -10,7 +18,38 @@ class CGMG6Transmitter: CGMG5Transmitter { // call super.init super.init(address: address, transmitterID: transmitterID, delegate: delegate) - scalingFactor = 34.0 + } + + override func scaleRawValue(firmwareVersion: String?, rawValue: Double) -> Double { + + if let firmwareVersion = firmwareVersion { + if firmwareVersion.startsWith("1") { + + // G6-v1 + return rawValue * G6v1ScalingFactor; + + } else { + + var scalingFactor1 = CGMG6Transmitter.G6v2DefaultScalingFactor1 + if let factor = UserDefaults.standard.G6v2ScalingFactor1, let factorAsDouble = factor.toDouble() { + scalingFactor1 = factorAsDouble + } + + var scalingFactor2 = CGMG6Transmitter.G6v2DefaultScalingFactor2 + if let factor = UserDefaults.standard.G6v2ScalingFactor2, let factorAsDouble = factor.toDouble() { + scalingFactor2 = factorAsDouble + } + + // G6-v2 + return (rawValue - scalingFactor1) / scalingFactor2 + } + } else { + + // assumed G6-v1, although firmwareVersion will normally not be nil + return rawValue * G6v1ScalingFactor; + + } + } } diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/CGMTransmitter.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/CGMTransmitter.swift index 45e0d18a..8fc343b4 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/CGMTransmitter.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/CGMTransmitter.swift @@ -62,7 +62,7 @@ enum CGMTransmitterType:String, CaseIterable { case GNSentry = "GNSentry" /// Blucon - case Blucon = "Blucon NOT READY !" + case Blucon = "Blucon" /// does the transmitter need a transmitter id ? /// @@ -90,6 +90,8 @@ enum CGMTransmitterType:String, CaseIterable { /// if true, then a class conforming to the protocol CGMTransmitterDelegate will call newSensorDetected if it detects a new sensor is placed. Means there's no need to let the user start and stop a sensor /// /// example MiaoMiao can detect new sensor, implementation should return true, Dexcom transmitter's can't + /// + /// if true, then transmitterType must also be able to give the sensor age, ie sensorTimeInMinutes func canDetectNewSensor() -> Bool { switch self { @@ -107,7 +109,7 @@ enum CGMTransmitterType:String, CaseIterable { return false case .Blucon: - return false + return true } } @@ -151,19 +153,19 @@ enum CGMTransmitterType:String, CaseIterable { switch self { case .dexcomG4: - return Constants.DefaultAlertLevels.defaultBatteryAlertLevelDexcomG4 + return ConstantsDefaultAlertLevels.defaultBatteryAlertLevelDexcomG4 case .dexcomG5, .dexcomG6: - return Constants.DefaultAlertLevels.defaultBatteryAlertLevelDexcomG5 + return ConstantsDefaultAlertLevels.defaultBatteryAlertLevelDexcomG5 case .miaomiao: - return Constants.DefaultAlertLevels.defaultBatteryAlertLevelMiaoMiao + return ConstantsDefaultAlertLevels.defaultBatteryAlertLevelMiaoMiao case .GNSentry: - return Constants.DefaultAlertLevels.defaultBatteryAlertLevelGNSEntry + return ConstantsDefaultAlertLevels.defaultBatteryAlertLevelGNSEntry case .Blucon: - return Constants.DefaultAlertLevels.defaultBatteryAlertLevelBlucon + return ConstantsDefaultAlertLevels.defaultBatteryAlertLevelBlucon } } diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/CGMTransmitterDelegate.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/CGMTransmitterDelegate.swift index 5c812c3f..05f94f7e 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/CGMTransmitterDelegate.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/CGMTransmitterDelegate.swift @@ -29,9 +29,10 @@ protocol CGMTransmitterDelegate:AnyObject { /// - sensorTimeInMinutes : sensor age in minutes, only if transmitter can give that info, eg MiaoMiao, otherwise nil /// - firmware : only if transmitter can give that info, eg G5, otherwise nil /// - hardware : only if transmitter can give that info, eg G5, otherwise nil - /// - serialNumber : only if transmitter can give that info, eg G5, otherwise nil + /// - serialNumber : transmitter serial number, only if transmitter can give that info, eg G5, otherwise nil /// - bootloader : for the moment only used by GNSentry, otherwise nil - func cgmTransmitterInfoReceived(glucoseData:inout [RawGlucoseData], transmitterBatteryInfo:TransmitterBatteryInfo?, sensorState:LibreSensorState?, sensorTimeInMinutes:Int?, firmware:String?, hardware:String?, serialNumber:String?, bootloader:String?) + /// - sensorSerialNumber : serial number of the sensor, only applicable for Libre transmitters (MiaoMiao, Blucon, ...) + func cgmTransmitterInfoReceived(glucoseData:inout [RawGlucoseData], transmitterBatteryInfo:TransmitterBatteryInfo?, sensorState:LibreSensorState?, sensorTimeInMinutes:Int?, firmware:String?, hardware:String?, hardwareSerialNumber:String?, bootloader:String?, sensorSerialNumber:String?) /// transmitter needs bluetooth pairing func cgmTransmitterNeedsPairing() diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Blucon/BluconTransmitterOpCode.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Blucon/BluconTransmitterOpCode.swift index 49644658..0381ee72 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Blucon/BluconTransmitterOpCode.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Blucon/BluconTransmitterOpCode.swift @@ -2,7 +2,9 @@ import Foundation enum BluconTransmitterOpCode: String, CaseIterable { - case wakeUp = "cb010000" + case wakeUpRequest = "cb010000" + + case wakeUpResponse = "810a00" case getPatchInfoRequest = "010d0900" @@ -12,6 +14,32 @@ enum BluconTransmitterOpCode: String, CaseIterable { case sensorNotDetected = "8b1a02000f" + case sleep = "010c0e00" + + case bluconAckResponse = "8b0a00" + + case unknown1Command = "010d0b00" + + case unknown1CommandResponse = "8bdb" + + case unknown2Command = "010d0a00" + + case unknown2CommandResponse = "8bda" + + case getHistoricDataAllBlocksCommand = "010d0f02002b" + + case multipleBlockResponseIndex = "8bdf" + + case getNowDataIndex = "010d0e0103" + + case singleBlockInfoResponsePrefix = "8bde" + + case singleBlockInfoPrefix = "010d0e010" + + case bluconBatteryLowIndication1 = "cb020000" + + case bluconBatteryLowIndication2 = "cbdb0000" + /// iterates through all cases, and as soon as one is found that starts with valueReceived, then initializes with that case. If none found then returns nil public init?(withOpCodeValue: String) { for opCode in BluconTransmitterOpCode.allCases { @@ -24,14 +52,16 @@ enum BluconTransmitterOpCode: String, CaseIterable { return nil } - public var description:String { switch self { - case .wakeUp: + case .wakeUpRequest: return "wakeUp" + case .wakeUpResponse: + return "wakeUpResponse" + case .getPatchInfoRequest: return "getPatchInfo" @@ -43,6 +73,46 @@ enum BluconTransmitterOpCode: String, CaseIterable { case .getPatchInfoResponse: return "getPatchInfoResponse" + + case .sleep: + return "sleep" + + case .bluconAckResponse: + return "bluconAckResponse" + + case .unknown1Command: + return "unknown1Command" + + case .unknown1CommandResponse: + return "unknown1CommandResponse" + + case .unknown2Command: + return "unknown2Command" + + case .unknown2CommandResponse: + return "unknown2CommandResponse" + + case .getHistoricDataAllBlocksCommand: + return "getHistoricDataAllBlocksCommand" + + case .multipleBlockResponseIndex: + return "multipleBlockResponseIndex" + + case .getNowDataIndex: + return "getNowDataIndex" + + case .singleBlockInfoResponsePrefix: + return "singleBlockInfoResponsePrefixResponse" + + case .singleBlockInfoPrefix: + return "singleBlockInfoPrefix" + + case .bluconBatteryLowIndication1: + return "bluconBatteryLowIndication1" + + case .bluconBatteryLowIndication2: + return "bluconBatteryLowIndication2" + } } } diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Blucon/BluconUtilities.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Blucon/BluconUtilities.swift index e07aec44..a48d56a4 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Blucon/BluconUtilities.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Blucon/BluconUtilities.swift @@ -1,39 +1,46 @@ import Foundation +fileprivate let lookupTable = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "T", "U", "V", "W", "X", "Y", "Z"] + /// static functions for Blucon class BluconUtilities { - /*public static func decodeSerialNumber(input: Data) -> String{ - - let uuidShort = Data(bytes: <#T##Sequence#>)//new byte[]{0, 0, 0, 0, 0, 0, 0, 0}; - int i; - - for (i = 2; i < 8; i++) uuidShort[i - 2] = input[(2 + 8) - i]; - uuidShort[6] = 0x00; - uuidShort[7] = 0x00; - - String binary = ""; - String binS = ""; - for (i = 0; i < 8; i++) { - binS = String.format("%8s", Integer.toBinaryString(uuidShort[i] & 0xFF)).replace(' ', '0'); - binary += binS; - } - - String v = "0"; - char[] pozS = {0, 0, 0, 0, 0}; - for (i = 0; i < 10; i++) { - for (int k = 0; k < 5; k++) pozS[k] = binary.charAt((5 * i) + k); - int value = (pozS[0] - '0') * 16 + (pozS[1] - '0') * 8 + (pozS[2] - '0') * 4 + (pozS[3] - '0') * 2 + (pozS[4] - '0') * 1; - v += lookupTable[value]; - } - Log.e(TAG, "decodeSerialNumber=" + v); - - return v; - }*/ + /// - parameters: + /// - input : data received from Blucon + /// - returns: The sensor serial number + /// + /// decodes serial number, copied forp xdripplus , commit 2b25bfdf6a563aea16de63053aec5e0e3be16e5f + public static func decodeSerialNumber(input: Data) -> String { + + var uuidShort = Data([0, 0, 0, 0, 0, 0, 0, 0]) + + for i in 2..<8 { + uuidShort[i - 2] = input[(2 + 8) - i] + } + + uuidShort[6] = 0x00 + uuidShort[7] = 0x00 + + var binary = "" + for i in 0..<8 { + var binS = String(uuidShort[i] & 0xFF, radix: 2) + while binS.count < 8 { + binS = "0" + binS + } + binary = binary + binS + } + var v = "0" + var pozS = [0, 0, 0, 0, 0] + for i in 0..<10 { + for k in 0..<5 { + let index = (5 * i) + k + pozS[k] = binary[index..<(index + 1)] == "0" ? 0:1 + } + let value = pozS[0] * 16 + pozS[1] * 8 + pozS[2] * 4 + pozS[3] * 2 + pozS[4] * 1 + v += lookupTable[value] + } + + return v; + } } - -fileprivate let lookupTable = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", - "A", "C", "D", "E", "F", "G", "H", "J", "K", "L", - "M", "N", "P", "Q", "R", "T", "U", "V", "W", "X", - "Y", "Z"] diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Blucon/CGMBluconTransmitter.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Blucon/CGMBluconTransmitter.swift index 468f54c4..5994aff8 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Blucon/CGMBluconTransmitter.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Blucon/CGMBluconTransmitter.swift @@ -10,16 +10,19 @@ class CGMBluconTransmitter: BluetoothTransmitter { private(set) weak var cgmTransmitterDelegate:CGMTransmitterDelegate? /// Blucon Service - let CBUUID_BluconService = "436A62C0-082E-4CE8-A08B-01D81F195B24" + private let CBUUID_BluconService = "436A62C0-082E-4CE8-A08B-01D81F195B24" /// receive characteristic - let CBUUID_ReceiveCharacteristic_Blucon: String = "436A0C82-082E-4CE8-A08B-01D81F195B24" + private let CBUUID_ReceiveCharacteristic_Blucon: String = "436A0C82-082E-4CE8-A08B-01D81F195B24" /// write characteristic - let CBUUID_WriteCharacteristic_Blucon: String = "436AA6E9-082E-4CE8-A08B-01D81F195B24" + private let CBUUID_WriteCharacteristic_Blucon: String = "436AA6E9-082E-4CE8-A08B-01D81F195B24" + + /// if value starts with this string, then it's assume that a battery low indication is sent by the Blucon + private let unknownCommand2BatteryLowIndicator = "8bda02" /// for OS_log - private let log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryBlucon) + private let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryBlucon) // actual device address private var actualDeviceAddress:String? @@ -30,12 +33,47 @@ class CGMBluconTransmitter: BluetoothTransmitter { // waiting successful pairing yes or not private var waitingSuccessfulPairing:Bool = false + // current sensor serial number, if nil then it's not known yet + private var sensorSerialNumber:String? + + /// used as parameter in call to cgmTransmitterDelegate.cgmTransmitterInfoReceived, when there's no glucosedata to send + private var emptyArray: [RawGlucoseData] = [] + + /// timestamp when wakeUpResponse was sent to Blucon + private var timeStampLastWakeUpResponse:Date? + + /// BluconACKResponse will come in two different situations + /// - after we have sent an ackwakeup command + /// - after we have a sleep command + /// shouldSendUnknown1CommandAfterReceivingWakeUpResponse and timeStampLastWakeUpResponsetime work together to determine the case + private var shouldSendUnknown1CommandAfterReceivingBluconAckResponse = true + + /// used when processing Blucon data packet + private var timestampFirstPacketReception:Date? + + // how long to wait for next packet before considering the session as failed + private let maxWaitForHistoricDataInSeconds = 5.0 + + // receive buffer for Blucon packets + private var rxBuffer:Data + + // used in Blucon protocol + private var nowGlucoseOffset:UInt8 = 0 + + // used in Blucon protocol - corresponds to m_getNowGlucoseDataCommand + private var waitingForGlucoseData = false + + // timestamp of sending singleBlockInfoPrefix command, if time of receiving singleBlockInfoResponsePrefix is too late, then reading will be ignored + private var timeStampOfSendingSingleBlockInfoPrefix:Date? + // MARK: - public functions /// - parameters: /// - address: if already connected before, then give here the address that was received during previous connect, if not give nil /// - transmitterID: expected transmitterID - init?(address:String?, transmitterID:String, delegate:CGMTransmitterDelegate, timeStampLastBgReading:Date) { + /// - delegate : CGMTransmitterDelegate + /// - sensorSerialNumber : is needed to allow detection of a new sensor. + init?(address:String?, transmitterID:String, delegate:CGMTransmitterDelegate, timeStampLastBgReading:Date, sensorSerialNumber:String?) { // assign addressname and name or expected devicename // start by using expected device name @@ -46,8 +84,14 @@ class CGMBluconTransmitter: BluetoothTransmitter { actualDeviceAddress = address } - //initialize timeStampLastBgReading + // initialize timeStampLastBgReading self.timeStampLastBgReading = timeStampLastBgReading + + // initialize sensorSerialNumber + self.sensorSerialNumber = sensorSerialNumber + + // initialize rxbuffer + rxBuffer = Data() // initialize super.init(addressAndName: newAddressAndName, CBUUID_Advertisement: nil, servicesCBUUIDs: [CBUUID(string: CBUUID_BluconService)], CBUUID_ReceiveCharacteristic: CBUUID_ReceiveCharacteristic_Blucon, CBUUID_WriteCharacteristic: CBUUID_WriteCharacteristic_Blucon, startScanningAfterInit: CGMTransmitterType.Blucon.startScanningAfterInit()) @@ -71,14 +115,124 @@ class CGMBluconTransmitter: BluetoothTransmitter { if !returnValue.uppercased().startsWith("BLU") { while returnValue.count < 5 { - returnValue = "0" + returnValue; + returnValue = "0" + returnValue } - returnValue = "BLU" + returnValue; + returnValue = "BLU" + returnValue } return returnValue } + /// writes command to blucon, withResponse, and also logs the command + private func sendCommandToBlucon(opcode:BluconTransmitterOpCode) { + + os_log(" send opcode %{public}@ to Blucon", log: log, type: .info, opcode.description) + _ = writeDataToPeripheral(data: Data(hexadecimalString: opcode.rawValue)!, type: .withResponse) + + } + + /// reset rxBuffer, reset timestampFirstPacketReception, stop packetRxMonitorTimer + private func resetRxBuffer() { + rxBuffer = Data() + timestampFirstPacketReception = Date() + } + + /// process new historic data block received from Blucon, one block is the contents when receiving multipleBlockResponseIndex, inclusive the opcode - this is used if we ask all Libre data from the transmitter, which includes sensorTime and sensorStatus + /// - returns: + /// - did receive all data yes or no, if yes, then blucon can go to sleep + /// also calls delegate with result of new readings + private func handleNewHistoricData(block: Data) -> Bool { + + //check if buffer needs to be reset + if let timestampFirstPacketReception = timestampFirstPacketReception { + if (Date() > timestampFirstPacketReception.addingTimeInterval(maxWaitForHistoricDataInSeconds - 1)) { + os_log("in handleNewHistoricData, more than %{public}d seconds since last update - or first update since app launch, resetting buffer", log: log, type: .info,maxWaitForHistoricDataInSeconds) + resetRxBuffer() + } + } + + //add new packet to buffer, ignoring the opcode (2 bytes), the number of the next block (1 byte), and the number of blocks in the data (1 byte) + rxBuffer.append(block[4..= 344 { + + os_log("in handleNewHistoricData, reached minimum length, processing data", log: log, type: .info) + + // crc check + guard Crc.LibreCrc(data: &rxBuffer, headerOffset: 0) else { + + os_log(" crc check failed, no further processing", log: log, type: .error) + + // transmitter can go to sleep + return true + + } + + //get readings from buffer and send to delegate + var result = parseLibreData(data: &rxBuffer, timeStampLastBgReadingStoredInDatabase: timeStampLastBgReading, headerOffset: 0) + + //TODO: sort glucosedata before calling newReadingsReceived + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &result.glucoseData, transmitterBatteryInfo: nil, sensorState: result.sensorState, sensorTimeInMinutes: result.sensorTimeInMinutes, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil) + + //set timeStampLastBgReading to timestamp of latest reading in the response so that next time we parse only the more recent readings + if result.glucoseData.count > 0 { + timeStampLastBgReading = result.glucoseData[0].timeStamp + } + + //reset the buffer + resetRxBuffer() + + // transmitter can go to sleep + return true + + } + + // transmitter should send more data + return false + } + + private func blockNumberForNowGlucoseData(input:Data) -> String { + + // caculate byte position in sensor body, decrement index to get the index where the last valid BG reading is stored + var nowGlucoseIndex2 = input[5] * 6 + 4 - 6 + + // adjust round robin + if nowGlucoseIndex2 < 4 { + nowGlucoseIndex2 = nowGlucoseIndex2 + 96 + } + + // calculate the absolute block number which correspond to trend index + let nowGlucoseIndex3 = 3 + (nowGlucoseIndex2/8) + + // calculate offset of the 2 bytes in the block + nowGlucoseOffset = nowGlucoseIndex2 % 8 + + + let nowGlucoseDataAsHexString = nowGlucoseIndex3.description + + return nowGlucoseDataAsHexString + } + + private func nowGetGlucoseValue(input:Data) -> Double { + + // example 8BDE07DB010F04C868DB01 + // value 1 = 0F + // value 2 = 04 + //rawGlucose = (input[3 + nowGlucoseOffset + 1] & 0x0F) * 256 + input[3 + nowGlucoseOffset] = 1039 + let value1 = input[3 + Int(nowGlucoseOffset)] + let value2 = input[3 + Int(nowGlucoseOffset) + 1] + + let rawGlucose = Double((UInt16(value2 & 0x0F)<<8) | UInt16(value1 & 0xFF)) + + // rescale for Libre + let curGluc = rawGlucose * ConstantsBloodGlucose.libreMultiplier + + return(curGluc) + } + + + } extension CGMBluconTransmitter: CGMTransmitter { @@ -164,29 +318,220 @@ extension CGMBluconTransmitter: BluetoothTransmitterDelegate { // get Opcode if let opCode = BluconTransmitterOpCode(withOpCodeValue: valueAsString) { - os_log(" opcode = %{public}@", log: log, type: .info, opCode.description) + os_log(" received opcode = %{public}@ from Blucon", log: log, type: .info, opCode.description) switch opCode { - case .wakeUp: - // send getPatchInfo command - _ = writeDataToPeripheral(data: Data(hexadecimalString: BluconTransmitterOpCode.getPatchInfoRequest.rawValue)!, type: .withResponse) - - case .getPatchInfoRequest: - // shouldn't receive this ? - return + case .getPatchInfoRequest, .wakeUpResponse, .sleep, .unknown1Command, .unknown2Command, .getHistoricDataAllBlocksCommand, .getNowDataIndex, .singleBlockInfoPrefix: + // these are commands that app sends to Blucon, shouldn't receive any of them + break + case .wakeUpRequest: + + // start by setting waitingForGlucoseData to false, it might still have value true due to protocol error + waitingForGlucoseData = false + + // send getPatchInfoRequest + sendCommandToBlucon(opcode: BluconTransmitterOpCode.getPatchInfoRequest) + + // by default set battery level to 100 + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: 100), sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil) + + case .error14: + // Blucon didn't receive the next command it was waiting for, need to wait 5 minutes os_log(" Timeout received, need to wait 5 minutes or push button to restart!", log: log, type: .error) - + + // and send Blucon to sleep + sendCommandToBlucon(opcode: .sleep) + case .sensorNotDetected: - // Blucon didn't detected sensor, call delegate + + // Blucon didn't detect sensor, call delegate cgmTransmitterDelegate?.sensorNotDetected() + // and send Blucon to sleep + sendCommandToBlucon(opcode: .sleep) + case .getPatchInfoResponse: + + // get serial number + let newSerialNumber = BluconUtilities.decodeSerialNumber(input: value) + + // verify serial number and if changed inform delegate + if newSerialNumber != sensorSerialNumber { + + os_log(" new sensor detected : %{public}@", log: log, type: .info, newSerialNumber) + + sensorSerialNumber = newSerialNumber + + // inform delegate about new sensor detected + cgmTransmitterDelegate?.newSensorDetected() + + // also reset timestamp last reading, to be sure that if new sensor is started, we get historic data + timeStampLastBgReading = Date(timeIntervalSince1970: 0) + + } + + // read sensorState + let sensorState = LibreSensorState(stateByte: value[17]) + + // if sensor is ready then send Ack, otherwise send sleep + if sensorState == LibreSensorState.ready { + + timeStampLastWakeUpResponse = Date() + shouldSendUnknown1CommandAfterReceivingBluconAckResponse = true + + sendCommandToBlucon(opcode: BluconTransmitterOpCode.wakeUpResponse) + + } else { + + os_log(" sensorState = %{public}@", log: log, type: .info, sensorState.description) + + sendCommandToBlucon(opcode: BluconTransmitterOpCode.sleep) + + } + + // inform delegate about sensorSerialNumber and sensorState + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: nil, sensorState: sensorState, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: sensorSerialNumber) + return + + case .bluconAckResponse: + + // BluconACKResponse will come in two different situations + // 1) after we have sent an ackwakeup command ==> need to send unknown1Command + // 2) after we have sent a sleep command ==> no command to send + // to verify in which case we are, timeStampLastWakeUpResponse is used + // assuming bluconAckResponse will arrive less than 5 seconds after having send wakeUpResponse + if let timeStampLastWakeUpResponse = timeStampLastWakeUpResponse, abs(timeStampLastWakeUpResponse.timeIntervalSinceNow) < 5 && shouldSendUnknown1CommandAfterReceivingBluconAckResponse { + + // set to false to be sure + shouldSendUnknown1CommandAfterReceivingBluconAckResponse = false + + // send unknown1Command + sendCommandToBlucon(opcode: BluconTransmitterOpCode.unknown1Command) + + } else { + + os_log(" no further processing, Blucon is sleeping now and should send a new reading in 5 minutes", log: log, type: .info) + + } + + case .unknown1CommandResponse: + + sendCommandToBlucon(opcode: BluconTransmitterOpCode.unknown2Command) + + case .unknown2CommandResponse: + + // check if there's a battery low indication + if valueAsString.startsWith(unknownCommand2BatteryLowIndicator) { + + // this is considered as battery level 5% + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: 5), sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil) + + } + + // if timeStampLastBgReading > 5 minutes ago, then we'll get historic data, otherwise just get the latest reading + if abs(timeStampLastBgReading.timeIntervalSinceNow) > 5 * 60 + 10 { + + sendCommandToBlucon(opcode: BluconTransmitterOpCode.getHistoricDataAllBlocksCommand) + + } else { + + // not asking for sensorAge as in Spike and xdripplus, we know the sensorAge because we started with getHistoricDataAllBlocksCommand + sendCommandToBlucon(opcode: BluconTransmitterOpCode.getNowDataIndex) + + } + + case .multipleBlockResponseIndex: + + if handleNewHistoricData(block: value) { + + // when Blucon responds with bluconAckResponse, then there's no need to send unknown1Command + shouldSendUnknown1CommandAfterReceivingBluconAckResponse = false + + // send sleep command + sendCommandToBlucon(opcode: .sleep) + + } + + case .singleBlockInfoResponsePrefix: + + if !waitingForGlucoseData { + + // get blockNumber and compose command + let commandToSend = BluconTransmitterOpCode.singleBlockInfoPrefix.rawValue + blockNumberForNowGlucoseData(input: value) + + // convert command to hexstring, might fail if blockNumberForNowGlucoseData returned an invalid value + if let commandToSendAsData = Data(hexadecimalString: commandToSend) { + + os_log(" send %{public}@ to Blucon", log: log, type: .info, commandToSend) + _ = writeDataToPeripheral(data: commandToSendAsData, type: .withResponse) + + waitingForGlucoseData = true + + } else { + + os_log(" failed to convert commandToSend to Data", log: log, type: .error) + } + + } else { + + // reset waitingForGlucoseData to false as we will not wait for glucosedata, after having processed this reading + waitingForGlucoseData = false + + // to be sure that waitingForGlucoseData is not having value true due to having broken protcol, verify when SingleBlockInfoPrefix was sent + if let timeStampOfSendingSingleBlockInfoPrefix = timeStampOfSendingSingleBlockInfoPrefix { + // should be a matter of milliseconds, so take 2 seconds + if abs(timeStampOfSendingSingleBlockInfoPrefix.timeIntervalSinceNow) > 2 { + + os_log(" time since sending SingleBlockInfoPrefix is more than 2 seconds, ignoring this reading", log: log, type: .error) + + // send sleep command + sendCommandToBlucon(opcode: .sleep) + + return + + } + } + + // checking now timestamp of last reading, if less than 30 seconds old, then reading will be ignored, seems a bit late to do that check, but after a few tests it seems to be the best to continue up to here to make sure the Blucon stays in a consistent state. + if abs(timeStampLastBgReading.timeIntervalSinceNow) < 30 { + os_log(" last reading less than 30 seconds old, ignoring this one", log: log, type: .info) + } else { + + os_log(" creating glucoseValue", log: log, type: .info) + + // create glucose reading with timestamp now + timeStampLastBgReading = Date() + + // get glucoseValue from value + let glucoseValue = nowGetGlucoseValue(input: value) + + let glucoseData = RawGlucoseData(timeStamp: timeStampLastBgReading, glucoseLevelRaw: glucoseValue, glucoseLevelFiltered: glucoseValue) + var glucoseDataArray = [glucoseData] + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: nil, sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil) + + } + + sendCommandToBlucon(opcode: .sleep) + + } + + case .bluconBatteryLowIndication1: + + // this is considered as battery level 3% + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: 3), sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil) + + case .bluconBatteryLowIndication2: + + // this is considered as battery level 2% + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: 2), sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil) + } + } } else { diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/GNSEntry/CGMGNSEntryTransmitter.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/GNSEntry/CGMGNSEntryTransmitter.swift index 8c0c5b2a..ed666c0d 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/GNSEntry/CGMGNSEntryTransmitter.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/GNSEntry/CGMGNSEntryTransmitter.swift @@ -67,7 +67,7 @@ class CGMGNSEntryTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, private(set) weak var cgmTransmitterDelegate:CGMTransmitterDelegate? /// for OS_log - private let log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryCGMGNSEntry) + private let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryCGMGNSEntry) /// used in parsing packet private var timeStampLastBgReadingInMinutes:Double @@ -154,22 +154,22 @@ class CGMGNSEntryTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, actualSerialNumber = String(data: value, encoding: String.Encoding.utf8) // TODO : is this the serial number of the sensor ? if yes we can use this to detect new sensor ? as with blucon ? if let actualSerialNumber = actualSerialNumber { - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: nil, sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, serialNumber: actualSerialNumber, bootloader: nil) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: nil, sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, hardwareSerialNumber: actualSerialNumber, bootloader: nil, sensorSerialNumber: nil) } case .CBUUID_Firmware: actualFirmWareVersion = String(data: value, encoding: String.Encoding.utf8) if let actualFirmWareVersion = actualFirmWareVersion { - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: nil, sensorState: nil, sensorTimeInMinutes: nil, firmware: actualFirmWareVersion, hardware: nil, serialNumber: nil, bootloader: nil) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: nil, sensorState: nil, sensorTimeInMinutes: nil, firmware: actualFirmWareVersion, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil) } case .CBUUID_Bootloader: actualBootLoader = String(data: value, encoding: String.Encoding.utf8) if let actualBootLoader = actualBootLoader { - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: nil, sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, serialNumber: nil, bootloader: actualBootLoader) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: nil, sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: actualBootLoader, sensorSerialNumber: nil) } case .CBUUID_BatteryLevel: let dataAsString = value.hexEncodedString() if let batteryLevel = Int(dataAsString, radix: 16) { - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: batteryLevel), sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, serialNumber: nil, bootloader: nil) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: batteryLevel), sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil) } else { os_log(" in peripheralDidUpdateValueFor, could not read batterylevel, received hex value = %{public}@", log: log, type: .error , dataAsString) } @@ -227,7 +227,7 @@ class CGMGNSEntryTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, // sometimes 0 values are received, skip those if readingValueInMgDl > 0 { if readingTimeStampInMinutes * 60 * 1000 < timeStampLastAddedGlucoseDataInMinutes * 60 * 1000 - (5 * 60 * 1000 - 10000) { - let glucoseData = RawGlucoseData(timeStamp: Date(timeIntervalSince1970: Double(readingTimeStampInMinutes) * 60.0), glucoseLevelRaw: Double(readingValueInMgDl) * Constants.BloodGlucose.libreMultiplier) + let glucoseData = RawGlucoseData(timeStamp: Date(timeIntervalSince1970: Double(readingTimeStampInMinutes) * 60.0), glucoseLevelRaw: Double(readingValueInMgDl) * ConstantsBloodGlucose.libreMultiplier) readings.append(glucoseData) timeStampLastAddedGlucoseDataInMinutes = readingTimeStampInMinutes } @@ -241,7 +241,7 @@ class CGMGNSEntryTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, i = i + 1 } - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &readings, transmitterBatteryInfo: nil, sensorState: sensorStatus, sensorTimeInMinutes: Int(sensorElapsedTimeInMinutes), firmware: actualFirmWareVersion, hardware: nil, serialNumber: actualSerialNumber, bootloader: actualBootLoader) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &readings, transmitterBatteryInfo: nil, sensorState: sensorStatus, sensorTimeInMinutes: Int(sensorElapsedTimeInMinutes), firmware: actualFirmWareVersion, hardware: nil, hardwareSerialNumber: actualSerialNumber, bootloader: actualBootLoader, sensorSerialNumber: nil) //set timeStampLastBgReading to timestamp of latest reading in the response so that next time we parse only the more recent readings if readings.count > 0 { diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/MiaoMiao/CGMMiaoMiaoTransmitter.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/MiaoMiao/CGMMiaoMiaoTransmitter.swift index efc54c0a..a6f3632d 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/MiaoMiao/CGMMiaoMiaoTransmitter.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/MiaoMiao/CGMMiaoMiaoTransmitter.swift @@ -22,7 +22,7 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, let maxPacketResendRequests = 3; /// for OS_log - private let log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryCGMMiaoMiao) + private let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryCGMMiaoMiao) // used in parsing packet private var timeStampLastBgReading:Date @@ -31,7 +31,7 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, private var resendPacketCounter:Int = 0 /// used when processing MiaoMiao data packet - private var startDate:Date + private var timestampFirstPacketReception:Date // receive buffer for miaomiao packets private var rxBuffer:Data // how long to wait for next packet before sending startreadingcommand @@ -56,7 +56,7 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, // initialize rxbuffer rxBuffer = Data() - startDate = Date() + timestampFirstPacketReception = Date() //initialize timeStampLastBgReading self.timeStampLastBgReading = timeStampLastBgReading @@ -70,7 +70,7 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, // MARK: - public functions func sendStartReadingCommmand() -> Bool { - if writeDataToPeripheral(data: Data.init(bytes: [0xF0]), type: .withoutResponse) { + if writeDataToPeripheral(data: Data.init([0xF0]), type: .withoutResponse) { return true } else { os_log("in sendStartReadingCommmand, write failed", log: log, type: .error) @@ -106,7 +106,7 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, if let value = characteristic.value { //check if buffer needs to be reset - if (Date() > startDate.addingTimeInterval(CGMMiaoMiaoTransmitter.maxWaitForpacketInSeconds - 1)) { + if (Date() > timestampFirstPacketReception.addingTimeInterval(CGMMiaoMiaoTransmitter.maxWaitForpacketInSeconds - 1)) { os_log("in peripheral didUpdateValueFor, more than %{public}d seconds since last update - or first update since app launch, resetting buffer", log: log, type: .info, CGMMiaoMiaoTransmitter.maxWaitForpacketInSeconds) resetRxBuffer() } @@ -129,11 +129,11 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, let firmware = String(describing: rxBuffer[14...15].hexEncodedString()) let hardware = String(describing: rxBuffer[16...17].hexEncodedString()) let batteryPercentage = Int(rxBuffer[13]) - + //get readings from buffer and send to delegate var result = parseLibreData(data: &rxBuffer, timeStampLastBgReadingStoredInDatabase: timeStampLastBgReading, headerOffset: miaoMiaoHeaderLength) //TODO: sort glucosedata before calling newReadingsReceived - cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &result.glucoseData, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: batteryPercentage), sensorState: result.sensorState, sensorTimeInMinutes: result.sensorTimeInMinutes, firmware: firmware, hardware: hardware, serialNumber: nil, bootloader: nil) + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &result.glucoseData, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: batteryPercentage), sensorState: result.sensorState, sensorTimeInMinutes: result.sensorTimeInMinutes, firmware: firmware, hardware: hardware, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil) //set timeStampLastBgReading to timestamp of latest reading in the response so that next time we parse only the more recent readings if result.glucoseData.count > 0 { @@ -166,7 +166,7 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, // send 0xD3 and 0x01 to confirm sensor change as defined in MiaoMiao protocol documentation // after that send start reading command, each with delay of 500 milliseconds DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .milliseconds(500)) { - if self.writeDataToPeripheral(data: Data.init(bytes: [0xD3, 0x01]), type: .withoutResponse) { + if self.writeDataToPeripheral(data: Data.init([0xD3, 0x01]), type: .withoutResponse) { os_log("in peripheralDidUpdateValueFor, successfully sent 0xD3 and 0x01, confirm sensor change to MiaoMiao", log: self.log, type: .info) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .milliseconds(500)) { if !self.sendStartReadingCommmand() { @@ -212,10 +212,10 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, // MARK: - helpers - /// reset rxBuffer, reset startDate, stop packetRxMonitorTimer, set resendPacketCounter to 0 + /// reset rxBuffer, reset startDate, set resendPacketCounter to 0 private func resetRxBuffer() { rxBuffer = Data() - startDate = Date() + timestampFirstPacketReception = Date() resendPacketCounter = 0 } diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/ParseLibreData.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/ParseLibreData.swift index f10f064f..6703541e 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/ParseLibreData.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/ParseLibreData.swift @@ -42,7 +42,7 @@ func parseLibreData(data:inout Data, timeStampLastBgReadingStoredInDatabase:Date byte.append(data[headerOffset + (i * 6 + 28)]) let glucoseLevelRaw = Double(getGlucoseRaw(bytes: byte)) if (glucoseLevelRaw > 0) { - glucoseData = RawGlucoseData(timeStamp: Date(timeIntervalSince1970: sensorStartTimeInMilliseconds/1000 + timeInMinutes * 60), glucoseLevelRaw: Double(getGlucoseRaw(bytes: byte)) * Constants.BloodGlucose.libreMultiplier) + glucoseData = RawGlucoseData(timeStamp: Date(timeIntervalSince1970: sensorStartTimeInMilliseconds/1000 + timeInMinutes * 60), glucoseLevelRaw: Double(getGlucoseRaw(bytes: byte)) * ConstantsBloodGlucose.libreMultiplier) returnValue.append(glucoseData) timeStampLastAddedGlucoseData = timeStampOfNewGlucoseData } @@ -67,7 +67,7 @@ func parseLibreData(data:inout Data, timeStampLastBgReadingStoredInDatabase:Date byte.append(data[headerOffset + (i * 6 + 124)]) let glucoseLevelRaw = Double(getGlucoseRaw(bytes: byte)) if (glucoseLevelRaw > 0) { - glucoseData = RawGlucoseData(timeStamp: Date(timeIntervalSince1970: sensorStartTimeInMilliseconds/1000 + timeInMinutes * 60), glucoseLevelRaw: Double(getGlucoseRaw(bytes: byte)) * Constants.BloodGlucose.libreMultiplier) + glucoseData = RawGlucoseData(timeStamp: Date(timeIntervalSince1970: sensorStartTimeInMilliseconds/1000 + timeInMinutes * 60), glucoseLevelRaw: Double(getGlucoseRaw(bytes: byte)) * ConstantsBloodGlucose.libreMultiplier) returnValue.append(glucoseData) timeStampLastAddedGlucoseData = timeStampOfNewGlucoseData } diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/SensorState.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/SensorState.swift index 01aaa8ba..9d5caa4a 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/SensorState.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/SensorState.swift @@ -13,8 +13,8 @@ import Foundation /// - notYetStarted: 0x01 sensor not yet started /// - starting: 0x02 sensor is in the starting phase /// - ready: 0x03 sensor is ready, i.e. in normal operation mode -/// - stateFour: 0x04 state with yet unknown meaning -/// - expired: 0x05 sensor is expired +/// - expired: 0x04 sensor is expired, status after 14 days, less than 14,5 days +/// - shutdown: 0x05 sensor stops operation after 15d after start /// - failure: 0x06 sensor has an error /// - unknown: any other state enum LibreSensorState { diff --git a/xdrip/Transmitter/GenericBluetoothTransmitter/BluetoothTransmitter.swift b/xdrip/Transmitter/GenericBluetoothTransmitter/BluetoothTransmitter.swift index f4c81aea..56abd88c 100644 --- a/xdrip/Transmitter/GenericBluetoothTransmitter/BluetoothTransmitter.swift +++ b/xdrip/Transmitter/GenericBluetoothTransmitter/BluetoothTransmitter.swift @@ -35,7 +35,7 @@ class BluetoothTransmitter: NSObject, CBCentralManagerDelegate, CBPeripheralDele private let startScanningAfterInit:Bool // for OS_log, - private let log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryBlueTooth) + private let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryBlueTooth) /// centralManager private var centralManager: CBCentralManager? diff --git a/xdrip/Utilities/Log.swift b/xdrip/Utilities/Log.swift index 260ea83c..cd9ff7a6 100644 --- a/xdrip/Utilities/Log.swift +++ b/xdrip/Utilities/Log.swift @@ -2,7 +2,7 @@ import Foundation import os fileprivate var log:OSLog = { - let log:OSLog = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.debuglogging) + let log:OSLog = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.debuglogging) return log }() diff --git a/xdrip/Utilities/SoundPlayer/SoundPlayer.swift b/xdrip/Utilities/SoundPlayer/SoundPlayer.swift index 95803511..442a35ce 100644 --- a/xdrip/Utilities/SoundPlayer/SoundPlayer.swift +++ b/xdrip/Utilities/SoundPlayer/SoundPlayer.swift @@ -10,7 +10,7 @@ class SoundPlayer { // MARK: - properties /// for logging - private var log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryPlaySound) + private var log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryPlaySound) /// audioplayer private var audioPlayer:AVAudioPlayer? diff --git a/xdrip/View Controllers/Helpers/SettingsSelectedRowAction.swift b/xdrip/View Controllers/Helpers/SettingsSelectedRowAction.swift index 68b7e7d1..411ecb4d 100644 --- a/xdrip/View Controllers/Helpers/SettingsSelectedRowAction.swift +++ b/xdrip/View Controllers/Helpers/SettingsSelectedRowAction.swift @@ -50,6 +50,11 @@ enum SettingsSelectedRowAction { /// (it's not the right place to define this, not a clear split view/model) case performSegue(withIdentifier: String) + /// to show Info to user, eg licenseInfo, with a title and a message + /// + /// typical a pop up with a title and the message + case showInfoText(title: String, message: String) + } /* explanation UITableViewCell.AccessoryType diff --git a/xdrip/View Controllers/Root View Controller/RootViewController.swift b/xdrip/View Controllers/Root View Controller/RootViewController.swift index 77f4ede3..189296c7 100644 --- a/xdrip/View Controllers/Root View Controller/RootViewController.swift +++ b/xdrip/View Controllers/Root View Controller/RootViewController.swift @@ -52,13 +52,16 @@ final class RootViewController: UIViewController { /// constant for key in ApplicationManager.shared.addClosureToRunWhenAppWillEnterForeground - initiate pairing private let applicationManagerKeyInitiatePairing = "RootViewController-InitiatePairing" + /// constant for key in ApplicationManager.shared.addClosureToRunWhenAppWillEnterForeground - initial calibration + private let applicationManagerKeyInitialCalibration = "RootViewController-InitialCalibration" + // MARK: - Properties - other private properties /// a reference to the CGMTransmitter currently in use - nil means there's none, because user hasn't selected yet all required settings private var cgmTransmitter:CGMTransmitter? /// for logging - private var log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryFirstView) + private var log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryFirstView) /// coreDataManager to be used throughout the project private var coreDataManager:CoreDataManager? @@ -123,7 +126,7 @@ final class RootViewController: UIViewController { // Setup Core Data Manager - setting up coreDataManager happens asynchronously // completion handler is called when finished. This gives the app time to already continue setup which is independent of coredata, like setting up the transmitter, start scanning // In the exceptional case that the transmitter would give a new reading before the DataManager is set up, then this new reading will be ignored - coreDataManager = CoreDataManager(modelName: Constants.CoreData.modelName, completion: { + coreDataManager = CoreDataManager(modelName: ConstantsCoreData.modelName, completion: { self.setupApplicationData() @@ -172,7 +175,7 @@ final class RootViewController: UIViewController { // if licenseinfo not yet accepted, show license info with only ok button if !UserDefaults.standard.licenseInfoAccepted { - UIAlertController(title: Constants.HomeView.applicationName, message: Texts_HomeView.licenseInfo + Constants.HomeView.infoEmailAddress, actionHandler: { + UIAlertController(title: ConstantsHomeView.applicationName, message: Texts_HomeView.licenseInfo + ConstantsHomeView.infoEmailAddress, actionHandler: { // set licenseInfoAccepted to true UserDefaults.standard.licenseInfoAccepted = true @@ -251,7 +254,11 @@ final class RootViewController: UIViewController { }) } - private func processNewCGMInfo(glucoseData: inout [RawGlucoseData], sensorState: LibreSensorState?, firmware: String?, hardware: String?, transmitterBatteryInfo: TransmitterBatteryInfo?, sensorTimeInMinutes: Int?) { + /// process new glucose data received from transmitter. + /// - parameters: + /// - glucoseData : array with new readings + /// - sensorTimeInMinutes : should be present only if it's the first reading(s) being processed for a specific sensor and is needed if it's a transmitterType that returns true to the function canDetectNewSensor + private func processNewGlucoseData(glucoseData: inout [RawGlucoseData], sensorTimeInMinutes: Int?) { // check that calibrations and coredata manager is not nil guard let calibrationsAccessor = calibrationsAccessor, let coreDataManager = coreDataManager else { @@ -337,7 +344,7 @@ final class RootViewController: UIViewController { } if let alertManager = alertManager { - alertManager.checkAlerts(maxAgeOfLastBgReadingInSeconds: Constants.Master.maximumBgReadingAgeForAlertsInSeconds) + alertManager.checkAlerts(maxAgeOfLastBgReadingInSeconds: ConstantsMaster.maximumBgReadingAgeForAlertsInSeconds) } if let healthKitManager = healthKitManager { @@ -354,10 +361,6 @@ final class RootViewController: UIViewController { } } - // check transmitterBatteryInfo and if available store in settings - if let transmitterBatteryInfo = transmitterBatteryInfo { - UserDefaults.standard.transmitterBatteryInfo = transmitterBatteryInfo - } } // MARK:- observe function @@ -457,7 +460,7 @@ final class RootViewController: UIViewController { // check if timer already exists, if so invalidate it invalidateUpdateLabelsTimer() // now recreate, schedule and return - return Timer.scheduledTimer(timeInterval: Constants.HomeView.updateHomeViewIntervalInSeconds, target: self, selector: #selector(self.updateLabels), userInfo: nil, repeats: true) + return Timer.scheduledTimer(timeInterval: ConstantsHomeView.updateHomeViewIntervalInSeconds, target: self, selector: #selector(self.updateLabels), userInfo: nil, repeats: true) } // call scheduleUpdateLabelsTimer function now - as the function setupUpdateLabelsTimer is called from viewdidload, it will be called immediately after app launch @@ -540,7 +543,7 @@ final class RootViewController: UIViewController { // check alerts if let alertManager = self.alertManager { - alertManager.checkAlerts(maxAgeOfLastBgReadingInSeconds: Constants.Master.maximumBgReadingAgeForAlertsInSeconds) + alertManager.checkAlerts(maxAgeOfLastBgReadingInSeconds: ConstantsMaster.maximumBgReadingAgeForAlertsInSeconds) } // update labels @@ -598,7 +601,7 @@ final class RootViewController: UIViewController { case .Blucon: if let currentTransmitterId = UserDefaults.standard.transmitterId { - cgmTransmitter = CGMBluconTransmitter(address: UserDefaults.standard.bluetoothDeviceAddress, transmitterID: currentTransmitterId, delegate: self, timeStampLastBgReading: Date(timeIntervalSince1970: 0)) + cgmTransmitter = CGMBluconTransmitter(address: UserDefaults.standard.bluetoothDeviceAddress, transmitterID: currentTransmitterId, delegate: self, timeStampLastBgReading: Date(timeIntervalSince1970: 0), sensorSerialNumber: UserDefaults.standard.sensorSerialNumber) calibrator = Libre1Calibrator() } @@ -622,11 +625,11 @@ final class RootViewController: UIViewController { } } - // creates initial calibration request notification + /// creates initial calibration request notification private func createInitialCalibrationRequest() { // first remove existing notification if any - UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [Constants.Notifications.NotificationIdentifiersForCalibration.initialCalibrationRequest]) + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [ConstantsNotifications.NotificationIdentifiersForCalibration.initialCalibrationRequest]) // Create Notification Content let notificationContent = UNMutableNotificationContent() @@ -641,7 +644,7 @@ final class RootViewController: UIViewController { notificationContent.sound = UNNotificationSound.init(named: UNNotificationSoundName.init("")) // Create Notification Request - let notificationRequest = UNNotificationRequest(identifier: Constants.Notifications.NotificationIdentifiersForCalibration.initialCalibrationRequest, content: notificationContent, trigger: nil) + let notificationRequest = UNNotificationRequest(identifier: ConstantsNotifications.NotificationIdentifiersForCalibration.initialCalibrationRequest, content: notificationContent, trigger: nil) // Add Request to User Notification Center UNUserNotificationCenter.current().add(notificationRequest) { (error) in @@ -649,9 +652,25 @@ final class RootViewController: UIViewController { os_log("Unable to Add Notification Request : %{public}@", log: self.log, type: .error, error.localizedDescription) } } + + // 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) + // whenever app comes from-back to foreground, requestCalibration needs to be called + ApplicationManager.shared.addClosureToRunWhenAppWillEnterForeground(key: applicationManagerKeyInitialCalibration, closure: { + + // first of all reremove from application key manager + ApplicationManager.shared.removeClosureToRunWhenAppWillEnterForeground(key: self.applicationManagerKeyInitialCalibration) + + // remove existing notification if any + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [ConstantsNotifications.NotificationIdentifiersForCalibration.initialCalibrationRequest]) + + // request the calibration + self.requestCalibration(userRequested: false) + + }) + } - // creates bgreading notification + /// creates bgreading notification private func createBgReadingNotification() { // bgReadingsAccessor should not be nil at all, but let's not create a fatal error for that, there's already enough checks for it @@ -673,10 +692,10 @@ final class RootViewController: UIViewController { } // remove existing notification if any - UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [Constants.Notifications.NotificationIdentifierForBgReading.bgReadingNotificationRequest]) + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [ConstantsNotifications.NotificationIdentifierForBgReading.bgReadingNotificationRequest]) // also remove the sensor not detected notification, if any - UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [Constants.Notifications.NotificationIdentifierForSensorNotDetected.sensorNotDetected]) + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [ConstantsNotifications.NotificationIdentifierForSensorNotDetected.sensorNotDetected]) // Create Notification Content let notificationContent = UNMutableNotificationContent() @@ -695,7 +714,7 @@ final class RootViewController: UIViewController { notificationContent.body = " " // Create Notification Request - let notificationRequest = UNNotificationRequest(identifier: Constants.Notifications.NotificationIdentifierForBgReading.bgReadingNotificationRequest, content: notificationContent, trigger: nil) + let notificationRequest = UNNotificationRequest(identifier: ConstantsNotifications.NotificationIdentifierForBgReading.bgReadingNotificationRequest, content: notificationContent, trigger: nil) // Add Request to User Notification Center UNUserNotificationCenter.current().add(notificationRequest) { (error) in @@ -921,16 +940,21 @@ final class RootViewController: UIViewController { } - // stop the active sensor + // stops the active sensor and sets sensorSerialNumber in UserDefaults to nil private func stopSensor() { + if let activeSensor = activeSensor, let coreDataManager = coreDataManager { activeSensor.endDate = Date() coreDataManager.saveChanges() } - activeSensor = nil - // save the changes coreDataManager?.saveChanges() + + activeSensor = nil + + // reset also serialNubmer to nil + UserDefaults.standard.sensorSerialNumber = nil + } // start a new sensor, ask user for starttime @@ -989,7 +1013,7 @@ extension RootViewController:CGMTransmitterDelegate { 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) + let notificationRequest = UNNotificationRequest(identifier: ConstantsNotifications.NotificationIdentifierForResetResult.transmitterResetResult, content: notificationContent, trigger: nil) // Add Request to User Notification Center UNUserNotificationCenter.current().add(notificationRequest) { (error) in @@ -1011,7 +1035,7 @@ extension RootViewController:CGMTransmitterDelegate { func successfullyPaired() { // remove existing notification if any - UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [Constants.Notifications.NotificationIdentifierForTransmitterNeedsPairing.transmitterNeedsPairing]) + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [ConstantsNotifications.NotificationIdentifierForTransmitterNeedsPairing.transmitterNeedsPairing]) // invalidate transmitterPairingResponseTimer if let transmitterPairingResponseTimer = transmitterPairingResponseTimer { @@ -1066,6 +1090,8 @@ extension RootViewController:CGMTransmitterDelegate { _ = cgmTransmitter.startScanning() } + @unknown default: + break } } @@ -1078,7 +1104,7 @@ extension RootViewController:CGMTransmitterDelegate { if let timeStampLastNotificationForPairing = timeStampLastNotificationForPairing { // check timestamp of last notification, if too soon then return - if Int(abs(timeStampLastNotificationForPairing.timeIntervalSinceNow)) < Constants.BluetoothPairing.minimumTimeBetweenTwoPairingNotificationsInSeconds { + if Int(abs(timeStampLastNotificationForPairing.timeIntervalSinceNow)) < ConstantsBluetoothPairing.minimumTimeBetweenTwoPairingNotificationsInSeconds { return } } @@ -1087,7 +1113,7 @@ extension RootViewController:CGMTransmitterDelegate { timeStampLastNotificationForPairing = Date() // remove existing notification if any - UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [Constants.Notifications.NotificationIdentifierForTransmitterNeedsPairing.transmitterNeedsPairing]) + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [ConstantsNotifications.NotificationIdentifierForTransmitterNeedsPairing.transmitterNeedsPairing]) // Create Notification Content let notificationContent = UNMutableNotificationContent() @@ -1101,7 +1127,7 @@ extension RootViewController:CGMTransmitterDelegate { notificationContent.sound = UNNotificationSound.init(named: UNNotificationSoundName.init("")) // Create Notification Request - let notificationRequest = UNNotificationRequest(identifier: Constants.Notifications.NotificationIdentifierForTransmitterNeedsPairing.transmitterNeedsPairing, content: notificationContent, trigger: nil) + let notificationRequest = UNNotificationRequest(identifier: ConstantsNotifications.NotificationIdentifierForTransmitterNeedsPairing.transmitterNeedsPairing, content: notificationContent, trigger: nil) // Add Request to User Notification Center UNUserNotificationCenter.current().add(notificationRequest) { (error) in @@ -1117,18 +1143,17 @@ extension RootViewController:CGMTransmitterDelegate { // 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)) + let maxTimeUserCanOpenApp = Date(timeIntervalSinceNow: TimeInterval(ConstantsDexcomG5.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 + // whenever app comes from-back to foreground, 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]) + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [ConstantsNotifications.NotificationIdentifierForTransmitterNeedsPairing.transmitterNeedsPairing]) // if it was too long since notification was fired, then forget about it if Date() > maxTimeUserCanOpenApp { @@ -1163,7 +1188,7 @@ extension RootViewController:CGMTransmitterDelegate { notificationContent.body = Texts_HomeView.sensorNotDetected // Create Notification Request - let notificationRequest = UNNotificationRequest(identifier: Constants.Notifications.NotificationIdentifierForSensorNotDetected.sensorNotDetected, content: notificationContent, trigger: nil) + let notificationRequest = UNNotificationRequest(identifier: ConstantsNotifications.NotificationIdentifierForSensorNotDetected.sensorNotDetected, content: notificationContent, trigger: nil) // Add Request to User Notification Center UNUserNotificationCenter.current().add(notificationRequest) { (error) in @@ -1175,18 +1200,32 @@ extension RootViewController:CGMTransmitterDelegate { /// - parameters: /// - readings: first entry is the most recent - func cgmTransmitterInfoReceived(glucoseData: inout [RawGlucoseData], transmitterBatteryInfo: TransmitterBatteryInfo?, sensorState: LibreSensorState?, sensorTimeInMinutes: Int?, firmware: String?, hardware: String?, serialNumber: String?, bootloader: String?) { + func cgmTransmitterInfoReceived(glucoseData: inout [RawGlucoseData], transmitterBatteryInfo: TransmitterBatteryInfo?, sensorState: LibreSensorState?, sensorTimeInMinutes: Int?, firmware: String?, hardware: String?, hardwareSerialNumber: String?, bootloader: String?, sensorSerialNumber: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") - os_log("serialNumber %{public}@", log: log, type: .debug, serialNumber ?? "no serialNumber found") + os_log("hardwareSerialNumber %{public}@", log: log, type: .debug, hardwareSerialNumber ?? "no serialNumber found") + os_log("sensorSerialNumber %{public}@", log: log, type: .debug, sensorSerialNumber ?? "no sensorSerialNumber found") 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) + + // if received sensorSerialNumber not nil, and if value different from currently stored value, then store it + if let sensorSerialNumber = sensorSerialNumber { + if sensorSerialNumber != UserDefaults.standard.sensorSerialNumber { + UserDefaults.standard.sensorSerialNumber = sensorSerialNumber + } + } - processNewCGMInfo(glucoseData: &glucoseData, sensorState: sensorState, firmware: firmware, hardware: hardware, transmitterBatteryInfo: transmitterBatteryInfo, sensorTimeInMinutes: sensorTimeInMinutes) + // if received transmitterBatteryInfo not nil, then store it + if let transmitterBatteryInfo = transmitterBatteryInfo { + UserDefaults.standard.transmitterBatteryInfo = transmitterBatteryInfo + } + + // process new readings + processNewGlucoseData(glucoseData: &glucoseData, sensorTimeInMinutes: sensorTimeInMinutes) } } @@ -1212,24 +1251,27 @@ extension RootViewController:UNUserNotificationCenterDelegate { // 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 { + if notification.request.identifier == ConstantsNotifications.NotificationIdentifiersForCalibration.initialCalibrationRequest { // request calibration requestCalibration(userRequested: false) + /// remove applicationManagerKeyInitialCalibration from application key manager - there's no need to initiate the calibration via this closure + ApplicationManager.shared.removeClosureToRunWhenAppWillEnterForeground(key: self.applicationManagerKeyInitialCalibration) + // call completionhandler to avoid that notification is shown to the user completionHandler([]) - } else if notification.request.identifier == Constants.Notifications.NotificationIdentifierForSensorNotDetected.sensorNotDetected { + } else if notification.request.identifier == ConstantsNotifications.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.NotificationIdentifierForResetResult.transmitterResetResult { + } else if notification.request.identifier == ConstantsNotifications.NotificationIdentifierForResetResult.transmitterResetResult { completionHandler([.alert]) - } else if notification.request.identifier == Constants.Notifications.NotificationIdentifierForTransmitterNeedsPairing.transmitterNeedsPairing { + } else if notification.request.identifier == ConstantsNotifications.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([]) @@ -1260,22 +1302,21 @@ extension RootViewController:UNUserNotificationCenterDelegate { completionHandler() } - if response.notification.request.identifier == Constants.Notifications.NotificationIdentifiersForCalibration.initialCalibrationRequest { + if response.notification.request.identifier == ConstantsNotifications.NotificationIdentifiersForCalibration.initialCalibrationRequest { - os_log(" userNotificationCenter didReceive, user pressed calibration notification to open the app", log: log, type: .info) - // request calibration - requestCalibration(userRequested: false) + // nothing required, the requestCalibration function will be called as it's been added to ApplicationManager + os_log(" userNotificationCenter didReceive, user pressed calibration notification to open the app, requestCalibration should be called because closure is added in ApplicationManager.shared", log: log, type: .info) - } else if response.notification.request.identifier == Constants.Notifications.NotificationIdentifierForSensorNotDetected.sensorNotDetected { + } else if response.notification.request.identifier == ConstantsNotifications.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 { + } else if response.notification.request.identifier == ConstantsNotifications.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 { + } else if response.notification.request.identifier == ConstantsNotifications.NotificationIdentifierForResetResult.transmitterResetResult { // nothing required @@ -1341,7 +1382,7 @@ extension RootViewController:NightScoutFollowerDelegate { // check alerts if let alertManager = alertManager { - alertManager.checkAlerts(maxAgeOfLastBgReadingInSeconds: Constants.Follower.maximumBgReadingAgeForAlertsInSeconds) + alertManager.checkAlerts(maxAgeOfLastBgReadingInSeconds: ConstantsFollower.maximumBgReadingAgeForAlertsInSeconds) } if let healthKitManager = healthKitManager { diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/AlertTypesSettingsViewController/AlertTypeSettingsViewController/AlertTypeSettingsViewController.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/AlertTypesSettingsViewController/AlertTypeSettingsViewController/AlertTypeSettingsViewController.swift index 8512739f..895a3880 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/AlertTypesSettingsViewController/AlertTypeSettingsViewController/AlertTypeSettingsViewController.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/AlertTypesSettingsViewController/AlertTypeSettingsViewController/AlertTypeSettingsViewController.swift @@ -65,13 +65,13 @@ final class AlertTypeSettingsViewController: UIViewController { // MARK:- alerttype temp properties // following properties are used to temporary store alertType attributes which can be modified. The actual update of the alertType being processed will be done only when the user clicks the done button - private var enabled = Constants.DefaultAlertTypeSettings.enabled - private var name = Constants.DefaultAlertTypeSettings.name - private var overrideMute = Constants.DefaultAlertTypeSettings.overrideMute - private var snooze = Constants.DefaultAlertTypeSettings.snooze - private var snoozePeriod = Constants.DefaultAlertTypeSettings.snoozePeriod - private var vibrate = Constants.DefaultAlertTypeSettings.vibrate - private var soundName = Constants.DefaultAlertTypeSettings.soundName + private var enabled = ConstantsDefaultAlertTypeSettings.enabled + private var name = ConstantsDefaultAlertTypeSettings.name + private var overrideMute = ConstantsDefaultAlertTypeSettings.overrideMute + private var snooze = ConstantsDefaultAlertTypeSettings.snooze + private var snoozePeriod = ConstantsDefaultAlertTypeSettings.snoozePeriod + private var vibrate = ConstantsDefaultAlertTypeSettings.vibrate + private var soundName = ConstantsDefaultAlertTypeSettings.soundName // MARK:- public functions @@ -299,7 +299,7 @@ extension AlertTypeSettingsViewController: UITableViewDataSource, UITableViewDel case .soundName: // create array of all sounds and sound filenames, inclusive default ios sound and also empty string, which is "no sound" - var sounds = Constants.Sounds.allSoundsBySoundNameAndFileName() + var sounds = ConstantsSounds.allSoundsBySoundNameAndFileName() sounds.soundNames.insert(Texts_AlertTypeSettingsView.alertTypeDefaultIOSSound, at: 0) sounds.soundNames.insert(Texts_AlertTypeSettingsView.alertTypeNoSound, at: 0) diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewController.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewController.swift index 8c9a2281..c8049d17 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewController.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewController.swift @@ -14,6 +14,8 @@ final class SettingsViewController: UIViewController { fileprivate var healthKitSettingsViewModel = SettingsViewHealthKitSettingsViewModel() fileprivate var alarmsSettingsViewModel = SettingsViewAlertSettingsViewModel() fileprivate var speakSettingsViewModel = SettingsViewSpeakSettingsViewModel() + fileprivate var developmentSettingsViewModel = SettingsViewDevelopmentSettingsViewModel() + fileprivate var infoSettingsViewModel = SettingsViewInfoViewModel() private lazy var pickerViewController: PickerViewController = { // Instantiate View Controller @@ -132,6 +134,10 @@ extension SettingsViewController:UITableViewDataSource, UITableViewDelegate { case healthkit /// store bg values in healthkit case speak + /// info + case info + /// developper settings + case developer } // MARK: - UITableViewDataSource protocol Methods @@ -153,6 +159,10 @@ extension SettingsViewController:UITableViewDataSource, UITableViewDelegate { return alarmsSettingsViewModel.sectionTitle() case .speak: return speakSettingsViewModel.sectionTitle() + case .developer: + return developmentSettingsViewModel.sectionTitle() + case .info: + return infoSettingsViewModel.sectionTitle() } } @@ -177,6 +187,11 @@ extension SettingsViewController:UITableViewDataSource, UITableViewDelegate { return alarmsSettingsViewModel.numberOfRows() case .speak: return speakSettingsViewModel.numberOfRows() + case .developer: + return developmentSettingsViewModel.numberOfRows() + case .info: + return infoSettingsViewModel.numberOfRows() + } } @@ -201,6 +216,10 @@ extension SettingsViewController:UITableViewDataSource, UITableViewDelegate { viewModel = alarmsSettingsViewModel case .speak: viewModel = speakSettingsViewModel + case .developer: + viewModel = developmentSettingsViewModel + case .info: + viewModel = infoSettingsViewModel } @@ -226,6 +245,8 @@ extension SettingsViewController:UITableViewDataSource, UITableViewDelegate { cell.selectionStyle = .gray case .none: cell.selectionStyle = .none + @unknown default: + cell.selectionStyle = .none } cell.accessoryView = viewModel.uiView(index: indexPath.row) @@ -287,6 +308,10 @@ extension SettingsViewController:UITableViewDataSource, UITableViewDelegate { viewModel = alarmsSettingsViewModel case .speak: viewModel = speakSettingsViewModel + case .developer: + viewModel = developmentSettingsViewModel + case .info: + viewModel = infoSettingsViewModel } if let viewModel = viewModel { @@ -349,6 +374,11 @@ extension SettingsViewController:UITableViewDataSource, UITableViewDelegate { case .performSegue(let withIdentifier): self.performSegue(withIdentifier: withIdentifier, sender: nil) + + case let .showInfoText(title, message): + + UIAlertController(title: title, message: message, actionHandler: nil).presentInOwnWindow(animated: true, completion: nil) + } } else { @@ -356,6 +386,14 @@ extension SettingsViewController:UITableViewDataSource, UITableViewDelegate { } } } + + func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) { + + // apple doc says : Use this method to respond to taps in the detail button accessory view of a row. The table view does not call this method for other types of accessory views. + // when user clicks on of the detail buttons, then consider this as row selected, for now - as it's only license that is using this button for now + self.tableView(tableView, didSelectRowAt: indexPath) + + } } /// defines perform segue identifiers used within settingsviewcontroller diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewDevelopmentSettingsViewModel.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewDevelopmentSettingsViewModel.swift new file mode 100644 index 00000000..ee35e688 --- /dev/null +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewDevelopmentSettingsViewModel.swift @@ -0,0 +1,121 @@ +import UIKit + +fileprivate enum Setting:Int, CaseIterable { + + // for G6 testing, factor 1 + case G6v2ScalingFactor1 = 0 + + // for G6 testing, factor 2 + case G6v2ScalingFactor2 = 1 + +} + +struct SettingsViewDevelopmentSettingsViewModel:SettingsViewModelProtocol { + + func sectionTitle() -> String? { + return "Developer Settings" + } + + func settingsRowText(index: Int) -> String { + + guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } + + switch setting { + + case .G6v2ScalingFactor1: + return "G6 v2 scaling factor 1" + case .G6v2ScalingFactor2: + return "G6 v2 scaling factor 2" + + } + } + + func accessoryType(index: Int) -> UITableViewCell.AccessoryType { + + guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } + + switch setting { + + case .G6v2ScalingFactor1: + return UITableViewCell.AccessoryType.disclosureIndicator + + case .G6v2ScalingFactor2: + return UITableViewCell.AccessoryType.disclosureIndicator + + } + } + + func detailedText(index: Int) -> String? { + + guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } + + switch setting { + + case .G6v2ScalingFactor1: + if let factor = UserDefaults.standard.G6v2ScalingFactor1 { + return factor + } else { + return CGMG6Transmitter.G6v2DefaultScalingFactor1.description + } + case .G6v2ScalingFactor2: + if let factor = UserDefaults.standard.G6v2ScalingFactor2 { + return factor + } else { + return CGMG6Transmitter.G6v2DefaultScalingFactor2.description + } + } + + } + + func uiView(index: Int) -> UIView? { + + return nil + + } + + func numberOfRows() -> Int { + return Setting.allCases.count + } + + func onRowSelect(index: Int) -> SettingsSelectedRowAction { + + guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } + + switch setting { + + case .G6v2ScalingFactor1: + + return SettingsSelectedRowAction.askText(title: "G6 scaling", message: "Give G6 v2 scaling factor 1", keyboardType: UIKeyboardType.decimalPad, text: UserDefaults.standard.G6v2ScalingFactor1, placeHolder: CGMG6Transmitter.G6v2DefaultScalingFactor1.description, actionTitle: nil, cancelTitle: nil, actionHandler: {(factor:String) in + + // convert to uppercase + if let factorAsDouble = factor.toDouble() { + UserDefaults.standard.G6v2ScalingFactor1 = factorAsDouble.description + } + + }, cancelHandler: nil) + + case .G6v2ScalingFactor2: + + return SettingsSelectedRowAction.askText(title: "G6 scaling", message: "Give G6 v2 scaling factor 2", keyboardType: UIKeyboardType.decimalPad, text: UserDefaults.standard.G6v2ScalingFactor2, placeHolder: CGMG6Transmitter.G6v2DefaultScalingFactor2.description, actionTitle: nil, cancelTitle: nil, actionHandler: {(factor:String) in + + // convert to uppercase + if let factorAsDouble = factor.toDouble() { + UserDefaults.standard.G6v2ScalingFactor2 = factorAsDouble.description + } + + + }, cancelHandler: nil) + + } + } + + func isEnabled(index: Int) -> Bool { + return true + } + + func completeSettingsViewRefreshNeeded(index: Int) -> Bool { + return false + } + + +} diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewGeneralSettingsViewModel.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewGeneralSettingsViewModel.swift index 9b9f55f7..ea9c3d50 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewGeneralSettingsViewModel.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewGeneralSettingsViewModel.swift @@ -33,10 +33,10 @@ struct SettingsViewGeneralSettingsViewModel:SettingsViewModelProtocol { case .bloodGlucoseUnit: return SettingsSelectedRowAction.callFunction(function: {UserDefaults.standard.bloodGlucoseUnitIsMgDl ? (UserDefaults.standard.bloodGlucoseUnitIsMgDl) = false : (UserDefaults.standard.bloodGlucoseUnitIsMgDl = true)}) case .lowMarkValue: - return SettingsSelectedRowAction.askText(title: Texts_SettingsView.labelLowValue, message: nil, keyboardType: UserDefaults.standard.bloodGlucoseUnitIsMgDl ? .numberPad:.decimalPad, text: UserDefaults.standard.lowMarkValueInUserChosenUnitRounded, placeHolder: Constants.BGGraphBuilder.defaultLowMarkInMgdl.description, actionTitle: nil, cancelTitle: nil, actionHandler: {(lowMarkValue:String) in UserDefaults.standard.lowMarkValueInUserChosenUnitRounded = lowMarkValue}, cancelHandler: nil) + return SettingsSelectedRowAction.askText(title: Texts_SettingsView.labelLowValue, message: nil, keyboardType: UserDefaults.standard.bloodGlucoseUnitIsMgDl ? .numberPad:.decimalPad, text: UserDefaults.standard.lowMarkValueInUserChosenUnitRounded, placeHolder: ConstantsBGGraphBuilder.defaultLowMarkInMgdl.description, actionTitle: nil, cancelTitle: nil, actionHandler: {(lowMarkValue:String) in UserDefaults.standard.lowMarkValueInUserChosenUnitRounded = lowMarkValue}, cancelHandler: nil) case .highMarkValue: - return SettingsSelectedRowAction.askText(title: Texts_SettingsView.labelHighValue, message: nil, keyboardType: UserDefaults.standard.bloodGlucoseUnitIsMgDl ? .numberPad:.decimalPad, text: UserDefaults.standard.highMarkValueInUserChosenUnitRounded, placeHolder: Constants.BGGraphBuilder.defaultHighMmarkInMgdl.description, actionTitle: nil, cancelTitle: nil, actionHandler: {(highMarkValue:String) in UserDefaults.standard.highMarkValueInUserChosenUnitRounded = highMarkValue}, cancelHandler: nil) + return SettingsSelectedRowAction.askText(title: Texts_SettingsView.labelHighValue, message: nil, keyboardType: UserDefaults.standard.bloodGlucoseUnitIsMgDl ? .numberPad:.decimalPad, text: UserDefaults.standard.highMarkValueInUserChosenUnitRounded, placeHolder: ConstantsBGGraphBuilder.defaultHighMmarkInMgdl.description, actionTitle: nil, cancelTitle: nil, actionHandler: {(highMarkValue:String) in UserDefaults.standard.highMarkValueInUserChosenUnitRounded = highMarkValue}, cancelHandler: nil) case .masterFollower: return SettingsSelectedRowAction.callFunction(function: { diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewHealthKitSettingsViewModel.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewHealthKitSettingsViewModel.swift index 05b1e6cc..7ea95789 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewHealthKitSettingsViewModel.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewHealthKitSettingsViewModel.swift @@ -8,7 +8,7 @@ class SettingsViewHealthKitSettingsViewModel:SettingsViewModelProtocol { // MARK: - private properties /// for logging - private var log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryHealthKitManager) + private var log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryHealthKitManager) // MARK: - functions in protocol SettingsViewModelProtocol @@ -75,6 +75,8 @@ class SettingsViewHealthKitSettingsViewModel:SettingsViewModelProtocol { os_log("user removed authorization to store bgreadings in healthkit", log: self.log, type: .error) case .sharingAuthorized: break + @unknown default: + os_log("unknown authorizationstatus for healthkit - SettingsViewHealthKitSettingsViewModel", log: self.log, type: .error) } } else { os_log("User enabled HealthKit however failed to create bloodGlucoseType", log: self.log, type: .error) diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewInfoViewModel.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewInfoViewModel.swift new file mode 100644 index 00000000..9bad0a4b --- /dev/null +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewInfoViewModel.swift @@ -0,0 +1,112 @@ +import UIKit + +fileprivate enum Setting:Int, CaseIterable { + + /// version Number + case versionNumber = 0 + + /// licenseInfo + case licenseInfo = 1 + +} + +struct SettingsViewInfoViewModel:SettingsViewModelProtocol { + + func sectionTitle() -> String? { + return Texts_HomeView.info + } + + func settingsRowText(index: Int) -> String { + + guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } + + switch setting { + + case .versionNumber: + + return Texts_SettingsView.version + + case .licenseInfo: + return Texts_SettingsView.license + + } + + } + + func accessoryType(index: Int) -> UITableViewCell.AccessoryType { + + guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } + + switch setting { + + case .versionNumber: + return UITableViewCell.AccessoryType.none + + case .licenseInfo: + return UITableViewCell.AccessoryType.detailButton + + } + + } + + func detailedText(index: Int) -> String? { + + guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } + + switch setting { + + case .versionNumber: + + guard let dictionary = Bundle.main.infoDictionary else {return "unknown"} + + guard let version = dictionary["CFBundleShortVersionString"] as? String else {return "unknown"} + + return version + + case .licenseInfo: + + return nil + + } + + } + + func uiView(index: Int) -> UIView? { + return nil + } + + func numberOfRows() -> Int { + return Setting.allCases.count + } + + func onRowSelect(index: Int) -> SettingsSelectedRowAction { + + guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") } + + switch setting { + + case .versionNumber: + return SettingsSelectedRowAction.nothing + + case .licenseInfo: + + return SettingsSelectedRowAction.showInfoText(title: ConstantsHomeView.applicationName, message: Texts_HomeView.licenseInfo + ConstantsHomeView.infoEmailAddress) + + } + } + + func isEnabled(index: Int) -> Bool { + + return true + + } + + func completeSettingsViewRefreshNeeded(index: Int) -> Bool { + + return false + + } + + +} + diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewSpeakSettingsViewModel.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewSpeakSettingsViewModel.swift index 314caaa0..3cac425b 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewSpeakSettingsViewModel.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewSpeakSettingsViewModel.swift @@ -49,14 +49,14 @@ class SettingsViewSpeakSettingsViewModel:SettingsViewModelProtocol { //find index for languageCode type currently stored in userdefaults var selectedRow:Int? if let languageCode = UserDefaults.standard.speakReadingLanguageCode { - selectedRow = Constants.SpeakReadingLanguages.allLanguageNamesAndCodes.codes.index(of:languageCode) + selectedRow = ConstantsSpeakReadingLanguages.allLanguageNamesAndCodes.codes.firstIndex(of:languageCode) } else { - selectedRow = Constants.SpeakReadingLanguages.allLanguageNamesAndCodes.codes.index(of:Texts_SpeakReading.defaultLanguageCode) + selectedRow = ConstantsSpeakReadingLanguages.allLanguageNamesAndCodes.codes.firstIndex(of:Texts_SpeakReading.defaultLanguageCode) } - return SettingsSelectedRowAction.selectFromList(title: Texts_SettingsView.speakReadingLanguageSelection, data: Constants.SpeakReadingLanguages.allLanguageNamesAndCodes.names, selectedRow: selectedRow, actionTitle: nil, cancelTitle: nil, actionHandler: {(index:Int) in + return SettingsSelectedRowAction.selectFromList(title: Texts_SettingsView.speakReadingLanguageSelection, data: ConstantsSpeakReadingLanguages.allLanguageNamesAndCodes.names, selectedRow: selectedRow, actionTitle: nil, cancelTitle: nil, actionHandler: {(index:Int) in if index != selectedRow { - UserDefaults.standard.speakReadingLanguageCode = Constants.SpeakReadingLanguages.allLanguageNamesAndCodes.codes[index] + UserDefaults.standard.speakReadingLanguageCode = ConstantsSpeakReadingLanguages.allLanguageNamesAndCodes.codes[index] } }, cancelHandler: nil, didSelectRowHandler: nil) diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewTransmitterSettingsViewModel.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewTransmitterSettingsViewModel.swift index a2cdd9e3..22dd7b9b 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewTransmitterSettingsViewModel.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewTransmitterSettingsViewModel.swift @@ -56,7 +56,7 @@ struct SettingsViewTransmitterSettingsViewModel:SettingsViewModelProtocol { //find index for transmitter type currently stored in userdefaults var selectedRow:Int? if let transmitterType = UserDefaults.standard.transmitterType?.rawValue { - selectedRow = data.index(of:transmitterType) + selectedRow = data.firstIndex(of:transmitterType) } return SettingsSelectedRowAction.selectFromList(title: Texts_SettingsView.labelTransmitterId, data: data, selectedRow: selectedRow, actionTitle: nil, cancelTitle: nil, actionHandler: {(index:Int) in