Загрузка данных
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)
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 секунд, пока утихнет ветер и откроется дверь
time.sleep(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("Ошибка датчиков!")
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)