Merge pull request #1 from JohanDegraeve/master

sync
This commit is contained in:
bubbledevteam 2019-07-29 15:45:00 +08:00 committed by GitHub
commit b33e8d2bbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
71 changed files with 1554 additions and 631 deletions

View File

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

View File

@ -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 = "<group>"; };
F821CF9C22AEF483005C1E43 /* BGReadingSpeaker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BGReadingSpeaker.swift; sourceTree = "<group>"; };
F856CE5A22EDC8E50083E436 /* ConstantsBluetoothPairing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsBluetoothPairing.swift; sourceTree = "<group>"; };
F85DC2E721CFE2F500B9F74A /* BgReading+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "BgReading+CoreDataProperties.swift"; path = "../Extensions/BgReading+CoreDataProperties.swift"; sourceTree = "<group>"; };
F85DC2E921CFE2F500B9F74A /* Sensor+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Sensor+CoreDataProperties.swift"; path = "../Extensions/Sensor+CoreDataProperties.swift"; sourceTree = "<group>"; };
F85DC2F021CFE3D400B9F74A /* Calibration+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Calibration+CoreDataClass.swift"; sourceTree = "<group>"; };
@ -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 = "<group>"; };
F897AAFA2201018800CDDD10 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
F8A1584C22ECA445007F5B5D /* SettingsViewDevelopmentSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewDevelopmentSettingsViewModel.swift; sourceTree = "<group>"; };
F8A1584E22ECB281007F5B5D /* SettingsViewInfoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewInfoViewModel.swift; sourceTree = "<group>"; };
F8A1585022EDB597007F5B5D /* ConstantsBGGraphBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsBGGraphBuilder.swift; sourceTree = "<group>"; };
F8A1585222EDB602007F5B5D /* ConstantsBloodGlucose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsBloodGlucose.swift; sourceTree = "<group>"; };
F8A1585422EDB706007F5B5D /* ConstantsCalibrationAlgorithms.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsCalibrationAlgorithms.swift; sourceTree = "<group>"; };
F8A1585622EDB754007F5B5D /* ConstantsCoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsCoreData.swift; sourceTree = "<group>"; };
F8A1585822EDB7C6007F5B5D /* ConstantsDefaultAlertLevels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsDefaultAlertLevels.swift; sourceTree = "<group>"; };
F8A1585A22EDB7EA007F5B5D /* ConstantsDexcomG5.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsDexcomG5.swift; sourceTree = "<group>"; };
F8A1585E22EDB81E007F5B5D /* ConstantsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsLog.swift; sourceTree = "<group>"; };
F8A1586022EDB844007F5B5D /* ConstantsNotifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsNotifications.swift; sourceTree = "<group>"; };
F8A1586222EDB86E007F5B5D /* ConstantsSounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsSounds.swift; sourceTree = "<group>"; };
F8A1586422EDB89D007F5B5D /* ConstantsDefaultAlertTypeSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsDefaultAlertTypeSettings.swift; sourceTree = "<group>"; };
F8A1586622EDB8BF007F5B5D /* ConstantsHomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsHomeView.swift; sourceTree = "<group>"; };
F8A1586A22EDB967007F5B5D /* ConstantsMaster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsMaster.swift; sourceTree = "<group>"; };
F8A1586C22EDB9BE007F5B5D /* ConstantsDexcomFollower.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConstantsDexcomFollower.swift; sourceTree = "<group>"; };
F8A1586E22EDC7EE007F5B5D /* ConstantsSuspensionPrevention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsSuspensionPrevention.swift; sourceTree = "<group>"; };
F8A1587022EDC865007F5B5D /* ConstantsSpeakReadingLanguages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsSpeakReadingLanguages.swift; sourceTree = "<group>"; };
F8A1587222EDC893007F5B5D /* ConstantsDexcomShare.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsDexcomShare.swift; sourceTree = "<group>"; };
F8A54AAC22D6859200934E7A /* SlopeParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SlopeParameters.swift; sourceTree = "<group>"; };
F8A54AAE22D686CD00934E7A /* NightScoutBgReading.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightScoutBgReading.swift; sourceTree = "<group>"; };
F8A54AB322D9111900934E7A /* CGMTransmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMTransmitter.swift; sourceTree = "<group>"; };
@ -442,7 +479,6 @@
F8BDD458221DEF24006EAB84 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/SettingsViews.strings; sourceTree = "<group>"; };
F8E3C3AA21FE17B700907A04 /* StringProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringProtocol.swift; sourceTree = "<group>"; };
F8E3C3AC21FE551C00907A04 /* DexcomCalibrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomCalibrator.swift; sourceTree = "<group>"; };
F8EA6C7E21B70E390082976B /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
F8EA6C8121B723BC0082976B /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = "<group>"; };
F8EA6CA821BBE3010082976B /* UniqueId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniqueId.swift; sourceTree = "<group>"; };
F8EA6CAC21BC2CA40082976B /* BluetoothTransmitter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothTransmitter.swift; sourceTree = "<group>"; };
@ -976,6 +1012,8 @@
F8B3A83C227F090D004BA588 /* SettingsViewHealthKitSettingsViewModel.swift */,
F8B3A83D227F090D004BA588 /* SettingsViewSpeakSettingsViewModel.swift */,
F8B3A83E227F090D004BA588 /* SettingsViewGeneralSettingsViewModel.swift */,
F8A1584C22ECA445007F5B5D /* SettingsViewDevelopmentSettingsViewModel.swift */,
F8A1584E22ECB281007F5B5D /* SettingsViewInfoViewModel.swift */,
);
path = SettingsViewModels;
sourceTree = "<group>";
@ -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;

View File

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

View File

@ -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..<indexOfBackSlash[0]
// now get the soundName in a string
let soundName = String(sound.rawValue[soundNameRange])
// add the soundName to the returnvalue
soundNames.append(soundName)
// define range to get the soundFileName
let languageCodeRange = sound.rawValue.index(after: indexOfBackSlash[0])..<sound.rawValue.endIndex
// now get the language in a string
let fileName = String(sound.rawValue[languageCodeRange])
// add the languageCode to the returnvalue
soundFileNames.append(fileName)
}
return (soundNames, soundFileNames)
}
/// gets the soundname for specific case
static func getSoundName(forSound:Sounds) -> String {
let indexOfBackSlash = forSound.rawValue.indexes(of: "/")
let soundNameRange = forSound.rawValue.startIndex..<indexOfBackSlash[0]
return String(forSound.rawValue[soundNameRange])
}
/// gets the soundFie for specific case
static func getSoundFile(forSound:Sounds) -> String {
let indexOfBackSlash = forSound.rawValue.indexes(of: "/")
let soundNameRange = forSound.rawValue.index(after: indexOfBackSlash[0])..<forSound.rawValue.endIndex
return String(forSound.rawValue[soundNameRange])
}
}
/// default values to be used when creating a new AlertType
enum DefaultAlertTypeSettings {
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
}
/// constants for home view, ie first view
enum HomeView {
/// 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"
}
/// constants for follower mode
enum Follower {
/// 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
}
/// constants typically for master mode
enum Master {
/// 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
}
/// suspension prevention
enum SuspensionPrevention {
/// 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
}
/// supported languages for speak readings - defines name and language code, example "Dutch" and "nl-NL", both are defined in one case, seperated by a backslash
///
/// alphabetically ordered
enum SpeakReadingLanguages: String, CaseIterable {
case chinese = "Chinese/zh"
case dutch = "Dutch/nl"
case english = "English/en"
case french = "French/fr"
case italian = "Italian/it"
case polish = "Polish/pl-PL"
case portugese_portugal = "Portuguese/pt"
case portugese_brasil = "Portuguese (Brazil)/pt-BR"
case russian = "Russian/ru"
case slovenian = "Slovenian/sl"
case spanish_mexico = "Spanish (Mexico)/es-MX"
case spanish_spain = "Spanish (Spain)/es-ES"
/// gets all language names and language codes in two arrays
/// - returns:
/// ie part of the case before the / in the first array, part of the case after the / in the second array
public static var allLanguageNamesAndCodes: (names:[String], codes:[String]) {
var languageNames = [String]()
var languageCodes = [String]()
languageloop: for speakReadingLanguage in Constants.SpeakReadingLanguages.allCases {
// SpeakReadingLanguages defines available languages. Per case there is a string which is the language as shown in the UI and the language code, seperated by backslash
// get array of indexes, of location of "/"
let indexOfBackSlash = speakReadingLanguage.rawValue.indexes(of: "/")
// define range to get the language (as shown in UI)
let languageNameRange = speakReadingLanguage.rawValue.startIndex..<indexOfBackSlash[0]
// now get the language in a string
let language = String(speakReadingLanguage.rawValue[languageNameRange])
// add the soundName to the returnvalue
languageNames.append(language)
// define range to get the languagecode
let languageCodeRange = speakReadingLanguage.rawValue.index(after: indexOfBackSlash[0])..<speakReadingLanguage.rawValue.endIndex
// now get the language in a string
let languageCode = String(speakReadingLanguage.rawValue[languageCodeRange])
// add the languageCode to the returnvalue
languageCodes.append(languageCode)
}
return (languageNames, languageCodes)
}
/// gets the language name for specific case
static func languageName(forLanguageCode:String?) -> 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
}
}

View File

@ -0,0 +1,5 @@
enum ConstantsBGGraphBuilder {
static let maxSlopeInMinutes = 21
static let defaultLowMarkInMgdl = 70.0
static let defaultHighMmarkInMgdl = 170.0
}

View File

@ -0,0 +1,7 @@
enum ConstantsBloodGlucose {
static let mmollToMgdl = 18.01801801801802
static let mgDlToMmoll = 0.0555
static let libreMultiplier = 117.64705
}

View File

@ -0,0 +1,7 @@
/// constants related to Bluetooth pairing
enum ConstantsBluetoothPairing {
/// minimum time in seconds between two pairing notifications
static let minimumTimeBetweenTwoPairingNotificationsInSeconds = 30
}

View File

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

View File

@ -0,0 +1,4 @@
/// coredata specific constants
enum ConstantsCoreData {
static let modelName = "xdrip"
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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..<indexOfBackSlash[0]
// now get the soundName in a string
let soundName = String(sound.rawValue[soundNameRange])
// add the soundName to the returnvalue
soundNames.append(soundName)
// define range to get the soundFileName
let languageCodeRange = sound.rawValue.index(after: indexOfBackSlash[0])..<sound.rawValue.endIndex
// now get the language in a string
let fileName = String(sound.rawValue[languageCodeRange])
// add the languageCode to the returnvalue
soundFileNames.append(fileName)
}
return (soundNames, soundFileNames)
}
/// gets the soundname for specific case
static func getSoundName(forSound:ConstantsSounds) -> String {
let indexOfBackSlash = forSound.rawValue.indexes(of: "/")
let soundNameRange = forSound.rawValue.startIndex..<indexOfBackSlash[0]
return String(forSound.rawValue[soundNameRange])
}
/// gets the soundFie for specific case
static func getSoundFile(forSound:ConstantsSounds) -> String {
let indexOfBackSlash = forSound.rawValue.indexes(of: "/")
let soundNameRange = forSound.rawValue.index(after: indexOfBackSlash[0])..<forSound.rawValue.endIndex
return String(forSound.rawValue[soundNameRange])
}
}

View File

@ -0,0 +1,67 @@
/// supported languages for speak readings - defines name and language code, example "Dutch" and "nl-NL", both are defined in one case, seperated by a backslash
///
/// alphabetically ordered
enum ConstantsSpeakReadingLanguages: String, CaseIterable {
case chinese = "Chinese/zh"
case dutch = "Dutch/nl"
case english = "English/en"
case french = "French/fr"
case italian = "Italian/it"
case polish = "Polish/pl-PL"
case portugese_portugal = "Portuguese/pt"
case portugese_brasil = "Portuguese (Brazil)/pt-BR"
case russian = "Russian/ru"
case slovenian = "Slovenian/sl"
case spanish_mexico = "Spanish (Mexico)/es-MX"
case spanish_spain = "Spanish (Spain)/es-ES"
/// gets all language names and language codes in two arrays
/// - returns:
/// ie part of the case before the / in the first array, part of the case after the / in the second array
public static var allLanguageNamesAndCodes: (names:[String], codes:[String]) {
var languageNames = [String]()
var languageCodes = [String]()
languageloop: for speakReadingLanguage in ConstantsSpeakReadingLanguages.allCases {
// SpeakReadingLanguages defines available languages. Per case there is a string which is the language as shown in the UI and the language code, seperated by backslash
// get array of indexes, of location of "/"
let indexOfBackSlash = speakReadingLanguage.rawValue.indexes(of: "/")
// define range to get the language (as shown in UI)
let languageNameRange = speakReadingLanguage.rawValue.startIndex..<indexOfBackSlash[0]
// now get the language in a string
let language = String(speakReadingLanguage.rawValue[languageNameRange])
// add the soundName to the returnvalue
languageNames.append(language)
// define range to get the languagecode
let languageCodeRange = speakReadingLanguage.rawValue.index(after: indexOfBackSlash[0])..<speakReadingLanguage.rawValue.endIndex
// now get the language in a string
let languageCode = String(speakReadingLanguage.rawValue[languageCodeRange])
// add the languageCode to the returnvalue
languageCodes.append(languageCode)
}
return (languageNames, languageCodes)
}
/// gets the language name for specific case
static func languageName(forLanguageCode:String?) -> String {
if let forLanguageCode = forLanguageCode {
for (index, languageCode) in allLanguageNamesAndCodes.codes.enumerated() {
if languageCode == forLanguageCode {
return allLanguageNamesAndCodes.names[index]
}
}
}
return Texts_SpeakReading.defaultLanguageCode
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,6 +16,8 @@ extension CBManagerState {
return "unknown"
case .unsupported:
return "unsupported"
@unknown default:
return "unknown state"
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -36,3 +36,5 @@
"settingsviews_speakreadingslanguageselection" = "Select Language";
"settingsviews_speakBgReadingslanguage" = "Language";
"settingsviews_resettransmitter" = "Reset Transmitter";
"settingsviews_Version" = "Version";
"settingsviews_license" = "License";

View File

@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2.3.1</string>
<string>2.4.2</string>
<key>CFBundleVersion</key>
<string>2.3.1</string>
<string>2.4.2</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSBluetoothPeripheralUsageDescription</key>

View File

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

View File

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

View File

@ -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..<indexOfHyphen[0]
@ -64,7 +64,7 @@ public static func setLanguageCode(code:String?) {
}
// set languageName
languageName = Constants.SpeakReadingLanguages.languageName(forLanguageCode: languageCode)
languageName = ConstantsSpeakReadingLanguages.languageName(forLanguageCode: languageCode)
}

View File

@ -19,7 +19,7 @@ final class CGMG4xDripTransmitter: BluetoothTransmitter, BluetoothTransmitterDel
private(set) weak var cgmTransmitterDelegate:CGMTransmitterDelegate?
/// for OS_log
private let log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryCGMxDripG4)
private let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryCGMxDripG4)
/// transmitterId
private let transmitterId:String
@ -108,7 +108,7 @@ final class CGMG4xDripTransmitter: BluetoothTransmitter, BluetoothTransmitterDel
}
// Data packet Acknowledgement, to put wixel to sleep
_ = writeDataToPeripheral(data: Data(bytes: [0x02,0xF0]), type: .withoutResponse)
_ = writeDataToPeripheral(data: Data([0x02,0xF0]), type: .withoutResponse)
if let glucoseData = result.glucoseData {
var glucoseDataArray = [glucoseData]
@ -116,7 +116,7 @@ final class CGMG4xDripTransmitter: BluetoothTransmitter, BluetoothTransmitterDel
if let level = result.batteryLevel {
transmitterBatteryInfo = TransmitterBatteryInfo.DexcomG4(level: level)
}
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: transmitterBatteryInfo, sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, serialNumber: nil, bootloader: nil)
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: transmitterBatteryInfo, sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil)
}
case .beaconPacket?:
os_log(" in peripheral didUpdateValueFor, received beaconPacket", log: log, type: .info)
@ -149,7 +149,7 @@ final class CGMG4xDripTransmitter: BluetoothTransmitter, BluetoothTransmitterDel
if let batteryLevel = result.batteryLevel {
transmitterBatteryInfo = TransmitterBatteryInfo.DexcomG4(level: batteryLevel)
}
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: transmitterBatteryInfo, sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, serialNumber: nil, bootloader: nil)
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: transmitterBatteryInfo, sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil)
}
}
}

View File

@ -6,8 +6,10 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
// MARK: - properties
/// scaling factor, for DexcomG5 this will be 1, for DexcomG6 it will be 34
var scalingFactor = 1.0
/// G5 or G6 transmitter firmware version - only used internally, if nil then it was never received
///
/// created public because inheriting classes need it
var firmwareVersion:String?
// MARK: UUID's
@ -78,14 +80,11 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
private(set) weak var cgmTransmitterDelegate:CGMTransmitterDelegate?
/// for OS_log
private let log = OSLog(subsystem: Constants.Log.subSystem, category: Constants.Log.categoryCGMG5)
private let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryCGMG5)
/// is G5 reset necessary or not
private var G5ResetRequested:Bool
// G5 transmitter firmware version - only used internally, if nil then it was never received
private var transmitterVersion:String?
// actual device address
private var actualDeviceAddress:String?
@ -165,12 +164,26 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
let testdata = RawGlucoseData(timeStamp: Date(), glucoseLevelRaw: testAmount, glucoseLevelFiltered: testAmount)
debuglogging("timestamp testdata = " + testdata.timeStamp.description + ", with amount = " + testAmount.description)
var testdataasarray = [testdata]
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &testdataasarray, transmitterBatteryInfo: nil, sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, serialNumber: nil, bootloader: nil)
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &testdataasarray, transmitterBatteryInfo: nil, sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil)
testAmount = testAmount + 1
}
// MARK: public functions
/// scale the rawValue, dependent on transmitter version G5 , G6 --
/// for G6, there's two possible scaling factors, depending on the firmware version. For G5 there's only one, firmware version independent
/// - parameters:
/// - firmwareVersion : for G6, the scaling factor is firmware dependent. Parameter created optional although it is known at the moment the function is used
/// - the value to be scaled
/// this function can be override in CGMG6Transmitter, which can then return the scalingFactor , firmware dependent
func scaleRawValue(firmwareVersion: String?, rawValue: Double) -> Double {
// for G5, the scaling is independent of the firmwareVersion
// and there's no scaling to do
return rawValue
}
// MARK: CBCentralManager overriden functions
override func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
@ -401,10 +414,10 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
if let sensorDataRxMessage = SensorDataRxMessage(data: value) {
if transmitterVersion != nil {
if firmwareVersion != nil {
// transmitterversion was already recceived, let's see if we need to get the batterystatus
if Date() > Date(timeInterval: Constants.DexcomG5.batteryReadPeriodInHours * 60 * 60, since: timeStampOfLastBatteryReading) {
if Date() > Date(timeInterval: ConstantsDexcomG5.batteryReadPeriodInHours * 60 * 60, since: timeStampOfLastBatteryReading) {
os_log(" last battery reading was long time ago, requesting now", log: log, type: .info)
if let writeControlCharacteristic = writeControlCharacteristic {
_ = writeDataToPeripheral(data: BatteryStatusTxMessage().data, characteristicToWriteTo: writeControlCharacteristic, type: .withResponse)
@ -435,9 +448,12 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
os_log(" last reading was less than 1 minute ago, ignoring", log: log, type: .info)
} else {
timeStampOfLastG5Reading = Date()
let glucoseData = RawGlucoseData(timeStamp: sensorDataRxMessage.timestamp, glucoseLevelRaw: sensorDataRxMessage.unfiltered * scalingFactor, glucoseLevelFiltered: sensorDataRxMessage.filtered * scalingFactor)
let glucoseData = RawGlucoseData(timeStamp: sensorDataRxMessage.timestamp, glucoseLevelRaw: scaleRawValue(firmwareVersion: firmwareVersion, rawValue: sensorDataRxMessage.unfiltered), glucoseLevelFiltered: scaleRawValue(firmwareVersion: firmwareVersion, rawValue: sensorDataRxMessage.unfiltered))
var glucoseDataArray = [glucoseData]
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: nil, sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, serialNumber: nil, bootloader: nil)
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &glucoseDataArray, transmitterBatteryInfo: nil, sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil)
}
}
@ -538,7 +554,7 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
private func processBatteryStatusRxMessage(value:Data) {
if let batteryStatusRxMessage = BatteryStatusRxMessage(data: value) {
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.DexcomG5(voltageA: batteryStatusRxMessage.voltageA, voltageB: batteryStatusRxMessage.voltageB, resist: batteryStatusRxMessage.resist, runtime: batteryStatusRxMessage.runtime, temperature: batteryStatusRxMessage.temperature), sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, serialNumber: nil, bootloader: nil)
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: TransmitterBatteryInfo.DexcomG5(voltageA: batteryStatusRxMessage.voltageA, voltageB: batteryStatusRxMessage.voltageB, resist: batteryStatusRxMessage.resist, runtime: batteryStatusRxMessage.runtime, temperature: batteryStatusRxMessage.temperature), sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil)
} else {
os_log("batteryStatusRxMessage is nil", log: log, type: .error)
}
@ -546,9 +562,12 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
private func processTransmitterVersionRxMessage(value:Data) {
if let transmitterVersionRxMessage = TransmitterVersionRxMessage(data: value) {
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: nil, sensorState: nil, sensorTimeInMinutes: nil, firmware: transmitterVersionRxMessage.firmwareVersion.hexEncodedString(), hardware: nil, serialNumber: nil, bootloader: nil)
cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: nil, sensorState: nil, sensorTimeInMinutes: nil, firmware: transmitterVersionRxMessage.firmwareVersion.hexEncodedString(), hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil)
// assign transmitterVersion
transmitterVersion = transmitterVersionRxMessage.firmwareVersion.hexEncodedString()
firmwareVersion = transmitterVersionRxMessage.firmwareVersion.hexEncodedString()
} else {
os_log("transmitterVersionRxMessage is nil", log: log, type: .error)
}
@ -596,7 +615,7 @@ class CGMG5Transmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTr
if let receiveAuthenticationCharacteristic = receiveAuthenticationCharacteristic {
// to make sure the Dexcom doesn't disconnect the next 60 seconds, this gives the user sufficient time to accept the pairing request, which will come next
_ = writeDataToPeripheral(data: KeepAliveTxMessage(time: UInt8(Constants.DexcomG5.maxTimeToAcceptPairingInSeconds)).data, characteristicToWriteTo: receiveAuthenticationCharacteristic, type: .withResponse)
_ = writeDataToPeripheral(data: KeepAliveTxMessage(time: UInt8(ConstantsDexcomG5.maxTimeToAcceptPairingInSeconds)).data, characteristicToWriteTo: receiveAuthenticationCharacteristic, type: .withResponse)
} else {
os_log(" in sendKeepAliveMessage, receiveAuthenticationCharacteristic is nil, can not send KeepAliveTxMessage", log: log, type: .error)

View File

@ -16,7 +16,7 @@ struct AuthRequestTxMessage: TransmitterTxMessage {
init() {
let uuid = UUID().uuid
singleUseToken = Data(bytes: [uuid.0, uuid.1, uuid.2, uuid.3,
singleUseToken = Data([uuid.0, uuid.1, uuid.2, uuid.3,
uuid.4, uuid.5, uuid.6, uuid.7])
}

View File

@ -10,7 +10,7 @@ struct BatteryStatusRxMessage: TransmitterRxMessage {
let temperature:Int
init?(data: Data) {
guard data.count >= 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))
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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..<block.count])
// if rxBuffer has reached minimum lenght, then start processing
if rxBuffer.count >= 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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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