Skip to content

Commit 70ca9ff

Browse files
authored
Merge pull request adafruit#1016 from brentru/pyportal-pet-planter
Add Guide Resources for PyPortal Adafruit IO Planter Guide
2 parents 2d7d0a3 + 361dc86 commit 70ca9ff

File tree

8 files changed

+14978
-0
lines changed

8 files changed

+14978
-0
lines changed

pyportal_pet_planter/code.py

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
import time
2+
3+
import board
4+
import busio
5+
from digitalio import DigitalInOut
6+
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
7+
from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager
8+
import adafruit_imageload
9+
import displayio
10+
import neopixel
11+
from adafruit_bitmap_font import bitmap_font
12+
from adafruit_display_text.label import Label
13+
from adafruit_io.adafruit_io import IO_MQTT
14+
from adafruit_minimqtt import MQTT
15+
from adafruit_pyportal import PyPortal
16+
from adafruit_seesaw.seesaw import Seesaw
17+
from simpleio import map_range
18+
19+
#---| User Config |---------------
20+
21+
# How often to poll the soil sensor, in seconds
22+
DELAY_SENSOR = 30
23+
24+
# How often to send data to adafruit.io, in minutes
25+
DELAY_PUBLISH = 5
26+
27+
# Maximum soil moisture measurement
28+
SOIL_LEVEL_MAX = 500.0
29+
30+
# Minimum soil moisture measurement
31+
SOIL_LEVEL_MIN= 350.0
32+
33+
#---| End User Config |---------------
34+
35+
# Background image
36+
BACKGROUND = "/images/roots.bmp"
37+
# Icons for water level and temperature
38+
ICON_LEVEL = "/images/icon-wetness.bmp"
39+
ICON_TEMP = "/images/icon-temp.bmp"
40+
WATER_COLOR = 0x16549E
41+
42+
# Audio files
43+
wav_water_high = "/sounds/water-high.wav"
44+
wav_water_low = "/sounds/water-low.wav"
45+
46+
# the current working directory (where this file is)
47+
cwd = ("/"+__file__).rsplit('/', 1)[0]
48+
49+
# Get wifi details and more from a secrets.py file
50+
try:
51+
from secrets import secrets
52+
except ImportError:
53+
print("WiFi secrets are kept in secrets.py, please add them there!")
54+
raise
55+
56+
# Set up i2c bus
57+
i2c_bus = busio.I2C(board.SCL, board.SDA)
58+
59+
# Initialize soil sensor (s.s)
60+
ss = Seesaw(i2c_bus, addr=0x36)
61+
62+
# PyPortal ESP32 AirLift Pins
63+
esp32_cs = DigitalInOut(board.ESP_CS)
64+
esp32_ready = DigitalInOut(board.ESP_BUSY)
65+
esp32_reset = DigitalInOut(board.ESP_RESET)
66+
67+
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
68+
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
69+
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
70+
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
71+
72+
# Initialize PyPortal Display
73+
display = board.DISPLAY
74+
75+
WIDTH = board.DISPLAY.width
76+
HEIGHT = board.DISPLAY.height
77+
78+
# Initialize new PyPortal object
79+
pyportal = PyPortal(esp=esp,
80+
external_spi=spi)
81+
82+
# Set backlight level
83+
pyportal.set_backlight(0.5)
84+
85+
# Create a new DisplayIO group
86+
splash = displayio.Group(max_size=15)
87+
88+
# show splash group
89+
display.show(splash)
90+
91+
# Palette for water bitmap
92+
palette = displayio.Palette(2)
93+
palette[0] = 0x000000
94+
palette[1] = WATER_COLOR
95+
palette.make_transparent(0)
96+
97+
# Create water bitmap
98+
water_bmp = displayio.Bitmap(display.width, display.height, len(palette))
99+
water = displayio.TileGrid(water_bmp, pixel_shader=palette)
100+
splash.append(water)
101+
102+
print("drawing background..")
103+
# Load background image
104+
try:
105+
bg_bitmap, bg_palette = adafruit_imageload.load(BACKGROUND,
106+
bitmap=displayio.Bitmap,
107+
palette=displayio.Palette)
108+
# Or just use solid color
109+
except (OSError, TypeError):
110+
BACKGROUND = BACKGROUND if isinstance(BACKGROUND, int) else 0x000000
111+
bg_bitmap = displayio.Bitmap(display.width, display.height, 1)
112+
bg_palette = displayio.Palette(1)
113+
bg_palette[0] = BACKGROUND
114+
bg_palette.make_transparent(0)
115+
background = displayio.TileGrid(bg_bitmap, pixel_shader=bg_palette)
116+
117+
# Add background to display
118+
splash.append(background)
119+
120+
print('loading fonts...')
121+
# Fonts within /fonts/ folder
122+
font = cwd+"/fonts/GothamBlack-50.bdf"
123+
font_small = cwd+"/fonts/GothamBlack-25.bdf"
124+
125+
# pylint: disable=syntax-error
126+
data_glyphs = b'0123456789FC-* '
127+
font = bitmap_font.load_font(font)
128+
font.load_glyphs(data_glyphs)
129+
130+
font_small = bitmap_font.load_font(font_small)
131+
full_glyphs = b'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-,.: '
132+
font_small.load_glyphs(full_glyphs)
133+
134+
# Label to display Adafruit IO status
135+
label_status = Label(font_small, max_glyphs=20)
136+
label_status.x = 305
137+
label_status.y = 10
138+
splash.append(label_status)
139+
140+
# Create a label to display the temperature
141+
label_temp = Label(font, max_glyphs=4)
142+
label_temp.x = 35
143+
label_temp.y = 300
144+
splash.append(label_temp)
145+
146+
# Create a label to display the water level
147+
label_level = Label(font, max_glyphs=4)
148+
label_level.x = display.width - 130
149+
label_level.y = 300
150+
splash.append(label_level)
151+
152+
print('loading icons...')
153+
# Load temperature icon
154+
icon_tmp_bitmap, icon_palette = adafruit_imageload.load(ICON_TEMP,
155+
bitmap=displayio.Bitmap,
156+
palette=displayio.Palette)
157+
icon_palette.make_transparent(0)
158+
icon_tmp_bitmap = displayio.TileGrid(icon_tmp_bitmap,
159+
pixel_shader=icon_palette,
160+
x=0, y=280)
161+
splash.append(icon_tmp_bitmap)
162+
163+
# Load level icon
164+
icon_lvl_bitmap, icon_palette = adafruit_imageload.load(ICON_LEVEL,
165+
bitmap=displayio.Bitmap,
166+
palette=displayio.Palette)
167+
icon_palette.make_transparent(0)
168+
icon_lvl_bitmap = displayio.TileGrid(icon_lvl_bitmap,
169+
pixel_shader=icon_palette,
170+
x=315, y=280)
171+
splash.append(icon_lvl_bitmap)
172+
173+
# Connect to WiFi
174+
label_status.text = "Connecting..."
175+
while not esp.is_connected:
176+
try:
177+
wifi.connect()
178+
except RuntimeError as e:
179+
print("could not connect to AP, retrying: ",e)
180+
wifi.reset()
181+
continue
182+
print("Connected to WiFi!")
183+
184+
# Initialize a new MiniMQTT Client object
185+
mqtt_client = MQTT(
186+
socket=socket,
187+
broker="io.adafruit.com",
188+
username=secrets["aio_username"],
189+
password=secrets["aio_key"],
190+
network_manager=wifi
191+
)
192+
193+
# Adafruit IO Callback Methods
194+
# pylint: disable=unused-argument
195+
def connected(client):
196+
# Connected function will be called when the client is connected to Adafruit IO.
197+
print('Connected to Adafruit IO!')
198+
199+
def subscribe(client, userdata, topic, granted_qos):
200+
# This method is called when the client subscribes to a new feed.
201+
print('Subscribed to {0} with QOS level {1}'.format(topic, granted_qos))
202+
203+
# pylint: disable=unused-argument
204+
def disconnected(client):
205+
# Disconnected function will be called if the client disconnects
206+
# from the Adafruit IO MQTT broker.
207+
print("Disconnected from Adafruit IO!")
208+
209+
# Initialize an Adafruit IO MQTT Client
210+
io = IO_MQTT(mqtt_client)
211+
212+
# Connect the callback methods defined above to the Adafruit IO MQTT Client
213+
io.on_connect = connected
214+
io.on_subscribe = subscribe
215+
io.on_disconnect = disconnected
216+
217+
# Connect to Adafruit IO
218+
print("Connecting to Adafruit IO...")
219+
io.connect()
220+
label_status.text = " "
221+
print("Connected!")
222+
223+
fill_val = 0.0
224+
def fill_water(fill_percent):
225+
"""Fills the background water.
226+
:param float fill_percent: Percentage of the display to fill.
227+
228+
"""
229+
assert fill_percent <= 1.0, "Water fill value may not be > 100%"
230+
# pylint: disable=global-statement
231+
global fill_val
232+
233+
if fill_val > fill_percent:
234+
for _y in range(int((board.DISPLAY.height-1) - ((board.DISPLAY.height-1)*fill_val)),
235+
int((board.DISPLAY.height-1) - ((board.DISPLAY.height-1)*fill_percent))):
236+
for _x in range(1, board.DISPLAY.width-1):
237+
water_bmp[_x, _y] = 0
238+
else:
239+
for _y in range(board.DISPLAY.height-1,
240+
(board.DISPLAY.height-1) - ((board.DISPLAY.height-1)*fill_percent), -1):
241+
for _x in range(1, board.DISPLAY.width-1):
242+
water_bmp[_x, _y] = 1
243+
fill_val = fill_percent
244+
245+
def display_temperature(temp_val, is_celsius=False):
246+
"""Displays the temperature from the STEMMA soil sensor
247+
on the PyPortal Titano.
248+
:param float temp: Temperature value.
249+
:param bool is_celsius:
250+
251+
"""
252+
if not is_celsius:
253+
temp_val = (temp_val * 9 / 5) + 32 - 15
254+
print('Temperature: %0.0fF'%temp_val)
255+
label_temp.text = '%0.0fF'%temp_val
256+
return int(temp_val)
257+
else:
258+
print('Temperature: %0.0fC'%temp_val)
259+
label_temp.text = '%0.0fC'%temp_val
260+
return int(temp_val)
261+
262+
# initial reference time
263+
initial = time.monotonic()
264+
while True:
265+
# Explicitly pump the message loop
266+
# to keep the connection active
267+
try:
268+
io.loop()
269+
except (ValueError, RuntimeError) as e:
270+
print("Failed to get data, retrying...\n", e)
271+
wifi.reset()
272+
continue
273+
now = time.monotonic()
274+
275+
print("reading soil sensor...")
276+
# Read capactive
277+
moisture = ss.moisture_read()
278+
label_level.text = str(moisture)
279+
280+
# Convert into percentage for filling the screen
281+
moisture_percentage = map_range(float(moisture), SOIL_LEVEL_MIN, SOIL_LEVEL_MAX, 0.0, 1.0)
282+
283+
# Read temperature
284+
temp = ss.get_temp()
285+
temp = display_temperature(temp)
286+
287+
# fill display
288+
print("filling disp..")
289+
fill_water(moisture_percentage)
290+
print("disp filled..")
291+
292+
print("temp: " + str(temp) + " moisture: " + str(moisture))
293+
294+
# Play water level alarms
295+
if moisture <= SOIL_LEVEL_MIN:
296+
print("Playing low water level warning...")
297+
pyportal.play_file(wav_water_low)
298+
elif moisture >= SOIL_LEVEL_MAX:
299+
print("Playing high water level warning...")
300+
pyportal.play_file(wav_water_high)
301+
302+
303+
if now - initial > (DELAY_PUBLISH * 60):
304+
try:
305+
print("Publishing data to Adafruit IO...")
306+
label_status.text = "Sending to IO..."
307+
io.publish("moisture", moisture)
308+
io.publish("temperature", temp)
309+
print("Published")
310+
label_status.text = "Data Sent!"
311+
312+
# reset timer
313+
initial = now
314+
except (ValueError, RuntimeError) as e:
315+
label_status.text = "ERROR!"
316+
print("Failed to get data, retrying...\n", e)
317+
wifi.reset()
318+
time.sleep(DELAY_SENSOR)

0 commit comments

Comments
 (0)