├── .gitignore ├── CPUTemp ├── pi_cpu_temp_mqtt.py ├── readme.md └── requirements.txt ├── LICENSE.txt ├── commands.md ├── joystick ├── joystick_wiring.fzz ├── joystick_wiring.png ├── pubsub_servo.py ├── pubsub_stick.py ├── readme.md └── server_relay.py ├── kit parts list.md ├── pubsub-thermostat ├── control_server.py ├── pubsub-thermostat.fzz ├── pubsub_thermostat.py ├── readme.md └── requirements.txt ├── readme.md └── utility ├── photo_res.py └── servo_gpio.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pem 2 | *.rtf 3 | service_account.json 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /CPUTemp/pi_cpu_temp_mqtt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import argparse 16 | import datetime 17 | import time 18 | from time import gmtime, strftime 19 | import subprocess 20 | import random 21 | import ssl 22 | 23 | import jwt 24 | import paho.mqtt.client as mqtt 25 | 26 | # The initial backoff time after a disconnection occurs, in seconds. 27 | minimum_backoff_time = 1 28 | 29 | # The maximum backoff time before giving up, in seconds. 30 | MAXIMUM_BACKOFF_TIME = 32 31 | 32 | # Whether to wait with exponential backoff before publishing. 33 | should_backoff = False 34 | 35 | def create_jwt(project_id, private_key_file, algorithm): 36 | """Creates a JWT (https://jwt.io) to establish an MQTT connection. 37 | Args: 38 | project_id: The cloud project ID this device belongs to 39 | private_key_file: A path to a file containing either an RSA256 or ES256 40 | private key. 41 | algorithm: The encryption algorithm to use. Either 'RS256' or 'ES256' 42 | Returns: 43 | An MQTT generated from the given project_id and private key, which 44 | expires in 20 minutes. After 20 minutes, your client will be 45 | disconnected, and a new JWT will have to be generated. 46 | Raises: 47 | ValueError: If the private_key_file does not contain a known key. 48 | """ 49 | 50 | token = { 51 | # The time that the token was issued at 52 | 'iat': datetime.datetime.utcnow(), 53 | # When this token expires. The device will be disconnected after the 54 | # token expires, and will have to reconnect. 55 | 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=60), 56 | # The audience field should always be set to the GCP project id. 57 | 'aud': project_id 58 | } 59 | 60 | # Read the private key file. 61 | with open(private_key_file, 'r') as f: 62 | private_key = f.read() 63 | 64 | print( 65 | 'Creating JWT using {} from private key file {}'.format( 66 | algorithm, private_key_file)) 67 | 68 | return jwt.encode(token, private_key, algorithm=algorithm) 69 | 70 | 71 | def error_str(rc): 72 | """Convert a Paho error to a human readable string.""" 73 | return '{}: {}'.format(rc, mqtt.error_string(rc)) 74 | 75 | 76 | def on_connect(unused_client, unused_userdata, unused_flags, rc): 77 | """Callback for when a device connects.""" 78 | print('on_connect', error_str(rc)) 79 | # After a successful connect, reset backoff time and stop backing off. 80 | global should_backoff 81 | global minimum_backoff_time 82 | should_backoff = False 83 | minimum_backoff_time = 1 84 | 85 | 86 | def on_disconnect(unused_client, unused_userdata, rc): 87 | """Paho callback for when a device disconnects.""" 88 | print('on_disconnect', error_str(rc)) 89 | # Since a disconnect occurred, the next loop iteration will wait with 90 | # exponential backoff. 91 | global should_backoff 92 | should_backoff = True 93 | 94 | 95 | def on_publish(unused_client, unused_userdata, unused_mid): 96 | """Paho callback when a message is sent to the broker.""" 97 | print('on_publish') 98 | 99 | 100 | def parse_command_line_args(): 101 | """Parse command line arguments.""" 102 | parser = argparse.ArgumentParser( 103 | description=( 104 | 'Example Google Cloud IoT Core MQTT device connection code.')) 105 | parser.add_argument( 106 | '--project_id', 107 | required=True, 108 | help='GCP cloud project name') 109 | parser.add_argument( 110 | '--registry_id', 111 | required=True, 112 | help='Cloud IoT Core registry id') 113 | parser.add_argument( 114 | '--device_id', 115 | required=True, 116 | help='Cloud IoT Core device id') 117 | parser.add_argument( 118 | '--private_key_file', 119 | required=True, 120 | help='Path to private key file.') 121 | parser.add_argument( 122 | '--algorithm', 123 | choices=('RS256', 'ES256'), 124 | required=True, 125 | help='Which encryption algorithm to use to generate the JWT.') 126 | parser.add_argument( 127 | '--cloud_region', default='us-central1', help='GCP cloud region') 128 | parser.add_argument( 129 | '--ca_certs', 130 | default='roots.pem', 131 | help=('CA root certificate from https://pki.google.com/roots.pem')) 132 | parser.add_argument( 133 | '--mqtt_bridge_hostname', 134 | default='mqtt.googleapis.com', 135 | help='MQTT bridge hostname.') 136 | parser.add_argument( 137 | '--mqtt_bridge_port', default=8883, help='MQTT bridge port.') 138 | 139 | return parser.parse_args() 140 | 141 | 142 | def main(): 143 | global minimum_backoff_time 144 | args = parse_command_line_args() 145 | 146 | # Create our MQTT client. The client_id is a unique string that identifies 147 | # this device. For Google Cloud IoT Core, it must be in the format below. 148 | client = mqtt.Client( 149 | client_id=( 150 | 'projects/{}/locations/{}/registries/{}/devices/{}' 151 | .format( 152 | args.project_id, args.cloud_region, 153 | args.registry_id, args.device_id))) 154 | 155 | # With Google Cloud IoT Core, the username field is ignored, and the 156 | # password field is used to transmit a JWT to authorize the device. 157 | client.username_pw_set( 158 | username='unused', 159 | password=create_jwt( 160 | args.project_id, args.private_key_file, args.algorithm)) 161 | 162 | # Enable SSL/TLS support. 163 | client.tls_set(ca_certs=args.ca_certs, tls_version=ssl.PROTOCOL_TLSv1_2) 164 | 165 | # Register message callbacks. https://eclipse.org/paho/clients/python/docs/ 166 | # describes additional callbacks that Paho supports. In this example, the 167 | # callbacks just print to standard out. 168 | client.on_connect = on_connect 169 | client.on_publish = on_publish 170 | client.on_disconnect = on_disconnect 171 | 172 | # Connect to the Google MQTT bridge. 173 | client.connect(args.mqtt_bridge_hostname, args.mqtt_bridge_port) 174 | 175 | # Start the network loop. 176 | client.loop_start() 177 | 178 | # Wait if backoff is required. 179 | if should_backoff: 180 | # If backoff time is too large, give up. 181 | if minimum_backoff_time > MAXIMUM_BACKOFF_TIME: 182 | print('Exceeded maximum backoff time. Giving up.') 183 | else: 184 | # Otherwise, wait and connect again. 185 | delay = minimum_backoff_time + random.randint(0, 1000) / 1000.0 186 | print('Waiting for {} before reconnecting.'.format(delay)) 187 | time.sleep(delay) 188 | minimum_backoff_time *= 2 189 | client.connect(args.mqtt_bridge_hostname, args.mqtt_bridge_port) 190 | 191 | mqtt_topic = '/devices/{}/events'.format(args.device_id) 192 | 193 | # Get the Raspberry Pi's processor temperature. 194 | if subprocess.call(["which", "/opt/vc/bin/vcgencmd"]) == 0: 195 | temp = subprocess.check_output(["sudo", "/opt/vc/bin/vcgencmd", "measure_temp"]).split('=', 1)[-1].rstrip() 196 | # Get Mac's proc temp if "sudo gem install iStats" is installed 197 | elif subprocess.call(["which", "istats"]) == 0: 198 | #temp = subprocess.check_output(["istats"]).decode().split(":")[-1].split("\n")[0] 199 | #temp = "".join(subprocess.check_output(["istats"]).decode().split()).split(":")[-1].split("F")[0] 200 | temp = 40 201 | else: 202 | temp = 40 203 | 204 | payload = '{}/{} Time {} temperature {}'.format( 205 | args.registry_id, args.device_id, strftime("%Y-%m-%d %H:%M:%S", gmtime()), temp) 206 | print('Publishing message {}'.format(payload)) 207 | # Publish "payload" to the MQTT topic. qos=1 means at least once 208 | # delivery. Cloud IoT Core also supports qos=0 for at most once 209 | # delivery. 210 | client.publish(mqtt_topic, payload, qos=1) 211 | 212 | # End the network loop and finish. 213 | client.loop_stop() 214 | print('Finished.') 215 | 216 | 217 | if __name__ == '__main__': 218 | main() 219 | -------------------------------------------------------------------------------- /CPUTemp/readme.md: -------------------------------------------------------------------------------- 1 | ## CPUTemp Example 2 | --- 3 | 4 | This example is the our "Hello World" for our Raspberry Pi 3 setup. This should verify that you are able to send JWT encoded messages with MQTT to your Google Cloud project registery topic 5 | 6 | On your Pi export or set $project $registry and $device varialbes to your own and run: 7 | 8 | pi_cpu_temp_mqtt.py --project_id=$project --registry_id=$registry --device_id=$device --private_key_file=rsa_private.pem --algorithm=RS256 9 | 10 | gcloud command to fetch CPU temperature: 11 | 12 | gcloud pubsub subscriptions pull --auto-ack projects/$project/subscriptions/$mysub 13 | 14 | --- 15 | -------------------------------------------------------------------------------- /CPUTemp/requirements.txt: -------------------------------------------------------------------------------- 1 | google-api-python-client==1.6.3 2 | google-auth-httplib2==0.0.2 3 | google-auth==1.0.2 4 | google-cloud==0.27.0 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /commands.md: -------------------------------------------------------------------------------- 1 | ## This is a list of useful and example commands for gcloud and the example code in this repository 2 | 3 | Shell variables aren't necessary but useful for copy and pasting the following commands without modification. Please set: 4 | 5 | project=my-project-name-1234 6 | region=us-central1 7 | registry=example-registry 8 | device=my-rs256-device 9 | mysub=my-sub 10 | events=events 11 | 12 | 13 | List registries and devices in your $project 14 | 15 | gcloud beta iot registries list --project=$project --region=$region 16 | gcloud beta iot devices list --project=$project --region=$region --registry=$registry 17 | 18 | Create a new registery 19 | 20 | gcloud beta iot registries create $registry \ 21 | --project=$project \ 22 | --region=$region \ 23 | --event-pubsub-topic=projects/$project/topics/events 24 | 25 | Create a new device 26 | 27 | gcloud beta iot devices create $device \ 28 | --project=$project \ 29 | --region=$region \ 30 | --registry=$registry \ 31 | --public-key path=rsa_cert.pem,type=rs256 32 | 33 | Create a new pubsub subscription to an event 34 | 35 | gcloud beta pubsub subscriptions create projects/$project/subscriptions/$mysub --topic=$events 36 | 37 | Find more at:[ https://cloud.google.com/iot/docs/gcloud_examples](https://cloud.google.com/iot/docs/gcloud_examples) 38 | 39 | --- 40 | ## Sync files on host machine with RasPi as they are changed 41 | 42 | find $IDEdir -type f|entr rsync -aiv --no-o --size-only --progress $IDEdir -e ssh $destPi 43 | 44 | --- 45 | ## Useful RasPi commands for checking your wiring 46 | 47 | Blink GPIO 21 (i.e. and LED) 48 | 49 | gpio -g blink 21 50 | 51 | Read all of the GPIOs 52 | 53 | gpio readall 54 | 55 | See everything on the i2c bus 56 | 57 | i2cdetect -y 1 -------------------------------------------------------------------------------- /joystick/joystick_wiring.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARM-software/Cloud-IoT-Core-Kit-Examples/91923cdf588f3983159bb3ae4683736d5b06ffe4/joystick/joystick_wiring.fzz -------------------------------------------------------------------------------- /joystick/joystick_wiring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARM-software/Cloud-IoT-Core-Kit-Examples/91923cdf588f3983159bb3ae4683736d5b06ffe4/joystick/joystick_wiring.png -------------------------------------------------------------------------------- /joystick/pubsub_servo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import argparse 16 | import datetime 17 | import json 18 | import time 19 | import ssl 20 | 21 | import jwt 22 | import paho.mqtt.client as mqtt 23 | 24 | # Import SPI library (for hardware SPI) and MCP3008 library. 25 | import Adafruit_GPIO.SPI as SPI 26 | import Adafruit_MCP3008 27 | 28 | from subprocess import call 29 | 30 | # Software SPI configuration: 31 | CLK = 12 32 | MISO = 23 33 | MOSI = 24 34 | CS = 25 35 | mcp = Adafruit_MCP3008.MCP3008(clk=CLK, cs=CS, miso=MISO, mosi=MOSI) 36 | 37 | servoMin = 50 38 | servoMax = 250 39 | servoSteps = servoMax - servoMin 40 | stickSensitivity = 5 # the lower the number the more sensitive we are to stick changes that transmit a message 41 | stickToServoPositionRatio = 1024/servoSteps # assume 10bit ADC 42 | 43 | #Servo settings 44 | pwmGPIO = "18" 45 | pwmClock = "192" 46 | pwmRange = "2000" 47 | 48 | # Update and publish readings at a rate of SENSOR_POLL per second. 49 | SENSOR_POLL=2 50 | 51 | def create_jwt(project_id, private_key_file, algorithm): 52 | """Create a JWT (https://jwt.io) to establish an MQTT connection.""" 53 | token = { 54 | 'iat': datetime.datetime.utcnow(), 55 | 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=60), 56 | 'aud': project_id 57 | } 58 | with open(private_key_file, 'r') as f: 59 | private_key = f.read() 60 | print 'Creating JWT using {} from private key file {}'.format( 61 | algorithm, private_key_file) 62 | return jwt.encode(token, private_key, algorithm=algorithm) 63 | 64 | 65 | def error_str(rc): 66 | """Convert a Paho error to a human readable string.""" 67 | return '{}: {}'.format(rc, mqtt.error_string(rc)) 68 | 69 | 70 | class Device(object): 71 | """Represents the state of a single device.""" 72 | 73 | def __init__(self): 74 | #self.leftright = 512 75 | #self.updown = 512 76 | self.servoStep = 150 77 | self.connected = False 78 | 79 | def update_sensor_data(self): 80 | #self.leftright = mcp.read_adc(0) 81 | #self.updown = mcp.read_adc(1) 82 | leftRightServoStep = mcp.read_adc(0)/stickToServoPositionRatio 83 | leftRightServoStep = (leftRightServoStep/stickSensitivity)*stickSensitivity 84 | leftRightServoStep = leftRightServoStep + servoMin 85 | #print 'leftRightServoStep', leftRightServoStep 86 | #poll until the stick moves 87 | # while leftRightServoStep == self.servoStep: 88 | # leftRightServoStep = mcp.read_adc(0)/stickToServoPositionRatio 89 | # leftRightServoStep = (leftRightServoStep/stickSensitivity)*stickSensitivity 90 | # leftRightServoStep = leftRightServoStep + servoMin 91 | 92 | #print 'leftRightServoStep', leftRightServoStep 93 | self.servoStep = leftRightServoStep 94 | 95 | def wait_for_connection(self, timeout): 96 | """Wait for the device to become connected.""" 97 | total_time = 0 98 | while not self.connected and total_time < timeout: 99 | time.sleep(1) 100 | total_time += 1 101 | 102 | if not self.connected: 103 | raise RuntimeError('Could not connect to MQTT bridge.') 104 | 105 | def on_connect(self, unused_client, unused_userdata, unused_flags, rc): 106 | """Callback for when a device connects.""" 107 | print 'Connection Result:', error_str(rc) 108 | self.connected = True 109 | 110 | def on_disconnect(self, unused_client, unused_userdata, rc): 111 | """Callback for when a device disconnects.""" 112 | print 'Disconnected:', error_str(rc) 113 | self.connected = False 114 | 115 | def on_publish(self, unused_client, unused_userdata, unused_mid): 116 | """Callback when the device receives a PUBACK from the MQTT bridge.""" 117 | print 'Published message acked.' 118 | 119 | def on_subscribe(self, unused_client, unused_userdata, unused_mid, 120 | granted_qos): 121 | """Callback when the device receives a SUBACK from the MQTT bridge.""" 122 | print 'Subscribed: ', granted_qos 123 | if granted_qos[0] == 128: 124 | print 'Subscription failed.' 125 | 126 | def on_message(self, unused_client, unused_userdata, message): 127 | """Callback when the device receives a message on a subscription.""" 128 | payload = message.payload 129 | print "Received message '{}' on topic '{}' with Qos {}".format( 130 | payload, message.topic, str(message.qos)) 131 | 132 | # The device will receive its latest config when it subscribes to the config 133 | # topic. If there is no configuration for the device, the device will 134 | # receive an config with an empty payload. 135 | if not payload: 136 | print 'no payload' 137 | return 138 | 139 | # The config is passed in the payload of the message. In this example, the 140 | # server sends a serialized JSON string. 141 | data = json.loads(payload) 142 | if data['servoStep']: 143 | # If we're changing the servo position, print a message and update our 144 | # internal state. 145 | 146 | self.servoStep = data['servoStep'] 147 | if self.servoStep: 148 | print 'ServoStep', self.servoStep 149 | # move the servo to new position and respond with new position 150 | err = call(["gpio", "-g", "pwm", pwmGPIO, str(data['servoStep'])]) 151 | if err != 0: 152 | print "Couldn't move servo, error:", err 153 | 154 | def parse_command_line_args(): 155 | """Parse command line arguments.""" 156 | parser = argparse.ArgumentParser( 157 | description='Example Google Cloud IoT MQTT device connection code.') 158 | parser.add_argument( 159 | '--project_id', required=True, help='GCP cloud project name') 160 | parser.add_argument( 161 | '--registry_id', required=True, help='Cloud IoT registry id') 162 | parser.add_argument('--device_id', required=True, help='Cloud IoT device id') 163 | parser.add_argument( 164 | '--private_key_file', required=True, help='Path to private key file.') 165 | parser.add_argument( 166 | '--algorithm', 167 | choices=('RS256', 'ES256'), 168 | required=True, 169 | help='Which encryption algorithm to use to generate the JWT.') 170 | parser.add_argument( 171 | '--cloud_region', default='us-central1', help='GCP cloud region') 172 | parser.add_argument( 173 | '--ca_certs', 174 | default='roots.pem', 175 | help='CA root certificate. Get from https://pki.google.com/roots.pem') 176 | parser.add_argument( 177 | '--num_messages', 178 | type=int, 179 | default=100, 180 | help='Number of messages to publish.') 181 | parser.add_argument( 182 | '--mqtt_bridge_hostname', 183 | default='mqtt.googleapis.com', 184 | help='MQTT bridge hostname.') 185 | parser.add_argument( 186 | '--mqtt_bridge_port', default=8883, help='MQTT bridge port.') 187 | 188 | return parser.parse_args() 189 | 190 | 191 | def main(): 192 | args = parse_command_line_args() 193 | 194 | #setup PWM for servo 195 | err = call(["gpio", "-g", "mode", pwmGPIO, "pwm"]) 196 | err |= call(["gpio", "pwm-ms"]) 197 | err |= call(["gpio", "pwmc", pwmClock]) 198 | err |= call(["gpio", "pwmr", pwmRange]) 199 | if err != 0: 200 | print "gpio setup error:", err 201 | quit() 202 | 203 | # Create our MQTT client and connect to Cloud IoT. 204 | client = mqtt.Client( 205 | client_id='projects/{}/locations/{}/registries/{}/devices/{}'.format( 206 | args.project_id, args.cloud_region, args.registry_id, args.device_id)) 207 | client.username_pw_set( 208 | username='unused', 209 | password=create_jwt(args.project_id, args.private_key_file, 210 | args.algorithm)) 211 | client.tls_set(ca_certs=args.ca_certs, tls_version=ssl.PROTOCOL_TLSv1_2) 212 | 213 | device = Device() 214 | 215 | client.on_connect = device.on_connect 216 | # client.on_publish = device.on_publish 217 | client.on_disconnect = device.on_disconnect 218 | client.on_subscribe = device.on_subscribe 219 | client.on_message = device.on_message 220 | 221 | client.connect(args.mqtt_bridge_hostname, args.mqtt_bridge_port) 222 | 223 | client.loop_start() 224 | 225 | # This is the topic that the device will publish telemetry events to. 226 | mqtt_telemetry_topic = '/devices/{}/events'.format(args.device_id) 227 | 228 | # This is the topic that the device will receive configuration updates on. 229 | mqtt_config_topic = '/devices/{}/config'.format(args.device_id) 230 | 231 | # Wait up to 5 seconds for the device to connect. 232 | device.wait_for_connection(5) 233 | 234 | # Subscribe to the config topic. 235 | client.subscribe(mqtt_config_topic, qos=1) 236 | 237 | # Update and publish stick position readings at a rate of one per SENSOR_POLL but poll the sensor for "stickSensitivity" changes. 238 | while True: 239 | pass 240 | # time.sleep(SENSOR_POLL) 241 | 242 | client.disconnect() 243 | client.loop_stop() 244 | print 'Finished loop successfully. Goodbye!' 245 | 246 | 247 | if __name__ == '__main__': 248 | main() 249 | -------------------------------------------------------------------------------- /joystick/pubsub_stick.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import argparse 16 | import datetime 17 | import json 18 | import time 19 | import ssl 20 | 21 | import jwt 22 | import paho.mqtt.client as mqtt 23 | 24 | # Import SPI library (for hardware SPI) and MCP3008 library. 25 | import Adafruit_GPIO.SPI as SPI 26 | import Adafruit_MCP3008 27 | 28 | from subprocess import call 29 | 30 | # Software SPI configuration: 31 | CLK = 12 32 | MISO = 23 33 | MOSI = 24 34 | CS = 25 35 | mcp = Adafruit_MCP3008.MCP3008(clk=CLK, cs=CS, miso=MISO, mosi=MOSI) 36 | 37 | servoMin = 50 38 | servoMax = 250 39 | servoSteps = servoMax - servoMin 40 | stickSensitivity = 5 # the lower the number the more sensitive we are to stick changes that transmit a message 41 | stickToServoPositionRatio = 1024/float(servoSteps) # assume 10bit ADC 42 | 43 | #Servo settings 44 | # pwmGPIO = "18" 45 | # pwmClock = "192" 46 | # pwmRange = "2000" 47 | 48 | # Update and publish readings at a rate of SENSOR_POLL per second. 49 | SENSOR_POLL=0 50 | SENSOR_DEBOUNCE=0.1 51 | 52 | def create_jwt(project_id, private_key_file, algorithm): 53 | """Create a JWT (https://jwt.io) to establish an MQTT connection.""" 54 | token = { 55 | 'iat': datetime.datetime.utcnow(), 56 | 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=60), 57 | 'aud': project_id 58 | } 59 | with open(private_key_file, 'r') as f: 60 | private_key = f.read() 61 | print 'Creating JWT using {} from private key file {}'.format( 62 | algorithm, private_key_file) 63 | return jwt.encode(token, private_key, algorithm=algorithm) 64 | 65 | 66 | def error_str(rc): 67 | """Convert a Paho error to a human readable string.""" 68 | return '{}: {}'.format(rc, mqtt.error_string(rc)) 69 | 70 | 71 | class Device(object): 72 | """Represents the state of a single device.""" 73 | 74 | def __init__(self): 75 | #self.leftright = 512 76 | #self.updown = 512 77 | self.servoStep = 150 78 | self.connected = False 79 | # self.acked = True 80 | 81 | def update_sensor_data(self): 82 | leftRightServoStep = self.servoStep 83 | #self.leftright = mcp.read_adc(0) 84 | #self.updown = mcp.read_adc(1) 85 | # while self.acked == False: 86 | # pass 87 | # print "." 88 | #self.acked = False 89 | 90 | #print 'leftRightServoStep', leftRightServoStep 91 | #poll until the stick moves 92 | while leftRightServoStep == self.servoStep: 93 | leftRightServoStepPreDeb = mcp.read_adc(0)/stickToServoPositionRatio 94 | time.sleep(SENSOR_DEBOUNCE) 95 | leftRightServoStepPostDeb = mcp.read_adc(0)/stickToServoPositionRatio 96 | if leftRightServoStepPreDeb == leftRightServoStepPostDeb: 97 | leftRightServoStep = int(leftRightServoStepPreDeb/stickSensitivity)*stickSensitivity 98 | leftRightServoStep = leftRightServoStep + servoMin 99 | 100 | #print 'leftRightServoStep', leftRightServoStep 101 | self.servoStep = leftRightServoStep 102 | 103 | def wait_for_connection(self, timeout): 104 | """Wait for the device to become connected.""" 105 | total_time = 0 106 | while not self.connected and total_time < timeout: 107 | time.sleep(1) 108 | total_time += 1 109 | 110 | if not self.connected: 111 | raise RuntimeError('Could not connect to MQTT bridge.') 112 | 113 | def on_connect(self, unused_client, unused_userdata, unused_flags, rc): 114 | """Callback for when a device connects.""" 115 | print 'Connection Result:', error_str(rc) 116 | self.connected = True 117 | 118 | def on_disconnect(self, unused_client, unused_userdata, rc): 119 | """Callback for when a device disconnects.""" 120 | print 'Disconnected:', error_str(rc) 121 | self.connected = False 122 | 123 | def on_publish(self, unused_client, unused_userdata, unused_mid): 124 | """Callback when the device receives a PUBACK from the MQTT bridge.""" 125 | # self.acked = True 126 | print 'Published message acked.' 127 | 128 | def parse_command_line_args(): 129 | """Parse command line arguments.""" 130 | parser = argparse.ArgumentParser( 131 | description='Example Google Cloud IoT MQTT device connection code.') 132 | parser.add_argument( 133 | '--project_id', required=True, help='GCP cloud project name') 134 | parser.add_argument( 135 | '--registry_id', required=True, help='Cloud IoT registry id') 136 | parser.add_argument('--device_id', required=True, help='Cloud IoT device id') 137 | parser.add_argument( 138 | '--private_key_file', required=True, help='Path to private key file.') 139 | parser.add_argument( 140 | '--algorithm', 141 | choices=('RS256', 'ES256'), 142 | required=True, 143 | help='Which encryption algorithm to use to generate the JWT.') 144 | parser.add_argument( 145 | '--cloud_region', default='us-central1', help='GCP cloud region') 146 | parser.add_argument( 147 | '--ca_certs', 148 | default='roots.pem', 149 | help='CA root certificate. Get from https://pki.google.com/roots.pem') 150 | parser.add_argument( 151 | '--num_messages', 152 | type=int, 153 | default=100, 154 | help='Number of messages to publish.') 155 | parser.add_argument( 156 | '--mqtt_bridge_hostname', 157 | default='mqtt.googleapis.com', 158 | help='MQTT bridge hostname.') 159 | parser.add_argument( 160 | '--mqtt_bridge_port', default=8883, help='MQTT bridge port.') 161 | 162 | return parser.parse_args() 163 | 164 | 165 | def main(): 166 | args = parse_command_line_args() 167 | 168 | # print "stickToServoPositionRatio", stickToServoPositionRatio 169 | #setup PWM for servo 170 | # err = call(["gpio", "-g", "mode", pwmGPIO, "pwm"]) 171 | # err |= call(["gpio", "pwm-ms"]) 172 | # err |= call(["gpio", "pwmc", pwmClock]) 173 | # err |= call(["gpio", "pwmr", pwmRange]) 174 | # if err != 0: 175 | # print "gpio setup error:", err 176 | # quit() 177 | 178 | # Create our MQTT client and connect to Cloud IoT. 179 | client = mqtt.Client( 180 | client_id='projects/{}/locations/{}/registries/{}/devices/{}'.format( 181 | args.project_id, args.cloud_region, args.registry_id, args.device_id)) 182 | client.username_pw_set( 183 | username='unused', 184 | password=create_jwt(args.project_id, args.private_key_file, 185 | args.algorithm)) 186 | client.tls_set(ca_certs=args.ca_certs, tls_version=ssl.PROTOCOL_TLSv1_2) 187 | 188 | device = Device() 189 | 190 | client.on_connect = device.on_connect 191 | client.on_publish = device.on_publish 192 | client.on_disconnect = device.on_disconnect 193 | 194 | client.connect(args.mqtt_bridge_hostname, args.mqtt_bridge_port) 195 | 196 | client.loop_start() 197 | 198 | # This is the topic that the device will publish telemetry events to. 199 | mqtt_telemetry_topic = '/devices/{}/events'.format(args.device_id) 200 | 201 | # This is the topic that the device will receive configuration updates on. 202 | mqtt_config_topic = '/devices/{}/config'.format(args.device_id) 203 | 204 | # Wait up to 5 seconds for the device to connect. 205 | device.wait_for_connection(5) 206 | 207 | # Subscribe to the config topic. 208 | client.subscribe(mqtt_config_topic, qos=1) 209 | 210 | # Update and publish stick position readings at a rate of one per SENSOR_POLL but poll the sensor for "stickSensitivity" changes. 211 | for _ in range(args.num_messages): 212 | # In an actual device, this would read the device's sensors. 213 | device.update_sensor_data() 214 | 215 | # Report the joystick's position to the server, by serializing it as a JSON 216 | # string. 217 | payload = json.dumps({'servoStep': device.servoStep}) 218 | print 'Publishing payload', payload 219 | client.publish(mqtt_telemetry_topic, payload, qos=1) 220 | time.sleep(SENSOR_POLL) 221 | 222 | client.disconnect() 223 | client.loop_stop() 224 | print 'Finished loop successfully. Goodbye!' 225 | 226 | 227 | if __name__ == '__main__': 228 | main() 229 | -------------------------------------------------------------------------------- /joystick/readme.md: -------------------------------------------------------------------------------- 1 | This example will use the kit's servo and joystick in a complete IoT system with both server and device component(s). The devices in this system (your Cloud IoT Core kit(s) in this case) publish the joystick position (e.g. a dimmable light switch or mobile application control) data on its pubsub registry feed and individual device ID. A server python application, which you can run from any machine you like, consumes the telemetry data from your cloud Pub/Sub registery, topic and events. The server transmits the servo position to a device via a Cloud IoT Core configuration update. In this case, it transmits back to our Pi but it could be any number of devices; e.g. light bulbs, HVAC vents and fans, etc. 2 | 3 | Connect the RasPi Cobbler board to your breadboard and the 40 pin cable to your Pi 3 [as pictured here](https://cdn-shop.adafruit.com/970x728/2029-01.jpg). The keyed end in the cobbler is obvious, the white striped end of the cable and 90° angle of the cable coming off the RasPi (which is not keyed) are useful visual queues. 4 | 5 | This example requires the [included 10-bit ADC (MCP3008) to be connected via software SPI to the Pi](https://github.com/ARM-software/Cloud-IoT-Core-Kit-Examples/blob/master/joystick/joystick_wiring.png?raw=true) in order to read the joystick position. Follow [this guide](https://learn.adafruit.com/raspberry-pi-analog-to-digital-converters/mcp3008) for setting up software SPI. Please note, if you're using the same Pi to control both the servo and read the joystick, the servo must use GPIO 18 for pulse width modulation (PWM) so we'll be using GPIO 12 as our clock pin in this SPI setup. 6 | 7 | Install the Adafruit Python MCP3008 driver from GitHub as requested in the aforementioned setup guide in order to get the "simpletest.py" example code to debug your wiring. You'll have to change simpletest.py to use GPIO12 as the clock. You can pull any of the channels (0-7) up or down to test your wiring or just jump to hooking up the joystick and use that to test. 8 | 9 | sudo apt-get install build-essential python-dev python-smbus git 10 | cd ~ && mkdir dev 11 | cd dev 12 | git clone https://github.com/adafruit/Adafruit_Python_MCP3008.git 13 | cd Adafruit_Python_MCP3008 14 | sudo python setup.py install 15 | 16 | [Connect the joystick to the breadboard](https://github.com/ARM-software/Cloud-IoT-Core-Kit-Examples/blob/master/joystick/joystick_wiring.png?raw=true), the L/R+ to the 3.3v power rail, GND to the ground rail and L/R to CH0 on the MCP3008 ADC. You can also connect U/D to CH1 if you like but we'll only be using left and right in this example. In your Adafruit_Python_MCP3008/examples directory, you can now run: 17 | 18 | cd examples 19 | 20 | edit GPIO 18 to 12 with your favorite editor 21 | 22 | python simpletest.py 23 | 24 | 25 | Changes in the L/R stick position should show up as values between 0 to 1024. 26 | 27 | [Connect the servo](https://github.com/ARM-software/Cloud-IoT-Core-Kit-Examples/blob/master/joystick/joystick_wiring.png?raw=true) by wiring the brown wire to the ground rail, the red wire to the 5v rail or horizontal row and the orange wire to GPIO 18. Adafruit has an [explaination of the pulse width modulation](https://learn.adafruit.com/adafruits-raspberry-pi-lesson-8-using-a-servo-motor/software) (PWM) settings we'll use to below to test your wiring. Raspbian has a gpio command included so testing from your shell is easy. Configure with: 28 | 29 | gpio -g mode 18 pwm 30 | gpio pwm-ms 31 | gpio pwmc 192 32 | gpio pwmr 2000 33 | 34 | Move the servo to the middle, left and right: 35 | 36 | gpio -g pwm 18 150 37 | gpio -g pwm 18 50 38 | gpio -g pwm 18 250 39 | 40 | You'll need the Python pub/sub library and APIs and an API key if you haven't already performed these steps in the thermostat example code. 41 | 42 | sudo pip install --upgrade google-cloud-pubsub 43 | sudo pip install google-api-python-client google-auth-httplib2 google-auth google-cloud 44 | 45 | [Create an API key and service account named api-tester](https://cloud.google.com/iot/docs/samples/end-to-end-sample#create_your_credentials) and make a service_account.json file (steps 1 and 2 in the link) and put it in this example's directory (scp or rsync over ssh are easy ways to move files to your ssh connected Pi if you've downloaded the json file on a host machine). Set the an environment variable to point to this json file: 46 | 47 | export GOOGLE_APPLICATION_CREDENTIALS=service_account.json 48 | 49 | Make sure you're authenticated. If you haven't already associated a gcloud project_id with this project, you'll be asked to do so. Use the project you created in the top level readme of this code base. 50 | 51 | gcloud auth application-default login 52 | 53 | Change to the directory you've cloned this example to. i.e. "cd ~/Cloud-IoT-Core-Kit-Examples/joystick" and make sure to copy your rsa_private and ec_private files in to this directory or point the scripts to wherever they are. 54 | 55 | Our pubsub server relays the servo position to one or many devices: 56 | 57 | python server_relay.py \ 58 | --project_id=$project \ 59 | --pubsub_topic=$mytopic \ 60 | --pubsub_subscription=$mysub \ 61 | --api_key=$apiKey \ 62 | --service_account_json=/path_to_the_file/service_account.json 63 | 64 | This client will read the joystick position and send it to the server: 65 | 66 | python pubsub_stick.py \ 67 | --project_id=$project \ 68 | --registry_id=$registry \ 69 | --device_id=$device \ 70 | --private_key_file=/path_to_the_file/rsa_private.pem \ 71 | --algorithm=RS256 \ 72 | --ca_certs=/path_to_the_file/roots.pem 73 | 74 | This acknowledgement device will receive the servo position from the server: 75 | 76 | python pubsub_servo.py \ 77 | --project_id=$project \ 78 | --registry_id=$registry \ 79 | --device_id=$device2 \ 80 | --private_key_file=/path_to_the_file/ec_private.pem \ 81 | --algorithm=ES256 82 | -------------------------------------------------------------------------------- /joystick/server_relay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import argparse 16 | import base64 17 | import json 18 | import sys 19 | import time 20 | 21 | from googleapiclient import discovery 22 | from oauth2client.service_account import ServiceAccountCredentials 23 | 24 | from google.cloud import pubsub 25 | from google.oauth2 import service_account 26 | 27 | API_SCOPES = ['https://www.googleapis.com/auth/cloud-platform'] 28 | API_VERSION = 'v1' 29 | DISCOVERY_API = 'https://cloudiot.googleapis.com/$discovery/rest' 30 | SERVICE_NAME = 'cloudiot' 31 | 32 | def discovery_url(api_key): 33 | """Construct the discovery url for the given api key.""" 34 | return '{}?version={}&key={}'.format(DISCOVERY_API, API_VERSION, api_key) 35 | 36 | def get_client(service_account_json): 37 | """Returns an authorized API client by discovering the IoT API and creating 38 | a service object using the service account credentials JSON.""" 39 | api_scopes = ['https://www.googleapis.com/auth/cloud-platform'] 40 | api_version = 'v1' 41 | discovery_api = 'https://cloudiot.googleapis.com/$discovery/rest' 42 | service_name = 'cloudiotcore' 43 | 44 | credentials = service_account.Credentials.from_service_account_file( 45 | service_account_json) 46 | scoped_credentials = credentials.with_scopes(api_scopes) 47 | 48 | discovery_url = '{}?version={}'.format( 49 | discovery_api, api_version) 50 | 51 | return discovery.build( 52 | service_name, 53 | api_version, 54 | discoveryServiceUrl=discovery_url, 55 | credentials=scoped_credentials) 56 | 57 | def list_devices( 58 | service_account_json, project_id, cloud_region, registry_id): 59 | registry_path = 'projects/{}/locations/{}/registries/{}'.format( 60 | project_id, cloud_region, registry_id) 61 | client = get_client(service_account_json) 62 | devices = client.projects().locations().registries().devices( 63 | ).list(parent=registry_path).execute().get('devices', []) 64 | 65 | return devices 66 | 67 | class Server(object): 68 | """Represents the state of the server.""" 69 | 70 | def __init__(self, service_account_json, api_key): 71 | credentials = ServiceAccountCredentials.from_json_keyfile_name( 72 | service_account_json, API_SCOPES) 73 | if not credentials: 74 | sys.exit('Could not load service account credential from {}'.format( 75 | service_account_json)) 76 | 77 | self._service = discovery.build( 78 | SERVICE_NAME, 79 | API_VERSION, 80 | discoveryServiceUrl=discovery_url(api_key), 81 | credentials=credentials) 82 | 83 | def _update_device_config(self, project_id, region, registry_id, device_id, 84 | data): 85 | """Push the data to the given device as configuration.""" 86 | config_data = None 87 | if data['servoStep']: 88 | config_data = {'servoStep': data['servoStep']} 89 | print 'servoStep:', data['servoStep'], 'for device', device_id 90 | else: 91 | # servo hasn't moved, don't need to respond. 92 | return 93 | 94 | config_data_json = json.dumps(config_data) 95 | body = { 96 | # The device configuration specifies a version to update, which can be 97 | # used to avoid having configuration updates race. In this case, we 98 | # use the special value of 0, which tells Cloud IoT to always update the 99 | # config. 100 | 'version_to_update': 0, 101 | # The data is passed as raw bytes, so you encode it as base64. 102 | # Note that the device will receive the decoded string, so you 103 | # do not need to base64 decode the string on the device. 104 | 'binary_data': base64.b64encode( 105 | config_data_json.encode('utf-8')).decode('ascii') 106 | } 107 | 108 | device_name = 'projects/{}/locations/{}/registries/{}/devices/{}'.format( 109 | project_id, region, registry_id, device_id) 110 | 111 | request = self._service.projects().locations().registries().devices( 112 | ).modifyCloudToDeviceConfig( 113 | name=device_name, body=body) 114 | return request.execute() 115 | 116 | def run(self, project_id, pubsub_topic, pubsub_subscription, service_account_json): 117 | """The main loop for the device. Consume messages from the Pub/Sub topic.""" 118 | pubsub_client = pubsub.SubscriberClient() 119 | topic_name = 'projects/{}/topics/{}'.format(project_id, pubsub_topic) 120 | subscription_path = pubsub_client.subscription_path(project_id, pubsub_subscription) 121 | print 'Server running. Consuming telemetry events from', topic_name 122 | 123 | def callback(message): 124 | print 'waiting' 125 | # Pull from the subscription, waiting until there are messages. 126 | # print '.' 127 | data = json.loads(message.data) 128 | # Get the registry id and device id from the attributes. These are 129 | # automatically supplied by IoT, and allow the server to determine which 130 | # device sent the event. 131 | device_project_id = message.attributes['projectId'] 132 | device_registry_id = message.attributes['deviceRegistryId'] 133 | # device_id = message.attributes['deviceId'] 134 | device_region = message.attributes['deviceRegistryLocation'] 135 | 136 | devices = list_devices(service_account_json, device_project_id, device_region, device_registry_id) 137 | for device in devices: 138 | device_id = device.get('id') 139 | 140 | # don't send to the joystick device 141 | if (message.attributes['deviceId'] != device_id): 142 | # Send the config to the device. 143 | self._update_device_config(device_project_id, device_region, device_registry_id, device_id, data) 144 | time.sleep(1) 145 | messesage.ack() 146 | 147 | pubsub_client.subscribe(subscription_path, callback=callback) 148 | 149 | while(True): 150 | time.sleep(60) 151 | #subscription.acknowledge([ack_id for ack_id, message in results]) 152 | 153 | 154 | def parse_command_line_args(): 155 | """Parse command line arguments.""" 156 | parser = argparse.ArgumentParser( 157 | description='Example of Google Cloud IoT registry and device management.') 158 | # Required arguments 159 | parser.add_argument( 160 | '--project_id', required=True, help='GCP cloud project name.') 161 | parser.add_argument( 162 | '--pubsub_topic', 163 | required=True, 164 | help=('Google Cloud Pub/Sub topic name.')) 165 | parser.add_argument( 166 | '--pubsub_subscription', 167 | required=True, 168 | help='Google Cloud Pub/Sub subscription name.') 169 | parser.add_argument('--api_key', required=True, help='Your API key.') 170 | 171 | # Optional arguments 172 | parser.add_argument( 173 | '--service_account_json', 174 | default='service_account.json', 175 | help='Path to service account json file.') 176 | 177 | return parser.parse_args() 178 | 179 | def main(): 180 | args = parse_command_line_args() 181 | 182 | server = Server(args.service_account_json, args.api_key) 183 | server.run(args.project_id, args.pubsub_topic, args.pubsub_subscription, args.service_account_json) 184 | 185 | 186 | if __name__ == '__main__': 187 | main() 188 | -------------------------------------------------------------------------------- /kit parts list.md: -------------------------------------------------------------------------------- 1 | **Parts included in kit** 2 | 3 | --- 4 | 5 | • 1x Cardboard standard Adafruit black cardboard box 6 | 7 | • 1x Set of stickers for box top with name of kit and logos (Google IoT Core, Google  and ARM) 8 | 9 | • 1x Card in the box- “Start here!” web links to a Github with the examples and logos on card (Google IoT Core, Google, ARM) 10 | 11 | • [1x Pi3](https://www.adafruit.com/product/3055) 12 | 13 | • [1x Power Supply](https://www.adafruit.com/product/1995 ) 14 | 15 | • [1x I2C Temp/Pressure/Humidity Sensor](https://www.adafruit.com/product/2652) 16 | 17 | • [1x 8-Channel 10-bit ADC with SPI](https://www.adafruit.com/product/856) 18 | 19 | • [1x 8Gb SD with latest Raspian pre-loaded](https://www.adafruit.com/product/2767) 20 | 21 | • [1x Breadboard](https://www.adafruit.com/product/239) 22 | 23 | • [1x Servo](https://www.adafruit.com/product/169) 24 | 25 | • [1x Joystick (pluggable into breadboard)](https://www.adafruit.com/product/245) 26 | 27 | • [1 x Small pixel screen](https://www.adafruit.com/product/1633) 28 | 29 | • [1x Photo cell- CdS photoresistor](https://www.adafruit.com/product/161) 30 | 31 | • 1x Premium Male/Male Jumper Wires - 20 x 6" [150mm] 32 | 33 | • [1x Assembled Pi Cobbler Plus - Breakout Cable - for Pi B+/A+/Pi 2/Pi 3](https://www.adafruit.com/product/2029) 34 | 35 | • [1x 8 Channel ADC](https://www.adafruit.com/product/856) 36 | 37 | • 5x 10K 5% 1/4W Resistor 38 | 39 | • 5x 560 ohm 5% 1/4W Resistor 40 | 41 | • 1x Diffused 10mm Blue LED 42 | 43 | • 1x Electrolytic Capacitor - 1.0uF 44 | 45 | • 2x Diffused 10mm Red LED 46 | 47 | • 2x Diffused 10mm Green LED 48 | 49 | • 2x Diffused 10mm Blue LED 50 | 51 | • 2x Breadboard Trim Potentiometer 52 | 53 | • 3x 12mm Tactile Switches 54 | -------------------------------------------------------------------------------- /pubsub-thermostat/control_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import argparse 16 | import base64 17 | import json 18 | import sys 19 | import time 20 | 21 | from googleapiclient import discovery 22 | from oauth2client.service_account import ServiceAccountCredentials 23 | 24 | from google.cloud import pubsub 25 | 26 | API_SCOPES = ['https://www.googleapis.com/auth/cloud-platform'] 27 | API_VERSION = 'v1' 28 | DISCOVERY_API = 'https://cloudiot.googleapis.com/$discovery/rest' 29 | SERVICE_NAME = 'cloudiot' 30 | 31 | 32 | def discovery_url(api_key): 33 | """Construct the discovery url for the given api key.""" 34 | return '{}?version={}&key={}'.format(DISCOVERY_API, API_VERSION, api_key) 35 | 36 | 37 | class Server(object): 38 | """Represents the state of the server.""" 39 | 40 | def __init__(self, service_account_json, api_key): 41 | credentials = ServiceAccountCredentials.from_json_keyfile_name( 42 | service_account_json, API_SCOPES) 43 | if not credentials: 44 | sys.exit('Could not load service account credential from {}'.format( 45 | service_account_json)) 46 | 47 | self._service = discovery.build( 48 | SERVICE_NAME, 49 | API_VERSION, 50 | discoveryServiceUrl=discovery_url(api_key), 51 | credentials=credentials) 52 | 53 | def _update_device_config(self, project_id, region, registry_id, device_id, 54 | data, fan_on_thresh, fan_off_thresh): 55 | """Push the data to the given device as configuration.""" 56 | config_data = None 57 | if data['temperature'] < fan_off_thresh: 58 | # Turn off the fan. 59 | config_data = {'fan_on': False} 60 | print 'Temp:', data['temperature'],'C. Setting fan state for device', device_id, 'to off.' 61 | elif data['temperature'] > fan_on_thresh: 62 | # Turn on the fan 63 | config_data = {'fan_on': True} 64 | print 'Temp:', data['temperature'],'C. Setting fan state for device', device_id, 'to on.' 65 | else: 66 | # Temperature is OK, don't need to push a new config. 67 | return 68 | 69 | config_data_json = json.dumps(config_data) 70 | body = { 71 | # The device configuration specifies a version to update, which can be 72 | # used to avoid having configuration updates race. In this case, we 73 | # use the special value of 0, which tells Cloud IoT to always update the 74 | # config. 75 | 'version_to_update': 0, 76 | # The data is passed as raw bytes, so you encode it as base64. 77 | # Note that the device will receive the decoded string, so you 78 | # do not need to base64 decode the string on the device. 79 | 'binary_data': base64.b64encode( 80 | config_data_json.encode('utf-8')).decode('ascii') 81 | } 82 | 83 | device_name = 'projects/{}/locations/{}/registries/{}/devices/{}'.format( 84 | project_id, region, registry_id, device_id) 85 | 86 | request = self._service.projects().locations().registries().devices( 87 | ).modifyCloudToDeviceConfig( 88 | name=device_name, body=body) 89 | return request.execute() 90 | 91 | def run(self, project_id, pubsub_topic, pubsub_subscription, fan_on_thresh, fan_off_thresh): 92 | """The main loop for the device. Consume messages from the Pub/Sub topic.""" 93 | pubsub_client = pubsub.SubscriberClient() 94 | topic_name = 'projects/{}/topics/{}'.format(project_id, pubsub_topic) 95 | #topic = pubsub_client.topic(topic_name) 96 | subscription_path = pubsub_client.subscription_path(project_id, pubsub_subscription) 97 | print 'Server running. Consuming telemetry events from', topic_name 98 | 99 | def callback(message): 100 | # Pull from the subscription, waiting until there are messages..' 101 | data = json.loads(message.data) 102 | # Get the registry id and device id from the attributes. These are 103 | # automatically supplied by IoT, and allow the server to determine which 104 | # device sent the event. 105 | device_project_id = message.attributes['projectId'] 106 | device_registry_id = message.attributes['deviceRegistryId'] 107 | device_id = message.attributes['deviceId'] 108 | device_region = message.attributes['deviceRegistryLocation'] 109 | 110 | # Send the config to the device. 111 | self._update_device_config(device_project_id, device_region, device_registry_id, device_id, data, fan_on_thresh, fan_off_thresh) 112 | # state change updates throttled to 1 sec by pubsub. Obey or crash. 113 | time.sleep(1) 114 | messesage.ack() 115 | 116 | pubsub_client.subscribe(subscription_path, callback=callback) 117 | 118 | while(True): 119 | time.sleep(60) 120 | 121 | def parse_command_line_args(): 122 | """Parse command line arguments.""" 123 | parser = argparse.ArgumentParser( 124 | description='Example of Google Cloud IoT registry and device management.') 125 | # Required arguments 126 | parser.add_argument( 127 | '--project_id', required=True, help='GCP cloud project name.') 128 | parser.add_argument( 129 | '--pubsub_topic', 130 | required=True, 131 | help=('Google Cloud Pub/Sub topic name.')) 132 | parser.add_argument( 133 | '--pubsub_subscription', 134 | required=True, 135 | help='Google Cloud Pub/Sub subscription name.') 136 | parser.add_argument('--api_key', required=True, help='Your API key.') 137 | 138 | # Optional arguments 139 | parser.add_argument( 140 | '--service_account_json', 141 | default='service_account.json', 142 | help='Path to service account json file.') 143 | 144 | parser.add_argument( 145 | '--fan_on', 146 | type=int, 147 | default='23', 148 | help='Turn the fan on at or above this temperature, default 23C') 149 | 150 | parser.add_argument( 151 | '--fan_off', 152 | type=int, 153 | default='22', 154 | help='Turn the fan off at or below this temperature, default 22C') 155 | 156 | return parser.parse_args() 157 | 158 | 159 | def main(): 160 | args = parse_command_line_args() 161 | 162 | server = Server(args.service_account_json, args.api_key) 163 | server.run(args.project_id, args.pubsub_topic, args.pubsub_subscription, args.fan_on, args.fan_off) 164 | 165 | 166 | if __name__ == '__main__': 167 | main() 168 | -------------------------------------------------------------------------------- /pubsub-thermostat/pubsub-thermostat.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARM-software/Cloud-IoT-Core-Kit-Examples/91923cdf588f3983159bb3ae4683736d5b06ffe4/pubsub-thermostat/pubsub-thermostat.fzz -------------------------------------------------------------------------------- /pubsub-thermostat/pubsub_thermostat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import argparse 16 | import datetime 17 | import json 18 | import time 19 | import ssl 20 | 21 | import jwt 22 | import paho.mqtt.client as mqtt 23 | 24 | from Adafruit_BME280 import * 25 | import RPi.GPIO as GPIO 26 | 27 | FAN_GPIO = 21 28 | sensor = BME280(t_mode=BME280_OSAMPLE_8, p_mode=BME280_OSAMPLE_8, h_mode=BME280_OSAMPLE_8) 29 | 30 | # Update and publish temperature readings at a rate of SENSOR_POLL per second. 31 | SENSOR_POLL=5 32 | 33 | def create_jwt(project_id, private_key_file, algorithm): 34 | """Create a JWT (https://jwt.io) to establish an MQTT connection.""" 35 | token = { 36 | 'iat': datetime.datetime.utcnow(), 37 | 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=60), 38 | 'aud': project_id 39 | } 40 | with open(private_key_file, 'r') as f: 41 | private_key = f.read() 42 | print 'Creating JWT using {} from private key file {}'.format( 43 | algorithm, private_key_file) 44 | return jwt.encode(token, private_key, algorithm=algorithm) 45 | 46 | 47 | def error_str(rc): 48 | """Convert a Paho error to a human readable string.""" 49 | return '{}: {}'.format(rc, mqtt.error_string(rc)) 50 | 51 | 52 | class Device(object): 53 | """Represents the state of a single device.""" 54 | 55 | def __init__(self): 56 | self.temperature = 0 57 | self.fan_on = False 58 | self.connected = False 59 | 60 | def update_sensor_data(self): 61 | self.temperature = int(sensor.read_temperature()) 62 | 63 | def wait_for_connection(self, timeout): 64 | """Wait for the device to become connected.""" 65 | total_time = 0 66 | while not self.connected and total_time < timeout: 67 | time.sleep(1) 68 | total_time += 1 69 | 70 | if not self.connected: 71 | raise RuntimeError('Could not connect to MQTT bridge.') 72 | 73 | def on_connect(self, unused_client, unused_userdata, unused_flags, rc): 74 | """Callback for when a device connects.""" 75 | print 'Connection Result:', error_str(rc) 76 | self.connected = True 77 | 78 | def on_disconnect(self, unused_client, unused_userdata, rc): 79 | """Callback for when a device disconnects.""" 80 | print 'Disconnected:', error_str(rc) 81 | self.connected = False 82 | 83 | def on_publish(self, unused_client, unused_userdata, unused_mid): 84 | """Callback when the device receives a PUBACK from the MQTT bridge.""" 85 | print 'Published message acked.' 86 | 87 | def on_subscribe(self, unused_client, unused_userdata, unused_mid, 88 | granted_qos): 89 | """Callback when the device receives a SUBACK from the MQTT bridge.""" 90 | print 'Subscribed: ', granted_qos 91 | if granted_qos[0] == 128: 92 | print 'Subscription failed.' 93 | 94 | def on_message(self, unused_client, unused_userdata, message): 95 | """Callback when the device receives a message on a subscription.""" 96 | payload = message.payload 97 | print "Received message '{}' on topic '{}' with Qos {}".format( 98 | payload, message.topic, str(message.qos)) 99 | 100 | # The device will receive its latest config when it subscribes to the config 101 | # topic. If there is no configuration for the device, the device will 102 | # receive an config with an empty payload. 103 | if not payload: 104 | return 105 | 106 | # The config is passed in the payload of the message. In this example, the 107 | # server sends a serialized JSON string. 108 | data = json.loads(payload) 109 | if data['fan_on'] != self.fan_on: 110 | # If we're changing the state of the fan, print a message and update our 111 | # internal state. 112 | self.fan_on = data['fan_on'] 113 | if self.fan_on: 114 | GPIO.output(FAN_GPIO, GPIO.HIGH) 115 | print 'Fan turned on.' 116 | else: 117 | GPIO.output(FAN_GPIO, GPIO.LOW) 118 | print 'Fan turned off.' 119 | 120 | 121 | def parse_command_line_args(): 122 | """Parse command line arguments.""" 123 | parser = argparse.ArgumentParser( 124 | description='Example Google Cloud IoT MQTT device connection code.') 125 | parser.add_argument( 126 | '--project_id', required=True, help='GCP cloud project name') 127 | parser.add_argument( 128 | '--registry_id', required=True, help='Cloud IoT registry id') 129 | parser.add_argument('--device_id', required=True, help='Cloud IoT device id') 130 | parser.add_argument( 131 | '--private_key_file', required=True, help='Path to private key file.') 132 | parser.add_argument( 133 | '--algorithm', 134 | choices=('RS256', 'ES256'), 135 | required=True, 136 | help='Which encryption algorithm to use to generate the JWT.') 137 | parser.add_argument( 138 | '--cloud_region', default='us-central1', help='GCP cloud region') 139 | parser.add_argument( 140 | '--ca_certs', 141 | default='roots.pem', 142 | help='CA root certificate. Get from https://pki.google.com/roots.pem') 143 | parser.add_argument( 144 | '--num_messages', 145 | type=int, 146 | default=100, 147 | help='Number of messages to publish.') 148 | parser.add_argument( 149 | '--mqtt_bridge_hostname', 150 | default='mqtt.googleapis.com', 151 | help='MQTT bridge hostname.') 152 | parser.add_argument( 153 | '--mqtt_bridge_port', default=8883, help='MQTT bridge port.') 154 | 155 | return parser.parse_args() 156 | 157 | 158 | def main(): 159 | args = parse_command_line_args() 160 | 161 | # Setup GPIOs for the RasPi3 and cobbler 162 | GPIO.setmode(GPIO.BCM) 163 | GPIO.setup(FAN_GPIO, GPIO.OUT) 164 | 165 | # Create our MQTT client and connect to Cloud IoT. 166 | client = mqtt.Client( 167 | client_id='projects/{}/locations/{}/registries/{}/devices/{}'.format( 168 | args.project_id, args.cloud_region, args.registry_id, args.device_id)) 169 | client.username_pw_set( 170 | username='unused', 171 | password=create_jwt(args.project_id, args.private_key_file, 172 | args.algorithm)) 173 | client.tls_set(ca_certs=args.ca_certs, tls_version=ssl.PROTOCOL_TLSv1_2) 174 | 175 | device = Device() 176 | 177 | client.on_connect = device.on_connect 178 | client.on_publish = device.on_publish 179 | client.on_disconnect = device.on_disconnect 180 | client.on_subscribe = device.on_subscribe 181 | client.on_message = device.on_message 182 | 183 | client.connect(args.mqtt_bridge_hostname, args.mqtt_bridge_port) 184 | 185 | client.loop_start() 186 | 187 | # This is the topic that the device will publish telemetry events (temperature 188 | # data) to. 189 | mqtt_telemetry_topic = '/devices/{}/events'.format(args.device_id) 190 | 191 | # This is the topic that the device will receive configuration updates on. 192 | mqtt_config_topic = '/devices/{}/config'.format(args.device_id) 193 | 194 | # Wait up to 5 seconds for the device to connect. 195 | device.wait_for_connection(5) 196 | 197 | # Subscribe to the config topic. 198 | client.subscribe(mqtt_config_topic, qos=1) 199 | 200 | # Update and publish temperature readings at a rate of one per second. 201 | for _ in range(args.num_messages): 202 | device.update_sensor_data() 203 | 204 | # Report the device's temperature to the server, by serializing it as a JSON 205 | # string. 206 | payload = json.dumps({'temperature': device.temperature}) 207 | print 'Publishing payload', payload 208 | client.publish(mqtt_telemetry_topic, payload, qos=1) 209 | time.sleep(SENSOR_POLL) 210 | 211 | client.disconnect() 212 | client.loop_stop() 213 | GPIO.cleanup() 214 | print 'Finished loop successfully. Goodbye!' 215 | 216 | 217 | if __name__ == '__main__': 218 | main() 219 | -------------------------------------------------------------------------------- /pubsub-thermostat/readme.md: -------------------------------------------------------------------------------- 1 | This example will use the kit's temperature/pressure/humidity sensor to monitor temperature and control a fan in a complete IoT system with both a server and device component. The devices in this system (your Cloud IoT Core kit(s) in this case) publish temperature data on their pubsub registry feeds and individual device IDs. A server python application, which you can run from any machine you like, consumes the telemetry data from your Cloud Pub/Sub topic and events. The server then decides whether to turn on or off the individual devices' fans via a Cloud IoT Core configuration update. 2 | 3 | This example requires i2c to be enabled in order to read the [temperature sensor included with this kit](https://www.adafruit.com/product/2652). Please run 4 | 5 | sudo raspi-config 6 | 7 | Go to Interfacing Options->I2C and enable. Exit out of raspi-config and run: 8 | 9 | sudo i2cdetect -F 1 10 | 11 | Connect the RasPi Cobbler board to your breadboard and the 40 pin cable to your Pi 3 [as pictured here](https://cdn-shop.adafruit.com/970x728/2029-01.jpg). The keyed end in the cobbler is obvious, the white striped end of the cable and 90° angle of the cable coming off the RasPi (which is not keyed) are useful visual queues. Connect the Temp/Pressure/Humidity Sensor to the breadboard and connect the 3.3v and ground pins to the cobbler. Then connnect the i2c clock and data pins: On the [Pi Cobbler SDA is data pin and SCL is clock pin. On the BME280 sensor SDI is the data pin and SCK is the clock pin](https://cdn-learn.adafruit.com/assets/assets/000/046/724/medium800/temperature_end-to-end-thermo.png). 12 | 13 | Verify i2c is enabled. 14 | 15 | sudo i2cdetect -y 1 16 | 17 | Will display a grid showing what address any devices are using on the i2C bus. You can dump more information about any of the addresses shown with: 18 | 19 | sudo i2cdump -y 1 0x77 <--- hex number shown from previous command 20 | 21 | Install the Adafruit_Python_GPIO and Adafruit_Python_BME280 abstraction librabies 22 | 23 | sudo apt-get install build-essential python-pip python-dev python-smbus git 24 | cd ~ && mkdir dev 25 | cd dev 26 | git clone https://github.com/adafruit/Adafruit_Python_GPIO.git 27 | cd Adafruit_Python_GPIO 28 | sudo python setup.py install 29 | cd .. 30 | git clone https://github.com/adafruit/Adafruit_Python_BME280.git 31 | cd Adafruit_Python_BME280 32 | sudo python setup.py install 33 | If you wish to sanity check your i2c wiring and sensor further: 34 | 35 | python Adafruit_BME280_Example.py 36 | 37 | Now connect an LED to GIPO 21 and one of the GND pins with a resistor in series on your breadboard. i.e Pin 21 on your Cobbler -> the long pin of the blue LED -> resistor -> GND rail or pin row; [see this diagram](https://cdn-learn.adafruit.com/assets/assets/000/046/724/medium800/temperature_end-to-end-thermo.png). The included 560 Ohm and 10K Ohm resistors will both protect the circuit, the latter make the LED dim. You can sanity check your wiring using the following commands one by one: 38 | 39 | python 40 | import RPi.GPIO as GPIO 41 | GPIO.setmode(GPIO.BCM) 42 | GPIO.setup(21, GPIO.OUT) 43 | GPIO.output(21, GPIO.HIGH) 44 | GPIO.output(21, GPIO.LOW) 45 | quit() 46 | 47 | Using "GPIO.output(21, GPIO.HIGH)" and "GPIO.output(21, GPIO.LOW)" should toggle your LED on an off. 48 | 49 | You'll also need the Python pub/sub library and APIs 50 | 51 | sudo pip install --upgrade google-cloud-pubsub 52 | sudo pip install google-api-python-client google-auth-httplib2 google-auth google-cloud 53 | 54 | [Create an API key and service account named api-tester](https://cloud.google.com/iot/docs/device_manager_samples) and make a service_account.json file (steps 1 and 2 in the link) and put it in this example's directory (scp or rsync over ssh are easy ways to move files to your ssh connected Pi if you've downloaded the json file on a host machine). 55 | 56 | Make sure you're authenticated. If you haven't already associated a gcloud project_id with this project, you'll be asked to do so. Use the project you created in the top level readme of this code base. 57 | 58 | gcloud auth application-default login 59 | 60 | Change to the directory you've cloned this example to. i.e. "cd ~/Cloud-IoT-Core-Kit-Examples/pubsub-thermostat" 61 | Our control server can run on any host machine, including the RasPi. The "--fan_off" and "--fan_on" arguments are the integer temperatures in °C that will turn on the "fan" LED i.e. when a devices is over 23°C and when it will turn the fan back off i.e. when a device is under 22°C. See optional argument options like "--service_account_json=directory/location" in the code. 62 | 63 | python control_server.py \ 64 | --project_id=$project \ 65 | --pubsub_topic=$mytopic \ 66 | --pubsub_subscription=$mysub \ 67 | --api_key=$apiKey \ 68 | --fan_off=22 \ 69 | --fan_on=23 \ 70 | --service_account_json=/path_to_the_file/service_account.json 71 | 72 | The client will run on one or many RasPi Cloud IoT kits with unique device ids: 73 | 74 | python pubsub_thermostat.py \ 75 | --project_id=$project \ 76 | --registry_id=$registry \ 77 | --device_id=$device \ 78 | --private_key_file=/path_to_the_file/rsa_private.pem \ 79 | --algorithm=RS256 \ 80 | --ca_certs=/path_to_the_file/roots.pem 81 | -------------------------------------------------------------------------------- /pubsub-thermostat/requirements.txt: -------------------------------------------------------------------------------- 1 | google-api-python-client==1.6.3 2 | google-auth-httplib2==0.0.2 3 | google-auth==1.1.0 4 | google-cloud==0.27.0 5 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## [Example projects and code](https://github.com/ARM-software/Cloud-IoT-Core-Kit-Examples) are supplied to support the [Arm-based IoT Kit for Cloud IoT Core](https://www.adafruit.com/product/3609) [(kit including RasPi3)](https://www.adafruit.com/product/3594) from [Adafruit](https://www.adafruit.com) 2 | --- 3 | # Getting Started 4 | 5 | If you purchased the kit that includes the Raspberry Pi 3 Model B, this comes with a pre-formatted NOOBS microSD card. Simply inserting the card into the Pi and powering up the Pi with the included 5v micro USB power supply will boot the Pi and with no interaction, it will default to installing the Raspbian Linux distribution. This is what we want. There are many ways to get a Raspbian and the Pi set up for Google Cloud IoT Core functionality but this guide will focus on getting Raspbian on your WiFi network and headless with secure shell running, gcloud tools installed and IoT Core dependencies for Python installed. These steps will require an HDMI monitor, USB keyboard and mouse. 6 | 7 | ## Network and firmware updates 8 | 1. Hook up an HDMI monitor, USB keyboard and mouse (plug in an Ethernet cable if you do not intend to use WiFi) then power up your Pi. Once booted, use the WiFi menu in the upper right hand corner of the screen (it should appear with two red 'x's on boot) to connect to the SSID of the wireless network you wish to use. This assumes your network has a DHCP service running on it. If your network has corporate security features, please use another guide appropriate to the type of security required [most require creative use of the wpa_supplicant command and configuration in /etc]. 9 | 2. Use the Raspberry menu to access Preferences->Raspberry Pi Configuration. Under the system tab you can change the hostname to whatever you like and set Boot to CLI (not Desktop); this is optional. Under the Interfaces tab enable "ssh" if you intend to use the Pi without a keyboard and monitor going forward. Under the Localisation tab, set up your Locale, Time Zone and Keyboard preferences. A reboot is required after this. 10 | 3. Once rebooted and connected to a network we can secure shell into our Pi remotely or use the command line directly to update our Linux distro and Raspberry Pi 3 firmware. The default uersname is "pi", default password is "raspberry ". To get the Pi's IP, use the command "ifconfig" or nmap your subnet for new ssh services. However you connect, update your Pi with the following commands and change your pi's default password with the "passwd" command if you so choose: 11 | 12 | *Get root access for updates* 13 | 14 | 15 | `sudo -s` 16 | 17 | *This step can take a while due to the number of packages installed by default on the Pi, feel free to uninstall the wolfram-engine, browsers, office applications, etc. at your discretion before running the updates* 18 | 19 | `apt update && apt upgrade && apt dist-upgrade` 20 | 21 | 22 | *Update the pi firmware (most likely requires a reboot after completion)* 23 | 24 | `rpi-update && reboot` 25 | 26 | *note: you can change most boot, bus and, interface options with a curses interface as well using **sudo raspi-config** i.e. enabling the i2c interface* 27 | 28 | --- 29 | ## Enabling Cloud IoT Core AP, installing the Google Cloud SDK and registering your first device 30 | Before you proceed please ensure you are logged into Google via your browser with the same userid and password you used with gcloud init on your development machine. 31 | 32 | The Google Cloud SDK can be installed on another host machine or the Pi itself. These steps will get the gcloud command installed on the Pi but it can just as easily be done on any machine that you do your development on. 33 | 34 | 1. Create a Cloud Platform project and enable the Cloud IoT Core API using these **"[Before you begin](https://cloud.google.com/iot/docs/how-tos/getting-started)"** directions. 35 | 36 | 2. Install **[the latest Google Cloud Tools](https://cloud.google.com/sdk/docs/#deb)** with the included directions. In Linux some of the additions require "sudo gcloud" to be used so you'll need to authorize your root account with sudo in addition to your 'pi' account so instructions from here will diverge from those included [here](https://cloud.google.com/iot/docs/device_manager_guide#install_the_gcloud_cli). Simply follow the directions below instead if you are installing gcloud on the Pi rather than another host machine. SSHing into your Pi (headless) is **strongly** advised in order facilitate authentication of your accounts with your normal desktop browser using copy/paste. 37 | 38 | 39 | `sudo gcloud components repositories add https://storage.googleapis.com/cloud-iot-gcloud/components-2.json` 40 | 41 | 3. Create shell variables with your specific project name from step 1 as well as region, registry, device, subscription and event names. Fill in your project ID from step 1, the rest can remain as is below and used in your .profile or .bashrc. i.e. 42 | 43 | ```bash 44 | project=my-project-name-1234 45 | region=us-central1 46 | registry=example-registry 47 | device=my-rs256-device 48 | device2=my-es256-device 49 | mysub=my-sub 50 | events=events 51 | mytopic=events 52 | ``` 53 | 54 | 4. Create a new registry using the gcloud command. 55 | 56 | ```bash 57 | gcloud iot registries create $registry \ 58 | --project=$project \ 59 | --region=$region \ 60 | --event-notification-config=topic=projects/$project/topics/$events 61 | ``` 62 | 63 | 64 | 5. Create a public/private key pair(s) for your device(s) and create a new device(s) in your project and registry. Or, stretch goal, register one programmatically with [these code samples](https://cloud.google.com/iot/docs/device_manager_samples). 65 | 66 | ```bash 67 | openssl req -x509 -newkey rsa:2048 -keyout rsa_private.pem -nodes -out rsa_cert.pem 68 | ``` 69 | ```bash 70 | gcloud iot devices create $device \ 71 | --project=$project \ 72 | --region=$region \ 73 | --registry=$registry \ 74 | --public-key path=rsa_cert.pem,type=rs256 75 | ``` 76 | ```bash 77 | openssl ecparam -genkey -name prime256v1 -noout -out ec_private.pem 78 | openssl ec -in ec_private.pem -pubout -out ec_public.pem 79 | ``` 80 | ```bash 81 | gcloud iot devices create $device2 \ 82 | --project=$project \ 83 | --region=$region \ 84 | --registry=$registry \ 85 | --public-key path=ec_public.pem,type=es256 86 | ``` 87 | 88 | 89 | 6. Create a new pubsub subscription to an event 90 | 91 | 92 | `gcloud pubsub subscriptions create projects/$project/subscriptions/$mysub --topic=$events` 93 | 94 | 7. Download the CA root certificates from pki.google.com into the same directory as the example script you want to use: 95 | 96 | 97 | `wget https://pki.google.com/roots.pem` 98 | 99 | 100 | --- 101 | 102 | ## Dependencies 103 | Our initial examples for this kit will focus on Python but it is entirely possible to use Ruby, Java, C and other languages to work with Google Cloud IoT. Dependencies include a JSON Web Token and MQTT library as well as a SSL/TLS library like OpenSSL. You'll need the following to run any of the examples included in this repository. 104 | 105 | ```bash 106 | sudo -s 107 | apt install build-essential libssl-dev libffi-dev python-dev python-pip 108 | pip install pyjwt paho-mqtt cryptography 109 | pip install --upgrade google-api-python-client 110 | pip install --upgrade google-cloud-core 111 | pip install --upgrade google-cloud-pubsub 112 | pip install --upgrade google-auth-httplib2 google-auth oauth2client 113 | exit 114 | ``` 115 | --- 116 | 117 | ## Hello World - Temperature example 118 | 119 | See [CPUTemp example's readme.md](https://github.com/ARM-software/Cloud-IoT-Core-Kit-Examples/tree/master/CPUTemp) to verify your device can communicate with your gcloud project. 120 | 121 | --- 122 | 123 | ## PubSub End-to-end - Thermostat example 124 | 125 | Wire up the thermostat and led sensors and create an [end to end server and device thermostat system](https://github.com/ARM-software/Cloud-IoT-Core-Kit-Examples/tree/master/pubsub-thermostat). 126 | 127 | --- 128 | 129 | ## Joystick End-to-end - analog to digital input/servo control example 130 | 131 | Connect a [joystick](https://github.com/ARM-software/Cloud-IoT-Core-Kit-Examples/tree/master/joystick) via 10-bit ADC and use the input to control a PWM servo, e.g. the way a rheostat or app might control multiple lights or a an HVAC controls multiple vents and fans. 132 | 133 | --- 134 | 135 | Find more [samples](https://cloud.google.com/iot/docs/samples/) and [documentation](https://cloud.google.com/iot/docs/) at the Google Cloud Platform IoT site. 136 | -------------------------------------------------------------------------------- /utility/photo_res.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Read photo resistor levels without an ADC by using a capacitor and counting the charges per cycle 4 | # based on https://learn.adafruit.com/basic-resistor-sensor-reading-on-raspberry-pi/basic-photocell-reading 5 | # and this principal https://learn.adafruit.com/basic-resistor-sensor-reading-on-raspberry-pi 6 | 7 | import RPi.GPIO as GPIO, time, os 8 | 9 | DEBUG = 1 10 | GPIO.setmode(GPIO.BCM) 11 | pin = 26 12 | 13 | def RCtime (RCpin): 14 | reading = 0 15 | GPIO.setup(RCpin, GPIO.OUT) 16 | GPIO.output(RCpin, GPIO.LOW) 17 | time.sleep(0.1) 18 | 19 | GPIO.setup(RCpin, GPIO.IN) 20 | # This takes about 1 millisecond per loop cycle 21 | while (GPIO.input(RCpin) == GPIO.LOW): 22 | reading += 1 23 | return reading 24 | 25 | while True: 26 | print RCtime(pin) 27 | -------------------------------------------------------------------------------- /utility/servo_gpio.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Connect the servo to 5v power and ground and the orange line to GPIO 18 4 | # This utility code will run the servo through its full range of motion 5 | 6 | from subprocess import call 7 | from time import sleep 8 | 9 | pwmGPIO = "18" 10 | pwmClock = "192" 11 | pwmRange = "2000" 12 | servoMin = 50 13 | servoMax = 250 14 | servoSteps = servoMax - servoMin 15 | stepDelay = 0.01 16 | 17 | #setup PWM for servo 18 | err = call(["gpio", "-g", "mode", pwmGPIO, "pwm"]) 19 | err |= call(["gpio", "pwm-ms"]) 20 | err |= call(["gpio", "pwmc", pwmClock]) 21 | err |= call(["gpio", "pwmr", pwmRange]) 22 | if err != 0: 23 | print "gpio setup error:", err 24 | quit() 25 | else: 26 | #move servo 27 | # call(["gpio", "-g", "pwm", pwmGPIO, str(servoMax)]) 28 | # sleep(0.5) 29 | # call(["gpio", "-g", "pwm", pwmGPIO, str(servoMin)]) 30 | # sleep(0.5) 31 | for step in range(0, servoSteps): 32 | #print step 33 | call(["gpio", "-g", "pwm", pwmGPIO, str(servoMin+step)]) 34 | sleep(stepDelay) 35 | #sleep(0.5) 36 | for step in range(0, servoSteps): 37 | #print step 38 | call(["gpio", "-g", "pwm", pwmGPIO, str(servoMax-step)]) 39 | sleep(stepDelay) 40 | 41 | --------------------------------------------------------------------------------