Просмотр исходного кода

New version of the Loop 3 integration in particular about CGM :
- Integration of the UI for dexcom in "Configuration CGM"
- Refactor the CGM implementation in FAX
- Rollback to use again the initial FAX heartbeat with some improvements. Resolve the CGM Readings

Pierre L 3 лет назад
Родитель
Сommit
9715e1295a
25 измененных файлов с 840 добавлено и 460 удалено
  1. 79 57
      Dependencies/CGMBLEKit/CGMBLEKit/OSLog.swift
  2. 86 59
      Dependencies/G7SensorKit/G7SensorKit/OSLog.swift
  3. 80 57
      Dependencies/LoopKit/Extensions/OSLog.swift
  4. 80 57
      Dependencies/OmniBLE/Common/OSLog.swift
  5. 80 58
      Dependencies/rileylink_ios/Common/OSLog.swift
  6. 26 0
      FreeAPS.xcodeproj/project.pbxproj
  7. 6 4
      FreeAPS/Sources/APS/APSManager.swift
  8. 3 0
      FreeAPS/Sources/APS/CGM/AppGroupSource.swift
  9. 29 18
      FreeAPS/Sources/APS/CGM/DexcomSourceG5.swift
  10. 36 18
      FreeAPS/Sources/APS/CGM/DexcomSourceG6.swift
  11. 3 0
      FreeAPS/Sources/APS/CGM/GlucoseSimulatorSource.swift
  12. 4 0
      FreeAPS/Sources/APS/CGM/GlucoseSource.swift
  13. 11 10
      FreeAPS/Sources/APS/CGM/LibreTransmitterSource.swift
  14. 19 23
      FreeAPS/Sources/APS/CGM/dexcomSourceG7.swift
  15. 7 4
      FreeAPS/Sources/APS/DeviceDataManager.swift
  16. 45 33
      FreeAPS/Sources/APS/FetchGlucoseManager.swift
  17. 3 3
      FreeAPS/Sources/Logger/Logger.swift
  18. 4 0
      FreeAPS/Sources/Modules/CGM/CGMDataFlow.swift
  19. 3 1
      FreeAPS/Sources/Modules/CGM/CGMProvider.swift
  20. 35 4
      FreeAPS/Sources/Modules/CGM/CGMStateModel.swift
  21. 73 54
      FreeAPS/Sources/Modules/CGM/View/CGMRootView.swift
  22. 42 0
      FreeAPS/Sources/Modules/CGM/View/CGMSettingsView.swift
  23. 80 0
      FreeAPS/Sources/Modules/CGM/View/CGMSetupView.swift
  24. 3 0
      FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift
  25. 3 0
      FreeAPS/Sources/Services/Network/NightscoutManager.swift

+ 79 - 57
Dependencies/CGMBLEKit/CGMBLEKit/OSLog.swift

@@ -15,6 +15,7 @@
 import os.log
 import Foundation
 
+let storeLoopLog: Bool = false
 
 let loggerLock = NSRecursiveLock()
 let baseReporter: IssueReporter = SimpleLogReporter()
@@ -49,76 +50,97 @@ extension OSLog {
     }
 
     func debug(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-            loggerLock.perform {
-                category.logger.debug(
-                    {msg}(),
-                    printToConsole: true,
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .debug, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+                loggerLock.perform {
+                    category.logger.debug(
+                        {msg}(),
+                        printToConsole: true,
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
     }
 
     func info(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-            loggerLock.perform {
-                category.logger.info(
-                    {msg}(),
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .info, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+                loggerLock.perform {
+                    category.logger.info(
+                        {msg}(),
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
+        
+        
     }
 
     func `default`(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-            loggerLock.perform {
-                category.logger.debug(
-                    {msg}(),
-                    printToConsole: true,
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .default, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+                loggerLock.perform {
+                    category.logger.debug(
+                        {msg}(),
+                        printToConsole: true,
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
+        
     }
 
     func error(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-           
-            loggerLock.perform {
-                category.logger.warning(
-                    {msg}(),
-                    description:  {msg}(),
-                    error: nil,
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .error, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+               
+                loggerLock.perform {
+                    category.logger.warning(
+                        {msg}(),
+                        description:  {msg}(),
+                        error: nil,
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
+        
     }
 
     private func log(_ message: StaticString, type: OSLogType, _ args: [CVarArg]) {

+ 86 - 59
Dependencies/G7SensorKit/G7SensorKit/OSLog.swift

@@ -1,4 +1,9 @@
-
+//
+//  OSLog.swift
+//  Loop
+//
+//  Copyright © 2017 LoopKit Authors. All rights reserved.
+//
 //
 //  OSLog.swift
 //  OmniBLE
@@ -10,6 +15,7 @@
 import os.log
 import Foundation
 
+let storeLoopLog: Bool = false
 
 let loggerLock = NSRecursiveLock()
 let baseReporter: IssueReporter = SimpleLogReporter()
@@ -40,80 +46,101 @@ extension NSLock {
 extension OSLog {
     
     convenience init(category: String) {
-        self.init(subsystem: "org.loopkit.G7SensorKit", category: category)
+        self.init(subsystem: "com.loopkit.G7SensorKit", category: category)
     }
 
     func debug(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-            loggerLock.perform {
-                category.logger.debug(
-                    {msg}(),
-                    printToConsole: true,
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .debug, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+                loggerLock.perform {
+                    category.logger.debug(
+                        {msg}(),
+                        printToConsole: true,
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
     }
 
     func info(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-            loggerLock.perform {
-                category.logger.info(
-                    {msg}(),
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .info, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+                loggerLock.perform {
+                    category.logger.info(
+                        {msg}(),
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
+        
+        
     }
 
     func `default`(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-            loggerLock.perform {
-                category.logger.debug(
-                    {msg}(),
-                    printToConsole: true,
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .default, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+                loggerLock.perform {
+                    category.logger.debug(
+                        {msg}(),
+                        printToConsole: true,
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
+        
     }
 
     func error(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-           
-            loggerLock.perform {
-                category.logger.warning(
-                    {msg}(),
-                    description: message.description,
-                    error: nil,
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .error, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+               
+                loggerLock.perform {
+                    category.logger.warning(
+                        {msg}(),
+                        description:  {msg}(),
+                        error: nil,
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
+        
     }
 
     private func log(_ message: StaticString, type: OSLogType, _ args: [CVarArg]) {

+ 80 - 57
Dependencies/LoopKit/Extensions/OSLog.swift

@@ -15,6 +15,8 @@
 import os.log
 import Foundation
 
+let storeLoopLog: Bool = false
+
 
 let loggerLock = NSRecursiveLock()
 let baseReporter: IssueReporter = SimpleLogReporter()
@@ -49,76 +51,97 @@ extension OSLog {
     }
 
     func debug(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-            loggerLock.perform {
-                category.logger.debug(
-                    msg,
-                    printToConsole: true,
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .debug, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+                loggerLock.perform {
+                    category.logger.debug(
+                        {msg}(),
+                        printToConsole: true,
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
     }
 
     func info(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-            loggerLock.perform {
-                category.logger.info(
-                    msg,
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .info, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+                loggerLock.perform {
+                    category.logger.info(
+                        {msg}(),
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
+        
+        
     }
 
     func `default`(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-            loggerLock.perform {
-                category.logger.debug(
-                    msg,
-                    printToConsole: true,
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .default, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+                loggerLock.perform {
+                    category.logger.debug(
+                        {msg}(),
+                        printToConsole: true,
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
+        
     }
 
     func error(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-           
-            loggerLock.perform {
-                category.logger.warning(
-                    msg,
-                    description: message.debugDescription,
-                    error: nil,
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .error, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+               
+                loggerLock.perform {
+                    category.logger.warning(
+                        {msg}(),
+                        description:  {msg}(),
+                        error: nil,
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
+        
     }
 
     private func log(_ message: StaticString, type: OSLogType, _ args: [CVarArg]) {

+ 80 - 57
Dependencies/OmniBLE/Common/OSLog.swift

@@ -9,6 +9,8 @@
 import os.log
 import Foundation
 
+let storeLoopLog: Bool = false
+
 
 let loggerLock = NSRecursiveLock()
 let baseReporter: IssueReporter = SimpleLogReporter()
@@ -43,76 +45,97 @@ extension OSLog {
     }
 
     func debug(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-            loggerLock.perform {
-                category.logger.debug(
-                    {msg}(),
-                    printToConsole: true,
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .debug, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+                loggerLock.perform {
+                    category.logger.debug(
+                        {msg}(),
+                        printToConsole: true,
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
     }
 
     func info(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-            loggerLock.perform {
-                category.logger.info(
-                    {msg}(),
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .info, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+                loggerLock.perform {
+                    category.logger.info(
+                        {msg}(),
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
+        
+        
     }
 
     func `default`(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-            loggerLock.perform {
-                category.logger.debug(
-                    {msg}(),
-                    printToConsole: true,
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .default, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+                loggerLock.perform {
+                    category.logger.debug(
+                        {msg}(),
+                        printToConsole: true,
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
+        
     }
 
     func error(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-           
-            loggerLock.perform {
-                category.logger.warning(
-                    {msg}(),
-                    description: message.description,
-                    error: nil,
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .error, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+               
+                loggerLock.perform {
+                    category.logger.warning(
+                        {msg}(),
+                        description:  {msg}(),
+                        error: nil,
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
+        
     }
 
     private func log(_ message: StaticString, type: OSLogType, _ args: [CVarArg]) {

+ 80 - 58
Dependencies/rileylink_ios/Common/OSLog.swift

@@ -15,6 +15,7 @@
 import os.log
 import Foundation
 
+let storeLoopLog: Bool = false
 
 let loggerLock = NSRecursiveLock()
 let baseReporter: IssueReporter = SimpleLogReporter()
@@ -45,80 +46,101 @@ extension NSLock {
 extension OSLog {
     
     convenience init(category: String) {
-        self.init(subsystem: "com.ps2.rileylink", category: category)
+        self.init(subsystem: "com.loopkit.RileyLink", category: category)
     }
 
     func debug(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-            loggerLock.perform {
-                category.logger.debug(
-                    msg,
-                    printToConsole: true,
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .debug, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+                loggerLock.perform {
+                    category.logger.debug(
+                        {msg}(),
+                        printToConsole: true,
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
     }
 
     func info(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-            loggerLock.perform {
-                category.logger.info(
-                    msg,
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .info, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+                loggerLock.perform {
+                    category.logger.info(
+                        {msg}(),
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
+        
+        
     }
 
     func `default`(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-            loggerLock.perform {
-                category.logger.debug(
-                    msg,
-                    printToConsole: true,
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .default, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+                loggerLock.perform {
+                    category.logger.debug(
+                        {msg}(),
+                        printToConsole: true,
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
+        
     }
 
     func error(_ message: StaticString, _ args: CVarArg...) {
-        let msg_format = message.withUTF8Buffer{
-            String(decoding: $0, as: UTF8.self)
-        }
-        let msg = String(format: msg_format, args)
-        DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
-           
-            loggerLock.perform {
-                category.logger.warning(
-                    msg,
-                    description: message.debugDescription,
-                    error: nil,
-                    file: #file,
-                    function: #function,
-                    line: #line
-                )
+        if (!storeLoopLog) {
+            log(message, type: .error, args)
+        } else {
+            let msg_format = message.withUTF8Buffer{
+                String(decoding: $0, as: UTF8.self)
             }
-        }.perform()
+            let msg = String(format: msg_format.replacingOccurrences(of: "{public}", with: ""), args)
+            DispatchWorkItem(qos: .background, flags: .enforceQoS) {
+               
+                loggerLock.perform {
+                    category.logger.warning(
+                        {msg}(),
+                        description:  {msg}(),
+                        error: nil,
+                        file: #file,
+                        function: #function,
+                        line: #line
+                    )
+                }
+            }.perform()
+        }
+        
     }
 
     private func log(_ message: StaticString, type: OSLogType, _ args: [CVarArg]) {

+ 26 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -296,6 +296,14 @@
 		CE48C86428CA69D5007C0598 /* OmniBLEPumpManagerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE48C86328CA69D5007C0598 /* OmniBLEPumpManagerExtensions.swift */; };
 		CE48C86628CA6B48007C0598 /* OmniPodManagerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE48C86528CA6B48007C0598 /* OmniPodManagerExtensions.swift */; };
 		CE6B025728F350FF000C5502 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE6B025628F350FF000C5502 /* HealthKit.framework */; };
+		CE7950242997D81700FA576E /* CGMSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7950232997D81700FA576E /* CGMSettingsView.swift */; };
+		CE7950262998056D00FA576E /* CGMSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7950252998056D00FA576E /* CGMSetupView.swift */; };
+		CE79502829980C9600FA576E /* CGMBLEKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE79502729980C9600FA576E /* CGMBLEKitUI.framework */; };
+		CE79502A29980C9F00FA576E /* G7SensorKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE79502929980C9F00FA576E /* G7SensorKitUI.framework */; };
+		CE79502B29980CAF00FA576E /* CGMBLEKitUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE79502729980C9600FA576E /* CGMBLEKitUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+		CE79502C29980CB500FA576E /* G7SensorKitUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE79502929980C9F00FA576E /* G7SensorKitUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+		CE79502E29980E4D00FA576E /* ShareClientUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE79502D29980E4D00FA576E /* ShareClientUI.framework */; };
+		CE79502F29980E5800FA576E /* ShareClientUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE79502D29980E4D00FA576E /* ShareClientUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		CE82E02528E867BA00473A9C /* AlertStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE82E02428E867BA00473A9C /* AlertStorage.swift */; };
 		CE82E02728E869DF00473A9C /* AlertEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE82E02628E869DF00473A9C /* AlertEntry.swift */; };
 		CEB434DC28B8F5B900B70274 /* MKRingProgressView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEB434DB28B8F5B900B70274 /* MKRingProgressView.framework */; };
@@ -378,6 +386,9 @@
 			dstPath = "";
 			dstSubfolderSpec = 10;
 			files = (
+				CE79502F29980E5800FA576E /* ShareClientUI.framework in Embed Frameworks */,
+				CE79502C29980CB500FA576E /* G7SensorKitUI.framework in Embed Frameworks */,
+				CE79502B29980CAF00FA576E /* CGMBLEKitUI.framework in Embed Frameworks */,
 				CE2FAD38297D69E1001A872C /* ShareClient.framework in Embed Frameworks */,
 				CE398D19297C9EFD00DF218F /* G7SensorKit.framework in Embed Frameworks */,
 				3818AA6F274C26A500843DB3 /* RileyLinkKitUI.framework in Embed Frameworks */,
@@ -742,6 +753,11 @@
 		CE48C86328CA69D5007C0598 /* OmniBLEPumpManagerExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmniBLEPumpManagerExtensions.swift; sourceTree = "<group>"; };
 		CE48C86528CA6B48007C0598 /* OmniPodManagerExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmniPodManagerExtensions.swift; sourceTree = "<group>"; };
 		CE6B025628F350FF000C5502 /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS9.1.sdk/System/Library/Frameworks/HealthKit.framework; sourceTree = DEVELOPER_DIR; };
+		CE7950232997D81700FA576E /* CGMSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGMSettingsView.swift; sourceTree = "<group>"; };
+		CE7950252998056D00FA576E /* CGMSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGMSetupView.swift; sourceTree = "<group>"; };
+		CE79502729980C9600FA576E /* CGMBLEKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CGMBLEKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		CE79502929980C9F00FA576E /* G7SensorKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = G7SensorKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		CE79502D29980E4D00FA576E /* ShareClientUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ShareClientUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		CE82E02428E867BA00473A9C /* AlertStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertStorage.swift; sourceTree = "<group>"; };
 		CE82E02628E869DF00473A9C /* AlertEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertEntry.swift; sourceTree = "<group>"; };
 		CEB434DB28B8F5B900B70274 /* MKRingProgressView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MKRingProgressView.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -790,6 +806,9 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				CE79502E29980E4D00FA576E /* ShareClientUI.framework in Frameworks */,
+				CE79502A29980C9F00FA576E /* G7SensorKitUI.framework in Frameworks */,
+				CE79502829980C9600FA576E /* CGMBLEKitUI.framework in Frameworks */,
 				CEB434DC28B8F5B900B70274 /* MKRingProgressView.framework in Frameworks */,
 				38E87403274F78C000975559 /* libswiftCoreNFC.tbd in Frameworks */,
 				38E87401274F77E400975559 /* CoreNFC.framework in Frameworks */,
@@ -862,6 +881,8 @@
 			isa = PBXGroup;
 			children = (
 				38569352270B5E350002C50D /* CGMRootView.swift */,
+				CE7950232997D81700FA576E /* CGMSettingsView.swift */,
+				CE7950252998056D00FA576E /* CGMSetupView.swift */,
 			);
 			path = View;
 			sourceTree = "<group>";
@@ -1194,6 +1215,9 @@
 		3818AA48274C267000843DB3 /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				CE79502D29980E4D00FA576E /* ShareClientUI.framework */,
+				CE79502929980C9F00FA576E /* G7SensorKitUI.framework */,
+				CE79502729980C9600FA576E /* CGMBLEKitUI.framework */,
 				CE398D1A297D69A900DF218F /* ShareClient.framework */,
 				CE398D17297C9EE800DF218F /* G7SensorKit.framework */,
 				CE398D012977349800DF218F /* CryptoKit.framework */,
@@ -2271,6 +2295,7 @@
 				38A13D3225E28B4B00EAA382 /* PumpHistoryEvent.swift in Sources */,
 				E00EEC0627368630002FF094 /* UIAssembly.swift in Sources */,
 				3811DE1825C9D40400A708ED /* Router.swift in Sources */,
+				CE7950262998056D00FA576E /* CGMSetupView.swift in Sources */,
 				38A0363B25ECF07E00FCBB52 /* GlucoseStorage.swift in Sources */,
 				38E98A2725F52C9300C0CED0 /* CollectionIssueReporter.swift in Sources */,
 				E00EEC0427368630002FF094 /* SecurityAssembly.swift in Sources */,
@@ -2303,6 +2328,7 @@
 				3811DEAC25C9D88300A708ED /* NightscoutManager.swift in Sources */,
 				CEB434E528B8FF5D00B70274 /* UIColor.swift in Sources */,
 				3811DEA925C9D88300A708ED /* AppearanceManager.swift in Sources */,
+				CE7950242997D81700FA576E /* CGMSettingsView.swift in Sources */,
 				38D0B3D925EC07C400CB6E88 /* CarbsEntry.swift in Sources */,
 				38A9260525F012D8009E3739 /* CarbRatios.swift in Sources */,
 				38FCF3D625E8FDF40078B0D1 /* MD5.swift in Sources */,

+ 6 - 4
FreeAPS/Sources/APS/APSManager.swift

@@ -176,10 +176,12 @@ final class BaseAPSManager: APSManager, Injectable {
 
     // Loop entry point
     private func loop() {
-        // check the last start of looping is more the loopInterval
-        guard lastStartLoopDate.addingTimeInterval(Config.loopInterval) < Date() else {
-            debug(.apsManager, "too close to do a loop : \(lastStartLoopDate)")
-            return
+        // check the last start of looping is more the loopInterval but the previous loop was completed
+        if lastLoopDate > lastStartLoopDate {
+            guard lastStartLoopDate.addingTimeInterval(Config.loopInterval) < Date() else {
+                debug(.apsManager, "too close to do a loop : \(lastStartLoopDate)")
+                return
+            }
         }
 
         guard !isLooping.value else {

+ 3 - 0
FreeAPS/Sources/APS/CGM/AppGroupSource.swift

@@ -1,10 +1,13 @@
 import Combine
 import Foundation
 import LibreTransmitter
+import LoopKitUI
 
 struct AppGroupSource: GlucoseSource {
+    var cgmManager: CGMManagerUI?
     var glucoseManager: FetchGlucoseManager?
     let from: String
+    var cgmType: CGMType
 
     func fetch(_ heartbeat: DispatchTimer?) -> AnyPublisher<[BloodGlucose], Never> {
         guard let suiteName = Bundle.main.appGroupSuiteName,

+ 29 - 18
FreeAPS/Sources/APS/CGM/DexcomSourceG5.swift

@@ -10,7 +10,8 @@ final class DexcomSourceG5: GlucoseSource {
     private let glucoseStorage: GlucoseStorage!
     var glucoseManager: FetchGlucoseManager?
 
-    var cgmManager: G5CGMManager?
+    var cgmManager: CGMManagerUI?
+    var cgmType: CGMType = .dexcomG5
 
     var cgmHasValidSensorSession: Bool = false
 
@@ -20,17 +21,26 @@ final class DexcomSourceG5: GlucoseSource {
         self.glucoseStorage = glucoseStorage
         self.glucoseManager = glucoseManager
         cgmManager = G5CGMManager
-            .init(state: TransmitterManagerState(transmitterID: UserDefaults.standard.dexcomTransmitterID ?? "000000"))
+            .init(state: TransmitterManagerState(
+                transmitterID: UserDefaults.standard
+                    .dexcomTransmitterID ?? "000000"
+            )) as? CGMManagerUI
         cgmManager?.cgmManagerDelegate = self
     }
 
     var transmitterID: String {
-        cgmManager?.transmitter.ID ?? "000000"
+        guard let cgmG5Manager = cgmManager as? G5CGMManager else { return "000000" }
+        return cgmG5Manager.transmitter.ID
     }
 
     func fetch(_: DispatchTimer?) -> AnyPublisher<[BloodGlucose], Never> {
-        // dexcomManager.transmitter.resumeScanning()
-        Just([]).eraseToAnyPublisher()
+        Future<[BloodGlucose], Error> { [weak self] promise in
+            self?.promise = promise
+        }
+        .timeout(60 * 5, scheduler: processQueue, options: nil, customError: nil)
+        .replaceError(with: [])
+        .replaceEmpty(with: [])
+        .eraseToAnyPublisher()
     }
 
     func fetchIfNeeded() -> AnyPublisher<[BloodGlucose], Never> {
@@ -38,7 +48,7 @@ final class DexcomSourceG5: GlucoseSource {
             self.processQueue.async {
                 guard let cgmManager = self.cgmManager else { return }
                 cgmManager.fetchNewDataIfNeeded { result in
-                    self.processCGMReadingResult(cgmManager, readingResult: result, tickBLE: false) {
+                    self.processCGMReadingResult(cgmManager, readingResult: result) {
                         // nothing to do
                     }
                 }
@@ -58,11 +68,13 @@ final class DexcomSourceG5: GlucoseSource {
 extension DexcomSourceG5: CGMManagerDelegate {
     func deviceManager(
         _: LoopKit.DeviceManager,
-        logEventForDeviceIdentifier _: String?,
+        logEventForDeviceIdentifier deviceIdentifier: String?,
         type _: LoopKit.DeviceLogEntryType,
-        message _: String,
+        message: String,
         completion _: ((Error?) -> Void)?
-    ) {}
+    ) {
+        debug(.deviceManager, "device Manager for \(String(describing: deviceIdentifier)) : \(message)")
+    }
 
     func issueAlert(_: LoopKit.Alert) {}
 
@@ -82,12 +94,16 @@ extension DexcomSourceG5: CGMManagerDelegate {
 
     func recordRetractedAlert(_: LoopKit.Alert, at _: Date) {}
 
-    func cgmManagerWantsDeletion(_: CGMManager) {}
+    func cgmManagerWantsDeletion(_ manager: CGMManager) {
+        dispatchPrecondition(condition: .onQueue(.main))
+        debug(.deviceManager, " CGM Manager with identifier \(manager.managerIdentifier) wants deletion")
+        glucoseManager?.cgmGlucoseSourceType = nil
+    }
 
     func cgmManager(_ manager: CGMManager, hasNew readingResult: CGMReadingResult) {
         dispatchPrecondition(condition: .onQueue(.main))
-        processCGMReadingResult(manager, readingResult: readingResult, tickBLE: true) {
-            debug(.deviceManager, "DEXCOM - Direct return")
+        processCGMReadingResult(manager, readingResult: readingResult) {
+            debug(.deviceManager, "DEXCOM - Direct return done")
         }
     }
 
@@ -115,7 +131,6 @@ extension DexcomSourceG5: CGMManagerDelegate {
     private func processCGMReadingResult(
         _: CGMManager,
         readingResult: CGMReadingResult,
-        tickBLE: Bool,
         completion: @escaping () -> Void
     ) {
         debug(.deviceManager, "DEXCOM - Process CGM Reading Result launched")
@@ -138,11 +153,7 @@ extension DexcomSourceG5: CGMManagerDelegate {
                     transmitterID: self.transmitterID
                 )
             }
-            if tickBLE {
-                glucoseManager?.updateGlucoseStore(newBloodGlucose: bloodGlucose)
-            } else {
-                promise?(.success(bloodGlucose))
-            }
+            promise?(.success(bloodGlucose))
             completion()
         case .unreliableData:
             // loopManager.receivedUnreliableCGMReading()

+ 36 - 18
FreeAPS/Sources/APS/CGM/DexcomSourceG6.swift

@@ -10,7 +10,8 @@ final class DexcomSourceG6: GlucoseSource {
     private let glucoseStorage: GlucoseStorage!
     var glucoseManager: FetchGlucoseManager?
 
-    var cgmManager: G6CGMManager?
+    var cgmManager: CGMManagerUI?
+    var cgmType: CGMType = .dexcomG6
 
     var cgmHasValidSensorSession: Bool = false
 
@@ -20,16 +21,26 @@ final class DexcomSourceG6: GlucoseSource {
         self.glucoseStorage = glucoseStorage
         self.glucoseManager = glucoseManager
         cgmManager = G6CGMManager
-            .init(state: TransmitterManagerState(transmitterID: UserDefaults.standard.dexcomTransmitterID ?? "000000"))
+            .init(state: TransmitterManagerState(
+                transmitterID: UserDefaults.standard
+                    .dexcomTransmitterID ?? "000000"
+            ))
         cgmManager?.cgmManagerDelegate = self
     }
 
     var transmitterID: String {
-        cgmManager?.transmitter.ID ?? "000000"
+        guard let cgmG6Manager = cgmManager as? G6CGMManager else { return "000000" }
+        return cgmG6Manager.transmitter.ID
     }
 
     func fetch(_: DispatchTimer?) -> AnyPublisher<[BloodGlucose], Never> {
-        fetchIfNeeded()
+        Future<[BloodGlucose], Error> { [weak self] promise in
+            self?.promise = promise
+        }
+        .timeout(60 * 5, scheduler: processQueue, options: nil, customError: nil)
+        .replaceError(with: [])
+        .replaceEmpty(with: [])
+        .eraseToAnyPublisher()
     }
 
     func fetchIfNeeded() -> AnyPublisher<[BloodGlucose], Never> {
@@ -37,7 +48,7 @@ final class DexcomSourceG6: GlucoseSource {
             self.processQueue.async {
                 guard let cgmManager = self.cgmManager else { return }
                 cgmManager.fetchNewDataIfNeeded { result in
-                    self.processCGMReadingResult(cgmManager, readingResult: result, tickBLE: false) {
+                    self.processCGMReadingResult(cgmManager, readingResult: result) {
                         // nothing to do
                     }
                 }
@@ -57,11 +68,13 @@ final class DexcomSourceG6: GlucoseSource {
 extension DexcomSourceG6: CGMManagerDelegate {
     func deviceManager(
         _: LoopKit.DeviceManager,
-        logEventForDeviceIdentifier _: String?,
+        logEventForDeviceIdentifier deviceIdentifier: String?,
         type _: LoopKit.DeviceLogEntryType,
-        message _: String,
+        message: String,
         completion _: ((Error?) -> Void)?
-    ) {}
+    ) {
+        debug(.deviceManager, "device Manager for \(String(describing: deviceIdentifier)) : \(message)")
+    }
 
     func issueAlert(_: LoopKit.Alert) {}
 
@@ -81,12 +94,16 @@ extension DexcomSourceG6: CGMManagerDelegate {
 
     func recordRetractedAlert(_: LoopKit.Alert, at _: Date) {}
 
-    func cgmManagerWantsDeletion(_: CGMManager) {}
+    func cgmManagerWantsDeletion(_ manager: CGMManager) {
+        dispatchPrecondition(condition: .onQueue(.main))
+        debug(.deviceManager, " CGM Manager with identifier \(manager.managerIdentifier) wants deletion")
+        glucoseManager?.cgmGlucoseSourceType = nil
+    }
 
     func cgmManager(_ manager: CGMManager, hasNew readingResult: CGMReadingResult) {
         dispatchPrecondition(condition: .onQueue(.main))
-        processCGMReadingResult(manager, readingResult: readingResult, tickBLE: true) {
-            debug(.deviceManager, "DEXCOM - Direct return")
+        processCGMReadingResult(manager, readingResult: readingResult) {
+            debug(.deviceManager, "DEXCOM - Direct return done")
         }
     }
 
@@ -96,7 +113,13 @@ extension DexcomSourceG6: CGMManagerDelegate {
         //  return glucoseStore.latestGlucose?.startDate
     }
 
-    func cgmManagerDidUpdateState(_: CGMManager) {}
+    func cgmManagerDidUpdateState(_ manager: CGMManager) {
+        dispatchPrecondition(condition: .onQueue(.main))
+        guard let g6Manager = manager as? TransmitterManager else {
+            return
+        }
+        UserDefaults.standard.dexcomTransmitterID = g6Manager.rawState["transmitterID"] as? String
+    }
 
     func credentialStoragePrefix(for _: CGMManager) -> String {
         // return string unique to this instance of the CGMManager
@@ -114,7 +137,6 @@ extension DexcomSourceG6: CGMManagerDelegate {
     private func processCGMReadingResult(
         _: CGMManager,
         readingResult: CGMReadingResult,
-        tickBLE: Bool,
         completion: @escaping () -> Void
     ) {
         debug(.deviceManager, "DEXCOM - Process CGM Reading Result launched")
@@ -137,11 +159,7 @@ extension DexcomSourceG6: CGMManagerDelegate {
                     transmitterID: self.transmitterID
                 )
             }
-            if tickBLE {
-                glucoseManager?.updateGlucoseStore(newBloodGlucose: bloodGlucose)
-            } else {
-                promise?(.success(bloodGlucose))
-            }
+            promise?(.success(bloodGlucose))
             completion()
         case .unreliableData:
             // loopManager.receivedUnreliableCGMReading()

+ 3 - 0
FreeAPS/Sources/APS/CGM/GlucoseSimulatorSource.swift

@@ -22,11 +22,14 @@
 
 import Combine
 import Foundation
+import LoopKitUI
 
 // MARK: - Glucose simulator
 
 final class GlucoseSimulatorSource: GlucoseSource {
+    var cgmManager: CGMManagerUI?
     var glucoseManager: FetchGlucoseManager?
+    var cgmType: CGMType = .simulator
 
     private enum Config {
         // min time period to publish data

+ 4 - 0
FreeAPS/Sources/APS/CGM/GlucoseSource.swift

@@ -1,4 +1,6 @@
 import Combine
+import LoopKit
+import LoopKitUI
 
 protocol SourceInfoProvider {
     func sourceInfo() -> [String: Any]?
@@ -8,6 +10,8 @@ protocol GlucoseSource: SourceInfoProvider {
     func fetch(_ heartbeat: DispatchTimer?) -> AnyPublisher<[BloodGlucose], Never>
     func fetchIfNeeded() -> AnyPublisher<[BloodGlucose], Never>
     var glucoseManager: FetchGlucoseManager? { get set }
+    var cgmManager: CGMManagerUI? { get set }
+    var cgmType: CGMType { get }
 }
 
 extension GlucoseSource {

+ 11 - 10
FreeAPS/Sources/APS/CGM/LibreTransmitterSource.swift

@@ -1,6 +1,7 @@
 import Combine
 import Foundation
 import LibreTransmitter
+import LoopKitUI
 import Swinject
 
 protocol LibreTransmitterSource: GlucoseSource {
@@ -8,6 +9,9 @@ protocol LibreTransmitterSource: GlucoseSource {
 }
 
 final class BaseLibreTransmitterSource: LibreTransmitterSource, Injectable {
+    var cgmManager: CGMManagerUI?
+    var cgmType: CGMType = .libreTransmitter
+
     private let processQueue = DispatchQueue(label: "BaseLibreTransmitterSource.processQueue")
 
     @Injected() var glucoseStorage: GlucoseStorage!
@@ -31,19 +35,17 @@ final class BaseLibreTransmitterSource: LibreTransmitterSource, Injectable {
             manager = LibreTransmitterManager()
             manager?.cgmManagerDelegate = self
         }
-
         injectServices(resolver)
     }
 
     func fetch(_: DispatchTimer?) -> AnyPublisher<[BloodGlucose], Never> {
-        Just([]).eraseToAnyPublisher()
-//        Future<[BloodGlucose], Error> { [weak self] promise in
-//            self?.promise = promise
-//        }
-//        .timeout(60, scheduler: processQueue, options: nil, customError: nil)
-//        .replaceError(with: [])
-//        .replaceEmpty(with: [])
-//        .eraseToAnyPublisher()
+        Future<[BloodGlucose], Error> { [weak self] promise in
+            self?.promise = promise
+        }
+        .timeout(60, scheduler: processQueue, options: nil, customError: nil)
+        .replaceError(with: [])
+        .replaceEmpty(with: [])
+        .eraseToAnyPublisher()
     }
 
     func fetchIfNeeded() -> AnyPublisher<[BloodGlucose], Never> {
@@ -87,7 +89,6 @@ extension BaseLibreTransmitterSource: LibreTransmitterManagerDelegate {
                 )
             }
             NSLog("Debug Libre \(glucose)")
-            glucoseManager?.updateGlucoseStore(newBloodGlucose: glucose)
             promise?(.success(glucose))
 
         case let .failure(error):

+ 19 - 23
FreeAPS/Sources/APS/CGM/dexcomSourceG7.swift

@@ -10,8 +10,8 @@ final class DexcomSourceG7: GlucoseSource {
     private var glucoseStorage: GlucoseStorage!
     var glucoseManager: FetchGlucoseManager?
 
-    var cgmManager: G7CGMManager?
-
+    var cgmManager: CGMManagerUI?
+    var cgmType: CGMType = .dexcomG7
     var cgmHasValidSensorSession: Bool = false
 
     private var promise: Future<[BloodGlucose], Error>.Promise?
@@ -24,11 +24,10 @@ final class DexcomSourceG7: GlucoseSource {
     }
 
     func fetch(_: DispatchTimer?) -> AnyPublisher<[BloodGlucose], Never> {
-        // dexcomManager.transmitter.resumeScanning()
         Future<[BloodGlucose], Error> { [weak self] promise in
             self?.promise = promise
         }
-        .timeout(60, scheduler: processQueue, options: nil, customError: nil)
+        .timeout(60 * 5, scheduler: processQueue, options: nil, customError: nil)
         .replaceError(with: [])
         .replaceEmpty(with: [])
         .eraseToAnyPublisher()
@@ -39,7 +38,7 @@ final class DexcomSourceG7: GlucoseSource {
             self.processQueue.async {
                 guard let cgmManager = self.cgmManager else { return }
                 cgmManager.fetchNewDataIfNeeded { result in
-                    self.processCGMReadingResult(cgmManager, readingResult: result, tickBLE: false) {
+                    self.processCGMReadingResult(cgmManager, readingResult: result) {
                         // nothing to do
                     }
                 }
@@ -59,11 +58,13 @@ final class DexcomSourceG7: GlucoseSource {
 extension DexcomSourceG7: CGMManagerDelegate {
     func deviceManager(
         _: LoopKit.DeviceManager,
-        logEventForDeviceIdentifier _: String?,
+        logEventForDeviceIdentifier deviceIdentifier: String?,
         type _: LoopKit.DeviceLogEntryType,
-        message _: String,
+        message: String,
         completion _: ((Error?) -> Void)?
-    ) {}
+    ) {
+        debug(.deviceManager, "device Manager for \(String(describing: deviceIdentifier)) : \(message)")
+    }
 
     func issueAlert(_: LoopKit.Alert) {}
 
@@ -83,12 +84,16 @@ extension DexcomSourceG7: CGMManagerDelegate {
 
     func recordRetractedAlert(_: LoopKit.Alert, at _: Date) {}
 
-    func cgmManagerWantsDeletion(_: CGMManager) {}
+    func cgmManagerWantsDeletion(_ manager: CGMManager) {
+        dispatchPrecondition(condition: .onQueue(.main))
+        debug(.deviceManager, " CGM Manager with identifier \(manager.managerIdentifier) wants deletion")
+        glucoseManager?.cgmGlucoseSourceType = nil
+    }
 
     func cgmManager(_ manager: CGMManager, hasNew readingResult: CGMReadingResult) {
         dispatchPrecondition(condition: .onQueue(.main))
-        processCGMReadingResult(manager, readingResult: readingResult, tickBLE: true) {
-            // nothing to do
+        processCGMReadingResult(manager, readingResult: readingResult) {
+            debug(.deviceManager, "DEXCOM - Direct return done")
         }
     }
 
@@ -115,7 +120,6 @@ extension DexcomSourceG7: CGMManagerDelegate {
     private func processCGMReadingResult(
         _: CGMManager,
         readingResult: CGMReadingResult,
-        tickBLE: Bool,
         completion: @escaping () -> Void
     ) {
         debug(.deviceManager, "DEXCOMG7 - Process CGM Reading Result launched")
@@ -137,11 +141,9 @@ extension DexcomSourceG7: CGMManagerDelegate {
                     type: "sgv"
                 )
             }
-            if tickBLE {
-                glucoseManager?.updateGlucoseStore(newBloodGlucose: bloodGlucose)
-            } else {
-                promise?(.success(bloodGlucose))
-            }
+
+            promise?(.success(bloodGlucose))
+
             completion()
         case .unreliableData:
             // loopManager.receivedUnreliableCGMReading()
@@ -156,9 +158,3 @@ extension DexcomSourceG7: CGMManagerDelegate {
         }
     }
 }
-
-// extension DexcomSourceG7 {
-//    func sourceInfo() -> [String: Any]? {
-//        [GlucoseSourceKey.description.rawValue: "Dexcom tramsmitter ID: \(transmitterID)"]
-//    }
-// }

+ 7 - 4
FreeAPS/Sources/APS/DeviceDataManager.swift

@@ -162,10 +162,11 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
         }
 
         debug(.deviceManager, "Start updating the pump data")
-
-        pumpManager.ensureCurrentPumpData { _ in
-            debug(.deviceManager, "Pump data updated.")
-            self.updateUpdateFinished(true)
+        processQueue.safeSync {
+            pumpManager.ensureCurrentPumpData { _ in
+                debug(.deviceManager, "Pump data updated.")
+                self.updateUpdateFinished(true)
+            }
         }
 
 //        pumpUpdateCancellable = Future<Bool, Never> { [unowned self] promise in
@@ -221,6 +222,8 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
     @Persisted(key: "BaseDeviceDataManager.lastFetchGlucoseDate") private var lastFetchGlucoseDate: Date = .distantPast
 
     var glucoseManager: FetchGlucoseManager?
+    var cgmManager: CGMManagerUI?
+    var cgmType: CGMType = .enlite
 
     func fetchIfNeeded() -> AnyPublisher<[BloodGlucose], Never> {
         fetch(nil)

+ 45 - 33
FreeAPS/Sources/APS/FetchGlucoseManager.swift

@@ -6,6 +6,9 @@ import Swinject
 protocol FetchGlucoseManager: SourceInfoProvider {
     func updateGlucoseStore(newBloodGlucose: [BloodGlucose])
     func refreshCGM()
+    func updateGlucoseSource()
+    var glucoseSource: GlucoseSource! { get }
+    var cgmGlucoseSourceType: CGMType? { get set }
 }
 
 final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
@@ -20,6 +23,7 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
 
     private var lifetime = Lifetime()
     private let timer = DispatchTimer(timeInterval: 1.minutes.timeInterval)
+    var cgmGlucoseSourceType: CGMType?
 
     private lazy var dexcomSourceG5 = DexcomSourceG5(glucoseStorage: glucoseStorage, glucoseManager: self)
     private lazy var dexcomSourceG6 = DexcomSourceG6(glucoseStorage: glucoseStorage, glucoseManager: self)
@@ -42,10 +46,10 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
 
     var glucoseSource: GlucoseSource!
 
-    private func updateGlucoseSource() {
+    func updateGlucoseSource() {
         switch settingsManager.settings.cgm {
         case .xdrip:
-            glucoseSource = AppGroupSource(from: "xDrip")
+            glucoseSource = AppGroupSource(from: "xDrip", cgmType: .xdrip)
         case .dexcomG5:
             glucoseSource = dexcomSourceG5
         case .dexcomG6:
@@ -59,10 +63,12 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
         case .libreTransmitter:
             glucoseSource = libreTransmitter
         case .glucoseDirect:
-            glucoseSource = AppGroupSource(from: "GlucoseDirect")
+            glucoseSource = AppGroupSource(from: "GlucoseDirect", cgmType: .glucoseDirect)
         case .enlite:
             glucoseSource = deviceDataManager
         }
+        // update the config
+        cgmGlucoseSourceType = settingsManager.settings.cgm
 
         if settingsManager.settings.cgm != .libreTransmitter {
             libreTransmitter.manager = nil
@@ -71,7 +77,7 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
         }
     }
 
-    /// function called when a callback is fired by CGM BLE
+    /// function called when a callback is fired by CGM BLE - no more used
     public func updateGlucoseStore(newBloodGlucose: [BloodGlucose]) {
         let syncDate = glucoseStorage.syncDate()
         debug(.deviceManager, "CGM BLE FETCHGLUCOSE  : SyncDate is \(syncDate)")
@@ -101,54 +107,60 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
         var filteredByDate: [BloodGlucose] = []
         var filtered: [BloodGlucose] = []
 
-        if allGlucose.isNotEmpty {
-            filteredByDate = allGlucose.filter { $0.dateString > syncDate }
-            filtered = glucoseStorage.filterTooFrequentGlucose(filteredByDate, at: syncDate)
-            if filtered.isNotEmpty {
-                debug(.nightscout, "New glucose found")
-                glucoseStorage.storeGlucose(filtered)
-            }
-        }
+        guard allGlucose.isNotEmpty else { return }
 
-        if filtered.isEmpty {
-            let lastGlucoseDate = glucoseStorage.lastGlucoseDate()
-            guard lastGlucoseDate >= Date().addingTimeInterval(Config.eхpirationInterval) else {
-                debug(.nightscout, "Glucose is too old - \(lastGlucoseDate)")
-                return
-            }
-        }
+        filteredByDate = allGlucose.filter { $0.dateString > syncDate }
+        filtered = glucoseStorage.filterTooFrequentGlucose(filteredByDate, at: syncDate)
 
-        apsManager.heartbeat(date: Date())
+        guard filtered.isNotEmpty else { return }
+        debug(.deviceManager, "New glucose found")
 
-        // no need to go next step if no new data
-        guard filteredByDate.isNotEmpty else {
-            return
-        }
+        glucoseStorage.storeGlucose(filtered)
+
+        deviceDataManager.heartbeat(date: Date())
 
         nightscoutManager.uploadGlucose()
+
         let glucoseForHealth = filteredByDate.filter { !glucoseFromHealth.contains($0) }
+
         guard glucoseForHealth.isNotEmpty else { return }
         healthKitManager.saveIfNeeded(bloodGlucose: glucoseForHealth)
+
+//        if filtered.isEmpty {
+//            let lastGlucoseDate = glucoseStorage.lastGlucoseDate()
+//            guard lastGlucoseDate >= Date().addingTimeInterval(-Config.eхpirationInterval) else {
+//                debug(.nightscout, "Glucose is too old - \(lastGlucoseDate)")
+//                return
+//            }
+//        }
     }
 
     /// The function used to start the timer sync - Function of the variable defined in config
     private func subscribe() {
         timer.publisher
             .receive(on: processQueue)
-            .flatMap { date -> AnyPublisher<(Date, Date, [BloodGlucose], [BloodGlucose]), Never> in
-                debug(.nightscout, "FetchGlucoseManager heartbeat")
+            .flatMap { _ -> AnyPublisher<[BloodGlucose], Never> in
+                debug(.nightscout, "FetchGlucoseManager timer heartbeat")
                 self.updateGlucoseSource()
-                return Publishers.CombineLatest4(
-                    Just(date),
+                return self.glucoseSource.fetch(self.timer).eraseToAnyPublisher()
+            }
+            .sink { glucose in
+                debug(.nightscout, "FetchGlucoseManager callback sensor")
+                guard glucose.isNotEmpty else { return }
+                Publishers.CombineLatest3(
+                    Just(glucose),
                     Just(self.glucoseStorage.syncDate()),
-                    self.glucoseSource.fetch(self.timer),
                     self.healthKitManager.fetch(nil)
                 )
                 .eraseToAnyPublisher()
-            }
-            .sink { _, syncDate, glucose, glucoseFromHealth in
-                debug(.nightscout, "FETCHGLUCOSE : SyncDate is \(syncDate)")
-                self.glucoseStoreAndHeartDecision(syncDate: syncDate, glucose: glucose, glucoseFromHealth: glucoseFromHealth)
+                .sink { newGlucose, syncDate, glucoseFromHealth in
+                    self.glucoseStoreAndHeartDecision(
+                        syncDate: syncDate,
+                        glucose: newGlucose,
+                        glucoseFromHealth: glucoseFromHealth
+                    )
+                }
+                .store(in: &self.lifetime)
             }
             .store(in: &lifetime)
         timer.fire()

+ 3 - 3
FreeAPS/Sources/Logger/Logger.swift

@@ -18,7 +18,7 @@ func debug(
     line: UInt = #line
 ) {
     let msg = message()
-    DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
+    DispatchWorkItem(qos: .background, flags: .enforceQoS) {
         loggerLock.perform {
             category.logger.debug(msg, printToConsole: printToConsole, file: file, function: function, line: line)
         }
@@ -32,7 +32,7 @@ func info(
     function: String = #function,
     line: UInt = #line
 ) {
-    DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
+    DispatchWorkItem(qos: .background, flags: .enforceQoS) {
         loggerLock.perform {
             category.logger.info(message, file: file, function: function, line: line)
         }
@@ -48,7 +48,7 @@ func warning(
     function: String = #function,
     line: UInt = #line
 ) {
-    DispatchWorkItem(qos: .userInteractive, flags: .enforceQoS) {
+    DispatchWorkItem(qos: .background, flags: .enforceQoS) {
         loggerLock.perform {
             category.logger.warning(
                 message,

+ 4 - 0
FreeAPS/Sources/Modules/CGM/CGMDataFlow.swift

@@ -2,4 +2,8 @@ enum CGM {
     enum Config {}
 }
 
+enum cgmConfig {
+    enum Config {}
+}
+
 protocol CGMProvider: Provider {}

+ 3 - 1
FreeAPS/Sources/Modules/CGM/CGMProvider.swift

@@ -1,3 +1,5 @@
 extension CGM {
-    final class Provider: BaseProvider, CGMProvider {}
+    final class Provider: BaseProvider, CGMProvider {
+        @Injected() var apsManager: APSManager!
+    }
 }

+ 35 - 4
FreeAPS/Sources/Modules/CGM/CGMStateModel.swift

@@ -1,13 +1,17 @@
+import CGMBLEKit
 import Combine
+import LoopKitUI
 import SwiftUI
 
 extension CGM {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() var libreSource: LibreTransmitterSource!
+        @Injected() var cgmManager: FetchGlucoseManager!
         @Injected() var calendarManager: CalendarManager!
 
+        @Published var setupCGM: Bool = false
         @Published var cgm: CGMType = .nightscout
-        @Published var transmitterID = ""
+        // @Published var transmitterID = ""
         @Published var uploadGlucose = false
         @Published var createCalendarEvents = false
         @Published var calendarIDs: [String] = []
@@ -17,7 +21,6 @@ extension CGM {
 
         override func subscribe() {
             cgm = settingsManager.settings.cgm
-            transmitterID = UserDefaults.standard.dexcomTransmitterID ?? ""
             currentCalendarID = storedCalendarID ?? ""
             calendarIDs = calendarManager.calendarIDs()
             cgmTransmitterDeviceAddress = UserDefaults.standard.cgmTransmitterDeviceAddress
@@ -29,6 +32,10 @@ extension CGM {
                 .removeDuplicates()
                 .sink { [weak self] value in
                     guard let self = self else { return }
+                    guard self.cgmManager.cgmGlucoseSourceType != nil else {
+                        self.settingsManager.settings.cgm = .nightscout
+                        return
+                    }
                     self.settingsManager.settings.cgm = value
                 }
                 .store(in: &lifetime)
@@ -58,9 +65,33 @@ extension CGM {
                 }
                 .store(in: &lifetime)
         }
+    }
+}
 
-        func onChangeID() {
-            UserDefaults.standard.dexcomTransmitterID = transmitterID
+extension CGM.StateModel: CompletionDelegate {
+    func completionNotifyingDidComplete(_: CompletionNotifying) {
+        setupCGM = false
+        // if CGM was deleted
+        if cgmManager.cgmGlucoseSourceType == nil {
+            cgm = .nightscout
         }
+        cgmManager.updateGlucoseSource()
+    }
+}
+
+extension CGM.StateModel: CGMManagerOnboardingDelegate {
+    func cgmManagerOnboarding(didCreateCGMManager manager: LoopKitUI.CGMManagerUI) {
+        // Possibility add the dexcom number !
+        if let dexcomG6Manager: G6CGMManager = manager as? G6CGMManager {
+            UserDefaults.standard.dexcomTransmitterID = dexcomG6Manager.transmitter.ID
+
+        } else if let dexcomG5Manager: G5CGMManager = manager as? G5CGMManager {
+            UserDefaults.standard.dexcomTransmitterID = dexcomG5Manager.transmitter.ID
+        }
+        cgmManager.updateGlucoseSource()
+    }
+
+    func cgmManagerOnboarding(didOnboardCGMManager _: LoopKitUI.CGMManagerUI) {
+        // nothing to do ?
     }
 }

+ 73 - 54
FreeAPS/Sources/Modules/CGM/View/CGMRootView.swift

@@ -1,3 +1,4 @@
+import LoopKitUI
 import SwiftUI
 import Swinject
 
@@ -5,79 +6,97 @@ extension CGM {
     struct RootView: BaseView {
         let resolver: Resolver
         @StateObject var state = StateModel()
+        @State private var setupCGM = false
 
         // @AppStorage(UserDefaults.BTKey.cgmTransmitterDeviceAddress.rawValue) private var cgmTransmitterDeviceAddress: String? = nil
 
         var body: some View {
-            Form {
-                Section {
-                    Picker("Type", selection: $state.cgm) {
-                        ForEach(CGMType.allCases) { type in
-                            VStack(alignment: .leading) {
-                                Text(type.displayName)
-                                Text(type.subtitle).font(.caption).foregroundColor(.secondary)
-                            }.tag(type)
+            NavigationView {
+                Form {
+                    Section(header: Text("CGM")) {
+                        Picker("Type", selection: $state.cgm) {
+                            ForEach(CGMType.allCases) { type in
+                                VStack(alignment: .leading) {
+                                    Text(type.displayName)
+                                    Text(type.subtitle).font(.caption).foregroundColor(.secondary)
+                                }.tag(type)
+                            }
                         }
-                    }
-                    if let link = state.cgm.externalLink {
-                        Button("About this source") {
-                            UIApplication.shared.open(link, options: [:], completionHandler: nil)
+                        if let link = state.cgm.externalLink {
+                            Button("About this source") {
+                                UIApplication.shared.open(link, options: [:], completionHandler: nil)
+                            }
                         }
                     }
-                }
-                if [.dexcomG5, .dexcomG6].contains(state.cgm) {
-                    Section(header: Text("Transmitter ID")) {
-                        TextField("XXXXXX", text: $state.transmitterID, onCommit: {
-                            UIApplication.shared.endEditing()
-                            state.onChangeID()
-                        })
-                            .disableAutocorrection(true)
-                            .autocapitalization(.allCharacters)
-                            .keyboardType(.asciiCapable)
+                    if [.dexcomG5, .dexcomG6, .dexcomG7].contains(state.cgm) {
+                        Section {
+                            Button("Configuration CGM") {
+                                setupCGM.toggle()
+                            }
+                        }
                     }
-                    .onDisappear {
-                        state.onChangeID()
+                    if state.cgm == .xdrip {
+                        Section(header: Text("Heartbeat")) {
+                            VStack(alignment: .leading) {
+                                if let cgmTransmitterDeviceAddress = state.cgmTransmitterDeviceAddress {
+                                    Text("CGM address :")
+                                    Text(cgmTransmitterDeviceAddress)
+                                } else {
+                                    Text("CGM is not used as heartbeat.")
+                                }
+                            }
+                        }
                     }
-                }
-
-                if state.cgm == .libreTransmitter {
-                    Button("Configure Libre Transmitter") {
-                        state.showModal(for: .libreConfig)
+                    if state.cgm == .libreTransmitter {
+                        Button("Configure Libre Transmitter") {
+                            state.showModal(for: .libreConfig)
+                        }
+                        Text("Calibrations").navigationLink(to: .calibrations, from: self)
                     }
-                    Text("Calibrations").navigationLink(to: .calibrations, from: self)
-                }
-
-                if state.cgm == .xdrip {
-                    Section(header: Text("Heartbeat")) {
-                        VStack(alignment: .leading) {
-                            if let cgmTransmitterDeviceAddress = state.cgmTransmitterDeviceAddress {
-                                Text("CGM address :")
-                                Text(cgmTransmitterDeviceAddress)
-                            } else {
-                                Text("CGM is not used as heartbeat.")
+                    Section(header: Text("Calendar")) {
+                        Toggle("Create events in calendar", isOn: $state.createCalendarEvents)
+                        if state.calendarIDs.isNotEmpty {
+                            Picker("Calendar", selection: $state.currentCalendarID) {
+                                ForEach(state.calendarIDs, id: \.self) {
+                                    Text($0).tag($0)
+                                }
                             }
                         }
                     }
-                }
 
-                Section(header: Text("Calendar")) {
-                    Toggle("Create events in calendar", isOn: $state.createCalendarEvents)
-                    if state.calendarIDs.isNotEmpty {
-                        Picker("Calendar", selection: $state.currentCalendarID) {
-                            ForEach(state.calendarIDs, id: \.self) {
-                                Text($0).tag($0)
-                            }
-                        }
+                    Section(header: Text("Other")) {
+                        Toggle("Upload glucose to Nightscout", isOn: $state.uploadGlucose)
                     }
                 }
 
-                Section(header: Text("Other")) {
-                    Toggle("Upload glucose to Nightscout", isOn: $state.uploadGlucose)
+                .onAppear(perform: configureView)
+                .navigationTitle("CGM")
+                .navigationBarTitleDisplayMode(.automatic)
+                .sheet(isPresented: $setupCGM) {
+                    if let cgmFetchManager = state.cgmManager, cgmFetchManager.glucoseSource.cgmType == state.cgm {
+                        CGMSettingsView(
+                            cgmManager: cgmFetchManager.glucoseSource.cgmManager!,
+                            bluetoothManager: state.provider.apsManager.bluetoothManager!,
+                            unit: state.settingsManager.settings.units,
+                            completionDelegate: state
+                        )
+                    } else {
+                        CGMSetupView(
+                            CGMType: state.cgm,
+                            bluetoothManager: state.provider.apsManager.bluetoothManager!,
+                            unit: state.settingsManager.settings.units,
+                            completionDelegate: state,
+                            setupDelegate: state
+                        )
+                    }
+                }
+                .onChange(of: setupCGM) { setupCGM in
+                    state.setupCGM = setupCGM
+                }
+                .onChange(of: state.setupCGM) { setupCGM in
+                    self.setupCGM = setupCGM
                 }
             }
-            .onAppear(perform: configureView)
-            .navigationTitle("CGM")
-            .navigationBarTitleDisplayMode(.automatic)
         }
     }
 }

+ 42 - 0
FreeAPS/Sources/Modules/CGM/View/CGMSettingsView.swift

@@ -0,0 +1,42 @@
+import LoopKit
+import LoopKitUI
+import SwiftUI
+import UIKit
+
+extension CGM {
+    struct CGMSettingsView: UIViewControllerRepresentable {
+        let cgmManager: CGMManagerUI
+        let bluetoothManager: BluetoothStateManager
+        let unit: GlucoseUnits
+        weak var completionDelegate: CompletionDelegate?
+
+        func makeUIViewController(context _: UIViewControllerRepresentableContext<CGMSettingsView>) -> UIViewController {
+            let displayGlucoseUnitObservable: DisplayGlucoseUnitObservable
+            switch unit {
+            case .mgdL:
+                displayGlucoseUnitObservable = DisplayGlucoseUnitObservable(displayGlucoseUnit: .milligramsPerDeciliter)
+            case .mmolL:
+                displayGlucoseUnitObservable = DisplayGlucoseUnitObservable(displayGlucoseUnit: .millimolesPerLiter)
+            }
+
+            var vc = cgmManager.settingsViewController(
+                bluetoothProvider: bluetoothManager,
+                displayGlucoseUnitObservable: displayGlucoseUnitObservable,
+                colorPalette: .default,
+                allowDebugFeatures: false
+            )
+            // vc.cgmManagerOnboardingDelegate =
+            // vc.completionDelegate = self
+            vc.completionDelegate = completionDelegate
+
+            return vc
+        }
+
+        func updateUIViewController(
+            _ uiViewController: UIViewController,
+            context _: UIViewControllerRepresentableContext<CGMSettingsView>
+        ) {
+            uiViewController.isModalInPresentation = true
+        }
+    }
+}

+ 80 - 0
FreeAPS/Sources/Modules/CGM/View/CGMSetupView.swift

@@ -0,0 +1,80 @@
+import CGMBLEKit
+import CGMBLEKitUI
+import G7SensorKit
+import G7SensorKitUI
+import LoopKit
+import LoopKitUI
+import SwiftUI
+import UIKit
+
+extension CGM {
+    struct CGMSetupView: UIViewControllerRepresentable {
+        let CGMType: CGMType
+        let bluetoothManager: BluetoothStateManager
+        let unit: GlucoseUnits
+        weak var completionDelegate: CompletionDelegate?
+        weak var setupDelegate: CGMManagerOnboardingDelegate?
+
+        func makeUIViewController(context _: UIViewControllerRepresentableContext<CGMSetupView>) -> UIViewController {
+            var setupViewController: SetupUIResult<
+                CGMManagerViewController,
+                CGMManagerUI
+            >?
+
+            let displayGlucoseUnitObservable: DisplayGlucoseUnitObservable
+            switch unit {
+            case .mgdL:
+                displayGlucoseUnitObservable = DisplayGlucoseUnitObservable(displayGlucoseUnit: .milligramsPerDeciliter)
+            case .mmolL:
+                displayGlucoseUnitObservable = DisplayGlucoseUnitObservable(displayGlucoseUnit: .millimolesPerLiter)
+            }
+
+            switch CGMType {
+            case .dexcomG6:
+                setupViewController = G6CGMManager.setupViewController(
+                    bluetoothProvider: bluetoothManager,
+                    displayGlucoseUnitObservable: displayGlucoseUnitObservable,
+                    colorPalette: .default,
+                    allowDebugFeatures: false
+                )
+            case .dexcomG5:
+                setupViewController = G5CGMManager.setupViewController(
+                    bluetoothProvider: bluetoothManager,
+                    displayGlucoseUnitObservable: displayGlucoseUnitObservable,
+                    colorPalette: .default,
+                    allowDebugFeatures: false
+                )
+            case .dexcomG7:
+                setupViewController =
+                    G7CGMManager.setupViewController(
+                        bluetoothProvider: bluetoothManager,
+                        displayGlucoseUnitObservable: displayGlucoseUnitObservable,
+                        colorPalette: .default,
+                        allowDebugFeatures: false
+                    )
+            default:
+                break
+            }
+
+            switch setupViewController {
+            case var .userInteractionRequired(setupViewControllerUI):
+                setupViewControllerUI.cgmManagerOnboardingDelegate = setupDelegate
+                setupViewControllerUI.completionDelegate = completionDelegate
+                return setupViewControllerUI
+            case let .createdAndOnboarded(cgmManagerUI):
+                debug(.default, "CGM manager  created and onboarded")
+                setupDelegate?.cgmManagerOnboarding(didCreateCGMManager: cgmManagerUI)
+                return UIViewController()
+            case .none:
+                return UIViewController()
+            }
+        }
+
+        func updateUIViewController(
+            _ uiViewController: UIViewController,
+            context _: UIViewControllerRepresentableContext<CGMSetupView>
+        ) {
+            uiViewController.isModalInPresentation = true
+        }
+    }
+}

+ 3 - 0
FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift

@@ -1,6 +1,7 @@
 import Combine
 import Foundation
 import HealthKit
+import LoopKitUI
 import Swinject
 
 protocol HealthKitManager: GlucoseSource {
@@ -290,6 +291,8 @@ final class BaseHealthKitManager: HealthKitManager, Injectable {
     // MARK: - GlucoseSource
 
     var glucoseManager: FetchGlucoseManager?
+    var cgmManager: CGMManagerUI?
+    var cgmType: CGMType = .nightscout
 
     func fetch(_: DispatchTimer?) -> AnyPublisher<[BloodGlucose], Never> {
         Future { [weak self] promise in

+ 3 - 0
FreeAPS/Sources/Services/Network/NightscoutManager.swift

@@ -1,5 +1,6 @@
 import Combine
 import Foundation
+import LoopKitUI
 import Swinject
 import UIKit
 
@@ -128,6 +129,8 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
     // MARK: - GlucoseSource
 
     var glucoseManager: FetchGlucoseManager?
+    var cgmManager: CGMManagerUI?
+    var cgmType: CGMType = .nightscout
 
     func fetch(_: DispatchTimer?) -> AnyPublisher<[BloodGlucose], Never> {
         fetchGlucose(since: glucoseStorage.syncDate())