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


import time
import machine
import random
import dht
import ssd1306_rus
import remotexy
from machine import Pin, I2C, PWM

# Повышаем частоту процессора для плавной работы
machine.freq(160000000)

# Конфигурация RemoteXY (ИСПРАВЛЕН КРИТИЧЕСКИЙ БАГ С АДРЕСОМ СЕРВЕРА)
REMOTEXY_SERVER = "://remotexy.com"
REMOTEXY_PORT = 6376
REMOTEXY_TOKEN = "e87b7aef86326cd66db35cbe757917dc" # Твой токен из блокнота

# Инициализация I2C и Дисплея (128x32)
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
oled = ssd1306_rus.SSD1306_I2C(128, 32, i2c)

# Инициализация Мосфетов (ШИМ кулеров на частоте 25 кГц)
pwm_extract = PWM(Pin(4), freq=25000)   # Вытяжка
pwm_blow_up = PWM(Pin(16), freq=25000)   # Верхний обдув
pwm_blow_down = PWM(Pin(17), freq=25000) # Нижний обдув

# Инициализация Датчиков и кнопки
sensor_climat = dht.DHT22(Pin(15))
light_komparator = Pin(34, Pin.IN)
button = Pin(12, Pin.IN, Pin.PULL_UP)

# Настройка RemoteXY клиента
rtxy = remotexy.RemoteXY_Cloud(REMOTEXY_SERVER, REMOTEXY_PORT, REMOTEXY_TOKEN)

# Начальные системные переменные
CURRENT_MODE = "АВТО"
AUTO_MODE = True
ALARM_ACTIVE = False
SENSOR_ERROR = False
DAY_PHASE = "ДЕНЬ"

# Переменные вольтажа
volts_extract = 0.0
volts_blow_up = 0.0
volts_blow_down = 0.0

# Переменные интерфейса
screen_page = 0
screen_active_until = time.ticks_ms() + 10000

# Таймеры и флаги кнопки
btn_was_pressed = False
btn_press_start = 0
blink_timer = 0
blink_state = False

# Таймеры работы
watering_end_time = 0
next_sensor_read = time.ticks_ms() # Сработает сразу при старте
next_fan_adjust = time.ticks_ms()
wind_offset = 0.0

# Вспомогательная функция перевода вольт в ШИМ (ВСТАВЛЯЕМ СТРОГО СЮДА)
def volts_to_pwm(v):
    return max(0, min(1023, int((v / 12.0) * 1023)))

while True:
    current_time = time.ticks_ms()
    rtxy.update()
    
    # === БЛОК 1: НАДЁЖНЫЙ ОПРОС ДАТЧИКОВ (РАЗ В 10 СЕКУНД) ===
    # ИСПРАВЛЕН БАГ ПЕРЕПОЛНЕНИЯ: Теперь вместо "current_time > next_sensor_read"
    # используется безопасная функция time.ticks_diff
    if time.ticks_diff(next_sensor_read, current_time) <= 0:
        next_sensor_read = time.ticks_add(current_time, 10000) # Сдвиг на 10 сек вперед
        
        try:
            sensor_climat.measure()
            temp_box = sensor_climat.temperature()
            hum_box = sensor_climat.humidity()
            SENSOR_ERROR = False
        except OSError:
            temp_box = 0.0
            hum_box = 0.0
            SENSOR_ERROR = True
            
        # Проверка фазы суток по фотодатчику
        if light_komparator.value() == 0:
            DAY_PHASE = "НОЧЬ"
        else:
            DAY_PHASE = "ДЕНЬ"
            
        # Отправка актуальных данных в приложение RemoteXY
        rtxy.set("temp_box", f"{temp_box:.1f}°C")
        rtxy.set("hum_box", f"{hum_box:.1f}%")
        rtxy.set("status_line", "СБОЙ ДАТЧИКА" if SENSOR_ERROR else "КЛИМАТ ОК")
        
        # Защита от жесткого перегрева бокса
        if not SENSOR_ERROR and temp_box > 32.0:
            ALARM_ACTIVE = True
            
        # Генерация случайного смещения для имитации живого ветра
        wind_offset = random.choice([-0.5, 0.0, 0.5])
    # === БЛОК 2: НАДЁЖНЫЙ ОПРОС ФИЗИЧЕСКОЙ КНОПКИ (С ДЕБАУНСОМ) ===
    btn_now = button.value()
    
    if btn_now == 0 and not btn_was_pressed:
        time.sleep_ms(20) # Аппаратный дебаунс (защита от дребезга)
        if button.value() == 0:
            btn_was_pressed = True
            btn_press_start = current_time
            screen_active_until = time.ticks_add(current_time, 10000)
            oled.poweron()
            
    # ИСПРАВЛЕН БАГ ВЫЛЕТА: Расчет hold_time вынесен наверх блока отпускания кнопки
    elif btn_now == 1 and btn_was_pressed:
        hold_time = time.ticks_diff(current_time, btn_press_start)
        btn_was_pressed = False
        screen_active_until = time.ticks_add(current_time, 10000)
        oled.poweron()
        
        if ALARM_ACTIVE:
            ALARM_ACTIVE = False # Клик сбрасывает панику экрана
        elif CURRENT_MODE == "СУШКА":
            CURRENT_MODE = "АВТО" # Из сушки выходим коротким кликом
            screen_page = 2
        elif CURRENT_MODE == "ПОЛИВ":
            CURRENT_MODE = "AВТО" # Клик отменяет полив
        elif hold_time < 1500:
            if CURRENT_MODE != "ПОЛИВ":
                screen_page = (screen_page + 1) % 3
        elif hold_time >= 1500 and hold_time < 3500:
            # ЗАЖАТИЕ 2 СЕКУНДЫ — ПОЛИВ (СТРОГО ЕСЛИ СУШКА ВЫКЛЮЧЕНА!)
            if CURRENT_MODE == "АВТО":
                CURRENT_MODE = "ПОЛИВ"
                pwm_blow_up.duty(0)
                pwm_blow_down.duty(0)
                pwm_extract.duty(volts_to_pwm(8.0))
                watering_end_time = time.ticks_add(current_time, 180000)
        elif hold_time >= 4500:
            # ЗАЖАТИЕ 5 СЕКУНД — СУШКА (работает строго на странице 2)
            if screen_page == 2 and CURRENT_MODE == "АВТО":
                CURRENT_MODE = "СУШКА"
    # === БЛОК 3: ЧТЕНИЕ ДАННЫХ ИЗ REMOTEXY И УПРАВЛЕНИЕ КУЛЕРАМИ ===
    # Чтение ручных ползунков и флагов из приложения
    val_auto = rtxy.get("auto_mode")
    if val_auto is not None:
        AUTO_MODE = True if int(val_auto) == 1 else False

    val_v_ext = rtxy.get("v_extract")
    if val_v_ext is not None:
        v_extract_manual = float(val_v_ext)

    val_v_bup = rtxy.get("v_blow_up")
    if val_v_bup is not None:
        v_blow_up_manual = float(val_v_bup)

    val_v_bdn = rtxy.get("v_blow_down")
    if val_v_bdn is not None:
        v_blow_down_manual = float(val_v_bdn)

    # Логика работы кулеров в зависимости от текущего режима
    if ALARM_ACTIVE:
        volts_extract = 12.0
        volts_blow_up = 0.0
        volts_blow_down = 0.0
    elif CURRENT_MODE == "СУШКА":
        volts_extract = 7.0
        volts_blow_up = 0.0
        volts_blow_down = 0.0
    elif CURRENT_MODE == "ПОЛИВ":
        if time.ticks_diff(watering_end_time, current_time) <= 0:
            CURRENT_MODE = "АВТО"
        else:
            volts_extract = 8.0
            volts_blow_up = 0.0
            volts_blow_down = 0.0
    elif CURRENT_MODE == "АВТО":
        if AUTO_MODE:
            # Автоматическая регулировка кулеров по таймеру раз в 5 секунд
            if time.ticks_diff(next_fan_adjust, current_time) <= 0:
                next_fan_adjust = time.ticks_add(current_time, 5000)
                if not SENSOR_ERROR:
                    # Корректировка вытяжки по температуре
                    if temp_box <= 24.0:
                        volts_extract = 3.5
                    elif temp_box >= 28.0:
                        volts_extract = 7.5
                    else:
                        volts_extract = 3.5 + ((temp_box - 24.0) / 4.0) * 4.0

                    # Корректировка обдува по влажности и фазе суток
                    if DAY_PHASE == "ДЕНЬ":
                        base_up = 5.0 if hum_box < 60.0 else 7.0
                        base_down = 4.5 if hum_box < 60.0 else 6.5
                    else:
                        base_up = 4.0
                        base_down = 3.5

                    volts_blow_up = base_up + wind_offset
                    volts_blow_down = base_down - wind_offset
        else:
            # Ручной режим управления по ползункам из RemoteXY
            volts_extract = v_extract_manual
            volts_blow_up = v_blow_up_manual
            volts_blow_down = v_blow_down_manual



    # Подача итогового напряжения на физические пины мосфетов
    pwm_extract.duty(volts_to_pwm(volts_extract))
    pwm_blow_up.duty(volts_to_pwm(volts_blow_up))
    pwm_blow_down.duty(volts_to_pwm(volts_blow_down))

    # === БЛОК 4: ОТРИСОВКА ИНФОРМАЦИИ НА OLED ДИСПЛЕЕ ===
    oled.fill(0)
    
    if ALARM_ACTIVE:
        if current_time % 1000 < 500:
            oled.text_rus("ЖАРА !", 32, 8, 2)
        oled.poweron()
    elif CURRENT_MODE == "ПОЛИВ":
        if current_time > blink_timer:
            blink_state = not blink_state
            blink_timer = current_time + 500
        if blink_state:
            oled.fill(1)
            rem_sec = max(0, time.ticks_diff(watering_end_time, current_time) // 1000)
            oled.text_rus("ПОЛИВ", 0, 0, 2)
            s_sec = str(rem_sec)
            x_pos = 128 - (len(s_sec) * 12)
            oled.text_rus(s_sec, x_pos, 16, 2)
    elif CURRENT_MODE == "СУШКА":
        oled.text_rus("СУШКА", 32, 0, 2)
        s_t = f"{temp_box:.1f}"
        s_h = f"{hum_box:.1f}%"
        oled.text_rus(s_t, 0, 20, 1)
        oled.text_rus(s_h, 90, 20, 1)
    elif time.ticks_diff(screen_active_until, current_time) > 0:
        if screen_page == 0:
            s_t = f"{temp_box:.1f}"
            s_h = f"{hum_box:.1f}%"
            oled.text_rus(s_t, 0, 0, 2)
            x_pos = 128 - (len(s_h) * 12)
            oled.text_rus(s_h, x_pos, 16, 2)
        elif screen_page == 1:
            oled.text_rus(f"Т:{temp_box:.1f} ВЫТ:{volts_extract:.1f}В", 0, 0, 1)
            oled.text_rus(f"В:{hum_box:.1f}% ОБ.В:{volts_blow_up:.1f}В", 0, 8, 1)
            oled.text_rus(f"ФАЗА:{DAY_PHASE} ОБ.Н:{volts_blow_down:.1f}В", 0, 16, 1)
            status_txt = "СБОЙ" if SENSOR_ERROR else "ОК"
            net_txt = "@" if rtxy.get("auto_mode") is not None else " "
            oled.text_rus(f"КЛИМАТ:{status_txt} {net_txt} ТЯГА:8Па", 0, 24, 1)
        elif screen_page == 2:
            oled.text_rus("ВКЛ СУШКУ?", 32, 0)
            oled.text_rus("ЗАЖМИ", 98, 16)
            oled.text_rus("5 СЕК", 98, 24)
    else:
        oled.poweroff()

    oled.show()
    time.sleep_ms(20) # Небольшая пауза для разгрузки процессора