Skip to content

PyPortal AWS IoT Planter Project #884

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions PyPortal_AWS_IOT_Planter/aws_gfx_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
"""
GFX Helper for PyPortal AWS IoT Plant Monitor
"""
import board
import displayio
import terminalio
from adafruit_display_text.label import Label

# the current working directory (where this file is)
cwd = ("/"+__file__).rsplit('/', 1)[0]

# GFX Font
font = terminalio.FONT

class AWS_GFX(displayio.Group):
def __init__(self, is_celsius=False):
"""Creates an AWS_GFX object for displaying plant
and AWS IoT status.
:param bool is_celsius: Temperature displayed in Celsius.
"""
# root displayio group
root_group = displayio.Group(max_size=23)
self.display = board.DISPLAY
self.display.show(root_group)
super().__init__(max_size=15)

# temperature display option
self._is_celsius = is_celsius

# create background icon group
self._icon_group = displayio.Group(max_size=3)
self.append(self._icon_group)
self.display.show(self._icon_group)
# create text object group
self._text_group = displayio.Group(max_size=40)
self.append(self._text_group)

print("Displaying splash screen")
self._icon_sprite = None
self._icon_file = None
self._cwd = cwd
self.set_icon(self._cwd+"/images/aws_splash.bmp")

print('Setting up labels...')
header_group = displayio.Group(scale=3)
header_label = Label(font, text=" AWS IoT\n Planter")
header_label.x = (self.display.width // 2) // 22
header_label.y = 15
header_group.append(header_label)
self._text_group.append(header_group)

# Temperature Display
temp_group = displayio.Group(scale=2, max_size=400)
temp_label = Label(font, text="Temperature: ")
temp_label.x = (self.display.width//2) // 11
temp_label.y = 55
temp_group.append(temp_label)

self.temp_data_label = Label(font, text="75 F")
self.temp_data_label.x = (self.display.width//3)
self.temp_data_label.y = 55
temp_group.append(self.temp_data_label)
self._text_group.append(temp_group)

# Water Level
water_group = displayio.Group(scale=2, max_size=2)
self.water_level = Label(font, text="Water Level: ")
self.water_level.x = (self.display.width//2) // 11
self.water_level.y = 75
water_group.append(self.water_level)

self.water_lvl_label = Label(font, text="350")
self.water_lvl_label.x = (self.display.width//3)
self.water_lvl_label.y = 75
temp_group.append(self.water_lvl_label)
self._text_group.append(water_group)

# AWS Status
status_group = displayio.Group()
self.aws_status_label = Label(font, text="Connecting to AWS IoT...")
self.aws_status_label.x = int(self.display.width//3.5)
self.aws_status_label.y = 200
status_group.append(self.aws_status_label)
self._text_group.append(status_group)

self.display.show(self._text_group)

def show_aws_status(self, status_text):
"""Displays the system status on the PyPortal
:param str status_text: Description of current AWS IoT status.
"""
self.aws_status_label.text = status_text

def show_water_level(self, water_data):
"""Displays the water level from the Stemma Soil Sensor.
:param int water_data: water value
"""
self.water_lvl_label.text = str(water_data)

def show_temp(self, temp_data):
"""Displays the temperature from the Stemma Soil Sensor.
:param float temp_data: Temperature value.
"""
if not self._is_celsius:
temp_data = (temp_data * 9 / 5) + 32 - 15
print('Temperature: %0.0f°F'%temp_data)
if temp_data >= 212:
self.temp_data_label.color = 0xFD2EE
elif temp_data <= 32:
self.temp_data_label.color = 0xFF0000
self.temp_data_label = '%0.0f°F'%temp_data
temp_data = '%0.0f'%temp_data
return int(temp_data)
else:
print('Temperature: %0.0f°C'%temp_data)
if temp_data <= 0:
self.temp_data_label.color = 0xFD2EE
elif temp_data >= 100:
self.temp_data_label.color = 0xFF0000
self.temp_data_label.text = '%0.0f°C'%temp_data
temp_data = '%0.0f'%temp_data
return int(temp_data)

def set_icon(self, filename):
"""Sets the background image to a bitmap file.

:param filename: The filename of the chosen icon
"""
print("Set icon to ", filename)
if self._icon_group:
self._icon_group.pop()

if not filename:
return # we're done, no icon desired
if self._icon_file:
self._icon_file.close()
self._icon_file = open(filename, "rb")
icon = displayio.OnDiskBitmap(self._icon_file)
try:
self._icon_sprite = displayio.TileGrid(icon,
pixel_shader=displayio.ColorConverter())
except TypeError:
self._icon_sprite = displayio.TileGrid(icon,
pixel_shader=displayio.ColorConverter(),
position=(0,0))

self._icon_group.append(self._icon_sprite)
self.display.refresh_soon()
self.display.wait_for_frame()
Binary file added PyPortal_AWS_IOT_Planter/aws_splash.bmp
Binary file not shown.
168 changes: 168 additions & 0 deletions PyPortal_AWS_IOT_Planter/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
"""
PyPortal Amazon AWS IoT Plant Monitor
=========================================================
Log your plant's vitals to AWS IoT and receive email
notifications when it needs watering with your PyPortal.

Author: Brent Rubell for Adafruit Industries, 2019
"""
import time
import json
import board
import busio
from digitalio import DigitalInOut
import neopixel
from adafruit_esp32spi import adafruit_esp32spi
from adafruit_esp32spi import adafruit_esp32spi_wifimanager
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
from adafruit_minimqtt import MQTT
from adafruit_aws_iot import MQTT_CLIENT
from adafruit_seesaw.seesaw import Seesaw
import aws_gfx_helper

# Time between polling the STEMMA, in minutes
SENSOR_DELAY = 15

# 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

# Get device certificate
try:
with open("aws_cert.pem.crt", "rb") as f:
DEVICE_CERT = f.read()
except ImportError:
print("Certificate (aws_cert.pem.crt) not found on CIRCUITPY filesystem.")
raise

# Get device private key
try:
with open("private.pem.key", "rb") as f:
DEVICE_KEY = f.read()
except ImportError:
print("Key (private.pem.key) not found on CIRCUITPY filesystem.")
raise

# If you are using a board with pre-defined ESP32 Pins:
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

# If you have an externally connected ESP32:
# esp32_cs = DigitalInOut(board.D9)
# esp32_ready = DigitalInOut(board.D10)
# esp32_reset = DigitalInOut(board.D5)

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)

# Initialize the graphics helper
print("Loading AWS IoT Graphics...")
gfx = aws_gfx_helper.AWS_GFX()
print("Graphics loaded!")

# Set AWS Device Certificate
esp.set_certificate(DEVICE_CERT)

# Set AWS RSA Private Key
esp.set_private_key(DEVICE_KEY)

# Connect to WiFi
print("Connecting to WiFi...")
wifi.connect()
print("Connected!")

# Soil Sensor Setup
i2c_bus = busio.I2C(board.SCL, board.SDA)
ss = Seesaw(i2c_bus, addr=0x36)

# 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 AWS IoT!')
print('Flags: {0}\nRC: {1}'.format(flags, rc))

# Subscribe client to all shadow updates
print("Subscribing to shadow updates...")
aws_iot.shadow_subscribe()

def disconnect(client, userdata, rc):
# This method is called when the client disconnects
# from the broker.
print('Disconnected from AWS 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.
print("Message from {}: {}".format(topic, msg))

# Set up a new MiniMQTT Client
client = MQTT(socket,
broker = secrets['broker'],
client_id = secrets['client_id'],
network_manager = wifi)

# Initialize AWS IoT MQTT API Client
aws_iot = MQTT_CLIENT(client)

# Connect callback handlers to AWS IoT MQTT Client
aws_iot.on_connect = connect
aws_iot.on_disconnect = disconnect
aws_iot.on_subscribe = subscribe
aws_iot.on_unsubscribe = unsubscribe
aws_iot.on_publish = publish
aws_iot.on_message = message

print('Attempting to connect to %s'%client.broker)
aws_iot.connect()

# Time in seconds since power on
initial = time.monotonic()

while True:
try:
gfx.show_aws_status('Listening for msgs...')
now = time.monotonic()
if now - initial > (SENSOR_DELAY * 60):
# read moisture level
moisture = ss.moisture_read()
print("Moisture Level: ", moisture)
# read temperature
temperature = ss.get_temp()
# Display Soil Sensor values on pyportal
temperature = gfx.show_temp(temperature)
gfx.show_water_level(moisture)
print('Sending data to AWS IoT...')
gfx.show_aws_status('Publishing data...')
# Create a json-formatted device payload
payload = {"state":{"reported":{"moisture":str(moisture),
"temp":str(temperature)}}}
# Update device shadow
aws_iot.shadow_update(json.dumps(payload))
gfx.show_aws_status('Data published!')
print('Data sent!')
# Reset timer
initial = now
aws_iot.loop()
except (ValueError, RuntimeError) as e:
print("Failed to get data, retrying", e)
wifi.reset()