Skip to content

Commit 586624e

Browse files
gguussJon Wayne Parrott
authored and
Jon Wayne Parrott
committed
Final additions in private beta (#1136)
* Final additions in private beta * Adds HTTP client and state support * Fixes rouge space * Remove invalid message on error.
1 parent e1f6883 commit 586624e

File tree

7 files changed

+336
-90
lines changed

7 files changed

+336
-90
lines changed

iot/api-client/http_example/README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Cloud IoT Core Python HTTP example
2+
3+
This sample app publishes data to Cloud Pub/Sub using the HTTP bridge provided
4+
as part of Google Cloud IoT Core. For detailed running instructions see the
5+
[HTTP code samples
6+
guide](https://cloud.google.com/iot/docs/protocol_bridge_guide).
7+
8+
# Setup
9+
10+
1. Use virtualenv to create a local Python environment.
11+
12+
```
13+
virtualenv env && source env/bin/activate
14+
```
15+
16+
1. Install the dependencies
17+
18+
```
19+
pip install -r requirements.txt
20+
```
21+
22+
# Running the Sample
23+
24+
The following snippet summarizes usage:
25+
26+
```
27+
cloudiot_http_example.py [-h]
28+
--project_id PROJECT_ID
29+
--registry_id REGISTRY_ID
30+
--device_id DEVICE_ID
31+
--private_key_file PRIVATE_KEY_FILE
32+
--algorithm {RS256,ES256}
33+
--message_type={event,state}
34+
[--cloud_region CLOUD_REGION]
35+
[--ca_certs CA_CERTS]
36+
[--num_messages NUM_MESSAGES]
37+
```
38+
39+
For example, if your project ID is `blue-jet-123`, the following example shows
40+
how you would execute using the configuration from the HTTP code samples guide:
41+
42+
```
43+
python cloudiot_http_example.py \
44+
--registry_id=my-registry \
45+
--project_id=blue-jet-123 \
46+
--device_id=my-python-device \
47+
--message_type=event \
48+
--algorithm=RS256 \
49+
--private_key_file=../rsa_private.pem
50+
```
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#!/usr/bin/env python
2+
3+
# Copyright 2017 Google Inc.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
"""Python sample for connecting to Google Cloud IoT Core via HTTP, using JWT.
17+
This example connects to Google Cloud IoT Core via HTTP, using a JWT for device
18+
authentication. After connecting, by default the device publishes 100 messages
19+
to the server at a rate of one per second, and then exits.
20+
Before you run the sample, you must register your device as described in the
21+
README in the parent folder.
22+
"""
23+
24+
import argparse
25+
import base64
26+
import datetime
27+
import json
28+
import time
29+
30+
import jwt
31+
import requests
32+
33+
34+
_BASE_URL = 'https://cloudiot-device.googleapis.com/v1beta1'
35+
36+
37+
def create_jwt(project_id, private_key_file, algorithm):
38+
"""Creates a JWT (https://jwt.io) to authenticate this device.
39+
Args:
40+
project_id: The cloud project ID this device belongs to
41+
private_key_file: A path to a file containing either an RSA256 or
42+
ES256 private key.
43+
algorithm: The encryption algorithm to use. Either 'RS256' or
44+
'ES256'
45+
Returns:
46+
A JWT generated from the given project_id and private key, which
47+
expires in 20 minutes. After 20 minutes, your client will be
48+
disconnected, and a new JWT will have to be generated.
49+
Raises:
50+
ValueError: If the private_key_file does not contain a known key.
51+
"""
52+
53+
token = {
54+
# The time the token was issued.
55+
'iat': datetime.datetime.utcnow(),
56+
# Token expiration time.
57+
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=60),
58+
# The audience field should always be set to the GCP project id.
59+
'aud': project_id
60+
}
61+
62+
# Read the private key file.
63+
with open(private_key_file, 'r') as f:
64+
private_key = f.read()
65+
66+
print('Creating JWT using {} from private key file {}'.format(
67+
algorithm, private_key_file))
68+
69+
return jwt.encode(token, private_key, algorithm=algorithm)
70+
71+
72+
def parse_command_line_args():
73+
"""Parse command line arguments."""
74+
parser = argparse.ArgumentParser(description=(
75+
'Example Google Cloud IoT Core HTTP device connection code.'))
76+
parser.add_argument(
77+
'--project_id', required=True, help='GCP cloud project name')
78+
parser.add_argument(
79+
'--registry_id', required=True, help='Cloud IoT Core registry id')
80+
parser.add_argument(
81+
'--device_id', required=True, help='Cloud IoT Core device id')
82+
parser.add_argument(
83+
'--private_key_file',
84+
required=True,
85+
help='Path to private key file.')
86+
parser.add_argument(
87+
'--algorithm',
88+
choices=('RS256', 'ES256'),
89+
required=True,
90+
help='The encryption algorithm to use to generate the JWT.')
91+
parser.add_argument(
92+
'--cloud_region', default='us-central1', help='GCP cloud region')
93+
parser.add_argument(
94+
'--ca_certs',
95+
default='roots.pem',
96+
help=('CA root from https://pki.google.com/roots.pem'))
97+
parser.add_argument(
98+
'--num_messages',
99+
type=int,
100+
default=100,
101+
help='Number of messages to publish.')
102+
parser.add_argument(
103+
'--message_type',
104+
choices=('event', 'state'),
105+
default='event',
106+
required=True,
107+
help=('Indicates whether the message to be published is a '
108+
'telemetry event or a device state message.'))
109+
parser.add_argument(
110+
'--base_url',
111+
default=_BASE_URL,
112+
help=('Base URL for the Cloud IoT Core Device Service API'))
113+
114+
return parser.parse_args()
115+
116+
117+
def main():
118+
args = parse_command_line_args()
119+
120+
# Publish to the events or state topic based on the flag.
121+
url_suffix = 'publishEvent' if args.message_type == 'event' else 'setState'
122+
123+
publish_url = (
124+
'{}/projects/{}/locations/{}/registries/{}/devices/{}:{}').format(
125+
args.base_url, args.project_id, args.cloud_region,
126+
args.registry_id, args.device_id, url_suffix)
127+
128+
jwt_token = create_jwt(
129+
args.project_id, args.private_key_file, args.algorithm)
130+
131+
headers = {
132+
'Authorization': 'Bearer {}'.format(jwt_token),
133+
'Content-Type': 'application/json'
134+
}
135+
136+
# Publish num_messages mesages to the HTTP bridge once per second.
137+
for i in range(1, args.num_messages + 1):
138+
payload = '{}/{}-payload-{}'.format(
139+
args.registry_id, args.device_id, i)
140+
print('Publishing message {}/{}: \'{}\''.format(
141+
i, args.num_messages, payload))
142+
body = None
143+
if args.message_type == 'event':
144+
body = {'binary_data': base64.urlsafe_b64encode(payload)}
145+
else:
146+
body = {
147+
'state': {'binary_data': base64.urlsafe_b64encode(payload)}
148+
}
149+
150+
resp = requests.post(
151+
publish_url, data=json.dumps(body), headers=headers)
152+
153+
print('HTTP response: ', resp)
154+
155+
# Send events every second. State should not be updated as often
156+
time.sleep(1 if args.message_type == 'event' else 5)
157+
print('Finished.')
158+
159+
160+
if __name__ == '__main__':
161+
main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
cryptography==2.0.3
2+
pyjwt==1.5.3
3+
requests==2.18.4

iot/api-client/manager/manager.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def get_client(service_account_json, api_key):
6969
provided API key and creating a service object using the service account
7070
credentials JSON."""
7171
api_scopes = ['https://www.googleapis.com/auth/cloud-platform']
72-
api_version = 'v1beta1'
72+
api_version = 'v1'
7373
discovery_api = 'https://cloudiot.googleapis.com/$discovery/rest'
7474
service_name = 'cloudiotcore'
7575

@@ -173,7 +173,7 @@ def delete_device(
173173

174174

175175
def delete_registry(
176-
service_account_json, api_key, project_id, cloud_region, registry_id):
176+
service_account_json, api_key, project_id, cloud_region, registry_id):
177177
"""Deletes the specified registry."""
178178
print('Delete registry')
179179
client = get_client(service_account_json, api_key)
@@ -216,6 +216,23 @@ def get_device(
216216
return device
217217

218218

219+
def get_state(
220+
service_account_json, api_key, project_id, cloud_region, registry_id,
221+
device_id):
222+
"""Retrieve a device's state blobs."""
223+
client = get_client(service_account_json, api_key)
224+
registry_name = 'projects/{}/locations/{}/registries/{}'.format(
225+
project_id, cloud_region, registry_id)
226+
227+
device_name = '{}/devices/{}'.format(registry_name, device_id)
228+
devices = client.projects().locations().registries().devices()
229+
state = devices.states().list(name=device_name, numStates=5).execute()
230+
231+
print('State: {}\n'.format(state))
232+
233+
return state
234+
235+
219236
def list_devices(
220237
service_account_json, api_key, project_id, cloud_region, registry_id):
221238
"""List all devices in the registry."""
@@ -261,9 +278,9 @@ def create_registry(
261278
project_id,
262279
cloud_region)
263280
body = {
264-
'eventNotificationConfig': {
281+
'eventNotificationConfigs': [{
265282
'pubsubTopicName': pubsub_topic
266-
},
283+
}],
267284
'id': registry_id
268285
}
269286
request = client.projects().locations().registries().create(
@@ -274,6 +291,7 @@ def create_registry(
274291
print('Created registry')
275292
return response
276293
except HttpError:
294+
print('Error, registry not created')
277295
return ""
278296

279297

@@ -425,7 +443,8 @@ def parse_command_line_args():
425443
command.add_parser('delete-device', help=delete_device.__doc__)
426444
command.add_parser('delete-registry', help=delete_registry.__doc__)
427445
command.add_parser('get', help=get_device.__doc__)
428-
command.add_parser('get-registry', help=get_device.__doc__)
446+
command.add_parser('get-registry', help=get_registry.__doc__)
447+
command.add_parser('get-state', help=get_state.__doc__)
429448
command.add_parser('list', help=list_devices.__doc__)
430449
command.add_parser('list-registries', help=list_registries.__doc__)
431450
command.add_parser('patch-es256', help=patch_es256_auth.__doc__)
@@ -436,6 +455,10 @@ def parse_command_line_args():
436455

437456
def run_command(args):
438457
"""Calls the program using the specified command."""
458+
if args.project_id is None:
459+
print('You must specify a project ID or set the environment variable.')
460+
return
461+
439462
if args.command == 'create-rsa256':
440463
create_rs256_device(
441464
args.service_account_json, args.api_key, args.project_id,
@@ -476,6 +499,11 @@ def run_command(args):
476499
args.service_account_json, args.api_key, args.project_id,
477500
args.cloud_region, args.registry_id, args.device_id)
478501

502+
elif args.command == 'get-state':
503+
get_state(
504+
args.service_account_json, args.api_key, args.project_id,
505+
args.cloud_region, args.registry_id, args.device_id)
506+
479507
elif args.command == 'list':
480508
list_devices(
481509
args.service_account_json, args.api_key, args.project_id,

iot/api-client/manager/manager_test.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ def test_add_delete_rs256_device(test_topic, capsys):
101101
service_account_json, api_key, project_id, cloud_region,
102102
registry_id, device_id)
103103

104+
manager.get_state(
105+
service_account_json, api_key, project_id, cloud_region,
106+
registry_id, device_id)
107+
104108
manager.delete_device(
105109
service_account_json, api_key, project_id, cloud_region,
106110
registry_id, device_id)
@@ -111,6 +115,7 @@ def test_add_delete_rs256_device(test_topic, capsys):
111115

112116
out, _ = capsys.readouterr()
113117
assert 'format : RSA_X509_PEM' in out
118+
assert 'State: {' in out
114119

115120

116121
def test_add_delete_es256_device(test_topic, capsys):
@@ -127,6 +132,10 @@ def test_add_delete_es256_device(test_topic, capsys):
127132
service_account_json, api_key, project_id, cloud_region,
128133
registry_id, device_id)
129134

135+
manager.get_state(
136+
service_account_json, api_key, project_id, cloud_region,
137+
registry_id, device_id)
138+
130139
manager.delete_device(
131140
service_account_json, api_key, project_id, cloud_region,
132141
registry_id, device_id)
@@ -137,6 +146,7 @@ def test_add_delete_es256_device(test_topic, capsys):
137146

138147
out, _ = capsys.readouterr()
139148
assert 'format : ES256_PEM' in out
149+
assert 'State: {' in out
140150

141151

142152
def test_add_patch_delete_rs256(test_topic, capsys):

0 commit comments

Comments
 (0)