From b2d68211883ba6fb0b0bd3903411c0915f9d48a7 Mon Sep 17 00:00:00 2001 From: Zhihui Xia Date: Mon, 2 Oct 2023 15:20:51 -0700 Subject: [PATCH 01/10] shadow sample --- .github/workflows/ci.yml | 3 + .../workflows/ci_run_shadow_mqtt5_cfg.json | 30 ++ crt/aws-crt-cpp | 2 +- .../shadow/mqtt5_shadow_sync/CMakeLists.txt | 28 + samples/shadow/mqtt5_shadow_sync/README.md | 127 +++++ samples/shadow/mqtt5_shadow_sync/main.cpp | 479 ++++++++++++++++++ 6 files changed, 668 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci_run_shadow_mqtt5_cfg.json create mode 100644 samples/shadow/mqtt5_shadow_sync/CMakeLists.txt create mode 100644 samples/shadow/mqtt5_shadow_sync/README.md create mode 100644 samples/shadow/mqtt5_shadow_sync/main.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18075ada7..b60d5a75b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -509,6 +509,9 @@ jobs: - name: run Shadow sample run: | python3 ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_shadow_cfg.json + - name: run Mqtt5 Shadow sample + run: | + python3 ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_shadow_mqtt5_cfg.json - name: configure AWS credentials (Jobs) uses: aws-actions/configure-aws-credentials@v1 with: diff --git a/.github/workflows/ci_run_shadow_mqtt5_cfg.json b/.github/workflows/ci_run_shadow_mqtt5_cfg.json new file mode 100644 index 000000000..8f173b5e1 --- /dev/null +++ b/.github/workflows/ci_run_shadow_mqtt5_cfg.json @@ -0,0 +1,30 @@ +{ + "language": "CPP", + "sample_file": "./aws-iot-device-sdk-cpp-v2/build/samples/shadow/mqtt5_shadow_sync/mqtt5-shadow-sync", + "sample_region": "us-east-1", + "sample_main_class": "", + "arguments": [ + { + "name": "--endpoint", + "secret": "ci/endpoint" + }, + { + "name": "--cert", + "secret": "ci/Shadow/cert", + "filename": "tmp_certificate.pem" + }, + { + "name": "--key", + "secret": "ci/Shadow/key", + "filename": "tmp_key.pem" + }, + { + "name": "--thing_name", + "data": "CI_Shadow_Thing" + }, + { + "name": "--is_ci", + "data": "true" + } + ] +} diff --git a/crt/aws-crt-cpp b/crt/aws-crt-cpp index 363d09e8a..bcbf23e2c 160000 --- a/crt/aws-crt-cpp +++ b/crt/aws-crt-cpp @@ -1 +1 @@ -Subproject commit 363d09e8a3509f663327d6e8414d0b2e052c815d +Subproject commit bcbf23e2c466a5dfa695f111b6eebd9bc9029100 diff --git a/samples/shadow/mqtt5_shadow_sync/CMakeLists.txt b/samples/shadow/mqtt5_shadow_sync/CMakeLists.txt new file mode 100644 index 000000000..34e17996f --- /dev/null +++ b/samples/shadow/mqtt5_shadow_sync/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.1) +# note: cxx-17 requires cmake 3.8, cxx-20 requires cmake 3.12 +project(mqtt5-shadow-sync CXX) + +file(GLOB SRC_FILES + "*.cpp" + "../../utils/CommandLineUtils.cpp" + "../../utils/CommandLineUtils.h" +) + +add_executable(${PROJECT_NAME} ${SRC_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 14) + +#set warnings +if (MSVC) + target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX) +else () + target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wno-long-long -pedantic -Werror) +endif () + +find_package(aws-crt-cpp REQUIRED) +find_package(IotShadow-cpp REQUIRED) + +install(TARGETS ${PROJECT_NAME} DESTINATION bin) + +target_link_libraries(${PROJECT_NAME} PRIVATE AWS::aws-crt-cpp AWS::IotShadow-cpp) diff --git a/samples/shadow/mqtt5_shadow_sync/README.md b/samples/shadow/mqtt5_shadow_sync/README.md new file mode 100644 index 000000000..0c614a9e5 --- /dev/null +++ b/samples/shadow/mqtt5_shadow_sync/README.md @@ -0,0 +1,127 @@ +# Shadow + +[**Return to main sample list**](../../README.md) + +This sample uses the AWS IoT [Device Shadow](https://docs.aws.amazon.com/iot/latest/developerguide/iot-device-shadows.html) Service to keep a property in sync between device and server. Imagine a light whose color may be changed through an app, or set by a local user. + +Once connected, type a value in the terminal and press Enter to update the property's "reported" value. The sample also responds when the "desired" value changes on the server. To observe this, edit the Shadow document in the AWS Console and set a new "desired" value. + +On startup, the sample requests the shadow document to learn the property's initial state. The sample also subscribes to "delta" events from the server, which are sent when a property's "desired" value differs from its "reported" value. When the sample learns of a new desired value, that value is changed on the device and an update is sent to the server with the new "reported" value. + +Your IoT Core Thing's [Policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) must provide privileges for this sample to connect, subscribe, publish, and receive. Below is a sample policy that can be used on your IoT Core Thing that will allow this sample to run as intended. + +
+Sample Policy +
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Publish"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/shadow/get",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/shadow/update"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Receive"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/shadow/get/accepted",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/shadow/get/rejected",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/shadow/update/accepted",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/shadow/update/rejected",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/shadow/update/delta"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Subscribe"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/shadow/get/accepted",
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/shadow/get/rejected",
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/shadow/update/accepted",
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/shadow/update/rejected",
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/shadow/update/delta"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": "iot:Connect",
+      "Resource": "arn:aws:iot:region:account:client/test-*"
+    }
+  ]
+}
+
+ +Replace with the following with the data from your AWS account: +* ``: The AWS IoT Core region where you created your AWS IoT Core thing you wish to use with this sample. For example `us-east-1`. +* ``: Your AWS IoT Core account ID. This is the set of numbers in the top right next to your AWS account name when using the AWS IoT Core website. +* ``: The name of your AWS IoT Core thing you want the device connection to be associated with + +Note that in a real application, you may want to avoid the use of wildcards in your ClientID or use them selectively. Please follow best practices when working with AWS on production applications using the SDK. Also, for the purposes of this sample, please make sure your policy allows a client ID of `test-*` to connect or use `--client_id ` to send the client ID your policy supports. + +
+ +## How to run + +To run the Shadow sample use the following command: + +``` sh +./mqtt5-shadow-sync --endpoint --cert --key --thing_name --shadow_property +``` + +You can also pass a Certificate Authority file (CA) if your certificate and key combination requires it: + +``` sh +./mqtt5-shadow-sync --endpoint --cert --key --thing_name --shadow_property --ca_file +``` + + +## Service Client Notes +### Difference between MQTT5 and MQTT311 IotShadowClient +The IotShadowClient with Mqtt5 client is identical to Mqtt3 one. We wrapped the Mqtt5Client into MqttClientConnection so that we could keep the same interface for IotShadowClient. +The only difference is that you would need setup up a Mqtt5 Client for the IotShadowClient. For how to setup a Mqtt5 Client, please refer to [MQTT5 UserGuide](../../../documents/MQTT5_Userguide.md) and [MQTT5 PubSub Sample](../../mqtt5/mqtt5_pubsub/) + + + + + + + + + + +
Create a IotShadowClient with Mqtt5Create a IotShadowClient with Mqtt311
+ +```Cpp + // Build Mqtt5Client + std::shared_ptr client = builder->Build(); + + // Create shadow client with mqtt5 client + Aws::Iotshadow::IotShadowClient shadowClient(client); +``` + + + +```Cpp + // Create mqtt311 connection + Aws::Iot::MqttClient client = Aws::Iot::MqttClient(); + auto connection = client.NewConnection(clientConfig); + + // Create shadow client with mqtt311 connection + Aws::Iotshadow::IotShadowClient shadowClient(connection); + +``` + +
+ +### Mqtt::QOS v.s. Mqtt5::QOS +As the service client interface is unchanged for Mqtt3 Connection and Mqtt5 Client,the IotShadowClient will use Mqtt::QOS instead of Mqtt5::QOS even with a Mqtt5 Client. diff --git a/samples/shadow/mqtt5_shadow_sync/main.cpp b/samples/shadow/mqtt5_shadow_sync/main.cpp new file mode 100644 index 000000000..1633f21c5 --- /dev/null +++ b/samples/shadow/mqtt5_shadow_sync/main.cpp @@ -0,0 +1,479 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../../utils/CommandLineUtils.h" + +using namespace Aws::Crt; +using namespace Aws::Iotshadow; + +static const char *SHADOW_VALUE_DEFAULT = "off"; + +static void s_changeShadowValue( + IotShadowClient &client, + const String &thingName, + const String &shadowProperty, + const String &value) +{ + fprintf(stdout, "Changing local shadow value to %s.\n", value.c_str()); + + ShadowState state; + JsonObject desired; + JsonObject reported; + + if (value == "null") + { + JsonObject nullObject; + nullObject.AsNull(); + desired.WithObject(shadowProperty, nullObject); + reported.WithObject(shadowProperty, nullObject); + } + else if (value == "clear_shadow") + { + desired.AsNull(); + reported.AsNull(); + } + else + { + desired.WithString(shadowProperty, value); + reported.WithString(shadowProperty, value); + } + state.Desired = desired; + state.Reported = reported; + + UpdateShadowRequest updateShadowRequest; + Aws::Crt::UUID uuid; + updateShadowRequest.ClientToken = uuid.ToString(); + updateShadowRequest.ThingName = thingName; + updateShadowRequest.State = state; + + auto publishCompleted = [thingName, value](int ioErr) { + if (ioErr != AWS_OP_SUCCESS) + { + fprintf(stderr, "Failed to update %s shadow state: error %s\n", thingName.c_str(), ErrorDebugString(ioErr)); + return; + } + + fprintf(stdout, "Successfully updated shadow state for %s, to %s\n", thingName.c_str(), value.c_str()); + }; + + client.PublishUpdateShadow(updateShadowRequest, AWS_MQTT_QOS_AT_LEAST_ONCE, std::move(publishCompleted)); +} + +int main(int argc, char *argv[]) +{ + /************************ Setup ****************************/ + + // Do the global initialization for the API. + ApiHandle apiHandle; + + String currentShadowValue(""); + + /** + * cmdData is the arguments/input from the command line placed into a single struct for + * use in this sample. This handles all of the command line parsing, validating, etc. + * See the Utils/CommandLineUtils for more information. + */ + Utils::cmdData cmdData = Utils::parseSampleInputShadow(argc, argv, &apiHandle); + + // Create the MQTT5 builder and populate it with data from cmdData. + Aws::Iot::Mqtt5ClientBuilder *builder = Aws::Iot::Mqtt5ClientBuilder::NewMqtt5ClientBuilderWithMtlsFromPath( + cmdData.input_endpoint, cmdData.input_cert.c_str(), cmdData.input_key.c_str()); + + // Check if the builder setup correctly. + if (builder == nullptr) + { + printf( + "Failed to setup mqtt5 client builder with error code %d: %s", LastError(), ErrorDebugString(LastError())); + return -1; + } + + // Setup connection options + std::shared_ptr connectOptions = std::make_shared(); + connectOptions->WithClientId(cmdData.input_clientId); + builder->WithConnectOptions(connectOptions); + if (cmdData.input_port != 0) + { + builder->WithPort(static_cast(cmdData.input_port)); + } + + std::promise connectionPromise; + std::promise stoppedPromise; + + // Setup lifecycle callbacks + builder->WithClientConnectionSuccessCallback( + [&connectionPromise](const Mqtt5::OnConnectionSuccessEventData &eventData) + { + fprintf( + stdout, + "Mqtt5 Client connection succeed, clientid: %s.\n", + eventData.negotiatedSettings->getClientId().c_str()); + connectionPromise.set_value(true); + }); + builder->WithClientConnectionFailureCallback( + [&connectionPromise](const Mqtt5::OnConnectionFailureEventData &eventData) + { + fprintf( + stdout, "Mqtt5 Client connection failed with error: %s.\n", aws_error_debug_str(eventData.errorCode)); + connectionPromise.set_value(false); + }); + builder->WithClientStoppedCallback( + [&stoppedPromise](const Mqtt5::OnStoppedEventData &) + { + fprintf(stdout, "Mqtt5 Client stopped.\n"); + stoppedPromise.set_value(); + }); + + // Create Mqtt5Client + std::shared_ptr client = builder->Build(); + delete builder; + /************************ Run the sample ****************************/ + + fprintf(stdout, "Connecting...\n"); + if (!client->Start()) + { + fprintf(stderr, "MQTT5 Connection failed to start"); + exit(-1); + } + + if (connectionPromise.get_future().get()) + { + Aws::Iotshadow::IotShadowClient shadowClient(client); + + /********************** Shadow Delta Updates ********************/ + // This section is for when a Shadow document updates/changes, whether it is on the server side or client side. + + std::promise subscribeDeltaCompletedPromise; + std::promise subscribeDeltaAcceptedCompletedPromise; + std::promise subscribeDeltaRejectedCompletedPromise; + + auto onDeltaUpdatedSubAck = [&](int ioErr) { + if (ioErr != AWS_OP_SUCCESS) + { + fprintf(stderr, "Error subscribing to shadow delta: %s\n", ErrorDebugString(ioErr)); + exit(-1); + } + subscribeDeltaCompletedPromise.set_value(); + }; + + auto onDeltaUpdatedAcceptedSubAck = [&](int ioErr) { + if (ioErr != AWS_OP_SUCCESS) + { + fprintf(stderr, "Error subscribing to shadow delta accepted: %s\n", ErrorDebugString(ioErr)); + exit(-1); + } + subscribeDeltaAcceptedCompletedPromise.set_value(); + }; + + auto onDeltaUpdatedRejectedSubAck = [&](int ioErr) { + if (ioErr != AWS_OP_SUCCESS) + { + fprintf(stderr, "Error subscribing to shadow delta rejected: %s\n", ErrorDebugString(ioErr)); + exit(-1); + } + subscribeDeltaRejectedCompletedPromise.set_value(); + }; + + auto onDeltaUpdated = [&](ShadowDeltaUpdatedEvent *event, int ioErr) { + if (ioErr) + { + fprintf(stdout, "Error processing shadow delta: %s\n", ErrorDebugString(ioErr)); + exit(-1); + } + + if (event) + { + fprintf(stdout, "Received shadow delta event.\n"); + if (event->State && event->State->View().ValueExists(cmdData.input_shadowProperty)) + { + JsonView objectView = event->State->View().GetJsonObject(cmdData.input_shadowProperty); + if (objectView.IsNull()) + { + fprintf( + stdout, + "Delta reports that %s was deleted. Resetting defaults...\n", + cmdData.input_shadowProperty.c_str()); + s_changeShadowValue( + shadowClient, cmdData.input_thingName, cmdData.input_shadowProperty, SHADOW_VALUE_DEFAULT); + } + else + { + fprintf( + stdout, + "Delta reports that \"%s\" has a desired value of \"%s\", Changing local value...\n", + cmdData.input_shadowProperty.c_str(), + event->State->View().GetString(cmdData.input_shadowProperty).c_str()); + s_changeShadowValue( + shadowClient, + cmdData.input_thingName, + cmdData.input_shadowProperty, + event->State->View().GetString(cmdData.input_shadowProperty)); + } + + if (event->ClientToken) + { + fprintf(stdout, " ClientToken: %s\n", event->ClientToken->c_str()); + } + } + else + { + fprintf(stdout, "Delta did not report a change in \"%s\".\n", cmdData.input_shadowProperty.c_str()); + } + } + }; + + auto onUpdateShadowAccepted = [&](UpdateShadowResponse *response, int ioErr) { + if (ioErr != AWS_OP_SUCCESS) + { + fprintf(stderr, "Error on subscription: %s.\n", ErrorDebugString(ioErr)); + exit(-1); + } + + if (response->State->Reported) + { + currentShadowValue = response->State->Reported->View().GetString(cmdData.input_shadowProperty); + } + else + { + fprintf(stdout, "Finished clearing shadow properties\n"); + currentShadowValue = ""; + } + + if (cmdData.input_isCI == false) + { + fprintf(stdout, "Enter Desired state of %s:\n", cmdData.input_shadowProperty.c_str()); + } + }; + + auto onUpdateShadowRejected = [&](ErrorResponse *error, int ioErr) { + if (ioErr != AWS_OP_SUCCESS) + { + fprintf(stderr, "Error on subscription: %s.\n", ErrorDebugString(ioErr)); + exit(-1); + } + fprintf( + stdout, + "Update of shadow state failed with message %s and code %d.", + error->Message->c_str(), + *error->Code); + }; + + ShadowDeltaUpdatedSubscriptionRequest shadowDeltaUpdatedRequest; + shadowDeltaUpdatedRequest.ThingName = cmdData.input_thingName; + + shadowClient.SubscribeToShadowDeltaUpdatedEvents( + shadowDeltaUpdatedRequest, AWS_MQTT_QOS_AT_LEAST_ONCE, onDeltaUpdated, onDeltaUpdatedSubAck); + + UpdateShadowSubscriptionRequest updateShadowSubscriptionRequest; + updateShadowSubscriptionRequest.ThingName = cmdData.input_thingName; + + shadowClient.SubscribeToUpdateShadowAccepted( + updateShadowSubscriptionRequest, + AWS_MQTT_QOS_AT_LEAST_ONCE, + onUpdateShadowAccepted, + onDeltaUpdatedAcceptedSubAck); + + shadowClient.SubscribeToUpdateShadowRejected( + updateShadowSubscriptionRequest, + AWS_MQTT_QOS_AT_LEAST_ONCE, + onUpdateShadowRejected, + onDeltaUpdatedRejectedSubAck); + + subscribeDeltaCompletedPromise.get_future().wait(); + subscribeDeltaAcceptedCompletedPromise.get_future().wait(); + subscribeDeltaRejectedCompletedPromise.get_future().wait(); + + /********************** Shadow Value Get ********************/ + // This section is to get the initial value of the Shadow document. + + std::promise subscribeGetShadowAcceptedCompletedPromise; + std::promise subscribeGetShadowRejectedCompletedPromise; + std::promise onGetShadowRequestCompletedPromise; + std::promise gotInitialShadowPromise; + + auto onGetShadowUpdatedAcceptedSubAck = [&](int ioErr) { + if (ioErr != AWS_OP_SUCCESS) + { + fprintf(stderr, "Error subscribing to get shadow document accepted: %s\n", ErrorDebugString(ioErr)); + exit(-1); + } + subscribeGetShadowAcceptedCompletedPromise.set_value(); + }; + + auto onGetShadowUpdatedRejectedSubAck = [&](int ioErr) { + if (ioErr != AWS_OP_SUCCESS) + { + fprintf(stderr, "Error subscribing to get shadow document rejected: %s\n", ErrorDebugString(ioErr)); + exit(-1); + } + subscribeGetShadowRejectedCompletedPromise.set_value(); + }; + + auto onGetShadowRequestSubAck = [&](int ioErr) { + if (ioErr != AWS_OP_SUCCESS) + { + fprintf(stderr, "Error getting shadow document: %s\n", ErrorDebugString(ioErr)); + exit(-1); + } + onGetShadowRequestCompletedPromise.set_value(); + }; + + auto onGetShadowAccepted = [&](GetShadowResponse *response, int ioErr) { + if (ioErr != AWS_OP_SUCCESS) + { + fprintf(stderr, "Error getting shadow value from document: %s.\n", ErrorDebugString(ioErr)); + exit(-1); + } + if (response) + { + fprintf(stdout, "Received shadow document.\n"); + if (response->State && response->State->Reported->View().ValueExists(cmdData.input_shadowProperty)) + { + JsonView objectView = response->State->Reported->View().GetJsonObject(cmdData.input_shadowProperty); + if (objectView.IsNull()) + { + fprintf(stdout, "Shadow contains \"%s\" but is null.\n", cmdData.input_shadowProperty.c_str()); + currentShadowValue = ""; + } + else + { + currentShadowValue = response->State->Reported->View().GetString(cmdData.input_shadowProperty); + fprintf( + stdout, + "Shadow contains \"%s\". Updating local value to \"%s\"...\n", + cmdData.input_shadowProperty.c_str(), + currentShadowValue.c_str()); + } + } + else + { + fprintf( + stdout, "Shadow currently does not contain \"%s\".\n", cmdData.input_shadowProperty.c_str()); + currentShadowValue = ""; + } + gotInitialShadowPromise.set_value(); + } + }; + + auto onGetShadowRejected = [&](ErrorResponse *error, int ioErr) { + if (ioErr != AWS_OP_SUCCESS) + { + fprintf(stderr, "Error on getting shadow document: %s.\n", ErrorDebugString(ioErr)); + exit(-1); + } + fprintf( + stdout, + "Getting shadow document failed with message %s and code %d.\n", + error->Message->c_str(), + *error->Code); + gotInitialShadowPromise.set_value(); + }; + + GetShadowSubscriptionRequest shadowSubscriptionRequest; + shadowSubscriptionRequest.ThingName = cmdData.input_thingName; + + shadowClient.SubscribeToGetShadowAccepted( + shadowSubscriptionRequest, + AWS_MQTT_QOS_AT_LEAST_ONCE, + onGetShadowAccepted, + onGetShadowUpdatedAcceptedSubAck); + + shadowClient.SubscribeToGetShadowRejected( + shadowSubscriptionRequest, + AWS_MQTT_QOS_AT_LEAST_ONCE, + onGetShadowRejected, + onGetShadowUpdatedRejectedSubAck); + + subscribeGetShadowAcceptedCompletedPromise.get_future().wait(); + subscribeGetShadowRejectedCompletedPromise.get_future().wait(); + + GetShadowRequest shadowGetRequest; + shadowGetRequest.ThingName = cmdData.input_thingName; + + // Get the current shadow document so we start with the correct value + shadowClient.PublishGetShadow(shadowGetRequest, AWS_MQTT_QOS_AT_LEAST_ONCE, onGetShadowRequestSubAck); + + onGetShadowRequestCompletedPromise.get_future().wait(); + gotInitialShadowPromise.get_future().wait(); + + /********************** Shadow change value input loop ********************/ + /** + * This section is to getting user input and changing the shadow value passed to that input. + * If in CI, then input is automatically passed + */ + + if (cmdData.input_isCI == false) + { + fprintf(stdout, "Enter Desired state of %s:\n", cmdData.input_shadowProperty.c_str()); + while (true) + { + String input; + std::cin >> input; + + if (input == "exit" || input == "quit") + { + fprintf(stdout, "Exiting..."); + break; + } + + if (input == currentShadowValue) + { + fprintf(stdout, "Shadow is already set to \"%s\"\n", currentShadowValue.c_str()); + fprintf(stdout, "Enter Desired state of %s:\n", cmdData.input_shadowProperty.c_str()); + } + else + { + s_changeShadowValue(shadowClient, cmdData.input_thingName, cmdData.input_shadowProperty, input); + } + } + } + else + { + int messagesSent = 0; + while (messagesSent < 5) + { + String input = "Shadow_Value_"; + input.append(std::to_string(messagesSent).c_str()); + s_changeShadowValue(shadowClient, cmdData.input_thingName, cmdData.input_shadowProperty, input); + // Sleep so there is a gap between shadow updates + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + messagesSent += 1; + } + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + } + + // Disconnect + if (client->Stop()) + { + stoppedPromise.get_future().wait(); + } + return 0; +} \ No newline at end of file From 6c843bb5f23d37e37a5e917450a98d8bb74f659f Mon Sep 17 00:00:00 2001 From: Zhihui Xia Date: Mon, 2 Oct 2023 15:27:56 -0700 Subject: [PATCH 02/10] update jobs sample --- .github/workflows/ci.yml | 3 + .github/workflows/ci_run_jobs_mqtt5_cfg.json | 30 +++ .../CMakeLists.txt | 28 +++ .../mqtt5_describe_job_execution/README.md | 123 ++++++++++++ .../mqtt5_describe_job_execution/main.cpp | 182 ++++++++++++++++++ samples/shadow/mqtt5_shadow_sync/main.cpp | 25 +-- 6 files changed, 376 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/ci_run_jobs_mqtt5_cfg.json create mode 100644 samples/jobs/mqtt5_describe_job_execution/CMakeLists.txt create mode 100644 samples/jobs/mqtt5_describe_job_execution/README.md create mode 100644 samples/jobs/mqtt5_describe_job_execution/main.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b60d5a75b..7a710391d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -520,6 +520,9 @@ jobs: - name: run Jobs sample run: | python3 ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_jobs_cfg.json + - name: run Mqtt5 Jobs sample + run: | + python3 ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_jobs_mqtt5_cfg.json - name: configure AWS credentials (Fleet provisioning) uses: aws-actions/configure-aws-credentials@v1 with: diff --git a/.github/workflows/ci_run_jobs_mqtt5_cfg.json b/.github/workflows/ci_run_jobs_mqtt5_cfg.json new file mode 100644 index 000000000..23d48c2b1 --- /dev/null +++ b/.github/workflows/ci_run_jobs_mqtt5_cfg.json @@ -0,0 +1,30 @@ +{ + "language": "CPP", + "sample_file": "./aws-iot-device-sdk-cpp-v2/build/samples/jobs/mqtt5_describe_job_execution/mqtt5-describe-job-execution", + "sample_region": "us-east-1", + "sample_main_class": "", + "arguments": [ + { + "name": "--endpoint", + "secret": "ci/endpoint" + }, + { + "name": "--cert", + "secret": "ci/Jobs/cert", + "filename": "tmp_certificate.pem" + }, + { + "name": "--key", + "secret": "ci/Jobs/key", + "filename": "tmp_key.pem" + }, + { + "name": "--thing_name", + "data": "CI_Jobs_Thing" + }, + { + "name": "--job_id", + "data": "CI_Jobs_Thing_Job_1" + } + ] +} diff --git a/samples/jobs/mqtt5_describe_job_execution/CMakeLists.txt b/samples/jobs/mqtt5_describe_job_execution/CMakeLists.txt new file mode 100644 index 000000000..68f7fb33f --- /dev/null +++ b/samples/jobs/mqtt5_describe_job_execution/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.1) +# note: cxx-17 requires cmake 3.8, cxx-20 requires cmake 3.12 +project(mqtt5-describe-job-execution CXX) + +file(GLOB SRC_FILES + "*.cpp" + "../../utils/CommandLineUtils.cpp" + "../../utils/CommandLineUtils.h" +) + +add_executable(${PROJECT_NAME} ${SRC_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 14) + +#set warnings +if (MSVC) + target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX) +else () + target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wno-long-long -pedantic -Werror) +endif () + +find_package(aws-crt-cpp REQUIRED) +find_package(IotJobs-cpp REQUIRED) + +install(TARGETS ${PROJECT_NAME} DESTINATION bin) + +target_link_libraries(${PROJECT_NAME} PRIVATE AWS::aws-crt-cpp AWS::IotJobs-cpp) diff --git a/samples/jobs/mqtt5_describe_job_execution/README.md b/samples/jobs/mqtt5_describe_job_execution/README.md new file mode 100644 index 000000000..e97c806d6 --- /dev/null +++ b/samples/jobs/mqtt5_describe_job_execution/README.md @@ -0,0 +1,123 @@ +# Jobs + +[**Return to main sample list**](../../README.md) + +This sample uses the AWS IoT [Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) Service to describe jobs to execute. [Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) is a service that allows you to define and respond to remote operation requests defined through the AWS IoT Core website or via any other device (or CLI command) that can access the [Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) service. + +Note: This sample requires you to create jobs for your device to execute. See +[instructions here](https://docs.aws.amazon.com/iot/latest/developerguide/create-manage-jobs.html) for how to make jobs. + +On startup, the sample describes the jobs that are pending execution. + +Your IoT Core Thing's [Policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) must provide privileges for this sample to connect, subscribe, publish, and receive. Below is a sample policy that can be used on your IoT Core Thing that will allow this sample to run as intended. + +
+Sample Policy +
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": "iot:Publish",
+      "Resource": [
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/jobs/start-next",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/jobs/*/update",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/jobs/*/get",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/jobs/get"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": "iot:Receive",
+      "Resource": [
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/jobs/notify-next",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/jobs/start-next/*",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/jobs/*/update/*",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/jobs/get/*",
+        "arn:aws:iot:region:account:topic/$aws/things/thingname/jobs/*/get/*"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": "iot:Subscribe",
+      "Resource": [
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/jobs/notify-next",
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/jobs/start-next/*",
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/jobs/*/update/*",
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/jobs/get/*",
+        "arn:aws:iot:region:account:topicfilter/$aws/things/thingname/jobs/*/get/*"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": "iot:Connect",
+      "Resource": "arn:aws:iot:region:account:client/test-*"
+    }
+  ]
+}
+
+ +Replace with the following with the data from your AWS account: +* ``: The AWS IoT Core region where you created your AWS IoT Core thing you wish to use with this sample. For example `us-east-1`. +* ``: Your AWS IoT Core account ID. This is the set of numbers in the top right next to your AWS account name when using the AWS IoT Core website. +* ``: The name of your AWS IoT Core thing you want the device connection to be associated with + +Note that in a real application, you may want to avoid the use of wildcards in your ClientID or use them selectively. Please follow best practices when working with AWS on production applications using the SDK. Also, for the purposes of this sample, please make sure your policy allows a client ID of `test-*` to connect or use `--client_id ` to send the client ID your policy supports. + +
+ +## How to run + +Use the following command to run the Jobs sample: + +``` sh +./mqtt5-describe-job-execution --endpoint --cert --key --thing_name --job_id +``` + +You can also pass a Certificate Authority file (CA) if your certificate and key combination requires it: + +``` sh +./mqtt5-describe-job-execution --endpoint --cert --key --thing_name --job_id --ca_file +``` + +## Service Client Notes +### Difference between MQTT5 and MQTT311 IotJobsClient +The IotJobsClient with Mqtt5 client is identical to Mqtt3 one. We wrapped the Mqtt5Client into MqttClientConnection so that we could keep the same interface for IotJobsClient. +The only difference is that you would need setup up a Mqtt5 Client for the IotJobsClient. For how to setup a Mqtt5 Client, please refer to [MQTT5 UserGuide](../../../documents/MQTT5_Userguide.md) and [MQTT5 PubSub Sample](../../mqtt5/mqtt5_pubsub/) + + + + + + + + + + +
Create a IotJobsClient with Mqtt5Create a IotJobsClient with Mqtt311
+ +```Cpp + // Build Mqtt5Client + std::shared_ptr client = builder->Build(); + + // Create jobs client with mqtt5 client + IotJobsClient jobsClient(client); +``` + + + +```Cpp + // Create mqtt311 connection + Aws::Iot::MqttClient client = Aws::Iot::MqttClient(); + auto connection = client.NewConnection(clientConfig); + + // Create jobs client with mqtt311 connection + IotJobsClient jobsClient(connection); + +``` + +
+ +### Mqtt::QOS v.s. Mqtt5::QOS +As the service client interface is unchanged for Mqtt3 Connection and Mqtt5 Client,the IotJobsClient will use Mqtt::QOS instead of Mqtt5::QOS even with a Mqtt5 Client. diff --git a/samples/jobs/mqtt5_describe_job_execution/main.cpp b/samples/jobs/mqtt5_describe_job_execution/main.cpp new file mode 100644 index 000000000..c89b27f17 --- /dev/null +++ b/samples/jobs/mqtt5_describe_job_execution/main.cpp @@ -0,0 +1,182 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../../utils/CommandLineUtils.h" + +using namespace Aws::Crt; +using namespace Aws::Iotjobs; + +int main(int argc, char *argv[]) +{ + /************************ Setup ****************************/ + + // Do the global initialization for the API + ApiHandle apiHandle; + + /** + * cmdData is the arguments/input from the command line placed into a single struct for + * use in this sample. This handles all of the command line parsing, validating, etc. + * See the Utils/CommandLineUtils for more information. + */ + Utils::cmdData cmdData = Utils::parseSampleInputJobs(argc, argv, &apiHandle); + + // Create the MQTT5 builder and populate it with data from cmdData. + Aws::Iot::Mqtt5ClientBuilder *builder = Aws::Iot::Mqtt5ClientBuilder::NewMqtt5ClientBuilderWithMtlsFromPath( + cmdData.input_endpoint, cmdData.input_cert.c_str(), cmdData.input_key.c_str()); + + // Check if the builder setup correctly. + if (builder == nullptr) + { + printf( + "Failed to setup mqtt5 client builder with error code %d: %s", LastError(), ErrorDebugString(LastError())); + return -1; + } + + // Setup connection options + std::shared_ptr connectOptions = std::make_shared(); + connectOptions->WithClientId(cmdData.input_clientId); + builder->WithConnectOptions(connectOptions); + if (cmdData.input_port != 0) + { + builder->WithPort(static_cast(cmdData.input_port)); + } + + std::promise connectionPromise; + std::promise stoppedPromise; + + // Setup lifecycle callbacks + builder->WithClientConnectionSuccessCallback( + [&connectionPromise](const Mqtt5::OnConnectionSuccessEventData &eventData) { + fprintf( + stdout, + "Mqtt5 Client connection succeed, clientid: %s.\n", + eventData.negotiatedSettings->getClientId().c_str()); + connectionPromise.set_value(true); + }); + builder->WithClientConnectionFailureCallback([&connectionPromise]( + const Mqtt5::OnConnectionFailureEventData &eventData) { + fprintf(stdout, "Mqtt5 Client connection failed with error: %s.\n", aws_error_debug_str(eventData.errorCode)); + connectionPromise.set_value(false); + }); + builder->WithClientStoppedCallback([&stoppedPromise](const Mqtt5::OnStoppedEventData &) { + fprintf(stdout, "Mqtt5 Client stopped.\n"); + stoppedPromise.set_value(); + }); + + // Create Mqtt5Client + std::shared_ptr client = builder->Build(); + delete builder; + /************************ Run the sample ****************************/ + + fprintf(stdout, "Connecting...\n"); + if (!client->Start()) + { + fprintf(stderr, "MQTT5 Connection failed to start"); + exit(-1); + } + + if (connectionPromise.get_future().get()) + { + IotJobsClient jobsClient(client); + + DescribeJobExecutionSubscriptionRequest describeJobExecutionSubscriptionRequest; + describeJobExecutionSubscriptionRequest.ThingName = cmdData.input_thingName; + describeJobExecutionSubscriptionRequest.JobId = cmdData.input_jobId; + + /** + * This isn't absolutely necessary but since we're doing a publish almost immediately afterwards, + * to be cautious make sure the subscribe has finished before doing the publish. + */ + std::promise subAckedPromise; + auto subAckHandler = [&](int) { + // if error code returns it will be recorded by the other callback + subAckedPromise.set_value(); + }; + auto subscriptionHandler = [&](DescribeJobExecutionResponse *response, int ioErr) { + if (ioErr) + { + fprintf(stderr, "Error %d occurred\n", ioErr); + return; + } + fprintf(stdout, "Received Job:\n"); + fprintf(stdout, "Job Id: %s\n", response->Execution->JobId->c_str()); + fprintf(stdout, "ClientToken: %s\n", response->ClientToken->c_str()); + fprintf(stdout, "Execution Status: %s\n", JobStatusMarshaller::ToString(*response->Execution->Status)); + }; + + jobsClient.SubscribeToDescribeJobExecutionAccepted( + describeJobExecutionSubscriptionRequest, AWS_MQTT_QOS_AT_LEAST_ONCE, subscriptionHandler, subAckHandler); + subAckedPromise.get_future().wait(); + + subAckedPromise = std::promise(); + auto failureHandler = [&](RejectedError *rejectedError, int ioErr) { + if (ioErr) + { + fprintf(stderr, "Error %d occurred\n", ioErr); + return; + } + if (rejectedError) + { + fprintf(stderr, "Service Error %d occurred\n", (int)rejectedError->Code.value()); + return; + } + }; + + jobsClient.SubscribeToDescribeJobExecutionRejected( + describeJobExecutionSubscriptionRequest, AWS_MQTT_QOS_AT_LEAST_ONCE, failureHandler, subAckHandler); + subAckedPromise.get_future().wait(); + + DescribeJobExecutionRequest describeJobExecutionRequest; + describeJobExecutionRequest.ThingName = cmdData.input_thingName; + describeJobExecutionRequest.JobId = cmdData.input_jobId; + describeJobExecutionRequest.IncludeJobDocument = true; + Aws::Crt::UUID uuid; + describeJobExecutionRequest.ClientToken = uuid.ToString(); + std::promise publishDescribeJobExeCompletedPromise; + + auto publishHandler = [&](int ioErr) { + if (ioErr) + { + fprintf(stderr, "Error %d occurred\n", ioErr); + } + publishDescribeJobExeCompletedPromise.set_value(); + }; + + jobsClient.PublishDescribeJobExecution( + std::move(describeJobExecutionRequest), AWS_MQTT_QOS_AT_LEAST_ONCE, publishHandler); + publishDescribeJobExeCompletedPromise.get_future().wait(); + } + + // Wait just a little bit to let the console print + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + // Disconnect + if (client->Stop()) + { + stoppedPromise.get_future().wait(); + } + + return 0; +} \ No newline at end of file diff --git a/samples/shadow/mqtt5_shadow_sync/main.cpp b/samples/shadow/mqtt5_shadow_sync/main.cpp index 1633f21c5..831a98e6e 100644 --- a/samples/shadow/mqtt5_shadow_sync/main.cpp +++ b/samples/shadow/mqtt5_shadow_sync/main.cpp @@ -129,27 +129,22 @@ int main(int argc, char *argv[]) // Setup lifecycle callbacks builder->WithClientConnectionSuccessCallback( - [&connectionPromise](const Mqtt5::OnConnectionSuccessEventData &eventData) - { + [&connectionPromise](const Mqtt5::OnConnectionSuccessEventData &eventData) { fprintf( stdout, "Mqtt5 Client connection succeed, clientid: %s.\n", eventData.negotiatedSettings->getClientId().c_str()); connectionPromise.set_value(true); }); - builder->WithClientConnectionFailureCallback( - [&connectionPromise](const Mqtt5::OnConnectionFailureEventData &eventData) - { - fprintf( - stdout, "Mqtt5 Client connection failed with error: %s.\n", aws_error_debug_str(eventData.errorCode)); - connectionPromise.set_value(false); - }); - builder->WithClientStoppedCallback( - [&stoppedPromise](const Mqtt5::OnStoppedEventData &) - { - fprintf(stdout, "Mqtt5 Client stopped.\n"); - stoppedPromise.set_value(); - }); + builder->WithClientConnectionFailureCallback([&connectionPromise]( + const Mqtt5::OnConnectionFailureEventData &eventData) { + fprintf(stdout, "Mqtt5 Client connection failed with error: %s.\n", aws_error_debug_str(eventData.errorCode)); + connectionPromise.set_value(false); + }); + builder->WithClientStoppedCallback([&stoppedPromise](const Mqtt5::OnStoppedEventData &) { + fprintf(stdout, "Mqtt5 Client stopped.\n"); + stoppedPromise.set_value(); + }); // Create Mqtt5Client std::shared_ptr client = builder->Build(); From 4e3dc94d7da50fc6b0d74c3a44afc56d3e063dc9 Mon Sep 17 00:00:00 2001 From: Zhihui Xia Date: Mon, 2 Oct 2023 15:32:39 -0700 Subject: [PATCH 03/10] identity sample --- .github/workflows/ci.yml | 6 + .../ci_run_fleet_provisioning_mqtt5_cfg.json | 30 ++ .../mqtt5_fleet_provisioning/CMakeLists.txt | 28 ++ .../mqtt5_fleet_provisioning/README.md | 348 +++++++++++++ .../mqtt5_fleet_provisioning/main.cpp | 468 ++++++++++++++++++ 5 files changed, 880 insertions(+) create mode 100644 .github/workflows/ci_run_fleet_provisioning_mqtt5_cfg.json create mode 100644 samples/fleet_provisioning/mqtt5_fleet_provisioning/CMakeLists.txt create mode 100644 samples/fleet_provisioning/mqtt5_fleet_provisioning/README.md create mode 100644 samples/fleet_provisioning/mqtt5_fleet_provisioning/main.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a710391d..c66dfe69a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -534,6 +534,12 @@ jobs: Sample_UUID=$(python3 -c "import uuid; print (uuid.uuid4())") python3 ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_fleet_provisioning_cfg.json --input_uuid ${Sample_UUID} python3 ${{ env.CI_UTILS_FOLDER }}/delete_iot_thing_ci.py --thing_name "Fleet_Thing_${Sample_UUID}" --region "us-east-1" + - name: run Mqtt5 Fleet Provisioning sample + run: | + echo "Generating UUID for IoT thing" + Sample_UUID=$(python3 -c "import uuid; print (uuid.uuid4())") + python3 ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_fleet_provisioning_mqtt5_cfg.json --input_uuid ${Sample_UUID} + python3 ${{ env.CI_UTILS_FOLDER }}/delete_iot_thing_ci.py --thing_name "Fleet_Thing_${Sample_UUID}" --region "us-east-1" - name: configure AWS credentials (Secure tunneling) uses: aws-actions/configure-aws-credentials@v1 with: diff --git a/.github/workflows/ci_run_fleet_provisioning_mqtt5_cfg.json b/.github/workflows/ci_run_fleet_provisioning_mqtt5_cfg.json new file mode 100644 index 000000000..bc4408d3a --- /dev/null +++ b/.github/workflows/ci_run_fleet_provisioning_mqtt5_cfg.json @@ -0,0 +1,30 @@ +{ + "language": "CPP", + "sample_file": "./aws-iot-device-sdk-cpp-v2/build/samples/fleet_provisioning/mqtt5_fleet_provisioning/mqtt5-fleet-provisioning", + "sample_region": "us-east-1", + "sample_main_class": "", + "arguments": [ + { + "name": "--endpoint", + "secret": "ci/endpoint" + }, + { + "name": "--cert", + "secret": "ci/FleetProvisioning/cert", + "filename": "tmp_certificate.pem" + }, + { + "name": "--key", + "secret": "ci/FleetProvisioning/key", + "filename": "tmp_key.pem" + }, + { + "name": "--template_name", + "data": "CI_FleetProvisioning_Template" + }, + { + "name": "--template_parameters", + "data": "{\"SerialNumber\":\"$INPUT_UUID\"}" + } + ] +} diff --git a/samples/fleet_provisioning/mqtt5_fleet_provisioning/CMakeLists.txt b/samples/fleet_provisioning/mqtt5_fleet_provisioning/CMakeLists.txt new file mode 100644 index 000000000..b13a3f34d --- /dev/null +++ b/samples/fleet_provisioning/mqtt5_fleet_provisioning/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.1) +# note: cxx-17 requires cmake 3.8, cxx-20 requires cmake 3.12 +project(mqtt5-fleet-provisioning CXX) + +file(GLOB SRC_FILES + "*.cpp" + "../../utils/CommandLineUtils.cpp" + "../../utils/CommandLineUtils.h" +) + +add_executable(${PROJECT_NAME} ${SRC_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 14) + +#set warnings +if (MSVC) + target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX) +else () + target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wno-long-long -pedantic -Werror) +endif () + +find_package(aws-crt-cpp REQUIRED) +find_package(IotIdentity-cpp REQUIRED) + +install(TARGETS ${PROJECT_NAME} DESTINATION bin) + +target_link_libraries(${PROJECT_NAME} PRIVATE AWS::aws-crt-cpp AWS::IotIdentity-cpp) diff --git a/samples/fleet_provisioning/mqtt5_fleet_provisioning/README.md b/samples/fleet_provisioning/mqtt5_fleet_provisioning/README.md new file mode 100644 index 000000000..9746f5d41 --- /dev/null +++ b/samples/fleet_provisioning/mqtt5_fleet_provisioning/README.md @@ -0,0 +1,348 @@ +# Fleet provisioning + +[**Return to main sample list**](../../README.md) + +This sample uses the AWS IoT [Fleet provisioning](https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html) to provision devices using either a CSR or Keys-And-Certificate and subsequently calls RegisterThing. This allows you to create new AWS IoT Core things using a Fleet Provisioning Template. + +On startup, the script subscribes to topics based on the request type of either CSR or Keys topics, publishes the request to corresponding topic and calls RegisterThing. + +Your IoT Core Thing's [Policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) must provide privileges for this sample to connect, subscribe, publish, and receive. Below is a sample policy that can be used on your IoT Core Thing that will allow this sample to run as intended. + +
+(see sample policy) +
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": "iot:Publish",
+      "Resource": [
+        "arn:aws:iot:region:account:topic/$aws/certificates/create/json",
+        "arn:aws:iot:region:account:topic/$aws/certificates/create-from-csr/json",
+        "arn:aws:iot:region:account:topic/$aws/provisioning-templates/templatename/provision/json"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Receive"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:topic/$aws/certificates/create/json/accepted",
+        "arn:aws:iot:region:account:topic/$aws/certificates/create/json/rejected",
+        "arn:aws:iot:region:account:topic/$aws/certificates/create-from-csr/json/accepted",
+        "arn:aws:iot:region:account:topic/$aws/certificates/create-from-csr/json/rejected",
+        "arn:aws:iot:region:account:topic/$aws/provisioning-templates/templatename/provision/json/accepted",
+        "arn:aws:iot:region:account:topic/$aws/provisioning-templates/templatename/provision/json/rejected"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iot:Subscribe"
+      ],
+      "Resource": [
+        "arn:aws:iot:region:account:topicfilter/$aws/certificates/create/json/accepted",
+        "arn:aws:iot:region:account:topicfilter/$aws/certificates/create/json/rejected",
+        "arn:aws:iot:region:account:topicfilter/$aws/certificates/create-from-csr/json/accepted",
+        "arn:aws:iot:region:account:topicfilter/$aws/certificates/create-from-csr/json/rejected",
+        "arn:aws:iot:region:account:topicfilter/$aws/provisioning-templates/templatename/provision/json/accepted",
+        "arn:aws:iot:region:account:topicfilter/$aws/provisioning-templates/templatename/provision/json/rejected"
+      ]
+    },
+    {
+      "Effect": "Allow",
+      "Action": "iot:Connect",
+      "Resource": "arn:aws:iot:region:account:client/test-*"
+    }
+  ]
+}
+
+ +Replace with the following with the data from your AWS account: +* ``: The AWS IoT Core region where you created your AWS IoT Core thing you wish to use with this sample. For example `us-east-1`. +* ``: Your AWS IoT Core account ID. This is the set of numbers in the top right next to your AWS account name when using the AWS IoT Core website. +* ``: The name of your AWS Fleet Provisioning template you want to use to create new AWS IoT Core Things. + +Note that in a real application, you may want to avoid the use of wildcards in your ClientID or use them selectively. Please follow best practices when working with AWS on production applications using the SDK. Also, for the purposes of this sample, please make sure your policy allows a client ID of `test-*` to connect or use `--client_id ` to send the client ID your policy supports. + +
+ +## How to run + +There are many different ways to run the Fleet Provisioning sample because of how many different ways there are to setup a Fleet Provisioning template in AWS IoT Core. **The easiest and most common way is to run the sample with the following**: + +``` sh +./mqtt5-fleet-provisioning --endpoint --cert --key --template_name