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


import random
import time
from machine import Pin, SPI, PWM, time_pulse_us
import bluetooth
from ble_simple_peripheral import BLESimplePeripheral
import dht
# Если библиотеки для текста нет - закомментируй эту строку и все строки с display
from ili9341 import Display, color565 

# ==============================================================================
# 1. БЫСТРЫЙ ДРАЙВЕР ДЛЯ АНИМАЦИЙ (Моргание)
# ==============================================================================
class ILI9341Direct:
    def __init__(self, spi, cs_pin, dc_pin, rst_pin):
        self.spi = spi
        self.cs = Pin(cs_pin, Pin.OUT)
        self.dc = Pin(dc_pin, Pin.OUT)
        self.rst = Pin(rst_pin, Pin.OUT)
        self.reset()
        self.init_display()

    def reset(self):
        self.rst.value(0); time.sleep_ms(50); self.rst.value(1); time.sleep_ms(100)

    def write_cmd(self, cmd):
        self.dc.value(0); self.cs.value(0); self.spi.write(bytearray([cmd])); self.cs.value(1)

    def write_data(self, data):
        self.dc.value(1); self.cs.value(0); self.spi.write(data); self.cs.value(1)

    def init_display(self):
        self.write_cmd(0x01); time.sleep_ms(100)
        self.write_cmd(0x11); time.sleep_ms(120)
        self.write_cmd(0x3A); self.write_data(b'\x55')
        self.write_cmd(0x36); self.write_data(b'\x28')
        self.write_cmd(0x29); time.sleep_ms(50)

    def set_window(self, x0, y0, x1, y1):
        self.write_cmd(0x2A); self.write_data(bytes([(x0 >> 8) & 0xFF, x0 & 0xFF, (x1 >> 8) & 0xFF, x1 & 0xFF]))
        self.write_cmd(0x2B); self.write_data(bytes([(y0 >> 8) & 0xFF, y0 & 0xFF, (y1 >> 8) & 0xFF, y1 & 0xFF]))
        self.write_cmd(0x2C)

@micropython.viper
def decode_chunk_normal(raw_ptr: ptr8, rgb_ptr: ptr8, num_bytes: int, fg_h: int, fg_l: int, bg_h: int, bg_l: int):
    out_idx = 0
    for i in range(num_bytes):
        b = raw_ptr[i]
        for bit in range(7, -1, -1):
            if (b >> bit) & 1:
                rgb_ptr[out_idx] = fg_h; rgb_ptr[out_idx + 1] = fg_l
            else:
                rgb_ptr[out_idx] = bg_h; rgb_ptr[out_idx + 1] = bg_l
            out_idx += 2

def play_animation(tft, filename, width=240, height=240, fps=30, fg_color=0xFFFF, bg_color=0x0000):
    bytes_per_row = width // 8
    frame_bytes = bytes_per_row * height
    raw_buf = bytearray(frame_bytes)
    chunk_rows = 20
    chunk_raw_bytes = bytes_per_row * chunk_rows
    chunk_rgb = bytearray(width * chunk_rows * 2)
    fg_h, fg_l = (fg_color >> 8) & 0xFF, fg_color & 0xFF
    bg_h, bg_l = (bg_color >> 8) & 0xFF, bg_color & 0xFF
    x_offset = (320 - width) // 2
    y_offset = (240 - height) // 2
    delay_ms = int(1000 / fps) if fps > 0 else 0

    try:
        with open(filename, 'rb') as f:
            while True:
                read_bytes = f.readinto(raw_buf)
                if read_bytes < frame_bytes:
                    break
                t_start = time.ticks_ms()
                tft.set_window(x_offset, y_offset, x_offset + width - 1, y_offset + height - 1)
                for offset in range(0, frame_bytes, chunk_raw_bytes):
                    chunk_mem = memoryview(raw_buf)[offset : offset + chunk_raw_bytes]
                    decode_chunk_normal(chunk_mem, chunk_rgb, chunk_raw_bytes, fg_h, fg_l, bg_h, bg_l)
                    tft.write_data(chunk_rgb)
                if delay_ms > 0:
                    elapsed = time.ticks_diff(time.ticks_ms(), t_start)
                    sleep_time = delay_ms - elapsed
                    if sleep_time > 0:
                        time.sleep_ms(sleep_time)
    except OSError:
        print(f"Анимация {filename} не найдена")

# ==============================================================================
# 2. НАСТРОЙКА ПИНОВ
# ==============================================================================
spi = SPI(2, baudrate=40000000, sck=Pin(18), mosi=Pin(23), miso=Pin(19))
tft = ILI9341Direct(spi, cs_pin=5, dc_pin=2, rst_pin=4)
tft.clear(0x0000)

# Текстовый дисплей (если есть библиотека)
try:
    display = Display(spi, dc=Pin(2), cs=Pin(5), rst=Pin(4), width=320, height=240, rotation=90)
except:
    display = None

servo = PWM(Pin(25), freq=50)
dht_sensor = dht.DHT11(Pin(33))
trig = Pin(16, Pin.OUT)
echo = Pin(35, Pin.IN)

M_A_1A = Pin(26, Pin.OUT); M_A_1B = Pin(27, Pin.OUT)
M_B_1A = Pin(13, Pin.OUT); M_B_1B = Pin(14, Pin.OUT)

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

# ==============================================================================
# 3. БАЗОВЫЕ ФУНКЦИИ
# ==============================================================================
def set_servo_angle(angle):
    servo.duty_u16(int(4000 + (angle / 180) * 4000))

def measure_distance():
    trig.value(0); time.sleep_us(2); trig.value(1); time.sleep_us(10); trig.value(0)
    try:
        duration = time_pulse_us(echo, 1, 30000)
        return (duration * 0.0343) / 2 if duration > 0 else 999
    except OSError:
        return 999

def stop_motors():
    M_A_1A.value(0); M_A_1B.value(0); M_B_1A.value(0); M_B_1B.value(0)

def move_forward():
    M_A_1A.value(1); M_A_1B.value(0); M_B_1A.value(1); M_B_1B.value(0)

def move_backward():
    M_A_1A.value(0); M_A_1B.value(1); M_B_1A.value(0); M_B_1B.value(1)

def turn_left():
    M_A_1A.value(0); M_A_1B.value(1); M_B_1A.value(1); M_B_1B.value(0)

def turn_right():
    M_A_1A.value(1); M_A_1B.value(0); M_B_1A.value(0); M_B_1B.value(1)

def turn_around_180():
    turn_left()
    time.sleep(0.7) # Время на разворот
    stop_motors()

# ==============================================================================
# 4. ОБРАБОТКА КОМАНД BLUETOOTH
# ==============================================================================
def on_rx(data):
    try:
        cmd = data.decode('utf-8').strip().lower()
        print('BT Команда: ' + cmd)
        
        # --- КОМАНДЫ С ТВОИХ СКРИНШОТОВ ---
        if cmd == 'w': # LOVE
            play_animation(tft, 'anim_1bit6.bin', fps=30) 
        elif cmd == 'q': # HI
            play_animation(tft, 'hi.bin', fps=30) 
        elif cmd == 'y': # FORWARD
            move_forward(); time.sleep(1); stop_motors()
        elif cmd == 'o': # BACK
            move_backward(); time.sleep(1); stop_motors()
            
        # --- КОЛЕСА (БЕЗ ШЕИ) ---
        elif cmd == 'r': # LEFT (Колеса)
            turn_left(); time.sleep(0.5); stop_motors()
        elif cmd == 't': # RIGHT (Колеса)
            turn_right(); time.sleep(0.5); stop_motors()
            
        # --- ТОЛЬКО ШЕЯ (СЛИТНО) ---
        elif cmd == 'gleft': # Поворот только шеи налево
            set_servo_angle(180)
            time.sleep(1.5)
            set_servo_angle(90) # Возврат
        elif cmd == 'gright': # Поворот только шеи направо
            set_servo_angle(0)
            time.sleep(1.5)
            set_servo_angle(90) # Возврат

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

sp.on_write(on_rx)

# ==============================================================================
# 5. ПЕРЕМЕННЫЕ ДЛЯ АВТОНОМНОСТИ
# ==============================================================================
print("Робот LOIK запущен! Автономный режим.")

# Начальное состояние: робот стоит и ждет
last_move_time = time.ticks_ms()
is_moving = False
move_end_time = 0
wait_time = random.randint(10, 17) * 1000 # Ждем от 10 до 17 секунд

last_distance = 999
last_dht_time = time.ticks_ms()
last_blink_time = time.ticks_ms() # Таймер для моргания

# Флаг: проверяли ли мы шею на текущей стоянке
looked_around_this_wait = False 

# ==============================================================================
# 6. ОСНОВНОЙ ЦИКЛ
# ==============================================================================
while True:
    now = time.ticks_ms()

    # 1. ДАТЧИК DHT11 И ВЫВОД НА ЭКРАН (Раз в 3 секунды)
    if time.ticks_diff(now, last_dht_time) > 3000:
        try:
            dht_sensor.measure()
            t = dht_sensor.temperature()
            h = dht_sensor.humidity()
            print(f"Температура: {t}C, Влажность: {h}%")
            
            if display:
                display.fill_rectangle(0, 0, 320, 40, color565(0, 0, 0)) 
                display.draw_text8x8(10, 10, f"T: {t}C  H: {h}%", color565(255, 255, 255), color565(0,0,0), 0)
        except OSError:
            pass
        last_dht_time = now

    # 2. УЛЬТРАЗВУК И "КРАЙ СТОЛА"
    dist = measure_distance()
    if last_distance < 10 and dist > 20:
        print("КРАЙ СТОЛА! Разворот.")
        stop_motors()
        turn_around_180()
        
        # После разворота сразу едет вперед случайно (1-2.5 сек)
        move_forward()
        time.sleep(random.uniform(1.0, 2.5))
        stop_motors()
        
        # Сбрасываем таймеры, так как это была внеплановая поездка
        last_move_time = time.ticks_ms()
        wait_time = random.randint(10, 17) * 1000
        is_moving = False
        looked_around_this_wait = False
    last_distance = dist

    # 3. МОРГАНИЕ (Строго раз в 10 секунд)
    if time.ticks_diff(now, last_blink_time) >= 10000:
        print("Моргаем...")
        play_animation(tft, 'blink.bin', fps=30)
        last_blink_time = time.ticks_ms()

    # 4. ЛОГИКА ОЖИДАНИЯ И ОСМОТРА
    if not is_moving:
        elapsed_wait = time.ticks_diff(now, last_move_time)
        
        # Осмотр ровно на 10-й секунде ожидания
        if elapsed_wait >= 10000 and elapsed_wait < 10500 and not looked_around_this_wait:
            # Генерируем случайное число ОТ 1 ДО 20 (как ты просил)
            rand_check = random.randint(1, 20)
            print(f"Проверка осмотра: выпало {rand_check}")
            
            if rand_check > 14:
                print("Осматриваемся (шея)...")
                set_servo_angle(180); time.sleep(2)
                set_servo_angle(0);   time.sleep(4)
                set_servo_angle(90);  time.sleep(1)
            
            looked_around_this_wait = True # Больше не проверяем на этой стоянке

        # Если время ожидания вышло — начинаем движение
        if elapsed_wait >= wait_time:
            action = random.choice(['forward', 'forward', 'forward', 'left', 'right'])
            duration = random.uniform(1.0, 2.5) # Едем 1-2.5 секунды
            print(f"Едем: {action} ({duration:.1f} сек)")
            
            if action == 'forward': move_forward()
            elif action == 'left': turn_left()
            elif action == 'right': turn_right()
            
            is_moving = True
            move_end_time = time.ticks_add(now, int(duration * 1000))

    # 5. ЗАВЕРШЕНИЕ ДВИЖЕНИЯ
    else:
        if time.ticks_diff(now, move_end_time) >= 0:
            stop_motors()
            is_moving = False
            
            # Сбрасываем таймеры для новой стоянки
            last_move_time = time.ticks_ms()
            wait_time = random.randint(10, 17) * 1000 
            looked_around_this_wait = False # Разрешаем осмотр на новой стоянке

    time.sleep_ms(20)