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


import time
import machine
import random
import dht
import ssd1306_rus
import remotexy

from machine import Pin, I2C, PWM

# Установка частоты процессора
machine.freq(80000000)

# Настройка облака RemoteXY
REMOTEXY_SERVER = "://remotexy.com"
REMOTEXY_PORT = 6376
REMOTEXY_TOKEN = "0d415cc415d83fb71049ed72845291b0"

rtxy = remotexy.RemoteXY_Cloud(REMOTEXY_SERVER, REMOTEXY_PORT, REMOTEXY_TOKEN)

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

# Подключение дисплея и кнопок
i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=100000)
# Датчик bmp280 временно исключен из инициализации, пока он едет
oled = ssd1306_rus.SSD1306_RUS(128, 32, i2c) # Высота строго 32 пикселя
oled.poweroff()

sensor_climat = dht.DHT22(Pin(5))       
light_komparator = Pin(18, Pin.IN)      
button = Pin(12, Pin.IN, Pin.PULL_UP)
# === 5. КОМАНДНЫЙ ЦЕНТР ТЕПЛИЦЫ (ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ) ===
vaccum_global = 8.0      # Виртуальная тяга зафиксирована на 8 Па, пока нет датчика
temp_box = 0.0           # Текущая температура
hum_box = 0.0            # Текущая влажность
is_daytime = False       # Фаза суток (True - День, False - Ночь)

LIMIT_WARM = 26.5        # Порог "ПОТЕПЛЕЛО"
LIMIT_HOT = 28.0         # Порог "ЖАРА"

volts_extract = 7.5      # Базовый вольтаж вытяжки
volts_blow_up = 7.0      # Базовый вольтаж верха
volts_blow_down = 6.0    # Базовый вольтаж низа

CURRENT_MODE = "АВТО"    # АВТО, ПОЛИВ или СУШКА
AUTO_MODE = True         # True - автомат, False - ручной режим
ALARM_ACTIVE = False     # Флаг тревоги (перегрев)
SENSOR_ERROR = False     # Флаг поломки белого датчика

WATERING_TIME_MIN = 15   # Минуты полива из телефона

# === 6. ТЕХНИЧЕСКИЕ ТАЙМЕРЫ И БУДИЛЬНИКИ ===
next_sensor_read = 0     # Опрос датчиков (раз в 10 сек)
next_fan_adjust = 0      # Шаг вытяжки (раз в 3 минуты)
watering_end_time = 0    # Конец обратного отсчета полива
screen_active_until = 0  # Время подсветки экрана (10 сек)
blink_timer = 0          # Частота мигания инверсии
blink_state = True       #

btn_was_pressed = False
btn_press_start = 0
screen_page = 0          # Страница меню (0, 1, 2)
wind_offset = 0.0

def volts_to_pwm(v):
    return int((v / 12.0) * 1023)

def update_remotexy_inputs():
    global LIMIT_WARM, LIMIT_HOT, AUTO_MODE, CURRENT_MODE, watering_end_time
    global volts_extract, volts_blow_up, volts_blow_down
    
    if rtxy.is_connected():
        val_warm = rtxy.get("limit_warm")
        if val_warm is not None: LIMIT_WARM = float(val_warm)
            
        val_hot = rtxy.get("limit_hot")
        if val_hot is not None: LIMIT_HOT = float(val_hot)
            
        val_auto = rtxy.get("auto_mode")
        if val_auto is not None: AUTO_MODE = True if int(val_auto) == 1 else False

        val_wbtn = rtxy.get("water_btn")
        if val_wbtn is not None and int(val_wbtn) == 1:
            # БЛОКИРОВКА: Включаем полив только если сушка СЕЙЧАС выключена!
            if CURRENT_MODE == "АВТО":
                CURRENT_MODE = "ПОЛИВ"
                # Принудительно гасим обдувы и даем вытяжку 8В прямо сейчас
                pwm_blow_up.duty(0)
                pwm_blow_down.duty(0)
                pwm_extract.duty(volts_to_pwm(8.0))
                
                # Замираем на 10 секунд, пока утихнет ветер и откроется дверь
                
                
                # Старт таймера полива на 3 минуты
                watering_end_time = time.ticks_ms() + 180000  
            elif CURRENT_MODE == "ПОЛИВ":
                CURRENT_MODE = "АВТО"

        if not AUTO_MODE:
            val_v_ext = rtxy.get("v_extract")
            if val_v_ext is not None: volts_extract = float(val_v_ext)
            val_v_up = rtxy.get("v_blow_up")
            if val_v_up is not None: volts_blow_up = float(val_v_up)
            val_v_down = rtxy.get("v_blow_down")
            if val_v_down is not None: volts_blow_down = float(val_v_down)

def send_remotexy_data():
    if rtxy.is_connected():
        rtxy.send("g_vac", vaccum_global)  
        rtxy.send("g_temp", temp_box)      
        rtxy.send("g_hum", hum_box)        
        rtxy.send("ind_day", 1 if is_daytime else 0)
# =========================================================================
# === 7. ГЛАВНЫЙ ИСПОЛНИТЕЛЬНЫЙ ЦИКЛ ТЕПЛИЦЫ ===
# =========================================================================

while True:
    current_time = time.ticks_ms()
    
    # Синхронизация с облаком и опрос телефона
    rtxy.update() 
    update_remotexy_inputs()
    
    # --- БЛОК 1: ОПРОС ДАТЧИКОВ (Раз в 10 секунд) ---
    if current_time > next_sensor_read:
        try:
            # Датчик барометра отключен. Тяга жестко зафиксирована в переменных
            try:
                sensor_climat.measure()
                temp_box = sensor_climat.temperature()
                hum_box = sensor_climat.humidity()
                SENSOR_ERROR = False  
            except OSError:
                SENSOR_ERROR = True   # Сбой датчика AM2302
            
            is_daytime = True if light_komparator.value() == 0 else False
            send_remotexy_data()
            
            # Авария только по перегреву (так как барометра физически нет)
            if CURRENT_MODE == "АВТО":
                if temp_box > 32.0:
                    ALARM_ACTIVE = True
                
        except OSError:
            print("Ошибка датчиков!")
            if CURRENT_MODE == "АВТО" and AUTO_MODE:
            	wind_offset = random.randint(-5, 2) / 10.0
            else:
                wind_offset = 0.0
        next_sensor_read = current_time + 10000 

    # --- БЛОК 2: НАДЁЖНЫЙ ОПРОС ФИЗИЧЕСКОЙ КНОПКИ ---
    btn_now = button.value() 
    
    if btn_now == 0 and not btn_was_pressed:
        btn_press_start = current_time
        btn_was_pressed = True
        oled.poweron() 
        screen_active_until = current_time + 10000 
        
    elif btn_now == 1 and btn_was_pressed:
        hold_time = current_time - btn_press_start
        btn_was_pressed = False
        
        if ALARM_ACTIVE:
            ALARM_ACTIVE = False  # Клик сбрасывает панику экрана
            
        elif CURRENT_MODE == "СУШКА":
            CURRENT_MODE = "АВТО" # Из сушки выходим коротким кликом
            screen_page = 2 
            
        elif CURRENT_MODE == "ПОЛИВ":
            CURRENT_MODE = "АВТО" # Клик отменяет полив
            
        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))
                
                #time.sleep(10)  Даем обдувам утихнуть 10 секунд и открыть дверь
                watering_end_time = time.ticks_ms() + 180000  # 3 минуты
                
        elif hold_time >= 4500:
            # ЗАЖАТИЕ 5 СЕКУНД — СУШКА (работает строго на странице 2)
            if screen_page == 2 and CURRENT_MODE == "АВТО": 
                CURRENT_MODE = "СУШКА"
                
        elif hold_time < 1500:
            if CURRENT_MODE != "ПОЛИВ": 
                screen_page = (screen_page + 1) % 3

    # --- БЛОК 3: УМНЫЕ СЦЕНАРИИ КУЛЕРОВ ---
    if CURRENT_MODE == "ПОЛИВ":
        pwm_extract.duty(volts_to_pwm(8.0))
        pwm_blow_up.duty(0)
        pwm_blow_down.duty(0)
        if current_time > watering_end_time:
            CURRENT_MODE = "АВТО" 
            
    elif CURRENT_MODE == "СУШКА":
        pwm_extract.duty(volts_to_pwm(7.0))
        pwm_blow_up.duty(0)
        pwm_blow_down.duty(0)
        
    elif CURRENT_MODE == "АВТО":
        if AUTO_MODE:
            if current_time > next_fan_adjust:
                
                if SENSOR_ERROR:
                    volts_extract, volts_blow_up, volts_blow_down = 8.0, 7.0, 7.0
                
                elif is_daytime:
                    # ДНЕВНЫЕ АВТО-СЦЕНАРИИ
                    if temp_box > LIMIT_HOT:
                        volts_extract, volts_blow_down, volts_blow_up = 9.0, 8.0, 9.0     
                    else:
                        if temp_box > LIMIT_WARM:
                            volts_extract = min(9.0, volts_extract + 0.2)  
                        elif temp_box < LIMIT_WARM:
                            volts_extract = max(7.5, volts_extract - 0.2)  

                        if temp_box >= LIMIT_WARM:
                            volts_blow_down, volts_blow_up = 7.5, 8.0 
                        else:
                            volts_blow_down, volts_blow_up = 6.5, 7.0 
                
                else:
                    # НОЧНЫЕ АВТО-СЦЕНАРИИ
                    if hum_box > 65.0:
                        volts_extract = min(8.0, volts_extract + 0.2) 
                        volts_blow_down, volts_blow_up = 5.0, 7.0                           
                    else:
                        volts_extract = max(7.5, volts_extract - 0.2) 
                        volts_blow_down, volts_blow_up = 6.0, 6.0                           
                
                next_fan_adjust = current_time + 180000 
                
        # --- ГЕНЕРАТОР ЖИВОГО ВЕТРА НА ВЕРХНЕМ КУЛЕРЕ ---
        if CURRENT_MODE == "АВТО" and AUTO_MODE:
            v_wind = volts_blow_up + (random.randint(-5, 2) / 10.0)
        else:
            v_wind = volts_blow_up 

        pwm_extract.duty(volts_to_pwm(volts_extract))
        pwm_blow_up.duty(volts_to_pwm(v_wind))
        pwm_blow_down.duty(volts_to_pwm(volts_blow_down))

    # === БЛОК 4: ОТРИСОВКА ИНТЕРФЕЙСА ЭКРАНА (Строго 128х32) ===
    if ALARM_ACTIVE:
        if current_time > blink_timer:
            blink_state = not blink_state
            blink_timer = current_time + 400
        if blink_state:
            oled.poweron()
            oled.fill(0)
            oled.text_rus("ЖАРА", 0, 0, 2)   
            oled.text_rus("ЖАРА", 80, 16, 2) 
            oled.show()
        else:
            oled.fill(0); oled.show()
            
    elif current_time < screen_active_until or CURRENT_MODE == "ПОЛИВ" or CURRENT_MODE == "СУШКА":
        oled.poweron()
        oled.fill(0)
        
        if 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, (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 = str(round(temp_box, 1))
            s_h = str(round(hum_box, 1)) + "%"
            x_h = 128 - (len(s_h) * 12)
            oled.text_rus(s_t, 0, 16, 2)   
            oled.text_rus(s_h, x_h, 16, 2) 
            
        else:
            if screen_page == 0:
                s_t = str(round(temp_box, 1))
                s_h = str(round(hum_box, 1)) + "%"
                x_h = 128 - (len(s_h) * 12)
                oled.text_rus(s_t, 0, 0, 2)
                oled.text_rus(s_h, x_h, 16, 2)
                
            elif screen_page == 1:
                oled.text_rus("{}С".format(round(temp_box, 1)), 0, 0)
                oled.text_rus("Выт:{:>4}".format(round(volts_extract, 1)), 80, 0) 
                oled.text_rus("{}%".format(round(hum_box, 1)), 0, 8)
                oled.text_rus("В:{:>6}".format(round(volts_blow_up, 1)), 80, 8)   
                oled.text_rus("ДЕНЬ" if is_daytime else "НОЧЬ", 0, 16)
                oled.text_rus("Н:{:>6}".format(round(volts_blow_down, 1)), 80, 16) 
                
                if SENSOR_ERROR: s_st = "СБОЙ"
                elif is_daytime and temp_box > LIMIT_WARM: s_st = "ЖАРА" 
                elif not is_daytime and hum_box > 65.0: s_st = "СЫРО"   
                else: s_st = "ОК" 
                oled.text_rus(s_st, 0, 24) 
                
                if rtxy.is_connected(): 
                    oled.text_rus("@", 54, 24)
                
                s_vac = "{} Па".format(int(max(0, vaccum_global)))
                x_vac = 128 - (len(s_vac) * 6) 
                oled.text_rus(s_vac, x_vac, 24)
            elif screen_page == 2:
                oled.text_rus("ВКЛ СУШКУ?", 32, 0) 
                oled.text_rus("ЗАЖМИ", 98, 16)    
                oled.text_rus("5 СЕК", 98, 24)    
                
        oled.show()
    else:
        oled.fill(0)
        oled.poweroff()

    time.sleep_ms(20)