Skip to content

Commit 83d1c5b

Browse files
gguussJon Wayne Parrott
authored and
Jon Wayne Parrott
committed
Adds token refresh to MQTT example. (GoogleCloudPlatform#1197)
* Adds token refresh to MQTT example. * Fixes lint error.
1 parent 0715f6d commit 83d1c5b

File tree

4 files changed

+205
-84
lines changed

4 files changed

+205
-84
lines changed

iot/api-client/mqtt_example/README.md

-51
This file was deleted.
+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
.. This file is automatically generated. Do not edit this file directly.
2+
3+
Google Cloud IoT Core API Python Samples
4+
===============================================================================
5+
6+
This directory contains samples for Google Cloud IoT Core API. `Google Cloud IoT Core`_ allows developers to easily integrate Publish and Subscribe functionality with devices and programmatically manage device authorization.
7+
The following example runs the sample using the project ID `blue-jet-123` and the device name `my-python-device`:
8+
9+
python cloudiot_mqtt_example.py \
10+
--registry_id=my-registry \
11+
--project_id=blue-jet-123 \
12+
--device_id=my-python-device \
13+
--algorithm=RS256 \
14+
--private_key_file=../rsa_private.pem
15+
16+
17+
18+
19+
.. _Google Cloud IoT Core API: https://cloud.google.com/iot/docs
20+
21+
Setup
22+
-------------------------------------------------------------------------------
23+
24+
25+
Install Dependencies
26+
++++++++++++++++++++
27+
28+
#. Install `pip`_ and `virtualenv`_ if you do not already have them. You may want to refer to the `Python Development Environment Setup Guide`_ for Google Cloud Platform for instructions.
29+
30+
.. _Python Development Environment Setup Guide:
31+
https://cloud.google.com/python/setup
32+
33+
#. Create a virtualenv. Samples are compatible with Python 2.7 and 3.4+.
34+
35+
.. code-block:: bash
36+
37+
$ virtualenv env
38+
$ source env/bin/activate
39+
40+
#. Install the dependencies needed to run the samples.
41+
42+
.. code-block:: bash
43+
44+
$ pip install -r requirements.txt
45+
46+
.. _pip: https://pip.pypa.io/
47+
.. _virtualenv: https://virtualenv.pypa.io/
48+
49+
Samples
50+
-------------------------------------------------------------------------------
51+
52+
MQTT Device Client Example
53+
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
54+
55+
56+
57+
To run this sample:
58+
59+
.. code-block:: bash
60+
61+
$ python cloudiot_mqtt_example.py
62+
63+
usage: cloudiot_mqtt_example.py [-h] [--project_id PROJECT_ID] --registry_id
64+
REGISTRY_ID --device_id DEVICE_ID
65+
--private_key_file PRIVATE_KEY_FILE
66+
--algorithm {RS256,ES256}
67+
[--cloud_region CLOUD_REGION]
68+
[--ca_certs CA_CERTS]
69+
[--num_messages NUM_MESSAGES]
70+
[--message_type {event,state}]
71+
[--mqtt_bridge_hostname MQTT_BRIDGE_HOSTNAME]
72+
[--mqtt_bridge_port {8883,443}]
73+
[--jwt_expires_minutes JWT_EXPIRES_MINUTES]
74+
75+
Example Google Cloud IoT Core MQTT device connection code.
76+
77+
optional arguments:
78+
-h, --help show this help message and exit
79+
--project_id PROJECT_ID
80+
GCP cloud project name
81+
--registry_id REGISTRY_ID
82+
Cloud IoT Core registry id
83+
--device_id DEVICE_ID
84+
Cloud IoT Core device id
85+
--private_key_file PRIVATE_KEY_FILE
86+
Path to private key file.
87+
--algorithm {RS256,ES256}
88+
Which encryption algorithm to use to generate the JWT.
89+
--cloud_region CLOUD_REGION
90+
GCP cloud region
91+
--ca_certs CA_CERTS CA root from https://pki.google.com/roots.pem
92+
--num_messages NUM_MESSAGES
93+
Number of messages to publish.
94+
--message_type {event,state}
95+
Indicates whether the message to be published is a
96+
telemetry event or a device state message.
97+
--mqtt_bridge_hostname MQTT_BRIDGE_HOSTNAME
98+
MQTT bridge hostname.
99+
--mqtt_bridge_port {8883,443}
100+
MQTT bridge port.
101+
--jwt_expires_minutes JWT_EXPIRES_MINUTES
102+
Expiration time, in minutes, for JWT tokens.
103+
104+
105+
106+
107+
.. _Google Cloud SDK: https://cloud.google.com/sdk/
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# This file is used to generate README.rst
2+
3+
product:
4+
name: Google Cloud IoT Core API
5+
short_name: Cloud IoT Core
6+
url: https://cloud.google.com/iot/docs
7+
description: >
8+
`Google Cloud IoT Core`_ allows developers to easily integrate Publish and
9+
Subscribe functionality with devices and programmatically manage device
10+
authorization.
11+
12+
The following example runs the sample using the project ID `blue-jet-123`
13+
and the device name `my-python-device`:
14+
15+
python cloudiot_mqtt_example.py \
16+
--registry_id=my-registry \
17+
--project_id=blue-jet-123 \
18+
--device_id=my-python-device \
19+
--algorithm=RS256 \
20+
--private_key_file=../rsa_private.pem
21+
22+
setup:
23+
- install_deps
24+
25+
samples:
26+
- name: MQTT Device Client Example
27+
file: cloudiot_mqtt_example.py
28+
show_help: True
29+
30+
cloud_client_library: false

iot/api-client/mqtt_example/cloudiot_mqtt_example.py

+68-33
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import jwt
3030
import paho.mqtt.client as mqtt
3131

32+
3233
# [START iot_mqtt_jwt]
3334
def create_jwt(project_id, private_key_file, algorithm):
3435
"""Creates a JWT (https://jwt.io) to establish an MQTT connection.
@@ -64,6 +65,8 @@ def create_jwt(project_id, private_key_file, algorithm):
6465
return jwt.encode(token, private_key, algorithm=algorithm)
6566
# [END iot_mqtt_jwt]
6667

68+
69+
# [START iot_mqtt_config]
6770
def error_str(rc):
6871
"""Convert a Paho error to a human readable string."""
6972
return '{}: {}'.format(rc, mqtt.error_string(rc))
@@ -84,6 +87,46 @@ def on_publish(unused_client, unused_userdata, unused_mid):
8487
print('on_publish')
8588

8689

90+
def get_client(
91+
project_id, cloud_region, registry_id, device_id, private_key_file,
92+
algorithm, ca_certs, mqtt_bridge_hostname, mqtt_bridge_port):
93+
"""Create our MQTT client. The client_id is a unique string that identifies
94+
this device. For Google Cloud IoT Core, it must be in the format below."""
95+
client = mqtt.Client(
96+
client_id=('projects/{}/locations/{}/registries/{}/devices/{}'
97+
.format(
98+
project_id,
99+
cloud_region,
100+
registry_id,
101+
device_id)))
102+
103+
# With Google Cloud IoT Core, the username field is ignored, and the
104+
# password field is used to transmit a JWT to authorize the device.
105+
client.username_pw_set(
106+
username='unused',
107+
password=create_jwt(
108+
project_id, private_key_file, algorithm))
109+
110+
# Enable SSL/TLS support.
111+
client.tls_set(ca_certs=ca_certs)
112+
113+
# Register message callbacks. https://eclipse.org/paho/clients/python/docs/
114+
# describes additional callbacks that Paho supports. In this example, the
115+
# callbacks just print to standard out.
116+
client.on_connect = on_connect
117+
client.on_publish = on_publish
118+
client.on_disconnect = on_disconnect
119+
120+
# Connect to the Google MQTT bridge.
121+
client.connect(mqtt_bridge_hostname, mqtt_bridge_port)
122+
123+
# Start the network loop.
124+
client.loop_start()
125+
126+
return client
127+
# [END iot_mqtt_config]
128+
129+
87130
def parse_command_line_args():
88131
"""Parse command line arguments."""
89132
parser = argparse.ArgumentParser(description=(
@@ -131,57 +174,48 @@ def parse_command_line_args():
131174
default=8883,
132175
type=int,
133176
help='MQTT bridge port.')
177+
parser.add_argument(
178+
'--jwt_expires_minutes',
179+
default=20,
180+
type=int,
181+
help=('Expiration time, in minutes, for JWT tokens.'))
134182

135183
return parser.parse_args()
136184

137185

186+
# [START iot_mqtt_run]
138187
def main():
139188
args = parse_command_line_args()
140189

141-
# Create our MQTT client. The client_id is a unique string that identifies
142-
# this device. For Google Cloud IoT Core, it must be in the format below.
143-
client = mqtt.Client(
144-
client_id=('projects/{}/locations/{}/registries/{}/devices/{}'
145-
.format(
146-
args.project_id,
147-
args.cloud_region,
148-
args.registry_id,
149-
args.device_id)))
150-
151-
# With Google Cloud IoT Core, the username field is ignored, and the
152-
# password field is used to transmit a JWT to authorize the device.
153-
client.username_pw_set(
154-
username='unused',
155-
password=create_jwt(
156-
args.project_id, args.private_key_file, args.algorithm))
157-
158-
# Enable SSL/TLS support.
159-
client.tls_set(ca_certs=args.ca_certs)
160-
161-
# Register message callbacks. https://eclipse.org/paho/clients/python/docs/
162-
# describes additional callbacks that Paho supports. In this example, the
163-
# callbacks just print to standard out.
164-
client.on_connect = on_connect
165-
client.on_publish = on_publish
166-
client.on_disconnect = on_disconnect
167-
168-
# Connect to the Google MQTT bridge.
169-
client.connect(args.mqtt_bridge_hostname, args.mqtt_bridge_port)
170-
171-
# Start the network loop.
172-
client.loop_start()
173-
174190
# Publish to the events or state topic based on the flag.
175191
sub_topic = 'events' if args.message_type == 'event' else 'state'
176192

177193
mqtt_topic = '/devices/{}/{}'.format(args.device_id, sub_topic)
178194

195+
jwt_iat = datetime.datetime.utcnow()
196+
jwt_exp_mins = args.jwt_expires_minutes
197+
client = get_client(
198+
args.project_id, args.cloud_region, args.registry_id, args.device_id,
199+
args.private_key_file, args.algorithm, args.ca_certs,
200+
args.mqtt_bridge_hostname, args.mqtt_bridge_port)
201+
179202
# Publish num_messages mesages to the MQTT bridge once per second.
180203
for i in range(1, args.num_messages + 1):
181204
payload = '{}/{}-payload-{}'.format(
182205
args.registry_id, args.device_id, i)
183206
print('Publishing message {}/{}: \'{}\''.format(
184207
i, args.num_messages, payload))
208+
seconds_since_issue = (datetime.datetime.utcnow() - jwt_iat).seconds
209+
if seconds_since_issue > 60 * jwt_exp_mins:
210+
print('Refreshing token after {}s').format(seconds_since_issue)
211+
client.loop_stop()
212+
jwt_iat = datetime.datetime.utcnow()
213+
client = get_client(
214+
args.project_id, args.cloud_region,
215+
args.registry_id, args.device_id, args.private_key_file,
216+
args.algorithm, args.ca_certs, args.mqtt_bridge_hostname,
217+
args.mqtt_bridge_port)
218+
185219
# Publish "payload" to the MQTT topic. qos=1 means at least once
186220
# delivery. Cloud IoT Core also supports qos=0 for at most once
187221
# delivery.
@@ -193,6 +227,7 @@ def main():
193227
# End the network loop and finish.
194228
client.loop_stop()
195229
print('Finished.')
230+
# [END iot_mqtt_run]
196231

197232

198233
if __name__ == '__main__':

0 commit comments

Comments
 (0)