Skip to content

Remote 1: Action Models#453

Merged
ps2 merged 4 commits into
LoopKit:devfrom
gestrich:feature/2023/02/bg/remote-action-models
Mar 4, 2023
Merged

Remote 1: Action Models#453
ps2 merged 4 commits into
LoopKit:devfrom
gestrich:feature/2023/02/bg/remote-action-models

Conversation

@gestrich

@gestrich gestrich commented Feb 19, 2023

Copy link
Copy Markdown
Contributor

This will migrate away from the RemoteCommand type to the RemoteAction type.

See Loop PR for more details LoopKit/Loop#1930

Unit Tests

I added new unit tests to check that action validation is enforced.

// Created by Bill Gestrich on 12/25/22.
// Copyright © 2022 LoopKit Authors. All rights reserved.
//

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Similar to RemoteCommand. But the associated types are specific to each command. This is in LoopKit as these models will be shared by Caregiver.

public var description: String {
return "\(actionName) \(actionDetails)"
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

These are not used yet but helpful for Remote 2.0

}
}

extension RemoteBolusAction {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Putting the validation logic needed to enact an action, in the action itself. I wrestled with keeping this logic in Loop instead, but I kind of like keeping the validation logic with the model.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why are these in LoopKit at all? If it's for sharing types with plugins, maybe that would be necessary. If it's for sharing types between Loop and LCG, maybe pause on that for now, and keep them separate. Human input validation (LCG-only concern), network decoding and validation, and Loop's other validation concerns (like max bolus, max temp), are all separate things. Guessing there might be other differences in concerns between them that might make the types different too. Maybe Loop wants to have a received time?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If these types are strictly for representing a Swift version of the network encoding of these actions, then I guess that could be a shared concern.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think I'm following the suggested separation here. These models are to include all the information needed to enact an action in Loop. For remote 2.0, they will be used for the network encoding/decoding both from the plugins and Caregiver.

I can move the validation pieces out of here and put into the Loop target. That does leave the question of where to put these things to keep them unit testable as they are. Making an extension, like here, keeps it testable without involving the DeviceDataManager test setup or dedicated validation structures for each action (i.e. RemoteBolusValidator..). My inclination is to move the extensions to the actions to the Loop target to provide some separation. But I can consider a validator or wrapping these actions into Loop domain actions if something more dedicated is desired. Note that in remote 2.0, the plan is for a RemoteCommand to hold onto a RemoteAction and other things like the RemoteCommandState.

@gestrich gestrich Feb 20, 2023

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

For context, here is where I was planning to take things in remote 2.0 wrt actions. The RemoteCommand holds onto the action. Everything else in the RemoteCommand is specific to command management and lifecycle.

Rather than juggling a separate hook to the service that generated the command, the RemoteCommand itself has methods to interact with its service internally.. ex: RemoteCommand.markSuccess() rather than service.markSuccess(command)


public protocol RemoteCommand {
    
    var id: String {get}
    var action: RemoteAction {get}
    var status: RemoteCommandStatus {get}
    
    /*
     TODO: Should we support "future" commands?
     TODO: Should the expiration date be in here?
     TODO: Should creationDate be in here?
     */
    
    func checkValidity() throws
    
    func markInProgress() async throws
    func markError(_ error: Error) async throws
    func markSuccess() async throws
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think moving them to Loop sounds good. Yes, you will have a similar type in LCG, with similar fields, but I think this is one case where it's ok to avoid DRY. The responsibilities and things you will do with those types are going to be different.

Thanks for the context on eventual plans of wrapping RemoteAction with RemoteCommand. What do you think about RemoteAction -> Action? If a RemoteCommand has an action, the "remote" part can be implicit (if "actions" are things that could theoretically be triggered locally, by, say, a siri command), or could be namespaced by RemoteCommand (if they are not things that could be reused locally).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think making the Actions decoupled from the remote aspect makes sense. I've dropped the Remote verbiage from actions for now.

@gestrich gestrich force-pushed the feature/2023/02/bg/remote-action-models branch from 9b6d5a1 to 335082e Compare February 19, 2023 17:54
@gestrich gestrich marked this pull request as ready for review February 19, 2023 17:55
var actionName: String {
switch self {
case .carbsEntry:
return "Carb Entry"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Are these user displayed names? Localization?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added - thanks

}
}

extension RemoteBolusAction {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why are these in LoopKit at all? If it's for sharing types with plugins, maybe that would be necessary. If it's for sharing types between Loop and LCG, maybe pause on that for now, and keep them separate. Human input validation (LCG-only concern), network decoding and validation, and Loop's other validation concerns (like max bolus, max temp), are all separate things. Guessing there might be other differences in concerns between them that might make the types different too. Maybe Loop wants to have a received time?

}
}

extension RemoteBolusAction {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If these types are strictly for representing a Swift version of the network encoding of these actions, then I guess that could be a shared concern.

switch self {
case .carbsEntry:
return "Carb Entry"
return NSLocalizedString("Carb Entry", comment: "Carb entry action name")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

So this will lookup the string in the app bundle, not the LoopKit bundle. LocalizedString exists to let you look up strings in LoopKit. Not an issue if this moves into Loop. Also, I think I saw that this string is being combined with other (non-localized) strings?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I ended up dropping these strings for now in these pull requests but will keep these comments in mind in the future PRs where I may add back.

@gestrich

Copy link
Copy Markdown
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
dybs pushed a commit to dybs/LoopKit that referenced this pull request Feb 28, 2023
…t#453)

* extended bolus automatic to DoseActivationSource

* naming update

* refactor
@ps2

ps2 commented Mar 1, 2023

Copy link
Copy Markdown
Collaborator

I still feel like sharing a model here is not great. There are going to be (at least) the following representations of these models:

  1. Loop's internal representation (Swift dictionaries/types) after decoding from the notification.
  2. The JSON format that Nightscout (or another service) produces when sending a remote notification, and that the Loop app accepts.
  3. The JSON format that Nightscout accepts from the caregiver app.
  4. LCG's internal representation before encoding to send to NS>

Decisions like whether to use var amountInGrams: Double or, say,var amount: HKQuantity as the internal representation is the responsibility/choice of the app itself. And evolution of the internal representations might change not quite in lockstep with the encoded JSON.

I know if probably feels like a violation of DRY to be repeating these models in two places, but it does seem to me like they are separate domains, that if not already, will sometime have different concerns, or want some flexibility.

This is just my opinion, and I needed to share it, I don't think this change will hurt Loop in a big way, and will not block on this if you really think it's the right way, or you really want to go this route for some reason.

@gestrich

gestrich commented Mar 1, 2023

Copy link
Copy Markdown
Contributor Author

@ps2 I can understand the concerns about reusing these models in Remote 2.0 for decoding the payload. I'll keep that in mind for the upcoming Remote 2.0 PRs that introduce decoding.

Let me add a 5th item to the list that seems important to put in context with all of this:

  1. Shared representation between Loop and the the remote plugins so that Loop can request a plugins actions. This needs to live in LoopKit to be seen by both.

While I added this as a 5th item, I feel this actually should be a further description on number 1 and should not be replicated, as this feels like the same domain (unlike decoding and such). I think my RemoteActions in this PR serve that role.

I think the upcoming PRs will add a lot of clarity when we get to domains 2-4.

@ps2

ps2 commented Mar 4, 2023

Copy link
Copy Markdown
Collaborator

Yes, the Loop <-> Plugin interface is a shared domain, for sure and LoopKit is currently that kind of thing lives, so I'll merge this. Thanks!

@ps2 ps2 merged commit acd7817 into LoopKit:dev Mar 4, 2023
@gestrich

gestrich commented Mar 5, 2023

Copy link
Copy Markdown
Contributor Author

@ps2 Thanks - The next one I suggest reviewing/merging is this one here for the Loop repo LoopKit/Loop#1930

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants