import machine, time
from machine import Pin
from machine import PWM
from utime import sleep
from stepper import Stepper # Assuming 'stepper' module is available in the same directory

# --- HCSR04 (Ultrasonic Sensor) Configuration ---
# Import the HCSR04 class (assuming it's in the same file or a separate 'hcsr04' module)
# If HCSR04 is in a separate file named hcsr04.py:
# from hcsr04 import HCSR04 
# If HCSR04 class is in the same main.py file (as provided in the prompt), no extra 'from' import is needed for the class itself,
# but the machine.time_pulse_us function requires the 'machine' module.

class HCSR04:
    # echo_timeout_us is based in chip range limit (400cm)
    def __init__(self, trigger_pin, echo_pin, echo_timeout_us=500*2*30):
        """
        trigger_pin: Output pin to send pulses
        echo_pin: Readonly pin to measure the distance. The pin should be protected with 1k resistor
        echo_timeout_us: Timeout in microseconds to listen to echo pin. 
        By default is based in sensor limit range (4m)
        """
        self.echo_timeout_us = echo_timeout_us
        # Init trigger pin (out)
        self.trigger = Pin(trigger_pin, mode=Pin.OUT, pull=None)
        self.trigger.value(0)

        # Init echo pin (in)
        self.echo = Pin(echo_pin, mode=Pin.IN, pull=None)

    def _send_pulse_and_wait(self):
        """
        Send the pulse to trigger and listen on echo pin.
        We use the method `machine.time_pulse_us()` to get the microseconds until the echo is received.
        """
        self.trigger.value(0) # Stabilize the sensor
        time.sleep_us(5)
        self.trigger.value(1)
        # Send a 10us pulse.
        time.sleep_us(10)
        self.trigger.value(0)
        try:
            pulse_time = machine.time_pulse_us(self.echo, 1, self.echo_timeout_us)
            return pulse_time
        except OSError as ex:
            if ex.args[0] == 110: # 110 = ETIMEDOUT
                raise OSError('Out of range')
            raise ex

    def distance_cm(self):
        pulse_time = self._send_pulse_and_wait()

        # To calculate the distance we get the pulse_time and divide it by 2 
        # (the pulse walk the distance twice) and by 29.1 becasue
        # the sound speed on air (343.2 m/s), that It's equivalent to
        # 0.034320 cm/us that is 1cm each 29.1us
        cms = int((pulse_time / 2) // 29.1)
        return cms

# Create an HCSR04 instance
# Assuming typical GPIO pins for trigger and echo. For example, D5 (GPIO5) for trigger and D18 (GPIO18) for echo.
# You should adjust these pin numbers based on your actual hardware connections.
ultrasonic_sensor = HCSR04(trigger_pin=5, echo_pin=18) 
print("HCSR04 ultrasonic sensor initialized on Trigger D5, Echo D18.")


print("Hello, ESP32!")

# --- LED Configuration ---
# Define the LED pin as D12 (GPIO12 on ESP32)
led_pin = Pin(12, Pin.OUT)
# Initialize the LED to LOW (OFF) state
led_pin.value(0)
print("LED initialized to OFF.")

# --- Button Configuration ---
# Define the Button pin as D27 (GPIO27 on ESP32)
# Pin.IN sets it as an input
# Pin.PULL_UP enables the internal pull-up resistor
button_pin = Pin(27, Pin.IN, Pin.PULL_UP)
print("Button initialized on D27 with internal pull-up.")

# --- Buzzer Configuration ---
class Buzzer:
    def __init__(self, pin):
        self.pin = pin
        self.buzzer = PWM(Pin(pin, Pin.OUT))
        self.buzzer.duty(0) # Ensure buzzer is off initially

    def beep_once(self):
        # Set frequency for a high-pitched beep (e.g., 1047 Hz for C6)
        self.buzzer.freq(1047)
        # Set duty cycle (volume), 50% is a good starting point
        self.buzzer.duty(50)
        # Beep for 0.2 seconds
        sleep(0.2)
        # Turn buzzer off immediately after the beep
        self.buzzer.duty(0)

# Create a buzzer object for D15
my_buzzer = Buzzer(15)
print("Buzzer initialized on D15.")

# --- Function to wait for button press ---
def wait_button_press():
    """
    This is a blocking function that waits until the button is pressed.
    It returns only when the button value changes to 0 (pressed).
    """
    print("Waiting for button press...")
    # Loop indefinitely as long as the button is NOT pressed.
    # With a pull-up resistor, button_pin.value() == 1 means NOT pressed.
    while button_pin.value() == 1:
        sleep(0.05) # Small delay to prevent busy-waiting and save CPU cycles

    # Once the loop exits, it means button_pin.value() is 0 (button IS pressed).
    print("Button pressed! Returning from wait_button_press().")
    # Add a small delay *after* the button is detected as pressed
    # This acts as a simple debounce and ensures a single trigger per press.
    sleep(0.2)

right_stepper = Stepper(19) # D19 (GPIO19 on ESP32)
left_stepper = Stepper(13)  # D13 (GPIO13 on ESP32)
print("Right stepper initialized on D19.")
print("Left stepper initialized on D13.")

# --- 6. New Function: get_steps_from_distance() ---
def get_steps_from_distance(distance_cm):
    """
    Calculates the number of stepper motor steps required based on a given distance in cm.
    This is a placeholder for your specific mapping from distance to steps.
    
    For example:
    - If distance is less than 10 cm, maybe 50 steps.
    - If distance is between 10 and 20 cm, maybe 25 steps.
    - If distance is more than 20 cm, maybe 10 steps.
    
    You'll need to define the actual relationship between distance and steps
    based on your robot's mechanics and desired behavior.
    """
    if distance_cm < 10:
        return 50  # Move more if very close
    elif 10 <= distance_cm < 20:
        return 25  # Move moderately
    elif 20 <= distance_cm < 50:
        return 10 # Move a little
    else:
        return 0   # Don't move if too far or out of relevant range

# --- Main Loop ---
while True:
    # Ensure LED and buzzer are off before we start waiting for a new press
    led_pin.value(0)
    my_buzzer.buzzer.duty(0) # Ensure buzzer PWM is off if not already

    # Call the blocking function to wait for button press
    wait_button_press()

    # Once wait_button_press() returns (meaning the button was pressed):
    # Turn LED ON
    led_pin.value(1)
    print("LED ON (after button press)")

    # Make buzzer beep once
    my_buzzer.beep_once()
    print("Buzzer beeped (after button press)")
    
    # --- Read distance from HCSR04 ---
    try:
        distance = ultrasonic_sensor.distance_cm()
        print(f"Distance: {distance} cm")

        # --- 7. Calculate steps and move steppers ---
        steps_to_move = get_steps_from_distance(distance)
        print(f"Calculated steps to move: {steps_to_move}")

        if steps_to_move > 0:
            for _ in range(steps_to_move):
                right_stepper.move_one_step()
                left_stepper.move_one_step()
                sleep(0.01) # Small delay between steps for smooth movement
            print(f"Steppers moved {steps_to_move} steps.")
        else:
            print("No steps to move based on distance.")

    except OSError as e:
        print(f"HCSR04 Error: {e}")

    # A short delay to allow visual/auditory confirmation of the action
    sleep(1) # Keep LED on for 1 second, the beep is already done within beep_once

    # The loop will then repeat, turning the LED off again and waiting for the next press.
A4988
A4988