Skip to content

Remote 1: Action Models #1930

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 5 commits into from
Mar 10, 2023

Conversation

gestrich
Copy link
Contributor

@gestrich gestrich commented Feb 19, 2023

This will migrate away from the RemoteCommand type to the RemoteAction type. I'd like to separate actions, things that get enacted in Loop, from the RemoteCommand that delivered the action. This should help with unit testing and Remote Command 2.0 where this distinction is more meaningful. For now, I've remote the RemoteCommand type in favor of RemoteAction, however I plan to reintroduce it when Remote Commands 2.0 will bring the queuing capabilities (and responsibilities for a command)

Note that the related LoopKit PR will need merged with this LoopKit/LoopKit#453

@@ -812,6 +812,18 @@ extension DeviceDataManager {
self.loopManager.updateRemoteRecommendation()
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Async wrapper

@gestrich gestrich force-pushed the feature/2023/02/bg/remote-action-models branch from 99519a7 to ba6c7f3 Compare February 19, 2023 17:03
@@ -1341,8 +1353,15 @@ extension Notification.Name {

// MARK: - Remote Notification Handling
extension DeviceDataManager {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

High-level changes in this class:

  1. Make an async variant of handleRemoteNotification. This will be especially helpful for remote commands 2 later.
  2. Use the new RemoteAction models for enacting a command.
  3. Add methods for handling async background uploads


do {
command = try RemoteCommand.createRemoteCommand(notification: notification, allowedPresets: loopManager.settings.overridePresets, defaultAbsorptionTime: carbStore.defaultAbsorptionTimes.medium).get()
action = try RemoteAction.createRemoteAction(notification: notification).get()
} catch {
log.error("Remote Notification: Parse Error: %{public}@", String(describing: error))
return
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There were all sorts of validation logic for specific command types in this method previously. The new paradigm here is to move the validation logic into the RemoteAction. That helps hide this complexity and makes it more easy to unit test.

@@ -1372,156 +1388,144 @@ extension DeviceDataManager {
}
}

let command: RemoteCommand
let action: RemoteAction

Copy link
Contributor Author

Choose a reason for hiding this comment

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

RemoteCommand was renamed to RemoteAction. Later on I plan to reintroduce a RemoteCommand, but it will instead have a RemoteAction. The distinction is a command will include the action and all the ancillary things related to commands (security validation, status reporting, etc).

Separating these concepts should make more sense with Remote 2.0.

try await handleRemoteCarbAction(carbAction)
} catch {
await NotificationManager.sendRemoteCarbEntryFailureNotification(for: error, amountInGrams: carbAction.amountInGrams)
log.error("Remote Notification: Carb Action Error: %{public}@", String(describing: notification))
}
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These "handle" methods below all take an action and attempt to enact it.

}

//Remote Overrides

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The paradigm here is each RemoteAction is asked for its data required to enact it (i.e. TemporaryOverride, CarbEntry, etc).

That call allows the action to validate that its data is normalized wrt to Loop's expectations (ex: carbs not exceeding reasonable max)

@@ -209,6 +209,14 @@ final class RemoteDataServicesManager {
completion()
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Async wrapper

@@ -8,78 +8,34 @@

import Foundation
import LoopKit
import HealthKit

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not needed as each remote action type will own its own errors.

case bolusEntry(Double)
case carbsEntry(NewCarbEntry)
}


// Push Notifications
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Generally like before, this method parses a notification and extracts a remote action (previously command).

The difference is Loop valid action checks are not done here (ex: Carb amount within reasonable limits). That will instead be managed by the client.

Separating notification payload parsing and validation seems like a good idea here. This will go away when I use Codable to do all this parsing in Remote 2.0.


var absorptionTime = defaultAbsorptionTime
var absorptionTime: TimeInterval? = nil
if let absorptionOverrideInHours = notification["absorption-time"] as? Double {
absorptionTime = TimeInterval(hours: absorptionOverrideInHours)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Carb food type was missing from the APIs. This will require a change to the Nightscout codebase in order to support this but should be ok when empty too.

@@ -0,0 +1,108 @@
//
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I needed to update the RemoteCommandTests with these and change a few that are unnecessary.

I added more tests to LoopKit, which test the individual remote action types themselves which should cover all the cases I know of.

@gestrich gestrich marked this pull request as ready for review February 19, 2023 17:55
@gestrich
Copy link
Contributor Author

Updates done - I think I responded to all the feedback but let me know if other questions.

@gestrich gestrich changed the title Remote Action Models Remote 1: Action Models Feb 25, 2023
Copy link
Collaborator

@ps2 ps2 left a comment

Choose a reason for hiding this comment

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

LGTM! Thanks! I think at some point it'd be good to move more of the remote command processing out of DeviceDataManager. Maybe we have a RemoteCommandManager, or some other high level Loop manager responsible for processing remote commands.

@ps2 ps2 merged commit 4909f52 into LoopKit:dev Mar 10, 2023
@gestrich
Copy link
Contributor Author

gestrich commented Mar 11, 2023

@ps2 The last commit in this PR didn't seem to make it in the merge from what I can tell db2ef8d. Just some info on what that commit does...

Since the changes earlier in the PR to using Apple async apis, I found I needed to request background processing time before starting an async task. Otherwise, Loop would hang in the background before it makes it into the async block (doing in the block is too late). My testing showed that change seemed to work as well as Loop 3.0.

@ps2 Disregard - I had my repo in a weird state and see it did make it to dev just fine.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants