CircuitPython Library Installation
First make sure you are running the latest version of Adafruit CircuitPython for your board.
Next you'll need to install the necessary libraries to use the hardware--carefully follow the steps to find and install these libraries from Adafruit's CircuitPython library bundle matching your version of CircuitPython. PyPortal requires at least CircuitPython version 4.0.0.
Connect your PyPortal to your computer using a known good data+power USB cable. The board should show up in your operating system file explorer/finder as a flash drive names CIRCUITPY.
Before continuing make sure your board's lib folder on CIRCUITPY has the following files and folders copied over.
- adafruit_binascii.mpy
- adafruit_imageload
- adafruit_bitmap_font
- adafruit_itertools
- adafruit_rsa
- adafruit_bus_device
- adafruit_jwt.mpy
- adafruit_seesaw
- adafruit_logging.mpy
- neopixel.mpy
- adafruit_esp32spi
- adafruit_minimqtt
- adafruit_gc_iot_core.mpy
- adafruit_ntp.mpy
- adafruit_register
Add CircuitPython Code and Project Assets
In the embedded code element below, click on the Download Project Bundle button, and save the .zip archive file to your computer.
Then, uncompress the .zip file, it will unpack to a folder named PyPortal_GC_IOT_CORE_PLANT_MONITOR.
Copy the contents of thePyPortal_GC_IOT_CORE_PLANT_MONITOR directory to your PyPortal CIRCUITPY drive.
# SPDX-FileCopyrightText: 2019 Brent Rubell for Adafruit Industries # # SPDX-License-Identifier: MIT """ PyPortal Google Cloud IoT Core Planter ======================================================= Water your plant remotely and log its vitals to Google Cloud IoT Core with your PyPortal. Author: Brent Rubell for Adafruit Industries, 2019 """ import time import json import board import busio import gcp_gfx_helper import neopixel import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager from adafruit_gc_iot_core import MQTT_API, Cloud_Core import adafruit_minimqtt.adafruit_minimqtt as MQTT from adafruit_seesaw.seesaw import Seesaw import digitalio # Delay before reading the sensors, in minutes SENSOR_DELAY = 10 # Get wifi details and more from a secrets.py file try: from secrets import secrets except ImportError: print("WiFi secrets are kept in secrets.py, please add them there!") raise # PyPortal ESP32 Setup esp32_cs = digitalio.DigitalInOut(board.ESP_CS) esp32_ready = digitalio.DigitalInOut(board.ESP_BUSY) esp32_reset = digitalio.DigitalInOut(board.ESP_RESET) spi = busio.SPI(board.SCK, board.MOSI, board.MISO) esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager( esp, secrets, status_light) # Connect to WiFi print("Connecting to WiFi...") wifi.connect() print("Connected!") pool = adafruit_connection_manager.get_radio_socketpool(esp) ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp) # Soil Sensor Setup i2c_bus = busio.I2C(board.SCL, board.SDA) ss = Seesaw(i2c_bus, addr=0x36) # Water Pump Setup water_pump = digitalio.DigitalInOut(board.D3) water_pump.direction = digitalio.Direction.OUTPUT # Initialize the graphics helper print("Loading GCP Graphics...") gfx = gcp_gfx_helper.Google_GFX() print("Graphics loaded!") # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connect(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. print('Connected to Google Cloud IoT!') print('Flags: {0}\nRC: {1}'.format(flags, rc)) # Subscribes to commands/# topic google_mqtt.subscribe_to_all_commands() def disconnect(client, userdata, rc): # This method is called when the client disconnects # from the broker. print('Disconnected from Google Cloud IoT!') def subscribe(client, userdata, topic, granted_qos): # This method is called when the client subscribes to a new topic. print('Subscribed to {0} with QOS level {1}'.format(topic, granted_qos)) def unsubscribe(client, userdata, topic, pid): # This method is called when the client unsubscribes from a topic. print('Unsubscribed from {0} with PID {1}'.format(topic, pid)) def publish(client, userdata, topic, pid): # This method is called when the client publishes data to a topic. print('Published to {0} with PID {1}'.format(topic, pid)) def message(client, topic, msg): # This method is called when the client receives data from a topic. try: # Attempt to decode a JSON command msg_dict = json.loads(msg) # Handle water-pump commands if msg_dict['pump_time']: handle_pump(msg_dict) except TypeError: # Non-JSON command, print normally print("Message from {}: {}".format(topic, msg)) def handle_pump(command): """Handles command about the planter's watering pump from Google Core IoT. :param json command: Message from device/commands# """ print("handling pump...") # Parse the pump command message # Expected format: {"power": true, "pump_time":3} pump_time = command['pump_time'] pump_status = command['power'] if pump_status: print("Turning pump on for {} seconds...".format(pump_time)) start_pump = time.monotonic() while True: gfx.show_gcp_status('Watering plant...') cur_time = time.monotonic() if cur_time - start_pump > pump_time: # Timer expired, leave the loop print("Plant watered!") break water_pump.value = True gfx.show_gcp_status('Plant watered!') print("Turning pump off") water_pump.value = False # Initialize Google Cloud IoT Core interface google_iot = Cloud_Core(esp, secrets) # JSON-Web-Token (JWT) Generation print("Generating JWT...") jwt = google_iot.generate_jwt() print("Your JWT is: ", jwt) # Set up a new MiniMQTT Client client = MQTT.MQTT(broker=google_iot.broker, username=google_iot.username, password=jwt, client_id=google_iot.cid, socket_pool=pool, ssl_context=ssl_context) # Initialize Google MQTT API Client google_mqtt = MQTT_API(client) # Connect callback handlers to Google MQTT Client google_mqtt.on_connect = connect google_mqtt.on_disconnect = disconnect google_mqtt.on_subscribe = subscribe google_mqtt.on_unsubscribe = unsubscribe google_mqtt.on_publish = publish google_mqtt.on_message = message print('Attempting to connect to %s' % client.broker) google_mqtt.connect() # Time in seconds since power on initial = time.monotonic() while True: try: gfx.show_gcp_status('Listening for new messages...') now = time.monotonic() if now - initial > (SENSOR_DELAY * 60): # read moisture level moisture_level = ss.moisture_read() # read temperature temperature = ss.get_temp() # Display Soil Sensor values on pyportal temperature = gfx.show_temp(temperature) gfx.show_water_level(moisture_level) print('Sending data to GCP IoT Core') gfx.show_gcp_status('Publishing data...') google_mqtt.publish(temperature, "events") time.sleep(2) google_mqtt.publish(moisture_level, "events") gfx.show_gcp_status('Data published!') print('Data sent!') # Reset timer initial = now google_mqtt.loop() except (ValueError, RuntimeError, OSError, ConnectionError) as e: print("Failed to get data, retrying", e) wifi.reset() google_mqtt.reconnect() continue
This is what the final contents of the CIRCUITPY drive will look like:
Install Mu Editor
This guide requires you to edit and interact with CircuitPython code. While you can use any text editor of your choosing, Mu is a simple code editor that works with the Adafruit CircuitPython boards. It's written in Python and works on Windows, MacOS, Linux and Raspberry Pi. The serial console is built right in, so you get immediate feedback from your board's serial output!
Before proceeding, click the button below to install the Mu Editor. There are versions for PC, mac, and Linux.
Secrets File Setup
Open the secrets.py file on your CIRCUITPY drive using Mu. You're going to edit the file to enter your local WiFi credentials along with data about your Google Cloud Services Project and IoT Core configuration.
Make the following changes to the code below in the secrets.py file:
-
Replace
MY_WIFI_SSID
with the name of your WiFi SSID -
Replace
MY_WIFI_PASSWORD
with your WiFi's password -
Replace
MY_GCS_PROJECT_ID
with the name of your Google Cloud Services project. -
Replace
MY_GCS_PROJECT_REGION
with the project's region. -
Replace
MY_IOT_REGISTRY
with the name of your Cloud IoT registry. - Replace
MY_IOT_DEVICE
with the name of the device you created in your Cloud IoT registry.
# This file is where you keep secret settings, passwords, and tokens! # If you put them in the code you risk committing that info or sharing it secrets = { 'ssid' : 'MY_WIFI_SSID', 'password' : 'MY_WIFI_PASSWORD', 'project_id' : 'MY_GCS_PROJECT_ID', 'cloud_region' : 'MY_GCS_PROJECT_REGION', 'registry_id' : 'MY_IOT_REGISTRY', 'device_id' : 'MY_IOT_DEVICE' }
CircuitPython authenticates with Google's MQTT API using a special security key called a JSON Web Token (JWT).
A JSON Web Token is a JSON dictionary containing statements (claims) about the device. In our case, it looks like the following:
token = { # The time that the token was issued at 'iat': issue_time, # The time the token expires. 'exp': expiration_time, # Your Project Identifier 'aud': project_id }
We are not going to create this JWT manually, the Google Core IoT CircuitPython module handles this automatically for you using the CircuitPython JWT module.
- For more information about JWTs, check out JWT.io's excellent introduction page.
The final step in generating a JWT involves signing the JWT with a private key. This ensures that the key will only be used to communicate with a public key.
Your CircuitPython device will hold the device's private RSA, while Google Cloud IoT holds the device's public RSA key
While the CircuitPython RSA module can generate RSA keys, it cannot save the key pairs to the device's filesystem.
We'll be generating a RSA key pair on our computer using a Python script.
Click decode_key_priv.py to download the code below and save the code to your desktop.
# SPDX-FileCopyrightText: 2019 Google Inc. # SPDX-FileCopyrightText: 2019 Brent Rubell for Adafruit Industries # # SPDX-License-Identifier: Apache-2.0 """ `decode_priv_key.py` =================================================================== Generates RSA keys and decodes them using python-rsa for use with a CircuitPython secrets file. This script is designed to run on a computer, NOT a CircuitPython device. Requires Python-RSA (https://github.com/sybrenstuvel/python-rsa) * Author(s): Google Inc., Brent Rubell """ import subprocess import rsa # Generate private and public RSA keys with subprocess.Popen(["openssl", "genrsa", "-out", "rsa_private.pem", "2048"]) as proc: proc.wait() with subprocess.Popen( ["openssl", "rsa", "-in", "rsa_private.pem", "-pubout", "-out", "rsa_public.pem"] ) as proc: proc.wait() # Open generated private key file try: with open("rsa_private.pem", "rb") as file: private_key = file.read() except: # pylint: disable=bare-except print("No file named rsa_private.pem found in directory.") pk = rsa.PrivateKey.load_pkcs1(private_key) print("Copy and paste this into your secrets.py file:\n") print('"private_key": ' + str(pk)[10:] + ",")
You'll need a local installation of Python (click here to download and install the Python distribution for your operating system) to run this script.
You'll also need to install the python-rsa library, you can do this by typing the following into your terminal:
pip install rsa
Once installed, run the code from your desktop:
python3 decode_key_priv.py
If the script runs successfully, it'll create two files on your desktop - rsa_public.pem and rsa_private.pem. These are your public and private RSA keys. You'll use them in the next step.
It'll also output the decoded private key to your terminal. The private key should have five integer values.
Modify your secrets.py file by copying and pasting the output from the code into the file:
# This file is where you keep secret settings, passwords, and tokens! # If you put them in the code you risk committing that info or sharing it secrets = { 'ssid' : 'MY_WIFI_SSID', 'password' : 'MY_WIFI_PASSWORD', 'project_id' : 'MY_GCS_PROJECT_ID', 'cloud_region' : 'MY_GCS_PROJECT_REGION', 'registry_id' : 'MY_IOT_REGISTRY', 'device_id' : 'MY_IOT_DEVICE', "private_key": (24438159363269526254144311871580579031858357859966324445350326786364998912771634237547789185814277400858631944952065597018819979449812397621834604674421573629778503529017607686952918724898685881544477812184759676064843937596886704154729857293596401786101074754877589082423154083489847915661860834755738610786688547912322386416918350317006245900073735354143276892049027125601443947584374912401061688828446039255462953272156360234392950941978497936249124410101311599817221805182114152095007037371899964182199631346414794479580997760720063537930724219713985584071493296120508892403130706628712278713361122757185268631117, 65531, 16011940782440353652943478520006562150052609770159665100376017092734074695430275633005814541997117225275335393994868497733923278721561016736829240348500272695770159475350047668916159269529419591276986698433153493297288534421934404632560234252209126031023393655102364697792853446832385820201112413513111592950042350642403708159079499430564775961436272409066068179381765892260252909360654617051331730162665742415598304194771289070036798676791361721996492207147874480559927670849388398727297101629122127273532260070125100209595266819508897273144318330256879686971164694481555143431693246541875462896679755259675697969793, 171180510224532714639208966958930887752631657983050450948053675977812070176625671098825502436380896087798236950227432035881066562828394842137169407540235290205801047834085254364601442602722583477726608017578416412915568120972286874859375397533049118528847434756643019078803240871975917780146737832789354797749, 142762310320270325250889040291289810070312245928207105466534900926375358526304862222284449091864287073322237001651192479298990227289938371837057243365681576671132453564694769006817561479289703593958730636340134647097544182128899145836621518733215831051979336393263185036416885910092387701460789142994244341633) }
Adding your RSA Public Key to Google Cloud IoT Core
After the private key is set up on your CircuitPython device, you'll also need to add the public key to the IoT Core Device.
To view the public key data, enter the following into your terminal:
less rsa_public.pem
It should print the RSA Public Key data to your terminal.
Copy the output starting with -----BEGIN PUBLIC KEY----- to your clipboard.
Navigate to the IoT Core Device details page. Under Authentication, click Add public key.
Specify the public key format to be RS256 and paste the public key data into the Public key value text box.
Click Add Public Key to add the authentication key to your device.
With the private key stored on your CircuitPython device and the public key stored in Google IoT Core, you're ready to run your code!
Text editor powered by tinymce.