Загрузка данных
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) # Небольшая пауза для разгрузки процессора