secret.py
# This file is where you keep secret settings, passwords, and tokens!
# If you put them in the code you risk committing that info or sharing it
secrets = {
'ssid' : 'Your SSID name',
'password' : 'Your SSID password',
'timezone' : "Australia/Sydney", # http://worldtimeapi.org/timezones
'github_token' : 'fawfj23rakjnfawiefa',
'hackaday_token' : 'h4xx0rs3kret',
}
code.py
# This is the Python code for the pot that I modified using the Mu Editor to # use with the PyPortal Titano and STEMMA soil sensor.
import time
import board
import busio
from digitalio import DigitalInOut
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager
import adafruit_imageload
import displayio
import neopixel
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text.label import Label
from adafruit_io.adafruit_io import IO_MQTT
import adafruit_minimqtt.adafruit_minimqtt as MQTT
from adafruit_pyportal import PyPortal
from adafruit_seesaw.seesaw import Seesaw
from simpleio import map_range
# ---| User Config |---------------
# How often to read the soil sensor (in second)
DELAY_SENSOR = 10
# How often to send data to adafruit.io (in minute)
DELAY_PUBLISH = 1
# Maximum soil moisture measurement
SOIL_LEVEL_MAX = 590.0 # 75% between 330(min) - 675(max)
# Minimum soil moisture measurement
SOIL_LEVEL_MIN = 420.0 # 25% between 330(min) - 675(max)
# ---| End User Config |---------------
# Background image
BACKGROUND = "/images/roots.bmp"
# reduced size from 150KB originaly to 13KB and make it BW (no colour)
# Icons for water level and temperature
ICON_LEVEL = "/images/icon-wetness.bmp"
ICON_TEMP = "/images/icon-temp.bmp"
WATER_COLOR = 0x16549E
# Audio files
wav_water_high = "/sounds/water-high.wav"
wav_water_low = "/sounds/water-low.wav"
# the current working directory (where this file is)
cwd = ("/"+__file__).rsplit('/', 1)[0]
# Get wifi details and more from a secrets.py file
try:
from secrets import secrets
except ImportError:
print("Please add WiFi details in secrets.py")
raise
# Set up i2c bus to read the STEMMA soil sensor
i2c_bus = busio.I2C(board.SCL, board.SDA)
# Initialize soil sensor (s.s)
ss = Seesaw(i2c_bus, addr=0x36)
# PyPortal ESP32 AirLift Pins
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)
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.1)
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
# Initialize PyPortal Display
display = board.DISPLAY
WIDTH = board.DISPLAY.width
HEIGHT = board.DISPLAY.height
# Initialize new PyPortal object
pyportal = PyPortal(esp=esp, external_spi=spi)
# Set backlight level
pyportal.set_backlight(1)
# Create a new DisplayIO group
splash = displayio.Group(max_size=10)
# show splash group
display.show(splash)
# Palette for water bitmap
palette = displayio.Palette(2)
palette[0] = 0x000000
palette[1] = WATER_COLOR
palette.make_transparent(0)
# Create water bitmap
water_bmp = displayio.Bitmap(display.width, display.height, len(palette))
water = displayio.TileGrid(water_bmp, pixel_shader=palette)
splash.append(water)
print("drawing background..")
# Load background image
try:
bg_bitmap, bg_palette = adafruit_imageload.load(BACKGROUND,
bitmap=displayio.Bitmap,
palette=displayio.Palette)
# Or just use solid color
except (OSError, TypeError):
BACKGROUND = BACKGROUND if isinstance(BACKGROUND, int) else 0x000000
bg_bitmap = displayio.Bitmap(display.width, display.height, 1)
bg_palette = displayio.Palette(1)
bg_palette[0] = BACKGROUND
bg_palette.make_transparent(0)
background = displayio.TileGrid(bg_bitmap, pixel_shader=bg_palette, x=100, y = 0)
# Add background to display
splash.append(background)
print('loading fonts...')
# Fonts within /fonts/ folder
font = cwd+"/fonts/GothamBlack-50.bdf"
font_small = cwd+"/fonts/GothamBlack-25.bdf"
# pylint: disable=syntax-error
data_glyphs = b'0123456789FC-* '
font = bitmap_font.load_font(font)
font.load_glyphs(data_glyphs)
font_small = bitmap_font.load_font(font_small)
full_glyphs = b'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-,.: '
font_small.load_glyphs(data_glyphs)
# Label to display Adafruit IO status
label_status = Label(font_small, max_glyphs=20)
label_status.x = 305
label_status.y = 20
splash.append(label_status)
# Create a label to display the temperature
label_temp = Label(font, max_glyphs=4)
label_temp.x = 35
label_temp.y = 300
splash.append(label_temp)
# Create a label to display the water level
label_level = Label(font, max_glyphs=4)
label_level.x = display.width - 130
label_level.y = 300
splash.append(label_level)
print('loading icons...')
# Load temperature icon
icon_tmp_bitmap, icon_palette = adafruit_imageload.load(ICON_TEMP,
bitmap=displayio.Bitmap,
palette=displayio.Palette)
icon_palette.make_transparent(0)
icon_tmp_bitmap = displayio.TileGrid(icon_tmp_bitmap,
pixel_shader=icon_palette,
x=0, y=280)
splash.append(icon_tmp_bitmap)
# Load level icon
icon_lvl_bitmap, icon_palette = adafruit_imageload.load(ICON_LEVEL,
bitmap=displayio.Bitmap,
palette=displayio.Palette)
icon_palette.make_transparent(0)
icon_lvl_bitmap = displayio.TileGrid(icon_lvl_bitmap,
pixel_shader=icon_palette,
x=315, y=280)
splash.append(icon_lvl_bitmap)
# Connect to WiFi
label_status.text = "Connecting..."
while not esp.is_connected:
try:
wifi.connect()
except RuntimeError as e:
print("could not connect to AP, retrying: ", e)
wifi.reset()
continue
print("Connected to WiFi!")
# Initialize MQTT interface with the esp interface
MQTT.set_socket(socket, esp)
# Initialize a new MQTT Client object
mqtt_client = MQTT.MQTT(broker="io.adafruit.com",
username=secrets["aio_user"],
password=secrets["aio_key"])
# Adafruit IO Callback Methods
# pylint: disable=unused-argument
def connected(client):
# Connected function will be called when the client is connected to Adafruit IO.
print('Connected to Adafruit IO!')
def subscribe(client, userdata, topic, granted_qos):
# This method is called when the client subscribes to a new feed.
print('Subscribed to {0} with QOS level {1}'.format(topic, granted_qos))
# pylint: disable=unused-argument
def disconnected(client):
# Disconnected function will be called if the client disconnects
# from the Adafruit IO MQTT broker.
print("Disconnected from Adafruit IO!")
# Initialize an Adafruit IO MQTT Client
io = IO_MQTT(mqtt_client)
# Connect the callback methods defined above to the Adafruit IO MQTT Client
io.on_connect = connected
io.on_subscribe = subscribe
io.on_disconnect = disconnected
# Connect to Adafruit IO
print("Connecting to Adafruit IO...")
io.connect()
label_status.text = " "
# Fills the background water
fill_val = 0.0
def fill_water(fill_percent):
assert fill_percent <= 1.0, "Water fill value may not be > 100%"
# pylint: disable=global-statement
global fill_val
if fill_val > fill_percent:
for _y in range(int((board.DISPLAY.height-1) - ((board.DISPLAY.height-1)*fill_val)),
int((board.DISPLAY.height-1) - ((board.DISPLAY.height-1)*fill_percent))):
for _x in range(1, board.DISPLAY.width-1):
water_bmp[_x, _y] = 0
else:
for _y in range(board.DISPLAY.height-1,
(board.DISPLAY.height-1) - ((board.DISPLAY.height-1)*fill_percent), -1):
for _x in range(1, board.DISPLAY.width-1):
water_bmp[_x, _y] = 1
fill_val = fill_percent # Percentage of the display to fill
def display_temperature(temp_val, is_celsius=True):
if is_celsius:
print('Temperature: %0.0fC'%temp_val)
label_temp.text = '%0.0fC'%temp_val
return int(temp_val)
else:
temp_val = (temp_val * 9 / 5) + 32 - 15
print('Temperature: %0.0fF'%temp_val)
label_temp.text = '%0.0fF'%temp_val
return int(temp_val)
# initial reference time
initial = time.monotonic()
while True:
# Explicitly pump the message loop
# to keep the connection active
try:
io.loop()
except (ValueError, RuntimeError) as e:
print("Failed to get data, retrying...\n", e)
wifi.reset()
continue
now = time.monotonic()
print("Reading soil sensor...")
# Read capacitive soil sensor (ss)
moisture = ss.moisture_read()
label_level.text = str(moisture)
# Convert into percentage for filling the screen
moisture_percentage = map_range(float(moisture), SOIL_LEVEL_MIN, SOIL_LEVEL_MAX, 0.0, 1.0)
fill_water(moisture_percentage)
# Read temperature
temperature = ss.get_temp()
temperature = display_temperature(temperature)
print("Moisture: " + str(moisture))
# Play water level alarms
if moisture <= SOIL_LEVEL_MIN:
print("Playing low water level warning...")
pyportal.play_file(wav_water_low)
elif moisture >= SOIL_LEVEL_MAX:
print("Playing high water level warning...")
pyportal.play_file(wav_water_high)
if now - initial > (DELAY_PUBLISH * 60):
try:
print("Publishing data to Adafruit IO...")
label_status.text = "Sending to IO..."
io.publish("moisture", moisture)
io.publish("temperature", temperature)
print("Data Published")
label_status.text = "Data Sent!"
# reset timer
initial = now
except (ValueError, RuntimeError) as e:
label_status.text = "ERROR!"
print("Failed to get data, retrying...\n", e)
wifi.reset()
time.sleep(DELAY_SENSOR)