diff --git a/samples/greengrass_v2/README.md b/samples/greengrass_v2/README.md new file mode 100644 index 00000000..9ab38873 --- /dev/null +++ b/samples/greengrass_v2/README.md @@ -0,0 +1,82 @@ +# Greengrass V2 samples using AWS IoT Device SDK v2 for Python + +You can find the full API documentation for the Greengrass V2 IPC interface using the Python SDK here: https://aws.github.io/aws-iot-device-sdk-python-v2/awsiot/greengrasscoreipc.html + +## Sample: Low-Level IPC + +Folder: `low_level_ipc/` + +Once installed and running, this sample publishes messages to AWS IoT Core. It uses the low-level Greengrass v2 [Inter-Process-Communication API](https://docs.aws.amazon.com/greengrass/v2/developerguide/interprocess-communication.html). + +See the other samples for higher-level APIs to reduce the amount of code you have to write and maintain. + +## Sample: Publish/Subscribe to the cloud with AWS IoT Core + +Folder: `pubsub_cloud` + +Once installed and running, this sample subscribes to the `hello/world` topic. You can use the [MQTT Test Client](https://console.aws.amazon.com/iot/home#/test) to publish a message to this topic, while also subscribing to `hello/world/response` in the MQTT Test Client. The Greengrass device will receive the message and reply back on the response topic. + +## Sample: Public/Subscribe on the local device between Greengrass components + +Folder: `pubsub_local` + +Once installed and running, this sample subscribes to the `hello/world` topic. You can use a second component to publish a message and receive a reply message on the `hello/world/response` topic. These messages are **not** sent to AWS IoT Core (the cloud). This pub/sub mechanism is only connecting different components running on the same Greengrass device. You can use the Greegrass CLI to publish or subscribe to these local topics: + +* `greengrass-cli pubsub sub --topic hello/world/response` +* `greengrass-cli pubsub pub --topic hello/world --message Hi!` + +## Sample: Shadow Management + +Folder: `shadows` + +Once installed and running, this sample will retrieve a named shadow `special_shadow` when the component first starts executing, and then periodically update the shadow document with a new reported state every few seconds. + +This component depends on the [AWS-provided ShadowManager component](https://docs.aws.amazon.com/greengrass/v2/developerguide/shadow-manager-component.html#shadow-manager-component-configuration). You [need to configure](https://docs.aws.amazon.com/greengrass/v2/developerguide/shadow-manager-component.html#shadow-manager-component-configuration) it to synchronize named shadows from the local device to the cloud: + +```yaml +strategy: + type: realTime +synchronize: + coreThing: + namedShadows: + - special_shadow + direction: betweenDeviceAndCloud +``` + +## Sample: Deployment Configuration + +Folder: `deployment_configuration` + +Once installed and running, this sample will retrieve the component's deployment configuration and start a web server based on the provided parameters. Re-deploying with different parameters will update the component and upon restart of the Python process, it will start the web server based on these new parameters. + +## Deployment Helpers + +Deploy component locally using Greengrass CLI: + +```bash +func gg_deploy() { + COMPONENT_NAME=$(sed -nr 's/ComponentName: ([a-zA-Z.-_]+)/\1/p' recipe.yaml) + COMPONENT_VERSION=$(sed -nr 's/ComponentVersion: (.+)/\1/p' recipe.yaml | tr -d '"' | tr -d "'") + + mkdir -p build/artifacts/$COMPONENT_NAME/$COMPONENT_VERSION/ + command cp code.py build/artifacts/$COMPONENT_NAME/$COMPONENT_VERSION/ + + mkdir -p build/recipes/ + command cp recipe.yaml build/recipes/$COMPONENT_NAME.yaml + + RECIPES=$PWD/build/recipes + ARTIFACTS=$PWD/build/artifacts + sudo /greengrass/v2/bin/greengrass-cli deployment create \ + --recipeDir=$RECIPES \ + --artifactDir=$ARTIFACTS \ + --merge=$COMPONENT_NAME=$COMPONENT_VERSION +} + +func gg_remove() { + COMPONENT_NAME=$(sed -nr 's/ComponentName: ([a-zA-Z.-_]+)/\1/p' recipe.yaml) + sudo /greengrass/v2/bin/greengrass-cli deployment create \ + --recipeDir=$RECIPES \ + --artifactDir=$ARTIFACTS \ + --remove=$COMPONENT_NAME +} +``` diff --git a/samples/greengrass_v2/deployment_configuration/code.py b/samples/greengrass_v2/deployment_configuration/code.py new file mode 100644 index 00000000..66591019 --- /dev/null +++ b/samples/greengrass_v2/deployment_configuration/code.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0. + +import time +import os +import http.server +import socketserver + +from awsiot.greengrasscoreipc.clientv2 import GreengrassCoreIPCClientV2 + +client = GreengrassCoreIPCClientV2() +thing_name = os.environ["AWS_IOT_THING_NAME"] + + +def main(): + # https://docs.aws.amazon.com/greengrass/v2/developerguide/ipc-component-configuration.html + config = client.get_configuration().value + + print("This component was deployed with the following configuration:", config) + + # example use case that takes component configuration as arguments for a webserver + host = config["Webserver"]["Host"] + port = config["Webserver"]["Port"] + directory = config["Webserver"]["Directory"] + + os.chdir(directory) + + Handler = http.server.SimpleHTTPRequestHandler + + with socketserver.TCPServer((host, port), Handler) as httpd: + print(f"serving at {host}:{port} ...") + httpd.serve_forever() + + +if __name__ == "__main__": + # Once we enter here, we know: + # * all dependencies are available (imports succeeded) + # * IPC Client created + # * AWS_IOT_THING_NAME environment variable is available + # This should be sufficient to consider this component `running` and the deployment will be completed. + # If any of these failed, the component will be `broken`, and the deployment might roll-back or report the error. + # Once the component is `running`, we need to try as hard as possible to keep it alive and running. + while True: + try: + main() + except Exception as e: + print("ERROR", e) + time.sleep(5) diff --git a/samples/greengrass_v2/deployment_configuration/recipe.yaml b/samples/greengrass_v2/deployment_configuration/recipe.yaml new file mode 100644 index 00000000..311ccabb --- /dev/null +++ b/samples/greengrass_v2/deployment_configuration/recipe.yaml @@ -0,0 +1,25 @@ +# recipe reference: https://docs.aws.amazon.com/greengrass/v2/developerguide/component-recipe-reference.html +--- +RecipeFormatVersion: "2020-01-25" +ComponentName: com.example.greengrass_ipc.python.deployment_configuration +ComponentVersion: "1.0.0" +ComponentDescription: Greengrass IPC SDK component example +ComponentPublisher: Amazon +ComponentConfiguration: + DefaultConfiguration: + Webserver: + Host: "127.0.0.1" + Port: 8080 + Directory: "/greengrass/v2/packages/" +Manifests: + - Platform: + os: linux + Lifecycle: + Install: + RequiresPrivilege: true + Script: | + apt-get update + apt-get install --yes python3 python3-pip + python3 -m pip install "awsiotsdk>=1.15.0" + Run: | + python3 -u {artifacts:path}/code.py diff --git a/samples/greengrass_v2/low_level_ipc/code.py b/samples/greengrass_v2/low_level_ipc/code.py new file mode 100644 index 00000000..91ba22b7 --- /dev/null +++ b/samples/greengrass_v2/low_level_ipc/code.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0. + +import json +import time +import os +import random + +import awsiot.greengrasscoreipc +import awsiot.greengrasscoreipc.model as model + +ipc_client = awsiot.greengrasscoreipc.connect() +thing_name = os.environ["AWS_IOT_THING_NAME"] + + +def send_telemetry(): + telemetry_data = { + "timestamp": int(time.time()), + "battery_state_of_charge": random.random() * 99.9, + "location": { + "longitude": 48.15743 + random.random() / 10.0, + "latitude": 11.57549 + random.random() / 10.0, + }, + } + + op = ipc_client.new_publish_to_iot_core() + op.activate( + model.PublishToIoTCoreRequest( + topic_name=f"my/iot/{thing_name}/telemetry", + qos=model.QOS.AT_LEAST_ONCE, + payload=json.dumps(telemetry_data).encode(), + ) + ) + try: + result = op.get_response().result(timeout=5.0) + print("successfully published message:", result) + except Exception as e: + print("failed to publish message:", e) + + +def main(): + while True: + send_telemetry() + time.sleep(5) + + +if __name__ == "__main__": + # Once we enter here, we know: + # * all dependencies are available (imports succeeded) + # * IPC Client created + # * AWS_IOT_THING_NAME environment variable is available + # This should be sufficient to consider this component `running` and the deployment will be completed. + # If any of these failed, the component will be `broken`, and the deployment might roll-back or report the error. + # Once the component is `running`, we need to try as hard as possible to keep it alive and running. + while True: + try: + main() + except Exception as e: + print("ERROR", e) + time.sleep(5) diff --git a/samples/greengrass_v2/low_level_ipc/recipe.yaml b/samples/greengrass_v2/low_level_ipc/recipe.yaml new file mode 100644 index 00000000..f0f6a83c --- /dev/null +++ b/samples/greengrass_v2/low_level_ipc/recipe.yaml @@ -0,0 +1,31 @@ +# recipe reference: https://docs.aws.amazon.com/greengrass/v2/developerguide/component-recipe-reference.html +--- +RecipeFormatVersion: "2020-01-25" +ComponentName: com.example.greengrass_ipc.python.low_level_ipc +ComponentVersion: "1.0.0" +ComponentDescription: Greengrass IPC SDK component example +ComponentPublisher: Amazon +ComponentConfiguration: + DefaultConfiguration: + accessControl: + # see https://docs.aws.amazon.com/greengrass/v2/developerguide/ipc-iot-core-mqtt.html + "aws.greengrass.ipc.mqttproxy": + "com.example.greengrass_ipc.python.low_level_ipc:mqttproxy:1": + policyDescription: Allow access to publish/subscribe to all topics on AWS IoT Core. + operations: + - "aws.greengrass#PublishToIoTCore" + - "aws.greengrass#SubscribeToIoTCore" + resources: + - "*" # CHANGE ME: scope down based on principle of least privilege +Manifests: + - Platform: + os: linux + Lifecycle: + Install: + RequiresPrivilege: true + Script: | + apt-get update + apt-get install --yes python3 python3-pip + python3 -m pip install "awsiotsdk>=1.15.0" + Run: | + python3 -u {artifacts:path}/code.py diff --git a/samples/greengrass_v2/pubsub_cloud/code.py b/samples/greengrass_v2/pubsub_cloud/code.py new file mode 100644 index 00000000..4e47f664 --- /dev/null +++ b/samples/greengrass_v2/pubsub_cloud/code.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 + +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0. + +import time +import os +import json + +from awsiot.greengrasscoreipc.clientv2 import GreengrassCoreIPCClientV2 +from awsiot.greengrasscoreipc.model import QOS, IoTCoreMessage + +client = GreengrassCoreIPCClientV2() +thing_name = os.environ["AWS_IOT_THING_NAME"] + + +def on_stream_event(message: IoTCoreMessage): + print(f"Message received:", message) + reply = { + "pong": "sending back what was received", + "topic": message.message.topic_name, + "payload": message.message.payload.decode(), + } + + print("Sending pong message back:", reply) + resp = client.publish_to_iot_core( + topic_name="hello/world/response", + qos=QOS.AT_LEAST_ONCE, + payload=json.dumps(reply), + ) + print(resp) + + +def main(): + print(f"Running pubsub-cloud sample for thing: {thing_name}") + topic_name = "hello/world" + + print(f"Subscribing to AWS IoT Core topic {topic_name}") + resp, op = client.subscribe_to_iot_core( + topic_name=topic_name, + qos=QOS.AT_LEAST_ONCE, + on_stream_event=on_stream_event, + ) + print(resp, op) + + while True: + time.sleep(999) # wait for incoming messages + + +if __name__ == "__main__": + # Once we enter here, we know: + # * all dependencies are available (imports succeeded) + # * IPC Client created + # * AWS_IOT_THING_NAME environment variable is available + # This should be sufficient to consider this component `running` and the deployment will be completed. + # If any of these failed, the component will be `broken`, and the deployment might roll-back or report the error. + # Once the component is `running`, we need to try as hard as possible to keep it alive and running. + while True: + try: + main() + except Exception as e: + print("ERROR", e) + time.sleep(5) diff --git a/samples/greengrass_v2/pubsub_cloud/recipe.yaml b/samples/greengrass_v2/pubsub_cloud/recipe.yaml new file mode 100644 index 00000000..6dc2d73c --- /dev/null +++ b/samples/greengrass_v2/pubsub_cloud/recipe.yaml @@ -0,0 +1,31 @@ +# recipe reference: https://docs.aws.amazon.com/greengrass/v2/developerguide/component-recipe-reference.html +--- +RecipeFormatVersion: "2020-01-25" +ComponentName: com.example.greengrass_ipc.python.pubsub_cloud +ComponentVersion: "1.0.0" +ComponentDescription: Greengrass IPC SDK component example +ComponentPublisher: Amazon +ComponentConfiguration: + DefaultConfiguration: + accessControl: + # see https://docs.aws.amazon.com/greengrass/v2/developerguide/ipc-iot-core-mqtt.html + "aws.greengrass.ipc.mqttproxy": + "com.example.greengrass_ipc.python.pubsub_cloud:mqttproxy:1": + policyDescription: Allow access to publish/subscribe to all topics on AWS IoT Core. + operations: + - "aws.greengrass#PublishToIoTCore" + - "aws.greengrass#SubscribeToIoTCore" + resources: + - "*" # CHANGE ME: scope down based on principle of least privilege +Manifests: + - Platform: + os: linux + Lifecycle: + Install: + RequiresPrivilege: true + Script: | + apt-get update + apt-get install --yes python3 python3-pip + python3 -m pip install "awsiotsdk>=1.15.0" + Run: | + python3 -u {artifacts:path}/code.py diff --git a/samples/greengrass_v2/pubsub_local/code.py b/samples/greengrass_v2/pubsub_local/code.py new file mode 100644 index 00000000..a28872c1 --- /dev/null +++ b/samples/greengrass_v2/pubsub_local/code.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 + +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0. + +import time +import os +import json + +from awsiot.greengrasscoreipc.clientv2 import GreengrassCoreIPCClientV2 +from awsiot.greengrasscoreipc.model import ( + QOS, + JsonMessage, + PublishMessage, + SubscriptionResponseMessage, +) + +client = GreengrassCoreIPCClientV2() +thing_name = os.environ["AWS_IOT_THING_NAME"] + + +def on_stream_event(message: SubscriptionResponseMessage): + print(f"Message received:", message) + + topic = None + payload = None + if message.json_message: + topic = message.json_message.context.topic + payload = json.dumps(message.json_message.message) + else: + topic = message.binary_message.context.topic + payload = message.binary_message.message.decode() + + reply = { + "pong": "sending back what was received", + "topic": topic, + "payload": payload, + } + + print("Sending pong message back:", reply) + resp = client.publish_to_topic( + topic="hello/world/response", + publish_message=PublishMessage(json_message=JsonMessage(message=reply)), + ) + print(resp) + + +def main(): + print(f"Running pubsub-local sample for thing: {thing_name}") + topic_name = "hello/world" + + print(f"Subscribing to local topic {topic_name}") + resp, op = client.subscribe_to_topic( + topic=topic_name, + on_stream_event=on_stream_event, + ) + print(resp, op) + + while True: + time.sleep(999) # wait for incoming messages + + +if __name__ == "__main__": + # Once we enter here, we know: + # * all dependencies are available (imports succeeded) + # * IPC Client created + # * AWS_IOT_THING_NAME environment variable is available + # This should be sufficient to consider this component `running` and the deployment will be completed. + # If any of these failed, the component will be `broken`, and the deployment might roll-back or report the error. + # Once the component is `running`, we need to try as hard as possible to keep it alive and running. + while True: + try: + main() + except Exception as e: + print("ERROR", e) + time.sleep(5) diff --git a/samples/greengrass_v2/pubsub_local/recipe.yaml b/samples/greengrass_v2/pubsub_local/recipe.yaml new file mode 100644 index 00000000..282bbb44 --- /dev/null +++ b/samples/greengrass_v2/pubsub_local/recipe.yaml @@ -0,0 +1,31 @@ +# recipe reference: https://docs.aws.amazon.com/greengrass/v2/developerguide/component-recipe-reference.html +--- +RecipeFormatVersion: "2020-01-25" +ComponentName: com.example.greengrass_ipc.python.pubsub_local +ComponentVersion: "1.0.0" +ComponentDescription: Greengrass IPC SDK component example +ComponentPublisher: Amazon +ComponentConfiguration: + DefaultConfiguration: + accessControl: + # see https://docs.aws.amazon.com/greengrass/v2/developerguide/ipc-publish-subscribe.html + "aws.greengrass.ipc.pubsub": + "com.example.greengrass_ipc.python.pubsub_local:pubsub:1": + policyDescription: Allow access to publish/subscribe to all topics locally. + operations: + - "aws.greengrass#PublishToTopic" + - "aws.greengrass#SubscribeToTopic" + resources: + - "*" # CHANGE ME: scope down based on principle of least privilege +Manifests: + - Platform: + os: linux + Lifecycle: + Install: + RequiresPrivilege: true + Script: | + apt-get update + apt-get install --yes python3 python3-pip + python3 -m pip install "awsiotsdk>=1.15.0" + Run: | + python3 -u {artifacts:path}/code.py diff --git a/samples/greengrass_v2/shadows/code.py b/samples/greengrass_v2/shadows/code.py new file mode 100644 index 00000000..cdb3f31f --- /dev/null +++ b/samples/greengrass_v2/shadows/code.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 + +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0. + +import time +import os +import json + +from awsiot.greengrasscoreipc.clientv2 import GreengrassCoreIPCClientV2 +from awsiot.greengrasscoreipc.model import QOS, IoTCoreMessage + +client = GreengrassCoreIPCClientV2() +thing_name = os.environ["AWS_IOT_THING_NAME"] +shadow_name = "special_shadow" +shadow = {} + + +def on_stream_event(message: IoTCoreMessage): + print(f"Message received:", message) + reply = json.dumps( + { + "pong": "sending back what was received", + "topic": message.message.topic_name, + "payload": message.message.payload.decode(), + } + ) + + print("Sending pong message back:", reply) + resp = client.publish_to_topic( + topic_name=message.message.topic_name, + qos=QOS.AT_LEAST_ONCE, + payload=reply, + ) + print(resp) + + +def main(): + r = client.list_named_shadows_for_thing(thing_name=thing_name) + if not r.results: + # named shadow not found, so we have to create it first + client.update_thing_shadow( + thing_name=thing_name, + shadow_name=shadow_name, + payload=json.dumps( + { + "state": { + "reported": { + "health": "good", + } + } + } + ), + ) + + # getting the initial shadow document + p = client.get_thing_shadow(thing_name=thing_name, shadow_name=shadow_name).payload + shadow = json.loads(p) + print("Initial shadow:", shadow) + + # doing some useful work, e.g., changing engine speed and reporting the new state + loop_iteration = 0 + while True: + loop_iteration += 1 + shadow_update = { + "state": { + "reported": { + "my_loop_iteration": loop_iteration, + "my_time": int(time.time()), + } + } + } + p = json.dumps(shadow_update).encode() + print("Updating reported shadow value:", shadow_update) + p = client.update_thing_shadow( + thing_name=thing_name, shadow_name=shadow_name, payload=p + ) + print("Shadow updated:", p) + + time.sleep(5) + + +if __name__ == "__main__": + # Once we enter here, we know: + # * all dependencies are available (imports succeeded) + # * IPC Client created + # * AWS_IOT_THING_NAME environment variable is available + # This should be sufficient to consider this component `running` and the deployment will be completed. + # If any of these failed, the component will be `broken`, and the deployment might roll-back or report the error. + # Once the component is `running`, we need to try as hard as possible to keep it alive and running. + while True: + try: + main() + except Exception as e: + print("ERROR", e) + time.sleep(5) diff --git a/samples/greengrass_v2/shadows/recipe.yaml b/samples/greengrass_v2/shadows/recipe.yaml new file mode 100644 index 00000000..e1add184 --- /dev/null +++ b/samples/greengrass_v2/shadows/recipe.yaml @@ -0,0 +1,46 @@ +# recipe reference: https://docs.aws.amazon.com/greengrass/v2/developerguide/component-recipe-reference.html +--- +RecipeFormatVersion: "2020-01-25" +ComponentName: com.example.greengrass_ipc.python.shadows +ComponentVersion: "1.0.0" +ComponentDescription: Greengrass IPC SDK component example +ComponentPublisher: Amazon +ComponentDependencies: + aws.greengrass.ShadowManager: + VersionRequirement: ">=2.3" +ComponentConfiguration: + DefaultConfiguration: + accessControl: + # see https://docs.aws.amazon.com/greengrass/v2/developerguide/shadow-manager-component.html + aws.greengrass.ShadowManager: + com.example.greengrass_ipc.python.shadows:shadow:1: + policyDescription: Allows access to shadows + operations: + - aws.greengrass#ListNamedShadowsForThing + - aws.greengrass#GetThingShadow + - aws.greengrass#UpdateThingShadow + - aws.greengrass#DeleteThingShadow + resources: + - "*" # CHANGE ME: scope down based on principle of least privilege + # see https://docs.aws.amazon.com/greengrass/v2/developerguide/ipc-local-shadows.html + aws.greengrass.ipc.pubsub: + com.example.greengrass_ipc.python.shadows:pubsub:1: + policyDescription: Allows access to shadow sub topics + operations: + - aws.greengrass#SubscribeToTopic + resources: + - "*" # CHANGE ME: scope down based on principle of least privilege + # see https://docs.aws.amazon.com/greengrass/v2/developerguide/shadow-manager-component.html#shadow-manager-component-configuration + +Manifests: + - Platform: + os: linux + Lifecycle: + Install: + RequiresPrivilege: true + Script: | + apt-get update + apt-get install --yes python3 python3-pip + python3 -m pip install "awsiotsdk>=1.15.0" + Run: | + python3 -u {artifacts:path}/code.py diff --git a/samples/ipc_greengrass.py b/samples/ipc_greengrass.py deleted file mode 100644 index a8aeaa4e..00000000 --- a/samples/ipc_greengrass.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0. - -""" -This sample uses AWS IoT Greengrass v2 to publish messages from the device to -the AWS IoT Core MQTT broker. - -This example can be deployed as Greengrass v2 component and it will start -publishing telemetry data as MQTT messages in periodic intervals. The IPC -integration with Greegrass v2 allows this code to run without additional IoT -certificates or secrets, because it directly communicates with Greengrass Core -on the device. - -See this page for more information on Greengrass v2 components: -https://docs.aws.amazon.com/greengrass/v2/developerguide/create-components.html - -See this page for more information on IPC in Greengrass v2: -https://docs.aws.amazon.com/greengrass/v2/developerguide/interprocess-communication.html - -""" - -import json -import time -import os - -import awsiot.greengrasscoreipc -import awsiot.greengrasscoreipc.model as model - -if __name__ == '__main__': - ipc_client = awsiot.greengrasscoreipc.connect() - - while True: - telemetry_data = { - "timestamp": int(round(time.time() * 1000)), - "battery_state_of_charge": 42.5, - "location": { - "longitude": 48.15743, - "latitude": 11.57549, - }, - } - - op = ipc_client.new_publish_to_iot_core() - op.activate(model.PublishToIoTCoreRequest( - topic_name="my/iot/{}/telemetry".format(os.getenv("AWS_IOT_THING_NAME")), - qos=model.QOS.AT_LEAST_ONCE, - payload=json.dumps(telemetry_data).encode(), - )) - try: - result = op.get_response().result(timeout=5.0) - print("successfully published message:", result) - except Exception as e: - print("failed to publish message:", e) - - time.sleep(5)