Skip to content

Commit a180e4c

Browse files
authored
Merge pull request #884 from brentru/PyPortal_AWS_IOT_Planter
PyPortal AWS IoT Planter Project
2 parents f1e87c1 + 0a40c24 commit a180e4c

File tree

3 files changed

+317
-0
lines changed

3 files changed

+317
-0
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
"""
2+
GFX Helper for PyPortal AWS IoT Plant Monitor
3+
"""
4+
import board
5+
import displayio
6+
import terminalio
7+
from adafruit_display_text.label import Label
8+
9+
# the current working directory (where this file is)
10+
cwd = ("/"+__file__).rsplit('/', 1)[0]
11+
12+
# GFX Font
13+
font = terminalio.FONT
14+
15+
class AWS_GFX(displayio.Group):
16+
def __init__(self, is_celsius=False):
17+
"""Creates an AWS_GFX object for displaying plant
18+
and AWS IoT status.
19+
:param bool is_celsius: Temperature displayed in Celsius.
20+
"""
21+
# root displayio group
22+
root_group = displayio.Group(max_size=23)
23+
self.display = board.DISPLAY
24+
self.display.show(root_group)
25+
super().__init__(max_size=15)
26+
27+
# temperature display option
28+
self._is_celsius = is_celsius
29+
30+
# create background icon group
31+
self._icon_group = displayio.Group(max_size=3)
32+
self.append(self._icon_group)
33+
self.display.show(self._icon_group)
34+
# create text object group
35+
self._text_group = displayio.Group(max_size=40)
36+
self.append(self._text_group)
37+
38+
print("Displaying splash screen")
39+
self._icon_sprite = None
40+
self._icon_file = None
41+
self._cwd = cwd
42+
self.set_icon(self._cwd+"/images/aws_splash.bmp")
43+
44+
print('Setting up labels...')
45+
header_group = displayio.Group(scale=3)
46+
header_label = Label(font, text=" AWS IoT\n Planter")
47+
header_label.x = (self.display.width // 2) // 22
48+
header_label.y = 15
49+
header_group.append(header_label)
50+
self._text_group.append(header_group)
51+
52+
# Temperature Display
53+
temp_group = displayio.Group(scale=2, max_size=400)
54+
temp_label = Label(font, text="Temperature: ")
55+
temp_label.x = (self.display.width//2) // 11
56+
temp_label.y = 55
57+
temp_group.append(temp_label)
58+
59+
self.temp_data_label = Label(font, text="75 F")
60+
self.temp_data_label.x = (self.display.width//3)
61+
self.temp_data_label.y = 55
62+
temp_group.append(self.temp_data_label)
63+
self._text_group.append(temp_group)
64+
65+
# Water Level
66+
water_group = displayio.Group(scale=2, max_size=2)
67+
self.water_level = Label(font, text="Water Level: ")
68+
self.water_level.x = (self.display.width//2) // 11
69+
self.water_level.y = 75
70+
water_group.append(self.water_level)
71+
72+
self.water_lvl_label = Label(font, text="350")
73+
self.water_lvl_label.x = (self.display.width//3)
74+
self.water_lvl_label.y = 75
75+
temp_group.append(self.water_lvl_label)
76+
self._text_group.append(water_group)
77+
78+
# AWS Status
79+
status_group = displayio.Group()
80+
self.aws_status_label = Label(font, text="Connecting to AWS IoT...")
81+
self.aws_status_label.x = int(self.display.width//3.5)
82+
self.aws_status_label.y = 200
83+
status_group.append(self.aws_status_label)
84+
self._text_group.append(status_group)
85+
86+
self.display.show(self._text_group)
87+
88+
def show_aws_status(self, status_text):
89+
"""Displays the system status on the PyPortal
90+
:param str status_text: Description of current AWS IoT status.
91+
"""
92+
self.aws_status_label.text = status_text
93+
94+
def show_water_level(self, water_data):
95+
"""Displays the water level from the Stemma Soil Sensor.
96+
:param int water_data: water value
97+
"""
98+
self.water_lvl_label.text = str(water_data)
99+
100+
def show_temp(self, temp_data):
101+
"""Displays the temperature from the Stemma Soil Sensor.
102+
:param float temp_data: Temperature value.
103+
"""
104+
if not self._is_celsius:
105+
temp_data = (temp_data * 9 / 5) + 32 - 15
106+
print('Temperature: %0.0f°F'%temp_data)
107+
if temp_data >= 212:
108+
self.temp_data_label.color = 0xFD2EE
109+
elif temp_data <= 32:
110+
self.temp_data_label.color = 0xFF0000
111+
self.temp_data_label = '%0.0f°F'%temp_data
112+
temp_data = '%0.0f'%temp_data
113+
return int(temp_data)
114+
else:
115+
print('Temperature: %0.0f°C'%temp_data)
116+
if temp_data <= 0:
117+
self.temp_data_label.color = 0xFD2EE
118+
elif temp_data >= 100:
119+
self.temp_data_label.color = 0xFF0000
120+
self.temp_data_label.text = '%0.0f°C'%temp_data
121+
temp_data = '%0.0f'%temp_data
122+
return int(temp_data)
123+
124+
def set_icon(self, filename):
125+
"""Sets the background image to a bitmap file.
126+
127+
:param filename: The filename of the chosen icon
128+
"""
129+
print("Set icon to ", filename)
130+
if self._icon_group:
131+
self._icon_group.pop()
132+
133+
if not filename:
134+
return # we're done, no icon desired
135+
if self._icon_file:
136+
self._icon_file.close()
137+
self._icon_file = open(filename, "rb")
138+
icon = displayio.OnDiskBitmap(self._icon_file)
139+
try:
140+
self._icon_sprite = displayio.TileGrid(icon,
141+
pixel_shader=displayio.ColorConverter())
142+
except TypeError:
143+
self._icon_sprite = displayio.TileGrid(icon,
144+
pixel_shader=displayio.ColorConverter(),
145+
position=(0,0))
146+
147+
self._icon_group.append(self._icon_sprite)
148+
self.display.refresh_soon()
149+
self.display.wait_for_frame()
225 KB
Binary file not shown.

PyPortal_AWS_IOT_Planter/code.py

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

0 commit comments

Comments
 (0)