Skip to content

Commit d1b1e98

Browse files
committed
Support result in sample
1 parent 5a82bd0 commit d1b1e98

2 files changed

Lines changed: 88 additions & 20 deletions

File tree

samples/ServiceClients/CommandsSandbox/README.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,11 @@ Data Plane
5151
targeting the IoT Thing set on the application startup
5252
* `open-client-stream <payload-format>` - subscribe to a stream of AWS IoT command executions with a specified payload format
5353
targeting the MQTT client ID set on the application startup
54-
* `update-command-execution <execution-id> \<status> \[\<reason-code>] \[\<reason-description>]` - update status for specified
55-
execution ID;
54+
* `update-command-execution <execution-id> <status> [reason-code=<value>] [reason-description=<value>] [result=<key>:<value>;<key>:<value>]` -
55+
update status for specified execution ID;
5656
* status can be one of the following: IN_PROGRESS, SUCCEEDED, REJECTED, FAILED, TIMED_OUT
57-
* reason-code and reason-description may be optionally provided for the REJECTED, FAILED, or TIMED_OUT statuses
57+
* reason-code and reason-description may be optionally provided for any status
58+
* result is a semicolon-separated list of key:value pairs; if a value is true/false it is treated as boolean, otherwise as string
5859

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

357+
You can also provide execution results when updating the status. Results are specified as semicolon-separated
358+
key:value pairs. Values of `true` or `false` are treated as booleans, everything else is treated as a string:
359+
```
360+
update-command-execution 33333333-3333-3333-3333-333333333333 IN_PROGRESS result=batteryStatus:"unknown status";alive:true
361+
```
362+
363+
> [!NOTE]
364+
> You can also pass binary data in the result field using the CommandExecutionResult::bin member, which is not supported in this sample.
365+
356366
Then checking once again for the AWS IoT command execution status with
357367
```
358368
get-command-execution 33333333-3333-3333-3333-333333333333
@@ -362,6 +372,9 @@ should return
362372

363373
```
364374
Status of Command execution '33333333-3333-3333-3333-333333333333' is IN_PROGRESS
375+
Result:
376+
alive: true (boolean)
377+
batteryStatus: unknown (string)
365378
```
366379

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

384397
If you try to update the status of the same AWS IoT command execution to something else, it'll fail:

samples/ServiceClients/CommandsSandbox/src/main/java/commands/CommandsSandbox.java

Lines changed: 71 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -319,12 +319,12 @@ private static void printCommandHelp() {
319319
System.out.println(" application/json - subscribe to commands with JSON payload");
320320
System.out.println(" application/cbor - subscribe to commands with CBOR payload");
321321
System.out.println(" for any other value, subscribe to a generic topic");
322-
System.out.println(" update-command-execution <executionId> <status> [<reason-code>] [<reason-description>]");
322+
System.out.println(" update-command-execution <executionId> <status> [reason-code=<value>] [reason-description=<value>] [result=<key>:<value>;<key>:<value>]");
323323
System.out.println(" updates a command execution with a new status");
324324
System.out.println(" <status> can be one of the following:");
325325
System.out.println(" IN_PROGRESS, SUCCEEDED, REJECTED, FAILED, TIMED_OUT");
326-
System.out.println(" <reason-code> and <reason-description> may be optionally provided for");
327-
System.out.println(" the REJECTED, FAILED, or TIMED_OUT statuses\n");
326+
System.out.println(" reason-code and reason-description may be optionally provided for any status");
327+
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");
328328
System.out.println(" Miscellaneous commands:");
329329
System.out.println(" list-streams list all open streaming operations");
330330
System.out.println(" close-stream <streamID>");
@@ -426,39 +426,86 @@ private static void handleGetCommandExecution(ApplicationContext context, String
426426
GetCommandExecutionRequest getCommandExecutionRequest = GetCommandExecutionRequest.builder()
427427
.executionId(commandExecutionId)
428428
.targetArn(commandExecutionContext.deviceArn)
429+
.includeResult(true)
429430
.build();
430431
GetCommandExecutionResponse getCommandExecutionResponse = context.controlPlaneClient.getCommandExecution(getCommandExecutionRequest);
431432
System.out.printf("Status of command execution '%s' is %s\n", commandExecutionId, getCommandExecutionResponse.status());
432433
if (getCommandExecutionResponse.statusReason() != null) {
433434
System.out.printf(" Reason code: %s\n", getCommandExecutionResponse.statusReason().reasonCode());
434435
System.out.printf(" Reason description: %s\n", getCommandExecutionResponse.statusReason().reasonDescription());
435436
}
437+
if (getCommandExecutionResponse.hasResult()) {
438+
System.out.println(" Result:");
439+
getCommandExecutionResponse.result().forEach((key, value) -> {
440+
if (value.b() != null) {
441+
System.out.printf(" %s: %s (boolean)\n", key, value.b());
442+
} else if (value.s() != null) {
443+
System.out.printf(" %s: %s (string)\n", key, value.s());
444+
}
445+
});
446+
}
436447
} catch (Exception ex) {
437448
handleOperationException("get-command-execution", ex, context);
438449
}
439450
}
440451

452+
private static Map<String, String> parseKeyValueArgs(String input) {
453+
Pattern pattern = Pattern.compile("(\\S+?)=([^\\s\"]*(?:\"[^\"]*\"[^\\s\"]*)*)");
454+
Matcher matcher = pattern.matcher(input);
455+
456+
Map<String, String> result = new HashMap<>();
457+
458+
while (matcher.find()) {
459+
String key = matcher.group(1);
460+
String value = matcher.group(2);
461+
result.put(key, value);
462+
}
463+
464+
return result;
465+
}
466+
467+
private static HashMap<String, software.amazon.awssdk.iot.iotcommands.model.CommandExecutionResult> parseResult(String resultStr) {
468+
HashMap<String, software.amazon.awssdk.iot.iotcommands.model.CommandExecutionResult> result = new HashMap<>();
469+
470+
Pattern pattern = Pattern.compile("([^;:]*):([^;\"]*(?:\"[^\"]*\"[^;\"]*)*)");
471+
Matcher matcher = pattern.matcher(resultStr);
472+
473+
while (matcher.find()) {
474+
String key = matcher.group(1);
475+
String value = matcher.group(2).replaceAll("^\"|\"$", "");
476+
477+
software.amazon.awssdk.iot.iotcommands.model.CommandExecutionResult entry =
478+
new software.amazon.awssdk.iot.iotcommands.model.CommandExecutionResult();
479+
480+
/* NOTE: CommandExecutionResult also supports binary data via the `bin` member, which is not demonstrated in this
481+
* sample. */
482+
if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
483+
entry.b = Boolean.parseBoolean(value);
484+
} else {
485+
entry.s = value;
486+
}
487+
488+
result.put(key, entry);
489+
}
490+
491+
return result;
492+
}
493+
441494
private static void handleUpdateCommandExecution(ApplicationContext context, String arguments) {
442-
String[] argumentSplit = arguments.trim().split(" ", 4);
443-
if (argumentSplit.length < 2) {
495+
String[] parts = arguments.trim().split(" ", 3);
496+
if (parts.length < 2) {
444497
printCommandHelp();
445498
return;
446499
}
447500

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

454-
String statusStr = argumentSplit[1];
455-
456-
String reasonCode = null;
457-
String reasonDescription = null;
458-
if (argumentSplit.length > 3) {
459-
reasonCode = argumentSplit[2];
460-
reasonDescription = argumentSplit[3];
461-
}
507+
String statusStr = parts[1];
508+
Map<String, String> kvArgs = parseKeyValueArgs(parts.length > 2 ? parts[2] : "");
462509

463510
try {
464511
CommandExecutionContext commandExecutionContext = context.activeCommandExecutions.get(commandExecutionId);
@@ -467,10 +514,18 @@ private static void handleUpdateCommandExecution(ApplicationContext context, Str
467514
request.deviceType = commandExecutionContext.deviceType;
468515
request.deviceId = commandExecutionContext.deviceId;
469516
request.status = CommandExecutionStatus.valueOf(statusStr);
470-
if (reasonCode != null && reasonDescription != null) {
517+
518+
String reasonCode = kvArgs.get("reason-code");
519+
String reasonDescription = kvArgs.get("reason-description");
520+
if (reasonCode != null || reasonDescription != null) {
471521
request.statusReason = new StatusReason();
472522
request.statusReason.reasonCode = reasonCode;
473-
request.statusReason.reasonDescription = reasonDescription;
523+
request.statusReason.reasonDescription = reasonDescription.replaceAll("^\"|\"$", "");
524+
}
525+
526+
String resultStr = kvArgs.get("result");
527+
if (resultStr != null) {
528+
request.result = parseResult(resultStr);
474529
}
475530

476531
UpdateCommandExecutionResponse response = context.commandsClient.updateCommandExecution(request).get();

0 commit comments

Comments
 (0)