from machine import Pin, I2C
import ssd1306
import machine
import time
import random
# ESP32 Pin assignment
# i2c = I2C(0, scl=Pin(22), sda=Pin(21))
# --- Hardware Configuration ---
SCREEN_WIDTH = 128
SCREEN_HEIGHT = 64
OLED_ADDRESS = 0x3C
# Define I2C Pinout for your ESP32-S3 board
# Double-check these pin indices against your physical wiring diagram if the screen stays dark.
I2C_SDA_PIN = 21
I2C_SCL_PIN = 22
# Initialize I2C interface (Using Hardware I2C 1)
i2c = machine.I2C(1, scl=machine.Pin(I2C_SCL_PIN), sda=machine.Pin(I2C_SDA_PIN), freq=400000)
# Initialize SSD1306 OLED screen
oled = ssd1306.SSD1306_I2C(SCREEN_WIDTH, SCREEN_HEIGHT, i2c, addr=OLED_ADDRESS)
# --- State Variables ---
pupil_x = 0 # Look direction horizontal offset (-4 to 4)
pupil_y = 0 # Look direction vertical offset (-3 to 3)
def draw_rounded_rect(x, y, w, h, r, color):
"""
Helper function to render rounded corner blocks manually,
as standard MicroPython framebuf does not feature built-in round rects.
"""
# Draw central vertical and horizontal structural bars
oled.fill_rect(x + r, y, w - 2*r, h, color)
oled.fill_rect(x, y + r, w, h - 2*r, color)
# Fill in the four rounded corners with tiny pixel blocks
# Top-Left Corner
oled.fill_rect(x, y, r, r, 0 if color == 1 else 1) # Clear bounding block
oled.pixel(x+1, y, color); oled.pixel(x, y+1, color)
oled.pixel(x+2, y, color); oled.pixel(x, y+2, color)
# Top-Right Corner
oled.fill_rect(x + w - r, y, r, r, 0 if color == 1 else 1)
oled.pixel(x + w - 2, y, color); oled.pixel(x + w - 1, y + 1, color)
# Bottom-Left Corner
oled.fill_rect(x, y + h - r, r, r, 0 if color == 1 else 1)
oled.pixel(x, y + h - 2, color); oled.pixel(x + 1, y + h - 1, color)
# Bottom-Right Corner
oled.fill_rect(x + w - r, y + h - r, r, r, 0 if color == 1 else 1)
oled.pixel(x + w - 1, y + h - 2, color); oled.pixel(x + w - 2, y + h - 1, color)
def draw_eye(center_x, center_y, eye_w, eye_h, p_x, p_y):
"""Draws a complete robotic eye with outer frame, pupil, and glint highlight."""
# 1. Main outer white round box block
draw_rounded_rect(center_x - eye_w//2, center_y - eye_h//2, eye_w, eye_h, 4, 1)
# 2. Black inner cutout pupil block
pupil_w = 12
pupil_h = 12
px = (center_x - pupil_w//2) + p_x
py = (center_y - pupil_h//2) + p_y
oled.fill_rect(px, py, pupil_w, pupil_h, 0)
# 3. Floating tiny white catchlight reflection glint
oled.fill_rect(px + 2, py + 2, 4, 4, 1)
def perform_blink():
"""Generates a quick closing and opening frame animation loop."""
global pupil_x, pupil_y
# Eye Closing Frames
for h in:
oled.fill(0)
if h > 0:
draw_eye(40, 32, 34, h, pupil_x, (pupil_y * h) // 32)
draw_eye(88, 32, 34, h, pupil_x, (pupil_y * h) // 32)
oled.show()
time.sleep_ms(15)
time.sleep_ms(40) # Blank closed eye buffer interval
# Eye Opening Frames
for h in:
oled.fill(0)
draw_eye(40, 32, 34, h, pupil_x, (pupil_y * h) // 32)
draw_eye(88, 32, 34, h, pupil_x, (pupil_y * h) // 32)
oled.show()
time.sleep_ms(15)
# --- Infinite Main Executive Control Loop ---
while True:
decision = random.randint(0, 100)
if decision < 15:
# 15% probability rate calculation to initiate a generic blink sequence
perform_blink()
elif 15 <= decision < 40:
# 25% probability rate calculation to slide looking target shifts
pupil_x = random.randint(-5, 5)
pupil_y = random.randint(-3, 3)
# Paint default fixed position look states
oled.fill(0)
draw_eye(40, 32, 34, 32, pupil_x, pupil_y) # Left Eye Component
draw_eye(88, 32, 34, 32, pupil_x, pupil_y) # Right Eye Component
oled.show()
# Let expression settle before rolling next cycle logic
time.sleep_ms(random.randint(400, 1500))