Подвал у бабушки я измерил в первый же мартовский выходной: 88 % относительной влажности в воздухе, а на северной стене — пушистые пятна размером с ладонь. Никто из взрослых не знал, что там так сыро: в подвал заходят раз в неделю за картошкой, а плесень растёт тихо и без приглашения. Сторож на Arduino меряет воздух каждые две секунды, пищит один раз в десять секунд и ведёт журнал — с какого часа началось и сколько тянулось. Дешёвая коробочка, которая видит то, что человек видит, только когда уже поздно.
Пороги и логика здесь — продолжение разговоров раздела: рамки тревоги по жаре и сырости уже были в уроке про зуммер и пороги, но тот прибор орал сиреной и жил в комнате. Подвал диктует другое поведение: звук редкий и короткий, статус — на светодиоде, вся история — в журнале с метками времени. А почему влага выпадает именно на холодной стене, объясняет формула из урока про точку росы — она же подскажет, где вешать датчик.
Три зоны: норма, сыро, крит
Грибку нужна влага, и это не фигура речи, а условие жизни: профилактика плесени сводится к контролю влажности, сухой воздух для неё голод. Практика дачников и строителей сходится на таких линиях: до 70 % подвал считается сухим и безопасным, 70-80 % — зона риска, где на холодных стенах уже может начаться колония, а выше 80 % плесень растёт уверенно — мой подвал с его 88 % был не «слегка сырой», а живой теплицей для грибка. Идеалом считается 30-40 %: при такой сухости не живёт ни плесень, ни пылевые клещи, но в земляном подвале такие цифры реальны разве что летом в засуху.
Отсюда два порога скетча: 70 % — «сыро, внимание», 80 % — «крит, пора проветривать и сушить». Между ними прибор напоминает о себе пик раз в десять секунд, за 80 — двойным пиком и светодиодом, который горит не отрываясь. Почему не 60: сторож, который пугает на каждом третьем показании, быстро учит всех его выключать. Порог тревоги обязан быть редким событием — тогда его слышат.
| Влажность | Статус | Светодиод D12 | Звук |
|---|---|---|---|
| до 70 % | норма | не горит | тишина; в журнале только переходы |
| 70-80 % | сыро | мигает: полсекунды вкл, полсекунды выкл | пик 120 мс раз в 10 с |
| выше 80 % | крит | горит ровно | двойной пик раз в 10 с |
Почему пик, а не сирена: подвал — пустое помещение, вы заглядываете туда минут на пять. Сирена в пустом подвале орёт в пустоту, разряжает питание, а через два дня сосед просто выдёргивает её из розетки. Пик 120 миллисекунд раз в десять секунд — другая философия: услышать его можно из-за двери, но жить рядом с ним реально. И реализован он кооперативно, на millis(): скетч ни одной миллисекунды не сидит в delay(), зуммер включается и выключается digitalWrite() по расчётной фазе. Захотите вместо пикетов поставить вентилятор — в уроке про вытяжку реле уже прикручено к тем же порогам.
Пороги в скетче сидят на float-константах, а сравнение — строгое «выше»: 70.0 попадает в норму, 70.1 — уже в сыро. Кажущаяся мелочь, но когда в журнале спорят две записи на границе, именно такое чтение кода экономит вечер. Датчик между тем не лайв-статистика: DHT22 отвечает раз в две секунды, и чаще его спрашивать бессмысленно — темп опроса выбирается под сенсор, что мы разбирали в уроке про интервал опроса.
#include "DHT.h"// DHT22 на пине 2, зуммер на 8, светодиод на 12DHT dht(2, DHT22); #define BUZ 8#define LED 12#define H_WARN 70.0#define H_CRIT 80.0const unsigned long PEEK_PERIOD = 10000UL; // пик не чаще раза в 10 сconst unsigned long READ_PERIOD = 2000UL; // DHT22 отвечает раз в 2 с int status = 0; // 0 норма, 1 сыро, 2 критunsigned long lastRead = 0;unsigned long lastPeek = 0; void setup() { Serial.begin(9600); pinMode(BUZ, OUTPUT); pinMode(LED, OUTPUT); dht.begin(); Serial.println("podval: syro>70 krit>80");} void loop() { unsigned long now = millis(); if (now - lastRead >= READ_PERIOD) { lastRead = now; float h = dht.readHumidity(); if (h < 0.0 || h > 100.0) { Serial.println("err: sensor"); } else { int st = 0; if (h > H_CRIT) st = 2; else if (h > H_WARN) st = 1; if (st != status) { // журнал: только смена статуса status = st; Serial.print("sec="); Serial.print(now / 1000UL); Serial.print(" status="); Serial.print(status); Serial.print(" h="); Serial.println(h, 1); } } } // светодиод: норма спит, сыро мигает 750/750, крит горит ровно if (status == 1) { digitalWrite(LED, (now % 1500UL) < 750UL ? HIGH : LOW); } else { digitalWrite(LED, status == 2 ? HIGH : LOW); } // пик раз в 10 секунд при любом отклонении if (status > 0 && now - lastPeek >= PEEK_PERIOD) { lastPeek = now; Serial.print("sec="); Serial.print(now / 1000UL); Serial.println(" pik"); } // фаза звука от lastPeek: сыро один пик, крит — пик-пауза-пик bool zvu = false; if (status == 1 && now - lastPeek < 120UL) zvu = true; if (status == 2) { unsigned long ph = now - lastPeek; if (ph < 120UL || (ph >= 240UL && ph < 360UL)) zvu = true; } digitalWrite(BUZ, zvu ? HIGH : LOW);}Журнал здесь — не бонус, а половина прибора. Строка печатается только в момент смены статуса: sec=3600 status=1 h=73.4 читается как «через час после подачи питания стало сыро, 73,4 %». Пролистав монитор, вы видите историю подвала целиком: когда началось, как долго тянулось, как реагировало на проветривание. Метка sec считается делением millis() на тысячу — секунды от старта, для глаза этого хватает; для красивых таблиц с часами и минутами сторож подключается к журналу с кольцевым массивом без переделки логики.
Второе решение — звук через digitalWrite, а не через tone(): у tone() с третьим аргументом есть скрытая цена — скетч стоит в нём всё время звучания. Для 120 миллисекунд это мелочь, но привычка важнее: сторож, которого однажды повесят на автономное питание, не должен жечь его паузами. Фаза звука считается от lastPeek: первые 120 миллисекунд окно открыто — зуммер орёт; для крита окно открывается второй раз на отрезке 240-360 — получается «пик-пауза-пик», различимый ухом даже из-за двери.
Первое, что стоит сделать после симуляции, — поставить датчик правильно. У стены, на высоте полметра от пола, но не вплотную: конденсат на холодной поверхности убивает сенсор быстрее времени. Влага в подвале выпадает там, где холодно, — стена на пять градусов холоднее воздуха — это и место самой честной проверки, и место, где датчик найдёт свою смерть от капли. Простой щиток-навесик из пластика решает обе задачи: показания остаются правдивыми, а капля стекает мимо сенсора.
Дальше сторожу куда расти: журнал вынести на SD-карточку, статусы дублировать в дом по радиоканалу, а вместо пикетов повесить вытяжку, которая включается на 70 % и выключается на 60 — это уже не сигнализация, а автоматика. Все надстройки ложатся на тот же скелет: опрос раз в две секунды, три статуса, планировщик на millis(). Мой порядок стройки такой: сначала прибор, который честно показывает, потом прибор, который действует.
- Пороги 70/80 % не с потолка: ниже 70 плесень голодает, выше 80 растёт уверенно, между ними зона риска на холодных стенах
- Пик 120 мс раз в 10 с: сигнал, который слышно из-за двери, но с которым можно жить; крит — двойной «пик-пауза-пик»
- Журнал печатается только на сменах статуса: sec= — миллисекунды/1000, история подвала без спама
- DHT11 слепой выше 80 % — это его документированный потолок; для подвала только DHT22 с диапазоном 0-100 %
- Звук через digitalWrite и millis(), не через tone(): скетч не спит и готов взять на себя вентиляцию
- 1. Почему порог тревоги 70 %, а не 50 %?
- 2. В подвале 82 %, но сторож с DHT11 показывает «норма». Что случилось?
- 3. Чем двойной пик крита отличается по коду от одиночного?
- 4. Что напечатает журнал при переходе из «сыро» в «крит»?
- 5. Почему зуммер включается digitalWrite(), а не tone(8, 2000, 120)?
Частые вопросы по теме урока
- Почему порог тревоги 70 %, а не 50 %?
- Сухой подвал — это до 70 %: при такой влажности грибок голодает. Тревога на 50 % звучала бы большую часть года и перестала бы значить что-либо. ниже 70 % плесень не получает влаги, и тревога превратилась бы в фоновый шум
- В подвале 82 %, но сторож с DHT11 показывает «норма». Что случилось?
- DHT11 документирован до 80 % влажности: зона крит для него невидима. В подвале ставят DHT22 с диапазоном 0-100 %. потолок диапазона DHT11 — 80 %: выше для него цифры нет
- Чем двойной пик крита отличается по коду от одиночного?
- Звук — тот же digitalWrite по фазе от lastPeek; у крита окно открыто дважды за цикл, и ухо слышит «пик-пауза-пик». окном звука: оно открывается дважды — на 0-120 и 240-360 мс фазы
- Что напечатает журнал при переходе из «сыро» в «крит»?
- Журнал пишется только на смене статуса — история без спама. Текущая влажность видна отдельно, а строка перехода показывает время и уровень. одну строку в момент перехода: sec=..., status=2
- Почему зуммер включается digitalWrite(), а не tone(8, 2000, 120)?
- Третий аргумент tone() — пауза delay-типа внутри вызова. Кооперативный пик через digitalWrite и millis() оставляет скетч свободным для опроса и журнала. tone() с длительностью держит скетч всё время звучания, а сторожу нужен живой loop()