import RPi.GPIO as GPIO import time import logging import pygame import random import sys # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(message)s', handlers=[ logging.FileHandler("wecker.log"), logging.StreamHandler() ] ) # GPIO Setup BUTTON_PIN = 17 LED_PIN = 27 GPIO.setmode(GPIO.BCM) GPIO.setup(LED_PIN, GPIO.OUT) GPIO.setup(BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Audio Setup (with optimized buffer) pygame.mixer.pre_init(frequency=44100, size=-16, channels=2, buffer=4096) pygame.mixer.init() try: pygame.mixer.music.load("Laid Back - Sunshine Reggae.mp3") pygame.mixer.music.set_volume(1.0) except Exception as e: logging.error(f"Error loading audio file: {e}") sys.exit(1) # State Machine states for the flow STATE_RINGING = 0 STATE_WAIT_BEFORE_BLINK = 1 STATE_BLINKING = 2 STATE_WAIT_FOR_INPUT = 3 STATE_EVALUATING = 4 STATE_WAIT_BEFORE_RETRY = 5 state = STATE_RINGING target_blinks = 0 user_presses = 0 last_interaction_time = 0 button_was_pressed = False def set_led(on): """Turns the LED on or off (LOW = ON, HIGH = OFF)""" if on: GPIO.output(LED_PIN, GPIO.LOW) else: GPIO.output(LED_PIN, GPIO.HIGH) def blink_led(times): """Blinks the LED a specific number of times (blocking)""" for _ in range(times): set_led(True) time.sleep(0.3) # LED on for 300ms set_led(False) time.sleep(0.3) # LED off for 300ms logging.info("Alarm clock started. Music is playing in an endless loop.") # -1 means the song is played in an endless loop pygame.mixer.music.play(-1) set_led(False) # LED off at start try: while True: now = time.time() # Read button state (LOW = pressed, due to Pull-Up) is_pressed = (GPIO.input(BUTTON_PIN) == GPIO.LOW) button_just_pressed = False if is_pressed and not button_was_pressed: button_was_pressed = True button_just_pressed = True elif not is_pressed and button_was_pressed: button_was_pressed = False # --- State Machine --- if state == STATE_RINGING: if button_just_pressed: logging.info("Alarm button pressed! Puzzle started. Waiting 3 seconds...") state = STATE_WAIT_BEFORE_BLINK last_interaction_time = now elif state == STATE_WAIT_BEFORE_BLINK: if now - last_interaction_time >= 3.0: target_blinks = random.randint(1, 7) logging.info(f"Blinking {target_blinks} times...") state = STATE_BLINKING elif state == STATE_BLINKING: blink_led(target_blinks) logging.info("Blinking finished. Waiting for input...") state = STATE_WAIT_FOR_INPUT user_presses = 0 last_interaction_time = time.time() # Update the button state after the blocking blink button_was_pressed = (GPIO.input(BUTTON_PIN) == GPIO.LOW) elif state == STATE_WAIT_FOR_INPUT: # LED lights up as confirmation when the button is pressed set_led(is_pressed) if button_just_pressed: user_presses += 1 logging.info(f"Button pressed: {user_presses} times") if is_pressed: # As long as the button is held down, we keep resetting the timeout last_interaction_time = now # If the button was released and 3 seconds have passed without action -> Evaluation if not is_pressed and (now - last_interaction_time >= 3.0): state = STATE_EVALUATING elif state == STATE_EVALUATING: logging.info(f"Evaluation: Target={target_blinks}, Entered={user_presses}") if user_presses == target_blinks: logging.info("Puzzle solved correctly! Alarm clock is stopping.") pygame.mixer.music.stop() set_led(False) break # Exits the while loop and thus the program else: logging.info("Incorrect input! Waiting 3 seconds before retrying...") state = STATE_WAIT_BEFORE_RETRY last_interaction_time = time.time() set_led(False) elif state == STATE_WAIT_BEFORE_RETRY: if now - last_interaction_time >= 5.0: # Puzzle is repeated with a new random blink count target_blinks = random.randint(1, 7) logging.info(f"New attempt! Blinking {target_blinks} times...") state = STATE_BLINKING time.sleep(0.02) # Short polling interval to save CPU and ensure good responsiveness (20ms) except KeyboardInterrupt: logging.info("Manually aborted (CTRL+C).") finally: pygame.mixer.quit() GPIO.cleanup() logging.info("Program finished.")