<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on Малая автоматизация</title><link>https://smallautomation.iotex.biz/posts/</link><description>Recent content in Posts on Малая автоматизация</description><generator>Hugo -- gohugo.io</generator><language>ru-ru</language><lastBuildDate>Wed, 11 Nov 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://smallautomation.iotex.biz/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>Отвяжи свой термостат. Свой — на ESP32 и без облака</title><link>https://smallautomation.iotex.biz/posts/2026-11-11-thermostat/</link><pubDate>Wed, 11 Nov 2026 00:00:00 +0000</pubDate><guid>https://smallautomation.iotex.biz/posts/2026-11-11-thermostat/</guid><description>&lt;img src="https://smallautomation.iotex.biz/images/covers/thermostat.png" alt="Featured image of post Отвяжи свой термостат. Свой — на ESP32 и без облака" /&gt;&lt;p&gt;Умный термостат у большинства производителей — это не устройство, а &lt;strong&gt;абонемент&lt;/strong&gt;: железо ты купил один раз, а доступ к облаку, расписанию и приложению продают помесячно. Смена цены, отключение сервиса, обновление, которое ломает интеграцию — и термостат, который работал пять лет, превращается в кирпич с дисплеем.&lt;/p&gt;&#10;&lt;p&gt;Функция, которая нужна по сути, простая: &lt;strong&gt;держать температуру и дать мне ей управлять&lt;/strong&gt;. Ничего больше.&lt;/p&gt;&#10;&lt;h2 id="что-нужно-по-сути"&gt;Что нужно по сути&#10;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;ESP32&lt;/strong&gt; — контроллер с Wi-Fi. &lt;strong&gt;Реле&lt;/strong&gt; — чтобы включать и выключать нагрузку. &lt;strong&gt;Датчик температуры&lt;/strong&gt; — чтобы знать, что происходит. Всё.&lt;/p&gt;&#10;&lt;p&gt;Никаких подписок, никаких облаков, никаких производителей. Устройство работает, пока работает локальная сеть и есть питание.&lt;/p&gt;&#10;&lt;h2 id="почему-свой--это-не-про-экономию"&gt;Почему «свой» — это не про экономию&#10;&lt;/h2&gt;&lt;p&gt;Про подписку можно возразить: «дешёвые термостаты с облаком и так стоят 2-3 тысячи». И да, сам железный корпус стоит недорого.&lt;/p&gt;&#10;&lt;p&gt;Но дело не в цене устройства, а в &lt;strong&gt;том, что ты перестаёшь зависеть от чужого решения&lt;/strong&gt;:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Сервис закрыли или перенёс — у тебя дома не отключилось отопление&lt;/li&gt;&#10;&lt;li&gt;Приложение перестало поддерживать твой телефон или ОС — устройство не потеряло функций&lt;/li&gt;&#10;&lt;li&gt;Владелец сервиса решил, что твои данные нужны ему больше, чем тебе — у тебя их вообще не было&lt;/li&gt;&#10;&lt;li&gt;Через 5 лет захотел добавить датчик — и не можешь, потому что прошивка закрыта&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;strong&gt;Vendor-lock — это не абстрактная проблема.&lt;/strong&gt; Это когда ты не можешь починить вещь в своём доме, потому что дом зависит не от дома, а от чужого сервера.&lt;/p&gt;&#10;&lt;h2 id="честная-часть-почему-это-не-так-просто"&gt;Честная часть: почему это не так просто&#10;&lt;/h2&gt;&lt;p&gt;Свой термостат — это не «купил ESP32 и всё работает». Реальность такая:&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Железо — самая простая часть.&lt;/strong&gt; Модуль ESP32 стоит как пара пачек чая, реле и датчик температуры — ещё меньше. Собрать на макетке — вечер.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Сложнее — логика и безопасность.&lt;/strong&gt; Термостат управляет нагрузкой, а нагрузка бывает опасной. Газовый котёл, тёплый пол, водяной насос — здесь ошибка в логике или в заземлении бьёт по кошельку или по безопасности. Поэтому граница между «следить» и «вмешиваться» проходит по принципу: &lt;strong&gt;мониторинг — безопасно, управление — только после отдельной проработки&lt;/strong&gt;.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Плюс отсутствие поддержки.&lt;/strong&gt; Своё устройство — это твоя ответственность. Если через год оно перестанет работать, чинить будешь ты, а не сервис поддержки с бесплатными обновлениями.&lt;/p&gt;&#10;&lt;p&gt;И последнее: &lt;strong&gt;гайд по калибровке, подбор датчика под конкретную задачу — это отдельная работа.&lt;/strong&gt; Готового красивого приложения не будет. Будет веб-страница или дашборд, или вообще только вебхук в систему уведомлений.&lt;/p&gt;&#10;&lt;h2 id="что-на-самом-деле-даёт-свой-термостат"&gt;Что на самом деле даёт «свой» термостат&#10;&lt;/h2&gt;&lt;p&gt;Не «дешевле». А именно:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;Предсказуемость.&lt;/strong&gt; Устройство работает по правилам, которые ты видишь и можешь изменить.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Отсутствие подписки как обязательного условия.&lt;/strong&gt; Базовые функции не перестают работать, потому что кто-то закрыл SaaS.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Интеграция с тем, что уже есть.&lt;/strong&gt; Тот же ESP32, тот же Gotify-канал уведомлений, та же вики с логом. Это часть общей системы, а не отдельный брендированный прибор из чужой экосистемы.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="итог"&gt;Итог&#10;&lt;/h2&gt;&lt;p&gt;Термостат — это не сложная вещь. Это «включить-выключить» плюс «знать, что происходит». Вся сложность, которую добавляют производители, — это облако, подписка, приложение и экосистема вокруг простой задачи.&lt;/p&gt;&#10;&lt;p&gt;Вопрос только в том, готов ли ты взять эту простоту на себя.&lt;/p&gt;&#10;&lt;p&gt;А у вас термостат с подпиской или свой? И что вас в нём бесит больше — цена, приложение или зависимость от чужого сервера?&lt;/p&gt;&#10;</description></item><item><title>20 источников уведомлений. Один Gotify вместо двадцати Telegram-ботов</title><link>https://smallautomation.iotex.biz/posts/2026-11-10-gotify-hub/</link><pubDate>Tue, 10 Nov 2026 00:00:00 +0000</pubDate><guid>https://smallautomation.iotex.biz/posts/2026-11-10-gotify-hub/</guid><description>&lt;img src="https://smallautomation.iotex.biz/images/covers/gotify-hub.svg" alt="Featured image of post 20 источников уведомлений. Один Gotify вместо двадцати Telegram-ботов" /&gt;&lt;p&gt;Двадцать отдельных Telegram-ботов — это двадцать мест, где нужно искать уведомление, и двадцать точек, где интеграция может сломаться. Gotify превращает это в &lt;strong&gt;один центр&lt;/strong&gt;: каждый источник шлёт в один и тот же адрес, а ты читаешь всё в одном приложении.&lt;/p&gt;&#10;&lt;h2 id="проблема"&gt;Проблема&#10;&lt;/h2&gt;&lt;p&gt;Каждый новый источник уведомлений — это новый бот и новый канал в телефоне. Деплой — один канал, дедлайны из Obsidian — второй, алерты с серверов — третий, домашние датчики — четвёртый. Источников становится двадцать, и к каждому отдельному уведомлению надо возвращаться отдельно.&lt;/p&gt;&#10;&lt;p&gt;Это не «20 уведомлений». Это &lt;strong&gt;20 мест для тревоги&lt;/strong&gt;. Телефон превращается в ленту, где важное событие тонет между «сборка прошла» и «напоминание из заметки». Цена каждого нового источника растёт не линейно, а потому что добавляется ещё одно место, где нужно проверять.&lt;/p&gt;&#10;&lt;h2 id="решение"&gt;Решение&#10;&lt;/h2&gt;&lt;p&gt;Один self-hosted Gotify. Любой источник — деплой, дедлайн из Obsidian, алерт, скрипт — шлёт в один и тот же endpoint. Ты подписан на один канал и видишь всё в одном приложении.&lt;/p&gt;&#10;&lt;p&gt;Настройка источника сводится к одной строке: URL приложения плюс токен. Не надо нигде регистрировать бота, нигде просить токен у Telegram, нигде разбираться, как этот конкретный бот называет свои кнопки.&lt;/p&gt;&#10;&lt;h2 id="что-это-даёт-по-сути"&gt;Что это даёт по сути&#10;&lt;/h2&gt;&lt;p&gt;Главный эффект — не «ещё один бот», а &lt;strong&gt;разделение источника и канала доставки&lt;/strong&gt;.&lt;/p&gt;&#10;&lt;p&gt;Источник не знает, куда ты читаешь. Он просто умеет отправить сообщение по HTTP. А канал доставки — отдельный слой, который можно переключить целиком, не трогая ни один источник.&lt;/p&gt;&#10;&lt;p&gt;Это и есть то, что превращает набор интеграций в систему. Двадцать отправляющих точек, одна точка приёма. Если завтра захочется другой клиент — меняется приёмник, а не все источники.&lt;/p&gt;&#10;&lt;h2 id="честная-часть"&gt;Честная часть&#10;&lt;/h2&gt;&lt;p&gt;Gotify не решает задачу «как настроить 20 источников». Он решает задачу «куда они все шлют».&lt;/p&gt;&#10;&lt;p&gt;Второе после первого раза становится дешёвым. Первое — всегда самое дорогое. Настройка каждого нового источника требует разобраться, как именно этот источник умеет отправлять уведомления, и это отдельная работа на каждый источник.&lt;/p&gt;&#10;&lt;p&gt;И ещё: self-hosted значит, что сервис живёт на твоём железе. Его нужно обслуживать — обновлять, следить за диском, поднимать после перезагрузки. Это ещё одно дело, которое «работает без меня» ровно до первого раза, когда контейнер не поднялся. Gotify не отменяет DevOps — он просто складывает всё в одно место.&lt;/p&gt;&#10;&lt;h2 id="связка-с-остальным"&gt;Связка с остальным&#10;&lt;/h2&gt;&lt;p&gt;Уведомления — не отдельная сущность, а часть общей системы: вики, трекер, домашние датчики. Один канал доставки на всё.&lt;/p&gt;&#10;&lt;p&gt;Когда источники перестают быть отдельными ботами, появляется место, где видно их все сразу. А когда видно все сразу, можно начать ими управлять.&lt;/p&gt;&#10;&lt;h2 id="итог"&gt;Итог&#10;&lt;/h2&gt;&lt;p&gt;Двадцать ботов → один Gotify. Сколько у тебя источников, которые шлют уведомления? Напиши в комментариях — интересно, кто дошёл до этого раньше.&lt;/p&gt;&#10;</description></item><item><title>Portfolio Performance удалён. Теперь трекер мой, и я решаю, как считать</title><link>https://smallautomation.iotex.biz/posts/2026-11-06-portfolio-performance/</link><pubDate>Fri, 06 Nov 2026 00:00:00 +0000</pubDate><guid>https://smallautomation.iotex.biz/posts/2026-11-06-portfolio-performance/</guid><description>&lt;img src="https://smallautomation.iotex.biz/images/covers/own-tracker.svg" alt="Featured image of post Portfolio Performance удалён. Теперь трекер мой, и я решаю, как считать" /&gt;&lt;p&gt;Portfolio Performance я ставил, импортировал в него сделки и бросил. Две причины, и обе технические: он плохо работает с российскими брокерами, а свою логику в него выразить нельзя — только подстраиваться под чужую модель.&lt;/p&gt;&#10;&lt;h2 id="где-ломается-готовое-решение"&gt;Где ломается готовое решение&#10;&lt;/h2&gt;&lt;p&gt;Дело не в том, что инструмент плохой. Он закрывает задачу «посчитать портфель». Моя задача звучит иначе: посчитать портфель по моим правилам, с моими формулами и моими российскими брокерами. Разница в этом предложении — и она дороже, чем кажется.&lt;/p&gt;&#10;&lt;p&gt;Вот цепочка, которая в итоге выкинула готовый продукт:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;ZenMoney не умеет учитывать акции и облигации с переоценкой.&lt;/strong&gt; Там удобно считать расходы и доходы, но бумаги живут по другим правилам.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Tinkoff не отдаёт CSV.&lt;/strong&gt; Данные по сделкам и ценам доступны только через API.&lt;/li&gt;&#10;&lt;li&gt;Универсальный импортёр в такой схеме упирается в оба ограничения сразу.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Дальше вариантов два: подстраивать задачу под инструмент или написать свой код под задачу. Я выбрал второе, потому что подстройка обходится дороже — и в скорости, и в том, что результат всё равно пришлось бы пересчитывать руками.&lt;/p&gt;&#10;&lt;h2 id="три-источника-у-каждого-своя-роль"&gt;Три источника, у каждого своя роль&#10;&lt;/h2&gt;&lt;p&gt;Прежде чем писать код, я разделил ответственность. Это важнее, чем сам код.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Инструмент&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Что делает&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;ZenMoney&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;расходы, доходы, бюджеты, цели&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Свой трекер (Python → markdown)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;портфель, доли, ребаланс, формулы&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Tinkoff API&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;источник правды по сделкам и ценам&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Ни один инструмент тут не делает всё. ZenMoney остаётся источником транзакций по деньгам, Tinkoff — источником правды по бумагам, а мой скрипт занимается только тем, что между ними: считает доли по правилам и раскладывает результат в markdown.&lt;/p&gt;&#10;&lt;p&gt;Третье — ключевое. Скрипт не хранит состояние. Он читает API, считает и &lt;strong&gt;кладёт результат в вики&lt;/strong&gt; обычной markdown-страницей. Рядом лежат политика, цели и решения, на основе которых всё это посчитано. Отчёт и его основание живут в одном месте.&lt;/p&gt;&#10;&lt;h2 id="что-формулы-умеют-а-чужой-инструмент--нет"&gt;Что формулы умеют, а чужой инструмент — нет&#10;&lt;/h2&gt;&lt;p&gt;У меня есть инвестиционный план, и в нём доли считаются по формуле, а не заданы числами:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;резервный_фонд = 10%&#10;генерация_фонд = 5%&#10;акций_роста_фонд = 10%&#10;индексный_фонд = 0%&#10;&#10;дивидендный_фонд = 100 − резервный − генерация − акций_роста − индексный&#10; = 75%&#10;&#10;ядро_дивидендного = (100 − возраст) × доля_дивидендного / 100&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Последняя строка — то, ради чего всё затевалось. Доля дивидендного фонда зависит от возраста: ядро пересчитывается каждый год, и внутри ядра активная и пассивная части делятся пополам.&lt;/p&gt;&#10;&lt;p&gt;Универсальный трекер про такое не знает. Он умеет показывать структуру портфеля, но не умеет взять мою формулу и применить её к моим же данным. Правила отбора фондов (ограничение на долю одного актива, требуемую доходность относительно ключевой ставки, распределение ядра по типам облигаций) живут в плане, и в чужой интерфейс это не влезает.&lt;/p&gt;&#10;&lt;h2 id="до-и-после"&gt;До и после&#10;&lt;/h2&gt;&lt;p&gt;Пока трекер был в разработке, сверка портфеля выглядела так:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;4 шага по 5 минут, около 15 минут в неделю&lt;/li&gt;&#10;&lt;li&gt;вклады обновлялись вручную&lt;/li&gt;&#10;&lt;li&gt;акции и облигации &lt;strong&gt;не сверялись вообще&lt;/strong&gt; — нечем было&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Сейчас:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;4 шага по 1–2 минуты, около 5 минут в неделю&lt;/li&gt;&#10;&lt;li&gt;скрипт запускается, читает данные брокера и пишет markdown&lt;/li&gt;&#10;&lt;li&gt;отклонение долей от плана считается само&lt;/li&gt;&#10;&lt;li&gt;если отклонение больше 5% — приходит алерт в задачник по ребалансу&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Разница в минутах — не главное. Главное, что &lt;strong&gt;акции перестали быть слепой зоной&lt;/strong&gt;. Полгода я знал, что по акциям и облигациям не сверяюсь, и это молчало до момента, когда перекос стал бы дорогим.&lt;/p&gt;&#10;&lt;p&gt;Отдельно: в трекер встроена синхронизация налогов, купонов и дивидендов с ZenMoney. Операции появляются в учёте сами, вручную их вносить не нужно. Весь этот кусок я собрал за один день — задача была конечной и проверяемой, и это единственное, что уберегло её от бесконечной полировки.&lt;/p&gt;&#10;&lt;h2 id="почему-всё-это-стоит-в-вики-а-не-в-приложении"&gt;Почему всё это стоит в вики, а не в приложении&#10;&lt;/h2&gt;&lt;p&gt;Отдельное приложение для учёта портфеля — это ещё один инструмент, который требует внимания. У него свой интерфейс, свои обновления, свои поломки и своя логика, которую надо сверять с логикой в плане.&lt;/p&gt;&#10;&lt;p&gt;Markdown-страница в базе знаний работает иначе. Данные приезжают скриптом, отчёт лежит рядом с политикой и целями, и в тот же файл я пишу записи из дневника. Ничего не нужно открывать, обновлять и синхронизировать — оно просто лежит и читается.&lt;/p&gt;&#10;&lt;p&gt;Минус честно: это не интерфейс для повседневного использования. Это отчёт, который смотришь раз в неделю. Мне этого хватает, потому что рутина всё равно уходит в фон, а не в глаза.&lt;/p&gt;&#10;&lt;h2 id="вывод"&gt;Вывод&#10;&lt;/h2&gt;&lt;p&gt;Разница между «решить задачу» и «решить мою задачу» стоила мне одного готового инструмента и пары дней работы. Если ваш инструмент закрывает 90% задачи, это повод им пользоваться. Если оставшиеся 10% — это вся суть, дешевле написать своё, чем подстраиваться.&lt;/p&gt;&#10;&lt;p&gt;Код трекера открытый. Правила, по которым он считает, лежат рядом с ним же.&lt;/p&gt;&#10;</description></item><item><title>Мониторинг газового котла на ESP32: как узнать, что он работает, когда тебя нет дома</title><link>https://smallautomation.iotex.biz/posts/2026-11-05-monitoring-kotla/</link><pubDate>Thu, 05 Nov 2026 00:00:00 +0000</pubDate><guid>https://smallautomation.iotex.biz/posts/2026-11-05-monitoring-kotla/</guid><description>&lt;img src="https://smallautomation.iotex.biz/images/covers/monitoring-kotla.svg" alt="Featured image of post Мониторинг газового котла на ESP32: как узнать, что он работает, когда тебя нет дома" /&gt;&lt;p&gt;Когда зимой уезжаешь в отпуск или на дачу, остаётся тревожный вопрос: газовый котёл сейчас работает или нет? Даже если система включена, ты не получаешь подтверждения, что отопление действительно поддерживается, а поломку или сбой узнаёшь только по возвращении. Для частного дома нужен простой локальный мониторинг, который сообщит об изменении состояния котла и не зависит от облачного сервиса.&lt;/p&gt;&#10;&lt;h2 id="что-измеряем-и-как-уведомляем"&gt;Что измеряем и как уведомляем&#10;&lt;/h2&gt;&lt;p&gt;Схема состоит из ESP32, датчика давления и реле. Датчик давления позволяет связать состояние газового котла с измеряемой величиной, а ESP32 анализирует показания и формирует событие. Реле служит элементом управления или сигнализации: с его помощью можно переключать исполнительное устройство, которое сообщит о сбросе.&lt;/p&gt;&#10;&lt;p&gt;Сразу стоит отделить контроль от вмешательства. Мониторинг показывает, что происходит с котлом, но не должен сам запускать или останавливать газовое оборудование без отдельной проработки безопасности. Поэтому конкретную модель датчика давления, его цену и способ подключения сначала нужно определить, а затем зафиксировать схему wiring.&lt;/p&gt;&#10;&lt;p&gt;Для алерта пока не выбран канал доставки: Telegram заблокирован, а варианты email и push ещё не определены. Канал доставки следует выбрать до сборки, иначе устройство сможет обнаружить событие локально, но ты не получишь уведомление во время отъезда.&lt;/p&gt;&#10;&lt;h3 id="порог-и-логика-уведомлений"&gt;Порог и логика уведомлений&#10;&lt;/h3&gt;&lt;p&gt;Имеющегося давления недостаточно для настройки системы: порог срабатывания алерта не зафиксирован. Его нужно определить вместе с моделью датчика и условиями эксплуатации, после чего отдельно обработать несколько состояний.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Рабочий диапазон:&lt;/strong&gt; давление находится в пределах, при которых котёл считается работающим нормально.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Ниже рабочего диапазона:&lt;/strong&gt; состояние котла изменилось, и требуется уведомление.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Выше рабочего диапазона:&lt;/strong&gt; также нужно уведомление, если выбранный датчик позволяет достоверно фиксировать такие значения.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Ошибка датчика:&lt;/strong&gt; обрыв, отсутствие показаний или другая некорректная величина не должна выглядеть как штатная работа котла. Вместо этого событие нужно маркировать как неисправность самого канала измерения.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Не стоит отправлять письмо при каждом измерении. Полезнее фиксировать переходы между состояниями, чтобы уведомление означало изменение ситуации, а не повторяющийся шум.&lt;/p&gt;&#10;&lt;h3 id="выбор-датчика-и-подключение"&gt;Выбор датчика и подключение&#10;&lt;/h3&gt;&lt;p&gt;Конкретная модель датчика давления пока не выбрана — это первое, что нужно сделать перед покупкой компонентов и составлением wiring. Вместе с моделью уточни диапазон измерения, тип выходного сигнала, требуемое питание и допустимое разрешение. От этих данных зависит, можно ли подключить датчик непосредственно к ESP32 или потребуется преобразование сигнала.&lt;/p&gt;&#10;&lt;p&gt;Также нужно проверить, как именно датчик получает давление от контура котла. Не стоит устанавливать его методом проб: способ врезки, расположение и конструктивная совместимость должны соответствовать выбранному устройству и схеме котла.&lt;/p&gt;&#10;&lt;p&gt;Для реле также нужны конкретные параметры: ток катушки, напряжение питания и тип нагрузки. Если реле только переключает цепь индикации или другого исполнительного устройства, схема должна явно показывать этот отдельный контур и защиту выводов ESP32.&lt;/p&gt;&#10;&lt;h2 id="программный-каркас-esp32"&gt;Программный каркас ESP32&#10;&lt;/h2&gt;&lt;p&gt;Ниже показан программный каркас локального мониторинга. Он читает аналоговый сигнал датчика, сравнивает его с заданным диапазоном и управляет реле. Поскольку модель датчика и порог ещё не выбраны, значение &lt;code&gt;PRESSURE_MIN&lt;/code&gt;, &lt;code&gt;PRESSURE_MAX&lt;/code&gt;, пин датчика, пин реле и напряжение питания перед использованием нужно заменить на проверенные.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ESP32: локальный мониторинг давления газового котла.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Arduino.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;constexpr&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;SENSOR_PIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Аналоговый вход ESP32.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;constexpr&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;RELAY_PIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Управляющий вывод реле.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;constexpr&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;SAMPLE_INTERVAL_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Заменить на диапазон, проверенный для выбранной модели датчика&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// и условий эксплуатации котла.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;constexpr&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;PRESSURE_MIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;constexpr&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;PRESSURE_MAX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BoilerState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Unknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Normal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Low&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;High&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;SensorError&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;BoilerState&lt;/span&gt; &lt;span class="n"&gt;previousState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BoilerState&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;readRawPressure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;analogRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SENSOR_PIN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4095&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;BoilerState&lt;/span&gt; &lt;span class="nf"&gt;classifyPressure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;BoilerState&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SensorError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;PRESSURE_MIN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;BoilerState&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Low&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PRESSURE_MAX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;BoilerState&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;High&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;BoilerState&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Normal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;applyRelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BoilerState&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Зафиксировать активное состояние реле и схему подключения.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Управляющая логика должна быть инвертирована, если используется&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// модуль реле с низкоуровневым включением.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;alertActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;BoilerState&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Low&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;BoilerState&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;High&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;BoilerState&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SensorError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;digitalWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RELAY_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alertActive&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nl"&gt;HIGH&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LOW&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;115200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pinMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RELAY_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OUTPUT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;digitalWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RELAY_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LOW&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Перед использованием выбор входного канала уточнить&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// по конкретной модели датчика и его выходному сигналу.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;analogReadResolution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Мониторинг давления котла запущен&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;rawValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;readRawPressure&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;BoilerState&lt;/span&gt; &lt;span class="n"&gt;currentState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;classifyPressure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentState&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;previousState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Состояние изменилось: &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;static_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;, raw=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Здесь вызывается выбранный канал уведомлений.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Реализовать отправку только для выбранного канала:&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// email, push или другой доступный вариант.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;previousState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;applyRelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SAMPLE_INTERVAL_MS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Логика заложена на переходы между состояниями, а не на оповещение при каждом чтении датчика. Ошибка измерения обрабатывается отдельно и не должна маскироваться под нормальную работу котла. При этом место вызова канала уведомлений остаётся незаполненным: его нужно реализовать после выбора доступного способа доставки.&lt;/p&gt;&#10;&lt;h3 id="что-ещё-нужно-проверить-перед-использованием"&gt;Что ещё нужно проверить перед использованием&#10;&lt;/h3&gt;&lt;p&gt;Wiring нельзя считать готовым, пока не указаны конкретные компоненты. После выбора модели нужно записать точное подключение: питание датчика, его выход к пин ESP32, общую землю, питание катушки реле, пин управления реле и цепь нагрузки. Для реле с отдельным источником нельзя подавать ток катушки через вывод ESP32.&lt;/p&gt;&#10;&lt;p&gt;Также требуется проверить соответствие параметров датчика и выбранного канала уведомлений исходной задумке. Если датчик или линия связи оборвётся, система должна сообщить об ошибке измерения, а не делать вид, что котёл исправно отапливает дом.&lt;/p&gt;&#10;&lt;h2 id="питание-и-безопасность"&gt;Питание и безопасность&#10;&lt;/h2&gt;&lt;p&gt;Питание и безопасность здесь нельзя оставить на потом. Из контекента не зафиксированы ток потребления ESP32, ток катушки реле и тип источника питания, поэтому перед сборкой нужно проверить эти параметры по datasheet конкретных компонентов.&lt;/p&gt;&#10;&lt;p&gt;Нужно определить, будет ли вся схема питаться от USB, от отдельного блока питания или от комбинации источников. Если реле управляет нагрузкой, которой нельзя давать питание непосредственно с вывода ESP32, ему нужен отдельный источник. При этом общую землю ESP32 и источника реле следует соединить, а выводы микроконтроллера — защитить от обратного напряжения и бросков тока.&lt;/p&gt;&#10;&lt;h3 id="поведение-при-сбоях-и-ограничения-системы"&gt;Поведение при сбоях и ограничения системы&#10;&lt;/h3&gt;&lt;p&gt;При пропадании питания логичное поведение — прекратить обновление состояния и отметить отсутствие связи с устройством. Возможность резервного питания, его тип и время автономной работы в контексте не заданы, поэтому их нужно определить отдельно, если мониторинг должен пережить кратковременное отключение сети.&lt;/p&gt;&#10;&lt;p&gt;Не стоит подключать цепь управления котла параллельно его штатной автоматике без проверки схемы. Эта сборка задумана как наблюдение и оповещение; допустимость любого воздействия на управляющие цепи котла требует отдельного подтверждения.&lt;/p&gt;&#10;&lt;h2 id="вывод--tldr"&gt;Вывод / TL;DR&#10;&lt;/h2&gt;&lt;p&gt;Локальный мониторинг на ESP32, датчике давления и реле помогает узнать об изменении состояния газового котла, пока тебя нет дома, без зависимости от облака. Перед сборкой нужно выбрать датчик и канал уведомлений, определить рабочий диапазон, зафиксировать проверенную схему питания и отдельно проверить безопасность подключения к контуру котла.&lt;/p&gt;&#10;</description></item></channel></rss>