from machine import Pin, ADC, PWM, I2C
from time import sleep
import dht
import math
import network
from umqtt.simple import MQTTClient
def connect_wifi():
ssid = "Wokwi-GUEST"
password = ""
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
while not wlan.isconnected():
print("Connecting to Wi-Fi...")
sleep(1)
print("✅ Wi-Fi connected! IP:", wlan.ifconfig())
#-- initialisation t3 mqtt + connecting to mqtt --
MQTT_BROKER = "broker.hivemq.com"
MQTT_CLIENT_ID = "FishFeeder"
mqtt_client = MQTTClient(MQTT_CLIENT_ID, MQTT_BROKER)
def connect_mqtt():
mqtt_client.connect()
print("✅ Connected to MQTT broker!")
# Simulated RTC class
#It simulates a Real Time Clock (RTC) like the DS3231 using fake
#time updates (because the simulator doesn't support real hardware RTC).
#Because in Wokwi with MicroPython, we don’t have real RTC hardware
#like DS3231.
#So we simulate time manually to test our code.
class DS3231:
def __init__(self, i2c):
self.hour = 7
self.minute = 58
def get_time(self):
self.minute += 1
if self.minute >= 60:
self.minute = 0
self.hour += 1
if self.hour >= 24:
self.hour = 0
return (2025, 1, 1, 0, self.hour, self.minute, 0, 0)
# --- Constants ---
# Assigns the ESP32 GPIO pins
DHT_PIN = 15
SERVO_PIN = 5
BUTTON_PIN = 12
LED_PIN = 13
PH_PIN = 34
DO_PIN = 35
EC_PIN = 32
LEVEL_PIN = 33
LIGHT_PIN = 36
# --- Setup hardware ---
# initialisation of devices
##-- Creates sensor objects and sets them up
dht_sensor = dht.DHT22(Pin(DHT_PIN))
ph_sensor = ADC(Pin(PH_PIN))
do_sensor = ADC(Pin(DO_PIN))
ec_sensor = ADC(Pin(EC_PIN))
level_sensor = ADC(Pin(LEVEL_PIN))
light_sensor = ADC(Pin(LIGHT_PIN))
servo_pwm = PWM(Pin(SERVO_PIN), freq=50)
button = Pin(BUTTON_PIN, Pin.IN, Pin.PULL_UP)
led = Pin(LED_PIN, Pin.OUT)
#Sets up I2C communication on GPIO22 (SCL) and GPIO21 (SDA).
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
#Connects to the DS3231 real-time clock module using I2C.
rtc = DS3231(i2c)
# Analog resolution
#.atten(ADC.ATTN_11DB) → Allows full 3.3V range for accurate readings.
for s in [ph_sensor, do_sensor, ec_sensor, level_sensor, light_sensor]:
s.atten(ADC.ATTN_11DB)
# --- Globals ---
fish_type = "tilapia"
fish_age = "fry"
feed_times = []
has_fed_this_hour = False
last_checked_hour = -1
manual_feed = False
total_fish_weight = 0.0
class FishThresholds:
def __init__(self, temp_min, temp_max, ph_min, ph_max, do_min,
ec_min, ec_max, light_min, light_max, level_min, frequency):
self.temp_min = temp_min
self.temp_max = temp_max
self.ph_min = ph_min
self.ph_max = ph_max
self.do_min = do_min
self.ec_min = ec_min
self.ec_max = ec_max
self.light_min = light_min
self.light_max = light_max
self.level_min = level_min
self.frequency = frequency
fish = None
# --- Sensor Readings ---
def read_ph():
voltage = ph_sensor.read() * 3.3 / 4095
return voltage * (14.0 / 3.3)
def read_do():
voltage = do_sensor.read() * 3.3 / 4095
return voltage * (10.0 / 3.3)
def read_ec():
voltage = ec_sensor.read() * 3.3 / 4095
return voltage * (5.0 / 3.3)
def read_level():
return level_sensor.read() * 30.0 / 4095
def read_light():
return light_sensor.read()
#self.temp_min = temp_min
# self.temp_max = temp_max
# self.ph_min = ph_min
# self.ph_max = ph_max
# self.do_min = do_min
# self.ec_min = ec_min
# self.ec_max = ec_max
# self.light_min = light_min
#self.light_max = light_max
#self.level_min = level_min
#self.frequency = frequency
# --- Configuration ---
def set_fish_thresholds(fish_type, age):
global fish
if fish_type == "tilapia" and age == "fry":
fish = FishThresholds(23, 32, 6.5, 9.0, 5.0, 1.0, 2.5, 200, 4000, 20, 5)
elif fish_type == "tilapia" and age == "fingerlings":
fish = FishThresholds(24, 30, 6.0, 8.5, 4.5, 1.5, 3.0, 150, 2600, 18, 4)
elif fish_type == "tilapia" and age == "juveniles":
fish = FishThresholds(24, 30, 6.0, 8.5, 4.5, 1.5, 3.0, 150, 2600, 18, 3)
elif fish_type == "tilapia" and age == "adult":
fish = FishThresholds(24, 30, 6.0, 8.5, 4.5, 1.5, 3.0, 150, 2600, 18, 2)
else:
fish = FishThresholds(25, 32, 6.5, 9.0, 5.0, 1.0, 2.5, 200, 1000, 20, 3)
def generate_feed_times():
global feed_times
feed_times = [(8, 0), (12, 0), (16, 0), (18, 0), (20, 0)][:fish.frequency]
def get_ration_percent(temp):
if temp < 22:
return 0.01
elif temp < 28:
return 0.025
elif temp <= 32:
return 0.04
else:
return 0
def feed_fish(amount):
print(f"Dispensing {amount:.2f} grams of food...")
servo_pwm.duty(int((90 / 180) * 1023))
sleep(0.5 + amount / 10)
servo_pwm.duty(0)
led.off()
print("Feeding complete.")
# --- Initialization ---
def init():
global fish_type, fish_age, total_fish_weight
print("Starting FishFlow2025 System...")
# Initialize DHT sensor
dht_sensor.measure()
# Initialize servo
servo_pwm.duty(40) # Move to initial position (0 degrees)
# User input for fish type nd age
fish_type = input("Type fish name (tilapia, catfish, carp): ").strip().lower()
fish_age = input("Enter fish stage (fry, fingerlings, juveniles, adult): ").strip().lower()
print("Selected fish:", fish_type)
print("Stage of selected fish:", fish_age)
# Calculate fish weight based on dataset-driven values
count = 50
avg_weight_dict = {
"fry": 27.6,
"fingerlings": 105.0,
"juveniles": 199.0,
"adult": 411.0
}
avg_weight = avg_weight_dict.get(fish_age, 100.0)
total_fish_weight = count * avg_weight
print("Average weight:", avg_weight, "g")
print("Total biomass:", total_fish_weight, "g")
# Set thresholds and feed times
set_fish_thresholds(fish_type, fish_age)
generate_feed_times()
# RTC check
try:
now = rtc.get_time()
except Exception as e:
print("Couldn't find RTC. Check wiring!")
while True:
pass
print("Setup complete.\n-----------------------------")
# --- Main Loop ---
def main_loop():
global has_fed_this_hour, last_checked_hour, manual_feed
while True:
now = rtc.get_time()
hour, minute = now[4], now[5]
print(f"Time: {hour:02}:{minute:02}")
if hour != last_checked_hour:
has_fed_this_hour = False
last_checked_hour = hour
try:
dht_sensor.measure()
temp = dht_sensor.temperature()
except:
print("Temp read failed")
continue
pH = read_ph()
do = read_do()
ec = read_ec()
level = read_level()
light = read_light()
print(f"Temp: {temp}C, pH: {pH}, DO: {do}, EC: {ec}, Level: {level}, Light: {light}")
# Publish Data to MQTT
mqtt_client.publish(b"fish/temp", str(temp))
mqtt_client.publish(b"fish/ph", str(pH))
mqtt_client.publish(b"fish/do", str(do))
mqtt_client.publish(b"fish/ec", str(ec))
mqtt_client.publish(b"fish/level", str(level))
mqtt_client.publish(b"fish/light", str(light))
safe = (fish.temp_min <= temp <= fish.temp_max and
fish.ph_min <= pH <= fish.ph_max and
do >= fish.do_min and
fish.ec_min <= ec <= fish.ec_max and
fish.light_min <= light <= fish.light_max and
level >= fish.level_min)
print(f"safety conditions : {safe}")
if safe:
mqtt_client.publish(b"fish/safety", b"safe")
else:
mqtt_client.publish(b"fish/safety", b"unsafe")
if button.value() == 0:
manual_feed = True
time_to_feed = (hour, minute) in feed_times
if (time_to_feed and not has_fed_this_hour) or manual_feed:
safe = (fish.temp_min <= temp <= fish.temp_max and
fish.ph_min <= pH <= fish.ph_max and
do >= fish.do_min and
fish.ec_min <= ec <= fish.ec_max and
fish.light_min <= light <= fish.light_max and
level >= fish.level_min)
if safe:
# publish safe conditions
mqtt_client.publish(b"fish/safety", b"safe")
ration = get_ration_percent(temp)
daily_food = total_fish_weight * ration
per_feed = daily_food / fish.frequency
print(f"Feeding {per_feed:.2f} grams")
feed_fish(per_feed)
has_fed_this_hour = True
manual_feed = False
else:
mqtt_client.publish(b"fish/safety", b"unsafe")
print("Conditions unsafe for feeding")
led.on()
else:
print("No feeding now")
sleep(10)
# --- Run Program ---
connect_wifi()
connect_mqtt()
init()
main_loop()