/ PyPortal_AWS_IOT_Planter / code.py
code.py
1 # SPDX-FileCopyrightText: 2019 Brent Rubell for Adafruit Industries 2 # 3 # SPDX-License-Identifier: MIT 4 5 """ 6 PyPortal Amazon AWS IoT Plant Monitor 7 ========================================================= 8 Log your plant's vitals to AWS IoT and receive email 9 notifications when it needs watering with your PyPortal. 10 11 Author: Brent Rubell for Adafruit Industries, 2019 12 """ 13 import time 14 import json 15 import board 16 import busio 17 from digitalio import DigitalInOut 18 import neopixel 19 from adafruit_esp32spi import adafruit_esp32spi 20 from adafruit_esp32spi import adafruit_esp32spi_wifimanager 21 import adafruit_esp32spi.adafruit_esp32spi_socket as socket 22 import adafruit_minimqtt.adafruit_minimqtt as MQTT 23 from adafruit_aws_iot import MQTT_CLIENT 24 from adafruit_seesaw.seesaw import Seesaw 25 import aws_gfx_helper 26 27 # Time between polling the STEMMA, in minutes 28 SENSOR_DELAY = 15 29 30 # Get wifi details and more from a secrets.py file 31 try: 32 from secrets import secrets 33 except ImportError: 34 print("WiFi secrets are kept in secrets.py, please add them there!") 35 raise 36 37 # Get device certificate 38 try: 39 with open("aws_cert.pem.crt", "rb") as f: 40 DEVICE_CERT = f.read() 41 except ImportError: 42 print("Certificate (aws_cert.pem.crt) not found on CIRCUITPY filesystem.") 43 raise 44 45 # Get device private key 46 try: 47 with open("private.pem.key", "rb") as f: 48 DEVICE_KEY = f.read() 49 except ImportError: 50 print("Key (private.pem.key) not found on CIRCUITPY filesystem.") 51 raise 52 53 # If you are using a board with pre-defined ESP32 Pins: 54 esp32_cs = DigitalInOut(board.ESP_CS) 55 esp32_ready = DigitalInOut(board.ESP_BUSY) 56 esp32_reset = DigitalInOut(board.ESP_RESET) 57 58 # If you have an externally connected ESP32: 59 # esp32_cs = DigitalInOut(board.D9) 60 # esp32_ready = DigitalInOut(board.D10) 61 # esp32_reset = DigitalInOut(board.D5) 62 63 spi = busio.SPI(board.SCK, board.MOSI, board.MISO) 64 esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) 65 66 # Verify nina-fw version >= 1.4.0 67 assert int(bytes(esp.firmware_version).decode("utf-8")[2]) >= 4, "Please update nina-fw to >=1.4.0." 68 69 status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) 70 wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager( 71 esp, secrets, status_light) 72 73 # Initialize the graphics helper 74 print("Loading AWS IoT Graphics...") 75 gfx = aws_gfx_helper.AWS_GFX() 76 print("Graphics loaded!") 77 78 # Set AWS Device Certificate 79 esp.set_certificate(DEVICE_CERT) 80 81 # Set AWS RSA Private Key 82 esp.set_private_key(DEVICE_KEY) 83 84 # Connect to WiFi 85 print("Connecting to WiFi...") 86 wifi.connect() 87 print("Connected!") 88 89 # Initialize MQTT interface with the esp interface 90 MQTT.set_socket(socket, esp) 91 92 # Soil Sensor Setup 93 i2c_bus = busio.I2C(board.SCL, board.SDA) 94 ss = Seesaw(i2c_bus, addr=0x36) 95 96 # Define callback methods which are called when events occur 97 # pylint: disable=unused-argument, redefined-outer-name 98 def connect(client, userdata, flags, rc): 99 # This function will be called when the client is connected 100 # successfully to the broker. 101 print('Connected to AWS IoT!') 102 print('Flags: {0}\nRC: {1}'.format(flags, rc)) 103 104 # Subscribe client to all shadow updates 105 print("Subscribing to shadow updates...") 106 aws_iot.shadow_subscribe() 107 108 def disconnect(client, userdata, rc): 109 # This method is called when the client disconnects 110 # from the broker. 111 print('Disconnected from AWS IoT!') 112 113 def subscribe(client, userdata, topic, granted_qos): 114 # This method is called when the client subscribes to a new topic. 115 print('Subscribed to {0} with QOS level {1}'.format(topic, granted_qos)) 116 117 def unsubscribe(client, userdata, topic, pid): 118 # This method is called when the client unsubscribes from a topic. 119 print('Unsubscribed from {0} with PID {1}'.format(topic, pid)) 120 121 def publish(client, userdata, topic, pid): 122 # This method is called when the client publishes data to a topic. 123 print('Published to {0} with PID {1}'.format(topic, pid)) 124 125 def message(client, topic, msg): 126 # This method is called when the client receives data from a topic. 127 print("Message from {}: {}".format(topic, msg)) 128 129 # Set up a new MiniMQTT Client 130 client = MQTT.MQTT(broker = secrets['broker'], 131 client_id = secrets['client_id']) 132 133 # Initialize AWS IoT MQTT API Client 134 aws_iot = MQTT_CLIENT(client) 135 136 # Connect callback handlers to AWS IoT MQTT Client 137 aws_iot.on_connect = connect 138 aws_iot.on_disconnect = disconnect 139 aws_iot.on_subscribe = subscribe 140 aws_iot.on_unsubscribe = unsubscribe 141 aws_iot.on_publish = publish 142 aws_iot.on_message = message 143 144 print('Attempting to connect to %s'%client.broker) 145 aws_iot.connect() 146 147 # Time in seconds since power on 148 initial = time.monotonic() 149 150 while True: 151 try: 152 gfx.show_aws_status('Listening for msgs...') 153 now = time.monotonic() 154 if now - initial > (SENSOR_DELAY * 60): 155 # read moisture level 156 moisture = ss.moisture_read() 157 print("Moisture Level: ", moisture) 158 # read temperature 159 temperature = ss.get_temp() 160 # Display Soil Sensor values on pyportal 161 temperature = gfx.show_temp(temperature) 162 gfx.show_water_level(moisture) 163 print('Sending data to AWS IoT...') 164 gfx.show_aws_status('Publishing data...') 165 # Create a json-formatted device payload 166 payload = {"state":{"reported":{"moisture":str(moisture), 167 "temp":str(temperature)}}} 168 # Update device shadow 169 aws_iot.shadow_update(json.dumps(payload)) 170 gfx.show_aws_status('Data published!') 171 print('Data sent!') 172 # Reset timer 173 initial = now 174 aws_iot.loop() 175 except (ValueError, RuntimeError, ConnectionError, OSError) as e: 176 print("Failed to get data, retrying", e) 177 wifi.reset()