import time
import os
import sys
from datetime import datetime
import board
import busio
from gpiozero import DigitalOutputDevice
from w1thermsensor import W1ThermSensor
import adafruit_ads1x15.ads1115 as ADS
from adafruit_ads1x15.analog_in import AnalogIn
from adafruit_ads1x15.ads1x15 import Pin
from luma.core.interface.serial import i2c
from luma.oled.device import ssd1306
from luma.core.render import canvas
# — CONFIGURATION (Editable via CLI) —
config = {
“pump_pin”: 17, # IRLZ44N MOSFET gate
“moisture_threshold”: 30.0, # Pump triggers below this %
“temp_ideal”: 24.0, # Ideal soil temperature °C
“sensors_enabled”: True
}
faces = {
“happy”: “( ^_^ )”,
“thirsty”: “( >_< )”,
“watering”: “( ~_~ )”,
“error”: “( x_x )”
}
# — HARDWARE STATE & SENSOR DATA —
pump = None
oled = None
ads = None
temp_sensor = None
last_water_time = datetime.min
# Global variables to hold the latest readings for the OLED
latest_temp = 0.0
latest_moisture = 0.0
latest_water_lvl = 0.0
def init_hardware():
global pump, oled, ads, temp_sensor
print(“n— Initializing Hardware —“)
# 1. Initialize Pump (GPIO 17)
if pump: pump.close()
try:
pump = DigitalOutputDevice(config[“pump_pin”])
print(f”[OK] Pump ready on GPIO {config[‘pump_pin’]}”)
except Exception as e:
print(f”[FAIL] Pump error: {e}”)
if not config[“sensors_enabled”]:
print(“Sensors are currently disabled in settings.”)
return
# 2. Initialize I2C Bus (OLED & ADS1115)
try:
i2c_bus = busio.I2C(board.SCL, board.SDA)
ads = ADS.ADS1115(i2c_bus)
print(“[OK] ADS1115 Moisture ADC ready”)
serial = i2c(port=1, address=0x3C)
oled = ssd1306(serial)
print(“[OK] SSD1306 OLED ready”)
except Exception as e:
print(f”[FAIL] I2C Devices: {e}”)
# 3. Initialize DS18B20 Temp Sensor (GPIO 4)
try:
temp_sensor = W1ThermSensor()
print(“[OK] DS18B20 Temperature Sensor ready”)
except Exception as e:
print(f”[FAIL] DS18B20: {e}”)
def display_face(emotion_key):
“””Draws the pet face and the current sensor readings on the OLED”””
if oled is None: return
try:
with canvas(oled) as draw:
# Draw Face at the top center
draw.text((45, 2), faces.get(emotion_key, faces[“error”]), fill=”white”)
# Draw Sensor Data below the face
draw.text((5, 20), f”Temp : {latest_temp:.1f} C”, fill=”white”)
draw.text((5, 33), f”Soil : {latest_moisture:.1f} %”, fill=”white”)
draw.text((5, 46), f”Water : {latest_water_lvl:.1f} %”, fill=”white”)
except Exception as e:
pass # Silently fail if OLED errors during continuous loop
def safe_water_plant():
global last_water_time
now = datetime.now()
if (now – last_water_time).total_seconds() < 3600:
print(“n>> ERROR: Cooldown active! You must wait 1 hour between waterings.”)
return
print(“n>> Activating pump for 5 seconds…”)
display_face(“watering”)
try:
pump.on()
time.sleep(5)
finally:
pump.off()
last_water_time = datetime.now()
print(“>> Pump deactivated.”)
display_face(“happy”)
def live_sensor_dashboard():
“””Continuously reads and displays sensors every 1 second until Ctrl+C is pressed”””
global latest_temp, latest_moisture, latest_water_lvl
if not config[“sensors_enabled”]:
print(“n>> Sensors are currently disabled. Please enable them first.”)
return
print(“nStarting live dashboard… Press Ctrl+C to exit and return to menu.”)
time.sleep(1.5) # Give the user a moment to read the instruction
try:
while True:
# Clear the terminal screen for a clean “dashboard” look
os.system(‘clear’ if os.name == ‘posix’ else ‘cls’)
print(“=== AUTO POT LIVE DASHBOARD ===”)
print(“Press Ctrl+C to stop and return to main menu.n”)
system_ok = True
# 1. Read Soil Moisture (Pin A0)
try:
if ads:
chan_moisture = AnalogIn(ads, Pin.A0)
latest_moisture = max(0, min(100, (3.3 – chan_moisture.voltage) / 1.8 * 100))
print(f”[{datetime.now().strftime(‘%H:%M:%S’)}] Soil Moisture : {latest_moisture:.1f}%”)
else:
raise Exception(“ADC not initialized”)
except Exception as e:
print(f”[FAIL] Soil Moisture : {e}”)
system_ok = False
# 2. Read Reservoir Water Level (Pin A1)
try:
if ads:
chan_water = AnalogIn(ads, Pin.A1)
latest_water_lvl = max(0, min(100, (chan_water.voltage / 3.3) * 100))
print(f”[{datetime.now().strftime(‘%H:%M:%S’)}] Water Level : {latest_water_lvl:.1f}%”)
else:
raise Exception(“ADC not initialized”)
except Exception as e:
print(f”[FAIL] Water Level : {e}”)
system_ok = False
# 3. Read Soil Temperature (DS18B20)
try:
if temp_sensor:
latest_temp = temp_sensor.get_temperature()
print(f”[{datetime.now().strftime(‘%H:%M:%S’)}] Soil Temp : {latest_temp:.1f}°C”)
else:
raise Exception(“DS18B20 not initialized”)
except Exception as e:
print(f”[FAIL] Soil Temp : {e}”)
system_ok = False
# 4. Determine Status & Update OLED
if system_ok:
if latest_moisture < config[“moisture_threshold”]:
print(“n>> Status: Plant is thirsty! (Below threshold)”)
display_face(“thirsty”)
else:
print(“n>> Status: All systems running normally.”)
display_face(“happy”)
else:
print(“n>> Status: Errors detected. Check wiring.”)
display_face(“error”)
time.sleep(1) # Wait exactly 1 second before looping again
except KeyboardInterrupt:
# This catches the Ctrl+C command gracefully
print(“nnExiting Live Dashboard…”)
time.sleep(0.5)
# Clear screen again so the menu prints cleanly
os.system(‘clear’ if os.name == ‘posix’ else ‘cls’)
def cli_menu():
# Clear screen on startup
os.system(‘clear’ if os.name == ‘posix’ else ‘cls’)
init_hardware()
display_face(“happy”)
while True:
print(“n=== AUTO POT CLI MENU ===”)
print(“1. Start Live Sensor Dashboard (Updates every 1s)”)
print(“2. Toggle Sensors On/Off”)
print(“3. Edit Pump GPIO Pin”)
print(“4. Edit Ideal Moisture/Temperature Thresholds”)
print(“5. Manual Water (Tests 5s pump limit & cooldown)”)
print(“6. Modify Pet Faces”)
print(“0. Exit”)
choice = input(“nSelect an option: “)
if choice == ‘1’:
live_sensor_dashboard()
elif choice == ‘2’:
config[“sensors_enabled”] = not config[“sensors_enabled”]
print(f”>> Sensors Enabled: {config[‘sensors_enabled’]}”)
init_hardware()
elif choice == ‘3’:
new_pin = input(f”Enter new pump GPIO pin (current: {config[‘pump_pin’]}): “)
if new_pin.isdigit():
config[“pump_pin”] = int(new_pin)
init_hardware()
elif choice == ‘4’:
m = input(f”Enter moisture threshold % (current: {config[‘moisture_threshold’]}): “)
t = input(f”Enter ideal temp °C (current: {config[‘temp_ideal’]}): “)
if m: config[“moisture_threshold”] = float(m)
if t: config[“temp_ideal”] = float(t)
print(“>> Thresholds updated.”)
elif choice == ‘5’:
safe_water_plant()
elif choice == ‘6’:
print(f”Current Happy Face: {faces[‘happy’]}”)
new_face = input(“Enter new Happy Face text: “)
if new_face: faces[“happy”] = new_face
display_face(“happy”)
elif choice == ‘0’:
if pump: pump.close()
print(“Exiting…”)
if oled:
with canvas(oled) as draw: pass
break
else:
print(“Invalid choice. Try again.”)
if __name__ == “__main__”:
cli_menu()