Кибер-горшок и напоминалка полива
Идея поливать по расписанию у меня уже давно находится в планах. В практическом смысле, мне нужно было поливать газон только когда влажность почвы ниже нормы, а когда прошел дождь то поливать в ближайшее время не нужно. Прикручивал данные о погоде, по осадкам … оказалось не очень практично.
Полив домашних цветов тоже бы автоматизировать, хотя бы просто задачу в туду листе, если много цветов то и автополив можно сделать. Полив через ННН количество дней для всех не очень хорошо сработал. Как среднее по больнице, одним нужно больше вода, а другим меньше. Цветы то у меня разные, значит и потребности воды разные. Визуальный способ оценки мне не нравится, трогать каждый день землю тем более не хочу.
В этой статье Умный дом напомнит полить цветы я пробовал настроить напоминание, получилось достаточно простая автоматизация. Один из датчиков работал нормально, а другой явно врал и часто отваливался. Настроить частоту обновления я так и не смог, ее нет в видимых настройках ХА и в zigbee2mqtt
Увидел такой горшок https://aliclick.shop/r/c/1swb39y3yl67bqoe?erid=2SDnjcyp4gD / https://ya.cc/m/6othAi?erid=5jtCeReNx12oajvF3H1AVQiи решил сделать что-то похожее. Можно было конечно сделать прямо такой горшок, но я решил взять только самое необходимое для решения моей задачи.
А цветов у меня тем временем становится все больше, особенно мне нравятся ампельные растения вроде Сциндапсуса.
Задача - создать супер изделие.
Мне нужно получать информацию о влажности почвы от 2х горшков, выводить информацию на цветной микро дисплей. Сделать универсальный код, который будет работать для разных растений, то есть, создать пороги для полива. Например, одному растению нужен полив если влажность почвы будет меньше 40%, а для другого если меньше 60%.
Желательно, все это в минимальных размерах с питанием через USB (так как есть дисплей).
Задачу на полив сразу ставить в мой список дел, о котором писал тут https://dzen.ru/a/aBNDd3ZQwQathV1B.
На экране должна быть индикация о необходимости полива и конечно показания влажности. Сам экран должен быть маленький и работать только когда я рядом + автоматическая регулировка яркости.
Настоящая задача = потренироваться с логикой/железом/кодом + паяльником.
Интересно было, насколько можно использовать такие маленькие платы esp32 c3 в деле. Достаточно посмотреть размер
Пройти мимо цветного экрана маленького размера и с хорошим разрешением было сложно. Нужно было научиться загружать на экран фото, крутить изображение, выводить данные и подбирать шрифты, создавать анимацию, управлять подсветкой экрана - эти знания потом пригодятся для кастомных сенсоров с экраном или панелей сигнализации. Экран тоже мелкий
Анимация должна зависеть от показаний влажности почвы + установленного порога, быть адаптивной.
Проверить 2 типа датчиков влажности почвы - резистивный и емкостный. Чем отличаются и какой практичнее. Конечно подобрать правильный код и как их калибровать.
Научиться записывать данные на саму esp, например выставленные пороги, так как после перезагрузки все слетало.
В очередной раз потренироваться в создании сенсоров (влажно/сухо) на основе выставленного порога.
Забирая данные из esp я планировал создавать задачи на полив, а потом удалять после полива. Использовать интеграцию Adaptive lightning для уменьшения яркости экрана в темное время суток - очень полезная функция, особенно если будут использованы экраны в спальне.
Логика работы записанная на саму esp позволяет не зависить от автоматизаций HA, то есть, даже есть отключить от роутера плату - изображения будут меняться и показания выводиться на экран. В процессе создания такого “горшка” я вспомнил про автоматизацию сауны, логика которой лежит как раз на сервере - что не очень безопасно/практично.
Ну и конечно - дать себе повод купить барахла для пайки и под спокойную музыку с дождем за окном почилить.
Железо
В качестве основного мозга взял esp32 c3 так как хотел проверить на что его хватит, спойлер - ещё остались свободные пины и память. Не особо в них разбираясь, я взял сначала обычные, те у которых нет подключения внешней антенны. Позже прочитал что у них проблемы с сигналом, так что нужно брать с возможностью подключить внешнюю антенну + сами антенны. Точнее называется ESP32-C3 PRO mini.
Али https://aliclick.shop/r/c/1sw9mrh4gl1cnwbd?erid=2SDnjc2vqmP
Резистивный датчик в влажности я брал ещё давно на тест. Уже после непродолжительного использования он окислился на контактах. Зачем делать такой датчик который выйдет из строя очень быстро я не знаю, но попробовать стоило. В тестах оказалось что он ещё и тупит. Да и сам датчик состоит из 2 частей: сниматель показателей и отдельно “переходник”. Можно получать как аналоговые данные, так и вкл/выкл где на переходнике есть настройка. Оставлю ссылки на него, но брать не советую.
Али https://aliclick.shop/r/c/1swb8tn6q05fqdhy?erid=2SDnjepgu3T
Емкостный датчик уже и выглядит круче, “переходник” сразу установлен на пробе и для подключения уже есть кабель. Сам датчик покрыт краской и внушает доверия. Реагирует достаточно быстро. У меня версия 1.2, но уже есть версия 2.0
Ям https://ya.cc/m/6mmdVf?erid=5jtCeReNx12oajvEZJ5QMR2
Али https://aliclick.shop/r/c/1swb8xkcwt7futpc?erid=2SDnjcYkLSh
Дисплей 80х160 IPS 0.96” цветной за 100 рублей!!! почти 1$ Карл!!! Да это не экран смартфона от самсы, но я был удивлен, что можно загрузить фотографию на такой дисплей, это в дополнение к данным которые можно передать на экран. Да пришлось повозиться, подобрать смещение по пикселям так как установленный на экране чип видимо рассчитан на другое разрешение. В документации Esphome все это указано и есть метод указать смещение. Проводов конечно навалом, такой шлейф сложно спрятать.
Ям https://ya.cc/m/6oYs8v?erid=5jtCeReNx12oajvF3GuEscN
Али https://aliclick.shop/r/c/1sw98vbmk08so7a0?erid=2SDnjezfJEH
Стоит все это смешных денег по сравнению с готовыми решениями. Понятно дело что не так красиво, понятно что нужен паяльник и время на все это - но то что это работает и работает хорошо это факт. Создав концепт можно и уже заказать изделия на заводе в промышленных масштабах.
Пинаут тебе паяльником.
Вот наша ESP32‑C3 - питание по usb даст нам питание для датчиков и экрана, запитывать все будем от 3.3В и GND.
С датчками все просто - один идет к 0 (A0), а второй к 1 (A1).
У экрана целая пачка проводов. Соединяем так
- SCL (Serial Clock Line) – обеспечивает тактовый сигнал для синхронной передачи данных. В SPI-интерфейсе он отвечает за синхронизацию посылки битов.
- SDA (Serial Data Line) – линия для передачи данных. В SPI этот пин выполняет функцию передачи данных (часто обозначается как MOSI, то есть Master-Out Slave-In).
- RES (Reset) – сброс дисплея. Под действием этого сигнала контроллер экрана перезагружается и переходит в исходное состояние, что нужно для стабильной инициализации устройства.
- DC (Data/Command) – переключатель, который указывает, передаются ли текущие биты как данные для отображения или как команды для управления работой экрана.
- CS (Chip Select) – выбирает нужное устройство на шине (при использовании нескольких периферийных устройств). Этот пин сообщает дисплею, что он должен «прослушивать» шину и принимать посылаемые команды.
- BLK (Backlight) – отвечает за управление подсветкой экрана. С его помощью можно включать или регулировать яркость подсветки, что полезно для экономии энергии или адаптации к условиям освещённости.
Питание идет через usb type C. Правильный припой уже после заказал, спасибо за комментарии!
Для тестов подходит отлично быстрые соединения и бредборд, но оставлять так точно нельзя - сами провода через некоторое время разбалтываются и контакт очень плохой. Я пока в поисках более оптимального решения для подключения проводов и желательно без паяльника.
К экрану и одному из датчиков уже впаяны ножки, поэтому пришлось зафиксировать изолентой. Остальное запаял … пока как получилось. Провода зачистил и соединил скруткой.
Автоматизации и код
Датчики
У датчиков достаточно простой код, но лучше при настройке их откалибровать. Замерить показания на сухую и потом опустить в воду. В логах будет видно какие показания напряжения при сухом и влажном состоянии.
sensor: - platform: adc pin: 0 name: "Влажность почвы левый" icon: "mdi:water-percent" id: soil_moisture_1 unit_of_measurement: "%" accuracy_decimals: 0 update_interval: 6s attenuation: 12db filters: - calibrate_linear: - 1.06 -> 100.0 - 2.90 -> 0.0 - lambda: return esphome::clamp(x, 0.0f, 100.0f);
sensor: - platform: adc pin: 0 name: "Влажность почвы левый" icon: "mdi:water-percent" id: soil_moisture_1 unit_of_measurement: "%" accuracy_decimals: 0 update_interval: 6s attenuation: 12db filters: - calibrate_linear: - 1.06 -> 100.0 - 2.90 -> 0.0 - lambda: return esphome::clamp(x, 0.0f, 100.0f);
platform: adc
Определяет, что сенсор работает через аналогово-цифровой преобразователь (ADC). То есть ESPHome будет считывать аналоговое напряжение с датчика.pin: 0
Указывает, что для считывания сигнала используется пин 0 микроконтроллера. Соединяя датчик с этим пином, можно получать его аналоговое значение.accuracy_decimals: 0
Задает формат вывода результата: значение округляется до целого числа без десятичных знаков.update_interval: 6s
Обновление считывания датчика происходит каждые 6 секунд. То есть новые данные будут запрашиваться с такой периодичностью.attenuation: 12db
Это настройка ослабления (аттенюации) входного сигнала, которая используется для расширения допустимого диапазона измеряемых напряжений. При 12 дБ можно измерять более высокие напряжения, чем при минимальной настройке, что полезно для корректного считывания датчиков с выходными сигналами выше стандартного диапазона ADC.- Фильтры:
calibrate_linear:
Этот фильтр проводит линейную калибровку значений. Две заданные точки означают следующее: Таким образом, между этими точками происходит линейная интерполяция, позволяющая корректно преобразовать сырые аналоговые значения в нужный диапазон (от 0 до 100).- Значение 1.06 с датчика интерпретируется как 100% (например, 100% влажности).
- Значение 2.90 соответствует 0%.
lambda: return esphome::clamp(x, 0.0f, 100.0f);
Этот фильтр гарантирует, что итоговое значение не выйдет за пределы от 0 до 100. Функцияclamp
ограничивает переменнуюx
заданными минимумом и максимумом, что важно для защиты от выбросов или шума в измерениях.
Экран и некоторые мучения.
До того как правильно настроил экран у меня получались разные результаты.
В итоге все нормально настроил и понял принцип, можно тут посмотреть разные варианты если будет интересно.
Сначала нужно настроить правильное отображение экрана. Насколько я раскопал, есть программный поворот и аппаратный. На программный много нареканий и у меня так нормально не получилось настроить, поэтому будем использовать аппаратное. Больше примеров с использованием функции https://esphome.io/components/display/index.html
Базовая настройка c с загрузкой фотографии, после кода пояснения:
# Настройка SPI для дисплея: spi: clk_pin: GPIO4 # Назначаем пин для тактового сигнала SPI (CLK) – GPIO4 mosi_pin: GPIO6 # Назначаем пин для передачи данных SPI (MOSI) – GPIO6 # Загрузка и обработка изображения: image: - file: "test544.jpg" # Путь к файлу изображения, относительно папки проекта id: my_image # Уникальный идентификатор для обращения к этому изображению в коде resize: 160x80 # Изменяет размеры изображения до 160×80 пикселей для соответствия дисплею type: RGB565 # Формат цвета изображения; для дисплеев ST7735 часто используется RGB565 # Настройка дисплея с использованием драйвера ili9xxx для модели ST7735: display: - platform: ili9xxx # Используем драйвер ili9xxx для работы с дисплеем model: "ST7735" # Указываем модель дисплея, в данном случае ST7735 dimensions: # Определяем размеры области дисплея и смещения элементов width: 160 # Ширина дисплея – 160 пикселей height: 80 # Высота дисплея – 80 пикселей offset_width: 0 # Горизонтальное смещение (не используется) offset_height: 24 # Вертикальное смещение – 24 пикселя (например, с учётом рамки) cs_pin: GPIO7 # Пин Chip Select (выбор чипа) – GPIO7 dc_pin: GPIO3 # Пин Data/Command для переключения между данными и командами – GPIO3 reset_pin: GPIO10 # Пин сброса дисплея – GPIO10 invert_colors: false # Не инвертировать цвета (оставляем стандартную палитру) color_order: bgr # Порядок байтов цвета – синий, зеленый, красный (bgr) show_test_card: false # Не показывать тестовую карточку при старте дисплея transform: # Применяем преобразования координат изображения: swap_xy: true # Меняем оси X и Y местами (поворот изображения) mirror_x: true # Отражаем изображение по горизонтали mirror_y: false # Отражение по вертикали не применяется update_interval: 1s # Интервал обновления дисплея – 1 секунда lambda: |- // Вычисляем координаты центра экрана int center_x = (it.get_width() - id(my_image).get_width()) / 2; int center_y = (it.get_height() - id(my_image).get_height()) / 2; // Заливаем экран белым цветом it.fill(Color(255, 255, 255)); // Отображаем изображение, центрируя его на экране it.image(center_x, center_y, id(my_image)); # Настройка вывода для подсветки дисплея через LEDC: output: - platform: ledc # Используем аппаратный драйвер LEDC для ШИМ управления pin: GPIO5 # Пин, к которому подключена подсветка дисплея – GPIO5 id: backlight_pwm # Идентификатор данного выхода для дальнейшего использования # Настройка светодиодного устройства для управления подсветкой дисплея: light: - platform: monochromatic # Одноцветное освещение (подсветка) name: "Display Backlight" # Имя устройства, отображаемое в Home Assistant output: backlight_pwm # Указываем выход, ранее заданный для ШИМ подсветки restore_mode: RESTORE_DEFAULT_ON # Состояние после рестарта – включено по умолчанию # Загрузка шрифта для вывода текста на дисплее: font: - file: "gfonts://Roboto" # Загружаем шрифт Roboto из репозитория Google Fonts id: small_font # Идентификатор шрифта для обращения в коде дисплея size: 12 # Размер шрифта – 12 точек
Пины можно не менять если было подключение как описано ранее.
Вставка изображений происходит из папки esphome в HA. Если изображений будет не много тогда можно в корень esphome закинуть. Если планируется загружать много, тогда можно отдельную папку сделать images/ . Тогда в - file: "test544.jpg" будет - file: "images/test544.jpg" . ОЧЕНЬ ВАЖНО!!! Хоть мы и высталвяем размер изображения для ресайза, но система будет смотреть на соотношение сторон, если соотношение не соответствует - тогда изображение будет в центре, а по бокам могут быть полоски. У нас экран 160х80 = 2:1 такое соотношение нужно сделать.
Шрифт я в данном примере использую с загрузкой из гугла, но можено скачать и закинуть так же как и с изображением.
# Загрузка и обработка изображения: image: - file: "test544.jpg" # Путь к файлу изображения, относительно папки проекта id: my_image # Уникальный идентификатор для обращения к этому изображению в коде resize: 160x80 # Изменяет размеры изображения до 160×80 пикселей для соответствия дисплею type: RGB565 # Формат цвета изображения; для дисплеев ST7735 часто используется RGB565 # Загрузка шрифта для вывода текста на дисплее: font: - file: "gfonts://Roboto" # Загружаем шрифт Roboto из репозитория Google Fonts id: small_font # Идентификатор шрифта для обращения в коде дисплея size: 12 # Размер шрифта – 12 точек
Самое сложное было правильно настроить отображение и поворот экрана.
В горизонтальном положении у нас так:
image: resize: 160x80 # Изменяет размеры изображения до 160×80 пикселей для соответствия дисплею display: dimensions: # Определяем размеры области дисплея и смещения элементов width: 160 # Ширина дисплея – 160 пикселей height: 80 # Высота дисплея – 80 пикселей offset_width: 0 # Горизонтальное смещение (не используется) offset_height: 24 # Вертикальное смещение – 24 пикселя (например, с учётом рамки) transform: # Применяем преобразования координат изображения: swap_xy: true # Меняем оси X и Y местами (поворот изображения) mirror_x: true # Отражаем изображение по горизонтали mirror_y: false # Отражение по вертикали не применяется
А вот в вертикальном мы меняем ресайз изображения, ширину и высоту и смещение (offset_) местами + ставим в transform: wap_xy: false
image: resize: 80x160 # Изменяет (масштабирует) исходное изображение до разрешения 80x160 пикселей для корректного отображения на экране display: dimensions: # Настраивает активную область дисплея – ту часть, в которой выводится контент width: 80 # Активная ширина дисплея составляет 80 пикселей height: 160 # Активная высота дисплея составляет 160 пикселей offset_width: 24 # Горизонтальное смещение области: сдвигает область вправо на 24 пикселя offset_height: 0 # Вертикальное смещение области: не сдвигает область по вертикали (0 пикселей) transform: # Применяет геометрические преобразования для коррекции ориентации изображения swap_xy: true # Меняет местами оси X и Y, что эквивалентно повороту изображения на 90 градусов mirror_x: true # Отражает изображение по горизонтали (зеркальное отображение по оси X) mirror_y: false # Отражение по вертикали не применяется (изображение остаётся как есть по оси Y)
С параметрами mirror_x: и mirror_y: нужно поэксперементировать как удобнее отображать.
Настройка цветов и тестовая карточка
- invert_colors:
false
– цвета выводятся в нормальном виде. Если сменить наtrue
, получится инверсия (например, белое станет чёрным). - color_order:
bgr
– используется порядок: синий, зеленый, красный. Альтернатива –rgb
для порядка: красный, зеленый, синий. - show_test_card:
false
– тестовая карточка не отображается при запуске. Если включить, можно проверить подключение дисплея в режиме отладки.
update_interval: 1s
означает, что функция, заданная в блоке lambda
дисплея, перерисовывает содержимое экрана каждую секунду. Например, если я отображаю реальные данные с датчиков, такая частота обновления позволяет оперативно фиксировать изменения. Для статичных изображений можно увеличить интервал до 5–10 секунд для экономии ресурсов, а для анимаций или быстродинамичных элементов лучше использовать интервал 100–200 мс для более плавного перехода.
Как работает блок lambda
Код в блоке lambda выполняется каждый раз при обновлении дисплея. Этот блок — по сути, функция, в которой команды выполняются последовательно сверху вниз:
- Вычисление координат центра:
int center_x = (it.get_width() - id(my_image).get_width()) / 2; int center_y = (it.get_height() - id(my_image).get_height()) / 2;
Здесь определяется позиция так, чтобы изображение оказалось по центру экрана. Сначала берётся разница между размерами дисплея и изображения, затем делится пополам, что даёт смещение от края. - Заполнение экрана:
it.fill(Color(255, 255, 255));
Эта команда заливает всю активную область дисплея белым цветом. Важно делать заливку перед отрисовкой элементов, чтобы фон не перекрывал их. - Отображение изображения:
it.image(center_x, center_y, id(my_image));
Затем изображение выводится в позицию с координатами, вычисленными ранее, что центрирует его на экране.
Порядок команд критически важен: если, например, заливку выполнить после отрисовки изображения, то изображение окажется скрыто фоном.
Пример с "Hello world"
Ниже приведён пример блока lambda
, который выводит текст "Hello world" по центру дисплея. Код последовательно выполняет следующие шаги:
- Вычисление размеров текста: Определяется ширина текста с помощью функции
it.get_text_width(...)
и высота (обычно равна высоте строки шрифта). - Вычисление координат центра: Координаты (center_x, center_y) рассчитываются как разница между размерами дисплея и размерами текста, делённая на два. Это позволяет разместить текст по центру.
- Заливка экрана и вывод текста: Экран заливается фоновым цветом, затем текст выводится в рассчитанном положении.
lambda: |- // Определяем размеры текста "Hello world" int text_width = it.get_text_width(id(small_font), "Hello world"); int text_height = id(small_font).get_line_height(); // Вычисляем координаты для центрирования текста на экране int center_x = (it.get_width() - text_width) / 2; int center_y = (it.get_height() - text_height) / 2; // Заливаем экран черным цветом (фон) it.fill(Color(0, 0, 0)); // Выводим текст "Hello world" в центре экрана, белым цветом it.print(center_x, center_y, id(small_font), Color(255, 255, 255), "Hello world");
Эта логика демонстрирует, как последовательно: вычислить размеры текста, определить позицию по центру, обновить фон и затем вывести текст в нужном месте.
Остальные сенсоры для экрана
# Настройка вывода для подсветки дисплея через LEDC: output: - platform: ledc # Используем аппаратный контроллер LEDC, который генерирует ШИМ-сигнал pin: GPIO5 # Подключаем подсветку дисплея к пину GPIO5 id: backlight_pwm # Присваиваем идентификатор для ссылок на этот выход # Настройка светодиодного устройства для управления подсветкой дисплея: light: - platform: monochromatic # Определяем одноцветное устройство для управления подсветкой name: "Display Backlight" # Имя, которое будет отображаться в Home Assistant output: backlight_pwm # Связываем устройство с ранее настроенным ШИМ-выходом restore_mode: RESTORE_DEFAULT_ON # После перезагрузки подсветка включается по умолчанию
Этот код создаёт аппаратный ШИМ-выход для управления яркостью подсветки дисплея: контроллер LEDC через пин GPIO5 генерирует ШИМ-сигнал, а виртуальное световое устройство с типом "monochromatic" привязывается к этому сигналу, позволяя управлять яркостью из Home Assistant. Благодаря параметру restore_mode: RESTORE_DEFAULT_ON
, после перезагрузки микроконтроллера подсветка автоматически возвращается в рабочее состояние.
Как можно сделать по-другому? Например, можно изменить значение restore_mode
на RESTORE_DEFAULT_OFF
, если требуется, чтобы подсветка оставалась выключенной после рестарта, или использовать несколько ШИМ-выходов для управления разноцветными светодиодами (RGB) вместо одноцветной подсветки. В реальной жизни это похоже на настройку умного освещения в доме: можно задать мягкий ночной режим для чтения без ослепления или яркий дневной режим для работы, а также обеспечить автоматический запуск выбранного режима при включении системы.
Сенсоры, пороги и сохранение данных
Сенсоры добавили и экран настроили - теперь нам нужно создать порог после которого система будет показывать что нужен полив. Порог нужен что бы не править код постоянно если будут ещё датчики + поможет настроить бинарный сенсор сухо/влажно, который уже можно использовать в втоматизациях в HA. Как обычно, в код можно подставлять свои значения.
# Глобальные переменные для хранения пороговых значений влажности. # Эти переменные сохраняют своё значение между перезагрузками устройства. globals: - id: threshold_l # Идентификатор для левого порогового значения влажности restore_value: true # Значение будет восстановлено после перезагрузки устройства type: float # Тип данных – число с плавающей запятой initial_value: "30.0" # Начальное значение установлено равным 30.0 - id: threshold_r # Идентификатор для правого порогового значения влажности restore_value: true # Сохраняется между перезагрузками type: float # Тип данных – число с плавающей запятой initial_value: "30.0" # Начальное значение – 30.0 # Компоненты типа "number", позволяющие через Home Assistant изменять пороговые значения. # Значения отображаются и редактируются в интерфейсе, а изменения сохраняются в глобальных переменных. number: - platform: template name: "Порог левый" # Имя компонента, отображаемое в пользовательском интерфейсе icon: "mdi:water-percent" # Иконка для визуального представления (процент воды) id: threshold_l_number # Локальный идентификатор для компонента lambda: |- return id(threshold_l); # При запросе возвращается текущее значение переменной threshold_l set_action: lambda: |- id(threshold_l) = x; # При изменении значения через интерфейс, сохраняем новое значение в threshold_l update_interval: 10s # Интервал обновления компонента – каждые 10 секунд min_value: 0.0 # Минимальное допустимое значение – 0% max_value: 100.0 # Максимальное допустимое значение – 100% step: 1.0 # Шаг изменения значения – 1% - platform: template name: "Порог правый" # Аналогичный компонент для правого порога влажности icon: "mdi:water-percent" id: threshold_r_number lambda: |- return id(threshold_r); # Возвращаем текущее значение переменной threshold_r set_action: lambda: |- id(threshold_r) = x; # Сохраняем новое значение в threshold_r при изменении через UI update_interval: 10s min_value: 0.0 max_value: 100.0 step: 1.0 # Определение датчиков влажности почвы с использованием ADC (аналогово-цифровых преобразователей) sensor: - platform: adc pin: 0 # Используется пин 0 для левого датчика name: "Влажность почвы левый" # Имя датчика для отображения показаний icon: "mdi:water-percent" id: soil_moisture_1 # Идентификатор датчика, к которому можно обратиться в коде unit_of_measurement: "%" # Единица измерения – проценты accuracy_decimals: 0 # Отображение показаний без десятичных знаков update_interval: 6s # Обновление показаний датчика каждые 6 секунд attenuation: 12db # Настройка аттенюации для расширения диапазона измеряемого сигнала filters: - calibrate_linear: # Линейная калибровка показаний ADC в проценты - 1.06 -> 100.0 # Если на пине 1.06 В – это соответствует 100% влажности - 2.90 -> 0.0 # Если на пине 2.90 В – это соответствует 0% влажности - lambda: return esphome::clamp(x, 0.0f, 100.0f); # Ограничиваем итоговое значение диапазоном 0–100% - platform: adc pin: 1 # Используется пин 1 для правого датчика name: "Влажность почвы правый" icon: "mdi:water-percent" id: soil_moisture_2 unit_of_measurement: "%" accuracy_decimals: 0 update_interval: 6s attenuation: 12db filters: - calibrate_linear: - 0.885 -> 100.0 # При 0.885 В – датчик показывает 100% влажности - 2.21 -> 0.0 # При 2.21 В – датчик показывает 0% влажности - lambda: return esphome::clamp(x, 0.0f, 100.0f); # Гарантируем, что значение остаётся в допустимом диапазоне # Бинарные датчики, которые определяют, достаточно ли влажна почва, сравнивая измеренное значение с заданным порогом. binary_sensor: - platform: template name: "Состояние влажности левый" # Имя датчика, показывающее состояние левого горшка icon: "mdi:water" # Иконка, обозначающая уровень воды id: plant_l_moisture_state # Идентификатор датчика lambda: |- return id(soil_moisture_1).state >= id(threshold_l); # Сравнение: если измеренное значение soil_moisture_1 больше или равно заданному порогу threshold_l, вернется true device_class: moisture # Определяет класс датчика для корректного отображения в Home Assistant - platform: template name: "Состояние влажности правый" # Датчик для правой части, определяющий состояние влажности icon: "mdi:water" id: plant_r_moisture_state lambda: |- return id(soil_moisture_2).state >= id(threshold_r); # Если показания soil_moisture_2 превышают или равны пороговому значению threshold_r, датчик возвращает true device_class: moisture
Глобальные переменные threshold_l
и threshold_r
задают пороги влажности (начальное значение 30.0) и сохраняются между перезагрузками – как если бы вы установили и запомнили температуру на термостате.
Компоненты number
в Home Assistant отображают текущее значение этих переменных (с помощью lambda: return id(threshold_l);
) и позволяют изменять его через интерфейс (set_action
присваивает новое значение x). Таким образом, вы можете легко настроить порог влажности.
ADC-датчики считывают напряжение с пинов 0 и 1, переводят его в процентное соотношение (линейная калибровка и функция clamp
) – как измерительный прибор, показывающий уровень воды по шкале. Полученное значение сравнивается бинарными датчиками с порогом: если измерение ≥ заданного порога, датчик возвращает true
, сигнализируя, что почва влажная. Если, например, левый датчик показывает 35% при пороге 30%, система определяет, что почва достаточно влажная.
В Home Assistant для нас есть данные.
Так как весь дом усыпан различными датчиками и свет регулируется в зависимости от времени суток - мы можем использовать эти данные для нашего дисплея.
Есть интеграция, которая по bluetooth отслеживает местонахождение телефона/часов/брелока в доме, указывая при этом точную комнату. https://github.com/agittins/bermuda Пока тестирую и работает более-менее стабильно, но у меня всего несколько зон подключено и поэтому часто нахожусь на кухне. Если разбросать по дому esp и сделать bluetooth proxy + проставить комнаты то будет работать лучше, про бермуду сделаю статью как полностью проверю.
При появлении умных часов или телефона в зоне кухня надо включать дисплей. Автоматизация достаточно простая, важно использовать все триггеры, а условия уже проверять в действиях - появление одного из устройств включает экран, а выход из зоны ОБОИХ устройств его гасит + задержки так как бермуда переодически теряет связь.
alias: Подсветка экрана цветов description: >- Экран включается, если хотя бы один датчик (телефон или часы) в кухне, и выключается, если оба вне кухни (с задержкой) triggers: - entity_id: - sensor.egor_mobile_area - sensor.xiaomi_watch_s1_5509_area trigger: state for: hours: 0 minutes: 0 seconds: 5 conditions: [] actions: - choose: - conditions: - condition: template value_template: > {{ is_state('sensor.egor_mobile_area', 'Кухня') or is_state('sensor.xiaomi_watch_s1_5509_area', 'Кухня') }} sequence: - target: entity_id: light.plant_sensor_display_backlight action: light.turn_on data: {} - conditions: - condition: template value_template: > {{ not is_state('sensor.egor_mobile_area', 'Кухня') and not is_state('sensor.xiaomi_watch_s1_5509_area', 'Кухня') }} sequence: - delay: hours: 0 minutes: 0 seconds: 15 milliseconds: 0 - condition: template value_template: > {{ not (is_state('sensor.egor_mobile_area', 'Кухня') or is_state('sensor.xiaomi_watch_s1_5509_area', 'Кухня')) }} - target: entity_id: light.plant_sensor_display_backlight action: light.turn_off data: {} mode: restart
Да, лучше и проще ещё добавить датчик движения в саму ESP, но это мне пришло в голову только после того как все повесил. Да и нужно мне проверить эту бермуду.
За регулировку яркости в доме у меня отвечает интеграция https://github.com/basnijholt/adaptive-lighting Если кратко - меняет температуру и яркость в зависимости от времени суток. Так как большая часть ламп у меня умные и могут в изменение температуры/яркости, поэтому весь дом адаптирует освещение. В интеграции можно выделить настройки и перечислить устройства, для экранов (если их будет много) выделяю отдельную настройку что бы минимальная яркость была 15%. Если сделать 1% экран вообще гаснет.
В дальнейшем буду добавлять все новые экраны в эту интеграцию и конкретно в единую настройку.
Автоматизация добавляющая задачу полить цветы в todoist. (Больше информации тут https://dzen.ru/a/aBNDd3ZQwQathV1B)
В качестве триггера у нас изменение порогов и показаний датчиков с небольшой задержкой.
Автоматизация проверяет список дел, если показания датчиков ниже порога = ставит задачу Полить цветок, а если выше порога = задача удаляется.
Задача ставится на завтра, но всегда можно поменять.
Если влажность меняется и стоит задача полить = задача обновляет описание с текущим значением влажности, что автоматически попадает в todoist
alias: "Автоматизация задачи полива цветка" description: > Создаёт задачу в списке домашних дел, если влажность почвы падает ниже порога. Удаляет задачу, если влажность превышает пороговое значение + запас. trigger: # Изменение влажности почвы (с фильтром на 5 секунд) - platform: state entity_id: sensor.plant_sensor for: "00:00:05" - platform: state entity_id: sensor.plant_sensor_2 for: "00:00:05" # Изменение пороговых значений влажности - platform: state entity_id: number.plant_sensor for: "00:00:05" - platform: state entity_id: number.plant_sensor_2 for: "00:00:05" condition: [] action: # Получаем текущие задачи из домашнего списка дел - service: todo.get_items target: entity_id: todo.domashnie_dela data: status: - needs_action response_variable: existing_tasks # Проверяем необходимость задачи для ЛЕВОГО цветка - choose: # A. Если влажность ниже порога (нужен полив), создаём задачу на завтра - conditions: - condition: template value_template: "{{ states('sensor.plant_sensor') | float < states('number.plant_sensor') | float }}" - condition: template value_template: > {% set tasks = existing_tasks['todo.domashnie_dela']['items'] %} {% set headings = tasks | map(attribute='summary') | list %} {{ "💧 Полить цветок Левый" not in headings }} sequence: - service: todo.add_item target: entity_id: todo.domashnie_dela data: item: "💧 Полить цветок Левый" description: "Текущая влажность: {{ states('sensor.plant_sensor') }}%" due_date: "{{ (now().date() + timedelta(days=1)).isoformat() }}" # B. Если задача уже существует и влажность изменилась – обновляем её описание - conditions: - condition: template value_template: "{{ states('sensor.plant_sensor') | float < states('number.plant_sensor') | float }}" - condition: template value_template: > {% set tasks = existing_tasks['todo.domashnie_dela']['items'] %} {% set headings = tasks | map(attribute='summary') | list %} {{ "💧 Полить цветок Левый" in headings }} sequence: - service: todo.update_item target: entity_id: todo.domashnie_dela data: item: "💧 Полить цветок Левый" description: "Текущая влажность: {{ states('sensor.plant_sensor') }}%" # C. Если влажность выше порога +5% (цветок полит) – удаляем задачу - conditions: - condition: template value_template: "{{ states('sensor.plant_sensor') | float > states('number.plant_sensor') | float + 5 }}" - condition: template value_template: > {% set tasks = existing_tasks['todo.domashnie_dela']['items'] %} {% set headings = tasks | map(attribute='summary') | list %} {{ "💧 Полить цветок Левый" in headings }} sequence: - service: todo.remove_item target: entity_id: todo.domashnie_dela data: item: "💧 Полить цветок Левый" # Проверяем необходимость задачи для ПРАВОГО цветка - choose: # A. Если влажность ниже порога (нужен полив), создаём задачу на завтра - conditions: - condition: template value_template: "{{ states('sensor.plant_sensor_2') | float < states('number.plant_sensor_2') | float }}" - condition: template value_template: > {% set tasks = existing_tasks['todo.domashnie_dela']['items'] %} {% set headings = tasks | map(attribute='summary') | list %} {{ "💧 Полить цветок Правый" not in headings }} sequence: - service: todo.add_item target: entity_id: todo.domashnie_dela data: item: "💧 Полить цветок Правый" description: "Текущая влажность: {{ states('sensor.plant_sensor_2') }}%" due_date: "{{ (now().date() + timedelta(days=1)).isoformat() }}" # B. Если задача уже существует и влажность изменилась – обновляем её описание - conditions: - condition: template value_template: "{{ states('sensor.plant_sensor_2') | float < states('number.plant_sensor_2') | float }}" - condition: template value_template: > {% set tasks = existing_tasks['todo.domashnie_dela']['items'] %} {% set headings = tasks | map(attribute='summary') | list %} {{ "💧 Полить цветок Правый" in headings }} sequence: - service: todo.update_item target: entity_id: todo.domashnie_dela data: item: "💧 Полить цветок Правый" description: "Текущая влажность: {{ states('sensor.plant_sensor_2') }}%" # C. Если влажность выше порога +5% (цветок полит) – удаляем задачу - conditions: - condition: template value_template: "{{ states('sensor.plant_sensor_2') | float > states('number.plant_sensor_2') | float + 5 }}" - condition: template value_template: > {% set tasks = existing_tasks['todo.domashnie_dela']['items'] %} {% set headings = tasks | map(attribute='summary') | list %} {{ "💧 Полить цветок Правый" in headings }} sequence: - service: todo.remove_item target: entity_id: todo.domashnie_dela data: item: "💧 Полить цветок Правый" mode: restart
Прошло несколько дней и резистивный датчик полон оптимизма на 146%, в то время как емкостный приуныл до 85% процентов. Как и ожидалось - резистивный не проходит проверку на прочность, поэтому заказал ещё нормальных. Их уже буду сажать на батарейки без экрана в эконом режиме, с опросом датчика 2-4 раза в день - как раз посмотрю как долго будет держать батарейки.
Адаптивный экран работает супер, в темное время суток не привлекает внимание и включается только когда я нахожусь в зоне кухня… совсем немного подглючивает.
И дело за корпусами, я уже начал присматриваться к 3д принтеру.
На esp32 c3 остались свободные пины под TX и RX, а это значит что можно и подключать modbus - для любителей проводов. Убрав экран можно дополнительно повесить в общей сложности 5 датчиков аналоговых, а на другие пины повесить реле с запуском насоса или открытие кранов. Все это на маленькой платке.
Запись в глобальные работает хорошо, после перезагрузки все в строю - это критично для различных счетчиков. В любой момент можно забрать данные из esp.
Резистивный датчик помер, показывает ерунду.
Синим - это резистивный датчик, который потребовал полив и потом совсем отказал.
Желтый - это емкостный датчик и как видно он показывает стабильные показания.
Жду на замену еще емкостные датчики.
UPD. Замена пришла, подробнее тут https://teletype.in/@godisblind/0brtVQKJUMd
А так, конечно нужно быть проще
#сделайсам #обучение #настройка #планирование #esp32
Способ 1 Поддержать автора
Способ 2 https://donate.stream/yoomoney410013774736621
или через криптокошелёк (Только USDT) TCHekdJZFndXpDrHZGuTmqFNcqhWBTTzPr
Связаться со мной. (Консультации, проектирование и обучение)
Новый подход к электрике и дизайну помещений. Некоторые провода уже не нужно тянуть, какие-то решения можно принять после ремонта. Перенести выключатель, запустить кондиционер с телефона - возможно автоматизировать любую рутину.
Мой телеграм канал, там все быстрее обновляется телеграм.
Сайт smart4home.ru и альтернативный Умный дом на любом этапе
Соц сети: RuTube канал Удобный дом / You Tube канал Удобный дом Яндекс Дзен: Удобный дом / InGram
Платформы специалистов: Авито / Профи.ру / Яндекс Услуги https://uslugi.yandex.ru/profile/EgorSmirnov-2294380?from=telek
Реклама: ООО "АЛИБАБА.КОМ (РУ)" ИНН: 7703380158 Реклама. ООО «ЯНДЕКС», ИНН 7736207543