Введение
На работе появились датчики CO2 и строгое указание: «Когда вот тут красное, проветривайте помещение». Никогда раньше особо не задумывался, но стало интересно, кто же не любит свежий воздух. Офисная работа часто подразумевает концентрацию на задании, и тогда уже не замечаешь, какой воздух стал. А так появляется метрика, на которую можно опираться.
Датчики хотя и стоят немало, но ничего, кроме показа на экране, не умеют, а ведь любые метрики было бы полезно собирать… Но на работе отношение народа к этому пока несерьёзное и мысль дальше не двинулась.
Пролистывая товары на Алиэкспрессе, наткнулся на датчики SCD40/41 и SCD30. Купил. В разговорах с друзьями-хоббистами появился вопрос о том, что драйвер для SCD40/41 не работает, а автор не помогает. Раз уж у меня всё равно был датчик, то скачал первый попавшийся Datasheet и реализовал, что там было описано, — ничего особо сложного: просто передача команд. Большую часть работы переложил на «Т9++», сам занимался только возможностями восстановления связи, если вдруг возникают временные обрывы на линии I2C. Впоследствии выяснилось, что было несколько ревизий документа. Пришлось искать самый свежий и реализовать оставшиеся команды.
Попробовал, протестировал временные обрывы линии данных, питания — всё работает. Опубликовал компонент
Вообще, взял за привычку, что если какой-то свой компонент реализовал, то публиковать его в официальном ESP Component Registry. Так потом проще использовать свои же компоненты в следующих проектах.
Один из других долго тянущихся моих проектов — графическая библиотека для ESP32. Я в очередной раз переписал её «в поисках идеального API». На текущий момент, основная архитектура уже выстрадана и вряд ли будет меняться, но какие-то новые возможности дописываю по мере необходимости. Так совпало, что я тестировал новый круглый экран и у меня был датчик углекислого газа, и я решил взяться за проект измерительной станции. Заодно решил прокачать свои навыки 3D-печати и 3D-моделирования для неё. В обеих этих темах я новичок.
SCD41
SCD30 я пока нигде не использовал: он довольно крупный, хотя его показания, вероятно, должны быть точнее. SCD40/41 считается хорошим сенсором для «гражданского» применения. Ему не нужен большой корпус, он поддерживает автокалибровку, а также измеряет температуру и относительную влажность, которые затем использует для автоматической коррекции собственных показаний. Я купил SCD41, который отличается от SCD40 в основном только диапазоном измеряемых значений: SCD41 — до 5000 ppm, SCD40 — до 2000 ppm.
Выдержка из справки:
Сенсор Sensirion SCD41 — это один из самых точных и компактных миниатюрных датчиков углекислого газа (CO2) на рынке. Он работает на фотоакустическом принципе (PAS) и часто используется в профессиональных системах вентиляции, умных домах и портативных устройствах.
Его основные показатели точности:
— Погрешность CO2: ±(40 ppm + 5%) от текущего значения
— Диапазон измерений CO2: от 400 до 5000 ppm
— Температурная точность: ±0.8 °C (компенсируется встроенным датчиком)
— Точность влажности: ±6%
Сильные стороны: почему он так точен
— Встроенная компенсация: датчик имеет встроенные сенсоры температуры и влажности, что позволяет автоматически корректировать показания CO2 в зависимости от микроклимата в помещении.
— Прямое измерение: это настоящий оптический NDIR-сенсор, а не расчётный датчик (eCO2), который угадывает уровень углекислого газа по другим летучим веществам.
— Автокалибровка (ASC): сенсор умеет самокалиброваться по минимальному уровню чистого воздуха, обычно принимаемому за 400 ppm, когда помещение проветривается. Это защищает его от дрейфа показаний со временем.
Отображение и визуальная часть
Быстро соединив дисплей, сенсор и отладочную плату ESP32, я начал писать программу, показывающую качество воздуха. Поскольку мне нужно было использовать возможности графической библиотеки, я добавил анимацию и свой собственный шрифт, предназначенный именно для этого. Он описывается не бинарными матрицами, а координатами пикселей, которые располагаются в специальном порядке, как если бы их писал человек от руки. При выводе на экран каждая точка символа отрисовывается матрицей пикселей (своеобразной «жирной» точкой), но благодаря оптимизации библиотеки общая анимация всё равно остаётся плавной.
Управление устройством
Позже пришла мысль, что хорошо бы уметь выключать дисплей, например, если в комнате темно и не хочется, чтобы что-то светилось. Так появилась бесконтактная кнопка на сенсоре TTP223. К сожалению, у моего модуля дисплея нет управления подсветкой, поэтому небольшое свечение в темноте всё равно остаётся.
Сейчас логика управления такая:
-
короткое касание включает или выключает дисплей;
-
двойное касание, если Wi-Fi уже настроен, запускает веб-сервер со страницей параметров устройства, на дисплее внизу показывается IP-адрес веб-сервера настроек;
-
долгое удержание в течение 5 секунд переводит прибор в режим BLE-настройки Wi-Fi и показывает QR-код для приложения Espressif.
Если за 60 секунд новые настройки не были применены или фактически ничего не изменилось, устройство просто возвращается к обычной работе. Если же получены новые учётные данные Wi-Fi, то после успешного завершения настройки прибор перезапускается и уже затем поднимает сеть с новой конфигурацией.
Сетевые возможности
Показывать состояние воздуха полезно само по себе, но можно добавить и практическую интеграцию: устройство передаёт данные по протоколу MQTT, откуда информацию может забирать умный дом или любая другая похожая система. В настоящий момент показания интегрированы в Home Assistant. Устройство сообщает измеренные значения CO2, температуры и влажности, а также силу сигнала Wi-Fi.
Раз уж сеть всё равно приходится поднимать, хотелось сделать и настройку Wi-Fi более технологичной: через BLE provisioning с QR-кодом для приложения Espressif BLE AP, которое можно установить из официальных магазинов Google Play и App Store. Если сеть уже настроена, двойное касание открывает локальный веб-сервер со страницей конфигурации.
Что настраивается через веб-сервер
Через этот веб-сервер сейчас настраиваются такие параметры:
-
адрес MQTT;
-
имя пользователя MQTT;
-
пароль MQTT;
-
название устройства;
-
локальное имя хоста устройства, под которым оно регистрируется в сети;
-
часовой пояс;
-
смещение CO2;
-
смещение температуры;
-
смещение влажности.
Параметры сохраняются во внутренней памяти устройства, коррекции показаний применяются к текущим измерениям.
Про коррекции к измерениям
Коррекция показаний здесь — это эмпирическая подстройка по одной опорной точке, а не полноценная калибровка: задаётся только смещение к текущим измерениям. По моим наблюдениям, в моей сборке сенсор завышает показания влажности примерно на 5 процентных пунктов, что находится в пределах его документированной возможной погрешности.
Смещение температуры дополнительно записывается в сам сенсор SCD41 (регистр компенсации саморазогрева), поэтому учитывается аппаратно. Здесь есть важная особенность: этот регистр в SCD41 работает не так, как обычная программная прибавка к измерению. Чем больше значение в регистре, тем ниже итоговая температура, потому что сенсор сильнее компенсирует собственный нагрев. Из-за этого смещение температуры в настройках имеет более понятный «человеческий» смысл: положительное значение повышает показываемую температуру, отрицательное понижает. Внутри прошивки оно переводится в значение аппаратного регистра относительно заводской компенсации SCD41, которая по умолчанию составляет 4 °C. Например, если сенсор показывает 30,3 °C, а реальная температура 27,1 °C, то нужно задать смещение -3,2 °C. Прошивка переведёт это в значение 7,2 °C для регистра SCD41: чем больше это значение, тем сильнее сенсор компенсирует собственный нагрев и тем ниже получается итоговая температура.
Выбор платы
После того как устройства были подключены к обычной плате разработчика ESP32 и программа уже выполняла основные функции, я решил попробовать перейти на вариант платы поменьше: ESP32-C3-Zero.
Я не был уверен, хватит ли GPIO, будет ли достаточно питания 3,3 В для всех устройств и как вообще всё это уместить в компактном корпусе.
Пинов хватило впритык. Плата очень маленькая, GPIO у неё немного, а часть из них одновременно является bootstrap-пинами, поэтому подходят они не для любой задачи. Плюсы этой платы: маленькие размеры и низкая цена; минусы: мало пинов и мало возможностей для подключения чего-то ещё. Отдельным вопросом было, хватит ли встроенной SRAM. Для анимации я постоянно использую виртуальные экраны, которым нужна память. В итоге возможностей платы хватило, и это определило дальнейшее развитие корпуса устройства.
Пайка
Из-за особенностей размещения платы в корпусе пины пришлось паять вверх, в сторону размещения подключаемого оборудования. Припоя должно быть много, чтобы пины держались крепко, но лишнего припоя не должно быть на внешних сторонах платы, иначе в пазы будет плохо вставляться. Пины пришлось загнуть в сторону, чтобы провода не мешали упорам держать дисплей.
Антенна
Были сомнения в качестве разведения платы, хотя внешне антенна была сделана неплохо, но её маленькие размеры и опыт других похожих плат указывали на возможные проблемы с приёмом сигнала.
Для эксперимента купил медный посеребрённый провод, но пока так и не провёл эксперимент — в моих условиях приём сигнала оказался очень даже хорошим и так. Но если бы понадобилось, то место в корпусе под это есть, можно было бы улучшить приём.
Дисплей
Я использовал модуль круглого дисплея GC9A01 диаметром 1,28 дюйма. Шкала, похожая на спидометр, смотрится на нём очень хорошо.
Питание
Для питания подходит любой блок питания на 5 В, подключаемый к устройству по проводу USB Type-C. Автономное питание в корпусе не предусмотрено, но для него всегда можно воспользоваться внешним power bank.
Программа
Основной цикл и задача сенсора
После запуска app_main() программа инициализирует дисплей, сенсорную кнопку, Wi-Fi и затем создаёт отдельную FreeRTOS-задачу для работы с SCD41. Эта задача отвечает за работу с датчиком: опрашивает его, применяет поправки, при необходимости обновляет аппаратную температурную компенсацию и публикует наружу только уже готовое состояние воздуха.
Когда возможна параллельная работа с разным оборудованием на разных шинах и оно выносится в отдельные задачи, то лучше не пытаться работать с одним и тем же оборудованием из разных задач, а делать между ними необходимую передачу сообщений, чтобы с каждым оборудованием работала только одна своя задача.
Основной цикл при этом занимается экраном и логикой устройства: читает последнее измерение из очереди, обрабатывает жесты на TTP223, управляет режимом provisioning и обновляет отображение. Такое разделение упрощает логику: задержки при работе с датчиком не мешают интерфейсу, а экран не лезет напрямую в I2C-обмен с сенсором.
Перед передачей нового состояния в очередь и публикацией по MQTT значение CO2 дополнительно усредняется по последним четырём измерениям. Это слегка сглаживает естественный шум датчика и делает стрелку с цифрами на экране менее дёргаными. Температура и влажность сейчас передаются без такого усреднения. Похоже, что сенсор делает это самостоятельно.
Когда обновляется экран
Экран в программе не перерисовывается полностью на каждом проходе главного цикла. Обновление происходит по событиям: когда приходит новое измерение от задачи сенсора, когда меняется минута на часах и когда нужно сделать очередной шаг анимации цифр. Это заметно разгружает контроллер и делает интерфейс более отзывчивым.
Отдельно учтены специальные режимы. Если дисплей выключен, лишняя перерисовка не выполняется. Если запущен provisioning, обычный экран временно уступает место интерфейсу настройки Wi-Fi, а после завершения программа возвращает отображение на экране в нормальный режим.
Анимация показаний
Одной из особенностей моей графической библиотеки является поддержка шрифтов. В ней есть формат символов, которые описываются координатами точек. Это удобно использовать для эффекта трансформации одного символа в другой. При рисовании такого шрифта применяется матрица пикселей размером с выбранную точку шрифта. Размеры больше 5 представляют собой «сферу»: окружности с убыванием яркости от центра к краю. Размеры от 1 до 5 подобраны руками.
Для анимационного перехода одного символа в другой выполняется несколько шагов. Сначала создаются два массива координат точек: для исходного и для целевого символа. Затем количество элементов в этих массивах выравнивается: недостающие элементы добавляются повторением последней точки в соответствующем массиве. Функция преобразования рассчитывает сдвиг каждой точки по линии от старых координат к новым в зависимости от параметра t ∈ [0, 1], где t = 0 означает координаты исходного символа, а t = 1 соответствует координатам конечного символа. Параметр t рассчитывается по времени так, чтобы весь промежуток от 0 до 1 пройти за половину секунды. Этот алгоритм уже использовался мной в предыдущем проекте: Матричный шрифт с анимацией на микроконтроллере.
Временные зоны
Естественно, самое главное: нужно показывать измеренные значения CO2, температуры и влажности. Но почему-то, когда я смотрю на такой прибор, то ожидаю увидеть ещё и время. Чтобы показывать время, нужно знать временную зону. Копировать из проекта в проект файл tzones.c, который периодически устаревает, не очень правильно. Поэтому я добавил автоматическую генерацию списка возможных временных зон через скрипт на Python при сборке проекта.
Страница настроек
Микроконтроллер должен динамически генерировать HTML-страницы настроек. Хранить такие страницы прямо в исходном коде не очень удобно. Поэтому страница хранится как обычный HTML-файл, а при сборке проекта разбивается на фрагменты постоянного текста и вызовы функций, формирующих динамическое содержимое. Результат сохраняется в app_webserver_template.h, который затем подключается в app_webserver.c. Таким образом, динамический HTML генерируется максимально эффективно.
MQTT и Home Assistant
MQTT в проекте используется не только для отправки текущих измерений. После подключения устройство публикует также availability-статус и набор сообщений autodiscovery для Home Assistant. Благодаря этому в Home Assistant автоматически появляются сущности для CO2, температуры, влажности и уровня сигнала Wi-Fi без ручного описания их в YAML.
При изменении сетевых параметров в настройках MQTT-клиент может быть пересоздан с новыми адресом брокера, логином, паролем и идентификаторами устройства. То есть веб-сервер на самом деле управляет не только хранимыми настройками, но и поведением уже работающей сетевой части.
Список компонентов и материалов (BOM)
|
Компонент / Материал |
Модель / Описание |
Назначение в проекте |
Ориентировочная стоимость |
|
Микроконтроллер |
Отладочная плата ESP32-C3-zero |
Логика, Wi-Fi Provisioning по BLE, MQTT и веб-сервер |
$3 |
|
Сенсор CO2 |
Sensirion SCD41 (или SCD40) |
Фотоакустический датчик углекислого газа, температуры и влажности |
$25 |
|
Дисплей |
Круглый экран GC9A01 |
Вывод интерфейса и анимаций через графическую библиотеку |
$4 |
|
Интерфейс |
Сенсорная кнопка TTP223 |
Единственный элемент управления (одиночный/двойной клик, удержание) |
$0.2 |
|
Основной корпус + Задняя крышка |
3D-печать (37.5 г + 10.5 г прозрачного + 2.5 г чёрного пластика) |
Оригинальная конструкция |
$1 |
Итого: примерно $35. С учётом MQTT и интеграции в HA — это заметно дешевле предлагаемых вариантов на рынке. Если, конечно, не считать потраченное мной время…
Проектирование корпуса
Проектирование корпуса оказалось отдельным приключением. Нужно было не забыть об изоляции сенсора от остальной электроники внутри корпуса, о доступе воздуха к сенсору и, конечно, о внешнем виде.
Поскольку моя основная рабочая система дома — Linux, большого выбора программ для моделирования корпуса у меня не было. К тому же на YouTube я часто видел ролики по FreeCAD, поэтому решил осваивать именно его. Раньше я никогда не занимался 3D-моделированием, тем более для печати на 3D-принтере. Пришлось изучить очень много приёмов, искать информацию в Google и на YouTube. Получившийся результат, возможно, ещё можно как-то улучшить, но я уже учёл очень много мелочей и напечатал множество промежуточных вариантов.
Для печати я выбрал прозрачный PETG. Мне он показался очень симпатичным.
Сначала я попробовал сделать только лицевую часть, чтобы дисплей хорошо вставлялся. Несмотря на то что я штангенциркулем замерил все размеры, всё равно потребовалось несколько попыток, чтобы окончательно подобрать нужные параметры. У печати на 3D-принтере есть свои особенности.
Затем я занялся стенками и деталями, которые к ним крепятся. Место под сенсор огорожено от остальной части корпуса: туда нужно проводить воздух снаружи и ограничивать влияние того, что находится внутри, чтобы нагрев от платы или дисплея не искажал показания. Сенсор выполняет автокоррекцию показаний по температуре воздуха, поэтому это тоже важный параметр.
Работая во FreeCAD, я постоянно искал, как сделать то, что мне нужно. Советоваться приходилось с Google, YouTube и Claude. При этом ролики на YouTube часто рассказывают о моделировании без учёта последующей 3D-печати, а у неё есть свои особенности.
Затем я сделал уже полный корпус и заднюю крышку.
Решил добавить название. К сожалению, FreeCAD не умеет делать текст вдоль произвольной кривой. Пришлось написать скрипт на Python, который разместит надпись по окружности вокруг дисплея.
Первый блин вышел комом. Надпись получилась сильно кривая. Насколько я разобрался, слишком тонкие черты шрифта для печати соплом 0.4мм получились.
Решение — изменить метод генерации стен в слайсере на Arachne.
Заднюю крышку решил посадить на защёлки. Чтобы дисплей был плотно прижат к лицевой стороне, я сделал на задней крышке упоры прямо в дисплей.
Получилось хорошо!
Первый вариант размещения, на котором измерял необходимую длину проводов.
После всех этих экспериментов модель во FreeCAD у меня получилась сломанной, и я не знал, как её починить. Получилось так, что я удалил какие-то части, на которые опирались другие части. Нормально восстановить это я не смог, поэтому всё сделал заново. Заодно сделал стенку корпуса чуть более прочной, увеличил глубину корпуса на 5 мм и переделал отверстия для доступа воздуха.
При 3D-печати по умолчанию используется шаблон заполнения Grid, который в прозрачном пластике выглядит не очень симпатично. В окончательном варианте выбран шаблон Gyroid.
При нажатии на сенсорную кнопку загорается красный светодиод. Благодаря прозрачности пластика хорошо видно и понятно, что «кнопочка нажалась».
Из-за проводов к кнопке и сенсору с одной стороны пришлось сместить упоры для дисплея относительно центра. Это не создало заметных проблем.
Gyroid всё-таки намного лучше смотрится, чем Grid.
Запуск и управление.
Заключение
Эксперимент по созданию оригинального корпуса, интеграции с Home Assistant и вообще мониторингу качества воздуха, завершился. Я приобрёл много новых навыков в проектировании корпусов для таких устройств. Дома появился прибор, следящий за качеством воздуха. Жена сказала, что надо сделать ещё два, чтобы следить за качеством воздуха у детей.
ссылка на оригинал статьи https://habr.com/ru/articles/1043390/