code.py
  1  # SPDX-FileCopyrightText: 2019 Brent Rubell for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  
  5  """
  6  PyPortal Google Cloud IoT Core Planter
  7  =======================================================
  8  Water your plant remotely and log its vitals to Google
  9  Cloud IoT Core 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  import gcp_gfx_helper
 18  import neopixel
 19  from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager
 20  import adafruit_esp32spi.adafruit_esp32spi_socket as socket
 21  from adafruit_gc_iot_core import MQTT_API, Cloud_Core
 22  import adafruit_minimqtt.adafruit_minimqtt as MQTT
 23  from adafruit_seesaw.seesaw import Seesaw
 24  import digitalio
 25  
 26  # Delay before reading the sensors, in minutes
 27  SENSOR_DELAY = 10
 28  
 29  # Get wifi details and more from a secrets.py file
 30  try:
 31      from secrets import secrets
 32  except ImportError:
 33      print("WiFi secrets are kept in secrets.py, please add them there!")
 34      raise
 35  
 36  # PyPortal ESP32 Setup
 37  esp32_cs = digitalio.DigitalInOut(board.ESP_CS)
 38  esp32_ready = digitalio.DigitalInOut(board.ESP_BUSY)
 39  esp32_reset = digitalio.DigitalInOut(board.ESP_RESET)
 40  spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
 41  esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
 42  status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
 43  wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(
 44      esp, secrets, status_light)
 45  
 46  # Connect to WiFi
 47  print("Connecting to WiFi...")
 48  wifi.connect()
 49  print("Connected!")
 50  
 51  # Initialize MQTT interface with the esp interface
 52  MQTT.set_socket(socket, esp)
 53  
 54  # Soil Sensor Setup
 55  i2c_bus = busio.I2C(board.SCL, board.SDA)
 56  ss = Seesaw(i2c_bus, addr=0x36)
 57  
 58  # Water Pump Setup
 59  water_pump = digitalio.DigitalInOut(board.D3)
 60  water_pump.direction = digitalio.Direction.OUTPUT
 61  
 62  # Initialize the graphics helper
 63  print("Loading GCP Graphics...")
 64  gfx = gcp_gfx_helper.Google_GFX()
 65  print("Graphics loaded!")
 66  
 67  
 68  # Define callback methods which are called when events occur
 69  # pylint: disable=unused-argument, redefined-outer-name
 70  def connect(client, userdata, flags, rc):
 71      # This function will be called when the client is connected
 72      # successfully to the broker.
 73      print('Connected to Google Cloud IoT!')
 74      print('Flags: {0}\nRC: {1}'.format(flags, rc))
 75      # Subscribes to commands/# topic
 76      google_mqtt.subscribe_to_all_commands()
 77  
 78  
 79  def disconnect(client, userdata, rc):
 80      # This method is called when the client disconnects
 81      # from the broker.
 82      print('Disconnected from Google Cloud IoT!')
 83  
 84  
 85  def subscribe(client, userdata, topic, granted_qos):
 86      # This method is called when the client subscribes to a new topic.
 87      print('Subscribed to {0} with QOS level {1}'.format(topic, granted_qos))
 88  
 89  
 90  def unsubscribe(client, userdata, topic, pid):
 91      # This method is called when the client unsubscribes from a topic.
 92      print('Unsubscribed from {0} with PID {1}'.format(topic, pid))
 93  
 94  
 95  def publish(client, userdata, topic, pid):
 96      # This method is called when the client publishes data to a topic.
 97      print('Published to {0} with PID {1}'.format(topic, pid))
 98  
 99  
100  def message(client, topic, msg):
101      # This method is called when the client receives data from a topic.
102      try:
103          # Attempt to decode a JSON command
104          msg_dict = json.loads(msg)
105          # Handle water-pump commands
106          if msg_dict['pump_time']:
107              handle_pump(msg_dict)
108      except TypeError:
109          # Non-JSON command, print normally
110          print("Message from {}: {}".format(topic, msg))
111  
112  
113  def handle_pump(command):
114      """Handles command about the planter's
115      watering pump from Google Core IoT.
116      :param json command: Message from device/commands#
117      """
118      print("handling pump...")
119      # Parse the pump command message
120      # Expected format: {"power": true, "pump_time":3}
121      pump_time = command['pump_time']
122      pump_status = command['power']
123      if pump_status:
124          print("Turning pump on for {} seconds...".format(pump_time))
125          start_pump = time.monotonic()
126          while True:
127              gfx.show_gcp_status('Watering plant...')
128              cur_time = time.monotonic()
129              if cur_time - start_pump > pump_time:
130                  # Timer expired, leave the loop
131                  print("Plant watered!")
132                  break
133              water_pump.value = True
134      gfx.show_gcp_status('Plant watered!')
135      print("Turning pump off")
136      water_pump.value = False
137  
138  
139  # Initialize Google Cloud IoT Core interface
140  google_iot = Cloud_Core(esp, secrets)
141  
142  # JSON-Web-Token (JWT) Generation
143  print("Generating JWT...")
144  jwt = google_iot.generate_jwt()
145  print("Your JWT is: ", jwt)
146  
147  # Set up a new MiniMQTT Client
148  client = MQTT.MQTT(broker=google_iot.broker,
149                     username=google_iot.username,
150                     password=jwt,
151                     client_id=google_iot.cid)
152  
153  # Initialize Google MQTT API Client
154  google_mqtt = MQTT_API(client)
155  
156  # Connect callback handlers to Google MQTT Client
157  google_mqtt.on_connect = connect
158  google_mqtt.on_disconnect = disconnect
159  google_mqtt.on_subscribe = subscribe
160  google_mqtt.on_unsubscribe = unsubscribe
161  google_mqtt.on_publish = publish
162  google_mqtt.on_message = message
163  
164  print('Attempting to connect to %s' % client.broker)
165  google_mqtt.connect()
166  
167  # Time in seconds since power on
168  initial = time.monotonic()
169  
170  while True:
171      try:
172          gfx.show_gcp_status('Listening for new messages...')
173          now = time.monotonic()
174          if now - initial > (SENSOR_DELAY * 60):
175              # read moisture level
176              moisture_level = ss.moisture_read()
177              # read temperature
178              temperature = ss.get_temp()
179              # Display Soil Sensor values on pyportal
180              temperature = gfx.show_temp(temperature)
181              gfx.show_water_level(moisture_level)
182              print('Sending data to GCP IoT Core')
183              gfx.show_gcp_status('Publishing data...')
184              google_mqtt.publish(temperature, "events")
185              time.sleep(2)
186              google_mqtt.publish(moisture_level, "events")
187              gfx.show_gcp_status('Data published!')
188              print('Data sent!')
189              # Reset timer
190              initial = now
191          google_mqtt.loop()
192      except (ValueError, RuntimeError, OSError, ConnectionError) as e:
193          print("Failed to get data, retrying", e)
194          wifi.reset()
195          google_mqtt.reconnect()
196          continue