Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Common/Extensions/Double.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@
import Foundation


extension Double {
func floored(to increment: Double) -> Double {
extension FloatingPoint {
func floored(to increment: Self) -> Self {
if increment == 0 {
return self
}

return floor(self / increment) * increment
}

func ceiled(to increment: Double) -> Double {
func ceiled(to increment: Self) -> Self {
if increment == 0 {
return self
}
Expand Down
2 changes: 2 additions & 0 deletions Loop/Managers/DeviceDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,8 @@ extension DeviceDataManager: CustomDebugStringConvertible {
"",
pumpManager != nil ? String(reflecting: pumpManager!) : "pumpManager: nil",
"",
String(reflecting: watchManager!),
"",
String(reflecting: statusExtensionManager!),
].joined(separator: "\n")
}
Expand Down
143 changes: 101 additions & 42 deletions Loop/Managers/WatchDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,33 +42,18 @@ final class WatchDataManager: NSObject {
@objc private func updateWatch(_ notification: Notification) {
guard
let rawUpdateContext = notification.userInfo?[LoopDataManager.LoopUpdateContextKey] as? LoopDataManager.LoopUpdateContext.RawValue,
let updateContext = LoopDataManager.LoopUpdateContext(rawValue: rawUpdateContext),
let session = watchSession
let updateContext = LoopDataManager.LoopUpdateContext(rawValue: rawUpdateContext)
else {
return
}

switch updateContext {
case .glucose:
break
case .tempBasal:
break
case .glucose, .tempBasal:
sendWatchContextIfNeeded()
case .preferences:
sendSettingsIfNeeded()
return
default:
return
}

switch session.activationState {
case .notActivated, .inactive:
session.activate()
case .activated:
createWatchContext { (context) in
if let context = context {
self.sendWatchContext(context)
}
}
break
}
}

Expand Down Expand Up @@ -98,6 +83,17 @@ final class WatchDataManager: NSObject {

log.default("Transferring LoopSettingsUserInfo")
session.transferUserInfo(LoopSettingsUserInfo(settings: settings).rawValue)
}

private func sendWatchContextIfNeeded() {
guard let session = watchSession, session.isPaired, session.isWatchAppInstalled else {
return
}

guard case .activated = session.activationState else {
session.activate()
return
}

createWatchContext { (context) in
if let context = context {
Expand All @@ -107,32 +103,39 @@ final class WatchDataManager: NSObject {
}

private func sendWatchContext(_ context: WatchContext) {
if let session = watchSession, session.isPaired && session.isWatchAppInstalled {
let complicationShouldUpdate: Bool
guard let session = watchSession, session.isPaired, session.isWatchAppInstalled else {
return
}

guard case .activated = session.activationState else {
session.activate()
return
}

if let lastContext = lastComplicationContext,
let lastGlucose = lastContext.glucose, let lastGlucoseDate = lastContext.glucoseDate,
let newGlucose = context.glucose, let newGlucoseDate = context.glucoseDate
{
let enoughTimePassed = newGlucoseDate.timeIntervalSince(lastGlucoseDate).minutes >= 30
let enoughTrendDrift = abs(newGlucose.doubleValue(for: minTrendUnit) - lastGlucose.doubleValue(for: minTrendUnit)) >= minTrendDrift
let complicationShouldUpdate: Bool

complicationShouldUpdate = enoughTimePassed || enoughTrendDrift
} else {
complicationShouldUpdate = true
}
if let lastContext = lastComplicationContext,
let lastGlucose = lastContext.glucose, let lastGlucoseDate = lastContext.glucoseDate,
let newGlucose = context.glucose, let newGlucoseDate = context.glucoseDate
{
let enoughTimePassed = newGlucoseDate.timeIntervalSince(lastGlucoseDate) >= session.complicationUserInfoTransferInterval
let enoughTrendDrift = abs(newGlucose.doubleValue(for: minTrendUnit) - lastGlucose.doubleValue(for: minTrendUnit)) >= minTrendDrift

if session.isComplicationEnabled && complicationShouldUpdate {
log.default("transferCurrentComplicationUserInfo")
session.transferCurrentComplicationUserInfo(context.rawValue)
lastComplicationContext = context
} else {
do {
log.default("updateApplicationContext")
try session.updateApplicationContext(context.rawValue)
} catch let error {
log.error(error)
}
complicationShouldUpdate = enoughTimePassed || enoughTrendDrift
} else {
complicationShouldUpdate = true
}

if session.isComplicationEnabled && complicationShouldUpdate {
log.default("transferCurrentComplicationUserInfo")
session.transferCurrentComplicationUserInfo(context.rawValue)
lastComplicationContext = context
} else {
do {
log.default("updateApplicationContext")
try session.updateApplicationContext(context.rawValue)
} catch let error {
log.error(error)
}
}
}
Expand Down Expand Up @@ -266,6 +269,7 @@ extension WatchDataManager: WCSessionDelegate {
log.error(error)
} else {
sendSettingsIfNeeded()
sendWatchContextIfNeeded()
}
case .inactive, .notActivated:
break
Expand All @@ -292,6 +296,7 @@ extension WatchDataManager: WCSessionDelegate {
}

func sessionDidDeactivate(_ session: WCSession) {
lastSentSettings = nil
watchSession = WCSession.default
watchSession?.delegate = self
watchSession?.activate()
Expand All @@ -301,3 +306,57 @@ extension WatchDataManager: WCSessionDelegate {
sendSettingsIfNeeded()
}
}


extension WatchDataManager {
override var debugDescription: String {
var items = [
"## WatchDataManager",
"lastSentSettings: \(String(describing: lastSentSettings))",
"lastComplicationContext: \(String(describing: lastComplicationContext))",
]

if let session = watchSession {
items.append(String(reflecting: session))
} else {
items.append(contentsOf: [
"watchSession: nil"
])
}

return items.joined(separator: "\n")
}
}


extension WCSession {
open override var debugDescription: String {
return [
"\(self)",
"* hasContentPending: \(hasContentPending)",
"* isComplicationEnabled: \(isComplicationEnabled)",
"* isPaired: \(isPaired)",
"* isReachable: \(isReachable)",
"* isWatchAppInstalled: \(isWatchAppInstalled)",
"* outstandingFileTransfers: \(outstandingFileTransfers)",
"* outstandingUserInfoTransfers: \(outstandingUserInfoTransfers)",
"* receivedApplicationContext: \(receivedApplicationContext)",
"* remainingComplicationUserInfoTransfers: \(remainingComplicationUserInfoTransfers)",
"* complicationUserInfoTransferInterval: \(round(complicationUserInfoTransferInterval.minutes)) min",
"* watchDirectoryURL: \(watchDirectoryURL?.absoluteString ?? "nil")",
].joined(separator: "\n")
}

fileprivate var complicationUserInfoTransferInterval: TimeInterval {
Copy link
Contributor

@mpangburn mpangburn Oct 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I've been digging into this over the last week or two, I've found that midnight isn't the right estimate for the budget reset time—@elnjensen and I have been recording 5AM budget resets, but I don't think our two experiences are sufficient evidence to make a general conclusion.
I'm continuing to look into this, but we'd benefit from more logging here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have been recording additional resets at 5am (so a midnight and a 5am reset?) or no midnight reset, and just the 5am one?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's 5:02 AM every day, but not midnight, at least for the two of us who have been tracking it - see #816 . What we've observed is a daily reset at 5 AM, and then an additional reset (which doesn't change the daily schedule) whenever the Watch is fully charged.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. The midnight target isn't an estimate of when the counter will reset, but an estimate of when the user will be asleep. Still open to better algorithms for divvying this resource up, though.

let now = Date()
let timeUntilMidnight: TimeInterval

if let midnight = Calendar.current.nextDate(after: now, matching: DateComponents(hour: 0), matchingPolicy: .nextTime) {
timeUntilMidnight = midnight.timeIntervalSince(now)
} else {
timeUntilMidnight = .hours(24)
}

return timeUntilMidnight / Double(remainingComplicationUserInfoTransfers + 1)
}
}
3 changes: 3 additions & 0 deletions WatchApp Extension/Base.lproj/Localizable.strings
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/* Title of the user activity for adding carbs */
"Add Carb Entry" = "Add Carb Entry";

/* The title of the alert controller displayed after a bolus attempt fails */
"Bolus Failed" = "Bolus Failed";

Expand Down
12 changes: 6 additions & 6 deletions WatchApp Extension/Controllers/ActionHUDController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ final class ActionHUDController: HUDInterfaceController {
override func update() {
super.update()

let schedule = loopManager?.settings.glucoseTargetRangeSchedule
let schedule = loopManager.settings.glucoseTargetRangeSchedule
let activeOverrideContext: GlucoseRangeSchedule.Override.Context?
if let glucoseRangeScheduleOverride = schedule?.override, glucoseRangeScheduleOverride.isActive()
{
Expand Down Expand Up @@ -82,7 +82,7 @@ final class ActionHUDController: HUDInterfaceController {
// MARK: - Menu Items

@IBAction func togglePreMealMode() {
guard var glucoseTargetRangeSchedule = loopManager?.settings.glucoseTargetRangeSchedule else {
guard var glucoseTargetRangeSchedule = loopManager.settings.glucoseTargetRangeSchedule else {
return
}
if preMealButtonGroup.state == .on {
Expand All @@ -97,7 +97,7 @@ final class ActionHUDController: HUDInterfaceController {
}

@IBAction func toggleWorkoutMode() {
guard var glucoseTargetRangeSchedule = loopManager?.settings.glucoseTargetRangeSchedule else {
guard var glucoseTargetRangeSchedule = loopManager.settings.glucoseTargetRangeSchedule else {
return
}
if workoutButtonGroup.state == .on {
Expand Down Expand Up @@ -127,19 +127,19 @@ final class ActionHUDController: HUDInterfaceController {
if let error = error {
if self.pendingMessageResponses == 0 {
ExtensionDelegate.shared().present(error)
self.updateForOverrideContext(self.loopManager?.settings.glucoseTargetRangeSchedule?.override?.context)
self.updateForOverrideContext(self.loopManager.settings.glucoseTargetRangeSchedule?.override?.context)
}
} else {
if self.pendingMessageResponses == 0 {
self.loopManager?.settings.glucoseTargetRangeSchedule = schedule
self.loopManager.settings.glucoseTargetRangeSchedule = schedule
}
}
}
})
} catch {
pendingMessageResponses -= 1
if pendingMessageResponses == 0 {
updateForOverrideContext(self.loopManager?.settings.glucoseTargetRangeSchedule?.override?.context)
updateForOverrideContext(self.loopManager.settings.glucoseTargetRangeSchedule?.override?.context)
}
presentAlert(
withTitle: NSLocalizedString("Send Failed", comment: "The title of the alert controller displayed after a glucose range override send attempt fails"),
Expand Down
23 changes: 4 additions & 19 deletions WatchApp Extension/Controllers/AddCarbsInterfaceController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ final class AddCarbsInterfaceController: WKInterfaceController, IdentifiableClas
replyHandler: { (suggestion) in
DispatchQueue.main.async {
WKInterfaceDevice.current().play(.success)

ExtensionDelegate.shared().loopManager.addConfirmedCarbEntry(entry)

WKExtension.shared().rootInterfaceController?.presentController(withName: BolusInterfaceController.className, context: suggestion)
}
},
Expand Down Expand Up @@ -215,31 +218,13 @@ extension AddCarbsInterfaceController: WKCrownDelegate {
func crownDidRotate(_ crownSequencer: WKCrownSequencer?, rotationalDelta: Double) {
accumulatedRotation += rotationalDelta
let remainder = accumulatedRotation.truncatingRemainder(dividingBy: rotationsPerIncrement)
var delta = Int((accumulatedRotation - remainder) / rotationsPerIncrement)
let delta = Int((accumulatedRotation - remainder) / rotationsPerIncrement)

switch inputMode {
case .value:
let oldValue = carbValue
carbValue += delta

// If we didn't change, adjust the delta to prevent the haptic
if oldValue == carbValue {
delta = 0
}
case .date:
let oldValue = date
date += TimeInterval(minutes: Double(delta))

// If we didn't change, adjust the delta to prevent the haptic
if oldValue == date {
delta = 0
}
}

if delta > 0 {
WKInterfaceDevice.current().play(.click)
} else if delta < 0 {
WKInterfaceDevice.current().play(.click)
}

accumulatedRotation = remainder
Expand Down
24 changes: 10 additions & 14 deletions WatchApp Extension/Controllers/BolusInterfaceController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ final class BolusInterfaceController: WKInterfaceController, IdentifiableClass {
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}

override func didAppear() {
super.didAppear()

crownSequencer.focus()
}
Expand Down Expand Up @@ -141,7 +145,11 @@ final class BolusInterfaceController: WKInterfaceController, IdentifiableClass {
do {
try WCSession.default.sendBolusMessage(bolus) { (error) in
DispatchQueue.main.async {
ExtensionDelegate.shared().present(error)
if let error = error {
ExtensionDelegate.shared().present(error)
} else {
ExtensionDelegate.shared().loopManager.addConfirmedBolus(bolus)
}
}
}
} catch {
Expand Down Expand Up @@ -170,22 +178,10 @@ extension BolusInterfaceController: WKCrownDelegate {
accumulatedRotation += rotationalDelta

let remainder = accumulatedRotation.truncatingRemainder(dividingBy: rotationsPerValue)
var delta = Int((accumulatedRotation - remainder) / rotationsPerValue)
let delta = Int((accumulatedRotation - remainder) / rotationsPerValue)

let oldValue = pickerValue
pickerValue += delta

// If we didn't change, adjust the delta to prevent the haptic
if oldValue == pickerValue {
delta = 0
}

if delta > 0 {
WKInterfaceDevice.current().play(.click)
} else if delta < 0 {
WKInterfaceDevice.current().play(.click)
}

accumulatedRotation = remainder
}
}
Loading