Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions samples/pubsub.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# PubSub

[**Return to main sample list**](./README.md)

This sample uses the
[Message Broker](https://docs.aws.amazon.com/iot/latest/developerguide/iot-message-broker.html)
for AWS IoT to send and receive messages through an MQTT connection.

On startup, the device connects to the server, subscribes to a topic, and begins publishing messages to that topic. The device should receive those same messages back from the message broker, since it is subscribed to that same topic. Status updates are continually printed to the console. This sample demonstrates how to send and receive messages on designated IoT Core topics, an essential task that is the backbone of many IoT applications that need to send data over the internet. This sample simply subscribes and publishes to a topic, printing the messages it just sent as it is received from AWS IoT Core, but this can be used as a reference point for more complex Pub-Sub applications.

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.

<details>
<summary>(see sample policy)</summary>
<pre>
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iot:Publish",
"iot:Receive"
],
"Resource": [
"arn:aws:iot:<b>region</b>:<b>account</b>:topic/test/topic"
]
},
{
"Effect": "Allow",
"Action": [
"iot:Subscribe"
],
"Resource": [
"arn:aws:iot:<b>region</b>:<b>account</b>:topicfilter/test/topic"
]
},
{
"Effect": "Allow",
"Action": [
"iot:Connect"
],
"Resource": [
"arn:aws:iot:<b>region</b>:<b>account</b>:client/test-*"
]
}
]
}
</pre>

Replace with the following with the data from your AWS account:
* `<region>`: 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`.
* `<account>`: 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.

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 <client ID here>` to send the client ID your policy supports.

</details>

## How to run

To Run this sample from the `samples` folder, use the following command:

```sh
# For Windows: replace 'python3' with 'python' and '/' with '\'
python3 pubsub.py --endpoint <endpoint> --cert <file> --key <file>
```

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 pubsub.py --endpoint <endpoint> --cert <file> --key <file> --ca_file <file>
```
183 changes: 183 additions & 0 deletions samples/pubsub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0.

from awsiot import mqtt5_client_builder
from awscrt import mqtt5
import threading
import time
# This sample uses the Message Broker for AWS IoT to send and receive messages
# through an MQTT connection. On startup, the device connects to the server,
# subscribes to a topic, and begins publishing messages to that topic.
# The device should receive those same messages back from the message broker,
# since it is subscribed to that same topic.

# --------------------------------- ARGUMENT PARSING -----------------------------------------
import argparse
import uuid

parser = argparse.ArgumentParser(
description="MQTT5 X509 Sample (mTLS)",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
required = parser.add_argument_group("required arguments")
optional = parser.add_argument_group("optional arguments")

# Required Arguments
required.add_argument("--endpoint", required=True, metavar="", dest="input_endpoint",
help="IoT endpoint hostname")
required.add_argument("--cert", required=True, metavar="", dest="input_cert",
help="Path to the certificate file to use during mTLS connection establishment")
required.add_argument("--key", required=True, metavar="", dest="input_key",
help="Path to the private key file to use during mTLS connection establishment")

# Optional Arguments
optional.add_argument("--client_id", metavar="", dest="input_clientId", default=f"mqtt5-sample-{uuid.uuid4().hex[:8]}",
help="Client ID")
optional.add_argument("--topic", metavar="", default="test/topic", dest="input_topic",
help="Topic")
optional.add_argument("--message", metavar="", default="Hello from mqtt5 sample", dest="input_message",
help="Message payload")
optional.add_argument("--count", type=int, metavar="", default=5, dest="input_count",
help="Messages to publish (0 = infinite)")
optional.add_argument("--ca_file", metavar="", dest="input_ca", default=None,
help="Path to root CA file")

# args contains all the parsed commandline arguments used by the sample
args = parser.parse_args()
# --------------------------------- ARGUMENT PARSING END -----------------------------------------

TIMEOUT = 100
message_count = args.input_count
message_topic = args.input_topic
message_string = args.input_message
# Events used within callbacks to progress sample
connection_success_event = threading.Event()
stopped_event = threading.Event()
received_all_event = threading.Event()
received_count = 0


# Callback when any publish is received
def on_publish_received(publish_packet_data):
publish_packet = publish_packet_data.publish_packet
print("==== Received message from topic '{}': {} ====\n".format(
publish_packet.topic, publish_packet.payload.decode('utf-8')))

# Track number of publishes received
global received_count
received_count += 1
if received_count == args.input_count:
received_all_event.set()


# Callback for the lifecycle event Stopped
def on_lifecycle_stopped(lifecycle_stopped_data: mqtt5.LifecycleStoppedData):
print("Lifecycle Stopped\n")
stopped_event.set()


# Callback for lifecycle event Attempting Connect
def on_lifecycle_attempting_connect(lifecycle_attempting_connect_data: mqtt5.LifecycleAttemptingConnectData):
print("Lifecycle Connection Attempt\nConnecting to endpoint: '{}' with client ID'{}'".format(
args.input_endpoint, args.input_clientId))


# Callback for the lifecycle event Connection Success
def on_lifecycle_connection_success(lifecycle_connect_success_data: mqtt5.LifecycleConnectSuccessData):
connack_packet = lifecycle_connect_success_data.connack_packet
print("Lifecycle Connection Success with reason code:{}\n".format(
repr(connack_packet.reason_code)))
connection_success_event.set()


# Callback for the lifecycle event Connection Failure
def on_lifecycle_connection_failure(lifecycle_connection_failure: mqtt5.LifecycleConnectFailureData):
print("Lifecycle Connection Failure with exception:{}".format(
lifecycle_connection_failure.exception))


# Callback for the lifecycle event Disconnection
def on_lifecycle_disconnection(lifecycle_disconnect_data: mqtt5.LifecycleDisconnectData):
print("Lifecycle Disconnected with reason code:{}".format(
lifecycle_disconnect_data.disconnect_packet.reason_code if lifecycle_disconnect_data.disconnect_packet else "None"))


if __name__ == '__main__':
print("\nStarting MQTT5 X509 PubSub Sample\n")
message_count = args.input_count
message_topic = args.input_topic
message_string = args.input_message

# Create MQTT5 client using mutual TLS via X509 Certificate and Private Key
print("==== Creating MQTT5 Client ====\n")
client = mqtt5_client_builder.mtls_from_path(
endpoint=args.input_endpoint,
cert_filepath=args.input_cert,
pri_key_filepath=args.input_key,
on_publish_received=on_publish_received,
on_lifecycle_stopped=on_lifecycle_stopped,
on_lifecycle_attempting_connect=on_lifecycle_attempting_connect,
on_lifecycle_connection_success=on_lifecycle_connection_success,
on_lifecycle_connection_failure=on_lifecycle_connection_failure,
on_lifecycle_disconnection=on_lifecycle_disconnection,
client_id=args.input_clientId,
ca_filepath=args.input_ca)

# Start the client, instructing the client to desire a connected state. The client will try to
# establish a connection with the provided settings. If the client is disconnected while in this
# state it will attempt to reconnect automatically.
print("==== Starting client ====")
client.start()

# We await the `on_lifecycle_connection_success` callback to be invoked.
if not connection_success_event.wait(TIMEOUT):
raise TimeoutError("Connection timeout")

# Subscribe
print("==== Subscribing to topic '{}' ====".format(message_topic))
subscribe_future = client.subscribe(subscribe_packet=mqtt5.SubscribePacket(
subscriptions=[mqtt5.Subscription(
topic_filter=message_topic,
qos=mqtt5.QoS.AT_LEAST_ONCE)]
))
suback = subscribe_future.result(TIMEOUT)
print("Suback received with reason code:{}\n".format(suback.reason_codes))

# Publish
if message_count == 0:
print("==== Sending messages until program killed ====\n")
else:
print("==== Sending {} message(s) ====\n".format(message_count))

publish_count = 1
while (publish_count <= message_count) or (message_count == 0):
message = f"{message_string} [{publish_count}]"
print(f"Publishing message to topic '{message_topic}': {message}")
publish_future = client.publish(mqtt5.PublishPacket(
topic=message_topic,
payload=message,
qos=mqtt5.QoS.AT_LEAST_ONCE
))
publish_completion_data = publish_future.result(TIMEOUT)
print("PubAck received with {}\n".format(repr(publish_completion_data.puback.reason_code)))
time.sleep(1.5)
publish_count += 1

received_all_event.wait(TIMEOUT)
print("{} message(s) received.\n".format(received_count))

# Unsubscribe
print("==== Unsubscribing from topic '{}' ====".format(message_topic))
unsubscribe_future = client.unsubscribe(unsubscribe_packet=mqtt5.UnsubscribePacket(
topic_filters=[message_topic]))
unsuback = unsubscribe_future.result(TIMEOUT)
print("Unsubscribed with {}\n".format(unsuback.reason_codes))

# Stop the client. Instructs the client to disconnect and remain in a disconnected state.
print("==== Stopping Client ====")
client.stop()

if not stopped_event.wait(TIMEOUT):
raise TimeoutError("Stop timeout")

print("==== Client Stopped! ====")