diff --git a/.gitignore b/.gitignore
index 2bd1cb9a..e23c67fc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,7 @@ profile
DerivedData
*.hmap
*.ipa
+project.xcworkspace
# Bundler
.bundle
@@ -32,3 +33,4 @@ Carthage
#
Pods/
+
diff --git a/.travis.yml b/.travis.yml
index 6ee76693..5cd8db87 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,5 @@
language: objective-c
-osx_image: xcode9.2
+osx_image: xcode9.3
# cache: cocoapods
# podfile: Example/Podfile
@@ -12,5 +12,5 @@ script:
# Build cocoapods example project
# - set -o pipefail && xcodebuild -workspace Example/xDripG5.xcworkspace -scheme xDripG5-Example -sdk iphonesimulator -destination name="iPhone SE" ONLY_ACTIVE_ARCH=NO | xcpretty
# Build Travis project and run tests
-- xcodebuild -project xDripG5.xcodeproj -scheme xDripG5 -sdk iphonesimulator11.2 build -destination name="iPhone SE" test
+- xcodebuild -project xDripG5.xcodeproj -scheme xDripG5 build -destination name="iPhone SE" test
# - pod lib lint
diff --git a/ResetTransmitter/AppDelegate.swift b/ResetTransmitter/AppDelegate.swift
new file mode 100644
index 00000000..2b25b995
--- /dev/null
+++ b/ResetTransmitter/AppDelegate.swift
@@ -0,0 +1,45 @@
+//
+// AppDelegate.swift
+// ResetTransmitter
+//
+// Copyright © 2018 LoopKit Authors. All rights reserved.
+//
+
+import UIKit
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ var window: UIWindow?
+
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
+ // Override point for customization after application launch.
+ 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 invalidate graphics rendering callbacks. 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.
+ }
+
+ func applicationWillEnterForeground(_ application: UIApplication) {
+ // Called as part of the transition from the background to the active 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.
+ }
+
+ func applicationWillTerminate(_ application: UIApplication) {
+ // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
+ }
+
+
+}
+
diff --git a/ResetTransmitter/Assets.xcassets/AppIcon.appiconset/Contents.json b/ResetTransmitter/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 00000000..d8db8d65
--- /dev/null
+++ b/ResetTransmitter/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"
+ }
+}
\ No newline at end of file
diff --git a/ResetTransmitter/Assets.xcassets/Contents.json b/ResetTransmitter/Assets.xcassets/Contents.json
new file mode 100644
index 00000000..da4a164c
--- /dev/null
+++ b/ResetTransmitter/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/ResetTransmitter/Base.lproj/LaunchScreen.storyboard b/ResetTransmitter/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 00000000..94b34243
--- /dev/null
+++ b/ResetTransmitter/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ResetTransmitter/Base.lproj/Main.storyboard b/ResetTransmitter/Base.lproj/Main.storyboard
new file mode 100644
index 00000000..d59b73c3
--- /dev/null
+++ b/ResetTransmitter/Base.lproj/Main.storyboard
@@ -0,0 +1,270 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This tool can reset the clock on a transmitter that has reached its expiration date, allowing new sensor sessions to again be started.
+This may have unintended consequences for data services, such as Clarity and Share, especially when using a reset transmitter with the same account.
+Resetting cannot be undone.
+Use at your own risk.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ResetTransmitter/CompletionViewController.swift b/ResetTransmitter/CompletionViewController.swift
new file mode 100644
index 00000000..74aad2ee
--- /dev/null
+++ b/ResetTransmitter/CompletionViewController.swift
@@ -0,0 +1,38 @@
+//
+// CompletionViewController.swift
+// ResetTransmitter
+//
+// Copyright © 2018 LoopKit Authors. All rights reserved.
+//
+
+import UIKit
+import UserNotifications
+
+class CompletionViewController: UITableViewController {
+
+ @IBOutlet weak var textView: UITextView!
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ if UIApplication.shared.applicationState == .background {
+ let content = UNMutableNotificationContent()
+ content.badge = 1
+ content.title = NSLocalizedString("Transmitter Reset Complete", comment: "Notification title for background completion notification")
+ content.body = textView.text
+ content.sound = .default()
+
+ let request = UNNotificationRequest(identifier: "Completion", content: content, trigger: nil)
+
+ UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
+ }
+ }
+
+ override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
+ return false
+ }
+
+ override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
+ return nil
+ }
+}
diff --git a/ResetTransmitter/Info.plist b/ResetTransmitter/Info.plist
new file mode 100644
index 00000000..54be3bdc
--- /dev/null
+++ b/ResetTransmitter/Info.plist
@@ -0,0 +1,51 @@
+
+
+
+
+ CFBundleDisplayName
+ Reset
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ LSRequiresIPhoneOS
+
+ UIBackgroundModes
+
+ bluetooth-central
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
diff --git a/ResetTransmitter/Locked.swift b/ResetTransmitter/Locked.swift
new file mode 100644
index 00000000..20cafd22
--- /dev/null
+++ b/ResetTransmitter/Locked.swift
@@ -0,0 +1,40 @@
+//
+// Locked.swift
+// LoopKit
+//
+// Copyright © 2018 LoopKit Authors. All rights reserved.
+//
+
+import os.lock
+
+
+internal class Locked {
+ private var lock = os_unfair_lock()
+ private var _value: T
+
+ init(_ value: T) {
+ os_unfair_lock_lock(&lock)
+ defer { os_unfair_lock_unlock(&lock) }
+ _value = value
+ }
+
+ var value: T {
+ get {
+ os_unfair_lock_lock(&lock)
+ defer { os_unfair_lock_unlock(&lock) }
+ return _value
+ }
+ set {
+ os_unfair_lock_lock(&lock)
+ defer { os_unfair_lock_unlock(&lock) }
+ _value = newValue
+ }
+ }
+
+ func mutate(_ changes: (_ value: inout T) -> Void) -> T {
+ os_unfair_lock_lock(&lock)
+ defer { os_unfair_lock_unlock(&lock) }
+ changes(&value)
+ return value
+ }
+}
diff --git a/ResetTransmitter/ResetManager.swift b/ResetTransmitter/ResetManager.swift
new file mode 100644
index 00000000..09c88083
--- /dev/null
+++ b/ResetTransmitter/ResetManager.swift
@@ -0,0 +1,138 @@
+//
+// ResetManager.swift
+// ResetTransmitter
+//
+// Copyright © 2018 LoopKit Authors. All rights reserved.
+//
+
+import xDripG5
+import os.log
+
+
+class ResetManager {
+ enum State {
+ case initialized
+ case resetting(transmitter: Transmitter)
+ case completed
+ }
+
+ private(set) var state: State {
+ get {
+ return lockedState.value
+ }
+ set {
+ let oldValue = state
+
+ if case .resetting(let transmitter) = oldValue {
+ transmitter.stopScanning()
+ transmitter.delegate = nil
+ transmitter.commandSource = nil
+ }
+
+ lockedState.value = newValue
+
+ if case .resetting(let transmitter) = newValue {
+ transmitter.delegate = self
+ transmitter.commandSource = self
+ transmitter.resumeScanning()
+ }
+
+ os_log("State changed: %{public}@ -> %{public}@", log: log, type: .debug, String(describing: oldValue), String(describing: newValue))
+ delegate?.resetManager(self, didChangeStateFrom: oldValue)
+ }
+ }
+ private let lockedState = Locked(State.initialized)
+
+ private let log = OSLog(subsystem: "com.loopkit.CGMBLEKit", category: "ResetManager")
+
+ weak var delegate: ResetManagerDelegate?
+}
+
+
+protocol ResetManagerDelegate: class {
+ func resetManager(_ manager: ResetManager, didError error: Error)
+
+ func resetManager(_ manager: ResetManager, didChangeStateFrom oldState: ResetManager.State)
+}
+
+
+extension ResetManager {
+ func cancel() {
+ guard case .resetting = state else {
+ return
+ }
+
+ state = .initialized
+ }
+
+ func resetTransmitter(withID id: String) {
+ guard id.count == 6 else {
+ return
+ }
+
+ switch state {
+ case .initialized, .completed:
+ break
+ case .resetting(transmitter: let transmitter):
+ guard transmitter.ID != id else {
+ return
+ }
+ }
+
+ state = .resetting(transmitter: Transmitter(id: id, passiveModeEnabled: false))
+
+ #if targetEnvironment(simulator)
+ DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5)) {
+ self.delegate?.resetManager(self, didError: TransmitterError.controlError("Simulated Error"))
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5)) {
+ if case .resetting = self.state {
+ self.state = .completed
+ }
+ }
+ }
+ #endif
+ }
+}
+
+
+extension ResetManager: TransmitterDelegate {
+ func transmitter(_ transmitter: Transmitter, didError error: Error) {
+ os_log("Transmitter error: %{public}@", log: log, type: .error, String(describing: error))
+ delegate?.resetManager(self, didError: error)
+ }
+
+ func transmitter(_ transmitter: Transmitter, didRead glucose: Glucose) {
+ // Not interested
+ }
+
+ func transmitter(_ transmitter: Transmitter, didReadBackfill glucose: [Glucose]) {
+ // Not interested
+ }
+
+ func transmitter(_ transmitter: Transmitter, didReadUnknownData data: Data) {
+ // Not interested
+ }
+}
+
+
+extension ResetManager: TransmitterCommandSource {
+ func dequeuePendingCommand(for transmitter: Transmitter) -> Command? {
+ if case .resetting = state {
+ return .resetTransmitter
+ }
+
+ return nil
+ }
+
+ func transmitter(_ transmitter: Transmitter, didFail command: Command, with error: Error) {
+ os_log("Command error: %{public}@", log: log, type: .error, String(describing: error))
+ delegate?.resetManager(self, didError: error)
+ }
+
+ func transmitter(_ transmitter: Transmitter, didComplete command: Command) {
+ if case .resetTransmitter = command {
+ state = .completed
+ }
+ }
+}
diff --git a/ResetTransmitter/ResetViewController.swift b/ResetTransmitter/ResetViewController.swift
new file mode 100644
index 00000000..41606884
--- /dev/null
+++ b/ResetTransmitter/ResetViewController.swift
@@ -0,0 +1,261 @@
+//
+// ResetViewController.swift
+// ResetTransmitter
+//
+// Copyright © 2018 LoopKit Authors. All rights reserved.
+//
+
+import UIKit
+import UserNotifications
+
+
+class ResetViewController: UITableViewController {
+
+ private enum State {
+ case empty
+ case needsConfiguration
+ case configured
+ case resetting
+ case completed
+ }
+
+ private var state: State = .empty {
+ didSet {
+ guard oldValue != state else {
+ return
+ }
+
+ lastError = nil
+ updateButtonState()
+ updateTransmitterIDFieldState()
+ updateStatusIndicatorState()
+
+ if state == .completed {
+ performSegue(withIdentifier: "CompletionViewController", sender: self)
+ }
+ }
+ }
+
+ @IBOutlet var hairlines: [UIView]!
+
+ @IBOutlet weak var resetButton: Button!
+
+ @IBOutlet weak var transmitterIDField: TextField!
+
+ @IBOutlet weak var spinner: UIActivityIndicatorView!
+
+ @IBOutlet weak var errorLabel: UILabel!
+
+ @IBOutlet weak var buttonTopSpace: NSLayoutConstraint!
+
+ private var needsButtonTopSpaceUpdate = true
+
+ private var lastError: Error?
+
+ private lazy var resetManager: ResetManager = {
+ let manager = ResetManager()
+ manager.delegate = self
+ return manager
+ }()
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ for hairline in hairlines {
+ for constraint in hairline.constraints {
+ constraint.constant = 1 / UIScreen.main.scale
+ }
+ }
+
+ self.navigationController?.delegate = self
+ self.navigationController?.navigationBar.shadowImage = UIImage()
+
+ state = .needsConfiguration
+ }
+
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+
+ UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .sound, .alert]) { (success, error) in
+ //
+ }
+ }
+
+ override func viewDidDisappear(_ animated: Bool) {
+ super.viewDidDisappear(animated)
+
+ state = .needsConfiguration
+ }
+
+ override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+ // Update the constraint once to fit the height of the screen
+ if indexPath.section == tableView.numberOfSections - 1 && needsButtonTopSpaceUpdate {
+ needsButtonTopSpaceUpdate = false
+ let currentValue = buttonTopSpace.constant
+ let suggestedValue = max(0, tableView.bounds.size.height - tableView.contentSize.height - tableView.safeAreaInsets.bottom - tableView.safeAreaInsets.top)
+
+ if abs(currentValue - suggestedValue) > .ulpOfOne {
+ buttonTopSpace.constant = suggestedValue
+ }
+ }
+
+ return UITableViewAutomaticDimension
+ }
+
+ override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
+ return false
+ }
+
+ override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
+ return nil
+ }
+
+ // MARK: - Actions
+
+ @IBAction func performAction(_ sender: Any) {
+ switch state {
+ case .empty, .needsConfiguration:
+ // Actions are not allowed
+ break
+ case .configured:
+ // Begin reset
+ resetTransmitter(withID: transmitterIDField.text ?? "")
+ case .resetting:
+ // Cancel pending reset
+ resetManager.cancel()
+ case .completed:
+ // Ignore actions here
+ break
+ }
+ }
+
+ private func resetTransmitter(withID id: String) {
+ let controller = UIAlertController(
+ title: NSLocalizedString("Are you sure you want to reset this transmitter?", comment: "Title of the reset confirmation sheet"),
+ message: NSLocalizedString("It will take up to 10 minutes to complete.", comment: "Message of the reset confirmation sheet"), preferredStyle: .actionSheet
+ )
+
+ controller.addAction(UIAlertAction(
+ title: NSLocalizedString("Reset", comment: "Reset button title"),
+ style: .destructive,
+ handler: { (action) in
+ self.resetManager.resetTransmitter(withID: id)
+ }
+ ))
+
+ controller.addAction(UIAlertAction(
+ title: NSLocalizedString("Cancel", comment: "Title of button to cancel reset"),
+ style: .cancel,
+ handler: nil
+ ))
+
+ present(controller, animated: true, completion: nil)
+ }
+}
+
+
+// MARK: - UI state management
+extension ResetViewController {
+ private func updateButtonState() {
+ switch state {
+ case .empty, .needsConfiguration:
+ resetButton.isEnabled = false
+ case .configured, .resetting, .completed:
+ resetButton.isEnabled = true
+ }
+
+ switch state {
+ case .empty, .needsConfiguration, .configured:
+ resetButton.setTitle(NSLocalizedString("Reset", comment: "Title of button to begin reset"), for: .normal)
+ resetButton.tintColor = .red
+ case .resetting, .completed:
+ resetButton.setTitle(NSLocalizedString("Cancel", comment: "Title of button to cancel reset"), for: .normal)
+ resetButton.tintColor = .darkGray
+ }
+ }
+
+ private func updateTransmitterIDFieldState() {
+ switch state {
+ case .empty, .needsConfiguration:
+ transmitterIDField.text = ""
+ transmitterIDField.isEnabled = true
+ case .configured:
+ transmitterIDField.isEnabled = true
+ case .resetting, .completed:
+ transmitterIDField.isEnabled = false
+ }
+ }
+
+ private func updateStatusIndicatorState() {
+ switch self.state {
+ case .empty, .needsConfiguration, .configured, .completed:
+ self.spinner.stopAnimating()
+ self.errorLabel.superview?.isHidden = true
+ case .resetting:
+ self.spinner.startAnimating()
+ if let error = lastError {
+ self.errorLabel.text = String(describing: error)
+ }
+ self.errorLabel.superview?.isHidden =
+ (self.lastError == nil)
+ }
+ }
+}
+
+
+extension ResetViewController: ResetManagerDelegate {
+ func resetManager(_ manager: ResetManager, didError error: Error) {
+ DispatchQueue.main.async {
+ self.lastError = error
+ self.updateStatusIndicatorState()
+ }
+ }
+
+ func resetManager(_ manager: ResetManager, didChangeStateFrom oldState: ResetManager.State) {
+ DispatchQueue.main.async {
+ switch manager.state {
+ case .initialized:
+ self.state = .configured
+ case .resetting:
+ self.state = .resetting
+ case .completed:
+ self.state = .completed
+ }
+ }
+ }
+}
+
+extension ResetViewController: UINavigationControllerDelegate {
+ func navigationControllerSupportedInterfaceOrientations(_ navigationController: UINavigationController) -> UIInterfaceOrientationMask {
+ return .portrait
+ }
+}
+
+extension ResetViewController: UITextFieldDelegate {
+ func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+ textField.resignFirstResponder()
+ return false
+ }
+
+ func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
+ guard let text = textField.text, let stringRange = Range(range, in: text) else {
+ state = .needsConfiguration
+ return true
+ }
+
+ let newText = text.replacingCharacters(in: stringRange, with: string)
+
+ if newText.count >= 6 {
+ if newText.count == 6 {
+ textField.text = newText
+ textField.resignFirstResponder()
+ }
+
+ state = .configured
+ return false
+ }
+
+ state = .needsConfiguration
+ return true
+ }
+}
diff --git a/ResetTransmitter/Views/Button.swift b/ResetTransmitter/Views/Button.swift
new file mode 100644
index 00000000..c5a09ad2
--- /dev/null
+++ b/ResetTransmitter/Views/Button.swift
@@ -0,0 +1,53 @@
+//
+// Button.swift
+// ResetTransmitter
+//
+// Copyright © 2018 LoopKit Authors. All rights reserved.
+//
+
+import UIKit
+
+
+class Button: UIButton {
+
+ required init?(coder aDecoder: NSCoder) {
+ super.init(coder: aDecoder)
+ }
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+
+ backgroundColor = tintColor
+ layer.cornerRadius = 6
+
+ titleLabel?.adjustsFontForContentSizeCategory = true
+ contentEdgeInsets.top = 14
+ contentEdgeInsets.bottom = 14
+ setTitleColor(.white, for: .normal)
+ }
+
+ override func tintColorDidChange() {
+ super.tintColorDidChange()
+
+ backgroundColor = tintColor
+ }
+
+ override func prepareForInterfaceBuilder() {
+ super.prepareForInterfaceBuilder()
+
+ tintColor = .blue
+ tintColorDidChange()
+ }
+
+ override var isHighlighted: Bool {
+ didSet {
+ alpha = isHighlighted ? 0.5 : 1
+ }
+ }
+
+ override var isEnabled: Bool {
+ didSet {
+ tintAdjustmentMode = isEnabled ? .automatic : .dimmed
+ }
+ }
+}
diff --git a/ResetTransmitter/Views/ParagraphView.swift b/ResetTransmitter/Views/ParagraphView.swift
new file mode 100644
index 00000000..d51a6818
--- /dev/null
+++ b/ResetTransmitter/Views/ParagraphView.swift
@@ -0,0 +1,29 @@
+//
+// ParagraphView.swift
+// ResetTransmitter
+//
+// Copyright © 2018 LoopKit Authors. All rights reserved.
+//
+
+import UIKit
+
+class ParagraphView: UITextView {
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+
+ textContainer.lineFragmentPadding = 0
+
+ let paragraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
+ paragraphStyle.paragraphSpacing = 10
+
+ attributedText = NSAttributedString(
+ string: text,
+ attributes: [
+ .paragraphStyle: paragraphStyle,
+ .font: UIFont.preferredFont(forTextStyle: .body)
+ ]
+ )
+ }
+
+}
diff --git a/ResetTransmitter/Views/TextField.swift b/ResetTransmitter/Views/TextField.swift
new file mode 100644
index 00000000..668e1a9d
--- /dev/null
+++ b/ResetTransmitter/Views/TextField.swift
@@ -0,0 +1,26 @@
+//
+// TextField.swift
+// ResetTransmitter
+//
+// Copyright © 2018 LoopKit Authors. All rights reserved.
+//
+
+import UIKit
+
+class TextField: UITextField {
+
+ private let textInset = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
+
+ override func editingRect(forBounds bounds: CGRect) -> CGRect {
+ return UIEdgeInsetsInsetRect(bounds, textInset)
+ }
+
+ override func textRect(forBounds bounds: CGRect) -> CGRect {
+ return UIEdgeInsetsInsetRect(bounds, textInset)
+ }
+
+ override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
+ return UIEdgeInsetsInsetRect(bounds, textInset)
+ }
+
+}
diff --git a/xDripG5.xcodeproj/project.pbxproj b/xDripG5.xcodeproj/project.pbxproj
index 8f159a59..1a164056 100644
--- a/xDripG5.xcodeproj/project.pbxproj
+++ b/xDripG5.xcodeproj/project.pbxproj
@@ -7,13 +7,26 @@
objects = {
/* Begin PBXBuildFile section */
+ 4308103620785FEA00B66384 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4308103520785FEA00B66384 /* AppDelegate.swift */; };
+ 4308103B20785FEA00B66384 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4308103920785FEA00B66384 /* Main.storyboard */; };
+ 4308103D20785FEA00B66384 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4308103C20785FEA00B66384 /* Assets.xcassets */; };
+ 4308104020785FEA00B66384 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4308103E20785FEA00B66384 /* LaunchScreen.storyboard */; };
+ 43081047207861BC00B66384 /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43081046207861BC00B66384 /* Button.swift */; };
430D64C51CB7846A00FCA750 /* NSData+CRC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430D64C41CB7846A00FCA750 /* NSData+CRC.swift */; };
431CE7631F8EEF6D00255374 /* CBPeripheral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7621F8EEF6D00255374 /* CBPeripheral.swift */; };
431CE7671F91D0B300255374 /* PeripheralManager+G5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7661F91D0B300255374 /* PeripheralManager+G5.swift */; };
4323115F1EFC870300B95E62 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4323115E1EFC870300B95E62 /* OSLog.swift */; };
433BC81B205CB64A000B1200 /* GlucoseBackfillMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 433BC81A205CB64A000B1200 /* GlucoseBackfillMessage.swift */; };
433BC81D205CBB16000B1200 /* GlucoseBackfillMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 433BC81C205CBB16000B1200 /* GlucoseBackfillMessageTests.swift */; };
+ 433F2E532078990300808FA5 /* ParagraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 433F2E522078990300808FA5 /* ParagraphView.swift */; };
+ 433F2E5520789F9D00808FA5 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 433F2E5420789F9D00808FA5 /* TextField.swift */; };
+ 433F2E57207928A200808FA5 /* ResetManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 433F2E56207928A200808FA5 /* ResetManager.swift */; };
+ 433F2E5920792A0100808FA5 /* Locked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 433F2E5820792A0100808FA5 /* Locked.swift */; };
+ 433F2E5B2079747A00808FA5 /* CompletionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 433F2E5A2079747A00808FA5 /* CompletionViewController.swift */; };
+ 433F2E5C2079BB7D00808FA5 /* xDripG5.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43CABDF31C3506F100005705 /* xDripG5.framework */; };
+ 433F2E5D2079BB7D00808FA5 /* xDripG5.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 43CABDF31C3506F100005705 /* xDripG5.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
43460F88200B30D10030C0E3 /* TransmitterIDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43460F87200B30D10030C0E3 /* TransmitterIDTests.swift */; };
+ 434B288320649D3C000EE07B /* ResetMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 434B288220649D3C000EE07B /* ResetMessage.swift */; };
435535D41FB2C1B000CE5A23 /* PeripheralManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435535D31FB2C1B000CE5A23 /* PeripheralManagerError.swift */; };
437AFEFA2038EC43008C4892 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437AFEF92038EC43008C4892 /* AppDelegate.swift */; };
437AFEFC2038EC43008C4892 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437AFEFB2038EC43008C4892 /* ViewController.swift */; };
@@ -56,6 +69,7 @@
43D140D12047BD930032346D /* Opcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D140D02047BD930032346D /* Opcode.swift */; };
43DC87C01C8B509B005BC30D /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DC87BF1C8B509B005BC30D /* Data.swift */; };
43DC87C21C8B520F005BC30D /* GlucoseRxMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DC87C11C8B520F005BC30D /* GlucoseRxMessageTests.swift */; };
+ 43E05F22207869D9003212C1 /* ResetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43E05F21207869D9003212C1 /* ResetViewController.swift */; };
43E3978B1D5668BD0028E321 /* CalibrationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43E3978A1D5668BD0028E321 /* CalibrationState.swift */; };
43E3978D1D566AEA0028E321 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43E3978C1D566AEA0028E321 /* HealthKit.framework */; };
43E3978F1D566B170028E321 /* Glucose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43E3978E1D566B170028E321 /* Glucose.swift */; };
@@ -81,6 +95,13 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
+ 433F2E5E2079BB7D00808FA5 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 43CABDEA1C3506F100005705 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 43CABDF21C3506F100005705;
+ remoteInfo = xDripG5;
+ };
437AFF142038EDEC008C4892 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 43CABDEA1C3506F100005705 /* Project object */;
@@ -98,6 +119,17 @@
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
+ 433F2E602079BB7D00808FA5 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 433F2E5D2079BB7D00808FA5 /* xDripG5.framework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
434FD6891C352390000B4E2E /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@@ -121,13 +153,26 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 4308103320785FEA00B66384 /* ResetTransmitter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ResetTransmitter.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 4308103520785FEA00B66384 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 4308103A20785FEA00B66384 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ 4308103C20785FEA00B66384 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 4308103F20785FEA00B66384 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 4308104120785FEA00B66384 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 43081046207861BC00B66384 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; };
430D64C41CB7846A00FCA750 /* NSData+CRC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSData+CRC.swift"; sourceTree = ""; };
431CE7621F8EEF6D00255374 /* CBPeripheral.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBPeripheral.swift; sourceTree = ""; };
431CE7661F91D0B300255374 /* PeripheralManager+G5.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PeripheralManager+G5.swift"; sourceTree = ""; };
4323115E1EFC870300B95E62 /* OSLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; };
433BC81A205CB64A000B1200 /* GlucoseBackfillMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseBackfillMessage.swift; sourceTree = ""; };
433BC81C205CBB16000B1200 /* GlucoseBackfillMessageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseBackfillMessageTests.swift; sourceTree = ""; };
+ 433F2E522078990300808FA5 /* ParagraphView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParagraphView.swift; sourceTree = ""; };
+ 433F2E5420789F9D00808FA5 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; };
+ 433F2E56207928A200808FA5 /* ResetManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetManager.swift; sourceTree = ""; };
+ 433F2E5820792A0100808FA5 /* Locked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Locked.swift; sourceTree = ""; };
+ 433F2E5A2079747A00808FA5 /* CompletionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionViewController.swift; sourceTree = ""; };
43460F87200B30D10030C0E3 /* TransmitterIDTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransmitterIDTests.swift; sourceTree = ""; };
+ 434B288220649D3C000EE07B /* ResetMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetMessage.swift; sourceTree = ""; };
435535D31FB2C1B000CE5A23 /* PeripheralManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralManagerError.swift; sourceTree = ""; };
437AFEF72038EC43008C4892 /* CGMBLEKit Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "CGMBLEKit Example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
437AFEF92038EC43008C4892 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
@@ -170,6 +215,7 @@
43D140D02047BD930032346D /* Opcode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Opcode.swift; sourceTree = ""; };
43DC87BF1C8B509B005BC30D /* Data.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = ""; };
43DC87C11C8B520F005BC30D /* GlucoseRxMessageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseRxMessageTests.swift; sourceTree = ""; };
+ 43E05F21207869D9003212C1 /* ResetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetViewController.swift; sourceTree = ""; };
43E3978A1D5668BD0028E321 /* CalibrationState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalibrationState.swift; sourceTree = ""; };
43E3978C1D566AEA0028E321 /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = System/Library/Frameworks/HealthKit.framework; sourceTree = SDKROOT; };
43E3978E1D566B170028E321 /* Glucose.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Glucose.swift; sourceTree = ""; };
@@ -193,6 +239,14 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
+ 4308103020785FEA00B66384 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 433F2E5C2079BB7D00808FA5 /* xDripG5.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
437AFEF42038EC43008C4892 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -221,6 +275,33 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 4308103420785FEA00B66384 /* ResetTransmitter */ = {
+ isa = PBXGroup;
+ children = (
+ 43081045207861A500B66384 /* Views */,
+ 4308103520785FEA00B66384 /* AppDelegate.swift */,
+ 433F2E5A2079747A00808FA5 /* CompletionViewController.swift */,
+ 43E05F21207869D9003212C1 /* ResetViewController.swift */,
+ 433F2E5820792A0100808FA5 /* Locked.swift */,
+ 433F2E56207928A200808FA5 /* ResetManager.swift */,
+ 4308103920785FEA00B66384 /* Main.storyboard */,
+ 4308103C20785FEA00B66384 /* Assets.xcassets */,
+ 4308103E20785FEA00B66384 /* LaunchScreen.storyboard */,
+ 4308104120785FEA00B66384 /* Info.plist */,
+ );
+ path = ResetTransmitter;
+ sourceTree = "";
+ };
+ 43081045207861A500B66384 /* Views */ = {
+ isa = PBXGroup;
+ children = (
+ 43081046207861BC00B66384 /* Button.swift */,
+ 433F2E522078990300808FA5 /* ParagraphView.swift */,
+ 433F2E5420789F9D00808FA5 /* TextField.swift */,
+ );
+ path = Views;
+ sourceTree = "";
+ };
437AFEF82038EC43008C4892 /* CGMBLEKit Example */ = {
isa = PBXGroup;
children = (
@@ -251,6 +332,7 @@
43CABE011C3506F100005705 /* xDripG5Tests */,
437AFEF82038EC43008C4892 /* CGMBLEKit Example */,
43D140CD2047AA530032346D /* Common */,
+ 4308103420785FEA00B66384 /* ResetTransmitter */,
43CABDF41C3506F100005705 /* Products */,
437AFF172038EDF9008C4892 /* Frameworks */,
);
@@ -262,6 +344,7 @@
43CABDF31C3506F100005705 /* xDripG5.framework */,
43CABDFD1C3506F100005705 /* xDripG5Tests.xctest */,
437AFEF72038EC43008C4892 /* CGMBLEKit Example.app */,
+ 4308103320785FEA00B66384 /* ResetTransmitter.app */,
);
name = Products;
sourceTree = "";
@@ -330,6 +413,7 @@
43CABE1D1C350B3D00005705 /* GlucoseRxMessage.swift */,
43CABE1E1C350B3D00005705 /* GlucoseTxMessage.swift */,
43CABE1F1C350B3D00005705 /* KeepAliveTxMessage.swift */,
+ 434B288220649D3C000EE07B /* ResetMessage.swift */,
43F82BCD1D035D5C006F5DD7 /* SessionStartRxMessage.swift */,
43CE7CCD1CA73C22003CC1B0 /* SessionStartTxMessage.swift */,
43F82BCF1D035D68006F5DD7 /* SessionStopRxMessage.swift */,
@@ -367,6 +451,25 @@
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
+ 4308103220785FEA00B66384 /* ResetTransmitter */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 4308104420785FEA00B66384 /* Build configuration list for PBXNativeTarget "ResetTransmitter" */;
+ buildPhases = (
+ 4308102F20785FEA00B66384 /* Sources */,
+ 4308103020785FEA00B66384 /* Frameworks */,
+ 4308103120785FEA00B66384 /* Resources */,
+ 433F2E602079BB7D00808FA5 /* Embed Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 433F2E5F2079BB7D00808FA5 /* PBXTargetDependency */,
+ );
+ name = ResetTransmitter;
+ productName = ResetTransmitter;
+ productReference = 4308103320785FEA00B66384 /* ResetTransmitter.app */;
+ productType = "com.apple.product-type.application";
+ };
437AFEF62038EC43008C4892 /* CGMBLEKit Example */ = {
isa = PBXNativeTarget;
buildConfigurationList = 437AFF082038EC43008C4892 /* Build configuration list for PBXNativeTarget "CGMBLEKit Example" */;
@@ -429,10 +532,19 @@
43CABDEA1C3506F100005705 /* Project object */ = {
isa = PBXProject;
attributes = {
- LastSwiftUpdateCheck = 0920;
- LastUpgradeCheck = 0900;
+ LastSwiftUpdateCheck = 0930;
+ LastUpgradeCheck = 0930;
ORGANIZATIONNAME = "LoopKit Authors";
TargetAttributes = {
+ 4308103220785FEA00B66384 = {
+ CreatedOnToolsVersion = 9.3;
+ ProvisioningStyle = Automatic;
+ SystemCapabilities = {
+ com.apple.BackgroundModes = {
+ enabled = 1;
+ };
+ };
+ };
437AFEF62038EC43008C4892 = {
CreatedOnToolsVersion = 9.2;
ProvisioningStyle = Automatic;
@@ -468,11 +580,22 @@
43CABDF21C3506F100005705 /* xDripG5 */,
43CABDFC1C3506F100005705 /* xDripG5Tests */,
437AFEF62038EC43008C4892 /* CGMBLEKit Example */,
+ 4308103220785FEA00B66384 /* ResetTransmitter */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
+ 4308103120785FEA00B66384 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 4308104020785FEA00B66384 /* LaunchScreen.storyboard in Resources */,
+ 4308103D20785FEA00B66384 /* Assets.xcassets in Resources */,
+ 4308103B20785FEA00B66384 /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
437AFEF52038EC43008C4892 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -500,6 +623,21 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
+ 4308102F20785FEA00B66384 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 43081047207861BC00B66384 /* Button.swift in Sources */,
+ 4308103620785FEA00B66384 /* AppDelegate.swift in Sources */,
+ 433F2E5B2079747A00808FA5 /* CompletionViewController.swift in Sources */,
+ 433F2E5920792A0100808FA5 /* Locked.swift in Sources */,
+ 43E05F22207869D9003212C1 /* ResetViewController.swift in Sources */,
+ 433F2E5520789F9D00808FA5 /* TextField.swift in Sources */,
+ 433F2E532078990300808FA5 /* ParagraphView.swift in Sources */,
+ 433F2E57207928A200808FA5 /* ResetManager.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
437AFEF32038EC43008C4892 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -536,6 +674,7 @@
43CE7CD01CA73C57003CC1B0 /* SessionStopTxMessage.swift in Sources */,
431CE7671F91D0B300255374 /* PeripheralManager+G5.swift in Sources */,
43CABE2A1C350B3D00005705 /* GlucoseTxMessage.swift in Sources */,
+ 434B288320649D3C000EE07B /* ResetMessage.swift in Sources */,
43CE7CC81CA73AEB003CC1B0 /* FirmwareVersionTxMessage.swift in Sources */,
43D140D12047BD930032346D /* Opcode.swift in Sources */,
43CE7CCC1CA73BCC003CC1B0 /* BatteryStatusTxMessage.swift in Sources */,
@@ -585,6 +724,11 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
+ 433F2E5F2079BB7D00808FA5 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 43CABDF21C3506F100005705 /* xDripG5 */;
+ targetProxy = 433F2E5E2079BB7D00808FA5 /* PBXContainerItemProxy */;
+ };
437AFF152038EDEC008C4892 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 43CABDF21C3506F100005705 /* xDripG5 */;
@@ -598,6 +742,22 @@
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
+ 4308103920785FEA00B66384 /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 4308103A20785FEA00B66384 /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
+ 4308103E20785FEA00B66384 /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 4308103F20785FEA00B66384 /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
437AFF022038EC43008C4892 /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
@@ -617,6 +777,59 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
+ 4308104220785FEA00B66384 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ CODE_SIGN_STYLE = Automatic;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = ResetTransmitter/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.3;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.loopkit.ResetTransmitter;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_VERSION = 4.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 4308104320785FEA00B66384 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ CODE_SIGN_STYLE = Automatic;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = ResetTransmitter/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.3;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.loopkit.ResetTransmitter;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 4.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
437AFF062038EC43008C4892 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -676,12 +889,14 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
@@ -734,12 +949,14 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
@@ -781,7 +998,6 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
DEFINES_MODULE = YES;
- DEVELOPMENT_TEAM = 57NRR26737;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 15;
DYLIB_INSTALL_NAME_BASE = "@rpath";
@@ -804,7 +1020,6 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
DEFINES_MODULE = YES;
- DEVELOPMENT_TEAM = 57NRR26737;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 15;
DYLIB_INSTALL_NAME_BASE = "@rpath";
@@ -850,6 +1065,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
+ 4308104420785FEA00B66384 /* Build configuration list for PBXNativeTarget "ResetTransmitter" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 4308104220785FEA00B66384 /* Debug */,
+ 4308104320785FEA00B66384 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
437AFF082038EC43008C4892 /* Build configuration list for PBXNativeTarget "CGMBLEKit Example" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/xDripG5.xcodeproj/xcshareddata/xcschemes/xDripG5.xcscheme b/xDripG5.xcodeproj/xcshareddata/xcschemes/xDripG5.xcscheme
index 693dfd0c..683bc3f0 100644
--- a/xDripG5.xcodeproj/xcshareddata/xcschemes/xDripG5.xcscheme
+++ b/xDripG5.xcodeproj/xcshareddata/xcschemes/xDripG5.xcscheme
@@ -1,6 +1,6 @@
[CBUUID] {
- let knownServiceUUIDs = services?.flatMap({ $0.uuid }) ?? []
+ let knownServiceUUIDs = services?.compactMap({ $0.uuid }) ?? []
return serviceUUIDs.filter({ !knownServiceUUIDs.contains($0) })
}
func characteristicsToDiscover(from characteristicUUIDs: [CBUUID], for service: CBService) -> [CBUUID] {
- let knownCharacteristicUUIDs = service.characteristics?.flatMap({ $0.uuid }) ?? []
+ let knownCharacteristicUUIDs = service.characteristics?.compactMap({ $0.uuid }) ?? []
return characteristicUUIDs.filter({ !knownCharacteristicUUIDs.contains($0) })
}
}
diff --git a/xDripG5/Command.swift b/xDripG5/Command.swift
index a741c6bf..c9d0e2be 100644
--- a/xDripG5/Command.swift
+++ b/xDripG5/Command.swift
@@ -16,35 +16,40 @@ public enum Command: RawRepresentable {
case startSensor(at: Date)
case stopSensor(at: Date)
case calibrateSensor(to: HKQuantity, at: Date)
+ case resetTransmitter
public init?(rawValue: RawValue) {
- guard let action = rawValue["action"] as? Int else {
+ guard let action = rawValue["action"] as? Action.RawValue else {
return nil
}
+ let date = rawValue["date"] as? Date
+
switch Action(rawValue: action) {
case .startSensor?:
- guard let date = rawValue["date"] as? Date else {
+ guard let date = date else {
return nil
}
self = .startSensor(at: date)
case .stopSensor?:
- guard let date = rawValue["date"] as? Date else {
+ guard let date = date else {
return nil
}
self = .stopSensor(at: date)
case .calibrateSensor?:
- guard let date = rawValue["date"] as? Date, let glucose = rawValue["glucose"] as? HKQuantity else {
+ guard let date = date, let glucose = rawValue["glucose"] as? HKQuantity else {
return nil
}
self = .calibrateSensor(to: glucose, at: date)
+ case .resetTransmitter?:
+ self = .resetTransmitter
case .none:
return nil
}
}
private enum Action: Int {
- case startSensor, stopSensor, calibrateSensor
+ case startSensor, stopSensor, calibrateSensor, resetTransmitter
}
public var rawValue: RawValue {
@@ -65,6 +70,10 @@ public enum Command: RawRepresentable {
"date": date,
"glucose": glucose
]
+ case .resetTransmitter:
+ return [
+ "action": Action.resetTransmitter.rawValue
+ ]
}
}
}
diff --git a/xDripG5/Messages/ResetMessage.swift b/xDripG5/Messages/ResetMessage.swift
new file mode 100644
index 00000000..acaaf5d2
--- /dev/null
+++ b/xDripG5/Messages/ResetMessage.swift
@@ -0,0 +1,30 @@
+//
+// ResetMessage.swift
+// xDripG5
+//
+// Copyright © 2018 LoopKit Authors. All rights reserved.
+//
+
+import Foundation
+
+
+struct ResetTxMessage: RespondableMessage {
+ typealias Response = ResetRxMessage
+
+ var data: Data {
+ return Data(for: .resetTx).appendingCRC()
+ }
+}
+
+
+struct ResetRxMessage: TransmitterRxMessage {
+ let status: UInt8
+
+ init?(data: Data) {
+ guard data.count >= 2, data.starts(with: .resetRx) else {
+ return nil
+ }
+
+ status = data[1]
+ }
+}
diff --git a/xDripG5/Opcode.swift b/xDripG5/Opcode.swift
index 31249ed3..dc90e432 100644
--- a/xDripG5/Opcode.swift
+++ b/xDripG5/Opcode.swift
@@ -19,11 +19,6 @@ enum Opcode: UInt8 {
// Control
case disconnectTx = 0x09
-
-
-
-
-
case firmwareVersionTx = 0x20
case firmwareVersionRx = 0x21
case batteryStatusTx = 0x22
@@ -35,9 +30,6 @@ enum Opcode: UInt8 {
case sessionStopTx = 0x28
case sessionStopRx = 0x29
-
-
-
case glucoseTx = 0x30
case glucoseRx = 0x31
case calibrationDataTx = 0x32
@@ -47,16 +39,14 @@ enum Opcode: UInt8 {
case glucoseHistoryTx = 0x3e
- case eraseTx = 0x42
- case eraseRx = 0x43
+ case resetTx = 0x42
+ case resetRx = 0x43
case transmitterVersionTx = 0x4a
case transmitterVersionRx = 0x4b
case glucoseBackfillTx = 0x50
case glucoseBackfillRx = 0x51
-
-
}
diff --git a/xDripG5/Transmitter.swift b/xDripG5/Transmitter.swift
index a49c41bd..6a5b7d62 100644
--- a/xDripG5/Transmitter.swift
+++ b/xDripG5/Transmitter.swift
@@ -36,6 +36,17 @@ public enum TransmitterError: Error {
case controlError(String)
}
+extension TransmitterError: CustomStringConvertible {
+ public var description: String {
+ switch self {
+ case .authenticationError(let description):
+ return description
+ case .controlError(let description):
+ return description
+ }
+ }
+}
+
public final class Transmitter: BluetoothManagerDelegate {
@@ -125,6 +136,7 @@ public final class Transmitter: BluetoothManagerDelegate {
manager.peripheralManager?.perform { (peripheral) in
if self.passiveModeEnabled {
+ self.log.debug("Listening for control commands in passive mode")
do {
try peripheral.listenToControl()
} catch let error {
@@ -134,24 +146,30 @@ public final class Transmitter: BluetoothManagerDelegate {
}
} else {
do {
+ self.log.debug("Authenticating with transmitter")
let status = try peripheral.authenticate(id: self.id)
if status.bonded != 0x1 {
+ self.log.debug("Requesting bond")
try peripheral.requestBond()
- self.log.info("Bonding request sent. Waiting user to respond.")
+ self.log.debug("Bonding request sent. Waiting user to respond.")
}
try peripheral.enableNotify(shouldWaitForBond: status.bonded != 0x1)
defer {
+ self.log.debug("Initiating a disconnect")
peripheral.disconnect()
}
+ self.log.debug("Reading time")
let timeMessage = try peripheral.readTimeMessage()
let activationDate = Date(timeIntervalSinceNow: -TimeInterval(timeMessage.currentTime))
+ self.log.debug("Determined activation date: %@", String(describing: activationDate))
while let command = self.commandSource?.dequeuePendingCommand(for: self) {
+ self.log.debug("Sending command: %@", String(describing: command))
do {
_ = try peripheral.sendCommand(command, activationDate: activationDate)
self.commandSource?.transmitter(self, didComplete: command)
@@ -160,7 +178,9 @@ public final class Transmitter: BluetoothManagerDelegate {
}
}
+ self.log.debug("Reading glucose")
let glucoseMessage = try peripheral.readGlucose()
+ self.log.debug("Reading calibration data")
let calibrationMessage = try? peripheral.readCalibrationData()
let glucose = Glucose(
@@ -383,37 +403,27 @@ fileprivate extension PeripheralManager {
}
}
+ /// - Throws: TransmitterError.controlError
fileprivate func sendCommand(_ command: Command, activationDate: Date) throws -> TransmitterRxMessage {
- switch command {
- case .startSensor(let date):
- let startTime = UInt32(date.timeIntervalSince(activationDate))
- let secondsSince1970 = UInt32(date.timeIntervalSince1970)
-
- do {
+ do {
+ switch command {
+ case .startSensor(let date):
+ let startTime = UInt32(date.timeIntervalSince(activationDate))
+ let secondsSince1970 = UInt32(date.timeIntervalSince1970)
return try writeMessage(SessionStartTxMessage(startTime: startTime, secondsSince1970: secondsSince1970), for: .control)
- } catch let error {
- throw TransmitterError.controlError("Error starting session: \(error)")
- }
- case .stopSensor(let date):
- let stopTime = UInt32(date.timeIntervalSince(activationDate))
-
- do {
+ case .stopSensor(let date):
+ let stopTime = UInt32(date.timeIntervalSince(activationDate))
return try writeMessage(SessionStopTxMessage(stopTime: stopTime), for: .control)
- } catch let error {
- throw TransmitterError.controlError("Error stopping session: \(error)")
- }
- case .calibrateSensor(let glucose, let date):
- let unit = HKUnit.milligramsPerDeciliter
- let glucoseValue = UInt16(glucose.doubleValue(for: unit).rounded())
- let time = UInt32(date.timeIntervalSince(activationDate))
-
- do {
+ case .calibrateSensor(let glucose, let date):
+ let glucoseValue = UInt16(glucose.doubleValue(for: .milligramsPerDeciliter).rounded())
+ let time = UInt32(date.timeIntervalSince(activationDate))
return try writeMessage(CalibrateGlucoseTxMessage(time: time, glucose: glucoseValue), for: .control)
- } catch let error {
- throw TransmitterError.controlError("Error calibrating sensor: \(error)")
+ case .resetTransmitter:
+ return try writeMessage(ResetTxMessage(), for: .control)
}
+ } catch let error {
+ throw TransmitterError.controlError("Error during \(command): \(error)")
}
-
}
fileprivate func readGlucose() throws -> GlucoseRxMessage {