Skip to content
Open
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
21 changes: 17 additions & 4 deletions samples/ServiceClients/CommandsSandbox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ Data Plane
targeting the IoT Thing set on the application startup
* `open-client-stream <payload-format>` - subscribe to a stream of AWS IoT command executions with a specified payload format
targeting the MQTT client ID set on the application startup
* `update-command-execution <execution-id> \<status> \[\<reason-code>] \[\<reason-description>]` - update status for specified
execution ID;
* `update-command-execution <execution-id> <status> [reason-code=<value>] [reason-description=<value>] [result=<key>:<value>;<key>:<value>]` -
update status for specified execution ID;
* status can be one of the following: IN_PROGRESS, SUCCEEDED, REJECTED, FAILED, TIMED_OUT
* reason-code and reason-description may be optionally provided for the REJECTED, FAILED, or TIMED_OUT statuses
* reason-code and reason-description may be optionally provided for any status
* result is a semicolon-separated list of key:value pairs; if a value is true/false it is treated as boolean, otherwise as string

Miscellaneous
* `list-streams` - list all open streaming operations
Expand Down Expand Up @@ -353,6 +354,15 @@ Take an AWS IoT command execution ID your sample received at the end of the prev
update-command-execution 33333333-3333-3333-3333-333333333333 IN_PROGRESS
```

You can also provide execution results when updating the status. Results are specified as semicolon-separated
key:value pairs. Values of `true` or `false` are treated as booleans, everything else is treated as a string:
```
update-command-execution 33333333-3333-3333-3333-333333333333 IN_PROGRESS result=batteryStatus:"unknown status";alive:true
```

> [!NOTE]
> You can also pass binary data in the result field using the CommandExecutionResult::bin member, which is not supported in this sample.

Then checking once again for the AWS IoT command execution status with
```
get-command-execution 33333333-3333-3333-3333-333333333333
Expand All @@ -362,6 +372,9 @@ should return

```
Status of Command execution '33333333-3333-3333-3333-333333333333' is IN_PROGRESS
Result:
alive: true (boolean)
batteryStatus: unknown (string)
```

`IN_PROGRESS` is an intermediary execution status, i.e. it's possible to change this status.
Expand All @@ -378,7 +391,7 @@ update-command-execution 33333333-3333-3333-3333-333333333333 SUCCEEDED
```
or
```
update-command-execution 33333333-3333-3333-3333-333333333333 FAILED SHORT_FAILURE_CODE A longer description
update-command-execution 33333333-3333-3333-3333-333333333333 FAILED reason-code=SHORT_FAILURE_CODE reason-description="A longer description"
```

If you try to update the status of the same AWS IoT command execution to something else, it'll fail:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,12 @@ private static void printCommandHelp() {
System.out.println(" application/json - subscribe to commands with JSON payload");
System.out.println(" application/cbor - subscribe to commands with CBOR payload");
System.out.println(" for any other value, subscribe to a generic topic");
System.out.println(" update-command-execution <executionId> <status> [<reason-code>] [<reason-description>]");
System.out.println(" update-command-execution <executionId> <status> [reason-code=<value>] [reason-description=<value>] [result=<key>:<value>;<key>:<value>]");
System.out.println(" updates a command execution with a new status");
System.out.println(" <status> can be one of the following:");
System.out.println(" IN_PROGRESS, SUCCEEDED, REJECTED, FAILED, TIMED_OUT");
System.out.println(" <reason-code> and <reason-description> may be optionally provided for");
System.out.println(" the REJECTED, FAILED, or TIMED_OUT statuses\n");
System.out.println(" reason-code and reason-description may be optionally provided for any status");
System.out.println(" result is a semicolon-separated list of key:value pairs; if a value is true/false it is treated as boolean, otherwise as string\n");
System.out.println(" Miscellaneous commands:");
System.out.println(" list-streams list all open streaming operations");
System.out.println(" close-stream <streamID>");
Expand Down Expand Up @@ -426,39 +426,86 @@ private static void handleGetCommandExecution(ApplicationContext context, String
GetCommandExecutionRequest getCommandExecutionRequest = GetCommandExecutionRequest.builder()
.executionId(commandExecutionId)
.targetArn(commandExecutionContext.deviceArn)
.includeResult(true)
.build();
GetCommandExecutionResponse getCommandExecutionResponse = context.controlPlaneClient.getCommandExecution(getCommandExecutionRequest);
System.out.printf("Status of command execution '%s' is %s\n", commandExecutionId, getCommandExecutionResponse.status());
if (getCommandExecutionResponse.statusReason() != null) {
System.out.printf(" Reason code: %s\n", getCommandExecutionResponse.statusReason().reasonCode());
System.out.printf(" Reason description: %s\n", getCommandExecutionResponse.statusReason().reasonDescription());
}
if (getCommandExecutionResponse.hasResult()) {
System.out.println(" Result:");
getCommandExecutionResponse.result().forEach((key, value) -> {
if (value.b() != null) {
System.out.printf(" %s: %s (boolean)\n", key, value.b());
} else if (value.s() != null) {
System.out.printf(" %s: %s (string)\n", key, value.s());
}
});
}
} catch (Exception ex) {
handleOperationException("get-command-execution", ex, context);
}
}

private static Map<String, String> parseKeyValueArgs(String input) {
Pattern pattern = Pattern.compile("(\\S+?)=([^\\s\"]*(?:\"[^\"]*\"[^\\s\"]*)*)");
Matcher matcher = pattern.matcher(input);

Map<String, String> result = new HashMap<>();

while (matcher.find()) {
String key = matcher.group(1);
String value = matcher.group(2);
result.put(key, value);
}

return result;
}

private static HashMap<String, software.amazon.awssdk.iot.iotcommands.model.CommandExecutionResult> parseResult(String resultStr) {
HashMap<String, software.amazon.awssdk.iot.iotcommands.model.CommandExecutionResult> result = new HashMap<>();

Pattern pattern = Pattern.compile("([^;:]*):([^;\"]*(?:\"[^\"]*\"[^;\"]*)*)");
Matcher matcher = pattern.matcher(resultStr);

while (matcher.find()) {
String key = matcher.group(1);
String value = matcher.group(2).replaceAll("^\"|\"$", "");

software.amazon.awssdk.iot.iotcommands.model.CommandExecutionResult entry =
new software.amazon.awssdk.iot.iotcommands.model.CommandExecutionResult();

/* NOTE: CommandExecutionResult also supports binary data via the `bin` member, which is not demonstrated in this
* sample. */
if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
entry.b = Boolean.parseBoolean(value);
} else {
entry.s = value;
}

result.put(key, entry);
}

return result;
}

private static void handleUpdateCommandExecution(ApplicationContext context, String arguments) {
String[] argumentSplit = arguments.trim().split(" ", 4);
if (argumentSplit.length < 2) {
String[] parts = arguments.trim().split(" ", 3);
if (parts.length < 2) {
printCommandHelp();
return;
}

String commandExecutionId = argumentSplit[0];
String commandExecutionId = parts[0];
if (!context.activeCommandExecutions.containsKey(commandExecutionId)) {
System.out.printf("Failed to update command execution status: unknown command execution ID '%s'\n", commandExecutionId);
return;
}

String statusStr = argumentSplit[1];

String reasonCode = null;
String reasonDescription = null;
if (argumentSplit.length > 3) {
reasonCode = argumentSplit[2];
reasonDescription = argumentSplit[3];
}
String statusStr = parts[1];
Map<String, String> kvArgs = parseKeyValueArgs(parts.length > 2 ? parts[2] : "");

try {
CommandExecutionContext commandExecutionContext = context.activeCommandExecutions.get(commandExecutionId);
Expand All @@ -467,10 +514,18 @@ private static void handleUpdateCommandExecution(ApplicationContext context, Str
request.deviceType = commandExecutionContext.deviceType;
request.deviceId = commandExecutionContext.deviceId;
request.status = CommandExecutionStatus.valueOf(statusStr);
if (reasonCode != null && reasonDescription != null) {

String reasonCode = kvArgs.get("reason-code");
String reasonDescription = kvArgs.get("reason-description");
if (reasonCode != null || reasonDescription != null) {
request.statusReason = new StatusReason();
request.statusReason.reasonCode = reasonCode;
request.statusReason.reasonDescription = reasonDescription;
request.statusReason.reasonDescription = reasonDescription.replaceAll("^\"|\"$", "");
}

String resultStr = kvArgs.get("result");
if (resultStr != null) {
request.result = parseResult(resultStr);
}

UpdateCommandExecutionResponse response = context.commandsClient.updateCommandExecution(request).get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ public void close() {

private CommandExecutionEvent createCommandExecutionEvent(IncomingPublishEvent publishEvent) {
CommandExecutionEvent event = new CommandExecutionEvent();
event.executionId = publishEvent.getTopic().split("/")[5];
String[] segments = publishEvent.getTopic().split("/");
if (segments.length <= 5) {
throw new CrtRuntimeException("Invalid topic: " + publishEvent.getTopic());
}
event.executionId = segments[5];
event.payload = publishEvent.getPayload();
String contentType = publishEvent.getContentType();
if (contentType != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*
* This file is generated.
*/

package software.amazon.awssdk.iot.iotcommands.model;


/**
* The result value of the command execution. The device can use the result field to share additional details about the execution such as a return value of a remote function call.
*
*/
public class CommandExecutionResult {

/**
* An attribute of type String.
*
*/
public String s;


/**
* An attribute of type Boolean.
*
*/
public Boolean b;


/**
* An attribute of type Binary.
*
*/
public byte[] bin;


}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

package software.amazon.awssdk.iot.iotcommands.model;

import java.util.HashMap;
import software.amazon.awssdk.iot.iotcommands.model.CommandExecutionResult;
import software.amazon.awssdk.iot.iotcommands.model.CommandExecutionStatus;
import software.amazon.awssdk.iot.iotcommands.model.DeviceType;
import software.amazon.awssdk.iot.iotcommands.model.StatusReason;
Expand Down Expand Up @@ -52,4 +54,11 @@ public class UpdateCommandExecutionRequest {
public StatusReason statusReason;


/**
* The result value for the current state of the command execution. The status provides information about the progress of the command execution. The device can use the result field to share additional details about the execution such as a return value of a remote function call.
*
*/
public HashMap<String, software.amazon.awssdk.iot.iotcommands.model.CommandExecutionResult> result;


}
Loading