Skip to content

Commit dd38b3d

Browse files
Ga time (#28)
* Updates to latest CRT, move to kwarg style arguments, update readme for GA.
1 parent ea2ed17 commit dd38b3d

10 files changed

+143
-120
lines changed

README.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,17 @@
22

33
Next generation AWS IoT Client SDK for Python.
44

5-
This project is in **DEVELOPER PREVIEW** while we gather feedback on
6-
interfaces and use cases. Please file issues and feature requests.
7-
Expect breaking API changes as we incorporate feedback.
8-
Until this project is promoted to General Availability, we advise you use the
9-
[previous SDK](https://github.com/aws/aws-iot-device-sdk-python)
10-
for a stable development environment.
5+
This project is in **GENERAL AVAILABILITY**. If you have any issues or feature requests, please file an issue or pull request.
116

127
This SDK is built on the AWS Common Runtime, a collection of libraries
138
([1](https://github.com/awslabs/aws-c-common),
149
[2](https://github.com/awslabs/aws-c-io),
1510
[3](https://github.com/awslabs/aws-c-mqtt),
16-
[4](https://github.com/awslabs/aws-c-http),
17-
[5](https://github.com/awslabs/aws-c-cal) ...) written in C to be
11+
[4](https://github.com/awslabs/aws-c-compression),
12+
[5](https://github.com/awslabs/aws-c-http),
13+
[6](https://github.com/awslabs/aws-c-cal),
14+
[7](https://github.com/awslabs/aws-c-auth),
15+
[8](https://github.com/awslabs/s2n) ...) written in C to be
1816
cross-platform, high-performance, secure, and reliable. The libraries are bound
1917
to Python by the `awscrt` package ([PyPI](https://pypi.org/project/awscrt/)) ([Github](https://github.com/awslabs/aws-crt-python)).
2018

awsiot/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
'iotjobs',
1616
'iotshadow',
1717
'greengrass_discovery',
18+
'mqtt_connection_builder',
1819
]
1920

2021
from awscrt import mqtt
22+
import awscrt.awsiot_mqtt_connection_builder as mqtt_connection_builder
2123
from concurrent.futures import Future
2224
import json
2325
from typing import Any, Callable, Dict, Optional, Tuple, TypeVar
@@ -140,9 +142,9 @@ def on_suback(suback_future):
140142
except Exception as e:
141143
future.set_exception(e)
142144

143-
def callback_wrapper(topic, payload_bytes):
145+
def callback_wrapper(topic, payload):
144146
try:
145-
payload_obj = json.loads(payload_bytes.decode())
147+
payload_obj = json.loads(payload.decode())
146148
event = payload_to_class_fn(payload_obj)
147149
except:
148150
# can't deliver payload, invoke callback with None

awsiot/greengrass_discovery.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# express or implied. See the License for the specific language governing
1212
# permissions and limitations under the License.
1313

14-
from awscrt.http import HttpClientConnection, HttpRequest
14+
from awscrt.http import HttpClientConnection, HttpRequest, HttpHeaders
1515
from awscrt.io import ClientBootstrap, ClientTlsContext, is_alpn_available, SocketOptions, TlsConnectionOptions
1616
import awsiot
1717
from concurrent.futures import Future
@@ -44,8 +44,8 @@ def discover(self, thing_name):
4444
future=Future(),
4545
response_body=bytearray())
4646

47-
def on_incoming_body(http_stream, response_chunk):
48-
discovery['response_body'].extend(response_chunk)
47+
def on_incoming_body(http_stream, chunk):
48+
discovery['response_body'].extend(chunk)
4949

5050
def on_request_complete(completion_future):
5151
try:
@@ -63,10 +63,12 @@ def on_request_complete(completion_future):
6363
def on_connection_completed(conn_future):
6464
try:
6565
connection = conn_future.result()
66+
headers = HttpHeaders()
67+
headers.add('host', self._gg_server_name)
6668
request = HttpRequest(
6769
method='GET',
6870
path='/greengrass/discover/thing/{}'.format(thing_name),
69-
headers=[('host', self._gg_server_name)])
71+
headers=headers)
7072

7173
http_stream = connection.request(
7274
request=request,

awsiot/iotshadow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ def __init__(self, metadata=None, state=None, timestamp=None, version=None):
414414
@classmethod
415415
def from_payload(cls, payload):
416416
# type: (typing.Dict[str, typing.Any]) -> GetShadowResponse
417-
new = cls()
417+
new = cls()
418418
val = payload.get('metadata')
419419
if val is not None:
420420
new.metadata = ShadowMetadata.from_payload(val)

codebuild/install_and_run_samples.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ phases:
2020
commands:
2121
- echo Build started on `date`
2222
- pip3 install ./
23-
- python3 samples/basic_discovery.py --region us-east-1 --cert /tmp/certificate.pem --key /tmp/privatekey.pem --ca_file /tmp/AmazonRootCA1.pem --thing_name aws-sdk-crt-unit-test --print_discover_resp_only -v Trace
23+
- python3 samples/basic_discovery.py --region us-east-1 --cert /tmp/certificate.pem --key /tmp/privatekey.pem --root-ca /tmp/AmazonRootCA1.pem --thing-name aws-sdk-crt-unit-test --print-discover-resp-only -v Trace
2424
- pip install ./
25-
- python samples/basic_discovery.py --region us-east-1 --cert /tmp/certificate.pem --key /tmp/privatekey.pem --ca_file /tmp/AmazonRootCA1.pem --thing_name aws-sdk-crt-unit-test --print_discover_resp_only -v Trace
25+
- python samples/basic_discovery.py --region us-east-1 --cert /tmp/certificate.pem --key /tmp/privatekey.pem --root-ca /tmp/AmazonRootCA1.pem --thing-name aws-sdk-crt-unit-test --print-discover-resp-only -v Trace
2626
post_build:
2727
commands:
2828
- echo Build completed on `date`

samples/basic_discovery.py

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,23 @@
2020
from awscrt.io import LogLevel
2121
from awscrt.mqtt import Connection, Client, QoS
2222
from awsiot.greengrass_discovery import DiscoveryClient, DiscoverResponse
23+
from awsiot import mqtt_connection_builder
2324

2425
allowed_actions = ['both', 'publish', 'subscribe']
2526

2627
parser = argparse.ArgumentParser()
27-
parser.add_argument('-r', '--ca_file', action='store', required=True, dest='root_ca_path', help='Root CA file path')
28+
parser.add_argument('-r', '--root-ca', action='store', dest='root_ca_path', help='Root CA file path')
2829
parser.add_argument('-c', '--cert', action='store', required=True, dest='certificate_path', help='Certificate file path')
2930
parser.add_argument('-k', '--key', action='store', required=True, dest='private_key_path', help='Private key file path')
30-
parser.add_argument('-n', '--thing_name', action='store', required=True, dest='thing_name', help='Targeted thing name')
31+
parser.add_argument('-n', '--thing-name', action='store', required=True, dest='thing_name', help='Targeted thing name')
3132
parser.add_argument('-t', '--topic', action='store', dest='topic', default='sdk/test/Python', help='Targeted topic')
3233
parser.add_argument('-m', '--mode', action='store', dest='mode', default='both',
3334
help='Operation modes: %s'%str(allowed_actions))
3435
parser.add_argument('-M', '--message', action='store', dest='message', default='Hello World!',
3536
help='Message to publish')
3637
parser.add_argument('--region', action='store', dest='region', default='us-east-1')
37-
parser.add_argument('--max_pub_ops', action='store', dest='max_pub_ops', default=10)
38-
parser.add_argument('--print_discover_resp_only', action='store_true', dest='print_discover_resp_only', default=False)
38+
parser.add_argument('--max-pub-ops', action='store', dest='max_pub_ops', default=10)
39+
parser.add_argument('--print-discover-resp-only', action='store_true', dest='print_discover_resp_only', default=False)
3940
parser.add_argument('-v', '--verbose', action='store', dest='verbosity', default='NoLogs')
4041

4142
args = parser.parse_args()
@@ -54,7 +55,8 @@
5455
io.init_logging(LogLevel.Trace, 'stderr')
5556

5657
event_loop_group = io.EventLoopGroup(1)
57-
client_bootstrap = io.ClientBootstrap(event_loop_group)
58+
host_resolver = io.DefaultHostResolver(event_loop_group)
59+
client_bootstrap = io.ClientBootstrap(event_loop_group, host_resolver)
5860

5961
tls_options = io.TlsContextOptions.create_client_with_mtls_from_path(args.certificate_path, args.private_key_path)
6062
tls_options.override_default_trust_store_from_path(None, args.root_ca_path)
@@ -84,25 +86,17 @@ def on_connection_resumed(connection, error_code, session_present):
8486
# Try IoT endpoints until we find one that works
8587
def try_iot_endpoints():
8688
for gg_group in discover_response.gg_groups:
87-
88-
gg_core_tls_options = io.TlsContextOptions.create_client_with_mtls_from_path(args.certificate_path, args.private_key_path)
89-
gg_core_tls_options.override_default_trust_store(bytes(gg_group.certificate_authorities[0], encoding='utf-8'))
90-
gg_core_tls_ctx = io.ClientTlsContext(gg_core_tls_options)
91-
mqtt_client = Client(client_bootstrap, gg_core_tls_ctx)
92-
9389
for gg_core in gg_group.cores:
9490
for connectivity_info in gg_core.connectivity:
9591
try:
9692
print('Trying core {} at host {} port {}'.format(gg_core.thing_arn, connectivity_info.host_address, connectivity_info.port))
97-
mqtt_connection = Connection(
98-
mqtt_client,
99-
on_connection_interrupted=on_connection_interupted,
100-
on_connection_resumed=on_connection_resumed)
101-
connect_future = mqtt_connection.connect(
102-
client_id=args.thing_name,
103-
host_name=connectivity_info.host_address,
104-
port=connectivity_info.port,
105-
clean_session=False)
93+
mqtt_connection = mqtt_connection_builder.mtls_from_path(endpoint=connectivity_info.host_address, port=connectivity_info.port,
94+
cert_filepath=args.certificate_path, pri_key_filepath=args.private_key_path, client_bootstrap=client_bootstrap,
95+
ca_bytes=bytes(gg_group.certificate_authorities[0], encoding='utf-8'),
96+
on_connection_interrupted=on_connection_interupted, on_connection_resumed=on_connection_resumed,
97+
client_id=args.thing_name, clean_session=False, keep_alive_secs=6)
98+
99+
connect_future = mqtt_connection.connect()
106100
connect_future.result()
107101
print('Connected!')
108102
return mqtt_connection
@@ -117,9 +111,9 @@ def try_iot_endpoints():
117111

118112
if args.mode == 'both' or args.mode == 'subscribe':
119113

120-
def on_publish(topic, message):
114+
def on_publish(topic, payload):
121115
print('Publish received on topic {}'.format(topic))
122-
print(message)
116+
print(payload)
123117

124118
subscribe_future, _ = mqtt_connection.subscribe(args.topic, QoS.AT_MOST_ONCE, on_publish)
125119
subscribe_result = subscribe_future.result()

samples/jobs.py

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
from __future__ import absolute_import
1515
from __future__ import print_function
1616
import argparse
17-
from awscrt import io, mqtt
17+
from awscrt import auth, http, io, mqtt
1818
from awsiot import iotjobs
19+
from awsiot import mqtt_connection_builder
1920
from concurrent.futures import Future
2021
import sys
2122
import threading
@@ -46,14 +47,22 @@
4647
parser = argparse.ArgumentParser(description="Jobs sample runs all pending job executions.")
4748
parser.add_argument('--endpoint', required=True, help="Your AWS IoT custom endpoint, not including a port. " +
4849
"Ex: \"w6zbse3vjd5b4p-ats.iot.us-west-2.amazonaws.com\"")
49-
parser.add_argument('--cert', required=True, help="File path to your client certificate, in PEM format")
50-
parser.add_argument('--key', required=True, help="File path to your private key file, in PEM format")
50+
parser.add_argument('--cert', help="File path to your client certificate, in PEM format")
51+
parser.add_argument('--key', help="File path to your private key file, in PEM format")
5152
parser.add_argument('--root-ca', help="File path to root certificate authority, in PEM format. " +
5253
"Necessary if MQTT server uses a certificate that's not already in " +
5354
"your trust store")
5455
parser.add_argument('--client-id', default='samples-client-id', help="Client ID for MQTT connection.")
5556
parser.add_argument('--thing-name', required=True, help="The name assigned to your IoT Thing")
5657
parser.add_argument('--job-time', default=5, type=float, help="Emulate working on job by sleeping this many seconds.")
58+
parser.add_argument('--use-websocket', default=False, action='store_true',
59+
help="To use a websocket instead of raw mqtt. If you " +
60+
"specify this option you must specify a region for signing, you can also enable proxy mode.")
61+
parser.add_argument('--signing-region', default='us-east-1', help="If you specify --use-web-socket, this " +
62+
"is the region that will be used for computing the Sigv4 signature")
63+
parser.add_argument('--proxy-host', help="Hostname for proxy to connect to. Note: if you use this feature, " +
64+
"you will likely need to set --root-ca to the ca for your proxy.")
65+
parser.add_argument('--proxy-port', type=int, default=8080, help="Port for proxy to connect to.")
5766

5867
# Using globals to simplify sample code
5968
is_sample_done = threading.Event()
@@ -226,26 +235,28 @@ def on_update_job_execution_rejected(rejected):
226235

227236
# Spin up resources
228237
event_loop_group = io.EventLoopGroup(1)
229-
client_bootstrap = io.ClientBootstrap(event_loop_group)
230-
231-
tls_options = io.TlsContextOptions.create_client_with_mtls_from_path(args.cert, args.key)
232-
if args.root_ca:
233-
tls_options.override_default_trust_store_from_path(ca_dirpath=None, ca_filepath=args.root_ca)
234-
tls_context = io.ClientTlsContext(tls_options)
235-
236-
mqtt_client = mqtt.Client(client_bootstrap, tls_context)
237-
238-
port = 8883
239-
print("Connecting to {} on port {}...".format(args.endpoint, port))
240-
mqtt_connection = mqtt.Connection(
241-
client=mqtt_client)
242-
connected_future = mqtt_connection.connect(
243-
client_id=args.client_id,
244-
host_name = args.endpoint,
245-
port = port,
246-
use_websocket=False,
247-
clean_session=True,
248-
keep_alive=6000)
238+
host_resolver = io.DefaultHostResolver(event_loop_group)
239+
client_bootstrap = io.ClientBootstrap(event_loop_group, host_resolver)
240+
241+
if args.use_websocket == True:
242+
proxy_options = None
243+
if (args.proxy_host):
244+
proxy_options = http.HttpProxyOptions(host_name=args.proxy_host, port=args.proxy_port)
245+
246+
credentials_provider = auth.AwsCredentialsProvider.new_default_chain(client_bootstrap)
247+
mqtt_connection = mqtt_connection_builder.websockets_with_default_aws_signing(endpoint=args.endpoint,
248+
client_bootstrap=client_bootstrap, region=args.signing_region, credentials_provider=credentials_provider, websocket_proxy_options=proxy_options,
249+
ca_filepath=args.root_ca, client_id=args.client_id, clean_session=False, keep_alive_secs=6)
250+
251+
else:
252+
mqtt_connection = mqtt_connection_builder.mtls_from_path(endpoint=args.endpoint, cert_filepath=args.cert, pri_key_filepath=args.key,
253+
client_bootstrap=client_bootstrap, ca_filepath=args.root_ca, client_id=args.client_id,
254+
clean_session=False, keep_alive_secs=6)
255+
256+
print("Connecting to {} with client ID '{}'...".format(
257+
args.endpoint, args.client_id))
258+
259+
connected_future = mqtt_connection.connect()
249260

250261
jobs_client = iotjobs.IotJobsClient(mqtt_connection)
251262

0 commit comments

Comments
 (0)