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()