diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5929c138..aff88807 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,7 @@ jobs: run: | python -m pip install boto3 - name: configure AWS credentials (containers) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_BUILD_AND_TEST_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -56,8 +56,24 @@ jobs: run: | python -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder.pyz')" python builder.pyz build -p ${{ env.PACKAGE_NAME }} - - name: configure AWS credentials (PubSub) + - name: configure AWS credentials (MQTT5) uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: ${{ env.CI_MQTT5_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + - name: Service tests + shell: bash + run: | + cd aws-iot-device-sdk-python-v2 + python3 -m pip install . + source utils/test_setup.sh s3://iot-sdk-ci-bucket-us-east1/IotUsProdMqtt5EnvironmentVariables.txt us-east-1 + python3 -m unittest test.test_shadow + python3 -m unittest test.test_jobs + python3 -m unittest test.test_identity + source utils/test_cleanup.sh + cd .. + - name: configure AWS credentials (PubSub) + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_PUBSUB_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -68,7 +84,7 @@ jobs: run: | python ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_windows_cert_connect_cfg.json - name: configure AWS credentials (MQTT5 samples) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_MQTT5_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -76,7 +92,7 @@ jobs: run: | python ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_mqtt5_pubsub_cfg.json - name: configure AWS credentials (Device Advisor) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_DEVICE_ADVISOR }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -103,7 +119,7 @@ jobs: source .venv/bin/activate python3 -m pip install boto3 - name: configure AWS credentials (containers) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_BUILD_AND_TEST_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -113,8 +129,25 @@ jobs: python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" chmod a+x builder ./builder build -p ${{ env.PACKAGE_NAME }} - - name: configure AWS credentials (PubSub) + - name: configure AWS credentials (MQTT5) uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: ${{ env.CI_MQTT5_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + - name: Service tests + shell: bash + run: | + source .venv/bin/activate + cd aws-iot-device-sdk-python-v2 + python3 -m pip install . + source utils/test_setup.sh s3://iot-sdk-ci-bucket-us-east1/IotUsProdMqtt5EnvironmentVariables.txt us-east-1 + python3 -m unittest test.test_shadow + python3 -m unittest test.test_jobs + python3 -m unittest test.test_identity + source utils/test_cleanup.sh + cd .. + - name: configure AWS credentials (PubSub) + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_PUBSUB_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -131,7 +164,7 @@ jobs: source .venv/bin/activate python3 ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_pkcs12_connect_cfg.json - name: configure AWS credentials (MQTT5 samples) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_MQTT5_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -140,7 +173,7 @@ jobs: source .venv/bin/activate python3 ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_mqtt5_pubsub_cfg.json - name: configure AWS credentials (Device Advisor) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_DEVICE_ADVISOR }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -155,11 +188,11 @@ jobs: permissions: id-token: write # This is required for requesting the JWT steps: - - name: Running samples in CI setup + - name: Install AWS SDK for Python run: | - python -m pip install boto3 + python3 -m pip install boto3 - name: configure AWS credentials (containers) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_BUILD_AND_TEST_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -168,11 +201,24 @@ jobs: python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" chmod a+x builder ./builder build -p ${{ env.PACKAGE_NAME }} - - name: Running samples in CI setup + - name: configure AWS credentials (MQTT5) + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: ${{ env.CI_MQTT5_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + - name: Service tests + shell: bash run: | - python3 -m pip install boto3 + cd aws-iot-device-sdk-python-v2 + python3 -m pip install . + source utils/test_setup.sh s3://iot-sdk-ci-bucket-us-east1/IotUsProdMqtt5EnvironmentVariables.txt us-east-1 + python3 -m unittest test.test_shadow + python3 -m unittest test.test_jobs + python3 -m unittest test.test_identity + source utils/test_cleanup.sh + cd .. - name: configure AWS credentials (PubSub) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_PUBSUB_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -180,7 +226,7 @@ jobs: run: | python3 ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_pubsub_cfg.json - name: configure AWS credentials (MQTT5 samples) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_MQTT5_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -188,7 +234,7 @@ jobs: run: | python3 ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_mqtt5_pubsub_cfg.json - name: configure AWS credentials (Device Advisor) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_DEVICE_ADVISOR }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -210,7 +256,7 @@ jobs: sudo apt-get install softhsm -y softhsm2-util --version - name: configure AWS credentials (containers) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_BUILD_AND_TEST_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -221,7 +267,7 @@ jobs: ./builder build -p ${{ env.PACKAGE_NAME }} - name: configure AWS credentials (service tests Fleet Provisioning) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_FLEET_PROVISIONING_ROLE}} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -247,7 +293,7 @@ jobs: python3 ./test_cases/test_fleet_provisioning.py --config-file test_cases/mqtt5_fleet_provisioning_with_csr_cfg.json --thing-name-prefix Fleet_Thing_ - name: configure AWS credentials (service tests Shadow) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_SHADOW_SERVICE_CLIENT_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -273,7 +319,7 @@ jobs: python3 ./test_cases/test_shadow_update.py --config-file test_cases/mqtt3_named_shadow_cfg.json - name: configure AWS credentials (service tests Jobs) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_JOBS_SERVICE_CLIENT_ROLE}} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -289,7 +335,7 @@ jobs: python3 ./test_cases/test_jobs_execution.py --config-file test_cases/mqtt5_jobs_cfg.json - name: configure AWS credentials (Connect and PubSub) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_PUBSUB_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -309,7 +355,7 @@ jobs: echo "directories.tokendir = /tmp/tokens" > /tmp/softhsm2.conf python3 ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_pkcs11_connect_cfg.json - name: configure AWS credentials (Cognito) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_COGNITO_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -317,7 +363,7 @@ jobs: run: | python3 ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_cognito_connect_cfg.json - name: configure AWS credentials (X509) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_X509_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -325,7 +371,7 @@ jobs: run: | python3 ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_x509_connect_cfg.json - name: configure AWS credentials (MQTT5 samples) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_MQTT5_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -348,7 +394,7 @@ jobs: run: | python3 ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_mqtt5_shared_subscription_cfg.json - name: configure AWS credentials (Custom Authorizer) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_CUSTOM_AUTHORIZER_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -356,7 +402,7 @@ jobs: run: | python3 ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_custom_authorizer_connect_cfg.json - name: configure AWS credentials (Shadow) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_SHADOW_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -367,7 +413,7 @@ jobs: run: | python3 ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_mqtt5_shadow_cfg.json - name: configure AWS credentials (Jobs) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_JOBS_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -378,7 +424,7 @@ jobs: run: | python3 ${{ env.CI_UTILS_FOLDER }}/run_sample_ci.py --file ${{ env.CI_SAMPLES_CFG_FOLDER }}/ci_run_mqtt5_jobs_cfg.json - name: configure AWS credentials (Fleet provisioning) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_FLEET_PROVISIONING_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -404,7 +450,7 @@ jobs: run: | python3 -m pip install boto3 - name: configure AWS credentials (containers) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_BUILD_AND_TEST_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} @@ -418,7 +464,7 @@ jobs: python3 -m pip install awsiotsdk python3 -m pip install -U git+https://github.com/aws-greengrass/aws-greengrass-gdk-cli.git@v1.6.2 - name: Configure AWS credentials (Greengrass) - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.CI_GREENGRASS_INSTALLER_ROLE }} aws-region: ${{ env.AWS_DEFAULT_REGION }} diff --git a/.github/workflows/ci_run_fleet_provisioning_cfg.json b/.github/workflows/ci_run_fleet_provisioning_cfg.json index 7068e745..47879668 100644 --- a/.github/workflows/ci_run_fleet_provisioning_cfg.json +++ b/.github/workflows/ci_run_fleet_provisioning_cfg.json @@ -1,6 +1,6 @@ { "language": "Python", - "sample_file": "./aws-iot-device-sdk-python-v2/samples/fleetprovisioning.py", + "sample_file": "./aws-iot-device-sdk-python-v2/samples/deprecated/fleetprovisioning.py", "sample_region": "us-east-1", "sample_main_class": "", "arguments": [ diff --git a/.github/workflows/ci_run_jobs_cfg.json b/.github/workflows/ci_run_jobs_cfg.json index 8df922b2..7e2a6b08 100644 --- a/.github/workflows/ci_run_jobs_cfg.json +++ b/.github/workflows/ci_run_jobs_cfg.json @@ -1,6 +1,6 @@ { "language": "Python", - "sample_file": "./aws-iot-device-sdk-python-v2/samples/jobs.py", + "sample_file": "./aws-iot-device-sdk-python-v2/samples/deprecated/jobs.py", "sample_region": "us-east-1", "sample_main_class": "", "arguments": [ diff --git a/.github/workflows/ci_run_mqtt5_fleet_provisioning_cfg.json b/.github/workflows/ci_run_mqtt5_fleet_provisioning_cfg.json index 9919f29f..48a1dea6 100644 --- a/.github/workflows/ci_run_mqtt5_fleet_provisioning_cfg.json +++ b/.github/workflows/ci_run_mqtt5_fleet_provisioning_cfg.json @@ -1,6 +1,6 @@ { "language": "Python", - "sample_file": "./aws-iot-device-sdk-python-v2/samples/fleetprovisioning_mqtt5.py", + "sample_file": "./aws-iot-device-sdk-python-v2/samples/deprecated/fleetprovisioning_mqtt5.py", "sample_region": "us-east-1", "sample_main_class": "", "arguments": [ diff --git a/.github/workflows/ci_run_mqtt5_jobs_cfg.json b/.github/workflows/ci_run_mqtt5_jobs_cfg.json index 6c0249e0..ccb5413d 100644 --- a/.github/workflows/ci_run_mqtt5_jobs_cfg.json +++ b/.github/workflows/ci_run_mqtt5_jobs_cfg.json @@ -1,6 +1,6 @@ { "language": "Python", - "sample_file": "./aws-iot-device-sdk-python-v2/samples/jobs_mqtt5.py", + "sample_file": "./aws-iot-device-sdk-python-v2/samples/deprecated/jobs_mqtt5.py", "sample_region": "us-east-1", "sample_main_class": "", "arguments": [ diff --git a/.github/workflows/ci_run_mqtt5_shadow_cfg.json b/.github/workflows/ci_run_mqtt5_shadow_cfg.json index 8342d464..c3cb6605 100644 --- a/.github/workflows/ci_run_mqtt5_shadow_cfg.json +++ b/.github/workflows/ci_run_mqtt5_shadow_cfg.json @@ -1,6 +1,6 @@ { "language": "Python", - "sample_file": "./aws-iot-device-sdk-python-v2/samples/shadow_mqtt5.py", + "sample_file": "./aws-iot-device-sdk-python-v2/samples/deprecated/shadow_mqtt5.py", "sample_region": "us-east-1", "sample_main_class": "", "arguments": [ diff --git a/.github/workflows/ci_run_shadow_cfg.json b/.github/workflows/ci_run_shadow_cfg.json index 01fd4824..d2a4174c 100644 --- a/.github/workflows/ci_run_shadow_cfg.json +++ b/.github/workflows/ci_run_shadow_cfg.json @@ -1,6 +1,6 @@ { "language": "Python", - "sample_file": "./aws-iot-device-sdk-python-v2/samples/shadow.py", + "sample_file": "./aws-iot-device-sdk-python-v2/samples/deprecated/shadow.py", "sample_region": "us-east-1", "sample_main_class": "", "arguments": [ diff --git a/awsiot/__init__.py b/awsiot/__init__.py index 8cd0f3f7..0bd6d472 100644 --- a/awsiot/__init__.py +++ b/awsiot/__init__.py @@ -9,12 +9,17 @@ 'greengrass_discovery', 'mqtt_connection_builder', 'mqtt5_client_builder', + 'V2ServiceException', + 'V2DeserializationFailure', + 'ServiceStreamOptions' ] -from awscrt import mqtt, mqtt5 +from awscrt import mqtt, mqtt5, mqtt_request_response from concurrent.futures import Future +from dataclasses import dataclass import json -from typing import Any, Callable, Dict, Optional, Tuple, TypeVar +from typing import Any, Callable, Dict, Generic, Optional, Tuple, TypeVar + __version__ = '1.0.0-dev' @@ -192,3 +197,78 @@ def __repr__(self): self.__class__.__module__, self.__class__.__name__, ', '.join(properties)) + + +class V2ServiceException(Exception): + + def __init__(self, message: str, inner_error: 'Optional[Exception]', modeled_error: 'Optional[Any]'): + self.message = message + self.inner_error = inner_error + self.modeled_error = modeled_error + +def create_v2_service_modeled_future(internal_unmodeled_future : Future, operation_name : str, accepted_topic : str, response_class, modeled_error_class): + modeled_future = Future() + + # force a strong ref to the hidden/internal unmodeled future so that it can't be GCed prior to completion + modeled_future.unmodeled_future = internal_unmodeled_future + + def complete_modeled_future(unmodeled_future): + if unmodeled_future.exception(): + service_error = V2ServiceException(f"{operation_name} failure", unmodeled_future.exception(), None) + modeled_future.set_exception(service_error) + else: + unmodeled_result = unmodeled_future.result() + try: + payload_as_json = json.loads(unmodeled_result.payload.decode()) + if unmodeled_result.topic == accepted_topic: + modeled_future.set_result(response_class.from_payload(payload_as_json)) + else: + modeled_error = modeled_error_class.from_payload(payload_as_json) + modeled_future.set_exception(V2ServiceException(f"{operation_name} failure", None, modeled_error)) + except Exception as e: + modeled_future.set_exception(V2ServiceException(f"{operation_name} failure", e, None)) + + internal_unmodeled_future.add_done_callback(lambda f: complete_modeled_future(f)) + + return modeled_future + +class V2DeserializationFailure(Exception): + def __init__(self, message: str, inner_error: 'Optional[Exception]', payload: Optional[bytes]): + self.message = message + self.inner_error = inner_error + self.payload = payload + +@dataclass +class ServiceStreamOptions(Generic[T]): + """ + Configuration options for an MQTT-based service streaming operation. + + Args: + incoming_event_listener (Callable[[T], None]): function object to invoke when a stream message is successfully deserialized + subscription_status_listener (Optional[awscrt.mqtt_request_response.SubscriptionStatusListener]): function object to invoke when the operation's subscription status changes + deserialization_failure_listener (Optional[Callable[[V2DeserializationFailure], None]]): function object to invoke when a publish is received on the streaming subscription that cannot be deserialized into the stream's output type. Should never happen. + """ + incoming_event_listener: 'Callable[[T], None]' + subscription_status_listener: 'Optional[mqtt_request_response.SubscriptionStatusListener]' = None + deserialization_failure_listener: 'Optional[Callable[[V2DeserializationFailure], None]]' = None + + def _validate(self): + """ + Stringently type-checks an instance's field values. + """ + assert callable(self.incoming_event_listener) + assert callable(self.subscription_status_listener) or self.subscription_status_listener is None + assert callable(self.deserialization_failure_listener) or self.deserialization_failure_listener is None + +def create_streaming_unmodeled_options(stream_options: ServiceStreamOptions[T], subscription_topic: str, event_name: str, event_class): + def modeled_event_callback(unmodeled_event : mqtt_request_response.IncomingPublishEvent): + try: + payload_as_json = json.loads(unmodeled_event.payload.decode()) + modeled_event = event_class.from_payload(payload_as_json) + stream_options.incoming_event_listener(modeled_event) + except Exception as e: + if stream_options.deserialization_failure_listener is not None: + failure_event = V2DeserializationFailure(f"{event_name} stream deserialization failure", e, unmodeled_event.payload) + stream_options.deserialization_failure_listener(failure_event) + + return mqtt_request_response.StreamingOperationOptions(subscription_topic, stream_options.subscription_status_listener, modeled_event_callback) \ No newline at end of file diff --git a/awsiot/iotidentity.py b/awsiot/iotidentity.py index 3b79b7a3..c60b69f9 100644 --- a/awsiot/iotidentity.py +++ b/awsiot/iotidentity.py @@ -3,8 +3,10 @@ # This file is generated +import awscrt import awsiot import concurrent.futures +import json import typing class IotIdentityClient(awsiot.MqttServiceClient): @@ -33,6 +35,7 @@ def publish_create_certificate_from_csr(self, request, qos): request is successfully published. The Future's result will be an exception if the request cannot be published. """ + request._validate() return self._publish_operation( topic='$aws/certificates/create-from-csr/json', @@ -56,11 +59,12 @@ def publish_create_keys_and_certificate(self, request, qos): request is successfully published. The Future's result will be an exception if the request cannot be published. """ + request._validate() return self._publish_operation( topic='$aws/certificates/create/json', qos=qos, - payload=None) + payload=request.to_payload()) def publish_register_thing(self, request, qos): # type: (RegisterThingRequest, int) -> concurrent.futures.Future @@ -79,8 +83,7 @@ def publish_register_thing(self, request, qos): request is successfully published. The Future's result will be an exception if the request cannot be published. """ - if not request.template_name: - raise ValueError("request.template_name is required") + request._validate() return self._publish_operation( topic='$aws/provisioning-templates/{0.template_name}/provision/json'.format(request), @@ -109,6 +112,7 @@ def subscribe_to_create_certificate_from_csr_accepted(self, request, qos, callba to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -141,6 +145,7 @@ def subscribe_to_create_certificate_from_csr_rejected(self, request, qos, callba to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -173,6 +178,7 @@ def subscribe_to_create_keys_and_certificate_accepted(self, request, qos, callba to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -205,6 +211,7 @@ def subscribe_to_create_keys_and_certificate_rejected(self, request, qos, callba to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -237,8 +244,7 @@ def subscribe_to_register_thing_accepted(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.template_name: - raise ValueError("request.template_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -271,8 +277,7 @@ def subscribe_to_register_thing_rejected(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.template_name: - raise ValueError("request.template_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -313,6 +318,9 @@ def to_payload(self): payload['certificateSigningRequest'] = self.certificate_signing_request return payload + def _validate(self): + return + class CreateCertificateFromCsrResponse(awsiot.ModeledClass): """ @@ -357,6 +365,9 @@ def from_payload(cls, payload): new.certificate_pem = val return new + def _validate(self): + return + class CreateCertificateFromCsrSubscriptionRequest(awsiot.ModeledClass): """ @@ -373,6 +384,9 @@ def __init__(self, *args, **kwargs): for key, val in zip([], args): setattr(self, key, val) + def _validate(self): + return + class CreateKeysAndCertificateRequest(awsiot.ModeledClass): """ @@ -389,6 +403,14 @@ def __init__(self, *args, **kwargs): for key, val in zip([], args): setattr(self, key, val) + def to_payload(self): + # type: () -> typing.Dict[str, typing.Any] + payload = {} # type: typing.Dict[str, typing.Any] + return payload + + def _validate(self): + return + class CreateKeysAndCertificateResponse(awsiot.ModeledClass): """ @@ -439,6 +461,9 @@ def from_payload(cls, payload): new.private_key = val return new + def _validate(self): + return + class CreateKeysAndCertificateSubscriptionRequest(awsiot.ModeledClass): """ @@ -455,6 +480,9 @@ def __init__(self, *args, **kwargs): for key, val in zip([], args): setattr(self, key, val) + def _validate(self): + return + class ErrorResponse(awsiot.ModeledClass): """ @@ -499,6 +527,9 @@ def from_payload(cls, payload): new.status_code = val return new + def _validate(self): + return + class RegisterThingRequest(awsiot.ModeledClass): """ @@ -537,6 +568,11 @@ def to_payload(self): payload['parameters'] = self.parameters return payload + def _validate(self): + if not self.template_name: + raise ValueError("template_name is required") + return + class RegisterThingResponse(awsiot.ModeledClass): """ @@ -575,6 +611,9 @@ def from_payload(cls, payload): new.thing_name = val return new + def _validate(self): + return + class RegisterThingSubscriptionRequest(awsiot.ModeledClass): """ @@ -598,3 +637,196 @@ def __init__(self, *args, **kwargs): for key, val in zip(['template_name'], args): setattr(self, key, val) + def _validate(self): + if not self.template_name: + raise ValueError("template_name is required") + return + +class V2ErrorResponse(awsiot.ModeledClass): + """ + + Response document containing details about a failed request. + + All attributes are None by default, and may be set by keyword in the constructor. + + Keyword Args: + error_code (str): Response error code + error_message (str): Response error message + status_code (int): Response status code + + Attributes: + error_code (str): Response error code + error_message (str): Response error message + status_code (int): Response status code + """ + + __slots__ = ['error_code', 'error_message', 'status_code'] + + def __init__(self, *args, **kwargs): + self.error_code = kwargs.get('error_code') + self.error_message = kwargs.get('error_message') + self.status_code = kwargs.get('status_code') + + # for backwards compatibility, read any arguments that used to be accepted by position + for key, val in zip([], args): + setattr(self, key, val) + + @classmethod + def from_payload(cls, payload): + # type: (typing.Dict[str, typing.Any]) -> V2ErrorResponse + new = cls() + val = payload.get('errorCode') + if val is not None: + new.error_code = val + val = payload.get('errorMessage') + if val is not None: + new.error_message = val + val = payload.get('statusCode') + if val is not None: + new.status_code = val + return new + + def _validate(self): + return + +class IotIdentityClientV2: + """ + + An AWS IoT service that assists with provisioning a device and installing unique client certificates on it + + AWS Docs: https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html + + """ + + def __init__(self, protocol_client: awscrt.mqtt.Connection or awscrt.mqtt5.Client, options: awscrt.mqtt_request_response.ClientOptions): + self._rr_client = awscrt.mqtt_request_response.Client(protocol_client, options) + + def create_certificate_from_csr(self, request : CreateCertificateFromCsrRequest) -> concurrent.futures.Future : + """ + + Creates a certificate from a certificate signing request (CSR). AWS IoT provides client certificates that are signed by the Amazon Root certificate authority (CA). The new certificate has a PENDING_ACTIVATION status. When you call RegisterThing to provision a thing with this certificate, the certificate status changes to ACTIVE or INACTIVE as described in the template. + + API Docs: https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html#fleet-provision-api + + Args: + request: `CreateCertificateFromCsrRequest` instance. + + Returns: + A Future whose result will be an instance of `CreateCertificateFromCsrResponse`. + """ + request._validate() + + publish_topic = '$aws/certificates/create-from-csr/json' + accepted_topic = publish_topic + "/accepted"; + rejected_topic = publish_topic + "/rejected"; + + subscription0 = '$aws/certificates/create-from-csr/json/accepted'; + subscription1 = '$aws/certificates/create-from-csr/json/rejected'; + + request_options = awscrt.mqtt_request_response.RequestOptions( + subscription_topic_filters = [ + subscription0, + subscription1, + ], + response_paths = [ + awscrt.mqtt_request_response.ResponsePath( + accepted_topic, + ), + awscrt.mqtt_request_response.ResponsePath( + rejected_topic, + ) + ], + publish_topic = publish_topic, + payload = json.dumps(request.to_payload()).encode(), + ) + + internal_unmodeled_future = self._rr_client.make_request(request_options) + + return awsiot.create_v2_service_modeled_future(internal_unmodeled_future, "create_certificate_from_csr", accepted_topic, CreateCertificateFromCsrResponse, V2ErrorResponse) + + def create_keys_and_certificate(self, request : CreateKeysAndCertificateRequest) -> concurrent.futures.Future : + """ + + Creates new keys and a certificate. AWS IoT provides client certificates that are signed by the Amazon Root certificate authority (CA). The new certificate has a PENDING_ACTIVATION status. When you call RegisterThing to provision a thing with this certificate, the certificate status changes to ACTIVE or INACTIVE as described in the template. + + API Docs: https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html#fleet-provision-api + + Args: + request: `CreateKeysAndCertificateRequest` instance. + + Returns: + A Future whose result will be an instance of `CreateKeysAndCertificateResponse`. + """ + request._validate() + + publish_topic = '$aws/certificates/create/json' + accepted_topic = publish_topic + "/accepted"; + rejected_topic = publish_topic + "/rejected"; + + subscription0 = '$aws/certificates/create/json/accepted'; + subscription1 = '$aws/certificates/create/json/rejected'; + + request_options = awscrt.mqtt_request_response.RequestOptions( + subscription_topic_filters = [ + subscription0, + subscription1, + ], + response_paths = [ + awscrt.mqtt_request_response.ResponsePath( + accepted_topic, + ), + awscrt.mqtt_request_response.ResponsePath( + rejected_topic, + ) + ], + publish_topic = publish_topic, + payload = json.dumps(request.to_payload()).encode(), + ) + + internal_unmodeled_future = self._rr_client.make_request(request_options) + + return awsiot.create_v2_service_modeled_future(internal_unmodeled_future, "create_keys_and_certificate", accepted_topic, CreateKeysAndCertificateResponse, V2ErrorResponse) + + def register_thing(self, request : RegisterThingRequest) -> concurrent.futures.Future : + """ + + Provisions an AWS IoT thing using a pre-defined template. + + API Docs: https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html#fleet-provision-api + + Args: + request: `RegisterThingRequest` instance. + + Returns: + A Future whose result will be an instance of `RegisterThingResponse`. + """ + request._validate() + + publish_topic = '$aws/provisioning-templates/{0.template_name}/provision/json'.format(request) + accepted_topic = publish_topic + "/accepted"; + rejected_topic = publish_topic + "/rejected"; + + subscription0 = '$aws/provisioning-templates/{0.template_name}/provision/json/accepted'.format(request); + subscription1 = '$aws/provisioning-templates/{0.template_name}/provision/json/rejected'.format(request); + + request_options = awscrt.mqtt_request_response.RequestOptions( + subscription_topic_filters = [ + subscription0, + subscription1, + ], + response_paths = [ + awscrt.mqtt_request_response.ResponsePath( + accepted_topic, + ), + awscrt.mqtt_request_response.ResponsePath( + rejected_topic, + ) + ], + publish_topic = publish_topic, + payload = json.dumps(request.to_payload()).encode(), + ) + + internal_unmodeled_future = self._rr_client.make_request(request_options) + + return awsiot.create_v2_service_modeled_future(internal_unmodeled_future, "register_thing", accepted_topic, RegisterThingResponse, V2ErrorResponse) + diff --git a/awsiot/iotjobs.py b/awsiot/iotjobs.py index 2c16eb91..011bfb4e 100644 --- a/awsiot/iotjobs.py +++ b/awsiot/iotjobs.py @@ -3,10 +3,13 @@ # This file is generated +import awscrt import awsiot import concurrent.futures import datetime +import json import typing +import uuid class IotJobsClient(awsiot.MqttServiceClient): """ @@ -34,10 +37,7 @@ def publish_describe_job_execution(self, request, qos): request is successfully published. The Future's result will be an exception if the request cannot be published. """ - if not request.job_id: - raise ValueError("request.job_id is required") - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() return self._publish_operation( topic='$aws/things/{0.thing_name}/jobs/{0.job_id}/get'.format(request), @@ -61,8 +61,7 @@ def publish_get_pending_job_executions(self, request, qos): request is successfully published. The Future's result will be an exception if the request cannot be published. """ - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() return self._publish_operation( topic='$aws/things/{0.thing_name}/jobs/get'.format(request), @@ -86,8 +85,7 @@ def publish_start_next_pending_job_execution(self, request, qos): request is successfully published. The Future's result will be an exception if the request cannot be published. """ - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() return self._publish_operation( topic='$aws/things/{0.thing_name}/jobs/start-next'.format(request), @@ -111,10 +109,7 @@ def publish_update_job_execution(self, request, qos): request is successfully published. The Future's result will be an exception if the request cannot be published. """ - if not request.job_id: - raise ValueError("request.job_id is required") - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() return self._publish_operation( topic='$aws/things/{0.thing_name}/jobs/{0.job_id}/update'.format(request), @@ -143,10 +138,7 @@ def subscribe_to_describe_job_execution_accepted(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.job_id: - raise ValueError("request.job_id is required") - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -179,10 +171,7 @@ def subscribe_to_describe_job_execution_rejected(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.job_id: - raise ValueError("request.job_id is required") - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -215,8 +204,7 @@ def subscribe_to_get_pending_job_executions_accepted(self, request, qos, callbac to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -249,8 +237,7 @@ def subscribe_to_get_pending_job_executions_rejected(self, request, qos, callbac to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -283,8 +270,7 @@ def subscribe_to_job_executions_changed_events(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -315,8 +301,7 @@ def subscribe_to_next_job_execution_changed_events(self, request, qos, callback) to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -349,8 +334,7 @@ def subscribe_to_start_next_pending_job_execution_accepted(self, request, qos, c to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -383,8 +367,7 @@ def subscribe_to_start_next_pending_job_execution_rejected(self, request, qos, c to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -417,10 +400,7 @@ def subscribe_to_update_job_execution_accepted(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.job_id: - raise ValueError("request.job_id is required") - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -453,10 +433,7 @@ def subscribe_to_update_job_execution_rejected(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.job_id: - raise ValueError("request.job_id is required") - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -513,6 +490,13 @@ def to_payload(self): payload['includeJobDocument'] = self.include_job_document return payload + def _validate(self): + if not self.job_id: + raise ValueError("job_id is required") + if not self.thing_name: + raise ValueError("thing_name is required") + return + class DescribeJobExecutionResponse(awsiot.ModeledClass): """ @@ -557,6 +541,9 @@ def from_payload(cls, payload): new.timestamp = datetime.datetime.fromtimestamp(val) return new + def _validate(self): + return + class DescribeJobExecutionSubscriptionRequest(awsiot.ModeledClass): """ @@ -583,6 +570,13 @@ def __init__(self, *args, **kwargs): for key, val in zip(['job_id', 'thing_name'], args): setattr(self, key, val) + def _validate(self): + if not self.job_id: + raise ValueError("job_id is required") + if not self.thing_name: + raise ValueError("thing_name is required") + return + class GetPendingJobExecutionsRequest(awsiot.ModeledClass): """ @@ -616,6 +610,11 @@ def to_payload(self): payload['clientToken'] = self.client_token return payload + def _validate(self): + if not self.thing_name: + raise ValueError("thing_name is required") + return + class GetPendingJobExecutionsResponse(awsiot.ModeledClass): """ @@ -666,6 +665,9 @@ def from_payload(cls, payload): new.timestamp = datetime.datetime.fromtimestamp(val) return new + def _validate(self): + return + class GetPendingJobExecutionsSubscriptionRequest(awsiot.ModeledClass): """ @@ -689,6 +691,11 @@ def __init__(self, *args, **kwargs): for key, val in zip(['thing_name'], args): setattr(self, key, val) + def _validate(self): + if not self.thing_name: + raise ValueError("thing_name is required") + return + class JobExecutionData(awsiot.ModeledClass): """ @@ -775,6 +782,9 @@ def from_payload(cls, payload): new.version_number = val return new + def _validate(self): + return + class JobExecutionState(awsiot.ModeledClass): """ @@ -819,6 +829,9 @@ def from_payload(cls, payload): new.version_number = val return new + def _validate(self): + return + class JobExecutionSummary(awsiot.ModeledClass): """ @@ -881,6 +894,9 @@ def from_payload(cls, payload): new.version_number = val return new + def _validate(self): + return + class JobExecutionsChangedEvent(awsiot.ModeledClass): """ @@ -919,6 +935,9 @@ def from_payload(cls, payload): new.timestamp = datetime.datetime.fromtimestamp(val) return new + def _validate(self): + return + class JobExecutionsChangedSubscriptionRequest(awsiot.ModeledClass): """ @@ -942,6 +961,16 @@ def __init__(self, *args, **kwargs): for key, val in zip(['thing_name'], args): setattr(self, key, val) + def to_payload(self): + # type: () -> typing.Dict[str, typing.Any] + payload = {} # type: typing.Dict[str, typing.Any] + return payload + + def _validate(self): + if not self.thing_name: + raise ValueError("thing_name is required") + return + class NextJobExecutionChangedEvent(awsiot.ModeledClass): """ @@ -980,6 +1009,9 @@ def from_payload(cls, payload): new.timestamp = datetime.datetime.fromtimestamp(val) return new + def _validate(self): + return + class NextJobExecutionChangedSubscriptionRequest(awsiot.ModeledClass): """ @@ -1003,6 +1035,16 @@ def __init__(self, *args, **kwargs): for key, val in zip(['thing_name'], args): setattr(self, key, val) + def to_payload(self): + # type: () -> typing.Dict[str, typing.Any] + payload = {} # type: typing.Dict[str, typing.Any] + return payload + + def _validate(self): + if not self.thing_name: + raise ValueError("thing_name is required") + return + class RejectedError(awsiot.ModeledClass): """ @@ -1059,6 +1101,9 @@ def from_payload(cls, payload): new.timestamp = datetime.datetime.fromtimestamp(val) return new + def _validate(self): + return + class StartNextJobExecutionResponse(awsiot.ModeledClass): """ @@ -1103,6 +1148,9 @@ def from_payload(cls, payload): new.timestamp = datetime.datetime.fromtimestamp(val) return new + def _validate(self): + return + class StartNextPendingJobExecutionRequest(awsiot.ModeledClass): """ @@ -1146,6 +1194,11 @@ def to_payload(self): payload['stepTimeoutInMinutes'] = self.step_timeout_in_minutes return payload + def _validate(self): + if not self.thing_name: + raise ValueError("thing_name is required") + return + class StartNextPendingJobExecutionSubscriptionRequest(awsiot.ModeledClass): """ @@ -1169,6 +1222,11 @@ def __init__(self, *args, **kwargs): for key, val in zip(['thing_name'], args): setattr(self, key, val) + def _validate(self): + if not self.thing_name: + raise ValueError("thing_name is required") + return + class UpdateJobExecutionRequest(awsiot.ModeledClass): """ @@ -1240,6 +1298,13 @@ def to_payload(self): payload['stepTimeoutInMinutes'] = self.step_timeout_in_minutes return payload + def _validate(self): + if not self.job_id: + raise ValueError("job_id is required") + if not self.thing_name: + raise ValueError("thing_name is required") + return + class UpdateJobExecutionResponse(awsiot.ModeledClass): """ @@ -1290,6 +1355,9 @@ def from_payload(cls, payload): new.timestamp = datetime.datetime.fromtimestamp(val) return new + def _validate(self): + return + class UpdateJobExecutionSubscriptionRequest(awsiot.ModeledClass): """ @@ -1316,6 +1384,72 @@ def __init__(self, *args, **kwargs): for key, val in zip(['job_id', 'thing_name'], args): setattr(self, key, val) + def _validate(self): + if not self.job_id: + raise ValueError("job_id is required") + if not self.thing_name: + raise ValueError("thing_name is required") + return + +class V2ErrorResponse(awsiot.ModeledClass): + """ + + Response document containing details about a failed request. + + All attributes are None by default, and may be set by keyword in the constructor. + + Keyword Args: + client_token (str): Opaque token that can correlate this response to the original request. + code (str): Indicates the type of error. + execution_state (JobExecutionState): A JobExecutionState object. This field is included only when the code field has the value InvalidStateTransition or VersionMismatch. + message (str): A text message that provides additional information. + timestamp (datetime.datetime): The date and time the response was generated by AWS IoT. + + Attributes: + client_token (str): Opaque token that can correlate this response to the original request. + code (str): Indicates the type of error. + execution_state (JobExecutionState): A JobExecutionState object. This field is included only when the code field has the value InvalidStateTransition or VersionMismatch. + message (str): A text message that provides additional information. + timestamp (datetime.datetime): The date and time the response was generated by AWS IoT. + """ + + __slots__ = ['client_token', 'code', 'execution_state', 'message', 'timestamp'] + + def __init__(self, *args, **kwargs): + self.client_token = kwargs.get('client_token') + self.code = kwargs.get('code') + self.execution_state = kwargs.get('execution_state') + self.message = kwargs.get('message') + self.timestamp = kwargs.get('timestamp') + + # for backwards compatibility, read any arguments that used to be accepted by position + for key, val in zip([], args): + setattr(self, key, val) + + @classmethod + def from_payload(cls, payload): + # type: (typing.Dict[str, typing.Any]) -> V2ErrorResponse + new = cls() + val = payload.get('clientToken') + if val is not None: + new.client_token = val + val = payload.get('code') + if val is not None: + new.code = val + val = payload.get('executionState') + if val is not None: + new.execution_state = JobExecutionState.from_payload(val) + val = payload.get('message') + if val is not None: + new.message = val + val = payload.get('timestamp') + if val is not None: + new.timestamp = datetime.datetime.fromtimestamp(val) + return new + + def _validate(self): + return + class JobStatus: """ @@ -1407,3 +1541,247 @@ class RejectedErrorCode: Occurs when a command to describe a job is performed on a job that is in a terminal state. """ +class IotJobsClientV2: + """ + + The AWS IoT jobs service can be used to define a set of remote operations that are sent to and executed on one or more devices connected to AWS IoT. + + AWS Docs: https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#jobs-mqtt-api + + """ + + def __init__(self, protocol_client: awscrt.mqtt.Connection or awscrt.mqtt5.Client, options: awscrt.mqtt_request_response.ClientOptions): + self._rr_client = awscrt.mqtt_request_response.Client(protocol_client, options) + + def describe_job_execution(self, request : DescribeJobExecutionRequest) -> concurrent.futures.Future : + """ + + Gets detailed information about a job execution. + + API Docs: https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-describejobexecution + + Args: + request: `DescribeJobExecutionRequest` instance. + + Returns: + A Future whose result will be an instance of `DescribeJobExecutionResponse`. + """ + request._validate() + + publish_topic = '$aws/things/{0.thing_name}/jobs/{0.job_id}/get'.format(request) + accepted_topic = publish_topic + "/accepted"; + rejected_topic = publish_topic + "/rejected"; + + subscription0 = '$aws/things/{0.thing_name}/jobs/{0.job_id}/get/+'.format(request); + + correlation_token = str(uuid.uuid4()) + request.client_token = correlation_token + + request_options = awscrt.mqtt_request_response.RequestOptions( + subscription_topic_filters = [ + subscription0, + ], + response_paths = [ + awscrt.mqtt_request_response.ResponsePath( + accepted_topic, + "clientToken" + ), + awscrt.mqtt_request_response.ResponsePath( + rejected_topic, + "clientToken" + ) + ], + publish_topic = publish_topic, + payload = json.dumps(request.to_payload()).encode(), + correlation_token = correlation_token, + ) + + internal_unmodeled_future = self._rr_client.make_request(request_options) + + return awsiot.create_v2_service_modeled_future(internal_unmodeled_future, "describe_job_execution", accepted_topic, DescribeJobExecutionResponse, V2ErrorResponse) + + def get_pending_job_executions(self, request : GetPendingJobExecutionsRequest) -> concurrent.futures.Future : + """ + + Gets the list of all jobs for a thing that are not in a terminal state. + + API Docs: https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-getpendingjobexecutions + + Args: + request: `GetPendingJobExecutionsRequest` instance. + + Returns: + A Future whose result will be an instance of `GetPendingJobExecutionsResponse`. + """ + request._validate() + + publish_topic = '$aws/things/{0.thing_name}/jobs/get'.format(request) + accepted_topic = publish_topic + "/accepted"; + rejected_topic = publish_topic + "/rejected"; + + subscription0 = '$aws/things/{0.thing_name}/jobs/get/+'.format(request); + + correlation_token = str(uuid.uuid4()) + request.client_token = correlation_token + + request_options = awscrt.mqtt_request_response.RequestOptions( + subscription_topic_filters = [ + subscription0, + ], + response_paths = [ + awscrt.mqtt_request_response.ResponsePath( + accepted_topic, + "clientToken" + ), + awscrt.mqtt_request_response.ResponsePath( + rejected_topic, + "clientToken" + ) + ], + publish_topic = publish_topic, + payload = json.dumps(request.to_payload()).encode(), + correlation_token = correlation_token, + ) + + internal_unmodeled_future = self._rr_client.make_request(request_options) + + return awsiot.create_v2_service_modeled_future(internal_unmodeled_future, "get_pending_job_executions", accepted_topic, GetPendingJobExecutionsResponse, V2ErrorResponse) + + def start_next_pending_job_execution(self, request : StartNextPendingJobExecutionRequest) -> concurrent.futures.Future : + """ + + Gets and starts the next pending job execution for a thing (status IN_PROGRESS or QUEUED). + + API Docs: https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-startnextpendingjobexecution + + Args: + request: `StartNextPendingJobExecutionRequest` instance. + + Returns: + A Future whose result will be an instance of `StartNextJobExecutionResponse`. + """ + request._validate() + + publish_topic = '$aws/things/{0.thing_name}/jobs/start-next'.format(request) + accepted_topic = publish_topic + "/accepted"; + rejected_topic = publish_topic + "/rejected"; + + subscription0 = '$aws/things/{0.thing_name}/jobs/start-next/+'.format(request); + + correlation_token = str(uuid.uuid4()) + request.client_token = correlation_token + + request_options = awscrt.mqtt_request_response.RequestOptions( + subscription_topic_filters = [ + subscription0, + ], + response_paths = [ + awscrt.mqtt_request_response.ResponsePath( + accepted_topic, + "clientToken" + ), + awscrt.mqtt_request_response.ResponsePath( + rejected_topic, + "clientToken" + ) + ], + publish_topic = publish_topic, + payload = json.dumps(request.to_payload()).encode(), + correlation_token = correlation_token, + ) + + internal_unmodeled_future = self._rr_client.make_request(request_options) + + return awsiot.create_v2_service_modeled_future(internal_unmodeled_future, "start_next_pending_job_execution", accepted_topic, StartNextJobExecutionResponse, V2ErrorResponse) + + def update_job_execution(self, request : UpdateJobExecutionRequest) -> concurrent.futures.Future : + """ + + Updates the status of a job execution. You can optionally create a step timer by setting a value for the stepTimeoutInMinutes property. If you don't update the value of this property by running UpdateJobExecution again, the job execution times out when the step timer expires. + + API Docs: https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-updatejobexecution + + Args: + request: `UpdateJobExecutionRequest` instance. + + Returns: + A Future whose result will be an instance of `UpdateJobExecutionResponse`. + """ + request._validate() + + publish_topic = '$aws/things/{0.thing_name}/jobs/{0.job_id}/update'.format(request) + accepted_topic = publish_topic + "/accepted"; + rejected_topic = publish_topic + "/rejected"; + + subscription0 = '$aws/things/{0.thing_name}/jobs/{0.job_id}/update/+'.format(request); + + correlation_token = str(uuid.uuid4()) + request.client_token = correlation_token + + request_options = awscrt.mqtt_request_response.RequestOptions( + subscription_topic_filters = [ + subscription0, + ], + response_paths = [ + awscrt.mqtt_request_response.ResponsePath( + accepted_topic, + "clientToken" + ), + awscrt.mqtt_request_response.ResponsePath( + rejected_topic, + "clientToken" + ) + ], + publish_topic = publish_topic, + payload = json.dumps(request.to_payload()).encode(), + correlation_token = correlation_token, + ) + + internal_unmodeled_future = self._rr_client.make_request(request_options) + + return awsiot.create_v2_service_modeled_future(internal_unmodeled_future, "update_job_execution", accepted_topic, UpdateJobExecutionResponse, V2ErrorResponse) + + def create_job_executions_changed_stream(self, request : JobExecutionsChangedSubscriptionRequest, options: awsiot.ServiceStreamOptions[JobExecutionsChangedEvent]): + """ + + Creates a stream of JobExecutionsChanged notifications for a given IoT thing. + + API Docs: https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-jobexecutionschanged + + Args: + request: `JobExecutionsChangedSubscriptionRequest` instance. + options: callbacks to invoke for streaming operation events + + Returns: + An instance of `awscrt.mqtt_request_response.StreamingOperation` + """ + request._validate() + options._validate() + + subscription_topic_filter = '$aws/things/{0.thing_name}/jobs/notify'.format(request) + + unmodeled_options = awsiot.create_streaming_unmodeled_options(options, subscription_topic_filter, "JobExecutionsChangedEvent", JobExecutionsChangedEvent) + + return self._rr_client.create_stream(unmodeled_options) + + def create_next_job_execution_changed_stream(self, request : NextJobExecutionChangedSubscriptionRequest, options: awsiot.ServiceStreamOptions[NextJobExecutionChangedEvent]): + """ + + API Docs: https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-nextjobexecutionchanged + + Args: + request: `NextJobExecutionChangedSubscriptionRequest` instance. + options: callbacks to invoke for streaming operation events + + Returns: + An instance of `awscrt.mqtt_request_response.StreamingOperation` + """ + request._validate() + options._validate() + + subscription_topic_filter = '$aws/things/{0.thing_name}/jobs/notify-next'.format(request) + + unmodeled_options = awsiot.create_streaming_unmodeled_options(options, subscription_topic_filter, "NextJobExecutionChangedEvent", NextJobExecutionChangedEvent) + + return self._rr_client.create_stream(unmodeled_options) + diff --git a/awsiot/iotshadow.py b/awsiot/iotshadow.py index d33888eb..7d7a850f 100644 --- a/awsiot/iotshadow.py +++ b/awsiot/iotshadow.py @@ -3,10 +3,13 @@ # This file is generated +import awscrt import awsiot import concurrent.futures import datetime +import json import typing +import uuid class IotShadowClient(awsiot.MqttServiceClient): """ @@ -34,10 +37,7 @@ def publish_delete_named_shadow(self, request, qos): request is successfully published. The Future's result will be an exception if the request cannot be published. """ - if not request.shadow_name: - raise ValueError("request.shadow_name is required") - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() return self._publish_operation( topic='$aws/things/{0.thing_name}/shadow/name/{0.shadow_name}/delete'.format(request), @@ -61,8 +61,7 @@ def publish_delete_shadow(self, request, qos): request is successfully published. The Future's result will be an exception if the request cannot be published. """ - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() return self._publish_operation( topic='$aws/things/{0.thing_name}/shadow/delete'.format(request), @@ -86,10 +85,7 @@ def publish_get_named_shadow(self, request, qos): request is successfully published. The Future's result will be an exception if the request cannot be published. """ - if not request.shadow_name: - raise ValueError("request.shadow_name is required") - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() return self._publish_operation( topic='$aws/things/{0.thing_name}/shadow/name/{0.shadow_name}/get'.format(request), @@ -113,8 +109,7 @@ def publish_get_shadow(self, request, qos): request is successfully published. The Future's result will be an exception if the request cannot be published. """ - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() return self._publish_operation( topic='$aws/things/{0.thing_name}/shadow/get'.format(request), @@ -138,10 +133,7 @@ def publish_update_named_shadow(self, request, qos): request is successfully published. The Future's result will be an exception if the request cannot be published. """ - if not request.shadow_name: - raise ValueError("request.shadow_name is required") - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() return self._publish_operation( topic='$aws/things/{0.thing_name}/shadow/name/{0.shadow_name}/update'.format(request), @@ -165,8 +157,7 @@ def publish_update_shadow(self, request, qos): request is successfully published. The Future's result will be an exception if the request cannot be published. """ - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() return self._publish_operation( topic='$aws/things/{0.thing_name}/shadow/update'.format(request), @@ -195,10 +186,7 @@ def subscribe_to_delete_named_shadow_accepted(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.shadow_name: - raise ValueError("request.shadow_name is required") - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -231,10 +219,7 @@ def subscribe_to_delete_named_shadow_rejected(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.shadow_name: - raise ValueError("request.shadow_name is required") - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -267,8 +252,7 @@ def subscribe_to_delete_shadow_accepted(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -301,8 +285,7 @@ def subscribe_to_delete_shadow_rejected(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -335,10 +318,7 @@ def subscribe_to_get_named_shadow_accepted(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.shadow_name: - raise ValueError("request.shadow_name is required") - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -371,10 +351,7 @@ def subscribe_to_get_named_shadow_rejected(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.shadow_name: - raise ValueError("request.shadow_name is required") - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -407,8 +384,7 @@ def subscribe_to_get_shadow_accepted(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -441,8 +417,7 @@ def subscribe_to_get_shadow_rejected(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -475,10 +450,7 @@ def subscribe_to_named_shadow_delta_updated_events(self, request, qos, callback) to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.shadow_name: - raise ValueError("request.shadow_name is required") - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -511,10 +483,7 @@ def subscribe_to_named_shadow_updated_events(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.shadow_name: - raise ValueError("request.shadow_name is required") - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -547,8 +516,7 @@ def subscribe_to_shadow_delta_updated_events(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -581,8 +549,7 @@ def subscribe_to_shadow_updated_events(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -615,10 +582,7 @@ def subscribe_to_update_named_shadow_accepted(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.shadow_name: - raise ValueError("request.shadow_name is required") - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -651,10 +615,7 @@ def subscribe_to_update_named_shadow_rejected(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.shadow_name: - raise ValueError("request.shadow_name is required") - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -687,8 +648,7 @@ def subscribe_to_update_shadow_accepted(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -721,8 +681,7 @@ def subscribe_to_update_shadow_rejected(self, request, qos, callback): to `unsubscribe()` to stop receiving messages. Note that messages may arrive before the subscription is acknowledged. """ - if not request.thing_name: - raise ValueError("request.thing_name is required") + request._validate() if not callable(callback): raise ValueError("callback is required") @@ -769,6 +728,13 @@ def to_payload(self): payload['clientToken'] = self.client_token return payload + def _validate(self): + if not self.shadow_name: + raise ValueError("shadow_name is required") + if not self.thing_name: + raise ValueError("thing_name is required") + return + class DeleteNamedShadowSubscriptionRequest(awsiot.ModeledClass): """ @@ -795,6 +761,13 @@ def __init__(self, *args, **kwargs): for key, val in zip(['shadow_name', 'thing_name'], args): setattr(self, key, val) + def _validate(self): + if not self.shadow_name: + raise ValueError("shadow_name is required") + if not self.thing_name: + raise ValueError("thing_name is required") + return + class DeleteShadowRequest(awsiot.ModeledClass): """ @@ -828,6 +801,11 @@ def to_payload(self): payload['clientToken'] = self.client_token return payload + def _validate(self): + if not self.thing_name: + raise ValueError("thing_name is required") + return + class DeleteShadowResponse(awsiot.ModeledClass): """ @@ -872,6 +850,9 @@ def from_payload(cls, payload): new.version = val return new + def _validate(self): + return + class DeleteShadowSubscriptionRequest(awsiot.ModeledClass): """ @@ -895,6 +876,11 @@ def __init__(self, *args, **kwargs): for key, val in zip(['thing_name'], args): setattr(self, key, val) + def _validate(self): + if not self.thing_name: + raise ValueError("thing_name is required") + return + class ErrorResponse(awsiot.ModeledClass): """ @@ -945,6 +931,9 @@ def from_payload(cls, payload): new.timestamp = datetime.datetime.fromtimestamp(val) return new + def _validate(self): + return + class GetNamedShadowRequest(awsiot.ModeledClass): """ @@ -981,6 +970,13 @@ def to_payload(self): payload['clientToken'] = self.client_token return payload + def _validate(self): + if not self.shadow_name: + raise ValueError("shadow_name is required") + if not self.thing_name: + raise ValueError("thing_name is required") + return + class GetNamedShadowSubscriptionRequest(awsiot.ModeledClass): """ @@ -1007,6 +1003,13 @@ def __init__(self, *args, **kwargs): for key, val in zip(['shadow_name', 'thing_name'], args): setattr(self, key, val) + def _validate(self): + if not self.shadow_name: + raise ValueError("shadow_name is required") + if not self.thing_name: + raise ValueError("thing_name is required") + return + class GetShadowRequest(awsiot.ModeledClass): """ @@ -1040,6 +1043,11 @@ def to_payload(self): payload['clientToken'] = self.client_token return payload + def _validate(self): + if not self.thing_name: + raise ValueError("thing_name is required") + return + class GetShadowResponse(awsiot.ModeledClass): """ @@ -1096,6 +1104,9 @@ def from_payload(cls, payload): new.version = val return new + def _validate(self): + return + class GetShadowSubscriptionRequest(awsiot.ModeledClass): """ @@ -1119,6 +1130,11 @@ def __init__(self, *args, **kwargs): for key, val in zip(['thing_name'], args): setattr(self, key, val) + def _validate(self): + if not self.thing_name: + raise ValueError("thing_name is required") + return + class NamedShadowDeltaUpdatedSubscriptionRequest(awsiot.ModeledClass): """ @@ -1145,6 +1161,13 @@ def __init__(self, *args, **kwargs): for key, val in zip(['shadow_name', 'thing_name'], args): setattr(self, key, val) + def _validate(self): + if not self.shadow_name: + raise ValueError("shadow_name is required") + if not self.thing_name: + raise ValueError("thing_name is required") + return + class NamedShadowUpdatedSubscriptionRequest(awsiot.ModeledClass): """ @@ -1171,6 +1194,13 @@ def __init__(self, *args, **kwargs): for key, val in zip(['shadow_name', 'thing_name'], args): setattr(self, key, val) + def _validate(self): + if not self.shadow_name: + raise ValueError("shadow_name is required") + if not self.thing_name: + raise ValueError("thing_name is required") + return + class ShadowDeltaUpdatedEvent(awsiot.ModeledClass): """ @@ -1227,6 +1257,9 @@ def from_payload(cls, payload): new.version = val return new + def _validate(self): + return + class ShadowDeltaUpdatedSubscriptionRequest(awsiot.ModeledClass): """ @@ -1250,6 +1283,11 @@ def __init__(self, *args, **kwargs): for key, val in zip(['thing_name'], args): setattr(self, key, val) + def _validate(self): + if not self.thing_name: + raise ValueError("thing_name is required") + return + class ShadowMetadata(awsiot.ModeledClass): """ @@ -1288,6 +1326,9 @@ def from_payload(cls, payload): new.reported = val return new + def _validate(self): + return + class ShadowState(awsiot.ModeledClass): """ @@ -1350,6 +1391,9 @@ def to_payload(self): payload['reported'] = self.reported return payload + def _validate(self): + return + class ShadowStateWithDelta(awsiot.ModeledClass): """ @@ -1394,6 +1438,9 @@ def from_payload(cls, payload): new.reported = val return new + def _validate(self): + return + class ShadowUpdatedEvent(awsiot.ModeledClass): """ @@ -1438,6 +1485,9 @@ def from_payload(cls, payload): new.timestamp = datetime.datetime.fromtimestamp(val) return new + def _validate(self): + return + class ShadowUpdatedSnapshot(awsiot.ModeledClass): """ @@ -1482,6 +1532,9 @@ def from_payload(cls, payload): new.version = val return new + def _validate(self): + return + class ShadowUpdatedSubscriptionRequest(awsiot.ModeledClass): """ @@ -1505,6 +1558,11 @@ def __init__(self, *args, **kwargs): for key, val in zip(['thing_name'], args): setattr(self, key, val) + def _validate(self): + if not self.thing_name: + raise ValueError("thing_name is required") + return + class UpdateNamedShadowRequest(awsiot.ModeledClass): """ @@ -1551,6 +1609,13 @@ def to_payload(self): payload['version'] = self.version return payload + def _validate(self): + if not self.shadow_name: + raise ValueError("shadow_name is required") + if not self.thing_name: + raise ValueError("thing_name is required") + return + class UpdateNamedShadowSubscriptionRequest(awsiot.ModeledClass): """ @@ -1577,6 +1642,13 @@ def __init__(self, *args, **kwargs): for key, val in zip(['shadow_name', 'thing_name'], args): setattr(self, key, val) + def _validate(self): + if not self.shadow_name: + raise ValueError("shadow_name is required") + if not self.thing_name: + raise ValueError("thing_name is required") + return + class UpdateShadowRequest(awsiot.ModeledClass): """ @@ -1620,6 +1692,11 @@ def to_payload(self): payload['version'] = self.version return payload + def _validate(self): + if not self.thing_name: + raise ValueError("thing_name is required") + return + class UpdateShadowResponse(awsiot.ModeledClass): """ @@ -1676,6 +1753,9 @@ def from_payload(cls, payload): new.version = val return new + def _validate(self): + return + class UpdateShadowSubscriptionRequest(awsiot.ModeledClass): """ @@ -1699,3 +1779,451 @@ def __init__(self, *args, **kwargs): for key, val in zip(['thing_name'], args): setattr(self, key, val) + def _validate(self): + if not self.thing_name: + raise ValueError("thing_name is required") + return + +class V2ErrorResponse(awsiot.ModeledClass): + """ + + Response document containing details about a failed request. + + All attributes are None by default, and may be set by keyword in the constructor. + + Keyword Args: + client_token (str): Opaque request-response correlation data. Present only if a client token was used in the request. + code (int): An HTTP response code that indicates the type of error. + message (str): A text message that provides additional information. + timestamp (datetime.datetime): The date and time the response was generated by AWS IoT. This property is not present in all error response documents. + + Attributes: + client_token (str): Opaque request-response correlation data. Present only if a client token was used in the request. + code (int): An HTTP response code that indicates the type of error. + message (str): A text message that provides additional information. + timestamp (datetime.datetime): The date and time the response was generated by AWS IoT. This property is not present in all error response documents. + """ + + __slots__ = ['client_token', 'code', 'message', 'timestamp'] + + def __init__(self, *args, **kwargs): + self.client_token = kwargs.get('client_token') + self.code = kwargs.get('code') + self.message = kwargs.get('message') + self.timestamp = kwargs.get('timestamp') + + # for backwards compatibility, read any arguments that used to be accepted by position + for key, val in zip([], args): + setattr(self, key, val) + + @classmethod + def from_payload(cls, payload): + # type: (typing.Dict[str, typing.Any]) -> V2ErrorResponse + new = cls() + val = payload.get('clientToken') + if val is not None: + new.client_token = val + val = payload.get('code') + if val is not None: + new.code = val + val = payload.get('message') + if val is not None: + new.message = val + val = payload.get('timestamp') + if val is not None: + new.timestamp = datetime.datetime.fromtimestamp(val) + return new + + def _validate(self): + return + +class IotShadowClientV2: + """ + + The AWS IoT Device Shadow service adds shadows to AWS IoT thing objects. Shadows are a simple data store for device properties and state. Shadows can make a device’s state available to apps and other services whether the device is connected to AWS IoT or not. + + AWS Docs: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html + + """ + + def __init__(self, protocol_client: awscrt.mqtt.Connection or awscrt.mqtt5.Client, options: awscrt.mqtt_request_response.ClientOptions): + self._rr_client = awscrt.mqtt_request_response.Client(protocol_client, options) + + def delete_named_shadow(self, request : DeleteNamedShadowRequest) -> concurrent.futures.Future : + """ + + Deletes a named shadow for an AWS IoT thing. + + API Docs: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#delete-pub-sub-topic + + Args: + request: `DeleteNamedShadowRequest` instance. + + Returns: + A Future whose result will be an instance of `DeleteShadowResponse`. + """ + request._validate() + + publish_topic = '$aws/things/{0.thing_name}/shadow/name/{0.shadow_name}/delete'.format(request) + accepted_topic = publish_topic + "/accepted"; + rejected_topic = publish_topic + "/rejected"; + + subscription0 = '$aws/things/{0.thing_name}/shadow/name/{0.shadow_name}/delete/+'.format(request); + + correlation_token = str(uuid.uuid4()) + request.client_token = correlation_token + + request_options = awscrt.mqtt_request_response.RequestOptions( + subscription_topic_filters = [ + subscription0, + ], + response_paths = [ + awscrt.mqtt_request_response.ResponsePath( + accepted_topic, + "clientToken" + ), + awscrt.mqtt_request_response.ResponsePath( + rejected_topic, + "clientToken" + ) + ], + publish_topic = publish_topic, + payload = json.dumps(request.to_payload()).encode(), + correlation_token = correlation_token, + ) + + internal_unmodeled_future = self._rr_client.make_request(request_options) + + return awsiot.create_v2_service_modeled_future(internal_unmodeled_future, "delete_named_shadow", accepted_topic, DeleteShadowResponse, V2ErrorResponse) + + def delete_shadow(self, request : DeleteShadowRequest) -> concurrent.futures.Future : + """ + + Deletes the (classic) shadow for an AWS IoT thing. + + API Docs: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#delete-pub-sub-topic + + Args: + request: `DeleteShadowRequest` instance. + + Returns: + A Future whose result will be an instance of `DeleteShadowResponse`. + """ + request._validate() + + publish_topic = '$aws/things/{0.thing_name}/shadow/delete'.format(request) + accepted_topic = publish_topic + "/accepted"; + rejected_topic = publish_topic + "/rejected"; + + subscription0 = '$aws/things/{0.thing_name}/shadow/delete/+'.format(request); + + correlation_token = str(uuid.uuid4()) + request.client_token = correlation_token + + request_options = awscrt.mqtt_request_response.RequestOptions( + subscription_topic_filters = [ + subscription0, + ], + response_paths = [ + awscrt.mqtt_request_response.ResponsePath( + accepted_topic, + "clientToken" + ), + awscrt.mqtt_request_response.ResponsePath( + rejected_topic, + "clientToken" + ) + ], + publish_topic = publish_topic, + payload = json.dumps(request.to_payload()).encode(), + correlation_token = correlation_token, + ) + + internal_unmodeled_future = self._rr_client.make_request(request_options) + + return awsiot.create_v2_service_modeled_future(internal_unmodeled_future, "delete_shadow", accepted_topic, DeleteShadowResponse, V2ErrorResponse) + + def get_named_shadow(self, request : GetNamedShadowRequest) -> concurrent.futures.Future : + """ + + Gets a named shadow for an AWS IoT thing. + + API Docs: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#get-pub-sub-topic + + Args: + request: `GetNamedShadowRequest` instance. + + Returns: + A Future whose result will be an instance of `GetShadowResponse`. + """ + request._validate() + + publish_topic = '$aws/things/{0.thing_name}/shadow/name/{0.shadow_name}/get'.format(request) + accepted_topic = publish_topic + "/accepted"; + rejected_topic = publish_topic + "/rejected"; + + subscription0 = '$aws/things/{0.thing_name}/shadow/name/{0.shadow_name}/get/+'.format(request); + + correlation_token = str(uuid.uuid4()) + request.client_token = correlation_token + + request_options = awscrt.mqtt_request_response.RequestOptions( + subscription_topic_filters = [ + subscription0, + ], + response_paths = [ + awscrt.mqtt_request_response.ResponsePath( + accepted_topic, + "clientToken" + ), + awscrt.mqtt_request_response.ResponsePath( + rejected_topic, + "clientToken" + ) + ], + publish_topic = publish_topic, + payload = json.dumps(request.to_payload()).encode(), + correlation_token = correlation_token, + ) + + internal_unmodeled_future = self._rr_client.make_request(request_options) + + return awsiot.create_v2_service_modeled_future(internal_unmodeled_future, "get_named_shadow", accepted_topic, GetShadowResponse, V2ErrorResponse) + + def get_shadow(self, request : GetShadowRequest) -> concurrent.futures.Future : + """ + + Gets the (classic) shadow for an AWS IoT thing. + + API Docs: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#get-pub-sub-topic + + Args: + request: `GetShadowRequest` instance. + + Returns: + A Future whose result will be an instance of `GetShadowResponse`. + """ + request._validate() + + publish_topic = '$aws/things/{0.thing_name}/shadow/get'.format(request) + accepted_topic = publish_topic + "/accepted"; + rejected_topic = publish_topic + "/rejected"; + + subscription0 = '$aws/things/{0.thing_name}/shadow/get/+'.format(request); + + correlation_token = str(uuid.uuid4()) + request.client_token = correlation_token + + request_options = awscrt.mqtt_request_response.RequestOptions( + subscription_topic_filters = [ + subscription0, + ], + response_paths = [ + awscrt.mqtt_request_response.ResponsePath( + accepted_topic, + "clientToken" + ), + awscrt.mqtt_request_response.ResponsePath( + rejected_topic, + "clientToken" + ) + ], + publish_topic = publish_topic, + payload = json.dumps(request.to_payload()).encode(), + correlation_token = correlation_token, + ) + + internal_unmodeled_future = self._rr_client.make_request(request_options) + + return awsiot.create_v2_service_modeled_future(internal_unmodeled_future, "get_shadow", accepted_topic, GetShadowResponse, V2ErrorResponse) + + def update_named_shadow(self, request : UpdateNamedShadowRequest) -> concurrent.futures.Future : + """ + + Update a named shadow for a device. + + API Docs: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#update-pub-sub-topic + + Args: + request: `UpdateNamedShadowRequest` instance. + + Returns: + A Future whose result will be an instance of `UpdateShadowResponse`. + """ + request._validate() + + publish_topic = '$aws/things/{0.thing_name}/shadow/name/{0.shadow_name}/update'.format(request) + accepted_topic = publish_topic + "/accepted"; + rejected_topic = publish_topic + "/rejected"; + + subscription0 = '$aws/things/{0.thing_name}/shadow/name/{0.shadow_name}/update/accepted'.format(request); + subscription1 = '$aws/things/{0.thing_name}/shadow/name/{0.shadow_name}/update/rejected'.format(request); + + correlation_token = str(uuid.uuid4()) + request.client_token = correlation_token + + request_options = awscrt.mqtt_request_response.RequestOptions( + subscription_topic_filters = [ + subscription0, + subscription1, + ], + response_paths = [ + awscrt.mqtt_request_response.ResponsePath( + accepted_topic, + "clientToken" + ), + awscrt.mqtt_request_response.ResponsePath( + rejected_topic, + "clientToken" + ) + ], + publish_topic = publish_topic, + payload = json.dumps(request.to_payload()).encode(), + correlation_token = correlation_token, + ) + + internal_unmodeled_future = self._rr_client.make_request(request_options) + + return awsiot.create_v2_service_modeled_future(internal_unmodeled_future, "update_named_shadow", accepted_topic, UpdateShadowResponse, V2ErrorResponse) + + def update_shadow(self, request : UpdateShadowRequest) -> concurrent.futures.Future : + """ + + Update a device's (classic) shadow. + + API Docs: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#update-pub-sub-topic + + Args: + request: `UpdateShadowRequest` instance. + + Returns: + A Future whose result will be an instance of `UpdateShadowResponse`. + """ + request._validate() + + publish_topic = '$aws/things/{0.thing_name}/shadow/update'.format(request) + accepted_topic = publish_topic + "/accepted"; + rejected_topic = publish_topic + "/rejected"; + + subscription0 = '$aws/things/{0.thing_name}/shadow/update/accepted'.format(request); + subscription1 = '$aws/things/{0.thing_name}/shadow/update/rejected'.format(request); + + correlation_token = str(uuid.uuid4()) + request.client_token = correlation_token + + request_options = awscrt.mqtt_request_response.RequestOptions( + subscription_topic_filters = [ + subscription0, + subscription1, + ], + response_paths = [ + awscrt.mqtt_request_response.ResponsePath( + accepted_topic, + "clientToken" + ), + awscrt.mqtt_request_response.ResponsePath( + rejected_topic, + "clientToken" + ) + ], + publish_topic = publish_topic, + payload = json.dumps(request.to_payload()).encode(), + correlation_token = correlation_token, + ) + + internal_unmodeled_future = self._rr_client.make_request(request_options) + + return awsiot.create_v2_service_modeled_future(internal_unmodeled_future, "update_shadow", accepted_topic, UpdateShadowResponse, V2ErrorResponse) + + def create_named_shadow_delta_updated_stream(self, request : NamedShadowDeltaUpdatedSubscriptionRequest, options: awsiot.ServiceStreamOptions[ShadowDeltaUpdatedEvent]): + """ + + Create a stream for NamedShadowDelta events for a named shadow of an AWS IoT thing. + + API Docs: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#update-delta-pub-sub-topic + + Args: + request: `NamedShadowDeltaUpdatedSubscriptionRequest` instance. + options: callbacks to invoke for streaming operation events + + Returns: + An instance of `awscrt.mqtt_request_response.StreamingOperation` + """ + request._validate() + options._validate() + + subscription_topic_filter = '$aws/things/{0.thing_name}/shadow/name/{0.shadow_name}/update/delta'.format(request) + + unmodeled_options = awsiot.create_streaming_unmodeled_options(options, subscription_topic_filter, "ShadowDeltaUpdatedEvent", ShadowDeltaUpdatedEvent) + + return self._rr_client.create_stream(unmodeled_options) + + def create_named_shadow_updated_stream(self, request : NamedShadowUpdatedSubscriptionRequest, options: awsiot.ServiceStreamOptions[ShadowUpdatedEvent]): + """ + + Create a stream for ShadowUpdated events for a named shadow of an AWS IoT thing. + + API Docs: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#update-documents-pub-sub-topic + + Args: + request: `NamedShadowUpdatedSubscriptionRequest` instance. + options: callbacks to invoke for streaming operation events + + Returns: + An instance of `awscrt.mqtt_request_response.StreamingOperation` + """ + request._validate() + options._validate() + + subscription_topic_filter = '$aws/things/{0.thing_name}/shadow/name/{0.shadow_name}/update/documents'.format(request) + + unmodeled_options = awsiot.create_streaming_unmodeled_options(options, subscription_topic_filter, "ShadowUpdatedEvent", ShadowUpdatedEvent) + + return self._rr_client.create_stream(unmodeled_options) + + def create_shadow_delta_updated_stream(self, request : ShadowDeltaUpdatedSubscriptionRequest, options: awsiot.ServiceStreamOptions[ShadowDeltaUpdatedEvent]): + """ + + Create a stream for ShadowDelta events for the (classic) shadow of an AWS IoT thing. + + API Docs: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#update-delta-pub-sub-topic + + Args: + request: `ShadowDeltaUpdatedSubscriptionRequest` instance. + options: callbacks to invoke for streaming operation events + + Returns: + An instance of `awscrt.mqtt_request_response.StreamingOperation` + """ + request._validate() + options._validate() + + subscription_topic_filter = '$aws/things/{0.thing_name}/shadow/update/delta'.format(request) + + unmodeled_options = awsiot.create_streaming_unmodeled_options(options, subscription_topic_filter, "ShadowDeltaUpdatedEvent", ShadowDeltaUpdatedEvent) + + return self._rr_client.create_stream(unmodeled_options) + + def create_shadow_updated_stream(self, request : ShadowUpdatedSubscriptionRequest, options: awsiot.ServiceStreamOptions[ShadowUpdatedEvent]): + """ + + Create a stream for ShadowUpdated events for the (classic) shadow of an AWS IoT thing. + + API Docs: https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html#update-documents-pub-sub-topic + + Args: + request: `ShadowUpdatedSubscriptionRequest` instance. + options: callbacks to invoke for streaming operation events + + Returns: + An instance of `awscrt.mqtt_request_response.StreamingOperation` + """ + request._validate() + options._validate() + + subscription_topic_filter = '$aws/things/{0.thing_name}/shadow/update/documents'.format(request) + + unmodeled_options = awsiot.create_streaming_unmodeled_options(options, subscription_topic_filter, "ShadowUpdatedEvent", ShadowUpdatedEvent) + + return self._rr_client.create_stream(unmodeled_options) + diff --git a/codebuild/samples/shadow-linux.sh b/codebuild/samples/shadow-linux.sh index e9aad66c..ffb34179 100755 --- a/codebuild/samples/shadow-linux.sh +++ b/codebuild/samples/shadow-linux.sh @@ -10,7 +10,7 @@ pushd $CODEBUILD_SRC_DIR/samples/ ENDPOINT=$(aws secretsmanager get-secret-value --secret-id "ci/endpoint" --query "SecretString" | cut -f2 -d":" | sed -e 's/[\\\"\}]//g') echo "Shadow test" -python3 shadow.py --endpoint $ENDPOINT --key /tmp/privatekey.pem --cert /tmp/certificate.pem --thing_name CI_CodeBuild_Thing --is_ci true -python3 shadow_mqtt5.py --endpoint $ENDPOINT --key /tmp/privatekey.pem --cert /tmp/certificate.pem --thing_name CI_CodeBuild_Thing --is_ci true +python3 deprecated/shadow.py --endpoint $ENDPOINT --key /tmp/privatekey.pem --cert /tmp/certificate.pem --thing_name CI_CodeBuild_Thing --is_ci true +python3 deprecated/shadow_mqtt5.py --endpoint $ENDPOINT --key /tmp/privatekey.pem --cert /tmp/certificate.pem --thing_name CI_CodeBuild_Thing --is_ci true popd diff --git a/samples/README.md b/samples/README.md index c35e638f..4758b875 100644 --- a/samples/README.md +++ b/samples/README.md @@ -9,9 +9,6 @@ * [MQTT5 Shared Subscription](./mqtt5_shared_subscription.md) * [MQTT5 PKCS#11 Connect](./mqtt5_pkcs11_connect.md) * [MQTT5 Custom Authorizer Connect](./mqtt5_custom_authorizer_connect.md) -* [MQTT5 Shadow](./shadow_mqtt5.md) -* [MQTT5 Jobs](./jobs_mqtt5.md) -* [MQTT5 Fleet Provisioning](./fleetprovisioning_mqtt5.md) ## MQTT311 Samples * [PubSub](./pubsub.md) * [Basic Connect](./basic_connect.md) @@ -22,10 +19,11 @@ * [Custom Authorizer Connect](./custom_authorizer_connect.md) * [Cognito Connect](./cognito_connect.md) * [X509 Connect](./x509_connect.md) +## Other +* [Basic Fleet Provisioning](./fleet_provisioning_basic.md) +* [CSR Fleet Provisioning](./fleet_provisioning_csr.md) * [Shadow](./shadow.md) * [Jobs](./jobs.md) -* [Fleet Provisioning](./fleetprovisioning.md) -## Other * [Greengrass Discovery](./basic_discovery.md) * [Greengrass IPC](./ipc_greengrass.md) diff --git a/samples/fleetprovisioning.md b/samples/deprecated/fleetprovisioning.md similarity index 99% rename from samples/fleetprovisioning.md rename to samples/deprecated/fleetprovisioning.md index eec65692..e035f416 100644 --- a/samples/fleetprovisioning.md +++ b/samples/deprecated/fleetprovisioning.md @@ -1,6 +1,6 @@ # Fleet provisioning -[**Return to main sample list**](./README.md) +[**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. diff --git a/samples/fleetprovisioning.py b/samples/deprecated/fleetprovisioning.py similarity index 100% rename from samples/fleetprovisioning.py rename to samples/deprecated/fleetprovisioning.py diff --git a/samples/fleetprovisioning_mqtt5.md b/samples/deprecated/fleetprovisioning_mqtt5.md similarity index 99% rename from samples/fleetprovisioning_mqtt5.md rename to samples/deprecated/fleetprovisioning_mqtt5.md index a89688f5..57b00ab8 100644 --- a/samples/fleetprovisioning_mqtt5.md +++ b/samples/deprecated/fleetprovisioning_mqtt5.md @@ -1,6 +1,6 @@ # Fleet provisioning MQTT5 -[**Return to main sample list**](./README.md) +[**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. diff --git a/samples/fleetprovisioning_mqtt5.py b/samples/deprecated/fleetprovisioning_mqtt5.py similarity index 100% rename from samples/fleetprovisioning_mqtt5.py rename to samples/deprecated/fleetprovisioning_mqtt5.py diff --git a/samples/deprecated/jobs.md b/samples/deprecated/jobs.md new file mode 100644 index 00000000..4648ab73 --- /dev/null +++ b/samples/deprecated/jobs.md @@ -0,0 +1,84 @@ +# 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 and pretends to process them, marking each job as complete as it does so. + +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 from the `samples` folder: + +``` sh +# For Windows: replace 'python3' with 'python' and '/' with '\' +python3 jobs.py --endpoint --cert --key --thing_name +``` + +You can also pass a Certificate Authority file (CA) if your certificate and key combination requires it: + +``` sh +# For Windows: replace 'python3' with 'python' and '/' with '\' +python3 jobs.py --endpoint --cert --key --thing_name --ca_file +``` diff --git a/samples/deprecated/jobs.py b/samples/deprecated/jobs.py new file mode 100644 index 00000000..4ac8d40a --- /dev/null +++ b/samples/deprecated/jobs.py @@ -0,0 +1,396 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0. + +from awscrt import mqtt, http +from awsiot import iotjobs, mqtt_connection_builder +from concurrent.futures import Future +import sys +import threading +import time +import traceback +import time +from utils.command_line_utils import CommandLineUtils + +# - Overview - +# This sample uses the AWS IoT Jobs Service to get a list of pending jobs and +# then execution operations on these pending jobs until there are no more +# remaining on the device. Imagine periodic software updates that must be sent to and +# executed on devices in the wild. +# +# - Instructions - +# This sample requires you to create jobs for your device to execute. See: +# https://docs.aws.amazon.com/iot/latest/developerguide/create-manage-jobs.html +# +# - Detail - +# On startup, the sample tries to get a list of all the in-progress and queued +# jobs and display them in a list. Then it tries to start the next pending job execution. +# If such a job exists, the sample emulates "doing work" by spawning a thread +# that sleeps for several seconds before marking the job as SUCCEEDED. When no +# pending job executions exist, the sample sits in an idle state. +# +# The sample also subscribes to receive "Next Job Execution Changed" events. +# If the sample is idle, this event wakes it to start the job. If the sample is +# already working on a job, it remembers to try for another when it's done. +# This event is sent by the service when the current job completes, so the +# sample will be continually prompted to try another job until none remain. + +# Using globals to simplify sample code +is_sample_done = threading.Event() + +# 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. +cmdData = CommandLineUtils.parse_sample_input_jobs() + +mqtt_connection = None +jobs_client = None +jobs_thing_name = cmdData.input_thing_name + + +class LockedData: + def __init__(self): + self.lock = threading.Lock() + self.disconnect_called = False + self.is_working_on_job = False + self.is_next_job_waiting = False + self.got_job_response = False + + +locked_data = LockedData() + +# Function for gracefully quitting this sample +def exit(msg_or_exception): + if isinstance(msg_or_exception, Exception): + print("Exiting Sample due to exception.") + traceback.print_exception(msg_or_exception.__class__, msg_or_exception, sys.exc_info()[2]) + else: + print("Exiting Sample:", msg_or_exception) + + with locked_data.lock: + if not locked_data.disconnect_called: + print("Disconnecting...") + locked_data.disconnect_called = True + future = mqtt_connection.disconnect() + future.add_done_callback(on_disconnected) + + +def try_start_next_job(): + print("Trying to start the next job...") + with locked_data.lock: + if locked_data.is_working_on_job: + print("Nevermind, already working on a job.") + return + + if locked_data.disconnect_called: + print("Nevermind, sample is disconnecting.") + return + + locked_data.is_working_on_job = True + locked_data.is_next_job_waiting = False + + print("Publishing request to start next job...") + request = iotjobs.StartNextPendingJobExecutionRequest(thing_name=jobs_thing_name) + publish_future = jobs_client.publish_start_next_pending_job_execution(request, mqtt.QoS.AT_LEAST_ONCE) + publish_future.add_done_callback(on_publish_start_next_pending_job_execution) + + +def done_working_on_job(): + with locked_data.lock: + locked_data.is_working_on_job = False + try_again = locked_data.is_next_job_waiting + + if try_again: + try_start_next_job() + + +def on_disconnected(disconnect_future): + # type: (Future) -> None + print("Disconnected.") + + # Signal that sample is finished + is_sample_done.set() + + +# A list to hold all the pending jobs +available_jobs = [] + + +def on_get_pending_job_executions_accepted(response): + # type: (iotjobs.GetPendingJobExecutionsResponse) -> None + with locked_data.lock: + if (len(response.queued_jobs) > 0 or len(response.in_progress_jobs) > 0): + print("Pending Jobs:") + for job in response.in_progress_jobs: + available_jobs.append(job) + print(f" In Progress: {job.job_id} @ {job.last_updated_at}") + for job in response.queued_jobs: + available_jobs.append(job) + print(f" {job.job_id} @ {job.last_updated_at}") + else: + print("No pending or queued jobs found!") + locked_data.got_job_response = True + + +def on_get_pending_job_executions_rejected(error): + # type: (iotjobs.RejectedError) -> None + print(f"Request rejected: {error.code}: {error.message}") + exit("Get pending jobs request rejected!") + + +def on_next_job_execution_changed(event): + # type: (iotjobs.NextJobExecutionChangedEvent) -> None + try: + execution = event.execution + if execution: + print("Received Next Job Execution Changed event. job_id:{} job_document:{}".format( + execution.job_id, execution.job_document)) + + # Start job now, or remember to start it when current job is done + start_job_now = False + with locked_data.lock: + if locked_data.is_working_on_job: + locked_data.is_next_job_waiting = True + else: + start_job_now = True + + if start_job_now: + try_start_next_job() + + else: + print("Received Next Job Execution Changed event: None. Waiting for further jobs...") + + except Exception as e: + exit(e) + + +def on_publish_start_next_pending_job_execution(future): + # type: (Future) -> None + try: + future.result() # raises exception if publish failed + + print("Published request to start the next job.") + + except Exception as e: + exit(e) + + +def on_start_next_pending_job_execution_accepted(response): + # type: (iotjobs.StartNextJobExecutionResponse) -> None + try: + if response.execution: + execution = response.execution + print("Request to start next job was accepted. job_id:{} job_document:{}".format( + execution.job_id, execution.job_document)) + + # To emulate working on a job, spawn a thread that sleeps for a few seconds + job_thread = threading.Thread( + target=lambda: job_thread_fn(execution.job_id, execution.job_document), + name='job_thread') + job_thread.start() + else: + print("Request to start next job was accepted, but there are no jobs to be done. Waiting for further jobs...") + done_working_on_job() + + except Exception as e: + exit(e) + + +def on_start_next_pending_job_execution_rejected(rejected): + # type: (iotjobs.RejectedError) -> None + exit("Request to start next pending job rejected with code:'{}' message:'{}'".format( + rejected.code, rejected.message)) + + +def job_thread_fn(job_id, job_document): + try: + print("Starting local work on job...") + time.sleep(cmdData.input_job_time) + print("Done working on job.") + + print("Publishing request to update job status to SUCCEEDED...") + request = iotjobs.UpdateJobExecutionRequest( + thing_name=jobs_thing_name, + job_id=job_id, + status=iotjobs.JobStatus.SUCCEEDED) + publish_future = jobs_client.publish_update_job_execution(request, mqtt.QoS.AT_LEAST_ONCE) + publish_future.add_done_callback(on_publish_update_job_execution) + + except Exception as e: + exit(e) + + +def on_publish_update_job_execution(future): + # type: (Future) -> None + try: + future.result() # raises exception if publish failed + print("Published request to update job.") + + except Exception as e: + exit(e) + + +def on_update_job_execution_accepted(response): + # type: (iotjobs.UpdateJobExecutionResponse) -> None + try: + print("Request to update job was accepted.") + done_working_on_job() + except Exception as e: + exit(e) + + +def on_update_job_execution_rejected(rejected): + # type: (iotjobs.RejectedError) -> None + exit("Request to update job status was rejected. code:'{}' message:'{}'.".format( + rejected.code, rejected.message)) + + +if __name__ == '__main__': + + # Create the proxy options if the data is present in cmdData + proxy_options = None + if cmdData.input_proxy_host is not None and cmdData.input_proxy_port != 0: + proxy_options = http.HttpProxyOptions( + host_name=cmdData.input_proxy_host, + port=cmdData.input_proxy_port) + + # Create a MQTT connection from the command line data + mqtt_connection = mqtt_connection_builder.mtls_from_path( + endpoint=cmdData.input_endpoint, + port=cmdData.input_port, + cert_filepath=cmdData.input_cert, + pri_key_filepath=cmdData.input_key, + ca_filepath=cmdData.input_ca, + client_id=cmdData.input_clientId, + clean_session=False, + keep_alive_secs=30, + http_proxy_options=proxy_options) + + if not cmdData.input_is_ci: + print(f"Connecting to {cmdData.input_endpoint} with client ID '{cmdData.input_clientId}'...") + else: + print("Connecting to endpoint with client ID") + + connected_future = mqtt_connection.connect() + + jobs_client = iotjobs.IotJobsClient(mqtt_connection) + + # Wait for connection to be fully established. + # Note that it's not necessary to wait, commands issued to the + # mqtt_connection before its fully connected will simply be queued. + # But this sample waits here so it's obvious when a connection + # fails or succeeds. + connected_future.result() + print("Connected!") + + try: + # List the jobs queued and pending + get_jobs_request = iotjobs.GetPendingJobExecutionsRequest(thing_name=jobs_thing_name) + jobs_request_future_accepted, _ = jobs_client.subscribe_to_get_pending_job_executions_accepted( + request=get_jobs_request, + qos=mqtt.QoS.AT_LEAST_ONCE, + callback=on_get_pending_job_executions_accepted + ) + # Wait for the subscription to succeed + jobs_request_future_accepted.result() + + jobs_request_future_rejected, _ = jobs_client.subscribe_to_get_pending_job_executions_rejected( + request=get_jobs_request, + qos=mqtt.QoS.AT_LEAST_ONCE, + callback=on_get_pending_job_executions_rejected + ) + # Wait for the subscription to succeed + jobs_request_future_rejected.result() + + # Get a list of all the jobs + get_jobs_request_future = jobs_client.publish_get_pending_job_executions( + request=get_jobs_request, + qos=mqtt.QoS.AT_LEAST_ONCE + ) + # Wait for the publish to succeed + get_jobs_request_future.result() + except Exception as e: + exit(e) + + # If we are running in CI, then we want to check how many jobs were reported and stop + if (cmdData.input_is_ci): + # Wait until we get a response. If we do not get a response after 50 tries, then abort + got_job_response_tries = 0 + while (locked_data.got_job_response == False): + got_job_response_tries += 1 + if (got_job_response_tries > 50): + exit("Got job response timeout exceeded") + sys.exit(-1) + time.sleep(0.2) + + if (len(available_jobs) > 0): + print("At least one job queued in CI! No further work to do. Exiting sample...") + sys.exit(0) + else: + print("ERROR: No jobs queued in CI! At least one job should be queued!") + sys.exit(-1) + + try: + # Subscribe to necessary topics. + # Note that is **is** important to wait for "accepted/rejected" subscriptions + # to succeed before publishing the corresponding "request". + print("Subscribing to Next Changed events...") + changed_subscription_request = iotjobs.NextJobExecutionChangedSubscriptionRequest( + thing_name=jobs_thing_name) + + subscribed_future, _ = jobs_client.subscribe_to_next_job_execution_changed_events( + request=changed_subscription_request, + qos=mqtt.QoS.AT_LEAST_ONCE, + callback=on_next_job_execution_changed) + + # Wait for subscription to succeed + subscribed_future.result() + + print("Subscribing to Start responses...") + start_subscription_request = iotjobs.StartNextPendingJobExecutionSubscriptionRequest( + thing_name=jobs_thing_name) + subscribed_accepted_future, _ = jobs_client.subscribe_to_start_next_pending_job_execution_accepted( + request=start_subscription_request, + qos=mqtt.QoS.AT_LEAST_ONCE, + callback=on_start_next_pending_job_execution_accepted) + + subscribed_rejected_future, _ = jobs_client.subscribe_to_start_next_pending_job_execution_rejected( + request=start_subscription_request, + qos=mqtt.QoS.AT_LEAST_ONCE, + callback=on_start_next_pending_job_execution_rejected) + + # Wait for subscriptions to succeed + subscribed_accepted_future.result() + subscribed_rejected_future.result() + + print("Subscribing to Update responses...") + # Note that we subscribe to "+", the MQTT wildcard, to receive + # responses about any job-ID. + update_subscription_request = iotjobs.UpdateJobExecutionSubscriptionRequest( + thing_name=jobs_thing_name, + job_id='+') + + subscribed_accepted_future, _ = jobs_client.subscribe_to_update_job_execution_accepted( + request=update_subscription_request, + qos=mqtt.QoS.AT_LEAST_ONCE, + callback=on_update_job_execution_accepted) + + subscribed_rejected_future, _ = jobs_client.subscribe_to_update_job_execution_rejected( + request=update_subscription_request, + qos=mqtt.QoS.AT_LEAST_ONCE, + callback=on_update_job_execution_rejected) + + # Wait for subscriptions to succeed + subscribed_accepted_future.result() + subscribed_rejected_future.result() + + # Make initial attempt to start next job. The service should reply with + # an "accepted" response, even if no jobs are pending. The response + # will contain data about the next job, if there is one. + # (Will do nothing if we are in CI) + try_start_next_job() + + except Exception as e: + exit(e) + + # Wait for the sample to finish + is_sample_done.wait() diff --git a/samples/jobs_mqtt5.md b/samples/deprecated/jobs_mqtt5.md similarity index 99% rename from samples/jobs_mqtt5.md rename to samples/deprecated/jobs_mqtt5.md index e8b2e1ed..f189f76e 100644 --- a/samples/jobs_mqtt5.md +++ b/samples/deprecated/jobs_mqtt5.md @@ -1,6 +1,6 @@ # Jobs MQTT5 -[**Return to main sample list**](./README.md) +[**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. diff --git a/samples/jobs_mqtt5.py b/samples/deprecated/jobs_mqtt5.py similarity index 100% rename from samples/jobs_mqtt5.py rename to samples/deprecated/jobs_mqtt5.py diff --git a/samples/deprecated/shadow.md b/samples/deprecated/shadow.md new file mode 100644 index 00000000..aa250757 --- /dev/null +++ b/samples/deprecated/shadow.md @@ -0,0 +1,87 @@ +# 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 from the `samples` folder, use the following command: + +``` sh +# For Windows: replace 'python3' with 'python' and '/' with '\' +python3 shadow.py --endpoint --cert --key --thing_name +``` + +You can also pass a Certificate Authority file (CA) if your certificate and key combination requires it: + +``` sh +# For Windows: replace 'python3' with 'python' and '/' with '\' +python3 shadow.py --endpoint --cert --key --thing_name --ca_file +``` diff --git a/samples/deprecated/shadow.py b/samples/deprecated/shadow.py new file mode 100644 index 00000000..285883c4 --- /dev/null +++ b/samples/deprecated/shadow.py @@ -0,0 +1,426 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0. + +from time import sleep +from awscrt import mqtt, http +from awsiot import iotshadow, mqtt_connection_builder +from concurrent.futures import Future +import sys +import threading +import traceback +from uuid import uuid4 +from utils.command_line_utils import CommandLineUtils + +# - Overview - +# This sample uses the AWS IoT Device Shadow 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. +# +# - Instructions - +# 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. +# +# - Detail - +# 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. + +# 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. +cmdData = CommandLineUtils.parse_sample_input_shadow() + +# Using globals to simplify sample code +is_sample_done = threading.Event() +mqtt_connection = None +shadow_thing_name = cmdData.input_thing_name +shadow_property = cmdData.input_shadow_property + +SHADOW_VALUE_DEFAULT = "off" + + +class LockedData: + def __init__(self): + self.lock = threading.Lock() + self.shadow_value = None + self.disconnect_called = False + self.request_tokens = set() + + +locked_data = LockedData() + +# Function for gracefully quitting this sample + + +def exit(msg_or_exception): + if isinstance(msg_or_exception, Exception): + print("Exiting sample due to exception.") + traceback.print_exception(msg_or_exception.__class__, msg_or_exception, sys.exc_info()[2]) + else: + print("Exiting sample:", msg_or_exception) + + with locked_data.lock: + if not locked_data.disconnect_called: + print("Disconnecting...") + locked_data.disconnect_called = True + future = mqtt_connection.disconnect() + future.add_done_callback(on_disconnected) + + +def on_disconnected(disconnect_future): + # type: (Future) -> None + print("Disconnected.") + + # Signal that sample is finished + is_sample_done.set() + + +def on_get_shadow_accepted(response): + # type: (iotshadow.GetShadowResponse) -> None + try: + with locked_data.lock: + # check that this is a response to a request from this session + try: + locked_data.request_tokens.remove(response.client_token) + except KeyError: + print("Ignoring get_shadow_accepted message due to unexpected token.") + return + + print("Finished getting initial shadow state.") + if locked_data.shadow_value is not None: + print(" Ignoring initial query because a delta event has already been received.") + return + + if response.state: + if response.state.delta: + value = response.state.delta.get(shadow_property) + if value: + print(" Shadow contains delta value '{}'.".format(value)) + change_shadow_value(value) + return + + if response.state.reported: + value = response.state.reported.get(shadow_property) + if value: + print(" Shadow contains reported value '{}'.".format(value)) + set_local_value_due_to_initial_query(response.state.reported[shadow_property]) + return + + print(" Shadow document lacks '{}' property. Setting defaults...".format(shadow_property)) + change_shadow_value(SHADOW_VALUE_DEFAULT) + return + + except Exception as e: + exit(e) + + +def on_get_shadow_rejected(error): + # type: (iotshadow.ErrorResponse) -> None + try: + # check that this is a response to a request from this session + with locked_data.lock: + try: + locked_data.request_tokens.remove(error.client_token) + except KeyError: + print("Ignoring get_shadow_rejected message due to unexpected token.") + return + + if error.code == 404: + print("Thing has no shadow document. Creating with defaults...") + change_shadow_value(SHADOW_VALUE_DEFAULT) + else: + exit("Get request was rejected. code:{} message:'{}'".format( + error.code, error.message)) + + except Exception as e: + exit(e) + + +def on_shadow_delta_updated(delta): + # type: (iotshadow.ShadowDeltaUpdatedEvent) -> None + try: + print("Received shadow delta event.") + if delta.state and (shadow_property in delta.state): + value = delta.state[shadow_property] + if value is None: + print(" Delta reports that '{}' was deleted. Resetting defaults...".format(shadow_property)) + change_shadow_value(SHADOW_VALUE_DEFAULT) + return + else: + print(" Delta reports that desired value is '{}'. Changing local value...".format(value)) + if (delta.client_token is not None): + print(" ClientToken is: " + delta.client_token) + change_shadow_value(value) + else: + print(" Delta did not report a change in '{}'".format(shadow_property)) + + except Exception as e: + exit(e) + + +def on_publish_update_shadow(future): + # type: (Future) -> None + try: + future.result() + print("Update request published.") + except Exception as e: + print("Failed to publish update request.") + exit(e) + + +def on_update_shadow_accepted(response): + # type: (iotshadow.UpdateShadowResponse) -> None + try: + # check that this is a response to a request from this session + with locked_data.lock: + try: + locked_data.request_tokens.remove(response.client_token) + except KeyError: + print("Ignoring update_shadow_accepted message due to unexpected token.") + return + + try: + if response.state.reported is not None: + if shadow_property in response.state.reported: + print("Finished updating reported shadow value to '{}'.".format( + response.state.reported[shadow_property])) # type: ignore + else: + print("Could not find shadow property with name: '{}'.".format(shadow_property)) # type: ignore + else: + print("Shadow states cleared.") # when the shadow states are cleared, reported and desired are set to None + print("Enter desired value: ") # remind user they can input new values + except BaseException: + exit("Updated shadow is missing the target property") + + except Exception as e: + exit(e) + + +def on_update_shadow_rejected(error): + # type: (iotshadow.ErrorResponse) -> None + try: + # check that this is a response to a request from this session + with locked_data.lock: + try: + locked_data.request_tokens.remove(error.client_token) + except KeyError: + print("Ignoring update_shadow_rejected message due to unexpected token.") + return + + exit("Update request was rejected. code:{} message:'{}'".format( + error.code, error.message)) + + except Exception as e: + exit(e) + + +def set_local_value_due_to_initial_query(reported_value): + with locked_data.lock: + locked_data.shadow_value = reported_value + print("Enter desired value: ") # remind user they can input new values + + +def change_shadow_value(value): + with locked_data.lock: + if locked_data.shadow_value == value: + print("Local value is already '{}'.".format(value)) + print("Enter desired value: ") # remind user they can input new values + return + + print("Changed local shadow value to '{}'.".format(value)) + locked_data.shadow_value = value + + print("Updating reported shadow value to '{}'...".format(value)) + + # use a unique token so we can correlate this "request" message to + # any "response" messages received on the /accepted and /rejected topics + token = str(uuid4()) + + # if the value is "clear shadow" then send a UpdateShadowRequest with None + # for both reported and desired to clear the shadow document completely. + if value == "clear_shadow": + tmp_state = iotshadow.ShadowState( + reported=None, + desired=None, + reported_is_nullable=True, + desired_is_nullable=True) + request = iotshadow.UpdateShadowRequest( + thing_name=shadow_thing_name, + state=tmp_state, + client_token=token, + ) + # Otherwise, send a normal update request + else: + # if the value is "none" then set it to a Python none object to + # clear the individual shadow property + if value == "none": + value = None + + request = iotshadow.UpdateShadowRequest( + thing_name=shadow_thing_name, + state=iotshadow.ShadowState( + reported={shadow_property: value}, + desired={shadow_property: value}, + ), + client_token=token, + ) + + future = shadow_client.publish_update_shadow(request, mqtt.QoS.AT_LEAST_ONCE) + + locked_data.request_tokens.add(token) + + future.add_done_callback(on_publish_update_shadow) + + +def user_input_thread_fn(): + # If we are not in CI, then take terminal input + if not cmdData.input_is_ci: + while True: + try: + # Read user input + new_value = input() + + # If user wants to quit sample, then quit. + # Otherwise change the shadow value. + if new_value in ['exit', 'quit']: + exit("User has quit") + break + else: + change_shadow_value(new_value) + + except Exception as e: + print("Exception on input thread.") + exit(e) + break + # Otherwise, send shadow updates automatically + else: + try: + messages_sent = 0 + while messages_sent < 5: + cli_input = "Shadow_Value_" + str(messages_sent) + change_shadow_value(cli_input) + sleep(1) + messages_sent += 1 + exit("CI has quit") + except Exception as e: + print("Exception on input thread (CI)") + exit(e) + + +if __name__ == '__main__': + # Create the proxy options if the data is present in cmdData + proxy_options = None + if cmdData.input_proxy_host is not None and cmdData.input_proxy_port != 0: + proxy_options = http.HttpProxyOptions( + host_name=cmdData.input_proxy_host, + port=cmdData.input_proxy_port) + + # Create a MQTT connection from the command line data + mqtt_connection = mqtt_connection_builder.mtls_from_path( + endpoint=cmdData.input_endpoint, + port=cmdData.input_port, + cert_filepath=cmdData.input_cert, + pri_key_filepath=cmdData.input_key, + ca_filepath=cmdData.input_ca, + client_id=cmdData.input_clientId, + clean_session=False, + keep_alive_secs=30, + http_proxy_options=proxy_options) + + if not cmdData.input_is_ci: + print(f"Connecting to {cmdData.input_endpoint} with client ID '{cmdData.input_clientId}'...") + else: + print("Connecting to endpoint with client ID") + + connected_future = mqtt_connection.connect() + + shadow_client = iotshadow.IotShadowClient(mqtt_connection) + + # Wait for connection to be fully established. + # Note that it's not necessary to wait, commands issued to the + # mqtt_connection before its fully connected will simply be queued. + # But this sample waits here so it's obvious when a connection + # fails or succeeds. + connected_future.result() + print("Connected!") + + try: + # Subscribe to necessary topics. + # Note that is **is** important to wait for "accepted/rejected" subscriptions + # to succeed before publishing the corresponding "request". + print("Subscribing to Update responses...") + update_accepted_subscribed_future, _ = shadow_client.subscribe_to_update_shadow_accepted( + request=iotshadow.UpdateShadowSubscriptionRequest(thing_name=shadow_thing_name), + qos=mqtt.QoS.AT_LEAST_ONCE, + callback=on_update_shadow_accepted) + + update_rejected_subscribed_future, _ = shadow_client.subscribe_to_update_shadow_rejected( + request=iotshadow.UpdateShadowSubscriptionRequest(thing_name=shadow_thing_name), + qos=mqtt.QoS.AT_LEAST_ONCE, + callback=on_update_shadow_rejected) + + # Wait for subscriptions to succeed + update_accepted_subscribed_future.result() + update_rejected_subscribed_future.result() + + print("Subscribing to Get responses...") + get_accepted_subscribed_future, _ = shadow_client.subscribe_to_get_shadow_accepted( + request=iotshadow.GetShadowSubscriptionRequest(thing_name=shadow_thing_name), + qos=mqtt.QoS.AT_LEAST_ONCE, + callback=on_get_shadow_accepted) + + get_rejected_subscribed_future, _ = shadow_client.subscribe_to_get_shadow_rejected( + request=iotshadow.GetShadowSubscriptionRequest(thing_name=shadow_thing_name), + qos=mqtt.QoS.AT_LEAST_ONCE, + callback=on_get_shadow_rejected) + + # Wait for subscriptions to succeed + get_accepted_subscribed_future.result() + get_rejected_subscribed_future.result() + + print("Subscribing to Delta events...") + delta_subscribed_future, _ = shadow_client.subscribe_to_shadow_delta_updated_events( + request=iotshadow.ShadowDeltaUpdatedSubscriptionRequest(thing_name=shadow_thing_name), + qos=mqtt.QoS.AT_LEAST_ONCE, + callback=on_shadow_delta_updated) + + # Wait for subscription to succeed + delta_subscribed_future.result() + + # The rest of the sample runs asynchronously. + + # Issue request for shadow's current state. + # The response will be received by the on_get_accepted() callback + print("Requesting current shadow state...") + + with locked_data.lock: + # use a unique token so we can correlate this "request" message to + # any "response" messages received on the /accepted and /rejected topics + token = str(uuid4()) + + publish_get_future = shadow_client.publish_get_shadow( + request=iotshadow.GetShadowRequest(thing_name=shadow_thing_name, client_token=token), + qos=mqtt.QoS.AT_LEAST_ONCE) + + locked_data.request_tokens.add(token) + + # Ensure that publish succeeds + publish_get_future.result() + + # Launch thread to handle user input. + # A "daemon" thread won't prevent the program from shutting down. + print("Launching thread to read user input...") + user_input_thread = threading.Thread(target=user_input_thread_fn, name='user_input_thread') + user_input_thread.daemon = True + user_input_thread.start() + + except Exception as e: + exit(e) + + # Wait for the sample to finish (user types 'quit', or an error occurs) + is_sample_done.wait() diff --git a/samples/shadow_mqtt5.md b/samples/deprecated/shadow_mqtt5.md similarity index 99% rename from samples/shadow_mqtt5.md rename to samples/deprecated/shadow_mqtt5.md index cc2dfb5d..14ca79cd 100644 --- a/samples/shadow_mqtt5.md +++ b/samples/deprecated/shadow_mqtt5.md @@ -1,6 +1,6 @@ # Shadow MQTT5 -[**Return to main sample list**](./README.md) +[**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. diff --git a/samples/shadow_mqtt5.py b/samples/deprecated/shadow_mqtt5.py similarity index 100% rename from samples/shadow_mqtt5.py rename to samples/deprecated/shadow_mqtt5.py diff --git a/samples/deprecated/utils/command_line_utils.py b/samples/deprecated/utils/command_line_utils.py new file mode 100644 index 00000000..81f9925e --- /dev/null +++ b/samples/deprecated/utils/command_line_utils.py @@ -0,0 +1,922 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0. + +import argparse +from awscrt import io +from uuid import uuid4 + +class CommandLineUtils: + def __init__(self, description) -> None: + self.parser = argparse.ArgumentParser(description="Send and receive messages through and MQTT connection.") + self.commands = {} + self.parsed_commands = None + + def register_command(self, command_name, example_input, help_output, required=False, type=None, default=None, choices=None, action=None): + self.commands[command_name] = { + "name":command_name, + "example_input":example_input, + "help_output":help_output, + "required": required, + "type": type, + "default": default, + "choices": choices, + "action": action + } + + def remove_command(self, command_name): + if command_name in self.commands.keys(): + self.commands.pop(command_name) + + """ + Returns the command if it exists and has been passed to the console, otherwise it will print the help for the sample and exit the application. + """ + def get_command_required(self, command_name, command_name_alt = None): + if(command_name_alt != None): + if hasattr(self.parsed_commands, command_name_alt): + if(getattr(self.parsed_commands, command_name_alt) != None): + return getattr(self.parsed_commands, command_name_alt) + + if hasattr(self.parsed_commands, command_name): + if(getattr(self.parsed_commands, command_name) != None): + return getattr(self.parsed_commands, command_name) + + self.parser.print_help() + print("Command --" + command_name + " required.") + exit() + + """ + Returns the command if it exists, has been passed to the console, and is not None. Otherwise it returns whatever is passed as the default. + """ + def get_command(self, command_name, default=None): + if hasattr(self.parsed_commands, command_name): + result = getattr(self.parsed_commands, command_name) + if (result != None): + return result + return default + + def get_args(self): + # if we have already parsed, then return the cached parsed commands + if self.parsed_commands is not None: + return self.parsed_commands + + # add all the commands + for command in self.commands.values(): + if not command["action"] is None: + self.parser.add_argument("--" + command["name"], action=command["action"], help=command["help_output"], + required=command["required"], default=command["default"]) + else: + self.parser.add_argument("--" + command["name"], metavar=command["example_input"], help=command["help_output"], + required=command["required"], type=command["type"], default=command["default"], choices=command["choices"]) + + self.parsed_commands = self.parser.parse_args() + # Automatically start logging if it is set + if self.parsed_commands.verbosity: + io.init_logging(getattr(io.LogLevel, self.parsed_commands.verbosity), 'stderr') + + return self.parsed_commands + + def update_command(self, command_name, new_example_input=None, new_help_output=None, new_required=None, new_type=None, new_default=None, new_action=None): + if command_name in self.commands.keys(): + if new_example_input: + self.commands[command_name]["example_input"] = new_example_input + if new_help_output: + self.commands[command_name]["help_output"] = new_help_output + if new_required: + self.commands[command_name]["required"] = new_required + if new_type: + self.commands[command_name]["type"] = new_type + if new_default: + self.commands[command_name]["default"] = new_default + if new_action: + self.commands[command_name]["action"] = new_action + + def add_common_mqtt_commands(self): + self.register_command( + CommandLineUtils.m_cmd_endpoint, + "", + "The endpoint of the mqtt server not including a port.", + True, + str) + self.register_command( + CommandLineUtils.m_cmd_ca_file, + "", + "Path to AmazonRootCA1.pem (optional, system trust store used by default)", + False, + str) + self.register_command( + CommandLineUtils.m_cmd_is_ci, + "", + "If present the sample will run in CI mode (optional, default='None')", + False, + str) + + def add_common_mqtt5_commands(self): + self.register_command( + CommandLineUtils.m_cmd_endpoint, + "", + "The endpoint of the mqtt server not including a port.", + True, + str) + self.register_command( + CommandLineUtils.m_cmd_ca_file, + "", + "Path to AmazonRootCA1.pem (optional, system trust store used by default)", + False, + str) + self.register_command( + CommandLineUtils.m_cmd_is_ci, + "", + "If present the sample will run in CI mode (optional, default='None')", + False, + str) + + def add_common_proxy_commands(self): + self.register_command( + CommandLineUtils.m_cmd_proxy_host, + "", + "Host name of the proxy server to connect through (optional)", + False, + str) + self.register_command( + CommandLineUtils.m_cmd_proxy_port, + "", + "Port of the http proxy to use (optional, default='8080')", + type=int, + default=8080) + + def add_common_topic_message_commands(self): + self.register_command( + CommandLineUtils.m_cmd_topic, + "", + "Topic to publish, subscribe to (optional, default='test/topic').", + default="test/topic") + self.register_command( + CommandLineUtils.m_cmd_message, + "", + "The message to send in the payload (optional, default='Hello World!').", + default="Hello World! ") + + def add_common_logging_commands(self): + self.register_command( + CommandLineUtils.m_cmd_verbosity, + "", + "Logging level.", + default=io.LogLevel.NoLogs.name, + choices=[ + x.name for x in io.LogLevel]) + + def add_common_key_cert_commands(self): + self.register_command(CommandLineUtils.m_cmd_key_file, "", "Path to your key in PEM format.", True, str) + self.register_command(CommandLineUtils.m_cmd_cert_file, "", "Path to your client certificate in PEM format.", True, str) + + def add_common_custom_authorizer_commands(self): + self.register_command( + CommandLineUtils.m_cmd_custom_auth_username, + "", + "The name to send when connecting through the custom authorizer (optional)") + self.register_command( + CommandLineUtils.m_cmd_custom_auth_authorizer_name, + "", + "The name of the custom authorizer to connect to (optional but required for everything but custom domains)") + self.register_command( + CommandLineUtils.m_cmd_custom_auth_authorizer_signature, + "", + "The signature to send when connecting through a custom authorizer (optional)") + self.register_command( + CommandLineUtils.m_cmd_custom_auth_password, + "", + "The password to send when connecting through a custom authorizer (optional)") + self.register_command( + CommandLineUtils.m_cmd_custom_auth_token_key_name, + "", + "Key used to extract the custom authorizer token (optional)") + self.register_command( + CommandLineUtils.m_cmd_custom_auth_token_value, + "", + "The opaque token value for the custom authorizer (optional)") + + def add_common_x509_commands(self): + self.register_command( + CommandLineUtils.m_cmd_x509_endpoint, + "", + "The credentials endpoint to fetch x509 credentials from", + ) + self.register_command( + CommandLineUtils.m_cmd_x509_thing_name, + "", + "Thing name to fetch x509 credentials on behalf of" + ) + self.register_command( + CommandLineUtils.m_cmd_x509_role_alias, + "", + "Role alias to use with the x509 credentials provider" + ) + self.register_command( + CommandLineUtils.m_cmd_x509_key, + "", + "Path to the IoT thing private key used in fetching x509 credentials" + ) + self.register_command( + CommandLineUtils.m_cmd_x509_cert, + "", + "Path to the IoT thing certificate used in fetching x509 credentials" + ) + + self.register_command( + CommandLineUtils.m_cmd_x509_ca, + "", + "Path to the root certificate used in fetching x509 credentials" + ) + + ######################################################################## + # cmdData utils/functions + ######################################################################## + + class CmdData: + # General use + input_endpoint : str + input_cert : str + input_key : str + input_ca : str + input_clientId : str + input_port : int + input_is_ci : bool + input_use_websockets : bool + # Proxy + input_proxy_host : str + input_proxy_port : int + # PubSub + input_topic : str + input_message : str + input_count : int + # Websockets + input_signing_region : str + # Cognito + input_cognito_identity : str + # Custom auth + input_custom_auth_username : str + input_custom_authorizer_name : str + input_custom_authorizer_signature : str + input_custom_auth_password : str + input_custom_authorizer_token_key_name : str + input_custom_authorizer_token_value : str + # Fleet provisioning + input_template_name : str + input_template_parameters : str + input_csr_path : str + # Services (Shadow, Jobs, Greengrass, etc) + input_thing_name : str + input_mode : str + # Shared Subscription + input_group_identifier : str + # PKCS#11 + input_pkcs11_lib_path : str + input_pkcs11_user_pin : str + input_pkcs11_token_label : str + input_pkcs11_slot_id : int + input_pkcs11_key_label : str + # X509 + input_x509_endpoint : str + input_x509_role : str + input_x509_thing_name : str + input_x509_cert : str + input_x509_key : str + input_x509_ca : str + # Basic discovery + input_max_pub_ops : int + input_print_discovery_resp_only : bool + # Jobs + input_job_time : int + # Shadow + input_shadow_property : str + input_shadow_value : str + input_shadow_name : str + # PKCS12 + input_pkcs12_file : str + input_pkcs12_password : str + # Static credentials + input_session_token : str + input_access_key_id : str + input_secret_access_key : str + + def __init__(self) -> None: + pass + + def parse_input_topic(self, cmdUtils): + self.input_topic = cmdUtils.get_command(CommandLineUtils.m_cmd_topic, "test/topic") + if (cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci) != None): + self.input_topic += "/" + str(uuid4()) + + def parse_sample_input_basic_connect(): + # Parse arguments + cmdUtils = CommandLineUtils("Basic Connect - Make a MQTT connection.") + cmdUtils.add_common_mqtt_commands() + cmdUtils.add_common_proxy_commands() + cmdUtils.add_common_logging_commands() + cmdUtils.add_common_key_cert_commands() + cmdUtils.register_command(CommandLineUtils.m_cmd_port, "", + "Connection port for direct connection. " + + "AWS IoT supports 443 and 8883 (optional, default=8883).", + False, int) + cmdUtils.register_command(CommandLineUtils.m_cmd_client_id, "", + "Client ID to use for MQTT connection (optional, default='test-*').", + default="test-" + str(uuid4())) + # Needs to be called so the command utils parse the commands + cmdUtils.get_args() + + cmdData = CommandLineUtils.CmdData() + cmdData.input_endpoint = cmdUtils.get_command_required(CommandLineUtils.m_cmd_endpoint) + cmdData.input_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_port, 8883)) + cmdData.input_cert = cmdUtils.get_command_required(CommandLineUtils.m_cmd_cert_file) + cmdData.input_key = cmdUtils.get_command_required(CommandLineUtils.m_cmd_key_file) + cmdData.input_ca = cmdUtils.get_command(CommandLineUtils.m_cmd_ca_file, None) + cmdData.input_clientId = cmdUtils.get_command(CommandLineUtils.m_cmd_client_id, "test-" + str(uuid4())) + cmdData.input_proxy_host = cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_host) + cmdData.input_proxy_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_port)) + cmdData.input_is_ci = cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci, None) != None + return cmdData + + def parse_sample_input_basic_discovery(): + allowed_actions = ['both', 'publish', 'subscribe'] + + cmdUtils = CommandLineUtils("Basic Discovery - Greengrass discovery example.") + cmdUtils.add_common_mqtt_commands() + cmdUtils.add_common_topic_message_commands() + cmdUtils.add_common_logging_commands() + cmdUtils.add_common_key_cert_commands() + cmdUtils.remove_command(CommandLineUtils.m_cmd_endpoint) + cmdUtils.register_command(CommandLineUtils.m_cmd_thing_name, "", "The name assigned to your IoT Thing", required=True) + cmdUtils.register_command( + CommandLineUtils.m_cmd_mode, "", + f"The operation mode (optional, default='both').\nModes:{allowed_actions}", default='both') + cmdUtils.register_command(CommandLineUtils.m_cmd_region, "", "The region to connect through.", required=True) + cmdUtils.register_command( + CommandLineUtils.m_cmd_max_pub_ops, "", + "The maximum number of publish operations (optional, default='10').", + default=10, type=int) + cmdUtils.register_command( + CommandLineUtils.m_cmd_print_discovery_resp_only, "", "(optional, default='False').", + default=False, type=bool, action="store_true") + cmdUtils.add_common_proxy_commands() + cmdUtils.get_args() + + cmdData = CommandLineUtils.CmdData() + cmdData.parse_input_topic(cmdUtils) + cmdData.input_message = cmdUtils.get_command(CommandLineUtils.m_cmd_message, "Hello World! ") + cmdData.input_cert = cmdUtils.get_command_required(CommandLineUtils.m_cmd_cert_file) + cmdData.input_key = cmdUtils.get_command_required(CommandLineUtils.m_cmd_key_file) + cmdData.input_ca = cmdUtils.get_command(CommandLineUtils.m_cmd_ca_file, None) + cmdData.input_thing_name = cmdUtils.get_command_required(CommandLineUtils.m_cmd_thing_name) + cmdData.input_mode = cmdUtils.get_command(CommandLineUtils.m_cmd_mode, "both") + cmdData.input_signing_region = cmdUtils.get_command_required(CommandLineUtils.m_cmd_region, CommandLineUtils.m_cmd_signing_region) + cmdData.input_max_pub_ops = int(cmdUtils.get_command(CommandLineUtils.m_cmd_max_pub_ops, 10)) + cmdData.input_print_discovery_resp_only = bool(cmdUtils.get_command(CommandLineUtils.m_cmd_print_discovery_resp_only, False)) + cmdData.input_proxy_host = cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_host) + cmdData.input_proxy_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_port)) + cmdData.input_is_ci = cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci, None) != None + return cmdData + + def parse_sample_input_cognito_connect(): + cmdUtils = CommandLineUtils("Cognito Connect - Make a Cognito MQTT connection.") + cmdUtils.add_common_mqtt_commands() + cmdUtils.add_common_proxy_commands() + cmdUtils.add_common_logging_commands() + cmdUtils.register_command(CommandLineUtils.m_cmd_signing_region, "", + "The signing region used for the websocket signer", + False, str) + cmdUtils.register_command(CommandLineUtils.m_cmd_region, "", + "The signing region used for the websocket signer", + False, str) + cmdUtils.register_command(CommandLineUtils.m_cmd_client_id, "", + "Client ID to use for MQTT connection (optional, default='test-*').", + default="test-" + str(uuid4())) + cmdUtils.register_command(CommandLineUtils.m_cmd_cognito_identity, "", + "The Cognito identity ID to use to connect via Cognito", + True, str) + cmdUtils.get_args() + + cmdData = CommandLineUtils.CmdData() + cmdData.input_endpoint = cmdUtils.get_command_required(CommandLineUtils.m_cmd_endpoint) + cmdData.input_signing_region = cmdUtils.get_command_required(CommandLineUtils.m_cmd_signing_region, CommandLineUtils.m_cmd_region) + cmdData.input_cognito_identity = cmdUtils.get_command_required(CommandLineUtils.m_cmd_cognito_identity) + cmdData.input_clientId = cmdUtils.get_command(CommandLineUtils.m_cmd_client_id, "test-" + str(uuid4())) + cmdData.input_proxy_host = cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_host) + cmdData.input_proxy_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_port)) + cmdData.input_is_ci = cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci, None) != None + return cmdData + + def parse_sample_input_custom_authorizer_connect(): + cmdUtils = CommandLineUtils( + "Custom Authorizer Connect - Make a MQTT connection using a custom authorizer.") + cmdUtils.add_common_mqtt_commands() + cmdUtils.add_common_logging_commands() + cmdUtils.add_common_custom_authorizer_commands() + cmdUtils.register_command(CommandLineUtils.m_cmd_client_id, "", + "Client ID to use for MQTT connection (optional, default='test-*').", + default="test-" + str(uuid4())) + cmdUtils.get_args() + + cmdData = CommandLineUtils.CmdData() + cmdData.input_endpoint = cmdUtils.get_command_required(CommandLineUtils.m_cmd_endpoint) + cmdData.input_custom_authorizer_name = cmdUtils.get_command(CommandLineUtils.m_cmd_custom_auth_authorizer_name) + cmdData.input_custom_authorizer_signature = cmdUtils.get_command(CommandLineUtils.m_cmd_custom_auth_authorizer_signature) + cmdData.input_custom_auth_password = cmdUtils.get_command(CommandLineUtils.m_cmd_custom_auth_password) + cmdData.input_custom_auth_username = cmdUtils.get_command(CommandLineUtils.m_cmd_custom_auth_username) + cmdData.input_custom_authorizer_token_key_name = cmdUtils.get_command(CommandLineUtils.m_cmd_custom_auth_token_key_name) + cmdData.input_custom_authorizer_token_value = cmdUtils.get_command(CommandLineUtils.m_cmd_custom_auth_token_value) + cmdData.input_clientId = cmdUtils.get_command(CommandLineUtils.m_cmd_client_id, "test-" + str(uuid4())) + cmdData.input_is_ci = cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci, None) != None + return cmdData + + def parse_sample_input_static_credentials_connect(): + cmdUtils = CommandLineUtils( + "Static Credentials Connect - Make a MQTT connection using Static Credentials.") + cmdUtils.add_common_mqtt_commands() + cmdUtils.add_common_logging_commands() + cmdUtils.register_command(CommandLineUtils.m_cmd_client_id, "", + "Client ID to use for MQTT connection (optional, default='test-*').", + default="test-" + str(uuid4())) + cmdUtils.register_command(CommandLineUtils.m_cmd_session_token, "", "", default="test-" + str(uuid4())) + cmdUtils.register_command(CommandLineUtils.m_cmd_access_key_id, "", "", type=int) + cmdUtils.register_command(CommandLineUtils.m_cmd_secret_access_key, "", "") + cmdUtils.get_args() + + cmdData = CommandLineUtils.CmdData() + cmdData.input_endpoint = cmdUtils.get_command_required(CommandLineUtils.m_cmd_endpoint) + cmdData.input_session_token = cmdUtils.get_command(CommandLineUtils.m_cmd_session_token) + cmdData.input_access_key_id = cmdUtils.get_command(CommandLineUtils.m_cmd_access_key_id) + cmdData.input_secret_access_key = cmdUtils.get_command(CommandLineUtils.m_secret_access_key) + cmdData.input_clientId = cmdUtils.get_command(CommandLineUtils.m_cmd_client_id, "test-" + str(uuid4())) + cmdData.input_is_ci = cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci, None) != None + return cmdData + + def parse_sample_input_fleet_provisioning(): + cmdUtils = CommandLineUtils("Fleet Provisioning - Provision device using either the keys or CSR.") + cmdUtils.add_common_mqtt_commands() + cmdUtils.add_common_proxy_commands() + cmdUtils.add_common_logging_commands() + cmdUtils.add_common_key_cert_commands() + cmdUtils.register_command(CommandLineUtils.m_cmd_client_id, "", "Client ID to use for MQTT connection (optional, default='test-*').", default="test-" + str(uuid4())) + cmdUtils.register_command(CommandLineUtils.m_cmd_port, "", "Connection port. AWS IoT supports 443 and 8883 (optional, default=8883).", type=int) + cmdUtils.register_command(CommandLineUtils.m_cmd_csr, "", "Path to CSR in Pem format (optional).") + cmdUtils.register_command(CommandLineUtils.m_cmd_template_name, "", "The name of your provisioning template.") + cmdUtils.register_command(CommandLineUtils.m_cmd_template_parameters, "", "Template parameters json.") + cmdUtils.register_command(CommandLineUtils.m_cmd_mqtt_version, "", "MQTT Version") + cmdUtils.get_args() + + cmdData = CommandLineUtils.CmdData() + cmdData.input_endpoint = cmdUtils.get_command_required(CommandLineUtils.m_cmd_endpoint) + cmdData.input_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_port, 8883)) + cmdData.input_cert = cmdUtils.get_command_required(CommandLineUtils.m_cmd_cert_file) + cmdData.input_key = cmdUtils.get_command_required(CommandLineUtils.m_cmd_key_file) + cmdData.input_ca = cmdUtils.get_command(CommandLineUtils.m_cmd_ca_file, None) + cmdData.input_clientId = cmdUtils.get_command(CommandLineUtils.m_cmd_client_id, "test-" + str(uuid4())) + cmdData.input_proxy_host = cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_host) + cmdData.input_proxy_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_port)) + cmdData.input_csr_path = cmdUtils.get_command(CommandLineUtils.m_cmd_csr, None) + cmdData.input_template_name = cmdUtils.get_command_required(CommandLineUtils.m_cmd_template_name) + cmdData.input_template_parameters = cmdUtils.get_command_required(CommandLineUtils.m_cmd_template_parameters) + cmdData.input_is_ci = cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci, None) != None + cmdData.input_mqtt_version = int(cmdUtils.get_command(CommandLineUtils.m_cmd_mqtt_version, 5)) + return cmdData + + def parse_sample_input_jobs(): + cmdUtils = CommandLineUtils("Jobs - Receive and execute operations on the device.") + cmdUtils.add_common_mqtt_commands() + cmdUtils.add_common_proxy_commands() + cmdUtils.add_common_logging_commands() + cmdUtils.add_common_key_cert_commands() + cmdUtils.register_command(CommandLineUtils.m_cmd_client_id, "", "Client ID to use for MQTT connection (optional, default='test-*').", default="test-" + str(uuid4())) + cmdUtils.register_command(CommandLineUtils.m_cmd_port, "", "Connection port. AWS IoT supports 443 and 8883 (optional, default=8883).", type=int) + cmdUtils.register_command(CommandLineUtils.m_cmd_thing_name, "", "The name assigned to your IoT Thing", required=True) + cmdUtils.register_command(CommandLineUtils.m_cmd_job_time, "", "Emulate working on a job by sleeping this many seconds (optional, default='5')", default=5, type=int) + cmdUtils.register_command(CommandLineUtils.m_cmd_mqtt_version, "", "mqtt version (optional, default='5')", default=5, type=int) + cmdUtils.get_args() + + cmdData = CommandLineUtils.CmdData() + cmdData.input_endpoint = cmdUtils.get_command_required(CommandLineUtils.m_cmd_endpoint) + cmdData.input_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_port, 8883)) + cmdData.input_cert = cmdUtils.get_command_required(CommandLineUtils.m_cmd_cert_file) + cmdData.input_key = cmdUtils.get_command_required(CommandLineUtils.m_cmd_key_file) + cmdData.input_ca = cmdUtils.get_command(CommandLineUtils.m_cmd_ca_file, None) + cmdData.input_clientId = cmdUtils.get_command(CommandLineUtils.m_cmd_client_id, "test-" + str(uuid4())) + cmdData.input_proxy_host = cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_host) + cmdData.input_proxy_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_port)) + cmdData.input_thing_name = cmdUtils.get_command_required(CommandLineUtils.m_cmd_thing_name) + cmdData.input_job_time = int(cmdUtils.get_command(CommandLineUtils.m_cmd_job_time, 5)) + cmdData.input_is_ci = cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci, None) != None + cmdData.input_mqtt_version = int(cmdUtils.get_command(CommandLineUtils.m_cmd_mqtt_version, 5)) + return cmdData + + def parse_sample_input_mqtt5_custom_authorizer_connect(): + cmdUtils = CommandLineUtils( + "Custom Authorizer Connect - Make a MQTT5 Client connection using a custom authorizer.") + cmdUtils.add_common_mqtt_commands() + cmdUtils.add_common_logging_commands() + cmdUtils.add_common_custom_authorizer_commands() + cmdUtils.register_command(CommandLineUtils.m_cmd_client_id, "", + "Client ID to use for MQTT connection (optional, default='test-*').", + default="test-" + str(uuid4())) + cmdUtils.register_command(CommandLineUtils.m_cmd_use_websockets, "", "If set, websockets will be used (optional, do not set to use direct MQTT)") + cmdUtils.get_args() + + cmdData = CommandLineUtils.CmdData() + cmdData.input_endpoint = cmdUtils.get_command_required(CommandLineUtils.m_cmd_endpoint) + cmdData.input_ca = cmdUtils.get_command(CommandLineUtils.m_cmd_ca_file, None) + cmdData.input_custom_authorizer_name = cmdUtils.get_command(CommandLineUtils.m_cmd_custom_auth_authorizer_name) + cmdData.input_custom_authorizer_signature = cmdUtils.get_command(CommandLineUtils.m_cmd_custom_auth_authorizer_signature) + cmdData.input_custom_auth_password = cmdUtils.get_command(CommandLineUtils.m_cmd_custom_auth_password) + cmdData.input_custom_auth_username = cmdUtils.get_command(CommandLineUtils.m_cmd_custom_auth_username) + cmdData.input_custom_authorizer_token_key_name = cmdUtils.get_command(CommandLineUtils.m_cmd_custom_auth_token_key_name) + cmdData.input_custom_authorizer_token_value = cmdUtils.get_command(CommandLineUtils.m_cmd_custom_auth_token_value) + cmdData.input_clientId = cmdUtils.get_command(CommandLineUtils.m_cmd_client_id, "test-" + str(uuid4())) + cmdData.input_use_websockets = bool(cmdUtils.get_command(CommandLineUtils.m_cmd_use_websockets, False)) + cmdData.input_is_ci = cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci, None) != None + return cmdData + + def parse_sample_input_mqtt5_pkcs11_connect(): + cmdUtils = CommandLineUtils("MQTT5 PKCS11 Connect - Make a MQTT5 Client connection using PKCS11.") + cmdUtils.add_common_mqtt5_commands() + cmdUtils.add_common_proxy_commands() + cmdUtils.add_common_logging_commands() + cmdUtils.register_command(CommandLineUtils.m_cmd_cert_file, "", "Path to your client certificate in PEM format.", True, str) + cmdUtils.register_command( + CommandLineUtils.m_cmd_port, + "", + "Connection port. AWS IoT supports 433 and 8883 (optional, default=8883).", + type=int) + cmdUtils.register_command( + CommandLineUtils.m_cmd_client_id, + "", + "Client ID to use for MQTT5 connection (optional, default=None).", + default="test-" + str(uuid4())) + cmdUtils.register_command(CommandLineUtils.m_cmd_pkcs11_lib, "", "Path to PKCS#11 Library", required=True) + cmdUtils.register_command(CommandLineUtils.m_cmd_pkcs11_pin, "", "User PIN for logging into PKCS#11 token.", required=True) + cmdUtils.register_command(CommandLineUtils.m_cmd_pkcs11_token, "", "Label of the PKCS#11 token to use (optional).") + cmdUtils.register_command(CommandLineUtils.m_cmd_pkcs11_slot, "", "Slot ID containing the PKCS#11 token to use (optional).", False, int) + cmdUtils.register_command(CommandLineUtils.m_cmd_pkcs11_key, "", "Label of private key on the PKCS#11 token (optional).") + cmdUtils.get_args() + + cmdData = CommandLineUtils.CmdData() + cmdData.input_endpoint = cmdUtils.get_command_required(CommandLineUtils.m_cmd_endpoint) + cmdData.input_proxy_host = cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_host) + cmdData.input_proxy_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_port)) + cmdData.input_cert = cmdUtils.get_command_required(CommandLineUtils.m_cmd_cert_file) + cmdData.input_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_port, 8883)) + cmdData.input_ca = cmdUtils.get_command(CommandLineUtils.m_cmd_ca_file, None) + cmdData.input_clientId = cmdUtils.get_command(CommandLineUtils.m_cmd_client_id, "test-" + str(uuid4())) + cmdData.input_pkcs11_lib_path = cmdUtils.get_command_required(CommandLineUtils.m_cmd_pkcs11_lib) + cmdData.input_pkcs11_user_pin = cmdUtils.get_command_required(CommandLineUtils.m_cmd_pkcs11_pin) + cmdData.input_pkcs11_token_label = cmdUtils.get_command_required(CommandLineUtils.m_cmd_pkcs11_token) + cmdData.input_pkcs11_slot_id = cmdUtils.get_command(CommandLineUtils.m_cmd_pkcs11_slot, None) + cmdData.input_pkcs11_key_label = cmdUtils.get_command(CommandLineUtils.m_cmd_pkcs11_key, None) + cmdData.input_is_ci = cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci, None) != None + return cmdData + + def parse_sample_input_mqtt5_pubsub(): + cmdUtils = CommandLineUtils("PubSub - Send and receive messages through an MQTT5 connection.") + cmdUtils.add_common_mqtt5_commands() + cmdUtils.add_common_topic_message_commands() + cmdUtils.add_common_proxy_commands() + cmdUtils.add_common_logging_commands() + cmdUtils.add_common_key_cert_commands() + cmdUtils.register_command( + CommandLineUtils.m_cmd_port, + "", + "Connection port. AWS IoT supports 433 and 8883 (optional, default=8883).", + type=int) + cmdUtils.register_command( + CommandLineUtils.m_cmd_client_id, + "", + "Client ID to use for MQTT5 connection (optional, default=None).", + default="test-" + str(uuid4())) + cmdUtils.register_command( + CommandLineUtils.m_cmd_count, + "", + "The number of messages to send (optional, default='10').", + default=10, + type=int) + cmdUtils.get_args() + + cmdData = CommandLineUtils.CmdData() + cmdData.input_endpoint = cmdUtils.get_command_required(CommandLineUtils.m_cmd_endpoint) + cmdData.input_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_port, 8883)) + cmdData.input_cert = cmdUtils.get_command_required(CommandLineUtils.m_cmd_cert_file) + cmdData.input_key = cmdUtils.get_command_required(CommandLineUtils.m_cmd_key_file) + cmdData.input_ca = cmdUtils.get_command(CommandLineUtils.m_cmd_ca_file, None) + cmdData.input_clientId = cmdUtils.get_command(CommandLineUtils.m_cmd_client_id, "test-" + str(uuid4())) + cmdData.input_proxy_host = cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_host) + cmdData.input_proxy_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_port)) + cmdData.input_message = cmdUtils.get_command(CommandLineUtils.m_cmd_message, "Hello World! ") + cmdData.parse_input_topic(cmdUtils) + cmdData.input_count = int(cmdUtils.get_command(CommandLineUtils.m_cmd_count, 10)) + cmdData.input_is_ci = cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci, None) != None + return cmdData + + def parse_sample_input_mqtt5_shared_subscription(): + cmdUtils = CommandLineUtils("SharedSubscription - Send and receive messages through a MQTT5 shared subscription") + cmdUtils.add_common_mqtt5_commands() + cmdUtils.add_common_topic_message_commands() + cmdUtils.add_common_proxy_commands() + cmdUtils.add_common_logging_commands() + cmdUtils.add_common_key_cert_commands() + cmdUtils.register_command( + CommandLineUtils.m_cmd_port, + "", + "Connection port. AWS IoT supports 433 and 8883 (optional, default=8883).", + type=int) + cmdUtils.register_command( + CommandLineUtils.m_cmd_client_id, + "", + "Client ID to use for MQTT5 connection (optional, default=None)." + "Note that '1', '2', and '3' will be added for to the given clientIDs since this sample uses 3 clients.", + default="test-" + str(uuid4())) + cmdUtils.register_command( + CommandLineUtils.m_cmd_count, + "", + "The number of messages to send (optional, default='10').", + default=10, + type=int) + cmdUtils.register_command( + CommandLineUtils.m_cmd_group_identifier, + "", + "The group identifier to use in the shared subscription (optional, default='python-sample')", + default="python-sample", + type=str) + cmdUtils.get_args() + + cmdData = CommandLineUtils.CmdData() + cmdData.input_endpoint = cmdUtils.get_command_required(CommandLineUtils.m_cmd_endpoint) + cmdData.input_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_port, 8883)) + cmdData.input_cert = cmdUtils.get_command_required(CommandLineUtils.m_cmd_cert_file) + cmdData.input_key = cmdUtils.get_command_required(CommandLineUtils.m_cmd_key_file) + cmdData.input_ca = cmdUtils.get_command(CommandLineUtils.m_cmd_ca_file, None) + cmdData.input_clientId = cmdUtils.get_command(CommandLineUtils.m_cmd_client_id, "test-" + str(uuid4())) + cmdData.input_proxy_host = cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_host) + cmdData.input_proxy_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_port)) + cmdData.input_message = cmdUtils.get_command(CommandLineUtils.m_cmd_message, "Hello World! ") + cmdData.parse_input_topic(cmdUtils) + cmdData.input_count = cmdUtils.get_command(CommandLineUtils.m_cmd_count, 10) + cmdData.input_group_identifier = cmdUtils.get_command(CommandLineUtils.m_cmd_group_identifier, "python-sample") + cmdData.input_is_ci = cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci, None) != None + return cmdData + + def parse_sample_input_pkcs11_connect(): + cmdUtils = CommandLineUtils("PKCS11 Connect - Make a MQTT connection using PKCS11.") + cmdUtils.add_common_mqtt_commands() + cmdUtils.add_common_proxy_commands() + cmdUtils.add_common_logging_commands() + cmdUtils.register_command(CommandLineUtils.m_cmd_cert_file, "", "Path to your client certificate in PEM format.", True, str) + cmdUtils.register_command(CommandLineUtils.m_cmd_client_id, "", + "Client ID to use for MQTT connection (optional, default='test-*').", + default="test-" + str(uuid4())) + cmdUtils.register_command(CommandLineUtils.m_cmd_port, "", + "Connection port. AWS IoT supports 443 and 8883 (optional, default=8883).", + type=int) + cmdUtils.register_command(CommandLineUtils.m_cmd_pkcs11_lib, "", "Path to PKCS#11 Library", required=True) + cmdUtils.register_command(CommandLineUtils.m_cmd_pkcs11_pin, "", "User PIN for logging into PKCS#11 token.", required=True) + cmdUtils.register_command(CommandLineUtils.m_cmd_pkcs11_token, "", "Label of the PKCS#11 token to use (optional).") + cmdUtils.register_command(CommandLineUtils.m_cmd_pkcs11_slot, "", "Slot ID containing the PKCS#11 token to use (optional).", False, int) + cmdUtils.register_command(CommandLineUtils.m_cmd_pkcs11_key, "", "Label of private key on the PKCS#11 token (optional).") + cmdUtils.get_args() + + cmdData = CommandLineUtils.CmdData() + cmdData.input_endpoint = cmdUtils.get_command_required(CommandLineUtils.m_cmd_endpoint) + cmdData.input_proxy_host = cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_host) + cmdData.input_proxy_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_port)) + cmdData.input_cert = cmdUtils.get_command_required(CommandLineUtils.m_cmd_cert_file) + cmdData.input_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_port, 8883)) + cmdData.input_ca = cmdUtils.get_command(CommandLineUtils.m_cmd_ca_file, None) + cmdData.input_clientId = cmdUtils.get_command(CommandLineUtils.m_cmd_client_id, "test-" + str(uuid4())) + cmdData.input_pkcs11_lib_path = cmdUtils.get_command_required(CommandLineUtils.m_cmd_pkcs11_lib) + cmdData.input_pkcs11_user_pin = cmdUtils.get_command_required(CommandLineUtils.m_cmd_pkcs11_pin) + cmdData.input_pkcs11_token_label = cmdUtils.get_command_required(CommandLineUtils.m_cmd_pkcs11_token) + cmdData.input_pkcs11_slot_id = cmdUtils.get_command(CommandLineUtils.m_cmd_pkcs11_slot, None) + cmdData.input_pkcs11_key_label = cmdUtils.get_command(CommandLineUtils.m_cmd_pkcs11_key, None) + cmdData.input_is_ci = cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci, None) != None + return cmdData + + def parse_sample_input_pubsub(): + cmdUtils = CommandLineUtils("PubSub - Send and receive messages through an MQTT connection.") + cmdUtils.add_common_mqtt_commands() + cmdUtils.add_common_topic_message_commands() + cmdUtils.add_common_proxy_commands() + cmdUtils.add_common_logging_commands() + cmdUtils.add_common_key_cert_commands() + cmdUtils.register_command(CommandLineUtils.m_cmd_port, "", "Connection port. AWS IoT supports 443 and 8883 (optional, default=8883).", type=int) + cmdUtils.register_command(CommandLineUtils.m_cmd_client_id, "", "Client ID to use for MQTT connection (optional, default='test-*').", default="test-" + str(uuid4())) + cmdUtils.register_command(CommandLineUtils.m_cmd_count, "", "The number of messages to send (optional, default='10').", default=10, type=int) + cmdUtils.get_args() + + cmdData = CommandLineUtils.CmdData() + cmdData.input_endpoint = cmdUtils.get_command_required(CommandLineUtils.m_cmd_endpoint) + cmdData.input_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_port, 8883)) + cmdData.input_cert = cmdUtils.get_command_required(CommandLineUtils.m_cmd_cert_file) + cmdData.input_key = cmdUtils.get_command_required(CommandLineUtils.m_cmd_key_file) + cmdData.input_ca = cmdUtils.get_command(CommandLineUtils.m_cmd_ca_file, None) + cmdData.input_clientId = cmdUtils.get_command(CommandLineUtils.m_cmd_client_id, "test-" + str(uuid4())) + cmdData.input_proxy_host = cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_host) + cmdData.input_proxy_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_port)) + cmdData.input_message = cmdUtils.get_command(CommandLineUtils.m_cmd_message, "Hello World! ") + cmdData.parse_input_topic(cmdUtils) + cmdData.input_count = int(cmdUtils.get_command(CommandLineUtils.m_cmd_count, 10)) + cmdData.input_is_ci = cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci, None) != None + return cmdData + + def parse_sample_input_shadow(): + cmdUtils = CommandLineUtils("Shadow - Keep a property in sync between device and server.") + cmdUtils.add_common_mqtt_commands() + cmdUtils.add_common_proxy_commands() + cmdUtils.add_common_logging_commands() + cmdUtils.add_common_key_cert_commands() + cmdUtils.register_command(CommandLineUtils.m_cmd_port, "", "Connection port. AWS IoT supports 443 and 8883 (optional, default=8883).", type=int) + cmdUtils.register_command(CommandLineUtils.m_cmd_client_id, "", "Client ID to use for MQTT connection (optional, default='test-*').", default="test-" + str(uuid4())) + cmdUtils.register_command(CommandLineUtils.m_cmd_thing_name, "", "The name assigned to your IoT Thing", required=True) + cmdUtils.register_command(CommandLineUtils.m_cmd_shadow_property, "", "The name of the shadow property you want to change (optional, default=''", default="") + cmdUtils.register_command(CommandLineUtils.m_cmd_shadow_value, "", "The desired value of the shadow property you want to set (optional)") + cmdUtils.register_command(CommandLineUtils.m_cmd_shadow_name, "", "Shadow name (optional, default='')", type=str) + cmdUtils.register_command(CommandLineUtils.m_cmd_mqtt_version, "", "mqtt version (optional, default='5')", default=5, type=int) + cmdUtils.get_args() + + cmdData = CommandLineUtils.CmdData() + cmdData.input_endpoint = cmdUtils.get_command_required(CommandLineUtils.m_cmd_endpoint) + cmdData.input_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_port, 8883)) + cmdData.input_cert = cmdUtils.get_command_required(CommandLineUtils.m_cmd_cert_file) + cmdData.input_key = cmdUtils.get_command_required(CommandLineUtils.m_cmd_key_file) + cmdData.input_ca = cmdUtils.get_command(CommandLineUtils.m_cmd_ca_file, None) + cmdData.input_clientId = cmdUtils.get_command(CommandLineUtils.m_cmd_client_id, "test-" + str(uuid4())) + cmdData.input_proxy_host = cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_host) + cmdData.input_proxy_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_port)) + cmdData.input_thing_name = cmdUtils.get_command_required(CommandLineUtils.m_cmd_thing_name) + cmdData.input_shadow_property = cmdUtils.get_command_required(CommandLineUtils.m_cmd_shadow_property) + cmdData.input_shadow_value = cmdUtils.get_command(CommandLineUtils.m_cmd_shadow_value, None) + cmdData.input_shadow_name = cmdUtils.get_command(CommandLineUtils.m_cmd_shadow_name, None) + cmdData.input_is_ci = cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci, None) != None + cmdData.input_mqtt_version = int(cmdUtils.get_command(CommandLineUtils.m_cmd_mqtt_version, 5)) + return cmdData + + def parse_sample_input_websocket_connect(): + cmdUtils = CommandLineUtils("Websocket Connect - Make a websocket MQTT connection.") + cmdUtils.add_common_mqtt_commands() + cmdUtils.add_common_proxy_commands() + cmdUtils.add_common_logging_commands() + cmdUtils.register_command(CommandLineUtils.m_cmd_signing_region, "", + "The signing region used for the websocket signer", + False, str) + cmdUtils.register_command(CommandLineUtils.m_cmd_region, "", + "The signing region used for the websocket signer", + False, str) + cmdUtils.register_command(CommandLineUtils.m_cmd_client_id, "", + "Client ID to use for MQTT connection (optional, default='test-*').", + default="test-" + str(uuid4())) + cmdUtils.get_args() + + cmdData = CommandLineUtils.CmdData() + cmdData.input_endpoint = cmdUtils.get_command_required(CommandLineUtils.m_cmd_endpoint) + cmdData.input_signing_region = cmdUtils.get_command_required(CommandLineUtils.m_cmd_signing_region, CommandLineUtils.m_cmd_region) + cmdData.input_clientId = cmdUtils.get_command(CommandLineUtils.m_cmd_client_id, "test-" + str(uuid4())) + cmdData.input_proxy_host = cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_host) + cmdData.input_proxy_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_port)) + cmdData.input_is_ci = cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci, None) != None + return cmdData + + def parse_sample_input_windows_cert_connect(): + cmdUtils = CommandLineUtils("Windows Cert Connect - Make a MQTT connection using Windows Store Certificates.") + cmdUtils.add_common_mqtt_commands() + cmdUtils.add_common_logging_commands() + cmdUtils.register_command(CommandLineUtils.m_cmd_client_id, "", + "Client ID to use for MQTT connection (optional, default='test-*').", + default="test-" + str(uuid4())) + cmdUtils.register_command(CommandLineUtils.m_cmd_cert_file, "", "Path to certificate in Windows cert store. " + "e.g. \"CurrentUser\\MY\\6ac133ac58f0a88b83e9c794eba156a98da39b4c\"", True, str) + cmdUtils.register_command(CommandLineUtils.m_cmd_port, "", "Connection port. AWS IoT supports 443 and 8883 (optional, default=auto).", type=int) + cmdUtils.get_args() + + cmdData = CommandLineUtils.CmdData() + cmdData.input_endpoint = cmdUtils.get_command_required(CommandLineUtils.m_cmd_endpoint) + cmdData.input_ca = cmdUtils.get_command(CommandLineUtils.m_cmd_ca_file, None) + cmdData.input_cert = cmdUtils.get_command_required(CommandLineUtils.m_cmd_cert_file) + cmdData.input_clientId = cmdUtils.get_command(CommandLineUtils.m_cmd_client_id, "test-" + str(uuid4())) + cmdData.input_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_port, 8883)) + cmdData.input_is_ci = cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci, None) != None + return cmdData + + def parse_sample_input_x509_connect(): + cmdUtils = CommandLineUtils("X509 Connect - Make a MQTT connection using X509.") + cmdUtils.add_common_mqtt_commands() + cmdUtils.add_common_proxy_commands() + cmdUtils.add_common_logging_commands() + cmdUtils.add_common_x509_commands() + cmdUtils.register_command(CommandLineUtils.m_cmd_signing_region, "", + "The signing region used for the websocket signer", + False, str) + cmdUtils.register_command(CommandLineUtils.m_cmd_region, "", + "The signing region used for the websocket signer", + False, str) + cmdUtils.register_command(CommandLineUtils.m_cmd_client_id, "", + "Client ID to use for MQTT connection (optional, default='test-*').", + default="test-" + str(uuid4())) + cmdUtils.get_args() + + cmdData = CommandLineUtils.CmdData() + cmdData.input_endpoint = cmdUtils.get_command_required(CommandLineUtils.m_cmd_endpoint) + cmdData.input_signing_region = cmdUtils.get_command_required(CommandLineUtils.m_cmd_signing_region, CommandLineUtils.m_cmd_region) + cmdData.input_clientId = cmdUtils.get_command(CommandLineUtils.m_cmd_client_id, "test-" + str(uuid4())) + cmdData.input_proxy_host = cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_host) + cmdData.input_proxy_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_port)) + cmdData.input_x509_endpoint = cmdUtils.get_command_required(CommandLineUtils.m_cmd_x509_endpoint) + cmdData.input_x509_thing_name = cmdUtils.get_command_required(CommandLineUtils.m_cmd_x509_thing_name) + cmdData.input_x509_role = cmdUtils.get_command_required(CommandLineUtils.m_cmd_x509_role_alias) + cmdData.input_x509_cert = cmdUtils.get_command_required(CommandLineUtils.m_cmd_x509_cert) + cmdData.input_x509_key = cmdUtils.get_command_required(CommandLineUtils.m_cmd_x509_key) + cmdData.input_x509_ca = cmdUtils.get_command(CommandLineUtils.m_cmd_x509_ca, None) + cmdData.input_is_ci = cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci, None) != None + return cmdData + + def parse_sample_input_pkcs12_connect(): + # Parse arguments + cmdUtils = CommandLineUtils("PKCS12 Connect - Make a MQTT connection.") + cmdUtils.add_common_mqtt_commands() + cmdUtils.add_common_proxy_commands() + cmdUtils.add_common_logging_commands() + cmdUtils.register_command(CommandLineUtils.m_cmd_pkcs12_file, "", + "Path to the PKCS12 file to use.", True, str) + cmdUtils.register_command(CommandLineUtils.m_cmd_pkcs12_password, "", + "The password for the PKCS12 file.", False, str) + cmdUtils.register_command(CommandLineUtils.m_cmd_port, "", + "Connection port for direct connection. " + + "AWS IoT supports 443 and 8883 (optional, default=8883).", + False, int) + cmdUtils.register_command(CommandLineUtils.m_cmd_client_id, "", + "Client ID to use for MQTT connection (optional, default='test-*').", + default="test-" + str(uuid4())) + # Needs to be called so the command utils parse the commands + cmdUtils.get_args() + + cmdData = CommandLineUtils.CmdData() + cmdData.input_endpoint = cmdUtils.get_command_required(CommandLineUtils.m_cmd_endpoint) + cmdData.input_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_port, 8883)) + cmdData.input_pkcs12_file = cmdUtils.get_command_required(CommandLineUtils.m_cmd_pkcs12_file) + cmdData.input_pkcs12_password = cmdUtils.get_command_required(CommandLineUtils.m_cmd_pkcs12_password) + cmdData.input_ca = cmdUtils.get_command(CommandLineUtils.m_cmd_ca_file, None) + cmdData.input_clientId = cmdUtils.get_command(CommandLineUtils.m_cmd_client_id, "test-" + str(uuid4())) + cmdData.input_proxy_host = cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_host) + cmdData.input_proxy_port = int(cmdUtils.get_command(CommandLineUtils.m_cmd_proxy_port)) + cmdData.input_is_ci = cmdUtils.get_command(CommandLineUtils.m_cmd_is_ci, None) != None + return cmdData + + + # Constants for commonly used/needed commands + m_cmd_endpoint = "endpoint" + m_cmd_ca_file = "ca_file" + m_cmd_cert_file = "cert" + m_cmd_key_file = "key" + m_cmd_proxy_host = "proxy_host" + m_cmd_proxy_port = "proxy_port" + m_cmd_signing_region = "signing_region" + m_cmd_pkcs11_lib = "pkcs11_lib" + m_cmd_pkcs11_cert = "cert" + m_cmd_pkcs11_pin = "pin" + m_cmd_pkcs11_token = "token_label" + m_cmd_pkcs11_slot = "slot_id" + m_cmd_pkcs11_key = "key_label" + m_cmd_message = "message" + m_cmd_topic = "topic" + m_cmd_verbosity = "verbosity" + m_cmd_custom_auth_username = "custom_auth_username" + m_cmd_custom_auth_authorizer_name = "custom_auth_authorizer_name" + m_cmd_custom_auth_authorizer_signature = "custom_auth_authorizer_signature" + m_cmd_custom_auth_password = "custom_auth_password" + m_cmd_custom_auth_token_key_name = "custom_auth_token_key_name" + m_cmd_custom_auth_token_value = "custom_auth_token_value" + m_cmd_cognito_identity = "cognito_identity" + m_cmd_x509_endpoint = "x509_endpoint" + m_cmd_x509_thing_name = "x509_thing_name" + m_cmd_x509_role_alias = "x509_role_alias" + m_cmd_x509_cert = "x509_cert" + m_cmd_x509_key = "x509_key" + m_cmd_x509_ca = "x509_ca_file" + m_cmd_port = "port" + m_cmd_client_id = "client_id" + m_cmd_is_ci = "is_ci" + m_cmd_thing_name = "thing_name" + m_cmd_mode = "mode" + m_cmd_max_pub_ops = "max_pub_ops" + m_cmd_print_discovery_resp_only = "print_discover_resp_only" + m_cmd_csr = "csr" + m_cmd_template_name = "template_name" + m_cmd_template_parameters = "template_parameters" + m_cmd_job_time = "job_time" + m_cmd_use_websockets = "use_websockets" + m_cmd_count = "count" + m_cmd_group_identifier = "group_identifier" + m_cmd_shadow_property = "shadow_property" + m_cmd_shadow_value = "shadow_value" + m_cmd_shadow_name = "shadow_name" + m_cmd_pkcs12_file = "pkcs12_file" + m_cmd_pkcs12_password = "pkcs12_password" + m_cmd_region = "region" + m_cmd_mqtt_version = "mqtt_version" + m_cmd_session_token = "session_token" + m_cmd_secret_access_key = "secret_access_key" + m_cmd_access_key_id = "access_key_id" diff --git a/samples/fleet_provisioning_basic.md b/samples/fleet_provisioning_basic.md new file mode 100644 index 00000000..b182ae22 --- /dev/null +++ b/samples/fleet_provisioning_basic.md @@ -0,0 +1,271 @@ +# Basic Fleet provisioning + +[**Return to main sample list**](./README.md) + +This sample uses the AWS IoT [Fleet provisioning service](https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html) to provision devices using the CreateKeysAndCertificate and RegisterThing APIs. This allows you to create new AWS IoT Core thing resources using a Fleet Provisioning Template. + +The [IAM Policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) attached to your provisioning certificate must provide privileges for this sample to connect, subscribe, publish, and receive. Below is a sample policy that can be used 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/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/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/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 +First, from an empty directory, clone the SDK via git: +``` sh +git clone https://github.com/aws/aws-iot-device-sdk-python-v2 +``` +If not already active, activate the [virtual environment](https://docs.python.org/3/library/venv.html) that will be used to contain Python's execution context. + +If the venv does not yet have the device SDK installed, install it: + +``` sh +python3 -m pip install awsiotsdk +``` + +Assuming you are in the SDK root directory, you can now run the shadow sandbox sample: + +``` sh +# from the samples folder +python3 samples/fleet_provisioning_basic.py --endpoint --cert --key --template_name