Skip to content

Commit 41d37ba

Browse files
brentrubrentru
authored andcommitted
add pyportal planter code
1 parent a18c149 commit 41d37ba

2 files changed

Lines changed: 337 additions & 0 deletions

File tree

PyPortal_GCP_IOT_Planter/code.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
"""
2+
PyPortal Google Cloud IoT Core Planter
3+
=======================================================
4+
Water your plant remotely and log its vitals to Google
5+
Cloud IoT Core with your PyPortal.
6+
7+
Author: Brent Rubell for Adafruit Industries, 2019
8+
"""
9+
import time
10+
import json
11+
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
12+
import board
13+
import busio
14+
import digitalio
15+
import gcp_gfx_helper
16+
import neopixel
17+
from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager
18+
from adafruit_gc_iot_core import MQTT_API, Cloud_Core
19+
from adafruit_minimqtt import MQTT
20+
from adafruit_seesaw.seesaw import Seesaw
21+
from digitalio import DigitalInOut
22+
23+
# Delay before reading the sensors, in minutes
24+
# TODO: switch to 10min
25+
SENSOR_DELAY = 1
26+
27+
# Get wifi details and more from a secrets.py file
28+
try:
29+
from secrets import secrets
30+
except ImportError:
31+
print("WiFi secrets are kept in secrets.py, please add them there!")
32+
raise
33+
34+
# PyPortal ESP32 Setup
35+
esp32_cs = DigitalInOut(board.ESP_CS)
36+
esp32_ready = DigitalInOut(board.ESP_BUSY)
37+
esp32_reset = DigitalInOut(board.ESP_RESET)
38+
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
39+
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
40+
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
41+
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(
42+
esp, secrets, status_light)
43+
44+
# Soil Sensor Setup
45+
i2c_bus = busio.I2C(board.SCL, board.SDA)
46+
ss = Seesaw(i2c_bus, addr=0x36)
47+
48+
# Water Pump Setup
49+
water_pump = digitalio.DigitalInOut(board.D3)
50+
water_pump.direction = digitalio.Direction.OUTPUT
51+
52+
# Initialize the graphics helper
53+
print("Loading GCP Graphics...")
54+
gfx = gcp_gfx_helper.Google_GFX()
55+
print("Graphics loaded!")
56+
57+
# Connect to WiFi
58+
print("Connecting to WiFi...")
59+
wifi.connect()
60+
print("Connected!")
61+
62+
# Define callback methods which are called when events occur
63+
# pylint: disable=unused-argument, redefined-outer-name
64+
65+
66+
def connect(client, userdata, flags, rc):
67+
# This function will be called when the client is connected
68+
# successfully to the broker.
69+
print('Connected to Google Cloud IoT!')
70+
print('Flags: {0}\n RC: {1}'.format(flags, rc))
71+
# Subscribes to commands/# topic
72+
google_mqtt.subscribe_to_all_commands()
73+
return
74+
75+
76+
def disconnect(client, userdata, rc):
77+
# This method is called when the client disconnects
78+
# from the broker.
79+
print('Disconnected from Google Cloud IoT!')
80+
return
81+
82+
83+
def subscribe(client, userdata, topic, granted_qos):
84+
# This method is called when the client subscribes to a new topic.
85+
print('Subscribed to {0} with QOS level {1}'.format(topic, granted_qos))
86+
return
87+
88+
89+
def unsubscribe(client, userdata, topic, pid):
90+
# This method is called when the client unsubscribes from a topic.
91+
print('Unsubscribed from {0} with PID {1}'.format(topic, pid))
92+
93+
94+
def publish(client, userdata, topic, pid):
95+
# This method is called when the client publishes data to a topic.
96+
print('Published to {0} with PID {1}'.format(topic, pid))
97+
98+
99+
def message(client, topic, msg):
100+
# This method is called when the client receives data from a topic.
101+
try:
102+
# Check if command is about the water pump
103+
msg_dict = json.loads(msg)
104+
if msg_dict['pump_time']:
105+
handle_pump(msg_dict)
106+
except KeyError:
107+
print("Message from {}: {}".format(topic, msg))
108+
109+
def handle_pump(command):
110+
"""Handles command about the planter's
111+
watering pump from Google Core IoT.
112+
Expected command format: {"pump": 1, "pump_time":3}
113+
:param json command: Message from device/commands#
114+
"""
115+
print("Pump command received: {} for {} seconds".format(
116+
command['pump'], command['pump_time']))
117+
pump_time = command['pump_time']
118+
pump_status = command['pump']
119+
if not pump_status:
120+
water_pump.value = False
121+
else:
122+
initial = time.monotonic()
123+
while True:
124+
if now - initial > pump_time:
125+
print("Turning pump off...")
126+
water_pump.value = False
127+
return
128+
water_pump.value = True
129+
print("Done watering!")
130+
131+
# Initialize Google Cloud IoT Core interface
132+
google_iot = Cloud_Core(esp, secrets)
133+
134+
# Optional JSON-Web-Token (JWT) Generation
135+
# print("Generating JWT...")
136+
# jwt = google_iot.generate_jwt()
137+
# print("Your JWT is: ", jwt)
138+
139+
# Set up a new MiniMQTT Client
140+
client = MQTT(socket,
141+
broker=google_iot.broker,
142+
username=google_iot.username,
143+
password=secrets['jwt'],
144+
client_id=google_iot.cid,
145+
network_manager=wifi)
146+
147+
# Initialize Google MQTT API Client
148+
google_mqtt = MQTT_API(client)
149+
150+
# Connect callback handlers to Google MQTT Client
151+
google_mqtt.on_connect = connect
152+
google_mqtt.on_disconnect = disconnect
153+
google_mqtt.on_subscribe = subscribe
154+
google_mqtt.on_unsubscribe = unsubscribe
155+
google_mqtt.on_publish = publish
156+
google_mqtt.on_message = message
157+
158+
print('Attempting to connect to %s' % client.broker)
159+
google_mqtt.connect()
160+
161+
# Time in seconds since power on
162+
initial = time.monotonic()
163+
164+
while True:
165+
try:
166+
gfx.show_gcp_status('Listening for new messages...')
167+
now = time.monotonic()
168+
if now - initial > (SENSOR_DELAY * 60):
169+
# read moisture level
170+
moisture_level = ss.moisture_read()
171+
# read temperature
172+
temperature = ss.get_temp()
173+
# display soil sensor values on pyportal
174+
gfx.show_water_level(moisture_level)
175+
temperature = gfx.show_temp(temperature)
176+
print('Sending data to GCP IoT Core')
177+
gfx.show_gcp_status('Sending data...')
178+
gfx.show_gcp_status('Data sent!')
179+
print('Data sent!')
180+
# Reset timer
181+
initial = now
182+
google_mqtt.loop()
183+
except (ValueError, RuntimeError) as e:
184+
print("Failed to get data, retrying", e)
185+
wifi.reset()
186+
continue
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"""
2+
GFX Helper for PyPortal Azure IoT Plant Monitor
3+
"""
4+
import board
5+
import displayio
6+
import terminalio
7+
from adafruit_bitmap_font import bitmap_font
8+
from adafruit_display_text.label import Label
9+
10+
# the current working directory (where this file is)
11+
cwd = ("/"+__file__).rsplit('/', 1)[0]
12+
13+
# GFX Font
14+
font = terminalio.FONT
15+
16+
class Google_GFX(displayio.Group):
17+
def __init__(self, is_celsius=False):
18+
"""Creates an Google_GFX object for displaying plant
19+
and Google Cloud IoT Core status.
20+
:param bool is_celsius: Temperature displayed in Celsius.
21+
"""
22+
# root displayio group
23+
root_group = displayio.Group(max_size=23)
24+
self.display = board.DISPLAY
25+
self.display.show(root_group)
26+
super().__init__(max_size=15)
27+
28+
# temperature display option
29+
self._is_celsius = is_celsius
30+
31+
# create background icon group
32+
self._icon_group = displayio.Group(max_size=3)
33+
self.append(self._icon_group)
34+
self.display.show(self._icon_group)
35+
# create text object group
36+
self._text_group = displayio.Group(max_size=40)
37+
self.append(self._text_group)
38+
39+
print("Displaying splash screen")
40+
self._icon_sprite = None
41+
self._icon_file = None
42+
self._cwd = cwd
43+
self.set_icon(self._cwd+"/images/gcp_splash.bmp")
44+
45+
print('Setting up labels...')
46+
header_group = displayio.Group(scale=3)
47+
header_label = Label(font, text="Google Cloud IoT\n Planter")
48+
header_label.x = (self.display.width // 2) // 22
49+
header_label.y = 15
50+
header_group.append(header_label)
51+
self._text_group.append(header_group)
52+
53+
# Temperature Display
54+
temp_group = displayio.Group(scale=2, max_size=3)
55+
temp_label = Label(font, text="Temperature: ")
56+
temp_label.x = (self.display.width//2) // 11
57+
temp_label.y = 55
58+
temp_group.append(temp_label)
59+
60+
self.temp_data_label = Label(font, text="75 F")
61+
self.temp_data_label.x = (self.display.width//3)
62+
self.temp_data_label.y = 55
63+
temp_group.append(self.temp_data_label)
64+
self._text_group.append(temp_group)
65+
66+
# Water Level
67+
water_group = displayio.Group(scale=2, max_size=2)
68+
self.water_level = Label(font, text="Water Level: ")
69+
self.water_level.x = (self.display.width//2) // 11
70+
self.water_level.y = 75
71+
water_group.append(self.water_level)
72+
73+
self.water_lvl_label = Label(font, text="350")
74+
self.water_lvl_label.x = (self.display.width//3)
75+
self.water_lvl_label.y = 75
76+
temp_group.append(self.water_lvl_label)
77+
self._text_group.append(water_group)
78+
79+
# GCP Status
80+
status_group = displayio.Group()
81+
self.gcp_status_label = Label(font, text="Connecting to GCP IoT Core...")
82+
self.gcp_status_label.x = self.display.width//4
83+
self.gcp_status_label.y = 200
84+
status_group.append(self.gcp_status_label)
85+
self._text_group.append(status_group)
86+
87+
self.display.show(self._text_group)
88+
89+
def show_gcp_status(self, status_text):
90+
"""Displays the system status on the PyPortal
91+
:param str status_text: Description of current GCP IoT status
92+
"""
93+
self.gcp_status_label.text = status_text
94+
95+
def show_water_level(self, water_data):
96+
"""Displays the water level from the Stemma Soil Sensor.
97+
:param int water_data: water value
98+
"""
99+
print('Water Level: ', water_data)
100+
self.water_lvl_label.text = str(water_data)
101+
102+
def show_temp(self, temp_data):
103+
"""Displays the temperature from the Stemma Soil Sensor.
104+
:param float temp_data: Temperature value.
105+
"""
106+
if not self._is_celsius:
107+
temp_data = (temp_data * 9 / 5) + 32 - 15
108+
print('Temperature: %0.0f°F'%temp_data)
109+
if temp_data >= 212:
110+
self.temp_data_label.color = 0xFD2EE
111+
elif temp_data <= 32:
112+
self.temp_data_label.color = 0xFF0000
113+
self.temp_data_label = '%0.0f°F'%temp_data
114+
temp_data = '%0.0f'%temp_data
115+
return int(temp_data)
116+
else:
117+
print('Temperature: %0.0f°C'%temp_data)
118+
if temp_data <= 0:
119+
self.temp_data_label.color = 0xFD2EE
120+
elif temp_data >= 100:
121+
self.temp_data_label.color = 0xFF0000
122+
self.temp_data_label.text = '%0.0f°C'%temp_data
123+
temp_data = '%0.0f'%temp_data
124+
return int(temp_data)
125+
126+
def set_icon(self, filename):
127+
"""Sets the background image to a bitmap file.
128+
129+
:param filename: The filename of the chosen icon
130+
"""
131+
print("Set icon to ", filename)
132+
if self._icon_group:
133+
self._icon_group.pop()
134+
135+
if not filename:
136+
return # we're done, no icon desired
137+
if self._icon_file:
138+
self._icon_file.close()
139+
self._icon_file = open(filename, "rb")
140+
icon = displayio.OnDiskBitmap(self._icon_file)
141+
try:
142+
self._icon_sprite = displayio.TileGrid(icon,
143+
pixel_shader=displayio.ColorConverter())
144+
except TypeError:
145+
self._icon_sprite = displayio.TileGrid(icon,
146+
pixel_shader=displayio.ColorConverter(),
147+
position=(0,0))
148+
149+
self._icon_group.append(self._icon_sprite)
150+
self.display.refresh_soon()
151+
self.display.wait_for_frame()

0 commit comments

Comments
 (0)