Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Question: How should a Consumer specify which action to query? #43

Open
benfrancis opened this issue Feb 12, 2025 · 6 comments
Open

Question: How should a Consumer specify which action to query? #43

benfrancis opened this issue Feb 12, 2025 · 6 comments
Labels

Comments

@benfrancis
Copy link
Member

In a queryaction operation, how should the Consumer specify which action to query?

  • It could set correlationID to the correlation ID of the invokeaction request, but then how should the response to the queryaction request be correlated with the queryaction response if there are multiple queryaction requests?
  • Is it OK for the response to a queryallactions request to contain multiple correlation IDs to correspond to each action whose status is being reported?
  • Is there maybe a need for a separate actionRequestID member which corresponds to the messageID or correlationID of the invokeaction request, so that a queryaction operation can have a separate correlationID for each queryaction request, and a response to a queryallactions operation only needs to contain one correlationID?
@hspaay
Copy link
Collaborator

hspaay commented Mar 13, 2025

Good question. This answer is based on proposal #42 and my experience with hiveot. This currently works in hiveot.

This also uses an ActionStatus type that http basic and the strawman proposal use (minor differences, same idea).

  1. Invokeaction always responds with a ResponseMessage containing an ActionStatus object as the output. This follows the same approach as http-basic and the strawman proposal.

If the action request is not accepted then the ResponseMessage contains an error and no ActionStatus is created.
If the action request is accepted, the output is always a single ActionStatus object.

ActionStatus:

   "id": "{ID of the action request; correlationID if the action was requested with the websocket protocol}",
   "thingID": "{thingID to which the action affordance belongs}",
   "name": "{name of the action affordance}",       
   "status": "{status of the action}",
   "requested": "{timestamp of the request}",
   "updated": "{timestamp the action status was last updated}",
   "output": "{output when status is completed}",
   "error": "{errer when status if failed}",

This uses 'id', not 'correlationID' to uniquely identify the action. It can be the correlationID but the device can generate its own UUID, href, or whatever. It is just a string.

  1. queryaction without input:

When sending a RequestMessage with operation 'queryaction', without an input this queries all running instances of the action with the name specified in RequestMessage.

The ResponseMessage contains a list of ActionStatus objects of that action.

If no actions are running this list contains a single ActionStatus instance of the last handled action.

  1. queryaction with the action 'id' as the input value:

If the RequestMessage input for the queryaction operation is provided it identifies the action to query. This must correspond to the 'id' field in the ActionStatus.

  1. queryallactions is similar to queryaction without input but applies to all actions.
    The 'name' property in the RequestMessage is not used.

(ps: hiveot used its own RequestMessage/ResponseMessage but this is currently being reworked to proposal #42 including NotificationMessages. It already uses the above approach for actions.

@hspaay
Copy link
Collaborator

hspaay commented Mar 13, 2025

The result looks like this:

queryaction without input

RequestMessage

{
  "correlationID": "5afb752f-8be0-4a3c-8108-1327a6009cbd"
  "messageID": "c370da58-69ae-4e83-bb5a-ac6cfb2fed54",
  "messageType": "request",
  "thingID": "https://mythingserver.com/things/mylamp1",
  "operation": "queryaction",
  "name": "fade",
}

ResponseMessage

{
  "correlationID": "5afb752f-8be0-4a3c-8108-1327a6009cbd"
  "messageID": "c370da58-69ae-4e83-bb5a-12345",
  "messageType": "response",
  "operation": "queryaction",
  "name": "fade",
  "thingID": "https://mythingserver.com/things/mylamp1",
  "output":  [
     { 
        "id": "action-id-12344",
        "thingID": "https://mythingserver.com/things/mylamp1",
        "name": "fade",  
        "status": "running",
        "timeRequested": "2024-11-11T11:43:10.100Z",
        "timeUpdated": "2024-11-11T11:43:10.101Z",
     },
     { 
        "id": "action-id-12345",
        "thingID": "https://mythingserver.com/things/mylamp1",
        "name": "fade",  
        "status": "pending",
        "timeRequested": "2024-11-11T11:43:20.135Z",
        "timeUpdated": "2024-11-11T11:43:20.136Z",
     },
     ...
  ],
}

@hspaay
Copy link
Collaborator

hspaay commented Mar 13, 2025

queryaction for a specific instance

RequestMessage

{
  "correlationID": "5afb752f-8be0-4a3c-8108-1327a6009cbd"
  "messageID": "c370da58-69ae-4e83-bb5a-ac6cfb2fed54",
  "messageType": "request",
  "thingID": "https://mythingserver.com/things/mylamp1",
  "operation": "queryaction",
  "name": "fade",
  "input": "action-id-12345"
}

ResponseMessage

{
  "correlationID": "5afb752f-8be0-4a3c-8108-1327a6009cbd"
  "messageID": "c370da58-69ae-4e83-bb5a-12345",
  "messageType": "response",
  "operation": "queryaction",
  "name": "fade",
  "thingID": "https://mythingserver.com/things/mylamp1",
  "output":  [
     { 
        "id": "action-id-12345",
        "thingID": "https://mythingserver.com/things/mylamp1",
        "name": "fade",  
        "status": "pending",
        "timeRequested": "2024-11-11T11:43:20.135Z",
        "timeUpdated": "2024-11-11T11:43:20.136Z",
     },
     ...
  ],
}

This always returns a list in the output for consistency. queryaction and queryallactions always return a list of requested action(s).

@benfrancis
Copy link
Member Author

@hspaay Thank you for the feedback. The fact that it's backed by implementation experience makes it particularly valuable.

If the action request is accepted, the output is always a single ActionStatus object.

If I've understood correctly this would mean having an output member nested inside an output member, which I think has the potential to cause confusion.

I would suggest only using the term output to refer to the action output data, which conforms to the output schema from the Thing Description.

When sending a RequestMessage with operation 'queryaction', without an input this queries all running instances of the action with the name specified in RequestMessage.

FYI this is not really the current meaning of the queryaction operation¹.

This uses 'id', not 'correlationID' to uniquely identify the action.

I think this is a good idea since it's clearer if there's only a single correlationID member per message. A couple of thoughts:

  1. Since there are so many IDs it might be clearer if it had a more specific name, like actionRequestID
  2. I wonder whether the value of this member could be set to the messageID of the original invoke action request rather than the correlationID. This would mean that a correlationID only ever spans messages relating to a single operation and this separate member is used to connect multiple operations relating to a single ongoing action request.

Combining these suggestions with #42 we'd get something like the following examples...

invokeaction

Request

{
  "thingID": "https://mythingserver.com/things/mylamp1",
  "messageID": "c370da58-69ae-4e83-bb5a-ac6cfb2fed54",
  "messageType": "request",
  "operation": "invokeaction",
  "name": "fade",
  "input": {
    "level": 100,
    "duration": 5
  }
  "correlationID": "5afb752f-8be0-4a3c-8108-1327a6009cbd"
}

Notification

{
  "thingID": "https://mythingserver.com/things/mylamp1",
  "messageID": "e5558fe8-0153-4358-8c50-1c983fbb13ba",
  "messageType": "notification",
  "operation": "invokeaction",
  "name": "fade",
  "status": "running",
  "correlationID": "5afb752f-8be0-4a3c-8108-1327a6009cbd"
}

Response

{
  "thingID": "https://mythingserver.com/things/mylamp1",
  "messageID": "844c2ad3-1789-48cb-a4b4-2e9db098283f",
  "messageType": "response",
  "operation": "invokeaction",
  "name": "fade",
  "output": true,
  "correlationID": "5afb752f-8be0-4a3c-8108-1327a6009cbd"
}

queryaction

Request

{
  "thingID": "https://mythingserver.com/things/mylamp1",
  "messageID": "6b46798d-394f-4ec9-b5ee-03b52a9501bf",
  "messageType": "request",
  "operation": "queryaction",
  "name": "fade",
  "actionRequestID": "c370da58-69ae-4e83-bb5a-ac6cfb2fed54",
  "correlationID": "e99c78ce-d8b9-4fe8-aabc-5c74829abfe1"
}

Response

{
  "thingID": "https://mythingserver.com/things/mylamp1",
  "messageID": "eecaba4e-3cf7-42be-9f58-808c164e127c",
  "messageType": "response",
  "operation": "queryaction",
  "name": "fade",
  "status": "completed",
  "output": true,
  "actionRequestID": "c370da58-69ae-4e83-bb5a-ac6cfb2fed54",
  "correlationID": "e99c78ce-d8b9-4fe8-aabc-5c74829abfe1"
}

queryallactions

Request

{
  "thingID": "https://mythingserver.com/things/mylamp1",
  "messageID": "fdf752e4-2ea3-4663-a806-368a1a262fad",
  "messageType": "request",
  "operation": "queryallactions",
  "correlationID": "e4acf901-21e6-4b0b-863c-1544fafb6812"
}

Response

{
  "thingID": "https://mythingserver.com/things/mylamp1",
  "messageID": "77716af4-9a0d-4bea-9a43-e62aceed6d9a",
  "messageType": "response",
  "operation": "queryallactions",
  "statuses": {
    "fade": {
      [
         {
          "status": "pending",
          "actionRequestID": "c370da58-69ae-4e83-bb5a-ac6cfb2fed54",
          "timeRequested": "2024-11-11T11:43:20.135Z"
        },
        {
          "status": "completed",
          "actionRequestID": "0cb12479-0a5a-47b7-9fcd-d90e7b51383b",
          "timeRequested": "2024-11-10T11:43:20.135Z",
          "timeEnded": "2024-11-10T11:43:25.135Z",
          "output": true
        }
    ]
  },
  "correlationID": "e4acf901-21e6-4b0b-863c-1544fafb6812"
}

This is still not quite as neat as I would like and might need further refinement.


  1. I mentioned elsewhere that I originally wanted separate operations for querying a single action request, querying all action requests for a given action and querying all action requests for all actions - but what we ended up with was just queryaction (meaning to query the status of a single action request) and queryallactions (meaning to query all action requests for all actions). Unless an additional operation is added to the Thing Description specification I think we need to stick to those meanings. (I would ideally like the Web Thing Protocol to be compatible with Thing Description 1.1 and not wait for Thing Description 2.0).

@hspaay
Copy link
Collaborator

hspaay commented Mar 20, 2025

1. Wrt invokeaction response

Invokeaction always responds with a ResponseMessage containing an ActionStatus object as the output. ...

If I've understood correctly this would mean having an output member nested inside an output member, which I think has the potential to cause confusion.

Hmm I might have misunderstood, earlier. Are you suggesting to wait with the response until the action is complete and send notifications to indicate progress? That makes a lot of sense. In that case I can also understand why the output is the actual action output and not an ActionStatus. If that is the intent then lets roll with that.

I do still believe it is more consistent that the invokeaction notification contains the ActionStatus object as it reports on, well ... action status. That would also omit the need for a status field in the notification message. (are there any other uses for it?)

2. queryaction input

When sending a RequestMessage with operation 'queryaction', without an input this queries all running instances of the action with the name specified in RequestMessage.
If the RequestMessage input for the queryaction operation is provided it identifies the action to query. This must correspond to the 'id' field in the ActionStatus.

FYI this is not really the current meaning of the queryaction operation¹.

Yeah I can see how this got watered down. Isn't this up to the protocol binding though? TD-1.1 says "Identifies the querying operation on Action Affordances to get the status of the corresponding action." This leaves it quite open.

Maybe best then to leave it as a future oppertunity to be more specific in querying actions by providing the actionID (actionRequestID) as the input parameter. It would be cleaner this way than adding another operation IMHO.

3. queryaction/queryallactions output format

Your example shows a queryaction responsemessage with status and actionRequestID members.

The consistent use of ActionStatus in the response instead of blending this with the response itself is less confusing. Blending is in my opinion more confusing as the response is both a response to the queryaction request as well as the response of the queried action.

A second point is that querying a single action can have multiple action results as actions can be queued or run in parallel. Assuming that this is still being considered. TD-1.1 is quite vague on this. You do point out that this isn't in the definition of queryaction but at the same time I don't see anything saying that you can't do this. Maybe best not to close this door?

Third, it is more in line with 'queryallactions' which does return ActionStatus objects. Even if the decision is to only return a single action status it is still more consistent to use the ActionStatus instance.

Fourth: It is the purpose of the ActionStatus instance to describe .. well the action status. The purpose of queryaction is to obtain the action status. It seems logical to use the ActionStatus instance in the result. Rather than needing a reason to use it there should be a reason for not using it when querying action status in all its variations.

Fifth: This is more in line with http-basic which does return an ActionStatus object in async results. Not that I think http-basic is just a wonderful example, but in this case it works out well ;)

This is why I propose to always use ActionStatus objects in response to queryaction/queryallactions and for action progress notifications.

I do strongly suggest not to blend the action status and the response message. I have done this in an earlier iteration and it became more difficult to debug and follow what is going on, so I changed hiveot to separate action status into its own object in the output of response/notification messages. The separation of concerns was a bit of a relieve in testing and debugging as it made it clearer to know what you're looking at. This confirmed to me it was the right decision.

If you prefer only a single ActionStatus in the output of queryaction then that works for me, so not a big deal. (it is what is currently happening in hiveot)

4. timeUpdated vs timeCompleted

I wonder if it is better to use 'timeUpdated' instead of 'timeCompleted' in ActionStatus. The action might be running and provide intermediate progress updates. timeUpdated would indicate the time of the confirmed update, which for long running actions can indicate things are progressing. After completion this is the completion time. After cancellation this is the cancellation time. It also avoids the need to have all these different timestamps separately.

5. Using 'actionRequestID' (proposing 'actionID')

Looks like we are on the same page that the action identification is best decoupled from the correlationID or messageID. An action be invoked through another protocol binding which uses another ID format. Its up to the action handler to choose the format.

As far as naming is concerned, can I propose 'actionID' as a middle ground? using 'actionRequestID' suggests it is related to the request, which we just agreed it isn't :)

6. Proposed format for queryaction

If I merge our combined feedback and just use a single response in queryaction, it looks like this:

RequestMessage

{
  "correlationID": "5afb752f-8be0-4a3c-8108-1327a6009cbd"
  "messageID": "c370da58-69ae-4e83-bb5a-ac6cfb2fed54",
  "messageType": "request",
  "thingID": "https://mythingserver.com/things/mylamp1",
  "operation": "queryaction",
  "name": "fade",
}

ResponseMessage

{
  "correlationID": "5afb752f-8be0-4a3c-8108-1327a6009cbd"
  "messageID": "c370da58-69ae-4e83-bb5a-12345",
  "messageType": "response",
  "operation": "queryaction",
  "name": "fade",
  "thingID": "https://mythingserver.com/things/mylamp1",
  "output":     { 
        "actionID": "action-id-12344",
        "thingID": "https://mythingserver.com/things/mylamp1",
        "name": "fade",  
        "status": "running",
        "timeRequested": "2024-11-11T11:43:10.100Z",
        "timeUpdated": "2024-11-11T11:43:10.101Z",
     }
}

thingID and name in the response are optional. This goes for all responses as correlationID identifies the corresponding request. Are we on the same page with this?

@benfrancis
Copy link
Member Author

benfrancis commented Mar 21, 2025

Hmm I might have misunderstood, earlier. Are you suggesting to wait with the response until the action is complete and send notifications to indicate progress?

Yes.

I do still believe it is more consistent that the invokeaction notification contains the ActionStatus object as it reports on, well ... action status.

I agree it would be more consistent with the queryallactions response, and this is one of the things I think needs further refinement. What I was trying to avoid was that value (properties), input (actions) and data (events) members all exist at the top level of messages (which I like), but output is nested inside another member - which would also be inconsistent.

That would also omit the need for a status field in the notification message. (are there any other uses for it?)

No, which is intentional since it's only relevant to queryable actions.

Isn't this up to the protocol binding though? TD-1.1 says "Identifies the querying operation on Action Affordances to get the status of the corresponding action." This leaves it quite open.

Yeah I guess it does if you squint, but there's still only one operation type. Currently WoT Profiles interprets this as querying an individual instance of an action rather than all instances of an action.

The consistent use of ActionStatus in the response instead of blending this with the response itself is less confusing.

Maybe. What I was trying to avoid was a status member inside a status member. It's mostly just about naming.

A second point is that querying a single action can have multiple action results as actions can be queued or run in parallel. Assuming that this is still being considered. TD-1.1 is quite vague on this. You do point out that this isn't in the definition of queryaction but at the same time I don't see anything saying that you can't do this. Maybe best not to close this door?

I would like the interpretation of the queryaction operation to be consistent with Profiles, so if we reinterpreted the meaning of this operation we would need to modify the profile specifications (and their implementations) as well. I'm not saying we shouldn't do that, I'm just not sure if it's worth it since you can also query multiple instances of an action using queryallactions.

I wonder if it is better to use 'timeUpdated' instead of 'timeCompleted' in ActionStatus. The action might be running and provide intermediate progress updates. timeUpdated would indicate the time of the confirmed update, which for long running actions can indicate things are progressing. After completion this is the completion time. After cancellation this is the cancellation time.

The terms timeRequested and timeEnded (not timeCompleted because the action may have failed, see #101 and #127) come from the WoT Profiles specification - so if we're going to change that here I think we should probably change it there as well.

However, I'm increasingly concerned that what we are defining here is a quasi-observeaction operation, which doesn't yet exist in the Thing Description specification (but may in future versions). There's an argument that we shouldn't have the notification messages at all inside the invokeaction operation and we should instead wait for an observeaction operation to be defined in the future which works more like subscribeevent and observeproperty.

It also avoids the need to have all these different timestamps separately.

I think you'll still want to know when an action was requested and when it was completed or failed.

As far as naming is concerned, can I propose 'actionID' as a middle ground? using 'actionRequestID' suggests it is related to the request, which we just agreed it isn't :)

Now we're just bikeshedding over names, which is a good sign ;)

Actually I think it is related to the request, since what's important is what invokeaction operation it corresponds to.

I think it's important to distinguish between an "action" (which is identified by a name like "fade") and an individual instance of an action (which has a unique ID like "239ae560-4c66-4e34-8aca-4d4241e70bad"). In the assertions of the WoT Profiles specification we call this an "action request". If that's confusing an alternative term might be "action instance", so actionInstanceID?

But if the term is set to the message ID of the invokeaction request then I would suggest that actionRequestID (or even actionInvocationRequestID or actionInvocationRequestMessageID if we wanted to get super specific!) is an appropriate term.

If I merge our combined feedback and just use a single response in queryaction, it looks like this:

I feel strongly that the output member should only contain data which matches the output data schema in the Thing Description. Same as with input and data.

What's missing from your example is the actual output of an action which would presumably mean having an output member inside an output member?

Overall I think I agree with adding an ActionStatus object to queryaction response (and notification?) messages, we just need another name for the top level ActionStatus member that isn't output or status, e.g.

{
  "thingID": "https://mythingserver.com/things/mylamp1",
  "messageID": "eecaba4e-3cf7-42be-9f58-808c164e127c",
  "messageType": "response",
  "operation": "queryaction",
  "name": "fade",
  "actionStatus": {
    "status": "completed",
    "output": true,
    "actionRequestID": "c370da58-69ae-4e83-bb5a-ac6cfb2fed54",
    "timeRequested": "2024-11-10T11:43:19.135Z",
    "timeEnded": "2024-11-10T11:43:20.513Z"
  }
  "correlationID": "e99c78ce-d8b9-4fe8-aabc-5c74829abfe1"
}

(or we use "status" and rename the other "status" member to something else, in Profiles as well).


BTW the reason I'm so concerned about the Web Thing Protocol being consistent with WoT Profiles is that I hope there will be a WebSocket Profile at some point in the future which mandates use of the Web Thing Protocol WebSocket sub-protocol, and we aim for consistency between profiles. However, although WoT Profiles has existing implementations which makes changes more painful it's not a Recommendation yet, so it is still possible to make changes if we really need to. Certainly for WoT Profiles 2.0.

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

No branches or pull requests

2 participants