Загрузка данных


# ==============================================================================
# БЛОК 8: КЛАСС УПРАВЛЕНИЯ РОБОТОМ (RobotController)
# Главный класс, связывающий всё воедино. Использует машину состояний.
# ==============================================================================
class RobotController:
    """
    Ядро робота. Управляет состояниями, таймерами и взаимодействием модулей.
    """
    
    # Определяем константы состояний
    STATE_IDLE = 0       # Стоим, ждем
    STATE_MOVING = 1     # Двигаемся
    STATE_LOOKING = 2    # Осматриваемся
    STATE_EMERGENCY = 3  # Экстренное торможение

    def __init__(self):
        # Инициализация аппаратных модулей
        self.spi = SPI(2, baudrate=40000000, sck=Pin(18), mosi=Pin(23), miso=Pin(19))
        self.display = DisplayDriver(self.spi, cs_pin=5, dc_pin=2, rst_pin=4)
        self.display.clear(0x0000)
        
        # Инициализация текстового дисплея
        self.text_display = None
        if TEXT_LIB_AVAILABLE:
            try:
                self.text_display = Display(self.spi, dc=Pin(2), cs=Pin(5), rst=Pin(4), width=320, height=240, rotation=90)
            except:
                pass
                
        # Инициализация менеджеров
        self.sensors = SensorManager(trig_pin=16, echo_pin=35, dht_pin=33)
        self.motors = MotorDriver(a1a_pin=26, a1b_pin=27, b1a_pin=13, b1b_pin=14)
        self.servo = ServoManager(pin=25)
        
        # Переменные машины состояний
        self.current_state = self.STATE_IDLE
        self.state_timer = time.ticks_ms() # Таймер текущего состояния
        self.action_timer = 0              # Таймер длительности движения
        self.last_dht_time = time.ticks_ms()
        
        # --- ИСПРАВЛЕНИЕ: Правильные названия переменных ---
        self.wait_duration = random.randint(10, 17) * 1000 # Сколько всего ждать
        self.has_looked = False                            # Флаг осмотра
        self.has_blinked = False                           # Флаг моргания
        self.action_type = ''                              # Текущее действие
        
    def update_display_text(self, t, h):
        """Обновляет текст в нижней полосе экрана."""
        if self.text_display:
            self.text_display.fill_rectangle(0, 220, 320, 20, color565(0, 0, 0))
            self.text_display.draw_text8x8(10, 225, f"T: {t}C  H: {h}%", color565(255, 255, 255), color565(0,0,0), 0)

    def show_text_on_screen(self, text):
        """Выводит произвольный текст на экран."""
        if self.text_display:
            self.text_display.fill_rectangle(0, 20, 320, 240, color565(0, 0, 0))
            self.text_display.draw_text8x8(10, 100, text, color565(255, 255, 255), color565(0,0,0), 0)

    def turn_around_180(self):
        """Разворот на 180 градусов."""
        self.motors.turn_left()
        time.sleep(0.7)
        self.motors.stop()

    def execute_emergency(self):
        """Экстренная остановка при обнаружении края."""
        print("!!! КРАЙ СТОЛА !!!")
        self.motors.stop()
        self.turn_around_180()
        self.motors.forward()
        time.sleep(1.5) # Отъезжаем назад
        self.motors.stop()
        
        # Сбрасываем таймеры и запускаем новый цикл ожидания
        self.start_new_wait_cycle()

    def start_new_wait_cycle(self):
        """
        Запускается ТОЛЬКО когда робот остановился.
        Строго генерирует число и фиксирует время старта.
        """
        self.current_state = self.STATE_IDLE
        self.state_timer = time.ticks_ms() # Засекаем время старта
        
        rand_val = random.randint(1, 20)
        self.wait_duration = rand_val * 1000 
        print(f"[ОЖИДАНИЕ] Выпало число: {rand_val} (Ждем {rand_val} сек)")
        
        self.has_looked = False
        self.has_blinked = False

    def run(self):
        print("Робот LOIK запущен!")
        self.start_new_wait_cycle() 
        
        while True:
            now = time.ticks_ms()

            # 1. Обновление DHT11
            if time.ticks_diff(now, self.last_dht_time) > 3000:
                try:
                    t, h = self.sensors.read_dht() # ИСПРАВЛЕНО: берем данные из sensor manager
                    if t is not None:
                        self.update_display_text(t, h)
                except OSError: pass
                self.last_dht_time = now

            # 2. Проверка края стола (ИСПРАВЛЕНО: вызов из self.sensors)
            if self.current_state != self.STATE_EMERGENCY:
                if self.sensors.check_edge():
                    self.execute_emergency()
                    continue

            # 3. Логика состояний
            if self.current_state == self.STATE_IDLE:
                elapsed = time.ticks_diff(now, self.state_timer)
                
                # 3.1 Моргание
                if elapsed >= 10000 and not self.has_blinked:
                    play_animation(self.display, 'BLINK.bin', fps=30) # ИСПРАВЛЕНО: self.display
                    self.has_blinked = True

                # 3.2 Осмотр
                if elapsed >= 10000 and not self.has_looked:
                    wait_seconds = self.wait_duration // 1000
                    if wait_seconds >= 14:
                        print(f"[ОСМОТР] Крутим шеей...")
                        self.servo.set_angle(180); time.sleep(2) # ИСПРАВЛЕНО: self.servo
                        self.servo.set_angle(0);   time.sleep(4)
                        self.servo.set_angle(90);  time.sleep(1)
                        
                        self.state_timer = time.ticks_add(self.state_timer, 7000)
                        
                    self.has_looked = True
                
                # 3.3 Начало движения
                if elapsed >= self.wait_duration:
                    action = random.choice(['forward', 'forward', 'forward', 'left', 'right'])
                    duration = random.uniform(1.0, 2.5)
                    
                    self.action_type = action
                    self.action_timer = time.ticks_add(now, int(duration * 1000))
                    self.current_state = self.STATE_MOVING
                    print(f"[ДВИЖЕНИЕ] Едем {action} ({duration:.1f} сек)")

            elif self.current_state == self.STATE_MOVING:
                if time.ticks_diff(now, self.action_timer) >= 0:
                    self.motors.stop() # ИСПРАВЛЕНО
                    self.start_new_wait_cycle()
                else:
                    if self.action_type == 'forward':
                        self.motors.forward(); time.sleep_ms(50); self.motors.stop() # ИСПРАВЛЕНО
                    elif self.action_type == 'left':
                        self.motors.turn_left(); time.sleep_ms(50); self.motors.stop() # ИСПРАВЛЕНО
                    elif self.action_type == 'right':
                        self.motors.turn_right(); time.sleep_ms(50); self.motors.stop() # ИСПРАВЛЕНО

            time.sleep_ms(10)


# ==============================================================================
# БЛОК 9: ОБРАБОТКА КОМАНД BLUETOOTH
# ==============================================================================
robot = RobotController()

ble = bluetooth.BLE()
sp = BLESimplePeripheral(ble, name="LOIK")

def on_rx(data):
    try:
        cmd = data.decode('utf-8').strip().lower()
        print(f"BT Команда: {cmd}")
        
        # --- КОМАНДЫ ДВИЖЕНИЯ ---
        if cmd == 'y': 
            if robot.sensors.check_edge():
                robot.execute_emergency()
            else:
                robot.current_state = robot.STATE_MOVING
                robot.action_type = 'forward'
                robot.action_timer = time.ticks_add(time.ticks_ms(), 1000)
                
        elif cmd == 'o': 
            robot.motors.backward(); time.sleep(1); robot.motors.stop() # ИСПРАВЛЕНО
        elif cmd == 'r': 
            robot.motors.turn_left(); time.sleep(0.5); robot.motors.stop() # ИСПРАВЛЕНО
        elif cmd == 't': 
            robot.motors.turn_right(); time.sleep(0.5); robot.motors.stop() # ИСПРАВЛЕНО
            
        # --- КОМАНДЫ ШЕИ ---
        elif cmd == 'gleft': 
            robot.servo.set_angle(180); time.sleep(1.5); robot.servo.set_angle(90) # ИСПРАВЛЕНО
        elif cmd == 'gright': 
            robot.servo.set_angle(0); time.sleep(1.5); robot.servo.set_angle(90) # ИСПРАВЛЕНО
            
        # --- АНИМАЦИИ ---
        elif cmd == 'w': 
            play_animation(robot.display, 'LOVE.bin', fps=30) 
        elif cmd == 'q': 
            play_animation(robot.display, 'hello.bin', fps=30) 
            
        # --- ТЕКСТ ---
        elif cmd.startswith('text:'):
            text_to_show = cmd.split(':')[1]
            robot.show_text_on_screen(text_to_show)
        
        # --- МОРГАНИЕ ---
        elif cmd.startswith('blink:'):
            try:
                count = int(cmd.split(':')[1])
                for _ in range(count):
                    play_animation(robot.display, 'blink.bin', fps=30) # ИСПРАВЛЕНО
                    time.sleep(0.5)
            except ValueError:
                print("Ошибка: неверный формат команды моргания")
            
        elif cmd == 'stop':
            robot.motors.stop() # ИСПРАВЛЕНО
            robot.start_new_wait_cycle()

    except Exception as e:
        print("Ошибка обработки команды:", e)

sp.on_write(on_rx)


# ==============================================================================
# БЛОК 10: ЗАПУСК ПРОГРАММЫ
# ==============================================================================
if __name__ == "__main__":
    robot.run()