Преглед изворни кода

Loop 3 framework update and best integration of CGME and G7

avouspierre пре 3 година
родитељ
комит
d4d81e568f
100 измењених фајлова са 6421 додато и 122 уклоњено
  1. 140 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/AppDelegate.swift
  2. 98 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/Assets.xcassets/AppIcon.appiconset/Contents.json
  3. 25 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/Base.lproj/LaunchScreen.storyboard
  4. 172 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/Base.lproj/Main.storyboard
  5. 34 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/CommandQueue.swift
  6. 49 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/Info.plist
  7. 39 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/NSUserDefaults.swift
  8. 200 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/ViewController.swift
  9. 8 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/da.lproj/Localizable.strings
  10. 27 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/da.lproj/Main.strings
  11. 8 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/de.lproj/Localizable.strings
  12. 27 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/de.lproj/Main.strings
  13. 8 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/en.lproj/Localizable.strings
  14. 27 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/en.lproj/Main.strings
  15. 8 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/es.lproj/Localizable.strings
  16. 27 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/es.lproj/Main.strings
  17. 9 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/fi.lproj/Localizable.strings
  18. 27 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/fi.lproj/Main.strings
  19. 8 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/fr.lproj/Localizable.strings
  20. 27 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/fr.lproj/Main.strings
  21. 1 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/he.lproj/LaunchScreen.strings
  22. 8 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/he.lproj/Localizable.strings
  23. 27 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/he.lproj/Main.strings
  24. 8 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/it.lproj/Localizable.strings
  25. 27 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/it.lproj/Main.strings
  26. 8 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/ja.lproj/Localizable.strings
  27. 27 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/ja.lproj/Main.strings
  28. 8 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/nb.lproj/Localizable.strings
  29. 27 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/nb.lproj/Main.strings
  30. 9 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/nl.lproj/Localizable.strings
  31. 21 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/nl.lproj/Main.strings
  32. 8 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/pl.lproj/Localizable.strings
  33. 27 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/pl.lproj/Main.strings
  34. 8 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/pt-BR.lproj/Localizable.strings
  35. 26 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/pt-BR.lproj/Main.strings
  36. 8 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/ro.lproj/Localizable.strings
  37. 27 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/ro.lproj/Main.strings
  38. 8 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/ru.lproj/Localizable.strings
  39. 27 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/ru.lproj/Main.strings
  40. 9 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/sv.lproj/Localizable.strings
  41. 27 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/sv.lproj/Main.strings
  42. 1 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/tr.lproj/LaunchScreen.strings
  43. 8 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/tr.lproj/Localizable.strings
  44. 27 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/tr.lproj/Main.strings
  45. 8 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/vi.lproj/Localizable.strings
  46. 27 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/vi.lproj/Main.strings
  47. 8 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/zh-Hans.lproj/Localizable.strings
  48. 27 0
      Dependencies/CGMBLEKit/CGMBLEKit Example/zh-Hans.lproj/Main.strings
  49. 1738 17
      Dependencies/CGMBLEKit/CGMBLEKit.xcodeproj/project.pbxproj
  50. 97 0
      Dependencies/CGMBLEKit/CGMBLEKit.xcodeproj/xcshareddata/xcschemes/CGMBLEKit Example.xcscheme
  51. 87 0
      Dependencies/CGMBLEKit/CGMBLEKit.xcodeproj/xcshareddata/xcschemes/ResetTransmitter.xcscheme
  52. 76 0
      Dependencies/CGMBLEKit/CGMBLEKit.xcodeproj/xcshareddata/xcschemes/Shared-watchOS.xcscheme
  53. 137 0
      Dependencies/CGMBLEKit/CGMBLEKit.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme
  54. 41 23
      Dependencies/CGMBLEKit/CGMBLEKit/Glucose+SensorDisplayable.swift
  55. 7 1
      Dependencies/CGMBLEKit/CGMBLEKit/Glucose.swift
  56. 1 0
      Dependencies/CGMBLEKit/CGMBLEKit/Messages/TransmitterTimeRxMessage.swift
  57. 255 81
      Dependencies/CGMBLEKit/CGMBLEKit/TransmitterManager.swift
  58. 39 0
      Dependencies/CGMBLEKit/CGMBLEKit/he.lproj/Localizable.strings
  59. 39 0
      Dependencies/CGMBLEKit/CGMBLEKit/tr.lproj/Localizable.strings
  60. 4 0
      Dependencies/CGMBLEKit/CGMBLEKitG5Plugin/CGMBLEKitG5Plugin-Bridging-Header.h
  61. 25 0
      Dependencies/CGMBLEKit/CGMBLEKitG5Plugin/CGMBLEKitG5Plugin.swift
  62. 30 0
      Dependencies/CGMBLEKit/CGMBLEKitG5Plugin/Info.plist
  63. 4 0
      Dependencies/CGMBLEKit/CGMBLEKitG6Plugin/CGMBLEKitG6Plugin-Bridging-Header.h
  64. 25 0
      Dependencies/CGMBLEKit/CGMBLEKitG6Plugin/CGMBLEKitG6Plugin.swift
  65. 30 0
      Dependencies/CGMBLEKit/CGMBLEKitG6Plugin/Info.plist
  66. 20 0
      Dependencies/CGMBLEKit/CGMBLEKitTests/CalibrationDataRxMessageTests.swift
  67. 433 0
      Dependencies/CGMBLEKit/CGMBLEKitTests/GlucoseBackfillMessageTests.swift
  68. 79 0
      Dependencies/CGMBLEKit/CGMBLEKitTests/GlucoseRxMessageTests.swift
  69. 83 0
      Dependencies/CGMBLEKit/CGMBLEKitTests/GlucoseTests.swift
  70. 24 0
      Dependencies/CGMBLEKit/CGMBLEKitTests/Info.plist
  71. 44 0
      Dependencies/CGMBLEKit/CGMBLEKitTests/SessionStartRxMessageTests.swift
  72. 44 0
      Dependencies/CGMBLEKit/CGMBLEKitTests/SessionStopRxMessageTests.swift
  73. 20 0
      Dependencies/CGMBLEKit/CGMBLEKitTests/TransmitterIDTests.swift
  74. 53 0
      Dependencies/CGMBLEKit/CGMBLEKitTests/TransmitterTimeRxMessageTests.swift
  75. 22 0
      Dependencies/CGMBLEKit/CGMBLEKitTests/TransmitterVersionRxMessageTests.swift
  76. 6 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/Assets.xcassets/Contents.json
  77. 12 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/Assets.xcassets/g6.imageset/Contents.json
  78. BIN
      Dependencies/CGMBLEKit/CGMBLEKitUI/Assets.xcassets/g6.imageset/g6.png
  79. 51 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/Base.lproj/Localizable.strings
  80. 114 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/Base.lproj/TransmitterManagerSetup.storyboard
  81. 19 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/CGMBLEKitUI.h
  82. 24 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/IdentifiableClass.swift
  83. 24 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/Info.plist
  84. 151 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/TransmitterIDSetupViewController.swift
  85. 88 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/TransmitterManager+UI.swift
  86. 558 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/TransmitterSettingsViewController.swift
  87. 96 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/TransmitterSetupViewController.swift
  88. 22 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/UIColor.swift
  89. 45 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/da.lproj/Localizable.strings
  90. 24 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/da.lproj/TransmitterManagerSetup.strings
  91. 45 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/de.lproj/Localizable.strings
  92. 24 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/de.lproj/TransmitterManagerSetup.strings
  93. 24 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/en.lproj/TransmitterManagerSetup.strings
  94. 45 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/es.lproj/Localizable.strings
  95. 24 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/es.lproj/TransmitterManagerSetup.strings
  96. 55 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/fi.lproj/Localizable.strings
  97. 24 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/fi.lproj/TransmitterManagerSetup.strings
  98. 45 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/fr.lproj/Localizable.strings
  99. 24 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/fr.lproj/TransmitterManagerSetup.strings
  100. 0 0
      Dependencies/CGMBLEKit/CGMBLEKitUI/he.lproj/Localizable.strings

+ 140 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/AppDelegate.swift

@@ -0,0 +1,140 @@
+//
+//  AppDelegate.swift
+//  xDrip5
+//
+//  Created by Nathan Racklyeft on 10/1/15.
+//  Copyright © 2015 Nathan Racklyeft. All rights reserved.
+//
+
+import UIKit
+import CGMBLEKit
+import CoreBluetooth
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate, TransmitterDelegate, TransmitterCommandSource {
+    
+    var window: UIWindow?
+
+    static var sharedDelegate: AppDelegate {
+        return UIApplication.shared.delegate as! AppDelegate
+    }
+
+    var transmitterID: String? {
+        didSet {
+            if let id = transmitterID {
+                transmitter = Transmitter(
+                    id: id,
+                    passiveModeEnabled: UserDefaults.standard.passiveModeEnabled
+                )
+                transmitter?.stayConnected = UserDefaults.standard.stayConnected
+                transmitter?.delegate = self
+                transmitter?.commandSource = self
+
+                UserDefaults.standard.transmitterID = id
+            }
+            glucose = nil
+        }
+    }
+
+    var transmitter: Transmitter?
+
+    let commandQueue = CommandQueue()
+
+    var glucose: Glucose?
+
+    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+
+        transmitterID = UserDefaults.standard.transmitterID
+
+        return true
+    }
+
+    func applicationWillResignActive(_ application: UIApplication) {
+        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
+        // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
+    }
+
+    func applicationDidEnterBackground(_ application: UIApplication) {
+        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
+        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
+
+        if let transmitter = transmitter, !transmitter.stayConnected {
+            transmitter.stopScanning()
+        }
+    }
+
+    func applicationWillEnterForeground(_ application: UIApplication) {
+        // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
+    }
+
+    func applicationDidBecomeActive(_ application: UIApplication) {
+        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
+
+        transmitter?.resumeScanning()
+    }
+
+    func applicationWillTerminate(_ application: UIApplication) {
+        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
+    }
+
+    // MARK: - TransmitterDelegate
+
+    private let dateFormatter: DateFormatter = {
+        let dateFormatter = DateFormatter()
+
+        dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
+        dateFormatter.locale = Locale(identifier: "en_US_POSIX")
+
+        return dateFormatter
+    }()
+
+    func dequeuePendingCommand(for transmitter: Transmitter) -> Command? {
+        return commandQueue.dequeue()
+    }
+
+    func transmitter(_ transmitter: Transmitter, didFail command: Command, with error: Error) {
+        // TODO: implement
+    }
+
+    func transmitter(_ transmitter: Transmitter, didComplete command: Command) {
+        // TODO: implement
+    }
+
+    func transmitter(_ transmitter: Transmitter, didError error: Error) {
+        DispatchQueue.main.async {
+            if let vc = self.window?.rootViewController as? TransmitterDelegate {
+                vc.transmitter(transmitter, didError: error)
+            }
+        }
+    }
+
+    func transmitter(_ transmitter: Transmitter, didRead glucose: Glucose) {
+        self.glucose = glucose
+        DispatchQueue.main.async {
+            if let vc = self.window?.rootViewController as? TransmitterDelegate {
+                vc.transmitter(transmitter, didRead: glucose)
+            }
+        }
+    }
+
+    func transmitter(_ transmitter: Transmitter, didReadUnknownData data: Data) {
+        DispatchQueue.main.async {
+            if let vc = self.window?.rootViewController as? TransmitterDelegate {
+                vc.transmitter(transmitter, didReadUnknownData: data)
+            }
+        }
+    }
+    
+    func transmitter(_ transmitter: Transmitter, didReadBackfill glucose: [Glucose]) {
+        DispatchQueue.main.async {
+            if let vc = self.window?.rootViewController as? TransmitterDelegate {
+                vc.transmitter(transmitter, didReadBackfill: glucose)
+            }
+        }
+    }
+    
+    func transmitterDidConnect(_ transmitter: Transmitter) {
+        // Ignore
+    }
+
+}

+ 98 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/Assets.xcassets/AppIcon.appiconset/Contents.json

@@ -0,0 +1,98 @@
+{
+  "images" : [
+    {
+      "idiom" : "iphone",
+      "size" : "20x20",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "20x20",
+      "scale" : "3x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "29x29",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "29x29",
+      "scale" : "3x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "40x40",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "40x40",
+      "scale" : "3x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "60x60",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "60x60",
+      "scale" : "3x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "20x20",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "20x20",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "29x29",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "29x29",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "40x40",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "40x40",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "76x76",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "76x76",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "83.5x83.5",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "ios-marketing",
+      "size" : "1024x1024",
+      "scale" : "1x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}

+ 25 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/Base.lproj/LaunchScreen.storyboard

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" systemVersion="17A277" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
+    <dependencies>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="EHf-IW-A2E">
+            <objects>
+                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="53" y="375"/>
+        </scene>
+    </scenes>
+</document>

+ 172 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/Base.lproj/Main.storyboard

@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
+    <device id="retina5_5" orientation="portrait">
+        <adaptation id="fullscreen"/>
+    </device>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
+        <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="tne-QT-ifu">
+            <objects>
+                <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="CGMBLEKit_Example" customModuleProvider="target" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+                        <rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="mqJ-p6-sSU">
+                                <rect key="frame" x="20" y="40" width="374" height="676"/>
+                                <subviews>
+                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="K5I-e5-MvQ">
+                                        <rect key="frame" x="15.666666666666657" y="0.0" width="343" height="52"/>
+                                        <subviews>
+                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Transmitter ID" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5y6-vU-qC3">
+                                                <rect key="frame" x="8" y="14.333333333333334" width="249" height="20.333333333333329"/>
+                                                <fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
+                                                <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                                                <nil key="highlightedColor"/>
+                                            </label>
+                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="oUg-jb-mCK">
+                                                <rect key="frame" x="265" y="42" width="70" height="1"/>
+                                                <color key="backgroundColor" red="0.33333333333333331" green="0.33333333333333331" blue="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                                                <constraints>
+                                                    <constraint firstAttribute="height" constant="1" id="Rjq-qn-DaB"/>
+                                                </constraints>
+                                            </view>
+                                            <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="500000" textAlignment="center" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="e0T-ru-tWD">
+                                                <rect key="frame" x="265" y="8" width="70" height="34"/>
+                                                <constraints>
+                                                    <constraint firstAttribute="width" constant="70" id="1v1-n5-aT4"/>
+                                                    <constraint firstAttribute="height" constant="34" id="CVt-L0-Ts1"/>
+                                                </constraints>
+                                                <fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
+                                                <textInputTraits key="textInputTraits" autocapitalizationType="allCharacters" autocorrectionType="no" spellCheckingType="no" keyboardType="alphabet" returnKeyType="done"/>
+                                                <connections>
+                                                    <outlet property="delegate" destination="BYZ-38-t0r" id="XHs-Ei-i6K"/>
+                                                </connections>
+                                            </textField>
+                                        </subviews>
+                                        <constraints>
+                                            <constraint firstItem="e0T-ru-tWD" firstAttribute="leading" secondItem="5y6-vU-qC3" secondAttribute="trailing" constant="8" id="1Y6-Tc-fCe"/>
+                                            <constraint firstItem="5y6-vU-qC3" firstAttribute="leading" secondItem="K5I-e5-MvQ" secondAttribute="leadingMargin" id="2up-6R-sUw"/>
+                                            <constraint firstItem="e0T-ru-tWD" firstAttribute="top" secondItem="K5I-e5-MvQ" secondAttribute="topMargin" id="9sj-7n-OeZ"/>
+                                            <constraint firstAttribute="trailingMargin" secondItem="e0T-ru-tWD" secondAttribute="trailing" id="BGc-6v-F21"/>
+                                            <constraint firstItem="5y6-vU-qC3" firstAttribute="baseline" secondItem="e0T-ru-tWD" secondAttribute="baseline" id="Ieq-vZ-reG"/>
+                                            <constraint firstItem="e0T-ru-tWD" firstAttribute="leading" secondItem="oUg-jb-mCK" secondAttribute="leading" id="PU1-pi-afh"/>
+                                            <constraint firstAttribute="bottomMargin" secondItem="e0T-ru-tWD" secondAttribute="bottom" constant="2" id="S63-7X-au0"/>
+                                            <constraint firstItem="oUg-jb-mCK" firstAttribute="width" secondItem="e0T-ru-tWD" secondAttribute="width" id="bYX-uk-kK1"/>
+                                            <constraint firstItem="oUg-jb-mCK" firstAttribute="top" secondItem="e0T-ru-tWD" secondAttribute="bottom" id="xJ6-dd-9PN"/>
+                                        </constraints>
+                                    </view>
+                                    <stackView opaque="NO" contentMode="scaleToFill" spacing="8" baselineRelativeArrangement="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4lp-eb-b4C">
+                                        <rect key="frame" x="74.333333333333329" y="129" width="225.33333333333337" height="31"/>
+                                        <subviews>
+                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Stay connected" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="NSs-9e-3Sl">
+                                                <rect key="frame" x="0.0" y="0.0" width="168.33333333333334" height="31"/>
+                                                <fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
+                                                <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                                                <nil key="highlightedColor"/>
+                                            </label>
+                                            <switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="DR7-3h-biq">
+                                                <rect key="frame" x="176.33333333333337" y="0.0" width="51" height="31"/>
+                                                <connections>
+                                                    <action selector="toggleStayConnected:" destination="BYZ-38-t0r" eventType="valueChanged" id="Wci-5s-BJm"/>
+                                                </connections>
+                                            </switch>
+                                        </subviews>
+                                    </stackView>
+                                    <stackView opaque="NO" contentMode="scaleToFill" spacing="8" baselineRelativeArrangement="YES" translatesAutoresizingMaskIntoConstraints="NO" id="lPZ-HJ-Wji">
+                                        <rect key="frame" x="74.333333333333329" y="237.33333333333331" width="225.33333333333337" height="31"/>
+                                        <subviews>
+                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Run alongside G5 app" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3i8-2m-QFG">
+                                                <rect key="frame" x="0.0" y="0.0" width="168.33333333333334" height="31"/>
+                                                <fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
+                                                <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                                                <nil key="highlightedColor"/>
+                                            </label>
+                                            <switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="mjL-My-zyc">
+                                                <rect key="frame" x="176.33333333333337" y="0.0" width="51" height="31"/>
+                                                <connections>
+                                                    <action selector="togglePassiveMode:" destination="BYZ-38-t0r" eventType="valueChanged" id="vGB-RZ-H3T"/>
+                                                </connections>
+                                            </switch>
+                                        </subviews>
+                                    </stackView>
+                                    <activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" animating="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="v7k-HB-Hm0">
+                                        <rect key="frame" x="177" y="345.33333333333331" width="20" height="20"/>
+                                    </activityIndicatorView>
+                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="––" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zAF-A7-1Oc">
+                                        <rect key="frame" x="173.66666666666666" y="442.33333333333331" width="27" height="31.333333333333314"/>
+                                        <fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
+                                        <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                                        <nil key="highlightedColor"/>
+                                    </label>
+                                    <stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" alignment="center" spacing="20" baselineRelativeArrangement="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VKn-a6-Gbj">
+                                        <rect key="frame" x="74" y="551" width="226" height="30"/>
+                                        <subviews>
+                                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="HEQ-Bp-kx7" userLabel="Start">
+                                                <rect key="frame" x="0.0" y="0.0" width="62" height="30"/>
+                                                <state key="normal" title="Start"/>
+                                                <connections>
+                                                    <action selector="start:" destination="BYZ-38-t0r" eventType="touchUpInside" id="CoA-cH-SFe"/>
+                                                </connections>
+                                            </button>
+                                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="HYW-Ex-7LC">
+                                                <rect key="frame" x="82" y="0.0" width="62" height="30"/>
+                                                <state key="normal" title="Calibrate"/>
+                                                <connections>
+                                                    <action selector="calibrate:" destination="BYZ-38-t0r" eventType="touchUpInside" id="5Ft-BY-tyB"/>
+                                                </connections>
+                                            </button>
+                                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="JAM-9K-KeE" userLabel="Stop">
+                                                <rect key="frame" x="164" y="0.0" width="62" height="30"/>
+                                                <state key="normal" title="Stop"/>
+                                                <connections>
+                                                    <action selector="stop:" destination="BYZ-38-t0r" eventType="touchUpInside" id="hWH-qi-m3s"/>
+                                                </connections>
+                                            </button>
+                                        </subviews>
+                                    </stackView>
+                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Waiting for first reading" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9p7-LX-EMK">
+                                        <rect key="frame" x="-20" y="658" width="0.0" height="18"/>
+                                        <fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
+                                        <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                                        <nil key="highlightedColor"/>
+                                    </label>
+                                </subviews>
+                                <constraints>
+                                    <constraint firstItem="NSs-9e-3Sl" firstAttribute="width" secondItem="3i8-2m-QFG" secondAttribute="width" id="drg-Lk-TNg"/>
+                                </constraints>
+                            </stackView>
+                        </subviews>
+                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <constraints>
+                            <constraint firstItem="mqJ-p6-sSU" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" constant="20" id="9eQ-5E-KMl"/>
+                            <constraint firstItem="wfy-db-euE" firstAttribute="top" secondItem="mqJ-p6-sSU" secondAttribute="bottom" constant="20" id="A1x-Bz-xwd"/>
+                            <constraint firstAttribute="trailingMargin" secondItem="mqJ-p6-sSU" secondAttribute="trailing" id="DSk-No-ziO"/>
+                            <constraint firstItem="mqJ-p6-sSU" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" id="m2Y-0g-gsl"/>
+                        </constraints>
+                    </view>
+                    <connections>
+                        <outlet property="passiveModeEnabledSwitch" destination="mjL-My-zyc" id="GAn-9j-EwV"/>
+                        <outlet property="scanningIndicatorView" destination="v7k-HB-Hm0" id="HgS-lj-R6v"/>
+                        <outlet property="stayConnectedSwitch" destination="DR7-3h-biq" id="cN5-a1-x8J"/>
+                        <outlet property="subtitleLabel" destination="9p7-LX-EMK" id="jTI-sZ-dNb"/>
+                        <outlet property="titleLabel" destination="zAF-A7-1Oc" id="W9K-V8-bIj"/>
+                        <outlet property="transmitterIDField" destination="e0T-ru-tWD" id="MTX-44-H7O"/>
+                    </connections>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="231" y="399"/>
+        </scene>
+    </scenes>
+</document>

+ 34 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/CommandQueue.swift

@@ -0,0 +1,34 @@
+//
+//  CommandQueue.swift
+//  CGMBLEKit Example
+//
+//  Created by Paul Dickens on 25/03/2018.
+//  Copyright © 2018 LoopKit Authors. All rights reserved.
+//
+
+import Foundation
+import CGMBLEKit
+
+
+class CommandQueue {
+    private var list = Array<Command>()
+    private var lock = os_unfair_lock()
+
+    func enqueue(_ element: Command) {
+        os_unfair_lock_lock(&lock)
+        list.append(element)
+        os_unfair_lock_unlock(&lock)
+    }
+
+    func dequeue() -> Command? {
+        os_unfair_lock_lock(&lock)
+        defer {
+            os_unfair_lock_unlock(&lock)
+        }
+        if !list.isEmpty {
+            return list.removeFirst()
+        } else {
+            return nil
+        }
+    }
+}

+ 49 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/Info.plist

@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>3.2</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>LSRequiresIPhoneOS</key>
+	<true/>
+	<key>UIBackgroundModes</key>
+	<array>
+		<string>bluetooth-central</string>
+	</array>
+	<key>UILaunchStoryboardName</key>
+	<string>LaunchScreen</string>
+	<key>UIMainStoryboardFile</key>
+	<string>Main</string>
+	<key>UIRequiredDeviceCapabilities</key>
+	<array>
+		<string>armv7</string>
+	</array>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+</dict>
+</plist>

+ 39 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/NSUserDefaults.swift

@@ -0,0 +1,39 @@
+//
+//  NSUserDefaults.swift
+//  xDripG5
+//
+//  Created by Nathan Racklyeft on 11/24/15.
+//  Copyright © 2015 Nathan Racklyeft. All rights reserved.
+//
+
+import Foundation
+
+
+extension UserDefaults {
+    var passiveModeEnabled: Bool {
+        get {
+            return bool(forKey: "passiveModeEnabled")
+        }
+        set {
+            set(newValue, forKey: "passiveModeEnabled")
+        }
+    }
+
+    var stayConnected: Bool {
+        get {
+            return object(forKey: "stayConnected") != nil ? bool(forKey: "stayConnected") : true
+        }
+        set {
+            set(newValue, forKey: "stayConnected")
+        }
+    }
+
+    var transmitterID: String {
+        get {
+            return string(forKey: "transmitterID") ?? "500000"
+        }
+        set {
+            set(newValue, forKey: "transmitterID")
+        }
+    }
+}

+ 200 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/ViewController.swift

@@ -0,0 +1,200 @@
+//
+//  ViewController.swift
+//  xDrip5
+//
+//  Created by Nathan Racklyeft on 10/1/15.
+//  Copyright © 2015 Nathan Racklyeft. All rights reserved.
+//
+
+import UIKit
+import HealthKit
+import CGMBLEKit
+
+class ViewController: UIViewController, TransmitterDelegate, UITextFieldDelegate {
+
+    @IBOutlet weak var titleLabel: UILabel!
+
+    @IBOutlet weak var subtitleLabel: UILabel!
+
+    @IBOutlet weak var passiveModeEnabledSwitch: UISwitch!
+
+    @IBOutlet weak var stayConnectedSwitch: UISwitch!
+
+    @IBOutlet weak var transmitterIDField: UITextField!
+
+    @IBOutlet weak var scanningIndicatorView: UIActivityIndicatorView!
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        passiveModeEnabledSwitch.isOn = AppDelegate.sharedDelegate.transmitter?.passiveModeEnabled ?? false
+
+        stayConnectedSwitch.isOn = AppDelegate.sharedDelegate.transmitter?.stayConnected ?? false
+
+        transmitterIDField.text = AppDelegate.sharedDelegate.transmitter?.ID
+    }
+
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+
+        updateIndicatorViewDisplay()
+    }
+
+    // MARK: - Actions
+
+    func updateIndicatorViewDisplay() {
+        if let transmitter = AppDelegate.sharedDelegate.transmitter, transmitter.isScanning {
+            scanningIndicatorView.startAnimating()
+        } else {
+            scanningIndicatorView.stopAnimating()
+        }
+    }
+
+    @IBAction func toggleStayConnected(_ sender: UISwitch) {
+        AppDelegate.sharedDelegate.transmitter?.stayConnected = sender.isOn
+        UserDefaults.standard.stayConnected = sender.isOn
+
+        updateIndicatorViewDisplay()
+    }
+
+    @IBAction func togglePassiveMode(_ sender: UISwitch) {
+        AppDelegate.sharedDelegate.transmitter?.passiveModeEnabled = sender.isOn
+        UserDefaults.standard.passiveModeEnabled = sender.isOn
+    }
+
+    @IBAction func start(_ sender: UIButton) {
+        let dialog = UIAlertController(title: "Confirm", message: "Start sensor session.", preferredStyle: .alert)
+
+        dialog.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action: UIAlertAction!) in
+            AppDelegate.sharedDelegate.commandQueue.enqueue(.startSensor(at: Date()))
+        }))
+
+        dialog.addAction(UIAlertAction(title: "Cancel", style: .cancel))
+
+        present(dialog, animated: true, completion: nil)
+    }
+
+    @IBAction func calibrate(_ sender: UIButton) {
+        let dialog = UIAlertController(title: "Enter BG", message: "Calibrate sensor.", preferredStyle: .alert)
+
+        let unit = HKUnit.milligramsPerDeciliter
+
+        dialog.addTextField { (textField : UITextField!) in
+            textField.placeholder = unit.unitString
+            textField.keyboardType = .numberPad
+        }
+
+        dialog.addAction(UIAlertAction(title: "Calibrate", style: .default, handler: { (action: UIAlertAction!) in
+            let textField = dialog.textFields![0] as UITextField
+            let minGlucose = HKQuantity(unit: HKUnit.milligramsPerDeciliter, doubleValue: 40)
+            let maxGlucose = HKQuantity(unit: HKUnit.milligramsPerDeciliter, doubleValue: 400)
+
+            if let text = textField.text, let entry = Double(text) {
+                guard entry >= minGlucose.doubleValue(for: unit) && entry <= maxGlucose.doubleValue(for: unit) else {
+                    // TODO: notify the user if the glucose is not in range
+                    return
+                }
+                let glucose = HKQuantity(unit: unit, doubleValue: Double(entry))
+                AppDelegate.sharedDelegate.commandQueue.enqueue(.calibrateSensor(to: glucose, at: Date()))
+            }
+        }))
+
+        dialog.addAction(UIAlertAction(title: "Cancel", style: .cancel))
+
+        present(dialog, animated: true, completion: nil)
+    }
+
+    @IBAction func stop(_ sender: UIButton) {
+        let dialog = UIAlertController(title: "Confirm", message: "Stop sensor session.", preferredStyle: .alert)
+
+        dialog.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action: UIAlertAction!) in
+            AppDelegate.sharedDelegate.commandQueue.enqueue(.stopSensor(at: Date()))
+        }))
+
+        dialog.addAction(UIAlertAction(title: "Cancel", style: .cancel))
+
+        present(dialog, animated: true, completion: nil)
+    }
+
+    // MARK: - UITextFieldDelegate
+
+    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
+        if let text = textField.text {
+            let newString = text.replacingCharacters(in: range.rangeOfString(text), with: string)
+
+            if newString.count > 6 {
+                return false
+            } else if newString.count == 6 {
+                AppDelegate.sharedDelegate.transmitterID = newString
+                textField.text = newString
+
+                textField.resignFirstResponder()
+
+                return false
+            }
+        }
+
+        return true
+    }
+
+    func textFieldDidEndEditing(_ textField: UITextField) {
+        if textField.text?.count != 6 {
+            textField.text = UserDefaults.standard.transmitterID
+        }
+    }
+
+    func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
+        return true
+    }
+
+    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+        return true
+    }
+
+    // MARK: - TransmitterDelegate
+
+    func transmitter(_ transmitter: Transmitter, didError error: Error) {
+        print("Transmitter Error: \(error)")
+        titleLabel.text = NSLocalizedString("Error", comment: "Title displayed during error response")
+
+        subtitleLabel.text = "\(error)"
+    }
+
+    func transmitter(_ transmitter: Transmitter, didRead glucose: Glucose) {
+        let unit = HKUnit.milligramsPerDeciliter
+        if let value = glucose.glucose?.doubleValue(for: unit) {
+            titleLabel.text = "\(value) \(unit.unitString)"
+        } else {
+            titleLabel.text = String(describing: glucose.state)
+        }
+
+
+        let date = glucose.readDate
+        subtitleLabel.text = DateFormatter.localizedString(from: date, dateStyle: .none, timeStyle: .long)
+    }
+
+    func transmitter(_ transmitter: Transmitter, didReadUnknownData data: Data) {
+        titleLabel.text = NSLocalizedString("Unknown Data", comment: "Title displayed during unknown data response")
+        subtitleLabel.text = data.hexadecimalString
+    }
+    
+    func transmitter(_ transmitter: Transmitter, didReadBackfill glucose: [Glucose]) {
+        titleLabel.text = NSLocalizedString("Backfill", comment: "Title displayed during backfill response")
+        subtitleLabel.text = String(describing: glucose.map { $0.glucose })
+    }
+    
+    func transmitterDidConnect(_ transmitter: Transmitter) {
+        // Ignore
+    }
+
+}
+
+
+private extension NSRange {
+    func rangeOfString(_ string: String) -> Range<String.Index> {
+        let startIndex = string.index(string.startIndex, offsetBy: location)
+        let endIndex = string.index(startIndex, offsetBy: length)
+        return startIndex..<endIndex
+    }
+}
+

+ 8 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/da.lproj/Localizable.strings

@@ -0,0 +1,8 @@
+/* Title displayed during backfill response */
+"Backfill" = "Backfill";
+
+/* Title displayed during error response */
+"Error" = "Error";
+
+/* Title displayed during unknown data response */
+"Unknown Data" = "Unknown Data";

+ 27 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/da.lproj/Main.strings

@@ -0,0 +1,27 @@
+
+/* Class = "UILabel"; text = "Run alongside G5 app"; ObjectID = "3i8-2m-QFG"; */
+"3i8-2m-QFG.text" = "Kør langs G5-appen";
+
+/* Class = "UILabel"; text = "Transmitter ID"; ObjectID = "5y6-vU-qC3"; */
+"5y6-vU-qC3.text" = "Senders id";
+
+/* Class = "UILabel"; text = "Waiting for first reading"; ObjectID = "9p7-LX-EMK"; */
+"9p7-LX-EMK.text" = "Venter på førstebehandling";
+
+/* Class = "UIButton"; normalTitle = "Start"; ObjectID = "HEQ-Bp-kx7"; */
+"HEQ-Bp-kx7.normalTitle" = "Start";
+
+/* Class = "UIButton"; normalTitle = "Calibrate"; ObjectID = "HYW-Ex-7LC"; */
+"HYW-Ex-7LC.normalTitle" = "Kalibrer";
+
+/* Class = "UIButton"; normalTitle = "Stop"; ObjectID = "JAM-9K-KeE"; */
+"JAM-9K-KeE.normalTitle" = "Hold op";
+
+/* Class = "UILabel"; text = "Stay connected"; ObjectID = "NSs-9e-3Sl"; */
+"NSs-9e-3Sl.text" = "Forbliv forbundet";
+
+/* Class = "UITextField"; text = "500000"; ObjectID = "e0T-ru-tWD"; */
+"e0T-ru-tWD.text" = "500000";
+
+/* Class = "UILabel"; text = "––"; ObjectID = "zAF-A7-1Oc"; */
+"zAF-A7-1Oc.text" = "––";

+ 8 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/de.lproj/Localizable.strings

@@ -0,0 +1,8 @@
+/* Title displayed during backfill response */
+"Backfill" = "Auffüllen";
+
+/* Title displayed during error response */
+"Error" = "Fehler";
+
+/* Title displayed during unknown data response */
+"Unknown Data" = "Unbekannte Daten";

+ 27 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/de.lproj/Main.strings

@@ -0,0 +1,27 @@
+
+/* Class = "UILabel"; text = "Run alongside G5 app"; ObjectID = "3i8-2m-QFG"; */
+"3i8-2m-QFG.text" = "Neben G5-App ausführen";
+
+/* Class = "UILabel"; text = "Transmitter ID"; ObjectID = "5y6-vU-qC3"; */
+"5y6-vU-qC3.text" = "Transmitter-ID";
+
+/* Class = "UILabel"; text = "Waiting for first reading"; ObjectID = "9p7-LX-EMK"; */
+"9p7-LX-EMK.text" = "Warte auf den ersten Wert";
+
+/* Class = "UIButton"; normalTitle = "Start"; ObjectID = "HEQ-Bp-kx7"; */
+"HEQ-Bp-kx7.normalTitle" = "Start";
+
+/* Class = "UIButton"; normalTitle = "Calibrate"; ObjectID = "HYW-Ex-7LC"; */
+"HYW-Ex-7LC.normalTitle" = "Kalibrieren";
+
+/* Class = "UIButton"; normalTitle = "Stop"; ObjectID = "JAM-9K-KeE"; */
+"JAM-9K-KeE.normalTitle" = "Stop";
+
+/* Class = "UILabel"; text = "Stay connected"; ObjectID = "NSs-9e-3Sl"; */
+"NSs-9e-3Sl.text" = "Verbunden bleiben";
+
+/* Class = "UITextField"; text = "500000"; ObjectID = "e0T-ru-tWD"; */
+"e0T-ru-tWD.text" = "500000";
+
+/* Class = "UILabel"; text = "––"; ObjectID = "zAF-A7-1Oc"; */
+"zAF-A7-1Oc.text" = "––";

+ 8 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/en.lproj/Localizable.strings

@@ -0,0 +1,8 @@
+/* Title displayed during backfill response */
+"Backfill" = "Backfill";
+
+/* Title displayed during error response */
+"Error" = "Error";
+
+/* Title displayed during unknown data response */
+"Unknown Data" = "Unknown Data";

+ 27 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/en.lproj/Main.strings

@@ -0,0 +1,27 @@
+
+/* Class = "UILabel"; text = "Run alongside G5 app"; ObjectID = "3i8-2m-QFG"; */
+"3i8-2m-QFG.text" = "Run alongside G5 app";
+
+/* Class = "UILabel"; text = "Transmitter ID"; ObjectID = "5y6-vU-qC3"; */
+"5y6-vU-qC3.text" = "Transmitter ID";
+
+/* Class = "UILabel"; text = "Waiting for first reading"; ObjectID = "9p7-LX-EMK"; */
+"9p7-LX-EMK.text" = "Waiting for first reading";
+
+/* Class = "UIButton"; normalTitle = "Start"; ObjectID = "HEQ-Bp-kx7"; */
+"HEQ-Bp-kx7.normalTitle" = "Start";
+
+/* Class = "UIButton"; normalTitle = "Calibrate"; ObjectID = "HYW-Ex-7LC"; */
+"HYW-Ex-7LC.normalTitle" = "Calibrate";
+
+/* Class = "UIButton"; normalTitle = "Stop"; ObjectID = "JAM-9K-KeE"; */
+"JAM-9K-KeE.normalTitle" = "Stop";
+
+/* Class = "UILabel"; text = "Stay connected"; ObjectID = "NSs-9e-3Sl"; */
+"NSs-9e-3Sl.text" = "Stay connected";
+
+/* Class = "UITextField"; text = "500000"; ObjectID = "e0T-ru-tWD"; */
+"e0T-ru-tWD.text" = "500000";
+
+/* Class = "UILabel"; text = "––"; ObjectID = "zAF-A7-1Oc"; */
+"zAF-A7-1Oc.text" = "––";

+ 8 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/es.lproj/Localizable.strings

@@ -0,0 +1,8 @@
+/* Title displayed during backfill response */
+"Backfill" = "Rellenar";
+
+/* Title displayed during error response */
+"Error" = "Error";
+
+/* Title displayed during unknown data response */
+"Unknown Data" = "Datos desconocidos";

+ 27 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/es.lproj/Main.strings

@@ -0,0 +1,27 @@
+
+/* Class = "UILabel"; text = "Run alongside G5 app"; ObjectID = "3i8-2m-QFG"; */
+"3i8-2m-QFG.text" = "Ejecutar junto con la app del G5";
+
+/* Class = "UILabel"; text = "Transmitter ID"; ObjectID = "5y6-vU-qC3"; */
+"5y6-vU-qC3.text" = "Identificador del transmisor";
+
+/* Class = "UILabel"; text = "Waiting for first reading"; ObjectID = "9p7-LX-EMK"; */
+"9p7-LX-EMK.text" = "Esperando por el primero dato";
+
+/* Class = "UIButton"; normalTitle = "Start"; ObjectID = "HEQ-Bp-kx7"; */
+"HEQ-Bp-kx7.normalTitle" = "Comenzar";
+
+/* Class = "UIButton"; normalTitle = "Calibrate"; ObjectID = "HYW-Ex-7LC"; */
+"HYW-Ex-7LC.normalTitle" = "Calibrar";
+
+/* Class = "UIButton"; normalTitle = "Stop"; ObjectID = "JAM-9K-KeE"; */
+"JAM-9K-KeE.normalTitle" = "Parar";
+
+/* Class = "UILabel"; text = "Stay connected"; ObjectID = "NSs-9e-3Sl"; */
+"NSs-9e-3Sl.text" = "Mantenerse conectado";
+
+/* Class = "UITextField"; text = "500000"; ObjectID = "e0T-ru-tWD"; */
+"e0T-ru-tWD.text" = "500000";
+
+/* Class = "UILabel"; text = "––"; ObjectID = "zAF-A7-1Oc"; */
+"zAF-A7-1Oc.text" = "––";

+ 9 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/fi.lproj/Localizable.strings

@@ -0,0 +1,9 @@
+/* Title displayed during backfill response */
+"Backfill" = "Täyttö";
+
+/* Title displayed during error response */
+"Error" = "Virhe";
+
+/* Title displayed during unknown data response */
+"Unknown Data" = "Tuntematon tieto";
+

+ 27 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/fi.lproj/Main.strings

@@ -0,0 +1,27 @@
+/* Class = "UILabel"; text = "Run alongside G5 app"; ObjectID = "3i8-2m-QFG"; */
+"3i8-2m-QFG.text" = "Käytä G5-sovelluksen rinnalla";
+
+/* Class = "UILabel"; text = "Transmitter ID"; ObjectID = "5y6-vU-qC3"; */
+"5y6-vU-qC3.text" = "Lähettimen tunniste";
+
+/* Class = "UILabel"; text = "Waiting for first reading"; ObjectID = "9p7-LX-EMK"; */
+"9p7-LX-EMK.text" = "Odotetaan ensimmäistä lukemaa";
+
+/* Class = "UITextField"; text = "500000"; ObjectID = "e0T-ru-tWD"; */
+"e0T-ru-tWD.text" = "500000";
+
+/* Class = "UIButton"; normalTitle = "Start"; ObjectID = "HEQ-Bp-kx7"; */
+"HEQ-Bp-kx7.normalTitle" = "Aloita";
+
+/* Class = "UIButton"; normalTitle = "Calibrate"; ObjectID = "HYW-Ex-7LC"; */
+"HYW-Ex-7LC.normalTitle" = "Kalibroi";
+
+/* Class = "UIButton"; normalTitle = "Stop"; ObjectID = "JAM-9K-KeE"; */
+"JAM-9K-KeE.normalTitle" = "Pysäytä";
+
+/* Class = "UILabel"; text = "Stay connected"; ObjectID = "NSs-9e-3Sl"; */
+"NSs-9e-3Sl.text" = "Pidä yhdistettynä";
+
+/* Class = "UILabel"; text = "––"; ObjectID = "zAF-A7-1Oc"; */
+"zAF-A7-1Oc.text" = "––";
+

+ 8 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/fr.lproj/Localizable.strings

@@ -0,0 +1,8 @@
+/* Title displayed during backfill response */
+"Backfill" = "Récupération des données antérieures";
+
+/* Title displayed during error response */
+"Error" = "Erreur";
+
+/* Title displayed during unknown data response */
+"Unknown Data" = "Donnée inconnue";

+ 27 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/fr.lproj/Main.strings

@@ -0,0 +1,27 @@
+
+/* Class = "UILabel"; text = "Run alongside G5 app"; ObjectID = "3i8-2m-QFG"; */
+"3i8-2m-QFG.text" = "Faire fonctionner l’application G5 en parallèle";
+
+/* Class = "UILabel"; text = "Transmitter ID"; ObjectID = "5y6-vU-qC3"; */
+"5y6-vU-qC3.text" = "ID du transmetteur";
+
+/* Class = "UILabel"; text = "Waiting for first reading"; ObjectID = "9p7-LX-EMK"; */
+"9p7-LX-EMK.text" = "En attente de la première mesure";
+
+/* Class = "UIButton"; normalTitle = "Start"; ObjectID = "HEQ-Bp-kx7"; */
+"HEQ-Bp-kx7.normalTitle" = "Commencer";
+
+/* Class = "UIButton"; normalTitle = "Calibrate"; ObjectID = "HYW-Ex-7LC"; */
+"HYW-Ex-7LC.normalTitle" = "Étalonner";
+
+/* Class = "UIButton"; normalTitle = "Stop"; ObjectID = "JAM-9K-KeE"; */
+"JAM-9K-KeE.normalTitle" = "Arrêter";
+
+/* Class = "UILabel"; text = "Stay connected"; ObjectID = "NSs-9e-3Sl"; */
+"NSs-9e-3Sl.text" = "Rester connecté";
+
+/* Class = "UITextField"; text = "500000"; ObjectID = "e0T-ru-tWD"; */
+"e0T-ru-tWD.text" = "500000";
+
+/* Class = "UILabel"; text = "––"; ObjectID = "zAF-A7-1Oc"; */
+"zAF-A7-1Oc.text" = "––";

+ 1 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/he.lproj/LaunchScreen.strings

@@ -0,0 +1 @@
+

+ 8 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/he.lproj/Localizable.strings

@@ -0,0 +1,8 @@
+/* Title displayed during backfill response */
+"Backfill" = "Backfill";
+
+/* Title displayed during error response */
+"Error" = "Error";
+
+/* Title displayed during unknown data response */
+"Unknown Data" = "Unknown Data";

+ 27 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/he.lproj/Main.strings

@@ -0,0 +1,27 @@
+
+/* Class = "UILabel"; text = "Run alongside G5 app"; ObjectID = "3i8-2m-QFG"; */
+"3i8-2m-QFG.text" = "Run alongside G5 app";
+
+/* Class = "UILabel"; text = "Transmitter ID"; ObjectID = "5y6-vU-qC3"; */
+"5y6-vU-qC3.text" = "Transmitter ID";
+
+/* Class = "UILabel"; text = "Waiting for first reading"; ObjectID = "9p7-LX-EMK"; */
+"9p7-LX-EMK.text" = "Waiting for first reading";
+
+/* Class = "UIButton"; normalTitle = "Start"; ObjectID = "HEQ-Bp-kx7"; */
+"HEQ-Bp-kx7.normalTitle" = "Start";
+
+/* Class = "UIButton"; normalTitle = "Calibrate"; ObjectID = "HYW-Ex-7LC"; */
+"HYW-Ex-7LC.normalTitle" = "Calibrate";
+
+/* Class = "UIButton"; normalTitle = "Stop"; ObjectID = "JAM-9K-KeE"; */
+"JAM-9K-KeE.normalTitle" = "Stop";
+
+/* Class = "UILabel"; text = "Stay connected"; ObjectID = "NSs-9e-3Sl"; */
+"NSs-9e-3Sl.text" = "Stay connected";
+
+/* Class = "UITextField"; text = "500000"; ObjectID = "e0T-ru-tWD"; */
+"e0T-ru-tWD.text" = "500000";
+
+/* Class = "UILabel"; text = "––"; ObjectID = "zAF-A7-1Oc"; */
+"zAF-A7-1Oc.text" = "––";

+ 8 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/it.lproj/Localizable.strings

@@ -0,0 +1,8 @@
+/* Title displayed during backfill response */
+"Backfill" = "Backfill";
+
+/* Title displayed during error response */
+"Error" = "Error";
+
+/* Title displayed during unknown data response */
+"Unknown Data" = "Unknown Data";

+ 27 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/it.lproj/Main.strings

@@ -0,0 +1,27 @@
+
+/* Class = "UILabel"; text = "Run alongside G5 app"; ObjectID = "3i8-2m-QFG"; */
+"3i8-2m-QFG.text" = "Esegui insieme all’app G5";
+
+/* Class = "UILabel"; text = "Transmitter ID"; ObjectID = "5y6-vU-qC3"; */
+"5y6-vU-qC3.text" = "ID trasmettitore";
+
+/* Class = "UILabel"; text = "Waiting for first reading"; ObjectID = "9p7-LX-EMK"; */
+"9p7-LX-EMK.text" = "In attesa della prima lettura";
+
+/* Class = "UIButton"; normalTitle = "Start"; ObjectID = "HEQ-Bp-kx7"; */
+"HEQ-Bp-kx7.normalTitle" = "Avvia";
+
+/* Class = "UIButton"; normalTitle = "Calibrate"; ObjectID = "HYW-Ex-7LC"; */
+"HYW-Ex-7LC.normalTitle" = "Calibra";
+
+/* Class = "UIButton"; normalTitle = "Stop"; ObjectID = "JAM-9K-KeE"; */
+"JAM-9K-KeE.normalTitle" = "Interrompi";
+
+/* Class = "UILabel"; text = "Stay connected"; ObjectID = "NSs-9e-3Sl"; */
+"NSs-9e-3Sl.text" = "Rimani connesso";
+
+/* Class = "UITextField"; text = "500000"; ObjectID = "e0T-ru-tWD"; */
+"e0T-ru-tWD.text" = "500000";
+
+/* Class = "UILabel"; text = "––"; ObjectID = "zAF-A7-1Oc"; */
+"zAF-A7-1Oc.text" = "––";

+ 8 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/ja.lproj/Localizable.strings

@@ -0,0 +1,8 @@
+/* Title displayed during backfill response */
+"Backfill" = "埋め戻し";
+
+/* Title displayed during error response */
+"Error" = "エラー";
+
+/* Title displayed during unknown data response */
+"Unknown Data" = "不明なデータ";

+ 27 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/ja.lproj/Main.strings

@@ -0,0 +1,27 @@
+
+/* Class = "UILabel"; text = "Run alongside G5 app"; ObjectID = "3i8-2m-QFG"; */
+"3i8-2m-QFG.text" = "G5アプリのそばで使用してください";
+
+/* Class = "UILabel"; text = "Transmitter ID"; ObjectID = "5y6-vU-qC3"; */
+"5y6-vU-qC3.text" = "トランスミッタ ID";
+
+/* Class = "UILabel"; text = "Waiting for first reading"; ObjectID = "9p7-LX-EMK"; */
+"9p7-LX-EMK.text" = "最初の読み取りを待っています";
+
+/* Class = "UIButton"; normalTitle = "Start"; ObjectID = "HEQ-Bp-kx7"; */
+"HEQ-Bp-kx7.normalTitle" = "開始";
+
+/* Class = "UIButton"; normalTitle = "Calibrate"; ObjectID = "HYW-Ex-7LC"; */
+"HYW-Ex-7LC.normalTitle" = "較正";
+
+/* Class = "UIButton"; normalTitle = "Stop"; ObjectID = "JAM-9K-KeE"; */
+"JAM-9K-KeE.normalTitle" = "停止";
+
+/* Class = "UILabel"; text = "Stay connected"; ObjectID = "NSs-9e-3Sl"; */
+"NSs-9e-3Sl.text" = "接続を維持";
+
+/* Class = "UITextField"; text = "500000"; ObjectID = "e0T-ru-tWD"; */
+"e0T-ru-tWD.text" = "500000";
+
+/* Class = "UILabel"; text = "––"; ObjectID = "zAF-A7-1Oc"; */
+"zAF-A7-1Oc.text" = "––";

+ 8 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/nb.lproj/Localizable.strings

@@ -0,0 +1,8 @@
+/* Title displayed during backfill response */
+"Backfill" = "Hent historikk";
+
+/* Title displayed during error response */
+"Error" = "Feil";
+
+/* Title displayed during unknown data response */
+"Unknown Data" = "Ukjent data";

+ 27 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/nb.lproj/Main.strings

@@ -0,0 +1,27 @@
+
+/* Class = "UILabel"; text = "Run alongside G5 app"; ObjectID = "3i8-2m-QFG"; */
+"3i8-2m-QFG.text" = "Kjør parralelt med G5 app";
+
+/* Class = "UILabel"; text = "Transmitter ID"; ObjectID = "5y6-vU-qC3"; */
+"5y6-vU-qC3.text" = "Sender ID";
+
+/* Class = "UILabel"; text = "Waiting for first reading"; ObjectID = "9p7-LX-EMK"; */
+"9p7-LX-EMK.text" = "Venter på første måling";
+
+/* Class = "UIButton"; normalTitle = "Start"; ObjectID = "HEQ-Bp-kx7"; */
+"HEQ-Bp-kx7.normalTitle" = "Start";
+
+/* Class = "UIButton"; normalTitle = "Calibrate"; ObjectID = "HYW-Ex-7LC"; */
+"HYW-Ex-7LC.normalTitle" = "Kalibrer";
+
+/* Class = "UIButton"; normalTitle = "Stop"; ObjectID = "JAM-9K-KeE"; */
+"JAM-9K-KeE.normalTitle" = "Stop";
+
+/* Class = "UILabel"; text = "Stay connected"; ObjectID = "NSs-9e-3Sl"; */
+"NSs-9e-3Sl.text" = "Vær tilkoblet";
+
+/* Class = "UITextField"; text = "500000"; ObjectID = "e0T-ru-tWD"; */
+"e0T-ru-tWD.text" = "500000";
+
+/* Class = "UILabel"; text = "––"; ObjectID = "zAF-A7-1Oc"; */
+"zAF-A7-1Oc.text" = "––";

+ 9 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/nl.lproj/Localizable.strings

@@ -0,0 +1,9 @@
+/* Title displayed during backfill response */
+"Backfill" = "Aanvullen van data";
+
+/* Title displayed during error response */
+"Error" = "Fout";
+
+/* Title displayed during unknown data response */
+"Unknown Data" = "Onbekende Data";
+

+ 21 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/nl.lproj/Main.strings

@@ -0,0 +1,21 @@
+/* Class = "UILabel"; text = "Run alongside G5 app"; ObjectID = "3i8-2m-QFG"; */
+"3i8-2m-QFG.text" = "Gebruik tegelijk met G5 app";
+
+/* Class = "UILabel"; text = "Transmitter ID"; ObjectID = "5y6-vU-qC3"; */
+"5y6-vU-qC3.text" = "Zender ID";
+
+/* Class = "UILabel"; text = "Waiting for first reading"; ObjectID = "9p7-LX-EMK"; */
+"9p7-LX-EMK.text" = "Wachten op eerste meting";
+
+/* Class = "UIButton"; normalTitle = "Start"; ObjectID = "HEQ-Bp-kx7"; */
+"HEQ-Bp-kx7.normalTitle" = "Start";
+
+/* Class = "UIButton"; normalTitle = "Calibrate"; ObjectID = "HYW-Ex-7LC"; */
+"HYW-Ex-7LC.normalTitle" = "Kalibreren";
+
+/* Class = "UIButton"; normalTitle = "Stop"; ObjectID = "JAM-9K-KeE"; */
+"JAM-9K-KeE.normalTitle" = "Stop";
+
+/* Class = "UILabel"; text = "Stay connected"; ObjectID = "NSs-9e-3Sl"; */
+"NSs-9e-3Sl.text" = "Blijf verbonden";
+

+ 8 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/pl.lproj/Localizable.strings

@@ -0,0 +1,8 @@
+/* Title displayed during backfill response */
+"Backfill" = "Backfill";
+
+/* Title displayed during error response */
+"Error" = "Error";
+
+/* Title displayed during unknown data response */
+"Unknown Data" = "Unknown Data";

+ 27 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/pl.lproj/Main.strings

@@ -0,0 +1,27 @@
+
+/* Class = "UILabel"; text = "Run alongside G5 app"; ObjectID = "3i8-2m-QFG"; */
+"3i8-2m-QFG.text" = "Przejrzyj aplikację G5";
+
+/* Class = "UILabel"; text = "Transmitter ID"; ObjectID = "5y6-vU-qC3"; */
+"5y6-vU-qC3.text" = "ID nadajnika";
+
+/* Class = "UILabel"; text = "Waiting for first reading"; ObjectID = "9p7-LX-EMK"; */
+"9p7-LX-EMK.text" = "Oczekiwanie na pierwszy odczyt";
+
+/* Class = "UIButton"; normalTitle = "Start"; ObjectID = "HEQ-Bp-kx7"; */
+"HEQ-Bp-kx7.normalTitle" = "Start";
+
+/* Class = "UIButton"; normalTitle = "Calibrate"; ObjectID = "HYW-Ex-7LC"; */
+"HYW-Ex-7LC.normalTitle" = "Kalibruj";
+
+/* Class = "UIButton"; normalTitle = "Stop"; ObjectID = "JAM-9K-KeE"; */
+"JAM-9K-KeE.normalTitle" = "Stop";
+
+/* Class = "UILabel"; text = "Stay connected"; ObjectID = "NSs-9e-3Sl"; */
+"NSs-9e-3Sl.text" = "Nie rozłączaj się";
+
+/* Class = "UITextField"; text = "500000"; ObjectID = "e0T-ru-tWD"; */
+"e0T-ru-tWD.text" = "500000";
+
+/* Class = "UILabel"; text = "––"; ObjectID = "zAF-A7-1Oc"; */
+"zAF-A7-1Oc.text" = "––";

+ 8 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/pt-BR.lproj/Localizable.strings

@@ -0,0 +1,8 @@
+/* Title displayed during backfill response */
+"Backfill" = "Preenchimento";
+
+/* Title displayed during error response */
+"Error" = "Erro";
+
+/* Title displayed during unknown data response */
+"Unknown Data" = "Dados Desconhecidos";

+ 26 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/pt-BR.lproj/Main.strings

@@ -0,0 +1,26 @@
+/* Class = "UILabel"; text = "Run alongside G5 app"; ObjectID = "3i8-2m-QFG"; */
+"3i8-2m-QFG.text" = "Executa em paralelo com o aplicativo G5";
+
+/* Class = "UILabel"; text = "Transmitter ID"; ObjectID = "5y6-vU-qC3"; */
+"5y6-vU-qC3.text" = "ID do Transmissor";
+
+/* Class = "UILabel"; text = "Waiting for first reading"; ObjectID = "9p7-LX-EMK"; */
+"9p7-LX-EMK.text" = "Esperando a primeira leitura";
+
+/* Class = "UIButton"; normalTitle = "Start"; ObjectID = "HEQ-Bp-kx7"; */
+"HEQ-Bp-kx7.normalTitle" = "Iniciar";
+
+/* Class = "UIButton"; normalTitle = "Calibrate"; ObjectID = "HYW-Ex-7LC"; */
+"HYW-Ex-7LC.normalTitle" = "Calibrar";
+
+/* Class = "UIButton"; normalTitle = "Stop"; ObjectID = "JAM-9K-KeE"; */
+"JAM-9K-KeE.normalTitle" = "Parar";
+
+/* Class = "UILabel"; text = "Stay connected"; ObjectID = "NSs-9e-3Sl"; */
+"NSs-9e-3Sl.text" = "Permanecer conectado";
+
+/* Class = "UITextField"; text = "500000"; ObjectID = "e0T-ru-tWD"; */
+"e0T-ru-tWD.text" = "500000";
+
+/* Class = "UILabel"; text = "––"; ObjectID = "zAF-A7-1Oc"; */
+"zAF-A7-1Oc.text" = "––";

+ 8 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/ro.lproj/Localizable.strings

@@ -0,0 +1,8 @@
+/* Title displayed during backfill response */
+"Backfill" = "Completare retroactivă";
+
+/* Title displayed during error response */
+"Error" = "Eroare";
+
+/* Title displayed during unknown data response */
+"Unknown Data" = "Date necunoscute";

+ 27 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/ro.lproj/Main.strings

@@ -0,0 +1,27 @@
+
+/* Class = "UILabel"; text = "Run alongside G5 app"; ObjectID = "3i8-2m-QFG"; */
+"3i8-2m-QFG.text" = "Rulează împreună cu aplicația G5";
+
+/* Class = "UILabel"; text = "Transmitter ID"; ObjectID = "5y6-vU-qC3"; */
+"5y6-vU-qC3.text" = "ID transmițător";
+
+/* Class = "UILabel"; text = "Waiting for first reading"; ObjectID = "9p7-LX-EMK"; */
+"9p7-LX-EMK.text" = "Se așteaptă prima citire";
+
+/* Class = "UIButton"; normalTitle = "Start"; ObjectID = "HEQ-Bp-kx7"; */
+"HEQ-Bp-kx7.normalTitle" = "Pornește";
+
+/* Class = "UIButton"; normalTitle = "Calibrate"; ObjectID = "HYW-Ex-7LC"; */
+"HYW-Ex-7LC.normalTitle" = "Calibrează";
+
+/* Class = "UIButton"; normalTitle = "Stop"; ObjectID = "JAM-9K-KeE"; */
+"JAM-9K-KeE.normalTitle" = "Stop";
+
+/* Class = "UILabel"; text = "Stay connected"; ObjectID = "NSs-9e-3Sl"; */
+"NSs-9e-3Sl.text" = "Păstrează conexiunea";
+
+/* Class = "UITextField"; text = "500000"; ObjectID = "e0T-ru-tWD"; */
+"e0T-ru-tWD.text" = "500000";
+
+/* Class = "UILabel"; text = "––"; ObjectID = "zAF-A7-1Oc"; */
+"zAF-A7-1Oc.text" = "––";

+ 8 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/ru.lproj/Localizable.strings

@@ -0,0 +1,8 @@
+/* Title displayed during backfill response */
+"Backfill" = "Заполнение данными";
+
+/* Title displayed during error response */
+"Error" = "Ошибка";
+
+/* Title displayed during unknown data response */
+"Unknown Data" = "Неизвестные данные";

+ 27 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/ru.lproj/Main.strings

@@ -0,0 +1,27 @@
+
+/* Class = "UILabel"; text = "Run alongside G5 app"; ObjectID = "3i8-2m-QFG"; */
+"3i8-2m-QFG.text" = "запустить совместно с приложением G5";
+
+/* Class = "UILabel"; text = "Transmitter ID"; ObjectID = "5y6-vU-qC3"; */
+"5y6-vU-qC3.text" = "идентификатор трансмиттера";
+
+/* Class = "UILabel"; text = "Waiting for first reading"; ObjectID = "9p7-LX-EMK"; */
+"9p7-LX-EMK.text" = "ожидание данных";
+
+/* Class = "UIButton"; normalTitle = "Start"; ObjectID = "HEQ-Bp-kx7"; */
+"HEQ-Bp-kx7.normalTitle" = "Начать";
+
+/* Class = "UIButton"; normalTitle = "Calibrate"; ObjectID = "HYW-Ex-7LC"; */
+"HYW-Ex-7LC.normalTitle" = "Калибровка";
+
+/* Class = "UIButton"; normalTitle = "Stop"; ObjectID = "JAM-9K-KeE"; */
+"JAM-9K-KeE.normalTitle" = "Стоп";
+
+/* Class = "UILabel"; text = "Stay connected"; ObjectID = "NSs-9e-3Sl"; */
+"NSs-9e-3Sl.text" = "Не разъединяйтесь";
+
+/* Class = "UITextField"; text = "500000"; ObjectID = "e0T-ru-tWD"; */
+"e0T-ru-tWD.text" = "500000";
+
+/* Class = "UILabel"; text = "––"; ObjectID = "zAF-A7-1Oc"; */
+"zAF-A7-1Oc.text" = "––";

+ 9 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/sv.lproj/Localizable.strings

@@ -0,0 +1,9 @@
+/* Title displayed during backfill response */
+"Backfill" = "Fyller i historisk data";
+
+/* Title displayed during error response */
+"Error" = "Fel";
+
+/* Title displayed during unknown data response */
+"Unknown Data" = "Okänd data";
+

+ 27 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/sv.lproj/Main.strings

@@ -0,0 +1,27 @@
+/* Class = "UILabel"; text = "Run alongside G5 app"; ObjectID = "3i8-2m-QFG"; */
+"3i8-2m-QFG.text" = "Kör samtidigt som G5-appen";
+
+/* Class = "UILabel"; text = "Transmitter ID"; ObjectID = "5y6-vU-qC3"; */
+"5y6-vU-qC3.text" = "Sändar-ID";
+
+/* Class = "UILabel"; text = "Waiting for first reading"; ObjectID = "9p7-LX-EMK"; */
+"9p7-LX-EMK.text" = "Väntar på första värdet";
+
+/* Class = "UITextField"; text = "500000"; ObjectID = "e0T-ru-tWD"; */
+"e0T-ru-tWD.text" = "500000";
+
+/* Class = "UIButton"; normalTitle = "Start"; ObjectID = "HEQ-Bp-kx7"; */
+"HEQ-Bp-kx7.normalTitle" = "Starta";
+
+/* Class = "UIButton"; normalTitle = "Calibrate"; ObjectID = "HYW-Ex-7LC"; */
+"HYW-Ex-7LC.normalTitle" = "Kalibrera";
+
+/* Class = "UIButton"; normalTitle = "Stop"; ObjectID = "JAM-9K-KeE"; */
+"JAM-9K-KeE.normalTitle" = "Stoppa";
+
+/* Class = "UILabel"; text = "Stay connected"; ObjectID = "NSs-9e-3Sl"; */
+"NSs-9e-3Sl.text" = "Fortsätt att vara ansluten";
+
+/* Class = "UILabel"; text = "––"; ObjectID = "zAF-A7-1Oc"; */
+"zAF-A7-1Oc.text" = "––";
+

+ 1 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/tr.lproj/LaunchScreen.strings

@@ -0,0 +1 @@
+

+ 8 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/tr.lproj/Localizable.strings

@@ -0,0 +1,8 @@
+/* Title displayed during backfill response */
+"Backfill" = "Backfill";
+
+/* Title displayed during error response */
+"Error" = "Error";
+
+/* Title displayed during unknown data response */
+"Unknown Data" = "Unknown Data";

+ 27 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/tr.lproj/Main.strings

@@ -0,0 +1,27 @@
+
+/* Class = "UILabel"; text = "Run alongside G5 app"; ObjectID = "3i8-2m-QFG"; */
+"3i8-2m-QFG.text" = "Run alongside G5 app";
+
+/* Class = "UILabel"; text = "Transmitter ID"; ObjectID = "5y6-vU-qC3"; */
+"5y6-vU-qC3.text" = "Transmitter ID";
+
+/* Class = "UILabel"; text = "Waiting for first reading"; ObjectID = "9p7-LX-EMK"; */
+"9p7-LX-EMK.text" = "Waiting for first reading";
+
+/* Class = "UIButton"; normalTitle = "Start"; ObjectID = "HEQ-Bp-kx7"; */
+"HEQ-Bp-kx7.normalTitle" = "Start";
+
+/* Class = "UIButton"; normalTitle = "Calibrate"; ObjectID = "HYW-Ex-7LC"; */
+"HYW-Ex-7LC.normalTitle" = "Calibrate";
+
+/* Class = "UIButton"; normalTitle = "Stop"; ObjectID = "JAM-9K-KeE"; */
+"JAM-9K-KeE.normalTitle" = "Stop";
+
+/* Class = "UILabel"; text = "Stay connected"; ObjectID = "NSs-9e-3Sl"; */
+"NSs-9e-3Sl.text" = "Stay connected";
+
+/* Class = "UITextField"; text = "500000"; ObjectID = "e0T-ru-tWD"; */
+"e0T-ru-tWD.text" = "500000";
+
+/* Class = "UILabel"; text = "––"; ObjectID = "zAF-A7-1Oc"; */
+"zAF-A7-1Oc.text" = "––";

+ 8 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/vi.lproj/Localizable.strings

@@ -0,0 +1,8 @@
+/* Title displayed during backfill response */
+"Backfill" = "Lấp đầy";
+
+/* Title displayed during error response */
+"Error" = "Lỗi";
+
+/* Title displayed during unknown data response */
+"Unknown Data" = "Không nhận biết dữ liệu";

+ 27 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/vi.lproj/Main.strings

@@ -0,0 +1,27 @@
+
+/* Class = "UILabel"; text = "Run alongside G5 app"; ObjectID = "3i8-2m-QFG"; */
+"3i8-2m-QFG.text" = "Chạy song song cùng ứng dụng G5";
+
+/* Class = "UILabel"; text = "Transmitter ID"; ObjectID = "5y6-vU-qC3"; */
+"5y6-vU-qC3.text" = "Số ID của Transmitter";
+
+/* Class = "UILabel"; text = "Waiting for first reading"; ObjectID = "9p7-LX-EMK"; */
+"9p7-LX-EMK.text" = "Đang đợi đọc kết quả đầu tiên";
+
+/* Class = "UIButton"; normalTitle = "Start"; ObjectID = "HEQ-Bp-kx7"; */
+"HEQ-Bp-kx7.normalTitle" = "Bắt đầu";
+
+/* Class = "UIButton"; normalTitle = "Calibrate"; ObjectID = "HYW-Ex-7LC"; */
+"HYW-Ex-7LC.normalTitle" = "Hiệu chỉnh";
+
+/* Class = "UIButton"; normalTitle = "Stop"; ObjectID = "JAM-9K-KeE"; */
+"JAM-9K-KeE.normalTitle" = "Dừng lại";
+
+/* Class = "UILabel"; text = "Stay connected"; ObjectID = "NSs-9e-3Sl"; */
+"NSs-9e-3Sl.text" = "Giữ kết  nối";
+
+/* Class = "UITextField"; text = "500000"; ObjectID = "e0T-ru-tWD"; */
+"e0T-ru-tWD.text" = "500000";
+
+/* Class = "UILabel"; text = "––"; ObjectID = "zAF-A7-1Oc"; */
+"zAF-A7-1Oc.text" = "––";

+ 8 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/zh-Hans.lproj/Localizable.strings

@@ -0,0 +1,8 @@
+/* Title displayed during backfill response */
+"Backfill" = "数据回填";
+
+/* Title displayed during error response */
+"Error" = "错误";
+
+/* Title displayed during unknown data response */
+"Unknown Data" = "未知数据";

+ 27 - 0
Dependencies/CGMBLEKit/CGMBLEKit Example/zh-Hans.lproj/Main.strings

@@ -0,0 +1,27 @@
+
+/* Class = "UILabel"; text = "Run alongside G5 app"; ObjectID = "3i8-2m-QFG"; */
+"3i8-2m-QFG.text" = "与G5 App同时使用";
+
+/* Class = "UILabel"; text = "Transmitter ID"; ObjectID = "5y6-vU-qC3"; */
+"5y6-vU-qC3.text" = "发射器编号";
+
+/* Class = "UILabel"; text = "Waiting for first reading"; ObjectID = "9p7-LX-EMK"; */
+"9p7-LX-EMK.text" = "输入第一个校准值";
+
+/* Class = "UIButton"; normalTitle = "Start"; ObjectID = "HEQ-Bp-kx7"; */
+"HEQ-Bp-kx7.normalTitle" = "启动";
+
+/* Class = "UIButton"; normalTitle = "Calibrate"; ObjectID = "HYW-Ex-7LC"; */
+"HYW-Ex-7LC.normalTitle" = "校准";
+
+/* Class = "UIButton"; normalTitle = "Stop"; ObjectID = "JAM-9K-KeE"; */
+"JAM-9K-KeE.normalTitle" = "停止";
+
+/* Class = "UILabel"; text = "Stay connected"; ObjectID = "NSs-9e-3Sl"; */
+"NSs-9e-3Sl.text" = "保持连接";
+
+/* Class = "UITextField"; text = "500000"; ObjectID = "e0T-ru-tWD"; */
+"e0T-ru-tWD.text" = "500000";
+
+/* Class = "UILabel"; text = "––"; ObjectID = "zAF-A7-1Oc"; */
+"zAF-A7-1Oc.text" = "––";

Разлика између датотеке није приказан због своје велике величине
+ 1738 - 17
Dependencies/CGMBLEKit/CGMBLEKit.xcodeproj/project.pbxproj


+ 97 - 0
Dependencies/CGMBLEKit/CGMBLEKit.xcodeproj/xcshareddata/xcschemes/CGMBLEKit Example.xcscheme

@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1330"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "437AFEF62038EC43008C4892"
+               BuildableName = "CGMBLEKit Example.app"
+               BlueprintName = "CGMBLEKit Example"
+               ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "437AFEF62038EC43008C4892"
+            BuildableName = "CGMBLEKit Example.app"
+            BlueprintName = "CGMBLEKit Example"
+            ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "43CABDFC1C3506F100005705"
+               BuildableName = "CGMBLEKitTests.xctest"
+               BlueprintName = "CGMBLEKitTests"
+               ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "437AFEF62038EC43008C4892"
+            BuildableName = "CGMBLEKit Example.app"
+            BlueprintName = "CGMBLEKit Example"
+            ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "437AFEF62038EC43008C4892"
+            BuildableName = "CGMBLEKit Example.app"
+            BlueprintName = "CGMBLEKit Example"
+            ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 87 - 0
Dependencies/CGMBLEKit/CGMBLEKit.xcodeproj/xcshareddata/xcschemes/ResetTransmitter.xcscheme

@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1330"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "4308103220785FEA00B66384"
+               BuildableName = "ResetTransmitter.app"
+               BlueprintName = "ResetTransmitter"
+               ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "4308103220785FEA00B66384"
+            BuildableName = "ResetTransmitter.app"
+            BlueprintName = "ResetTransmitter"
+            ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "4308103220785FEA00B66384"
+            BuildableName = "ResetTransmitter.app"
+            BlueprintName = "ResetTransmitter"
+            ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "4308103220785FEA00B66384"
+            BuildableName = "ResetTransmitter.app"
+            BlueprintName = "ResetTransmitter"
+            ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 76 - 0
Dependencies/CGMBLEKit/CGMBLEKit.xcodeproj/xcshareddata/xcschemes/Shared-watchOS.xcscheme

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1330"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "NO">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "A9AD37DE225EEE850058C179"
+               BuildableName = "CGMBLEKit.framework"
+               BlueprintName = "CGMBLEKit-watchOS"
+               ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "A9AD37DE225EEE850058C179"
+            BuildableName = "CGMBLEKit.framework"
+            BlueprintName = "CGMBLEKit-watchOS"
+            ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "A9AD37DE225EEE850058C179"
+            BuildableName = "CGMBLEKit.framework"
+            BlueprintName = "CGMBLEKit-watchOS"
+            ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 137 - 0
Dependencies/CGMBLEKit/CGMBLEKit.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme

@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1330"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "NO"
+      buildImplicitDependencies = "NO">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "43CABDF21C3506F100005705"
+               BuildableName = "CGMBLEKit.framework"
+               BlueprintName = "CGMBLEKit"
+               ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "43A8EC51210D0A7400A81379"
+               BuildableName = "CGMBLEKitUI.framework"
+               BlueprintName = "CGMBLEKitUI"
+               ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "B4D40D3D23A428BC00D7ECB5"
+               BuildableName = "CGMBLEKitG5Plugin.loopplugin"
+               BlueprintName = "CGMBLEKitG5Plugin"
+               ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "B4D40D2D23A3E91800D7ECB5"
+               BuildableName = "CGMBLEKitG6Plugin.loopplugin"
+               BlueprintName = "CGMBLEKitG6Plugin"
+               ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "43CABDF21C3506F100005705"
+            BuildableName = "CGMBLEKit.framework"
+            BlueprintName = "CGMBLEKit"
+            ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "43CABDFC1C3506F100005705"
+               BuildableName = "CGMBLEKitTests.xctest"
+               BlueprintName = "CGMBLEKitTests"
+               ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "43CABDF21C3506F100005705"
+            BuildableName = "CGMBLEKit.framework"
+            BlueprintName = "CGMBLEKit"
+            ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "43CABDF21C3506F100005705"
+            BuildableName = "CGMBLEKit.framework"
+            BlueprintName = "CGMBLEKit"
+            ReferencedContainer = "container:CGMBLEKit.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 41 - 23
Dependencies/CGMBLEKit/CGMBLEKit/Glucose+SensorDisplayable.swift

@@ -7,9 +7,10 @@
 //
 
 import Foundation
+import LoopKit
 
 
-extension Glucose {
+extension Glucose: GlucoseDisplayable {
     public var isStateValid: Bool {
         return state == .known(.ok) && status == .ok
     }
@@ -38,30 +39,47 @@ extension Glucose {
         return messages.joined(separator: ". ")
     }
 
-//    public var trendType: GlucoseTrend? {
-//        guard trend < Int(Int8.max) else {
-//            return nil
-//        }
-//
-//        switch trend {
-//        case let x where x <= -30:
-//            return .downDownDown
-//        case let x where x <= -20:
-//            return .downDown
-//        case let x where x <= -10:
-//            return .down
-//        case let x where x < 10:
-//            return .flat
-//        case let x where x < 20:
-//            return .up
-//        case let x where x < 30:
-//            return .upUp
-//        default:
-//            return .upUpUp
-//        }
-//    }
+    public var trendType: GlucoseTrend? {
+        guard trend < Int(Int8.max) else {
+            return nil
+        }
+
+        switch trend {
+        case let x where x <= -30:
+            return .downDownDown
+        case let x where x <= -20:
+            return .downDown
+        case let x where x <= -10:
+            return .down
+        case let x where x < 10:
+            return .flat
+        case let x where x < 20:
+            return .up
+        case let x where x < 30:
+            return .upUp
+        default:
+            return .upUpUp
+        }
+    }
 
     public var isLocal: Bool {
         return true
     }
+    
+    // TODO Placeholders. This functionality will come with LOOP-1311
+    public var glucoseRangeCategory: GlucoseRangeCategory? {
+        return nil
+    }
+}
+
+extension Glucose {
+    public var condition: GlucoseCondition? {
+        if glucoseMessage.glucose < GlucoseLimits.minimum {
+            return .belowRange
+        } else if glucoseMessage.glucose > GlucoseLimits.maximum {
+            return .aboveRange
+        } else {
+            return nil
+        }
+    }
 }

+ 7 - 1
Dependencies/CGMBLEKit/CGMBLEKit/Glucose.swift

@@ -9,6 +9,10 @@
 import Foundation
 import HealthKit
 
+enum GlucoseLimits {
+    static var minimum: UInt16 = 40
+    static var maximum: UInt16 = 400
+}
 
 public struct Glucose {
     let glucoseMessage: GlucoseSubMessage
@@ -46,6 +50,7 @@ public struct Glucose {
         self.activationDate = activationDate
 
         sessionStartDate = activationDate.addingTimeInterval(TimeInterval(timeMessage.sessionStartTime))
+        sessionExpDate = activationDate.addingTimeInterval(TimeInterval(timeMessage.sessionStartTime) + (10*24*60*60))
         readDate = activationDate.addingTimeInterval(TimeInterval(glucoseMessage.timestamp))
         lastCalibration = calibrationMessage != nil ? Calibration(calibrationMessage: calibrationMessage!, activationDate: activationDate) : nil
     }
@@ -55,6 +60,7 @@ public struct Glucose {
     public let status: TransmitterStatus
     public let activationDate: Date
     public let sessionStartDate: Date
+    public let sessionExpDate: Date
 
     // MARK: - Glucose Info
     public let lastCalibration: Calibration?
@@ -71,7 +77,7 @@ public struct Glucose {
 
         let unit = HKUnit.milligramsPerDeciliter
 
-        return HKQuantity(unit: unit, doubleValue: Double(glucoseMessage.glucose))
+        return HKQuantity(unit: unit, doubleValue: Double(min(max(glucoseMessage.glucose, GlucoseLimits.minimum), GlucoseLimits.maximum)))
     }
 
     public var state: CalibrationState {

+ 1 - 0
Dependencies/CGMBLEKit/CGMBLEKit/Messages/TransmitterTimeRxMessage.swift

@@ -26,6 +26,7 @@ struct TransmitterTimeRxMessage: TransmitterRxMessage {
         status = data[1]
         currentTime = data[2..<6].toInt()
         sessionStartTime = data[6..<10].toInt()
+
     }
 }
 

+ 255 - 81
Dependencies/CGMBLEKit/CGMBLEKit/TransmitterManager.swift

@@ -5,15 +5,14 @@
 //  Copyright © 2017 LoopKit Authors. All rights reserved.
 //
 
-import os.log
 import HealthKit
+import LoopKit
+import ShareClient
+import os.log
 
-public protocol TransmitterManagerDelegate: AnyObject {
-    func transmitterManager(_ manager: TransmitterManager, didRead glucose: [Glucose])
-    func transmitterManager(_ manager: TransmitterManager, didFailWith error: Error)
-}
 
-public struct TransmitterManagerState: Equatable {
+public struct TransmitterManagerState: RawRepresentable, Equatable {
+    public typealias RawValue = CGMManager.RawStateValue
 
     public static let version = 1
 
@@ -27,6 +26,24 @@ public struct TransmitterManagerState: Equatable {
         self.transmitterID = transmitterID
         self.shouldSyncToRemoteService = shouldSyncToRemoteService
     }
+
+    public init?(rawValue: RawValue) {
+        guard let transmitterID = rawValue["transmitterID"] as? String
+        else {
+            return nil
+        }
+        
+        let shouldSyncToRemoteService = rawValue["shouldSyncToRemoteService"] as? Bool ?? false
+
+        self.init(transmitterID: transmitterID, shouldSyncToRemoteService: shouldSyncToRemoteService)
+    }
+
+    public var rawValue: RawValue {
+        return [
+            "transmitterID": transmitterID,
+            "shouldSyncToRemoteService": shouldSyncToRemoteService,
+        ]
+    }
 }
 
 
@@ -34,9 +51,8 @@ public protocol TransmitterManagerObserver: AnyObject {
     func transmitterManagerDidUpdateLatestReading(_ manager: TransmitterManager)
 }
 
-public class TransmitterManager: TransmitterDelegate {
-    public weak var delegate: TransmitterManagerDelegate?
 
+public class TransmitterManager: TransmitterDelegate {
     private var state: TransmitterManagerState
 
     private let observers = WeakSynchronizedSet<TransmitterManagerObserver>()
@@ -46,13 +62,89 @@ public class TransmitterManager: TransmitterDelegate {
         // TODO: we should decode and persist transmitter session state
         return !state.transmitterID.isEmpty
     }
-
+    
+    public var cgmManagerStatus: CGMManagerStatus {
+        return CGMManagerStatus(hasValidSensorSession: hasValidSensorSession, device: device)
+    }
 
     public required init(state: TransmitterManagerState) {
         self.state = state
         self.transmitter = Transmitter(id: state.transmitterID, passiveModeEnabled: state.passiveModeEnabled)
+        self.shareManager = ShareClientManager()
 
         self.transmitter.delegate = self
+        
+        #if targetEnvironment(simulator)
+        setupSimulatedSampleGenerator()
+        #endif
+
+    }
+    
+    #if targetEnvironment(simulator)
+    var simulatedSampleGeneratorTimer: DispatchSourceTimer?
+
+    private func setupSimulatedSampleGenerator() {
+
+        let timer = DispatchSource.makeTimerSource(queue: DispatchQueue(label: "com.loopkit.simulatedSampleGenerator"))
+        timer.schedule(deadline: .now() + .seconds(10), repeating: .minutes(5))
+        timer.setEventHandler(handler: { [weak self] in
+            self?.generateSimulatedSample()
+        })
+        self.simulatedSampleGeneratorTimer = timer
+        timer.resume()
+    }
+
+    private func generateSimulatedSample() {
+        let timestamp = Date()
+        let syncIdentifier =  "\(self.state.transmitterID) \(timestamp)"
+        let period = TimeInterval(hours: 3)
+        func glucoseValueFunc(timestamp: Date, period: Double) -> Double {
+            return 100 + 20 * cos(timestamp.timeIntervalSinceReferenceDate.remainder(dividingBy: period) / period * Double.pi * 2)
+        }
+        let glucoseValue = glucoseValueFunc(timestamp: timestamp, period: period)
+        let prevGlucoseValue = glucoseValueFunc(timestamp: timestamp - period, period: period)
+        let trendRateValue = glucoseValue - prevGlucoseValue
+        let trend: GlucoseTrend? = {
+            switch trendRateValue {
+            case -0.01...0.01:
+                return .flat
+            case -2 ..< -0.01:
+                return .down
+            case -5 ..< -2:
+                return .downDown
+            case -Double.greatestFiniteMagnitude ..< -5:
+                return .downDownDown
+            case 0.01...2:
+                return .up
+            case 2...5:
+                return .upUp
+            case 5...Double.greatestFiniteMagnitude:
+                return .upUpUp
+            default:
+                return nil
+            }
+        }()
+
+        let quantity = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: glucoseValue)
+        let trendRate = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: trendRateValue)
+        let sample = NewGlucoseSample(date: timestamp, quantity: quantity, condition: nil, trend: trend, trendRate: trendRate, isDisplayOnly: false, wasUserEntered: false, syncIdentifier: syncIdentifier)
+        self.updateDelegate(with: .newData([sample]))
+    }
+    #endif
+
+    required convenience public init?(rawState: CGMManager.RawStateValue) {
+        guard let state = TransmitterManagerState(rawValue: rawState) else {
+            return nil
+        }
+
+        self.init(state: state)
+    }
+
+    public var rawState: CGMManager.RawStateValue {
+        return state.rawValue
+    }
+    
+    func logDeviceCommunication(_ message: String, type: DeviceLogEntryType = .send) {
     }
 
     public var shouldSyncToRemoteService: Bool {
@@ -65,6 +157,23 @@ public class TransmitterManager: TransmitterDelegate {
         }
     }
 
+    public var cgmManagerDelegate: CGMManagerDelegate? {
+        get {
+            return shareManager.cgmManagerDelegate
+        }
+        set {
+            shareManager.cgmManagerDelegate = newValue
+        }
+    }
+
+    public var delegateQueue: DispatchQueue! {
+        get {
+            return shareManager.delegateQueue
+        }
+        set {
+            shareManager.delegateQueue = newValue
+        }
+    }
 
     private(set) public var latestConnection: Date? {
         get {
@@ -76,6 +185,8 @@ public class TransmitterManager: TransmitterDelegate {
     }
     private let lockedLatestConnection: Locked<Date?> = Locked(nil)
 
+    public let shareManager: ShareClientManager
+
     public let transmitter: Transmitter
     let log = OSLog(category: "TransmitterManager")
 
@@ -83,6 +194,25 @@ public class TransmitterManager: TransmitterDelegate {
         return dataIsFresh
     }
 
+    public var glucoseDisplay: GlucoseDisplayable? {
+        let transmitterDate = latestReading?.readDate ?? .distantPast
+        let shareDate = shareManager.latestBackfill?.startDate ?? .distantPast
+
+        if transmitterDate >= shareDate {
+            return latestReading
+        } else {
+            return shareManager.glucoseDisplay
+        }
+    }
+
+    public var managedDataInterval: TimeInterval? {
+        if transmitter.passiveModeEnabled {
+            return .hours(3)
+        }
+
+        return shareManager.managedDataInterval
+    }
+
     private(set) public var latestReading: Glucose? {
         get {
             return lockedLatestReading.value
@@ -102,18 +232,28 @@ public class TransmitterManager: TransmitterDelegate {
         return true
     }
 
-//    public func fetchNewDataIfNeeded(_ completion: @escaping (CGMReadingResult) -> Void) {
-//        // Ensure our transmitter connection is active
-//        transmitter.resumeScanning()
-//
-//        // If our last glucose was less than 4.5 minutes ago, don't fetch.
-//        guard !dataIsFresh else {
-//            completion(.noData)
-//            return
-//        }
-//
-//        log.default("Fetching new glucose from Share because last reading is %{public}.1f minutes old", latestReading?.readDate.timeIntervalSinceNow.minutes ?? 0)
-//    }
+    public func fetchNewDataIfNeeded(_ completion: @escaping (CGMReadingResult) -> Void) {
+        // Ensure our transmitter connection is active
+        transmitter.resumeScanning()
+
+        // If our last glucose was less than 4.5 minutes ago, don't fetch.
+        guard !dataIsFresh else {
+            completion(.noData)
+            return
+        }
+        
+        if let latestReading = latestReading {
+            log.default("Fetching new glucose from Share because last reading is %{public}.1f minutes old", latestReading.readDate.timeIntervalSinceNow.minutes)
+        } else {
+            log.default("Fetching new glucose from Share because we don't have a previous reading")
+        }
+
+        shareManager.fetchNewDataIfNeeded(completion)
+    }
+
+    public var device: HKDevice? {
+        return nil
+    }
 
     public var debugDescription: String {
         return [
@@ -122,27 +262,28 @@ public class TransmitterManager: TransmitterDelegate {
             "latestConnection: \(String(describing: latestConnection))",
             "dataIsFresh: \(dataIsFresh)",
             "providesBLEHeartbeat: \(providesBLEHeartbeat)",
+            shareManager.debugDescription,
             "observers.count: \(observers.cleanupDeallocatedElements().count)",
             String(reflecting: transmitter),
         ].joined(separator: "\n")
     }
 
-//    private func updateDelegate(with result: CGMReadingResult) {
-//        if let manager = self as? CGMManager {
-//            shareManager.delegate.notify { (delegate) in
-//                delegate?.cgmManager(manager, hasNew: result)
-//            }
-//        }
-//
-//        notifyObserversOfLatestReading()
-//    }
+    private func updateDelegate(with result: CGMReadingResult) {
+        if let manager = self as? CGMManager {
+            shareManager.delegate.notify { (delegate) in
+                delegate?.cgmManager(manager, hasNew: result)
+            }
+        }
+
+        notifyObserversOfLatestReading()
+    }
     
     private func notifyDelegateOfStateChange() {
-//        if let manager = self as? CGMManager {
-//            shareManager.delegate.notify { (delegate) in
-//                delegate?.cgmManagerDidUpdateState(manager)
-//            }
-//        }
+        if let manager = self as? CGMManager {
+            shareManager.delegate.notify { (delegate) in
+                delegate?.cgmManagerDidUpdateState(manager)
+            }
+        }
     }
 
 
@@ -151,79 +292,86 @@ public class TransmitterManager: TransmitterDelegate {
     public func transmitterDidConnect(_ transmitter: Transmitter) {
         log.default("%{public}@", #function)
         latestConnection = Date()
-//        logDeviceCommunication("Connected", type: .connection)
+        logDeviceCommunication("Connected", type: .connection)
     }
 
     public func transmitter(_ transmitter: Transmitter, didError error: Error) {
         log.error("%{public}@: %{public}@", #function, String(describing: error))
-//        updateDelegate(with: .error(error))
-//        logDeviceCommunication("Error: \(error)", type: .error)
+        updateDelegate(with: .error(error))
+        logDeviceCommunication("Error: \(error)", type: .error)
     }
 
     public func transmitter(_ transmitter: Transmitter, didRead glucose: Glucose) {
         guard glucose != latestReading else {
-            delegate?.transmitterManager(self, didRead: [])
-//            updateDelegate(with: .noData)
+            updateDelegate(with: .noData)
             return
         }
 
         latestReading = glucose
 
-//        logDeviceCommunication("New reading: \(glucose.readDate)", type: .receive)
+        logDeviceCommunication("New reading: \(glucose.readDate)", type: .receive)
 
         guard glucose.state.hasReliableGlucose else {
             log.default("%{public}@: Unreliable glucose: %{public}@", #function, String(describing: glucose.state))
-            delegate?.transmitterManager(self, didFailWith: CalibrationError.unreliableState(glucose.state))
-//            updateDelegate(with: .error(CalibrationError.unreliableState(glucose.state)))
+            updateDelegate(with: .error(CalibrationError.unreliableState(glucose.state)))
             return
         }
         
-        guard glucose.glucose != nil else {
-            delegate?.transmitterManager(self, didRead: [])
-//            updateDelegate(with: .noData)
+        guard let quantity = glucose.glucose else {
+            updateDelegate(with: .noData)
             return
         }
 
         log.default("%{public}@: New glucose", #function)
 
-        delegate?.transmitterManager(self, didRead: [glucose])
-
-//        updateDelegate(with: .newData([
-//            NewGlucoseSample(
-//                date: glucose.readDate,
-//                quantity: quantity,
-//                trend: glucose.trendType,
-//                isDisplayOnly: glucose.isDisplayOnly,
-//                wasUserEntered: glucose.isDisplayOnly,
-//                syncIdentifier: glucose.syncIdentifier,
-//                device: device
-//            )
-//        ]))
+        updateDelegate(with: .newData([
+            NewGlucoseSample(
+                date: glucose.readDate,
+                quantity: quantity,
+                condition: glucose.condition,
+                trend: glucose.trendType,
+                trendRate: glucose.trendRate,
+                isDisplayOnly: glucose.isDisplayOnly,
+                wasUserEntered: glucose.isDisplayOnly,
+                syncIdentifier: glucose.syncIdentifier,
+                device: device
+            )
+        ]))
     }
 
     public func transmitter(_ transmitter: Transmitter, didReadBackfill glucose: [Glucose]) {
-        let samples = glucose.filter { glucose -> Bool in
-            guard glucose != latestReading, glucose.state.hasReliableGlucose, glucose.glucose != nil else {
-                return false
+        let samples = glucose.compactMap { (glucose) -> NewGlucoseSample? in
+            guard glucose != latestReading, glucose.state.hasReliableGlucose, let quantity = glucose.glucose else {
+                return nil
             }
-            return true
+
+            return NewGlucoseSample(
+                date: glucose.readDate,
+                quantity: quantity,
+                condition: glucose.condition,
+                trend: glucose.trendType,
+                trendRate: glucose.trendRate,
+                isDisplayOnly: glucose.isDisplayOnly,
+                wasUserEntered: glucose.isDisplayOnly,
+                syncIdentifier: glucose.syncIdentifier,
+                device: device
+            )
         }
-        delegate?.transmitterManager(self, didRead: samples)
-//
-//        guard samples.count > 0 else {
-//            return
-//        }
 
-//        updateDelegate(with: .newData(samples))
+        guard samples.count > 0 else {
+            return
+        }
+
+        updateDelegate(with: .newData(samples))
 
-//        logDeviceCommunication("New backfill: \(String(describing: samples.first?.date))", type: .receive)
+        logDeviceCommunication("New backfill: \(String(describing: samples.first?.date))", type: .receive)
     }
 
     public func transmitter(_ transmitter: Transmitter, didReadUnknownData data: Data) {
         log.error("Unknown sensor data: %{public}@", data.hexadecimalString)
         // This can be used for protocol discovery, but isn't necessary for normal operation
 
-//        logDeviceCommunication("Unknown sensor data: \(data.hexadecimalString)", type: .error)
+        logDeviceCommunication("Unknown sensor data: \(data.hexadecimalString)", type: .error)
     }
 }
 
@@ -246,7 +394,7 @@ extension TransmitterManager {
 }
 
 
-public class G5CGMManager: TransmitterManager {
+public class G5CGMManager: TransmitterManager, CGMManager {
     public let managerIdentifier: String = "DexG5Transmitter"
 
     public let localizedTitle = LocalizedString("Dexcom G5", comment: "CGM display title")
@@ -257,7 +405,7 @@ public class G5CGMManager: TransmitterManager {
         return URL(string: "dexcomcgm://")
     }
 
-    public var device: HKDevice? {
+    public override var device: HKDevice? {
         return HKDevice(
             name: "CGMBLEKit",
             manufacturer: "Dexcom",
@@ -270,14 +418,14 @@ public class G5CGMManager: TransmitterManager {
         )
     }
     
-//    func logDeviceCommunication(_ message: String, type: DeviceLogEntryType = .send) {
-//        self.cgmManagerDelegate?.deviceManager(self, logEventForDeviceIdentifier: transmitter.ID, type: type, message: message, completion: nil)
-//    }
+    override func logDeviceCommunication(_ message: String, type: DeviceLogEntryType = .send) {
+        self.cgmManagerDelegate?.deviceManager(self, logEventForDeviceIdentifier: transmitter.ID, type: type, message: message, completion: nil)
+    }
     
 }
 
 
-public class G6CGMManager: TransmitterManager {
+public class G6CGMManager: TransmitterManager, CGMManager {
     public let managerIdentifier: String = "DexG6Transmitter"
 
     public let localizedTitle = LocalizedString("Dexcom G6", comment: "CGM display title")
@@ -288,7 +436,7 @@ public class G6CGMManager: TransmitterManager {
         return nil
     }
 
-    public var device: HKDevice? {
+    public override var device: HKDevice? {
         return HKDevice(
             name: "CGMBLEKit",
             manufacturer: "Dexcom",
@@ -301,9 +449,9 @@ public class G6CGMManager: TransmitterManager {
         )
     }
     
-//    func logDeviceCommunication(_ message: String, type: DeviceLogEntryType = .send) {
-//        self.cgmManagerDelegate?.deviceManager(self, logEventForDeviceIdentifier: transmitter.ID, type: type, message: message, completion: nil)
-//    }
+    override func logDeviceCommunication(_ message: String, type: DeviceLogEntryType = .send) {
+        self.cgmManagerDelegate?.deviceManager(self, logEventForDeviceIdentifier: transmitter.ID, type: type, message: message, completion: nil)
+    }
 }
 
 
@@ -347,3 +495,29 @@ extension CalibrationState {
     }
 }
 
+// MARK: - AlertResponder implementation
+extension G5CGMManager {
+    public func acknowledgeAlert(alertIdentifier: Alert.AlertIdentifier, completion: @escaping (Error?) -> Void) {
+        completion(nil)
+    }
+}
+
+// MARK: - AlertSoundVendor implementation
+extension G5CGMManager {
+    public func getSoundBaseURL() -> URL? { return nil }
+    public func getSounds() -> [Alert.Sound] { return [] }
+}
+
+// MARK: - AlertResponder implementation
+extension G6CGMManager {
+    public func acknowledgeAlert(alertIdentifier: Alert.AlertIdentifier, completion: @escaping (Error?) -> Void) {
+        completion(nil)
+    }
+}
+
+// MARK: - AlertSoundVendor implementation
+extension G6CGMManager {
+    public func getSoundBaseURL() -> URL? { return nil }
+    public func getSounds() -> [Alert.Sound] { return [] }
+}
+

+ 39 - 0
Dependencies/CGMBLEKit/CGMBLEKit/he.lproj/Localizable.strings

@@ -0,0 +1,39 @@
+/* CGM display title */
+"Dexcom G5" = "Dexcom G5";
+
+/* CGM display title */
+"Dexcom G6" = "Dexcom G6";
+
+/* Error description for unreliable state */
+"Glucose data is unavailable" = "Glucose data is unavailable";
+
+/* Describes a low battery */
+"Low Battery" = "Low Battery";
+
+/* Describes a functioning transmitter */
+"OK" = "OK";
+
+/* Timeout error description */
+"Peripheral did not respond in time" = "Peripheral did not respond in time";
+
+/* Not ready error description */
+"Peripheral isnʼt connected" = "Peripheral isnʼt connected";
+
+/* The description of sensor calibration state when sensor calibration is ok. */
+"Sensor calibration is OK" = "Sensor calibration is OK";
+
+/* The description of sensor calibration state when raw value is unknown. (1: missing data details) */
+"Sensor is in unknown state %1$d" = "Sensor is in unknown state %1$d";
+
+/* The description of sensor calibration state when sensor sensor is stopped. */
+"Sensor is stopped" = "Sensor is stopped";
+
+/* The description of sensor calibration state when sensor sensor is warming up. */
+"Sensor is warming up" = "Sensor is warming up";
+
+/* The description of sensor calibration state when sensor needs calibration. */
+"Sensor needs calibration" = "Sensor needs calibration";
+
+/* Error description */
+"Unknown characteristic" = "Unknown characteristic";
+

+ 39 - 0
Dependencies/CGMBLEKit/CGMBLEKit/tr.lproj/Localizable.strings

@@ -0,0 +1,39 @@
+/* CGM display title */
+"Dexcom G5" = "Dexcom G5";
+
+/* CGM display title */
+"Dexcom G6" = "Dexcom G6";
+
+/* Error description for unreliable state */
+"Glucose data is unavailable" = "Glucose data is unavailable";
+
+/* Describes a low battery */
+"Low Battery" = "Low Battery";
+
+/* Describes a functioning transmitter */
+"OK" = "OK";
+
+/* Timeout error description */
+"Peripheral did not respond in time" = "Peripheral did not respond in time";
+
+/* Not ready error description */
+"Peripheral isnʼt connected" = "Peripheral isnʼt connected";
+
+/* The description of sensor calibration state when sensor calibration is ok. */
+"Sensor calibration is OK" = "Sensor calibration is OK";
+
+/* The description of sensor calibration state when raw value is unknown. (1: missing data details) */
+"Sensor is in unknown state %1$d" = "Sensor is in unknown state %1$d";
+
+/* The description of sensor calibration state when sensor sensor is stopped. */
+"Sensor is stopped" = "Sensor is stopped";
+
+/* The description of sensor calibration state when sensor sensor is warming up. */
+"Sensor is warming up" = "Sensor is warming up";
+
+/* The description of sensor calibration state when sensor needs calibration. */
+"Sensor needs calibration" = "Sensor needs calibration";
+
+/* Error description */
+"Unknown characteristic" = "Unknown characteristic";
+

+ 4 - 0
Dependencies/CGMBLEKit/CGMBLEKitG5Plugin/CGMBLEKitG5Plugin-Bridging-Header.h

@@ -0,0 +1,4 @@
+//
+//  Use this file to import your target's public headers that you would like to expose to Swift.
+//
+

+ 25 - 0
Dependencies/CGMBLEKit/CGMBLEKitG5Plugin/CGMBLEKitG5Plugin.swift

@@ -0,0 +1,25 @@
+//
+//  CGMBLEKitG5Plugin.swift
+//  CGMBLEKitG5Plugin
+//
+//  Created by Nathaniel Hamming on 2019-12-19.
+//  Copyright © 2019 LoopKit Authors. All rights reserved.
+//
+
+import os.log
+import LoopKitUI
+import CGMBLEKit
+import CGMBLEKitUI
+
+class CGMBLEKitG5Plugin: NSObject, CGMManagerUIPlugin {
+    private let log = OSLog(category: "CGMBLEKitG5Plugin")
+    
+    public var cgmManagerType: CGMManagerUI.Type? {
+        return G5CGMManager.self
+    }
+    
+    override init() {
+        super.init()
+        log.default("Instantiated")
+    }
+}

+ 30 - 0
Dependencies/CGMBLEKit/CGMBLEKitG5Plugin/Info.plist

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>FMWK</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>NSHumanReadableCopyright</key>
+	<string>Copyright © 2019 LoopKit Authors. All rights reserved.</string>
+	<key>NSPrincipalClass</key>
+	<string>CGMBLEKitG5Plugin</string>
+	<key>com.loopkit.Loop.CGMManagerDisplayName</key>
+	<string>Dexcom G5</string>
+	<key>com.loopkit.Loop.CGMManagerIdentifier</key>
+	<string>DexG5Transmitter</string>
+</dict>
+</plist>

+ 4 - 0
Dependencies/CGMBLEKit/CGMBLEKitG6Plugin/CGMBLEKitG6Plugin-Bridging-Header.h

@@ -0,0 +1,4 @@
+//
+//  Use this file to import your target's public headers that you would like to expose to Swift.
+//
+

+ 25 - 0
Dependencies/CGMBLEKit/CGMBLEKitG6Plugin/CGMBLEKitG6Plugin.swift

@@ -0,0 +1,25 @@
+//
+//  CGMBLEKitG6Plugin.swift
+//  CGMBLEKitG6Plugin
+//
+//  Created by Nathaniel Hamming on 2019-12-13.
+//  Copyright © 2019 LoopKit Authors. All rights reserved.
+//
+
+import os.log
+import LoopKitUI
+import CGMBLEKit
+import CGMBLEKitUI
+
+class CGMBLEKitG6Plugin: NSObject, CGMManagerUIPlugin {    
+    private let log = OSLog(category: "CGMBLEKitG6Plugin")
+    
+    public var cgmManagerType: CGMManagerUI.Type? {
+        return G6CGMManager.self
+    }
+    
+    override init() {
+        super.init()
+        log.default("Instantiated")
+    }
+}

+ 30 - 0
Dependencies/CGMBLEKit/CGMBLEKitG6Plugin/Info.plist

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>FMWK</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>NSHumanReadableCopyright</key>
+	<string>Copyright © 2019 LoopKit Authors. All rights reserved.</string>
+	<key>NSPrincipalClass</key>
+	<string>CGMBLEKitG6Plugin</string>
+	<key>com.loopkit.Loop.CGMManagerDisplayName</key>
+	<string>Dexcom G6</string>
+	<key>com.loopkit.Loop.CGMManagerIdentifier</key>
+	<string>DexG6Transmitter</string>
+</dict>
+</plist>

+ 20 - 0
Dependencies/CGMBLEKit/CGMBLEKitTests/CalibrationDataRxMessageTests.swift

@@ -0,0 +1,20 @@
+//
+//  CalibrationDataRxMessageTests.swift
+//  xDripG5
+//
+//  Created by Nate Racklyeft on 9/18/16.
+//  Copyright © 2016 Nathan Racklyeft. All rights reserved.
+//
+
+import XCTest
+@testable import CGMBLEKit
+
+
+class CalibrationDataRxMessageTests: XCTestCase {
+    
+    func testMessage() {
+        let data = Data(hexadecimalString: "33002b290090012900ae00800050e929001225")!
+        XCTAssertNotNil(CalibrationDataRxMessage(data: data))
+    }
+    
+}

+ 433 - 0
Dependencies/CGMBLEKit/CGMBLEKitTests/GlucoseBackfillMessageTests.swift

@@ -0,0 +1,433 @@
+//
+//  GlucoseBackfillMessageTests.swift
+//  xDripG5Tests
+//
+//  Copyright © 2018 LoopKit Authors. All rights reserved.
+//
+
+import XCTest
+@testable import CGMBLEKit
+
+class GlucoseBackfillMessageTests: XCTestCase {
+
+    func testTxMessage() {
+        let message = GlucoseBackfillTxMessage(byte1: 5, byte2: 2, identifier: 0, startTime: 5439415, endTime: 5440614) // 20 minutes
+
+        XCTAssertEqual(Data(hexadecimalString: "50050200b7ff5200660453000000000000007138")!, message.data)
+    }
+
+    func testRxMessage() {
+        let message = GlucoseBackfillRxMessage(data: Data(hexadecimalString: "51000100b7ff52006604530032000000e6cb9805")!)!
+
+        XCTAssertEqual(.ok, TransmitterStatus(rawValue: message.status))
+        XCTAssertEqual(1, message.backfillStatus)
+        XCTAssertEqual(0, message.identifier)
+        XCTAssertEqual(5439415, message.startTime)
+        XCTAssertEqual(5440614, message.endTime)
+        XCTAssertEqual(50, message.bufferLength)
+        XCTAssertEqual(0xcbe6, message.bufferCRC)
+
+        // 0xbc46
+        // 0b10111100 01000110
+        var buffer = GlucoseBackfillFrameBuffer(identifier: message.identifier)
+        buffer.append(Data(hexadecimalString: "0100bc460000b7ff52008b0006eee30053008500")!)
+        buffer.append(Data(hexadecimalString: "020006eb0f025300800006ee3a0353007e0006f5")!)
+        buffer.append(Data(hexadecimalString: "030066045300790006f8")!)
+
+        XCTAssertEqual(Int(message.bufferLength), buffer.count)
+        XCTAssertEqual(message.bufferCRC, buffer.crc16)
+
+        let messages = buffer.glucose
+        
+        XCTAssertEqual(139, messages[0].glucose)
+        XCTAssertEqual(5439415, messages[0].timestamp)
+        XCTAssertEqual(.known(.ok), CalibrationState(rawValue: messages[0].state))
+        XCTAssertEqual(-18, messages[0].trend)
+
+        XCTAssertEqual(133, messages[1].glucose)
+        XCTAssertEqual(5439715, messages[1].timestamp)
+        XCTAssertEqual(.known(.ok), CalibrationState(rawValue: messages[1].state))
+        XCTAssertEqual(-21, messages[1].trend)
+
+        XCTAssertEqual(128, messages[2].glucose)
+        XCTAssertEqual(5440015, messages[2].timestamp)
+        XCTAssertEqual(.known(.ok), CalibrationState(rawValue: messages[2].state))
+        XCTAssertEqual(-18, messages[2].trend)
+
+        XCTAssertEqual(126, messages[3].glucose)
+        XCTAssertEqual(5440314, messages[3].timestamp)
+        XCTAssertEqual(.known(.ok), CalibrationState(rawValue: messages[3].state))
+        XCTAssertEqual(-11, messages[3].trend)
+
+        XCTAssertEqual(121, messages[4].glucose)
+        XCTAssertEqual(5440614, messages[4].timestamp)
+        XCTAssertEqual(.known(.ok), CalibrationState(rawValue: messages[4].state))
+        XCTAssertEqual(-08, messages[4].trend)
+
+        XCTAssertEqual(message.startTime, messages.first!.timestamp)
+        XCTAssertEqual(message.endTime, messages.last!.timestamp)
+
+        XCTAssertTrue(messages.first!.timestamp <= messages.last!.timestamp)
+    }
+
+    func testGlucoseBackfill2() {
+        let message = GlucoseBackfillTxMessage(byte1: 5, byte2: 2, identifier: 0, startTime: 4648682, endTime: 4650182) // 25 minutes
+
+        XCTAssertEqual(Data(hexadecimalString: "50050200eaee4600c6f446000000000000009f6d")!, message.data, message.data.hexadecimalString)
+
+        let response = GlucoseBackfillRxMessage(data: Data(hexadecimalString: "51000103eaee4600c6f446003a0000004f3ac9e6")!)!
+
+        XCTAssertEqual(.ok, TransmitterStatus(rawValue: response.status))
+        XCTAssertEqual(1, response.backfillStatus)
+        XCTAssertEqual(3, response.identifier)
+        XCTAssertEqual(4648682, response.startTime)
+        XCTAssertEqual(4650182, response.endTime)
+        XCTAssertEqual(58, response.bufferLength)
+        XCTAssertEqual(0x3a4f, response.bufferCRC)
+
+        // 0x6e3c
+        // 0b01101110 00111100
+        var buffer = GlucoseBackfillFrameBuffer(identifier: 0xc0)
+        buffer.append(Data(hexadecimalString: "01c06e3c0000eaee4600920007fd16f046009500")!)
+        buffer.append(Data(hexadecimalString: "02c0070042f14600960007026ef2460099000704")!)
+        buffer.append(Data(hexadecimalString: "03c09af3460093000700c6f44600900007fc")!)
+
+        XCTAssertEqual(Int(response.bufferLength), buffer.count)
+        XCTAssertEqual(response.bufferCRC, buffer.crc16)
+
+        let messages = buffer.glucose
+
+        XCTAssertEqual(response.startTime, messages.first!.timestamp)
+        XCTAssertEqual(response.endTime, messages.last!.timestamp)
+
+        XCTAssertTrue(messages.first!.timestamp <= messages.last!.timestamp)
+
+        XCTAssertEqual(6, messages.count)
+    }
+
+    func testMalformedBackfill() {
+        var buffer = GlucoseBackfillFrameBuffer(identifier: 0)
+        buffer.append(Data(hexadecimalString: "0100bc460000b7ff52008b0006eee30053008500")!)
+        buffer.append(Data(hexadecimalString: "020006eb0f025300800006ee3a0353007e0006")!)
+
+        XCTAssertEqual(3, buffer.glucose.count)
+    }
+
+    func testGlucoseBackfill3() {
+        let response = GlucoseBackfillRxMessage(data: Data(hexadecimalString: "510001023d6a0e00c16d0e00280000005b1a9154")!)!
+
+        XCTAssertEqual(.ok, TransmitterStatus(rawValue: response.status))
+        XCTAssertEqual(1, response.backfillStatus)
+        XCTAssertEqual(2, response.identifier)
+        XCTAssertEqual(944701, response.startTime)
+        XCTAssertEqual(945601, response.endTime)
+        XCTAssertEqual(40, response.bufferLength)
+        XCTAssertEqual(0x1A5B, response.bufferCRC)
+
+        // 0x440c
+        // 0b01000100 00001100
+        var buffer = GlucoseBackfillFrameBuffer(identifier: 0x80)
+        buffer.append(Data(hexadecimalString: "0180440c00003d6a0e005c0007fe696b0e005d00")!)
+        buffer.append(Data(hexadecimalString: "028007ff956c0e005e000700c16d0e005d000700")!)
+
+        XCTAssertEqual(Int(response.bufferLength), buffer.count)
+        XCTAssertEqual(response.bufferCRC, buffer.crc16)
+
+        let messages = buffer.glucose
+
+        XCTAssertEqual(response.startTime, messages.first!.timestamp)
+        XCTAssertEqual(response.endTime, messages.last!.timestamp)
+
+        XCTAssertTrue(messages.first!.timestamp <= messages.last!.timestamp)
+
+        XCTAssertEqual(4, messages.count)
+    }
+
+    func testGlucoseBackfill4() {
+        let response = GlucoseBackfillRxMessage(data: Data(hexadecimalString: "51000103c9740e004d780e0028000000235bd94c")!)!
+
+        XCTAssertEqual(.ok, TransmitterStatus(rawValue: response.status))
+        XCTAssertEqual(1, response.backfillStatus)
+        XCTAssertEqual(3, response.identifier)
+        XCTAssertEqual(947401, response.startTime)
+        XCTAssertEqual(948301, response.endTime)
+        XCTAssertEqual(40, response.bufferLength)
+        XCTAssertEqual(0x5B23, response.bufferCRC)
+
+        // 0x04d0
+        // 0b00000100 11010000
+        var buffer = GlucoseBackfillFrameBuffer(identifier: 0xc0)
+        buffer.append(Data(hexadecimalString: "01c04d0c0000c9740e005a000700f5750e005800")!)
+        buffer.append(Data(hexadecimalString: "02c007ff21770e00590007ff4d780e0059000700")!)
+
+        XCTAssertEqual(Int(response.bufferLength), buffer.count)
+        XCTAssertEqual(response.bufferCRC, buffer.crc16)
+
+        let messages = buffer.glucose
+
+        XCTAssertEqual(response.startTime, messages.first!.timestamp)
+        XCTAssertEqual(response.endTime, messages.last!.timestamp)
+
+        XCTAssertTrue(messages.first!.timestamp <= messages.last!.timestamp)
+
+        XCTAssertEqual(4, messages.count)
+    }
+
+    func testNotGlucoseBackfill1() {
+        let response = GlucoseBackfillRxMessage(data: Data(hexadecimalString: "5100010339410e0085a90e00ac06000070ca9143")!)!
+
+        XCTAssertEqual(.ok, TransmitterStatus(rawValue: response.status))
+        XCTAssertEqual(1, response.backfillStatus)
+        XCTAssertEqual(3, response.identifier)
+        XCTAssertEqual(934201, response.startTime)
+        XCTAssertEqual(960901, response.endTime)
+        XCTAssertEqual(1708, response.bufferLength)
+        XCTAssertEqual(0xCA70, response.bufferCRC)
+
+        // 0x4a4f
+        // 0b01001010 01001111
+        var buffer = GlucoseBackfillFrameBuffer(identifier: 0xc0)
+        buffer.append(Data(hexadecimalString: "01c04a4f4a5558ef554453b7392a0df008571a7f")!)
+        buffer.append(Data(hexadecimalString: "02c0451e0d74bdec596b633cf2b03d511ef3d048")!)
+        buffer.append(Data(hexadecimalString: "03c009145e959ca51f7a1663ca31676b175d7bc7")!)
+        buffer.append(Data(hexadecimalString: "04c0de00c954fcd3281d5163ed873cdc136fca3e")!)
+        buffer.append(Data(hexadecimalString: "05c0c7da188dd5fbb8997206da1cc8d0c22f8434")!)
+        buffer.append(Data(hexadecimalString: "06c04d50b29df06b12e7162f2d73fd553e44e469")!)
+        buffer.append(Data(hexadecimalString: "07c02b4bb61d66cf6e949ee0f07dbe0cc12127ae")!)
+        buffer.append(Data(hexadecimalString: "08c03bf887be09ece7595adfee494b25368103b4")!)
+        buffer.append(Data(hexadecimalString: "09c07eefb9b5398468a53f00355341d19b50c8b1")!)
+        buffer.append(Data(hexadecimalString: "0ac028f0ddb4dc09a2c74deedf7fdff13fcd6b0e")!)
+        buffer.append(Data(hexadecimalString: "0bc0ad2d7311ac9ec1908fb7ee5557c463ea4fea")!)
+        buffer.append(Data(hexadecimalString: "0cc0bf3c62d9aa62d7c3d447c959b51d31fd016d")!)
+        buffer.append(Data(hexadecimalString: "0dc0278116abd1252ad66c894a39ed7c6d72086e")!)
+        buffer.append(Data(hexadecimalString: "0ec0aaee3bf9b05ccb7b23e1c27d777173c4d9fd")!)
+        buffer.append(Data(hexadecimalString: "0fc044048720d76a696249737f999f944995e44e")!)
+        buffer.append(Data(hexadecimalString: "10c0495e4cb7f22327a920a843de1b4522a68108")!)
+        buffer.append(Data(hexadecimalString: "11c058c482389192ed920e322b71900d747a9492")!)
+        buffer.append(Data(hexadecimalString: "12c0eac06906ff4863f0e8da07d1ead29fc15bd3")!)
+        buffer.append(Data(hexadecimalString: "13c0c0be38548fe9e229c64c9c0f3e9b4c4c1d83")!)
+        buffer.append(Data(hexadecimalString: "14c018a936bdde548e4244093e77c87adda0a1cf")!)
+        buffer.append(Data(hexadecimalString: "15c0fb97d1d147dd0bc6552faa4d62ab553e1682")!)
+        buffer.append(Data(hexadecimalString: "16c0f15f8cb77decb934bfe0c711a026dd4bf36b")!)
+        buffer.append(Data(hexadecimalString: "17c0bd268b0eee07ed20a0f3856ea449b1503708")!)
+        buffer.append(Data(hexadecimalString: "18c00872ed5a996a13480b81fc82b6ca1e7dd379")!)
+        buffer.append(Data(hexadecimalString: "19c06fb4c5bc84e63688b0a77edbab85bfb61b45")!)
+        buffer.append(Data(hexadecimalString: "1ac071d29d30edb43db6b8e114bbbcd67f9dd3a9")!)
+        buffer.append(Data(hexadecimalString: "1bc0569e17a8a80c015def11ddce1b8f194ff6e2")!)
+        buffer.append(Data(hexadecimalString: "1cc0df79ffbc1e077fe249b47550feb5dcd53044")!)
+        buffer.append(Data(hexadecimalString: "1dc0b557e2ba03caed61de30221b0330e1cc49b1")!)
+        buffer.append(Data(hexadecimalString: "1ec006f05e739d737939baf8b14a8b7a6faae96e")!)
+        buffer.append(Data(hexadecimalString: "1fc00b82d430e9e75fb8e7e2affbdd292a41fad2")!)
+        buffer.append(Data(hexadecimalString: "20c0fbf8e8f2686aaaf19d2809eecd3bd4f63516")!)
+        buffer.append(Data(hexadecimalString: "21c0a7df809e73538e459c1a9cd27a566f636e22")!)
+        buffer.append(Data(hexadecimalString: "22c0dbb3c23d7d7847dee77311287e6c6b192eb4")!)
+        buffer.append(Data(hexadecimalString: "23c0d30038d70241a80b9e390778a897dd1632cc")!)
+        buffer.append(Data(hexadecimalString: "24c0177b23127b464c07a499abeff05f13e40998")!)
+        buffer.append(Data(hexadecimalString: "25c0855350c7c4a335e95d2e569996639e8341b4")!)
+        buffer.append(Data(hexadecimalString: "26c0d42874475710a50764d4a4166c0e420aff7f")!)
+        buffer.append(Data(hexadecimalString: "27c0facb1d61cb8057de64546fc9f24f93603093")!)
+        buffer.append(Data(hexadecimalString: "28c080befb84f22c60d398f017dde114d0557b27")!)
+        buffer.append(Data(hexadecimalString: "29c07555e92425342c0674b62fa517b13ba0e3b0")!)
+        buffer.append(Data(hexadecimalString: "2ac0923624bce36c89fade1f66bd7ae1e8e7d598")!)
+        buffer.append(Data(hexadecimalString: "2bc0d345ceea668373d31f95b03a6ee7fff1a3b5")!)
+        buffer.append(Data(hexadecimalString: "2cc045e409b8d31dd53ae9d353f35738819fbb79")!)
+        buffer.append(Data(hexadecimalString: "2dc0a5d31fd3c3b7b217d3f79b245d3714b0523d")!)
+        buffer.append(Data(hexadecimalString: "2ec0eb576e0193584bff8ecada0dc54e4ebde86c")!)
+        buffer.append(Data(hexadecimalString: "2fc092b8ef52003f8b76e90d920ca738c998bb70")!)
+        buffer.append(Data(hexadecimalString: "30c07cfa0f7a69d14b79f605d254a164fd67c658")!)
+        buffer.append(Data(hexadecimalString: "31c049a329162e03f41c12db845b73301f5bbb81")!)
+        buffer.append(Data(hexadecimalString: "32c08a21ca0995b5aa413897ea9e2b7c563ced07")!)
+        buffer.append(Data(hexadecimalString: "33c05d51a18e19209f1c55054bd2f74677c71070")!)
+        buffer.append(Data(hexadecimalString: "34c0299e29ae5576a220b0b767fc4e898aaf2df1")!)
+        buffer.append(Data(hexadecimalString: "35c0bbb554546b69c53b4b3a63bd524bfbe728e6")!)
+        buffer.append(Data(hexadecimalString: "36c0cd4e8c6e10e72950e66bfa0d23b954a7aede")!)
+        buffer.append(Data(hexadecimalString: "37c0ea5df836af737298d44b4b156ced47727920")!)
+        buffer.append(Data(hexadecimalString: "38c02303edefc4916cfdba55829426c153d0d30c")!)
+        buffer.append(Data(hexadecimalString: "39c0dfee091fea60c2da239c9aabef8eddbe49b5")!)
+        buffer.append(Data(hexadecimalString: "3ac02788f23fb030e7606329ed24cbee10bc20eb")!)
+        buffer.append(Data(hexadecimalString: "3bc00a601d46c10bab8cdf04513a47550b0e4fe5")!)
+        buffer.append(Data(hexadecimalString: "3cc072ea5e514432c81e325464e1ac2d659378d2")!)
+        buffer.append(Data(hexadecimalString: "3dc0f050e994caa508fdea7202ed70a4acc6e8ab")!)
+        buffer.append(Data(hexadecimalString: "3ec069ab0d13863943415b492569db29b9594dbe")!)
+        buffer.append(Data(hexadecimalString: "3fc02c37277a98b88956f0def9ad866f44ca6d9f")!)
+        buffer.append(Data(hexadecimalString: "40c0e5bd6aa2dbd835fab2ec238de4a635a3f6cb")!)
+        buffer.append(Data(hexadecimalString: "41c0aafa8812d94d5fe722b3ecfb74eb4c12c622")!)
+        buffer.append(Data(hexadecimalString: "42c08c5b4bb2f28069fc6f9dcb26bc84c0cc01c7")!)
+        buffer.append(Data(hexadecimalString: "43c04ad95cefa1f62a18fa2c5a05bac208685cdb")!)
+        buffer.append(Data(hexadecimalString: "44c0ffe910ddc010b30f457578ab24a866b8a94d")!)
+        buffer.append(Data(hexadecimalString: "45c01b0bb36e58f401eb15da2e6710721e39c573")!)
+        buffer.append(Data(hexadecimalString: "46c06165075618fc9626c53acdd9cb8bcfb0719f")!)
+        buffer.append(Data(hexadecimalString: "47c081599f76725e30d4de39cdcc7f7c0c918d68")!)
+        buffer.append(Data(hexadecimalString: "48c0563b99dce4913105b793f4d539fe668feef6")!)
+        buffer.append(Data(hexadecimalString: "49c04ebaaf9f4dfda6cac4d617cd07098fec39f0")!)
+        buffer.append(Data(hexadecimalString: "4ac04c1ae961bc4f3e2cd395396dc8098bbf4bd5")!)
+        buffer.append(Data(hexadecimalString: "4bc0d95ed88f296e8d68c35085af86e5ef8d8bf0")!)
+        buffer.append(Data(hexadecimalString: "4cc0658ccce111259ce8ac5cbedfc46deda77433")!)
+        buffer.append(Data(hexadecimalString: "4dc05fda2f8d2885082db4b1356c5e2a0e830471")!)
+        buffer.append(Data(hexadecimalString: "4ec066c7813ff84a9da11fe343e5a95bbfa3082c")!)
+        buffer.append(Data(hexadecimalString: "4fc03bcfd6fe6d9657d04f06ed7bc461ebe18d47")!)
+        buffer.append(Data(hexadecimalString: "50c035bbe880ba24d7c84f73ae061b33d62a1845")!)
+        buffer.append(Data(hexadecimalString: "51c0650f0a6bbc91b2771549cf49a5a4faf8b278")!)
+        buffer.append(Data(hexadecimalString: "52c07ac551477e6cd10fe6a3b43d62b02569d110")!)
+        buffer.append(Data(hexadecimalString: "53c005f79d6de0ec017e7a0c98961ce6770f885d")!)
+        buffer.append(Data(hexadecimalString: "54c0d05fee0b5f5bf9de8c61b58f8634ecbf3347")!)
+        buffer.append(Data(hexadecimalString: "55c0e0c7d345fbc40f35aed12e82f8ccb0ed9335")!)
+        buffer.append(Data(hexadecimalString: "56c0b1c8b263179e")!)
+
+        XCTAssertEqual(Int(response.bufferLength), buffer.count)
+        XCTAssertEqual(response.bufferCRC, buffer.crc16)
+
+        let messages = buffer.glucose
+
+        XCTAssertNotEqual(response.startTime, messages.first!.timestamp)
+        XCTAssertNotEqual(response.endTime, messages.last!.timestamp)
+
+        XCTAssertEqual(191, messages.count)
+    }
+
+    func testNotGlucoseBackfill2() {
+        let response = GlucoseBackfillRxMessage(data: Data(hexadecimalString: "51000102b1aa0e00e5b20e00a000000020a39b7e")!)!
+
+        XCTAssertEqual(.ok, TransmitterStatus(rawValue: response.status))
+        XCTAssertEqual(1, response.backfillStatus)
+        XCTAssertEqual(2, response.identifier)
+        XCTAssertEqual(961201, response.startTime)
+        XCTAssertEqual(963301, response.endTime)
+        XCTAssertEqual(160, response.bufferLength)
+        XCTAssertEqual(0xA320, response.bufferCRC)
+
+        // 0xcde3
+        // 0b11001101 11100011
+        var buffer = GlucoseBackfillFrameBuffer(identifier: 0x80)
+        buffer.append(Data(hexadecimalString: "0180cde3fd48248e37a7bf6c2d9d78d4bfef6d5b")!)
+        buffer.append(Data(hexadecimalString: "02809f074c9039b6d3b841f422cf36398338f98c")!)
+        buffer.append(Data(hexadecimalString: "038004160a5a1ad37c382f3ca23ea215c644f7b6")!)
+        buffer.append(Data(hexadecimalString: "04802ed7376fa7c83c3ecf0b645233f9b3c80238")!)
+        buffer.append(Data(hexadecimalString: "05805692724e630a703f01b0a942250f725553d2")!)
+        buffer.append(Data(hexadecimalString: "06804ca2727a4051033a550da80905caf77c735d")!)
+        buffer.append(Data(hexadecimalString: "07808f937b4b9602c5dd6fa13ae983e00783b28e")!)
+        buffer.append(Data(hexadecimalString: "088069846e672c106b339159ead9ee1c08e1a159")!)
+
+        XCTAssertEqual(Int(response.bufferLength), buffer.count)
+        XCTAssertEqual(response.bufferCRC, buffer.crc16)
+
+        let messages = buffer.glucose
+
+        XCTAssertNotEqual(response.startTime, messages.first!.timestamp)
+        XCTAssertNotEqual(response.endTime, messages.last!.timestamp)
+        XCTAssertFalse(messages.first!.timestamp <= messages.last!.timestamp)
+
+        XCTAssertEqual(17, messages.count)
+    }
+
+    func testNotGlucoseBackfill3() {
+        let response = GlucoseBackfillRxMessage(data: Data(hexadecimalString: "51000102b6a36500010c6600ac0600000147db0a")!)!
+
+        XCTAssertEqual(.ok, TransmitterStatus(rawValue: response.status))
+        XCTAssertEqual(1, response.backfillStatus)
+        XCTAssertEqual(2, response.identifier)
+        XCTAssertEqual(6661046, response.startTime)
+        XCTAssertEqual(6687745, response.endTime)
+        XCTAssertEqual(1708, response.bufferLength)
+        XCTAssertEqual(0x4701, response.bufferCRC)
+
+        var buffer = GlucoseBackfillFrameBuffer(identifier: 0x80)
+        buffer.append(Data(hexadecimalString: "0180e1234bdf92845cec52822a8894854582b2b2")!)
+        buffer.append(Data(hexadecimalString: "02800f8a38cc876ad33ae0acdc25921132cc6f0d")!)
+        buffer.append(Data(hexadecimalString: "038032a6cd9e6d447916dd0b9699e499ae79b8d1")!)
+        buffer.append(Data(hexadecimalString: "048045f4b95e0ad80955d3a899d6083bd142f863")!)
+        buffer.append(Data(hexadecimalString: "05809cf9c189744ab66f6ca5c2833ef27442fa71")!)
+        buffer.append(Data(hexadecimalString: "068053694b279275f0d23eb826681e20e5ebb79d")!)
+        buffer.append(Data(hexadecimalString: "078098b921155eb5aed63119d5faec3ef3e53a37")!)
+        buffer.append(Data(hexadecimalString: "08807c87277557a0828e8dc81ff76f1a6e197103")!)
+        buffer.append(Data(hexadecimalString: "0980b8378b133898ce73f7989d67360123e9fdd8")!)
+        buffer.append(Data(hexadecimalString: "0a80383ce19d943a38796b594ff95a2dc93bd6a2")!)
+        buffer.append(Data(hexadecimalString: "0b806b548c5997dc67ed4fe07bcf236d59dd7f94")!)
+        buffer.append(Data(hexadecimalString: "0c802cb2382f40a06fde5f2dff3f0b8226a11f12")!)
+        buffer.append(Data(hexadecimalString: "0d8029800ae513c5b7bc8ea733544b7da84ded17")!)
+        buffer.append(Data(hexadecimalString: "0e80a95b6c3d36183e4409f916a6f1f775af338e")!)
+        buffer.append(Data(hexadecimalString: "0f80d098732f2abcf4a90628f321a048349142ff")!)
+        buffer.append(Data(hexadecimalString: "108077294e9d029bdc0602c76671d88ff4a87596")!)
+        buffer.append(Data(hexadecimalString: "1180bac50f8d705f6732c34b935a0b06545d6d8f")!)
+        buffer.append(Data(hexadecimalString: "1280cf6b9eb0d2f0059c1a7b5c65acb83eb43836")!)
+        buffer.append(Data(hexadecimalString: "13802f408f68fc7e48858daecf64d01f3f61827e")!)
+        buffer.append(Data(hexadecimalString: "1480cd5975c1062ed45311a2602c0bbc9c78cf21")!)
+        buffer.append(Data(hexadecimalString: "1580b6e27f3350bc7d4eb908313710931cbd4f23")!)
+        buffer.append(Data(hexadecimalString: "168061f70e5e27e8b72faecfbb58b6b6ff65cbf0")!)
+        buffer.append(Data(hexadecimalString: "178066bdd3a0b1e1ed0af8b2af88dcb1f4b1c3a4")!)
+        buffer.append(Data(hexadecimalString: "18801eb9326019bca25b74804d196c04d079e495")!)
+        buffer.append(Data(hexadecimalString: "1980a29097393f81aaef79ef421af54ccd3c35ed")!)
+        buffer.append(Data(hexadecimalString: "1a80a3039b0372ddd79ef65293e4e99484573ab3")!)
+        buffer.append(Data(hexadecimalString: "1b807e755140ea79b1913a7c491e606b7d1e4542")!)
+        buffer.append(Data(hexadecimalString: "1c800c968daf03958bd8784e1cf8cea4fa903a80")!)
+        buffer.append(Data(hexadecimalString: "1d8044c5c7baebadbf8e6877d725ab84484e6755")!)
+        buffer.append(Data(hexadecimalString: "1e8036be160e8a03d2c07552fc513c8869170528")!)
+        buffer.append(Data(hexadecimalString: "1f8038483ab634e7707e9ab8c8e3f87dd67f423f")!)
+        buffer.append(Data(hexadecimalString: "2080f184e4457558d9b7944f21d6421b717ddfb1")!)
+        buffer.append(Data(hexadecimalString: "2180bb4da6197852102a3a04b8acccea3c54f0f9")!)
+        buffer.append(Data(hexadecimalString: "2280da93975f3ea1c39d2aff5dbbc4b183b66044")!)
+        buffer.append(Data(hexadecimalString: "23804678951cdc83923fe5a88bda66221a48360b")!)
+        buffer.append(Data(hexadecimalString: "2480aa9dc3fee16106bd551754d896da72ff772c")!)
+        buffer.append(Data(hexadecimalString: "2580b825bb4eba580b57caadda1b90b449a8f2c5")!)
+        buffer.append(Data(hexadecimalString: "2680117b62c286b395d2bf016848c65953595f19")!)
+        buffer.append(Data(hexadecimalString: "27806d524b2b191bd9582f47fd3956ab851207af")!)
+        buffer.append(Data(hexadecimalString: "2880c7df85c2ee5e9b3f5ae68ffba44a86e237e8")!)
+        buffer.append(Data(hexadecimalString: "2980947fec3646851a510c8a61c0b3b7d90e410b")!)
+        buffer.append(Data(hexadecimalString: "2a8014b04b3ff32e4d9d16f46880533cf4562af4")!)
+        buffer.append(Data(hexadecimalString: "2b80c754e48edfa84f2f3b29976ce59cc110747d")!)
+        buffer.append(Data(hexadecimalString: "2c8095a3ab4b66254954a51ca5e5c92d07be80fc")!)
+        buffer.append(Data(hexadecimalString: "2d80bc4afa73d7f222f1b9e56083171057e32ca3")!)
+        buffer.append(Data(hexadecimalString: "2e80c88dbe9a052d7ffd29d2f665bdd66811712f")!)
+        buffer.append(Data(hexadecimalString: "2f804d2f9ee36fd6f3f48c30429c1629e39bbe3f")!)
+        buffer.append(Data(hexadecimalString: "30808b01f598fc6420d85b3190d15f8d55f43faf")!)
+        buffer.append(Data(hexadecimalString: "31801c171908c8ded10e81123f453c571c8f5199")!)
+        buffer.append(Data(hexadecimalString: "32806275a5652f2447f63f1ab5d0dac84387d80c")!)
+        buffer.append(Data(hexadecimalString: "3380f095361816ab06f0209a6ec3411c8f0c6ce1")!)
+        buffer.append(Data(hexadecimalString: "3480a99ac0dae0c87f6a1d4ee4fe4e19671c29ba")!)
+        buffer.append(Data(hexadecimalString: "3580811db50e1625a3b88305ea5c34b53e20700e")!)
+        buffer.append(Data(hexadecimalString: "36800fbf211b6a454c788aa17b0cf14db76695a9")!)
+        buffer.append(Data(hexadecimalString: "3780dfc186d1c189114f182709efc464f48c6b2f")!)
+        buffer.append(Data(hexadecimalString: "38805e629e8e6457b1ec149897210cb6336b123f")!)
+        buffer.append(Data(hexadecimalString: "398045d4dc9f4c074ec0e926a8d1768ae92b4866")!)
+        buffer.append(Data(hexadecimalString: "3a801edf0d5d1c1a86c90c5eeef69e115fdd513a")!)
+        buffer.append(Data(hexadecimalString: "3b8084223228b158081b465c74454450ec19a4c1")!)
+        buffer.append(Data(hexadecimalString: "3c80fa306d71fc211bd9b9e55aeb16c582d21ec2")!)
+        buffer.append(Data(hexadecimalString: "3d8072d8bbec74f1436958db431a92fc66cf5dd2")!)
+        buffer.append(Data(hexadecimalString: "3e80888ef69a91f8dbb0ce70b6e5ec9289245878")!)
+        buffer.append(Data(hexadecimalString: "3f8069c0d6d14e580be92f87a3255e124b25b451")!)
+        buffer.append(Data(hexadecimalString: "4080b3cbae3d50ea52720bf5029243a4a9fea906")!)
+        buffer.append(Data(hexadecimalString: "4180384321d07a4b5378aa272c9a7247830624b8")!)
+        buffer.append(Data(hexadecimalString: "4280acf0b265dd82b68aeec5114161a34135b30e")!)
+        buffer.append(Data(hexadecimalString: "43802d709c604266db64a4b5a5e6f6d8cfd7ece1")!)
+        buffer.append(Data(hexadecimalString: "44807b48711b0630cd919dbf9ea7bf81efa1e8f1")!)
+        buffer.append(Data(hexadecimalString: "4580c0282b679f9746ece875482d5e9a5ed59cb8")!)
+        buffer.append(Data(hexadecimalString: "46808c7b718de4299f081449cce9aa9afadfcea9")!)
+        buffer.append(Data(hexadecimalString: "478066cd4c36d6e816413b15955c958da4d8e866")!)
+        buffer.append(Data(hexadecimalString: "48809b5170078157c542236bc7a09c96bc559069")!)
+        buffer.append(Data(hexadecimalString: "49800be65a0bce639c69cd3d64db0fa22570756f")!)
+        buffer.append(Data(hexadecimalString: "4a80e5ebd5381b077a8ac56e952b631256a076cc")!)
+        buffer.append(Data(hexadecimalString: "4b80fb32d28e39021d49dc7b7ee65272ca1f28c1")!)
+        buffer.append(Data(hexadecimalString: "4c8004486cc3dcad9f39c602d3ed9030e327cec3")!)
+        buffer.append(Data(hexadecimalString: "4d809a5800c6d647c5f99e40a15327957745dce1")!)
+        buffer.append(Data(hexadecimalString: "4e80d03a0b5368fda78b28d3975500ab160ac693")!)
+        buffer.append(Data(hexadecimalString: "4f80dbc5ea65f540933f858a425ecdb378f62990")!)
+        buffer.append(Data(hexadecimalString: "50802e7980ce9365ad4e434308fb2a8102dc9f6a")!)
+        buffer.append(Data(hexadecimalString: "5180b71311e183ad9feecfd43b68072d5a9ad4af")!)
+        buffer.append(Data(hexadecimalString: "5280e721c37d2b57f95cbf5f51025fb22b6ca60c")!)
+        buffer.append(Data(hexadecimalString: "53805749eb01f070a5b015dcd0f68f5fea0b40c6")!)
+        buffer.append(Data(hexadecimalString: "5480fae4ee747357e4d73265ad9411c565c41865")!)
+        buffer.append(Data(hexadecimalString: "5580b75e9c62c7c2aa3ea3f94d219ef7330077d7")!)
+        buffer.append(Data(hexadecimalString: "5680f2c59ee6b54a")!)
+
+        XCTAssertEqual(Int(response.bufferLength), buffer.count)
+        XCTAssertEqual(response.bufferCRC, buffer.crc16)
+
+        let messages = buffer.glucose
+
+        XCTAssertNotEqual(response.startTime, messages.first!.timestamp)
+        XCTAssertNotEqual(response.endTime, messages.last!.timestamp)
+        XCTAssertFalse(messages.first!.timestamp <= messages.last!.timestamp)
+
+        XCTAssertEqual(191, messages.count)
+    }
+}

+ 79 - 0
Dependencies/CGMBLEKit/CGMBLEKitTests/GlucoseRxMessageTests.swift

@@ -0,0 +1,79 @@
+//
+//  GlucoseRxMessageTests.swift
+//  xDripG5
+//
+//  Created by Nathan Racklyeft on 3/5/16.
+//  Copyright © 2016 Nathan Racklyeft. All rights reserved.
+//
+
+import XCTest
+@testable import CGMBLEKit
+
+
+class GlucoseRxMessageTests: XCTestCase {
+
+    func testMessageData() {
+        let data = Data(hexadecimalString: "3100680a00008a715700cc0006ffc42a")!
+        let message = GlucoseRxMessage(data: data)!
+
+        XCTAssertEqual(0, message.status)
+        XCTAssertEqual(2664, message.sequence)
+        XCTAssertEqual(5730698, message.glucose.timestamp)
+        XCTAssertFalse(message.glucose.glucoseIsDisplayOnly)
+        XCTAssertEqual(204, message.glucose.glucose)
+        XCTAssertEqual(6, message.glucose.state)
+        XCTAssertEqual(-1, message.glucose.trend)
+    }
+
+    func testNegativeTrend() {
+        let data = Data(hexadecimalString: "31006f0a0000be7957007a0006e4818d")!
+        let message = GlucoseRxMessage(data: data)!
+
+        XCTAssertEqual(0, message.status)
+        XCTAssertEqual(2671, message.sequence)
+        XCTAssertEqual(5732798, message.glucose.timestamp)
+        XCTAssertFalse(message.glucose.glucoseIsDisplayOnly)
+        XCTAssertEqual(122, message.glucose.glucose)
+        XCTAssertEqual(6, message.glucose.state)
+        XCTAssertEqual(-28, message.glucose.trend)
+    }
+
+    func testDisplayOnly() {
+        let data = Data(hexadecimalString: "3100700a0000f17a5700584006e3cee9")!
+        let message = GlucoseRxMessage(data: data)!
+
+        XCTAssertEqual(0, message.status)
+        XCTAssertEqual(2672, message.sequence)
+        XCTAssertEqual(5733105, message.glucose.timestamp)
+        XCTAssertTrue(message.glucose.glucoseIsDisplayOnly)
+        XCTAssertEqual(88, message.glucose.glucose)
+        XCTAssertEqual(6, message.glucose.state)
+        XCTAssertEqual(-29, message.glucose.trend)
+    }
+
+    func testOldTransmitter() {
+        let data = Data(hexadecimalString: "3100aa00000095a078008b00060a8b34")!
+        let message = GlucoseRxMessage(data: data)!
+
+        XCTAssertEqual(0, message.status)
+        XCTAssertEqual(170, message.sequence)
+        XCTAssertEqual(7905429, message.glucose.timestamp)  // 90 days, status is still OK
+        XCTAssertFalse(message.glucose.glucoseIsDisplayOnly)
+        XCTAssertEqual(139, message.glucose.glucose)
+        XCTAssertEqual(6, message.glucose.state)
+        XCTAssertEqual(10, message.glucose.trend)
+    }
+
+    func testZeroSequence() {
+        let data = Data(hexadecimalString: "3100000000008eb14d00820006f6a038")!
+        let message = GlucoseRxMessage(data: data)!
+
+        XCTAssertEqual(0, message.status)
+        XCTAssertEqual(0, message.sequence)
+        XCTAssertEqual(5091726, message.glucose.timestamp)
+        XCTAssertFalse(message.glucose.glucoseIsDisplayOnly)
+        XCTAssertEqual(130, message.glucose.glucose)
+        XCTAssertEqual(6, message.glucose.state)
+        XCTAssertEqual(-10, message.glucose.trend)
+    }
+}

+ 83 - 0
Dependencies/CGMBLEKit/CGMBLEKitTests/GlucoseTests.swift

@@ -0,0 +1,83 @@
+//
+//  GlucoseTests.swift
+//  xDripG5
+//
+//  Created by Nate Racklyeft on 8/6/16.
+//  Copyright © 2016 Nathan Racklyeft. All rights reserved.
+//
+
+import XCTest
+import HealthKit
+@testable import CGMBLEKit
+
+class GlucoseTests: XCTestCase {
+
+    var timeMessage: TransmitterTimeRxMessage!
+    var calendar = Calendar(identifier: .gregorian)
+    var activationDate: Date!
+
+    override func setUp() {
+        super.setUp()
+
+        let data = Data(hexadecimalString: "2500470272007cff710001000000fa1d")!
+        timeMessage = TransmitterTimeRxMessage(data: data)!
+
+        calendar.timeZone = TimeZone(identifier: "UTC")!
+
+        activationDate = calendar.date(from: DateComponents(year: 2016, month: 10, day: 1))!
+    }
+
+    func testMessageData() {
+        let data = Data(hexadecimalString: "3100680a00008a715700cc0006ffc42a")!
+        let message = GlucoseRxMessage(data: data)!
+        let glucose = Glucose(transmitterID: "123456", glucoseMessage: message, timeMessage: timeMessage, activationDate: activationDate)
+
+        XCTAssertEqual(TransmitterStatus.ok, glucose.status)
+        XCTAssertEqual(calendar.date(from: DateComponents(year: 2016, month: 12, day: 6, hour: 7, minute: 51, second: 38))!, glucose.readDate)
+        XCTAssertEqual(calendar.date(from: DateComponents(year: 2016, month: 12, day: 26, hour: 11, minute: 16, second: 12))!, glucose.sessionStartDate)
+        XCTAssertFalse(glucose.isDisplayOnly)
+        XCTAssertEqual(204, glucose.glucose?.doubleValue(for: .milligramsPerDeciliter))
+        XCTAssertEqual(.known(.ok), glucose.state)
+        XCTAssertEqual(-1, glucose.trend)
+    }
+
+    func testNegativeTrend() {
+        let data = Data(hexadecimalString: "31006f0a0000be7957007a0006e4818d")!
+        let message = GlucoseRxMessage(data: data)!
+        let glucose = Glucose(transmitterID: "123456", glucoseMessage: message, timeMessage: timeMessage, activationDate: activationDate)
+
+        XCTAssertEqual(TransmitterStatus.ok, glucose.status)
+        XCTAssertEqual(calendar.date(from: DateComponents(year: 2016, month: 12, day: 6, hour: 8, minute: 26, second: 38))!, glucose.readDate)
+        XCTAssertFalse(glucose.isDisplayOnly)
+        XCTAssertEqual(122, glucose.glucose?.doubleValue(for: .milligramsPerDeciliter))
+        XCTAssertEqual(.known(.ok), glucose.state)
+        XCTAssertEqual(-28, glucose.trend)
+    }
+
+    func testDisplayOnly() {
+        let data = Data(hexadecimalString: "3100700a0000f17a5700584006e3cee9")!
+        let message = GlucoseRxMessage(data: data)!
+        let glucose = Glucose(transmitterID: "123456", glucoseMessage: message, timeMessage: timeMessage, activationDate: activationDate)
+
+        XCTAssertEqual(TransmitterStatus.ok, glucose.status)
+        XCTAssertEqual(calendar.date(from: DateComponents(year: 2016, month: 12, day: 6, hour: 8, minute: 31, second: 45))!, glucose.readDate)
+        XCTAssertTrue(glucose.isDisplayOnly)
+        XCTAssertEqual(88, glucose.glucose?.doubleValue(for: .milligramsPerDeciliter))
+        XCTAssertEqual(.known(.ok), glucose.state)
+        XCTAssertEqual(-29, message.glucose.trend)
+    }
+
+    func testOldTransmitter() {
+        let data = Data(hexadecimalString: "3100aa00000095a078008b00060a8b34")!
+        let message = GlucoseRxMessage(data: data)!
+        let glucose = Glucose(transmitterID: "123456", glucoseMessage: message, timeMessage: timeMessage, activationDate: activationDate)
+
+        XCTAssertEqual(TransmitterStatus.ok, glucose.status)
+        XCTAssertEqual(calendar.date(from: DateComponents(year: 2016, month: 12, day: 31, hour: 11, minute: 57, second: 09))!, glucose.readDate)  // 90 days, status is still OK
+        XCTAssertFalse(glucose.isDisplayOnly)
+        XCTAssertEqual(139, glucose.glucose?.doubleValue(for: .milligramsPerDeciliter))
+        XCTAssertEqual(.known(.ok), glucose.state)
+        XCTAssertEqual(10, message.glucose.trend)
+    }
+    
+}

+ 24 - 0
Dependencies/CGMBLEKit/CGMBLEKitTests/Info.plist

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>BNDL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>3.2</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>$(CURRENT_PROJECT_VERSION)</string>
+</dict>
+</plist>

+ 44 - 0
Dependencies/CGMBLEKit/CGMBLEKitTests/SessionStartRxMessageTests.swift

@@ -0,0 +1,44 @@
+//
+//  SessionStartRxMessageTests.swift
+//  xDripG5
+//
+//  Created by Nathan Racklyeft on 6/4/16.
+//  Copyright © 2016 Nathan Racklyeft. All rights reserved.
+//
+
+import XCTest
+@testable import CGMBLEKit
+
+/// Thanks to https://github.com/mthatcher for the fixtures!
+class SessionStartRxMessageTests: XCTestCase {
+
+    func testSuccessfulStart() {
+        var data = Data(hexadecimalString: "2700014bf871004bf87100e9f8710095d9")!
+        var message = SessionStartRxMessage(data: data)!
+
+        XCTAssertEqual(0, message.status)
+        XCTAssertEqual(1, message.received)
+        XCTAssertEqual(7469131, message.requestedStartTime)
+        XCTAssertEqual(7469131, message.sessionStartTime)
+        XCTAssertEqual(7469289, message.transmitterTime)
+
+        data = Data(hexadecimalString: "2700012bfd71002bfd710096fd71000f6a")!
+        message = SessionStartRxMessage(data: data)!
+
+        XCTAssertEqual(0, message.status)
+        XCTAssertEqual(1, message.received)
+        XCTAssertEqual(7470379, message.requestedStartTime)
+        XCTAssertEqual(7470379, message.sessionStartTime)
+        XCTAssertEqual(7470486, message.transmitterTime)
+
+        data = Data(hexadecimalString: "2700017cff71007cff7100eeff7100aeed")!
+        message = SessionStartRxMessage(data: data)!
+
+        XCTAssertEqual(0, message.status)
+        XCTAssertEqual(1, message.received)
+        XCTAssertEqual(7470972, message.requestedStartTime)
+        XCTAssertEqual(7470972, message.sessionStartTime)
+        XCTAssertEqual(7471086, message.transmitterTime)
+    }
+
+}

+ 44 - 0
Dependencies/CGMBLEKit/CGMBLEKitTests/SessionStopRxMessageTests.swift

@@ -0,0 +1,44 @@
+//
+//  SessionStopRxMessageTests.swift
+//  xDripG5
+//
+//  Created by Nathan Racklyeft on 6/4/16.
+//  Copyright © 2016 Nathan Racklyeft. All rights reserved.
+//
+
+import XCTest
+@testable import CGMBLEKit
+
+/// Thanks to https://github.com/mthatcher for the fixtures!
+class SessionStopRxMessageTests: XCTestCase {
+    
+    func testSuccessfulStop() {
+        var data = Data(hexadecimalString: "29000128027200ffffffff47027200ba85")!
+        var message = SessionStopRxMessage(data: data)!
+
+        XCTAssertEqual(0, message.status)
+        XCTAssertEqual(1, message.received)
+        XCTAssertEqual(7471656, message.sessionStopTime)
+        XCTAssertEqual(0xffffffff, message.sessionStartTime)
+        XCTAssertEqual(7471687, message.transmitterTime)
+
+        data = Data(hexadecimalString: "2900013ffe7100ffffffffc2fe71008268")!
+        message = SessionStopRxMessage(data: data)!
+
+        XCTAssertEqual(0, message.status)
+        XCTAssertEqual(1, message.received)
+        XCTAssertEqual(7470655, message.sessionStopTime)
+        XCTAssertEqual(0xffffffff, message.sessionStartTime)
+        XCTAssertEqual(7470786, message.transmitterTime)
+
+        data = Data(hexadecimalString: "290001f5fb7100ffffffff6afc7100fa8a")!
+        message = SessionStopRxMessage(data: data)!
+
+        XCTAssertEqual(0, message.status)
+        XCTAssertEqual(1, message.received)
+        XCTAssertEqual(7470069, message.sessionStopTime)
+        XCTAssertEqual(0xffffffff, message.sessionStartTime)
+        XCTAssertEqual(7470186, message.transmitterTime)
+    }
+    
+}

+ 20 - 0
Dependencies/CGMBLEKit/CGMBLEKitTests/TransmitterIDTests.swift

@@ -0,0 +1,20 @@
+//
+//  TransmitterIDTests.swift
+//  xDripG5Tests
+//
+//  Copyright © 2018 LoopKit Authors. All rights reserved.
+//
+
+import XCTest
+@testable import CGMBLEKit
+
+class TransmitterIDTests: XCTestCase {
+
+    /// Sanity check the hash computation path
+    func testComputeHash() {
+        let id = TransmitterID(id: "123456")
+
+        XCTAssertEqual("e60d4a7999b0fbb2", id.computeHash(of: Data(hexadecimalString: "0123456789abcdef")!)!.hexadecimalString)
+    }
+    
+}

+ 53 - 0
Dependencies/CGMBLEKit/CGMBLEKitTests/TransmitterTimeRxMessageTests.swift

@@ -0,0 +1,53 @@
+//
+//  TransmitterTimeRxMessageTests.swift
+//  xDripG5
+//
+//  Created by Nathan Racklyeft on 6/4/16.
+//  Copyright © 2016 Nathan Racklyeft. All rights reserved.
+//
+
+import XCTest
+@testable import CGMBLEKit
+
+/// Thanks to https://github.com/mthatcher for the fixtures!
+class TransmitterTimeRxMessageTests: XCTestCase {
+
+    func testNoSession() {
+        var data = Data(hexadecimalString: "2500e8f87100ffffffff010000000a70")!
+        var message = TransmitterTimeRxMessage(data: data)!
+
+        XCTAssertEqual(0, message.status)
+        XCTAssertEqual(7469288, message.currentTime)
+        XCTAssertEqual(0xffffffff, message.sessionStartTime)
+
+        data = Data(hexadecimalString: "250096fd7100ffffffff01000000226d")!
+        message = TransmitterTimeRxMessage(data: data)!
+
+        XCTAssertEqual(0, message.status)
+        XCTAssertEqual(7470486, message.currentTime)
+        XCTAssertEqual(0xffffffff, message.sessionStartTime)
+
+        data = Data(hexadecimalString: "2500eeff7100ffffffff010000008952")!
+        message = TransmitterTimeRxMessage(data: data)!
+
+        XCTAssertEqual(0, message.status)
+        XCTAssertEqual(7471086, message.currentTime)
+        XCTAssertEqual(0xffffffff, message.sessionStartTime)
+    }
+
+    func testInSession() {
+        var data = Data(hexadecimalString: "2500470272007cff710001000000fa1d")!
+        var message = TransmitterTimeRxMessage(data: data)!
+
+        XCTAssertEqual(0, message.status)
+        XCTAssertEqual(7471687, message.currentTime)
+        XCTAssertEqual(7470972, message.sessionStartTime)
+
+        data = Data(hexadecimalString: "2500beb24d00f22d4d000100000083c0")!
+        message = TransmitterTimeRxMessage(data: data)!
+
+        XCTAssertEqual(0, message.status)
+        XCTAssertEqual(5092030, message.currentTime)
+        XCTAssertEqual(5058034, message.sessionStartTime)
+    }
+}

+ 22 - 0
Dependencies/CGMBLEKit/CGMBLEKitTests/TransmitterVersionRxMessageTests.swift

@@ -0,0 +1,22 @@
+//
+//  TransmitterVersionRxMessageTests.swift
+//  xDripG5
+//
+//  Created by Nate Racklyeft on 9/29/16.
+//  Copyright © 2016 Nathan Racklyeft. All rights reserved.
+//
+
+import XCTest
+@testable import CGMBLEKit
+
+class TransmitterVersionRxMessageTests: XCTestCase {
+    
+    func testRxMessage() {
+        let data = Data(hexadecimalString: "4b0001000011df2900005100037000f00009b6")!
+        let message = TransmitterVersionRxMessage(data: data)!
+
+        XCTAssertEqual(0, message.status)
+        XCTAssertEqual([1, 0, 0, 17], message.firmwareVersion)
+    }
+
+}

+ 6 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/Assets.xcassets/Contents.json

@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

+ 12 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/Assets.xcassets/g6.imageset/Contents.json

@@ -0,0 +1,12 @@
+{
+  "images" : [
+    {
+      "filename" : "g6.png",
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
Dependencies/CGMBLEKit/CGMBLEKitUI/Assets.xcassets/g6.imageset/g6.png


+ 51 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/Base.lproj/Localizable.strings

@@ -0,0 +1,51 @@
+/* Format string for glucose trend per minute. (1: glucose value and unit) */
+"%@/min" = "%@/min";
+
+/* Confirmation message for deleting a CGM */
+"Are you sure you want to delete this CGM?" = "Are you sure you want to delete this CGM?";
+
+/* The title of the cancel action in an action sheet */
+"Cancel" = "Cancel";
+
+/* Title describing glucose date */
+"Date" = "Date";
+
+/* Button title to delete CGM
+Title text for the button to remove a CGM from Loop */
+"Delete CGM" = "Delete CGM";
+
+/* Title describing glucose value */
+"Glucose" = "Glucose";
+
+/* Describes a glucose value adjusted to reflect a recent calibration */
+"Glucose (Adjusted)" = "Glucose (Adjusted)";
+
+/* Section title for latest glucose calibration */
+"Latest Calibration" = "Latest Calibration";
+
+/* Section title for latest glucose reading */
+"Latest Reading" = "Latest Reading";
+
+/* Button title to open CGM app */
+"Open App" = "Open App";
+
+/* Title describing sensor session age */
+"Session Age" = "Session Age";
+
+/* Title describing sensor expiration */
+"Sensor Expires" = "Sensor Expires";
+
+/* Title describing past sensor expiration */
+"Sensor Expired" = "Sensor Expired";
+
+/* Title describing CGM calibration and battery state */
+"Status" = "Status";
+
+/* Title describing transmitter session age */
+"Transmitter Age" = "Transmitter Age";
+
+/* The title text for the Dexcom G5/G6 transmitter ID config value */
+"Transmitter ID" = "Transmitter ID";
+
+/* Title describing glucose trend */
+"Trend" = "Trend";

+ 114 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/Base.lproj/TransmitterManagerSetup.storyboard

@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14868" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="7N1-NK-BqO">
+    <device id="retina4_7" orientation="portrait" appearance="dark"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14824"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--Transmitter Setup-->
+        <scene sceneID="teK-Jz-HaX">
+            <objects>
+                <tableViewController title="Transmitter Setup" id="Dds-49-o7G" customClass="TransmitterIDSetupViewController" customModule="CGMBLEKitUI" customModuleProvider="target" sceneMemberID="viewController">
+                    <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="FOH-yM-6UM">
+                        <rect key="frame" x="0.0" y="0.0" width="375" height="623"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                        <sections>
+                            <tableViewSection headerTitle="Transmitter ID" id="Qub-6B-0aB">
+                                <string key="footerTitle">The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app.</string>
+                                <cells>
+                                    <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="af6-El-QDO">
+                                        <rect key="frame" x="0.0" y="55.5" width="375" height="44"/>
+                                        <autoresizingMask key="autoresizingMask"/>
+                                        <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="af6-El-QDO" id="16F-lP-knO">
+                                            <rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
+                                            <autoresizingMask key="autoresizingMask"/>
+                                            <subviews>
+                                                <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Enter the 6-digit transmitter ID" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="nKX-TW-GhD" customClass="PaddedTextField" customModule="LoopKitUI">
+                                                    <rect key="frame" x="16" y="0.0" width="343" height="44"/>
+                                                    <fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
+                                                    <textInputTraits key="textInputTraits" autocapitalizationType="allCharacters" autocorrectionType="no" spellCheckingType="no" keyboardType="alphabet" returnKeyType="done" smartDashesType="no" smartQuotesType="no"/>
+                                                    <userDefinedRuntimeAttributes>
+                                                        <userDefinedRuntimeAttribute type="size" keyPath="textInset">
+                                                            <size key="value" width="0.0" height="12"/>
+                                                        </userDefinedRuntimeAttribute>
+                                                    </userDefinedRuntimeAttributes>
+                                                    <connections>
+                                                        <outlet property="delegate" destination="Dds-49-o7G" id="C8g-MC-3F3"/>
+                                                    </connections>
+                                                </textField>
+                                            </subviews>
+                                            <constraints>
+                                                <constraint firstItem="nKX-TW-GhD" firstAttribute="trailing" secondItem="16F-lP-knO" secondAttribute="trailingMargin" id="3PT-cp-nfZ"/>
+                                                <constraint firstAttribute="bottom" secondItem="nKX-TW-GhD" secondAttribute="bottom" id="B7C-R4-5fg"/>
+                                                <constraint firstItem="nKX-TW-GhD" firstAttribute="leading" secondItem="16F-lP-knO" secondAttribute="leadingMargin" id="GjG-gf-0yR"/>
+                                                <constraint firstItem="nKX-TW-GhD" firstAttribute="top" secondItem="16F-lP-knO" secondAttribute="top" id="QwV-R0-aUq"/>
+                                            </constraints>
+                                        </tableViewCellContentView>
+                                    </tableViewCell>
+                                </cells>
+                            </tableViewSection>
+                            <tableViewSection headerTitle="Dexcom Share" footerTitle="Data can be downloaded over the Internet from Share when the transmitter connection fails." id="k1N-Rg-XDy">
+                                <cells>
+                                    <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="5oU-vK-JHQ" detailTextLabel="GOT-KQ-cEh" style="IBUITableViewCellStyleValue1" id="VKg-iR-eRk" customClass="SettingsTableViewCell" customModule="LoopKitUI">
+                                        <rect key="frame" x="0.0" y="223" width="375" height="44"/>
+                                        <autoresizingMask key="autoresizingMask"/>
+                                        <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VKg-iR-eRk" id="GZu-iC-2kt">
+                                            <rect key="frame" x="0.0" y="0.0" width="347.5" height="44"/>
+                                            <autoresizingMask key="autoresizingMask"/>
+                                            <subviews>
+                                                <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Credentials" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="5oU-vK-JHQ">
+                                                    <rect key="frame" x="16" y="12" width="87" height="20.5"/>
+                                                    <autoresizingMask key="autoresizingMask"/>
+                                                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
+                                                    <nil key="textColor"/>
+                                                    <nil key="highlightedColor"/>
+                                                </label>
+                                                <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="GOT-KQ-cEh">
+                                                    <rect key="frame" x="295.5" y="12" width="44" height="20.5"/>
+                                                    <autoresizingMask key="autoresizingMask"/>
+                                                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
+                                                    <color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                                    <nil key="highlightedColor"/>
+                                                </label>
+                                            </subviews>
+                                        </tableViewCellContentView>
+                                    </tableViewCell>
+                                </cells>
+                            </tableViewSection>
+                        </sections>
+                        <connections>
+                            <outlet property="dataSource" destination="Dds-49-o7G" id="PGK-sX-2c8"/>
+                            <outlet property="delegate" destination="Dds-49-o7G" id="Xp2-eI-dvm"/>
+                        </connections>
+                    </tableView>
+                    <connections>
+                        <outlet property="shareUsernameLabel" destination="GOT-KQ-cEh" id="j8W-Tr-9lH"/>
+                        <outlet property="transmitterIDTextField" destination="nKX-TW-GhD" id="ZjP-P7-x3Y"/>
+                    </connections>
+                </tableViewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="3nA-P9-fpH" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="12" y="87"/>
+        </scene>
+        <!--Transmitter Setup View Controller-->
+        <scene sceneID="MSW-kN-lDN">
+            <objects>
+                <navigationController id="7N1-NK-BqO" customClass="TransmitterSetupViewController" customModule="CGMBLEKitUI" customModuleProvider="target" sceneMemberID="viewController">
+                    <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" translucent="NO" largeTitles="YES" id="oCu-Q8-T22">
+                        <rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
+                        <autoresizingMask key="autoresizingMask"/>
+                        <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                    </navigationBar>
+                    <connections>
+                        <segue destination="Dds-49-o7G" kind="relationship" relationship="rootViewController" id="Bah-Mf-Thd"/>
+                    </connections>
+                </navigationController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="HBr-Z5-zYq" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="-1137" y="87"/>
+        </scene>
+    </scenes>
+</document>

+ 19 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/CGMBLEKitUI.h

@@ -0,0 +1,19 @@
+//
+//  CGMBLEKitUI.h
+//  CGMBLEKitUI
+//
+//  Created by Nathan Racklyeft on 7/28/18.
+//  Copyright © 2018 LoopKit Authors. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+//! Project version number for CGMBLEKitUI.
+FOUNDATION_EXPORT double CGMBLEKitUIVersionNumber;
+
+//! Project version string for CGMBLEKitUI.
+FOUNDATION_EXPORT const unsigned char CGMBLEKitUIVersionString[];
+
+// In this header, you should import all the public headers of your framework using statements like #import <CGMBLEKitUI/PublicHeader.h>
+
+

+ 24 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/IdentifiableClass.swift

@@ -0,0 +1,24 @@
+//
+//  IdentifiableClass.swift
+//  Naterade
+//
+//  Created by Nathan Racklyeft on 5/22/16.
+//  Copyright © 2016 Nathan Racklyeft. All rights reserved.
+//
+
+import Foundation
+
+
+protocol IdentifiableClass: AnyObject {
+    static var className: String { get }
+}
+
+
+extension IdentifiableClass {
+    static var className: String {
+        return NSStringFromClass(self).components(separatedBy: ".").last!
+    }
+}
+
+
+extension UITableViewCell: IdentifiableClass { }

+ 24 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/Info.plist

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>FMWK</string>
+	<key>CFBundleShortVersionString</key>
+	<string>3.2</string>
+	<key>CFBundleVersion</key>
+	<string>$(CURRENT_PROJECT_VERSION)</string>
+	<key>NSPrincipalClass</key>
+	<string></string>
+</dict>
+</plist>

+ 151 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/TransmitterIDSetupViewController.swift

@@ -0,0 +1,151 @@
+//
+//  TransmitterIDSetupViewController.swift
+//  CGMBLEKitUI
+//
+//  Copyright © 2018 LoopKit Authors. All rights reserved.
+//
+
+import UIKit
+import LoopKit
+import LoopKitUI
+import CGMBLEKit
+import ShareClient
+
+class TransmitterIDSetupViewController: SetupTableViewController {
+
+    lazy private(set) var shareManager = ShareClientManager()
+
+    private func updateShareUsername() {
+        shareUsernameLabel.text = shareManager.shareService.username ?? SettingsTableViewCell.TapToSetString
+    }
+
+    private(set) var transmitterID: String? {
+        get {
+            return transmitterIDTextField.text
+        }
+        set {
+            transmitterIDTextField.text = newValue
+        }
+    }
+
+    private func updateStateForSettings() {
+        let isReadyToRead = transmitterID?.count == 6
+
+        if isReadyToRead {
+            continueState = .completed
+        } else {
+            continueState = .inputSettings
+        }
+    }
+
+    private enum State {
+        case loadingView
+        case inputSettings
+        case completed
+    }
+
+    private var continueState: State = .loadingView {
+        didSet {
+            switch continueState {
+            case .loadingView:
+                updateStateForSettings()
+            case .inputSettings:
+                footerView.primaryButton.isEnabled = false
+            case .completed:
+                footerView.primaryButton.isEnabled = true
+            }
+        }
+    }
+
+    override func continueButtonPressed(_ sender: Any) {
+        if continueState == .completed,
+            let setupViewController = navigationController as? TransmitterSetupViewController,
+            let transmitterID = transmitterID
+        {
+            setupViewController.completeSetup(state: TransmitterManagerState(transmitterID: transmitterID))
+        }
+    }
+
+    override func cancelButtonPressed(_ sender: Any) {
+        if transmitterIDTextField.isFirstResponder {
+            transmitterIDTextField.resignFirstResponder()
+        } else {
+            super.cancelButtonPressed(sender)
+        }
+    }
+
+    override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
+        return continueState == .completed
+    }
+
+    // MARK: -
+
+    @IBOutlet private var shareUsernameLabel: UILabel!
+
+    @IBOutlet private var transmitterIDTextField: UITextField!
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        updateShareUsername()
+
+        continueState = .inputSettings
+    }
+
+    // MARK: - UITableViewDelegate
+
+    private enum Section: Int {
+        case transmitterID
+        case share
+    }
+
+    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        switch Section(rawValue: indexPath.section)! {
+        case .transmitterID:
+            tableView.deselectRow(at: indexPath, animated: false)
+        case .share:
+            let authVC = AuthenticationViewController(authentication: shareManager.shareService)
+            authVC.authenticationObserver = { [weak self] (service) in
+                self?.shareManager.shareService = service
+                self?.updateShareUsername()
+            }
+
+            show(authVC, sender: nil)
+        }
+    }
+}
+
+
+extension TransmitterIDSetupViewController: UITextFieldDelegate {
+    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
+        guard let text = textField.text, let stringRange = Range(range, in: text) else {
+            updateStateForSettings()
+            return true
+        }
+
+        let newText = text.replacingCharacters(in: stringRange, with: string)
+
+        if newText.count >= 6 {
+            if newText.count == 6 {
+                textField.text = newText
+                textField.resignFirstResponder()
+            }
+
+            updateStateForSettings()
+            return false
+        }
+
+        textField.text = newText
+        updateStateForSettings()
+        return false
+    }
+
+    func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
+        return true
+    }
+
+    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+        textField.resignFirstResponder()
+        return true
+    }
+}

+ 88 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/TransmitterManager+UI.swift

@@ -0,0 +1,88 @@
+//
+//  TransmitterManager+UI.swift
+//  Loop
+//
+//  Copyright © 2018 LoopKit Authors. All rights reserved.
+//
+
+import SwiftUI
+import LoopKit
+import LoopKitUI
+import HealthKit
+import CGMBLEKit
+
+
+extension G5CGMManager: CGMManagerUI {
+    public static var onboardingImage: UIImage? {
+        return nil
+    }
+
+    public static func setupViewController(bluetoothProvider: BluetoothProvider, displayGlucoseUnitObservable: DisplayGlucoseUnitObservable, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool) -> SetupUIResult<CGMManagerViewController, CGMManagerUI> {
+        let setupVC = TransmitterSetupViewController.instantiateFromStoryboard()
+        setupVC.cgmManagerType = self
+        return .userInteractionRequired(setupVC)
+    }
+
+    public func settingsViewController(bluetoothProvider: BluetoothProvider, displayGlucoseUnitObservable: DisplayGlucoseUnitObservable, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool) ->CGMManagerViewController {
+        let settings = TransmitterSettingsViewController(cgmManager: self, displayGlucoseUnitObservable: displayGlucoseUnitObservable)
+        let nav = CGMManagerSettingsNavigationViewController(rootViewController: settings)
+        return nav
+    }
+
+    public var smallImage: UIImage? {
+        return nil
+    }
+
+    // TODO Placeholder.
+    public var cgmStatusHighlight: DeviceStatusHighlight? {
+        return nil
+    }
+
+    // TODO Placeholder.
+    public var cgmStatusBadge: DeviceStatusBadge? {
+        return nil
+    }
+
+    // TODO Placeholder.
+    public var cgmLifecycleProgress: DeviceLifecycleProgress? {
+        return nil
+    }
+}
+
+
+extension G6CGMManager: CGMManagerUI {
+    public static var onboardingImage: UIImage? {
+        return nil
+    }
+
+    public static func setupViewController(bluetoothProvider: BluetoothProvider, displayGlucoseUnitObservable: DisplayGlucoseUnitObservable, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool) -> SetupUIResult<CGMManagerViewController, CGMManagerUI> {
+        let setupVC = TransmitterSetupViewController.instantiateFromStoryboard()
+        setupVC.cgmManagerType = self
+        return .userInteractionRequired(setupVC)
+    }
+
+    public func settingsViewController(bluetoothProvider: BluetoothProvider, displayGlucoseUnitObservable: DisplayGlucoseUnitObservable, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool) ->CGMManagerViewController {
+        let settings = TransmitterSettingsViewController(cgmManager: self, displayGlucoseUnitObservable: displayGlucoseUnitObservable)
+        let nav = CGMManagerSettingsNavigationViewController(rootViewController: settings)
+        return nav
+    }
+
+    public var smallImage: UIImage? {
+        UIImage(named: "g6", in: Bundle(for: TransmitterSetupViewController.self), compatibleWith: nil)!
+    }
+
+    // TODO Placeholder.
+    public var cgmStatusHighlight: DeviceStatusHighlight? {
+        return nil
+    }
+
+    // TODO Placeholder.
+    public var cgmStatusBadge: DeviceStatusBadge? {
+        return nil
+    }
+
+    // TODO Placeholder.
+    public var cgmLifecycleProgress: DeviceLifecycleProgress? {
+        return nil
+    }
+}

+ 558 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/TransmitterSettingsViewController.swift

@@ -0,0 +1,558 @@
+//
+//  TransmitterSettingsViewController.swift
+//  Loop
+//
+//  Copyright © 2018 LoopKit Authors. All rights reserved.
+//
+
+import UIKit
+import Combine
+import HealthKit
+import LoopKit
+import LoopKitUI
+import CGMBLEKit
+import ShareClientUI
+
+class TransmitterSettingsViewController: UITableViewController {
+
+    let cgmManager: TransmitterManager & CGMManagerUI
+
+    private let displayGlucoseUnitObservable: DisplayGlucoseUnitObservable
+
+    private lazy var cancellables = Set<AnyCancellable>()
+
+    private var glucoseUnit: HKUnit {
+        displayGlucoseUnitObservable.displayGlucoseUnit
+    }
+
+    init(cgmManager: TransmitterManager & CGMManagerUI, displayGlucoseUnitObservable: DisplayGlucoseUnitObservable) {
+        self.cgmManager = cgmManager
+        self.displayGlucoseUnitObservable = displayGlucoseUnitObservable
+
+        super.init(style: .grouped)
+
+        cgmManager.addObserver(self, queue: .main)
+
+        displayGlucoseUnitObservable.$displayGlucoseUnit
+            .sink { [weak self] _ in self?.tableView.reloadData() }
+            .store(in: &cancellables)
+    }
+
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        title = cgmManager.localizedTitle
+
+        tableView.rowHeight = UITableView.automaticDimension
+        tableView.estimatedRowHeight = 44
+
+        tableView.sectionHeaderHeight = UITableView.automaticDimension
+        tableView.estimatedSectionHeaderHeight = 55
+
+        tableView.register(SettingsTableViewCell.self, forCellReuseIdentifier: SettingsTableViewCell.className)
+        tableView.register(TextButtonTableViewCell.self, forCellReuseIdentifier: TextButtonTableViewCell.className)
+        tableView.register(SwitchTableViewCell.self, forCellReuseIdentifier: SwitchTableViewCell.className)
+        let button = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTapped(_:)))
+        self.navigationItem.setRightBarButton(button, animated: false)
+    }
+
+    @objc func doneTapped(_ sender: Any) {
+        complete()
+    }
+
+    private func complete() {
+        if let nav = navigationController as? SettingsNavigationViewController {
+            nav.notifyComplete()
+        }
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        if clearsSelectionOnViewWillAppear {
+            // Manually invoke the delegate for rows deselecting on appear
+            for indexPath in tableView.indexPathsForSelectedRows ?? [] {
+                _ = tableView(tableView, willDeselectRowAt: indexPath)
+            }
+        }
+
+        super.viewWillAppear(animated)
+    }
+
+    // MARK: - UITableViewDataSource
+
+    private enum Section: Int, CaseIterable {
+        case transmitterID
+        case remoteDataSync
+        case latestReading
+        case latestCalibration
+        case latestConnection
+        case ages
+        case share
+        case delete
+    }
+
+    override func numberOfSections(in tableView: UITableView) -> Int {
+        return Section.allCases.count
+    }
+
+    private enum LatestReadingRow: Int, CaseIterable {
+        case glucose
+        case date
+        case trend
+        case status
+    }
+
+    private enum LatestCalibrationRow: Int, CaseIterable {
+        case glucose
+        case date
+    }
+
+    private enum LatestConnectionRow: Int, CaseIterable {
+        case date
+    }
+
+    private enum AgeRow: Int, CaseIterable {
+        case sensorAge
+        case sensorCountdown
+        case sensorExpirationDate
+        case transmitter
+    }
+
+    private enum ShareRow: Int, CaseIterable {
+        case settings
+        case openApp
+    }
+
+    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        switch Section(rawValue: section)! {
+        case .transmitterID:
+            return 1
+        case .remoteDataSync:
+            return 1
+        case .latestReading:
+            return LatestReadingRow.allCases.count
+        case .latestCalibration:
+            return LatestCalibrationRow.allCases.count
+        case .latestConnection:
+            return LatestConnectionRow.allCases.count
+        case .ages:
+            return AgeRow.allCases.count
+        case .share:
+            return ShareRow.allCases.count
+        case .delete:
+            return 1
+        }
+    }
+
+    private lazy var glucoseFormatter: QuantityFormatter = {
+        let formatter = QuantityFormatter()
+        formatter.setPreferredNumberFormatter(for: glucoseUnit)
+        return formatter
+    }()
+
+    private lazy var dateFormatter: DateFormatter = {
+        let formatter = DateFormatter()
+        formatter.dateStyle = .long
+        formatter.timeStyle = .long
+        formatter.doesRelativeDateFormatting = true
+        return formatter
+    }()
+    
+    private lazy var sensorExpirationFullFormatter: DateFormatter = {
+        let formatter = DateFormatter()
+        formatter.dateStyle = .full
+        formatter.timeStyle = .short
+        formatter.doesRelativeDateFormatting = true
+        //formatter.dateFormat = "E, MMM d 'at' h:mm a"
+        return formatter
+    }()
+    
+    private lazy var sensorExpirationRelativeFormatter: DateFormatter = {
+        let formatter = DateFormatter()
+        formatter.dateStyle = .long
+        formatter.timeStyle = .short
+        formatter.doesRelativeDateFormatting = true
+        return formatter
+    }()
+    
+    private lazy var sensorExpAbsFormatter: DateFormatter = {
+        let formatter = DateFormatter()
+        formatter.dateStyle = .long
+        formatter.timeStyle = .short
+        formatter.doesRelativeDateFormatting = false
+        return formatter
+    }()
+    
+    private lazy var sessionLengthFormatter: DateComponentsFormatter = {
+        let formatter = DateComponentsFormatter()
+        formatter.allowedUnits = [.day, .hour, .minute]
+        formatter.unitsStyle = .full
+        formatter.maximumUnitCount = 2
+        return formatter
+    }()
+
+    private lazy var transmitterLengthFormatter: DateComponentsFormatter = {
+        let formatter = DateComponentsFormatter()
+        formatter.allowedUnits = [.day]
+        formatter.unitsStyle = .full
+        return formatter
+    }()
+
+    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        switch Section(rawValue: indexPath.section)! {
+        case .transmitterID:
+            let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) as! SettingsTableViewCell
+
+            cell.textLabel?.text = LocalizedString("Transmitter ID", comment: "The title text for the Dexcom G5/G6 transmitter ID config value")
+
+            cell.detailTextLabel?.text = cgmManager.transmitter.ID
+
+            return cell
+        case .remoteDataSync:
+            let switchCell = tableView.dequeueReusableCell(withIdentifier: SwitchTableViewCell.className, for: indexPath) as! SwitchTableViewCell
+
+            switchCell.selectionStyle = .none
+            switchCell.switch?.isOn = cgmManager.shouldSyncToRemoteService
+            switchCell.textLabel?.text = LocalizedString("Upload Readings", comment: "The title text for the upload glucose switch cell")
+
+            switchCell.switch?.addTarget(self, action: #selector(uploadEnabledChanged(_:)), for: .valueChanged)
+
+            return switchCell
+        case .latestReading:
+            let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) as! SettingsTableViewCell
+            let glucose = cgmManager.latestReading
+
+            switch LatestReadingRow(rawValue: indexPath.row)! {
+            case .glucose:
+                cell.setGlucose(glucose?.glucose, unit: glucoseUnit, formatter: glucoseFormatter, isDisplayOnly: glucose?.isDisplayOnly ?? false)
+            case .date:
+                cell.setGlucoseDate(glucose?.readDate, formatter: dateFormatter)
+            case .trend:
+                cell.textLabel?.text = LocalizedString("Trend", comment: "Title describing glucose trend")
+
+                if let trendRate = glucose?.trendRate {
+                    let glucoseUnitPerMinute = glucoseUnit.unitDivided(by: .minute())
+                    let trendPerMinute = HKQuantity(unit: glucoseUnit, doubleValue: trendRate.doubleValue(for: glucoseUnitPerMinute))
+
+                    if let formatted = glucoseFormatter.string(from: trendPerMinute, for: glucoseUnit) {
+                        cell.detailTextLabel?.text = String(format: LocalizedString("%@/min", comment: "Format string for glucose trend per minute. (1: glucose value and unit)"), formatted)
+                    } else {
+                        cell.detailTextLabel?.text = SettingsTableViewCell.NoValueString
+                    }
+                } else {
+                    cell.detailTextLabel?.text = SettingsTableViewCell.NoValueString
+                }
+            case .status:
+                cell.textLabel?.text = LocalizedString("Status", comment: "Title describing CGM calibration and battery state")
+
+                if let stateDescription = glucose?.stateDescription, !stateDescription.isEmpty {
+                    cell.detailTextLabel?.text = stateDescription
+                } else {
+                    cell.detailTextLabel?.text = SettingsTableViewCell.NoValueString
+                }
+            }
+
+            return cell
+        case .latestCalibration:
+            let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) as! SettingsTableViewCell
+            let calibration = cgmManager.latestReading?.lastCalibration
+
+            switch LatestCalibrationRow(rawValue: indexPath.row)! {
+            case .glucose:
+                cell.setGlucose(calibration?.glucose, unit: glucoseUnit, formatter: glucoseFormatter, isDisplayOnly: false)
+            case .date:
+                cell.setGlucoseDate(calibration?.date, formatter: dateFormatter)
+            }
+
+            return cell
+        case .latestConnection:
+            let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) as! SettingsTableViewCell
+            let connection = cgmManager.latestConnection
+
+            switch LatestConnectionRow(rawValue: indexPath.row)! {
+            case .date:
+                cell.setGlucoseDate(connection, formatter: dateFormatter)
+                cell.accessoryType = .disclosureIndicator
+            }
+
+            return cell
+        case .ages:
+            let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) as! SettingsTableViewCell
+            let glucose = cgmManager.latestReading
+            
+            switch AgeRow(rawValue: indexPath.row)! {
+            case .sensorAge:
+                cell.textLabel?.text = LocalizedString("Session Age", comment: "Title describing sensor session age")
+                
+                if let stateDescription = glucose?.stateDescription, !stateDescription.isEmpty && !stateDescription.contains("stopped") {
+                    if let sessionStart = cgmManager.latestReading?.sessionStartDate {
+                        cell.detailTextLabel?.text = sessionLengthFormatter.string(from: Date().timeIntervalSince(sessionStart))
+                    } else {
+                        cell.detailTextLabel?.text = SettingsTableViewCell.NoValueString
+                    }
+                } else {
+                    cell.detailTextLabel?.text = SettingsTableViewCell.NoValueString
+                }
+                
+            case .sensorCountdown:
+                cell.textLabel?.text = LocalizedString("Sensor Expires", comment: "Title describing sensor sensor expiration")
+                
+                if let stateDescription = glucose?.stateDescription, !stateDescription.isEmpty && !stateDescription.contains("stopped") {
+                    if let sessionExp = cgmManager.latestReading?.sessionExpDate {
+                        let sessionCountDown = sessionExp.timeIntervalSince(Date())
+                        if sessionCountDown < 0 {
+                            cell.textLabel?.text = LocalizedString("Sensor Expired", comment: "Title describing past sensor sensor expiration")
+                            cell.detailTextLabel?.text = (sessionLengthFormatter.string(from: sessionCountDown * -1) ?? "") + " ago"
+                        } else {
+                            cell.detailTextLabel?.text = sessionLengthFormatter.string(from: sessionCountDown)
+                        }
+                    } else {
+                        cell.detailTextLabel?.text = SettingsTableViewCell.NoValueString
+                    }
+                } else {
+                    cell.detailTextLabel?.text = SettingsTableViewCell.NoValueString
+                }
+                
+            case .sensorExpirationDate:
+                cell.textLabel?.text = ""
+                if let stateDescription = glucose?.stateDescription, !stateDescription.isEmpty && !stateDescription.contains("stopped") {
+                    if let sessionExp = cgmManager.latestReading?.sessionExpDate {
+                        if sensorExpirationRelativeFormatter.string(from: sessionExp) == sensorExpAbsFormatter.string(from: sessionExp) {
+                            cell.detailTextLabel?.text = sensorExpirationFullFormatter.string(from: sessionExp)
+                        } else {
+                            cell.detailTextLabel?.text = sensorExpirationRelativeFormatter.string(from: sessionExp)
+                        }
+                    } else {
+                        cell.detailTextLabel?.text = SettingsTableViewCell.NoValueString
+                    }
+                } else {
+                    cell.detailTextLabel?.text = SettingsTableViewCell.NoValueString
+                }
+            
+            case .transmitter:
+                cell.textLabel?.text = LocalizedString("Transmitter Age", comment: "Title describing transmitter session age")
+
+                if let activation = cgmManager.latestReading?.activationDate {
+                    cell.detailTextLabel?.text = transmitterLengthFormatter.string(from: Date().timeIntervalSince(activation))
+                } else {
+                    cell.detailTextLabel?.text = SettingsTableViewCell.NoValueString
+                }
+            }
+
+            return cell
+        case .share:
+            switch ShareRow(rawValue: indexPath.row)! {
+            case .settings:
+                let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) as! SettingsTableViewCell
+                let service = cgmManager.shareManager.shareService
+
+                cell.textLabel?.text = service.title
+                cell.detailTextLabel?.text = service.username ?? SettingsTableViewCell.TapToSetString
+                cell.accessoryType = .disclosureIndicator
+
+                return cell
+            case .openApp:
+                let cell = tableView.dequeueReusableCell(withIdentifier: TextButtonTableViewCell.className, for: indexPath)
+
+                cell.textLabel?.text = LocalizedString("Open App", comment: "Button title to open CGM app")
+
+                return cell
+            }
+        case .delete:
+            let cell = tableView.dequeueReusableCell(withIdentifier: TextButtonTableViewCell.className, for: indexPath) as! TextButtonTableViewCell
+
+            cell.textLabel?.text = LocalizedString("Delete CGM", comment: "Title text for the button to remove a CGM from Loop")
+            cell.textLabel?.textAlignment = .center
+            cell.tintColor = .delete
+            cell.isEnabled = true
+            return cell
+        }
+    }
+
+    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+        switch Section(rawValue: section)! {
+        case .transmitterID:
+            return nil
+        case .remoteDataSync:
+            return LocalizedString("Remote Data Synchronization", comment: "Section title for remote data synchronization")
+        case .latestReading:
+            return LocalizedString("Latest Reading", comment: "Section title for latest glucose reading")
+        case .latestCalibration:
+            return LocalizedString("Latest Calibration", comment: "Section title for latest glucose calibration")
+        case .latestConnection:
+            return LocalizedString("Latest Connection", comment: "Section title for latest connection date")
+        case .ages:
+            return nil
+        case .share:
+            return nil
+        case .delete:
+            return " "  // Use an empty string for more dramatic spacing
+        }
+    }
+
+    override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
+        switch Section(rawValue: indexPath.section)! {
+        case .transmitterID:
+            return false
+        case .remoteDataSync:
+            return false
+        case .latestReading:
+            return false
+        case .latestCalibration:
+            return false
+        case .latestConnection:
+            return true
+        case .ages:
+            return false
+        case .share:
+            return true
+        case .delete:
+            return true
+        }
+    }
+
+    override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
+        if self.tableView(tableView, shouldHighlightRowAt: indexPath) {
+            return indexPath
+        } else {
+            return nil
+        }
+    }
+
+    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        switch Section(rawValue: indexPath.section)! {
+        case .transmitterID:
+            break
+        case .remoteDataSync:
+            break
+        case .latestReading:
+            break
+        case .latestCalibration:
+            break
+        case .latestConnection:
+            let vc = CommandResponseViewController(command: { (completionHandler) -> String in
+                return String(reflecting: self.cgmManager)
+            })
+            vc.title = self.title
+            show(vc, sender: nil)
+        case .ages:
+            break
+        case .share:
+            switch ShareRow(rawValue: indexPath.row)! {
+            case .settings:
+                let vc = ShareClientSettingsViewController(cgmManager: cgmManager.shareManager, displayGlucoseUnitObservable: displayGlucoseUnitObservable, allowsDeletion: false)
+                show(vc, sender: nil)
+                return // Don't deselect
+            case .openApp:
+                if let appURL = URL(string: "dexcomg6://") {
+                    UIApplication.shared.open(appURL)
+                }
+            }
+        case .delete:
+            let confirmVC = UIAlertController(cgmDeletionHandler: {
+                self.cgmManager.notifyDelegateOfDeletion {
+                    DispatchQueue.main.async {
+                        self.complete()
+                    }
+                }
+            })
+
+            present(confirmVC, animated: true) {
+                tableView.deselectRow(at: indexPath, animated: true)
+            }
+        }
+
+        tableView.deselectRow(at: indexPath, animated: true)
+    }
+
+    override func tableView(_ tableView: UITableView, willDeselectRowAt indexPath: IndexPath) -> IndexPath? {
+        switch Section(rawValue: indexPath.section)! {
+        case .transmitterID:
+            break
+        case .remoteDataSync:
+            break
+        case .latestReading:
+            break
+        case .latestCalibration:
+            break
+        case .latestConnection:
+            break
+        case .ages:
+            break
+        case .share:
+            switch ShareRow(rawValue: indexPath.row)! {
+            case .settings:
+                tableView.reloadRows(at: [indexPath], with: .fade)
+            case .openApp:
+                break
+            }
+        case .delete:
+            break
+        }
+
+        return indexPath
+    }
+    
+    @objc private func uploadEnabledChanged(_ sender: UISwitch) {
+        cgmManager.shouldSyncToRemoteService = sender.isOn
+    }
+}
+
+
+extension TransmitterSettingsViewController: TransmitterManagerObserver {
+    func transmitterManagerDidUpdateLatestReading(_ manager: TransmitterManager) {
+        tableView.reloadData()
+    }
+}
+
+
+private extension UIAlertController {
+    convenience init(cgmDeletionHandler handler: @escaping () -> Void) {
+        self.init(
+            title: nil,
+            message: LocalizedString("Are you sure you want to delete this CGM?", comment: "Confirmation message for deleting a CGM"),
+            preferredStyle: .actionSheet
+        )
+
+        addAction(UIAlertAction(
+            title: LocalizedString("Delete CGM", comment: "Button title to delete CGM"),
+            style: .destructive,
+            handler: { (_) in
+                handler()
+            }
+        ))
+
+        let cancel = LocalizedString("Cancel", comment: "The title of the cancel action in an action sheet")
+        addAction(UIAlertAction(title: cancel, style: .cancel, handler: nil))
+    }
+}
+
+
+private extension SettingsTableViewCell {
+    func setGlucose(_ glucose: HKQuantity?, unit: HKUnit, formatter: QuantityFormatter, isDisplayOnly: Bool) {
+        if isDisplayOnly {
+            textLabel?.text = LocalizedString("Glucose (Adjusted)", comment: "Describes a glucose value adjusted to reflect a recent calibration")
+        } else {
+            textLabel?.text = LocalizedString("Glucose", comment: "Title describing glucose value")
+        }
+
+        if let quantity = glucose, let formatted = formatter.string(from: quantity, for: unit) {
+            detailTextLabel?.text = formatted
+        } else {
+            detailTextLabel?.text = SettingsTableViewCell.NoValueString
+        }
+    }
+
+    func setGlucoseDate(_ date: Date?, formatter: DateFormatter) {
+        textLabel?.text = LocalizedString("Date", comment: "Title describing glucose date")
+
+        if let date = date {
+            detailTextLabel?.text = formatter.string(from: date)
+        } else {
+            detailTextLabel?.text = SettingsTableViewCell.NoValueString
+        }
+    }
+}

+ 96 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/TransmitterSetupViewController.swift

@@ -0,0 +1,96 @@
+//
+//  TransmitterSetupViewController.swift
+//  CGMBLEKitUI
+//
+//  Copyright © 2018 LoopKit Authors. All rights reserved.
+//
+
+import UIKit
+import LoopKit
+import LoopKitUI
+import CGMBLEKit
+import ShareClient
+
+class TransmitterSetupViewController: UINavigationController, CGMManagerOnboarding, UINavigationControllerDelegate, CompletionNotifying {
+    class func instantiateFromStoryboard() -> TransmitterSetupViewController {
+        return UIStoryboard(name: "TransmitterManagerSetup", bundle: Bundle(for: TransmitterSetupViewController.self)).instantiateInitialViewController() as! TransmitterSetupViewController
+    }
+
+    weak var cgmManagerOnboardingDelegate: CGMManagerOnboardingDelegate?
+    weak var completionDelegate: CompletionDelegate?
+
+    var cgmManagerType: TransmitterManager.Type!
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        delegate = self
+        view.backgroundColor = .systemGroupedBackground
+        navigationBar.shadowImage = UIImage()
+    }
+
+    func completeSetup(state: TransmitterManagerState) {
+        if let manager = cgmManagerType.init(state: state) as? CGMManagerUI {
+            cgmManagerOnboardingDelegate?.cgmManagerOnboarding(didCreateCGMManager: manager)
+            cgmManagerOnboardingDelegate?.cgmManagerOnboarding(didOnboardCGMManager: manager)
+            completionDelegate?.completionNotifyingDidComplete(self)
+        }
+    }
+
+    // MARK: - UINavigationControllerDelegate
+
+    func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
+        // Read state values
+        let viewControllers = navigationController.viewControllers
+        let count = navigationController.viewControllers.count
+
+        if count >= 2 {
+            switch viewControllers[count - 2] {
+            case _ as TransmitterIDSetupViewController:
+                break
+            default:
+                break
+            }
+        }
+
+        if let setupViewController = viewController as? SetupTableViewController {
+            setupViewController.delegate = self
+        }
+
+        // Set state values
+        switch viewController {
+        case _ as TransmitterIDSetupViewController:
+            break
+        default:
+            break
+        }
+
+        // Adjust the appearance for the main setup view controllers only
+        if viewController is SetupTableViewController {
+            navigationBar.isTranslucent = false
+            navigationBar.shadowImage = UIImage()
+        } else {
+            navigationBar.isTranslucent = true
+            navigationBar.shadowImage = nil
+            viewController.navigationItem.largeTitleDisplayMode = .never
+        }
+    }
+
+    func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
+
+        // Adjust the appearance for the main setup view controllers only
+        if viewController is SetupTableViewController {
+            navigationBar.isTranslucent = false
+            navigationBar.shadowImage = UIImage()
+        } else {
+            navigationBar.isTranslucent = true
+            navigationBar.shadowImage = nil
+        }
+    }
+}
+
+extension TransmitterSetupViewController: SetupTableViewControllerDelegate {
+    public func setupTableViewControllerCancelButtonPressed(_ viewController: SetupTableViewController) {
+        completionDelegate?.completionNotifyingDidComplete(self)
+    }
+}

+ 22 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/UIColor.swift

@@ -0,0 +1,22 @@
+//
+//  UIColor.swift
+//  LoopKitUI
+//
+//  Copyright © 2018 LoopKit Authors. All rights reserved.
+//
+
+import UIKit
+
+
+extension UIColor {
+    static let delete = UIColor.higRed()
+}
+
+
+// MARK: - HIG colors
+// See: https://developer.apple.com/ios/human-interface-guidelines/visual-design/color/
+extension UIColor {
+    private static func higRed() -> UIColor {
+        return UIColor(red: 1, green: 59 / 255, blue: 48 / 255, alpha: 1)
+    }
+}

+ 45 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/da.lproj/Localizable.strings

@@ -0,0 +1,45 @@
+/* Format string for glucose trend per minute. (1: glucose value and unit) */
+"%@/min" = "%@/min";
+
+/* Confirmation message for deleting a CGM */
+"Are you sure you want to delete this CGM?" = "Er du sikker på, at du vil slette denne CGM?";
+
+/* The title of the cancel action in an action sheet */
+"Cancel" = "Afbestille";
+
+/* Title describing glucose date */
+"Date" = "Dato";
+
+/* Button title to delete CGM
+Title text for the button to remove a CGM from Loop */
+"Delete CGM" = "Slet CGM";
+
+/* Title describing glucose value */
+"Glucose" = "Blodsukker";
+
+/* Describes a glucose value adjusted to reflect a recent calibration */
+"Glucose (Adjusted)" = "Blodsukker (justeret)";
+
+/* Section title for latest glucose calibration */
+"Latest Calibration" = "Seneste kalibrering";
+
+/* Section title for latest glucose reading */
+"Latest Reading" = "Seneste læsning";
+
+/* Button title to open CGM app */
+"Open App" = "Åben app";
+
+/* Title describing sensor session age */
+"Session Age" = "Session alder";
+
+/* Title describing CGM calibration and battery state */
+"Status" = "status";
+
+/* Title describing transmitter session age */
+"Transmitter Age" = "Senders alder";
+
+/* The title text for the Dexcom G5/G6 transmitter ID config value */
+"Transmitter ID" = "Senders ID";
+
+/* Title describing glucose trend */
+"Trend" = "Trend";

+ 24 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/da.lproj/TransmitterManagerSetup.strings

@@ -0,0 +1,24 @@
+
+/* Class = "UILabel"; text = "Credentials"; ObjectID = "5oU-vK-JHQ"; */
+"5oU-vK-JHQ.text" = "Legitimationsoplysninger";
+
+/* Class = "UITableViewController"; title = "Transmitter Setup"; ObjectID = "Dds-49-o7G"; */
+"Dds-49-o7G.title" = "Senderopsætning";
+
+/* Class = "UILabel"; text = "Detail"; ObjectID = "GOT-KQ-cEh"; */
+"GOT-KQ-cEh.text" = "Detalje";
+
+/* Class = "UITableViewSection"; footerTitle = "The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app."; ObjectID = "Qub-6B-0aB"; */
+"Qub-6B-0aB.footerTitle" = "Sender-ID'et kan findes trykt på bagsiden af ​​enheden, på den side af boksen, den kom i, og fra indstillingsmenuerne på modtageren og mobilappen.";
+
+/* Class = "UITableViewSection"; headerTitle = "Transmitter ID"; ObjectID = "Qub-6B-0aB"; */
+"Qub-6B-0aB.headerTitle" = "Senders id";
+
+/* Class = "UITableViewSection"; footerTitle = "Data can be downloaded over the Internet from Share when the transmitter connection fails."; ObjectID = "k1N-Rg-XDy"; */
+"k1N-Rg-XDy.footerTitle" = "Data kan downloades over internettet fra Share, når transmitterforbindelsen mislykkes.";
+
+/* Class = "UITableViewSection"; headerTitle = "Dexcom Share"; ObjectID = "k1N-Rg-XDy"; */
+"k1N-Rg-XDy.headerTitle" = "Dexcom Share";
+
+/* Class = "UITextField"; placeholder = "Enter the 6-digit transmitter ID"; ObjectID = "nKX-TW-GhD"; */
+"nKX-TW-GhD.placeholder" = "Indtast det 6-cifrede sender-ID";

+ 45 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/de.lproj/Localizable.strings

@@ -0,0 +1,45 @@
+/* Format string for glucose trend per minute. (1: glucose value and unit) */
+"%@/min" = "%@/min";
+
+/* Confirmation message for deleting a CGM */
+"Are you sure you want to delete this CGM?" = "Möchten Sie dieses CGM wirklich löschen?";
+
+/* The title of the cancel action in an action sheet */
+"Cancel" = "Abbrechen";
+
+/* Title describing glucose date */
+"Date" = "Datum";
+
+/* Button title to delete CGM
+Title text for the button to remove a CGM from Loop */
+"Delete CGM" = "CGM löschen";
+
+/* Title describing glucose value */
+"Glucose" = "Glukose";
+
+/* Describes a glucose value adjusted to reflect a recent calibration */
+"Glucose (Adjusted)" = "BZ-Wert (angepasst)";
+
+/* Section title for latest glucose calibration */
+"Latest Calibration" = "Letzte Kalibrierung";
+
+/* Section title for latest glucose reading */
+"Latest Reading" = "Letzter Wert";
+
+/* Button title to open CGM app */
+"Open App" = "App öffnen";
+
+/* Title describing sensor session age */
+"Session Age" = "Sitzungsalter";
+
+/* Title describing CGM calibration and battery state */
+"Status" = "Status";
+
+/* Title describing transmitter session age */
+"Transmitter Age" = "Transmitteralter";
+
+/* The title text for the Dexcom G5/G6 transmitter ID config value */
+"Transmitter ID" = "Sender-ID";
+
+/* Title describing glucose trend */
+"Trend" = "Trend";

+ 24 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/de.lproj/TransmitterManagerSetup.strings

@@ -0,0 +1,24 @@
+
+/* Class = "UILabel"; text = "Credentials"; ObjectID = "5oU-vK-JHQ"; */
+"5oU-vK-JHQ.text" = "Logindaten";
+
+/* Class = "UITableViewController"; title = "Transmitter Setup"; ObjectID = "Dds-49-o7G"; */
+"Dds-49-o7G.title" = "Transmitter-Setup";
+
+/* Class = "UILabel"; text = "Detail"; ObjectID = "GOT-KQ-cEh"; */
+"GOT-KQ-cEh.text" = "Detail";
+
+/* Class = "UITableViewSection"; footerTitle = "The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app."; ObjectID = "Qub-6B-0aB"; */
+"Qub-6B-0aB.footerTitle" = "Die Transmitter-ID befindet sich auf der Rückseite des Transmitters, an der Seite der Verpackung und in den Einstellungsmenüs des Empfängers sowie der Dexcom-App.";
+
+/* Class = "UITableViewSection"; headerTitle = "Transmitter ID"; ObjectID = "Qub-6B-0aB"; */
+"Qub-6B-0aB.headerTitle" = "Transmitter-ID";
+
+/* Class = "UITableViewSection"; footerTitle = "Data can be downloaded over the Internet from Share when the transmitter connection fails."; ObjectID = "k1N-Rg-XDy"; */
+"k1N-Rg-XDy.footerTitle" = "Daten können über das Internet von Share heruntergeladen werden, wenn die Verbindung zum Transmitter unterbrochen ist.";
+
+/* Class = "UITableViewSection"; headerTitle = "Dexcom Share"; ObjectID = "k1N-Rg-XDy"; */
+"k1N-Rg-XDy.headerTitle" = "Dexcom Share";
+
+/* Class = "UITextField"; placeholder = "Enter the 6-digit transmitter ID"; ObjectID = "nKX-TW-GhD"; */
+"nKX-TW-GhD.placeholder" = "Geben Sie die 6-stellige Transmitter-ID ein";

+ 24 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/en.lproj/TransmitterManagerSetup.strings

@@ -0,0 +1,24 @@
+
+/* Class = "UILabel"; text = "Credentials"; ObjectID = "5oU-vK-JHQ"; */
+"5oU-vK-JHQ.text" = "Credentials";
+
+/* Class = "UITableViewController"; title = "Transmitter Setup"; ObjectID = "Dds-49-o7G"; */
+"Dds-49-o7G.title" = "Transmitter Setup";
+
+/* Class = "UILabel"; text = "Detail"; ObjectID = "GOT-KQ-cEh"; */
+"GOT-KQ-cEh.text" = "Detail";
+
+/* Class = "UITableViewSection"; footerTitle = "The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app."; ObjectID = "Qub-6B-0aB"; */
+"Qub-6B-0aB.footerTitle" = "The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app.";
+
+/* Class = "UITableViewSection"; headerTitle = "Transmitter ID"; ObjectID = "Qub-6B-0aB"; */
+"Qub-6B-0aB.headerTitle" = "Transmitter ID";
+
+/* Class = "UITableViewSection"; footerTitle = "Data can be downloaded over the Internet from Share when the transmitter connection fails."; ObjectID = "k1N-Rg-XDy"; */
+"k1N-Rg-XDy.footerTitle" = "Data can be downloaded over the Internet from Share when the transmitter connection fails.";
+
+/* Class = "UITableViewSection"; headerTitle = "Dexcom Share"; ObjectID = "k1N-Rg-XDy"; */
+"k1N-Rg-XDy.headerTitle" = "Dexcom Share";
+
+/* Class = "UITextField"; placeholder = "Enter the 6-digit transmitter ID"; ObjectID = "nKX-TW-GhD"; */
+"nKX-TW-GhD.placeholder" = "Enter the 6-digit transmitter ID";

+ 45 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/es.lproj/Localizable.strings

@@ -0,0 +1,45 @@
+/* Format string for glucose trend per minute. (1: glucose value and unit) */
+"%@/min" = "%@/min";
+
+/* Confirmation message for deleting a CGM */
+"Are you sure you want to delete this CGM?" = "¿Está usted seguro de querer eliminar este CGM?";
+
+/* The title of the cancel action in an action sheet */
+"Cancel" = "Cancelar";
+
+/* Title describing glucose date */
+"Date" = "Fecha";
+
+/* Button title to delete CGM
+Title text for the button to remove a CGM from Loop */
+"Delete CGM" = "Eliminar CGM";
+
+/* Title describing glucose value */
+"Glucose" = "Glucosa";
+
+/* Describes a glucose value adjusted to reflect a recent calibration */
+"Glucose (Adjusted)" = "Glucose (Adjusted)";
+
+/* Section title for latest glucose calibration */
+"Latest Calibration" = "Calibración más reciente";
+
+/* Section title for latest glucose reading */
+"Latest Reading" = "Dato más reciente";
+
+/* Button title to open CGM app */
+"Open App" = "Abrir App";
+
+/* Title describing sensor session age */
+"Session Age" = "Tiempo de sesión del sensor";
+
+/* Title describing CGM calibration and battery state */
+"Status" = "Estado";
+
+/* Title describing transmitter session age */
+"Transmitter Age" = "Edad del transmisor";
+
+/* The title text for the Dexcom G5/G6 transmitter ID config value */
+"Transmitter ID" = "ID de Transmisor";
+
+/* Title describing glucose trend */
+"Trend" = "Tendencia";

+ 24 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/es.lproj/TransmitterManagerSetup.strings

@@ -0,0 +1,24 @@
+
+/* Class = "UILabel"; text = "Credentials"; ObjectID = "5oU-vK-JHQ"; */
+"5oU-vK-JHQ.text" = "Credenciales";
+
+/* Class = "UITableViewController"; title = "Transmitter Setup"; ObjectID = "Dds-49-o7G"; */
+"Dds-49-o7G.title" = "Configuración del transmisor";
+
+/* Class = "UILabel"; text = "Detail"; ObjectID = "GOT-KQ-cEh"; */
+"GOT-KQ-cEh.text" = "Detalle";
+
+/* Class = "UITableViewSection"; footerTitle = "The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app."; ObjectID = "Qub-6B-0aB"; */
+"Qub-6B-0aB.footerTitle" = "La identificación del transmisor puede encontrarse en la parte trasera del dispositivo, en el lateral de la caja en la que venía, o en los menús de ajustes del receptor y la app del teléfono móvil. ";
+
+/* Class = "UITableViewSection"; headerTitle = "Transmitter ID"; ObjectID = "Qub-6B-0aB"; */
+"Qub-6B-0aB.headerTitle" = "Identificación del transmisor";
+
+/* Class = "UITableViewSection"; footerTitle = "Data can be downloaded over the Internet from Share when the transmitter connection fails."; ObjectID = "k1N-Rg-XDy"; */
+"k1N-Rg-XDy.footerTitle" = "Los datos pueden descargarse, vía internet, desde el Share cuando la conexión del transmisor falla.";
+
+/* Class = "UITableViewSection"; headerTitle = "Dexcom Share"; ObjectID = "k1N-Rg-XDy"; */
+"k1N-Rg-XDy.headerTitle" = "Dexcom Share";
+
+/* Class = "UITextField"; placeholder = "Enter the 6-digit transmitter ID"; ObjectID = "nKX-TW-GhD"; */
+"nKX-TW-GhD.placeholder" = "Introduzca la identificación de 6 cifras del transmisor";

+ 55 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/fi.lproj/Localizable.strings

@@ -0,0 +1,55 @@
+/* Format string for glucose trend per minute. (1: glucose value and unit) */
+"%@/min" = "%@/min";
+
+/* Confirmation message for deleting a CGM */
+"Are you sure you want to delete this CGM?" = "Haluatko varmasti poistaa CGM:n?";
+
+/* The title of the cancel action in an action sheet */
+"Cancel" = "Kumoa";
+
+/* Title describing glucose date */
+"Date" = "Aika";
+
+/* Button title to delete CGM
+   Title text for the button to remove a CGM from Loop */
+"Delete CGM" = "Poista CGM";
+
+/* Title describing glucose value */
+"Glucose" = "Glukoosi";
+
+/* Describes a glucose value adjusted to reflect a recent calibration */
+"Glucose (Adjusted)" = "Glukoosi (säädetty)";
+
+/* Section title for latest glucose calibration */
+"Latest Calibration" = "Viimeisin kalibrointi";
+
+/* Section title for latest connection date */
+"Latest Connection" = "Viimeisin yhteys";
+
+/* Section title for latest glucose reading */
+"Latest Reading" = "Viimeisin lukema";
+
+/* Button title to open CGM app */
+"Open App" = "Avaa sovellus";
+
+/* Section title for remote data synchronization */
+"Remote Data Synchronization" = "Etätietojen synkronointi";
+
+/* Title describing sensor session age */
+"Session Age" = "Session ikä";
+
+/* Title describing CGM calibration and battery state */
+"Status" = "Tila";
+
+/* Title describing transmitter session age */
+"Transmitter Age" = "Lähettimen ikä";
+
+/* The title text for the Dexcom G5/G6 transmitter ID config value */
+"Transmitter ID" = "Lähettimen tunniste";
+
+/* Title describing glucose trend */
+"Trend" = "Suunta";
+
+/* The title text for the upload glucose switch cell */
+"Upload Readings" = "Lataa lukemat";
+

+ 24 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/fi.lproj/TransmitterManagerSetup.strings

@@ -0,0 +1,24 @@
+/* Class = "UILabel"; text = "Credentials"; ObjectID = "5oU-vK-JHQ"; */
+"5oU-vK-JHQ.text" = "Tunnukset";
+
+/* Class = "UITableViewController"; title = "Transmitter Setup"; ObjectID = "Dds-49-o7G"; */
+"Dds-49-o7G.title" = "Lähettimen asennus";
+
+/* Class = "UILabel"; text = "Detail"; ObjectID = "GOT-KQ-cEh"; */
+"GOT-KQ-cEh.text" = "Yksityiskohta";
+
+/* Class = "UITableViewSection"; footerTitle = "Data can be downloaded over the Internet from Share when the transmitter connection fails."; ObjectID = "k1N-Rg-XDy"; */
+"k1N-Rg-XDy.footerTitle" = "Tiedot voidaan ladata Internetistä Share-palvelimelta, kun yhteys lähettimeen epäonnistuu.";
+
+/* Class = "UITableViewSection"; headerTitle = "Dexcom Share"; ObjectID = "k1N-Rg-XDy"; */
+"k1N-Rg-XDy.headerTitle" = "Dexcom Share";
+
+/* Class = "UITextField"; placeholder = "Enter the 6-digit transmitter ID"; ObjectID = "nKX-TW-GhD"; */
+"nKX-TW-GhD.placeholder" = "Syötä 6-numeroinen lähettimen tunniste";
+
+/* Class = "UITableViewSection"; footerTitle = "The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app."; ObjectID = "Qub-6B-0aB"; */
+"Qub-6B-0aB.footerTitle" = "Lähettimen tunniste on painettu lähettimen pohjaan ja tuotepakkauksen sivulle. Se löytyy myös vastaanottimen ja mobiilisovelluksen asetusvalikosta.";
+
+/* Class = "UITableViewSection"; headerTitle = "Transmitter ID"; ObjectID = "Qub-6B-0aB"; */
+"Qub-6B-0aB.headerTitle" = "Lähettimen tunniste";
+

+ 45 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/fr.lproj/Localizable.strings

@@ -0,0 +1,45 @@
+/* Format string for glucose trend per minute. (1: glucose value and unit) */
+"%@/min" = "%@/min";
+
+/* Confirmation message for deleting a CGM */
+"Are you sure you want to delete this CGM?" = "Voulez-vous vraiment supprimer ce CGM?";
+
+/* The title of the cancel action in an action sheet */
+"Cancel" = "Annuler";
+
+/* Title describing glucose date */
+"Date" = "Date";
+
+/* Button title to delete CGM
+Title text for the button to remove a CGM from Loop */
+"Delete CGM" = "Supprimer CGM";
+
+/* Title describing glucose value */
+"Glucose" = "Glycémie";
+
+/* Describes a glucose value adjusted to reflect a recent calibration */
+"Glucose (Adjusted)" = "Glycémie (ajustée)";
+
+/* Section title for latest glucose calibration */
+"Latest Calibration" = "Dernier étalonnage";
+
+/* Section title for latest glucose reading */
+"Latest Reading" = "Dernière mesure";
+
+/* Button title to open CGM app */
+"Open App" = "Ouvrir l’application";
+
+/* Title describing sensor session age */
+"Session Age" = "L’âge de la session";
+
+/* Title describing CGM calibration and battery state */
+"Status" = "Statut";
+
+/* Title describing transmitter session age */
+"Transmitter Age" = "L’âge du transmetteur";
+
+/* The title text for the Dexcom G5/G6 transmitter ID config value */
+"Transmitter ID" = "ID du transmetteur";
+
+/* Title describing glucose trend */
+"Trend" = "Tendance";

+ 24 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/fr.lproj/TransmitterManagerSetup.strings

@@ -0,0 +1,24 @@
+
+/* Class = "UILabel"; text = "Credentials"; ObjectID = "5oU-vK-JHQ"; */
+"5oU-vK-JHQ.text" = "Identifiants";
+
+/* Class = "UITableViewController"; title = "Transmitter Setup"; ObjectID = "Dds-49-o7G"; */
+"Dds-49-o7G.title" = "Configuration du transmetteur";
+
+/* Class = "UILabel"; text = "Detail"; ObjectID = "GOT-KQ-cEh"; */
+"GOT-KQ-cEh.text" = "Détail";
+
+/* Class = "UITableViewSection"; footerTitle = "The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app."; ObjectID = "Qub-6B-0aB"; */
+"Qub-6B-0aB.footerTitle" = "L’ID du transmetteur se trouve sur le dos de l’appareil, sur la boîte dans laquelle il est venu, et depuis les menus de réglages du receveur et de l’application mobile.";
+
+/* Class = "UITableViewSection"; headerTitle = "Transmitter ID"; ObjectID = "Qub-6B-0aB"; */
+"Qub-6B-0aB.headerTitle" = "ID du transmetteur";
+
+/* Class = "UITableViewSection"; footerTitle = "Data can be downloaded over the Internet from Share when the transmitter connection fails."; ObjectID = "k1N-Rg-XDy"; */
+"k1N-Rg-XDy.footerTitle" = "Les données peuvent être téléchargées depuis Internet avec Share quand la connexion au transmetteur échoue.";
+
+/* Class = "UITableViewSection"; headerTitle = "Dexcom Share"; ObjectID = "k1N-Rg-XDy"; */
+"k1N-Rg-XDy.headerTitle" = "Dexcom Share";
+
+/* Class = "UITextField"; placeholder = "Enter the 6-digit transmitter ID"; ObjectID = "nKX-TW-GhD"; */
+"nKX-TW-GhD.placeholder" = "Entrez l’ID du transmetteur, composé de 6 lettres et chiffres";

+ 0 - 0
Dependencies/CGMBLEKit/CGMBLEKitUI/he.lproj/Localizable.strings


Неке датотеке нису приказане због велике количине промена