from machine import Pin, PWM, I2C, time_pulse_us
from time import sleep, sleep_us, sleep_ms, ticks_ms, ticks_diff
import ssd1306
# pins
trig = Pin(26, Pin.OUT)
echo = Pin(27, Pin.IN)
red = Pin(16, Pin.OUT)
green = Pin(17, Pin.OUT)
blue = Pin(18, Pin.OUT)
buzzer = Pin(15, Pin.OUT)
servo = PWM(Pin(32), freq=50)
button = Pin(4, Pin.IN, Pin.PULL_UP)
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
oled = ssd1306.SSD1306_I2C(128, 64, i2c)
# settings
PET_DISTANCE = 20
NEAR_DISTANCE = 100
FEED_INTERVAL = 5
DISPLAY_TIMEOUT = 15
SLEEP_INTERVAL = 10
DISTANCE_CHANGE_THRESHOLD = 2
meal_due = False
sleeping = False
pet_present = False
system_running = False
last_button_state = 1
feed_seconds = 0
last_second_tick = ticks_ms()
no_signal_seconds = 0
last_no_signal_tick = ticks_ms()
last_distance = None
# get distance from ultrasonic sensor
def get_distance():
trig.on()
sleep_us(10)
trig.off()
duration = time_pulse_us(echo, 1, 1000000)
if duration < 0:
return 999
return duration * 0.0343 / 2
# set rgb led color
def set_color(r, g, b):
red.value(r)
green.value(g)
blue.value(b)
# update rgb led based on distance zone
def update_led(distance):
if sleeping:
set_color(0, 0, 0)
elif distance <= PET_DISTANCE:
set_color(0, 1, 0)
elif distance <= NEAR_DISTANCE:
set_color(0, 0, 1)
else:
set_color(1, 0, 0)
# move servo to an angle
def move_servo(angle):
pulse = int(500000 + (angle / 180) * 2000000)
servo.duty_ns(pulse)
# play one buzzer tone
def play_tone(freq, duration):
sound = PWM(Pin(15), freq=freq, duty=512)
sleep_ms(duration)
sound.deinit()
# play happy feeding melody
def happy_melody():
for note in [523, 659, 784, 1047]:
play_tone(note, 150)
sleep_ms(30)
# play wake up sound
def chirp():
play_tone(880, 80)
sleep_ms(30)
play_tone(1200, 100)
# center text on the oled
def center_text(text, y):
x = (128 - len(text) * 8) // 2
oled.text(text, x, y)
# show ready screen
def show_ready(distance, seconds):
oled.fill(0)
center_text("PET FEEDER", 0)
center_text("State: READY", 16)
center_text("Dist: {}cm".format(int(distance)), 32)
center_text("Time: {}/{}".format(seconds, FEED_INTERVAL), 44)
if meal_due:
center_text("Meal: DUE", 56)
else:
center_text("Meal: WAIT", 56)
oled.show()
# show pet detected screen
def show_pet(distance, seconds):
oled.fill(0)
center_text("PET FEEDER", 0)
center_text("Hello! ^_^", 16)
center_text("Dist: {}cm".format(int(distance)), 32)
center_text("Time: {}/{}".format(seconds, FEED_INTERVAL), 44)
if meal_due:
center_text("Meal: DUE", 56)
else:
center_text("Meal: WAIT", 56)
oled.show()
# show sleep screen
def show_sleep():
oled.fill(0)
center_text("Zzz...", 15)
center_text("POWER SAVING", 40)
oled.show()
# show system-off screen
def show_off():
oled.fill(0)
center_text("SYSTEM OFF", 28)
oled.show()
# feed the pet
def feed_pet():
global meal_due
global feed_seconds
global last_second_tick
happy_melody()
set_color(0, 1, 0)
move_servo(90)
for percent in range(0, 101, 20):
oled.fill(0)
center_text("FEEDING PET", 0)
center_text("Dispensing...", 20)
oled.text("[", 10, 40)
oled.text("]", 110, 40)
bars = "=" * (percent // 10)
oled.text(bars, 20, 40)
center_text("{}% Complete".format(percent), 54)
oled.show()
sleep(1)
move_servo(0)
set_color(0, 0, 0)
meal_due = False
feed_seconds = 0
last_second_tick = ticks_ms()
oled.fill(0)
center_text("PET FEEDER", 0)
center_text("Dispenser CLOSED", 25)
center_text("Enjoy your meal!", 45)
oled.show()
sleep(2)
move_servo(0)
buzzer.off()
set_color(0, 0, 0)
show_off()
while True:
current_time = ticks_ms()
# detect button press (falling edge: was 1, now 0)
current_button_state = button.value()
if current_button_state == 0 and last_button_state == 1:
system_running = not system_running
if system_running:
# reset state cleanly whenever the system turns on
meal_due = False
sleeping = False
pet_present = False
feed_seconds = 0
last_second_tick = ticks_ms()
no_signal_seconds = 0
last_no_signal_tick = ticks_ms()
last_distance = None
else:
move_servo(0)
set_color(0, 0, 0)
buzzer.off()
show_off()
sleep_ms(200)
last_button_state = current_button_state
if not system_running:
sleep(0.1)
continue
distance = get_distance()
if last_distance is not None and abs(distance - last_distance) >= DISTANCE_CHANGE_THRESHOLD:
no_signal_seconds = 0
last_no_signal_tick = current_time
if sleeping:
sleeping = False
chirp()
last_distance = distance
if ticks_diff(current_time, last_second_tick) >= 1000:
last_second_tick = current_time
if feed_seconds < FEED_INTERVAL:
feed_seconds += 1
if feed_seconds >= FEED_INTERVAL:
meal_due = True
currently_present = distance <= PET_DISTANCE
if currently_present and not pet_present:
if sleeping:
sleeping = False
chirp()
pet_present = currently_present
if currently_present:
no_signal_seconds = 0
last_no_signal_tick = current_time
if meal_due:
feed_pet()
else:
update_led(distance)
show_pet(distance, feed_seconds)
else:
if ticks_diff(current_time, last_no_signal_tick) >= 1000:
last_no_signal_tick = current_time
if no_signal_seconds < SLEEP_INTERVAL:
no_signal_seconds += 1
if no_signal_seconds >= SLEEP_INTERVAL:
sleeping = True
update_led(distance)
show_sleep()
elif no_signal_seconds > DISPLAY_TIMEOUT:
update_led(distance)
else:
update_led(distance)
show_ready(distance, feed_seconds)
sleep(0.5)