Skip to content

Commit 0f2335b

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

2 files changed

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

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

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-
}
508+
String statusStr = parts[1];
509+
Map<String, String> kvArgs = parseKeyValueArgs(parts.length > 2 ? parts[2] : "");
462510

463511
try {
464512
CommandExecutionContext commandExecutionContext = context.activeCommandExecutions.get(commandExecutionId);
@@ -467,10 +515,18 @@ private static void handleUpdateCommandExecution(ApplicationContext context, Str
467515
request.deviceType = commandExecutionContext.deviceType;
468516
request.deviceId = commandExecutionContext.deviceId;
469517
request.status = CommandExecutionStatus.valueOf(statusStr);
470-
if (reasonCode != null && reasonDescription != null) {
518+
519+
String reasonCode = kvArgs.get("reason-code");
520+
String reasonDescription = kvArgs.get("reason-description");
521+
if (reasonCode != null || reasonDescription != null) {
471522
request.statusReason = new StatusReason();
472523
request.statusReason.reasonCode = reasonCode;
473-
request.statusReason.reasonDescription = reasonDescription;
524+
request.statusReason.reasonDescription = reasonDescription.replaceAll("^\"|\"$", "");
525+
}
526+
527+
String resultStr = kvArgs.get("result");
528+
if (resultStr != null) {
529+
request.result = parseResult(resultStr);
474530
}
475531

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

0 commit comments

Comments
 (0)