diff --git a/servicetests/tests/ShadowUpdate/CMakeLists.txt b/servicetests/tests/ShadowUpdate/CMakeLists.txt new file mode 100644 index 000000000..3e10518c6 --- /dev/null +++ b/servicetests/tests/ShadowUpdate/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(shadow-update CXX) + +file(GLOB SRC_FILES + "*.cpp" + "../../../samples/utils/CommandLineUtils.cpp" + "../../../samples/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/servicetests/tests/ShadowUpdate/main.cpp b/servicetests/tests/ShadowUpdate/main.cpp new file mode 100644 index 000000000..710d2f8e8 --- /dev/null +++ b/servicetests/tests/ShadowUpdate/main.cpp @@ -0,0 +1,291 @@ +/** + * 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 + +#include "../../../samples/utils/CommandLineUtils.h" + +using namespace Aws::Crt; +using namespace Aws::Iotshadow; + +void changeShadowValue(Aws::Crt::String thingName, String property, String value); +void changeNamedShadowValue(String thingName, String property, String value, String shadowName); + +IotShadowClient *shadowClient = nullptr; +int main(int argc, char *argv[]) +{ + /************************ Setup ****************************/ + 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); + + /** + * In a real world application you probably don't want to enforce synchronous behavior + * but this is a sample console application, so we'll just do that with a condition variable. + */ + std::promise connectionCompletedPromise; + std::promise connectionClosedPromise; + std::shared_ptr connection; + + 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; + } + // Create Mqtt5Client + std::shared_ptr client5; + if (cmdData.input_mqtt_version == 5UL) + { + // Create the MQTT5 builder and populate it with data from cmdData. + // 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)); + } + // Setup lifecycle callbacks + builder->WithClientConnectionSuccessCallback( + [&connectionCompletedPromise](const Mqtt5::OnConnectionSuccessEventData &eventData) { + fprintf( + stdout, + "Mqtt5 Client connection succeed, clientid: %s.\n", + eventData.negotiatedSettings->getClientId().c_str()); + connectionCompletedPromise.set_value(true); + }); + builder->WithClientConnectionFailureCallback([&connectionCompletedPromise]( + const Mqtt5::OnConnectionFailureEventData &eventData) { + fprintf( + stdout, "Mqtt5 Client connection failed with error: %s.\n", aws_error_debug_str(eventData.errorCode)); + connectionCompletedPromise.set_value(false); + }); + builder->WithClientStoppedCallback([&connectionClosedPromise](const Mqtt5::OnStoppedEventData &) { + fprintf(stdout, "Mqtt5 Client stopped.\n"); + connectionClosedPromise.set_value(); + }); + + client5 = builder->Build(); + if (client5 == nullptr) + { + fprintf( + stdout, + "Failed to Init Mqtt5Client with error code %d: %s.\n", + LastError(), + ErrorDebugString(LastError())); + return -1; + } + fprintf(stdout, "Connecting...\n"); + if (!client5->Start()) + { + fprintf(stderr, "MQTT5 Connection failed to start"); + exit(-1); + } + shadowClient = new IotShadowClient(client5); + } + else if (cmdData.input_mqtt_version == 3UL) + { + Aws::Iot::MqttClientConnectionConfigBuilder clientConfigBuilder; + // Create the MQTT builder and populate it with data from cmdData. + clientConfigBuilder = + Aws::Iot::MqttClientConnectionConfigBuilder(cmdData.input_cert.c_str(), cmdData.input_key.c_str()); + clientConfigBuilder.WithEndpoint(cmdData.input_endpoint); + if (cmdData.input_ca != "") + { + clientConfigBuilder.WithCertificateAuthority(cmdData.input_ca.c_str()); + } + + // Create the MQTT connection from the MQTT builder + auto clientConfig = clientConfigBuilder.Build(); + if (!clientConfig) + { + fprintf( + stderr, + "Client Configuration initialization failed with error %s\n", + Aws::Crt::ErrorDebugString(clientConfig.LastError())); + exit(-1); + } + + Aws::Iot::MqttClient client3 = Aws::Iot::MqttClient(); + connection = client3.NewConnection(clientConfig); + if (!*connection) + { + fprintf( + stderr, + "MQTT Connection Creation failed with error %s\n", + Aws::Crt::ErrorDebugString(connection->LastError())); + exit(-1); + } + + // Invoked when a MQTT connect has completed or failed + auto onConnectionCompleted = [&](Mqtt::MqttConnection &, int errorCode, Mqtt::ReturnCode returnCode, bool) { + if (errorCode) + { + fprintf(stdout, "Connection failed with error %s\n", ErrorDebugString(errorCode)); + connectionCompletedPromise.set_value(false); + } + else + { + fprintf(stdout, "Connection completed with return code %d\n", returnCode); + connectionCompletedPromise.set_value(true); + } + }; + + // Invoked when a disconnect has been completed + auto onDisconnect = [&](Mqtt::MqttConnection & /*conn*/) { + { + fprintf(stdout, "Disconnect completed\n"); + connectionClosedPromise.set_value(); + } + }; + + connection->OnConnectionCompleted = std::move(onConnectionCompleted); + connection->OnDisconnect = std::move(onDisconnect); + + fprintf(stdout, "Connecting...\n"); + if (!connection->Connect(cmdData.input_clientId.c_str(), true, 0)) + { + fprintf(stderr, "MQTT Connection failed with error %s\n", ErrorDebugString(connection->LastError())); + exit(-1); + } + shadowClient = new IotShadowClient(connection); + } + else + { + fprintf(stderr, "MQTT Version not supported\n"); + exit(-1); + } + + delete builder; + + /************************ Run the sample ****************************/ + if (connectionCompletedPromise.get_future().get()) + { + if (cmdData.input_shadowName.empty()) + { + changeShadowValue( + cmdData.input_thingName, + cmdData.input_shadowProperty, + cmdData.input_shadowValue); + } + else + { + changeNamedShadowValue( + cmdData.input_thingName, + cmdData.input_shadowProperty, + cmdData.input_shadowValue, + cmdData.input_shadowName); + } + + } + /************************ sample ends ****************************/ + /* Closing down */ + // Wait just a little bit to let the console print + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + if (cmdData.input_mqtt_version == 5UL) + { + // Disconnect + if (client5->Stop() == true) + { + connectionClosedPromise.get_future().wait(); + } + } + else + { // mqtt3 + + // Disconnect + if (connection->Disconnect() == true) + { + connectionClosedPromise.get_future().wait(); + } + } + return 0; +} + +void changeShadowValue(Aws::Crt::String thingName, String property, String value) +{ + JsonObject desired; + JsonObject reported; + desired.WithObject(property, value); + reported.WithObject(property, value); + + UpdateShadowRequest request; + request.ThingName = thingName; + request.State->Reported = reported; + request.State->Desired = desired; + + 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()); + }; + shadowClient->PublishUpdateShadow(request, AWS_MQTT_QOS_AT_LEAST_ONCE, publishCompleted); +} + +void changeNamedShadowValue(String thingName, String property, String value, String shadowName) +{ + JsonObject desired; + JsonObject reported; + desired.WithObject(property, value); + reported.WithObject(property, value); + + UpdateNamedShadowRequest request; + request.ThingName = thingName; + request.State->Reported = reported; + request.State->Desired = desired; + request.ShadowName = shadowName; + + 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()); + }; + shadowClient->PublishUpdateNamedShadow(request, AWS_MQTT_QOS_AT_LEAST_ONCE, publishCompleted); +}