CNN TürkYargıtay'dan "sürgün tayin"e engelPunch15-year-old Ghanaian boy shot dead near New York schoolInquirerFlooded graves, fading traditions: Undas in HagonoyInquirer Entertainment‘Star Trek,’ ‘X-Men’ star Patrick Stewart dead at 86The Jerusalem PostBus driver killed, dozens wounded in collision with tractor near NetivotBollywood HungamaSCOOP: Anirudh exits Shah Rukh Khan’s King due to packed schedule; gifts SRK the theme track as Sachin-Jigar take overZDF heuteAktuelle Pressemitteilungen des ZDFDaily MaverickEYE ON THE PRIZE: Visual artist and activist Zanele Muholi wins top international awardESPN CricinfoNayeem triggers WI's slide as Bangladesh grab 27-run first-innings leadХабр[Перевод] HTML-in-Canvas в Chrome 157: интерактивный DOM в 2D, WebGL и WebGPUGhaflaBabu Owino Defends Marriage to Frida, Opens Up About Being Raised by Single MotherObservador DesportoRali Dakar 2027 vai decorrer em Portugal e Marrocos
The Daily Newsstand · Free, Always
Friday, October 30, 2026

Глоток свежего воздуха: встраиваем глупый датчик в умный дом

Translate

Больше 10 лет назад я купил датчик углекислого газа, чтобы отслеживать состояние воздуха в спальне

Чтобы начать действительно им пользоваться, мне пришлось собрать еще одну железку, которая подключает его к умному дому и собирает показания в красивый график

Под катом история проектирования адаптера, который делает глупый датчик достаточно умным, чтобы на него хоть кто-нибудь начал смотреть

Разбираем датчик

В центре внимания вот эта железка

В России она была популярна в первой половине десятых, и у меня лежит с тех времен

Но на маркетплейсах устройство все еще можно без проблем купить за 40-50 евро, и это один из лучших датчиков для измерения углекислоты в сегменте

Внутри этой коробки полноценный NDIR-сенсор, который считает именно CO2, а не коррелирующую с ним летучую органику

Сам принцип измерения нехитрый и строится на оптической непрозрачности углекислого газа для волны длиной 4,26 мкм

Внутри датчика находится трубка, у которой с одной стороны - ИК-излучатель, а с другой - интерференционный фильтр и фотоприемник

Воздух попадает в трубку, часть света поглощается молекулами углекислоты, а оставшиеся фотоны попадают на приемник, где контроллер пересчитывает излучение в конкретное значение CO2 с поправкой на температуру

Недисперсионные датчики - не самая дешевая вещь на рынке. Поэтому большая часть дешевых потребительских устройств для отслеживания качества воздуха хитрит и вместо того, чтобы отслеживать непосредственно CO2, считает коррелирующие показатели

Так, например, стандартный китайский «датчик чистоты воздуха для дома и офиса» обычно считает концентрацию летучей органики TVOC и, делая допущение, что где органика - там и выдыхаемая углекислота, по хитрой формуле выводит примерные цифры

Я пробовал сравнивать показания, и в бытовых условиях обычной квартиры они не сильно отличаются от результатов прямого измерения: то тут, то там есть расхождения, которые в общем и целом на картину не влияют

Но если условия станут менее обычными, проку от такого датчика будет немного, а я держу свой датчик именно для отслеживания проблемных моментов

Поэтому вместо того, чтобы пойти и купить в продуктовой лавке дешевый TVOC, который притворяется датчиком углекислоты, эти же деньги я потрачу на переходник с уже работающего устройства на современный мир

Читаем данные

Сам по себе датчик может работать автономно. У него есть дисплей, который отображает текущие значения температуры и углекислоты, три светодиода для оценки состояния «хорошо/плохо», звуковая индикация «совсем плохо», а micro-USB-разъем используется для питания железки

Тем не менее, если подключить девайс к USB-шине и опросить его, то выяснится, что он - обычное HID-устройство, которое идентифицируется операционкой

$ lsusb
...
Bus 001 Device 005: ID 04d9:a052 Holtek Semiconductor, Inc. USB-zyTemp

Я сделал поверхностный гуглеж железки по ее VID и PID и быстро нашел оригинальное устройство, которое до брендирования российским продавцом называлось ZyTemp CO2 Monitor и выпускалось компанией ZyAura

Если поискать чуть более глубоко, то выяснится, что его протокол обмена данными нетривиален, но хорошо изучен и описан больше 10 лет назад

Документ выше обещает, что процесс работы с датчиком по USB должен выглядеть так:

  1. Инициализация. Хост отправляет устройству HID-запрос SET_REPORT типа Feature с 8-байтным ключом (в hidraw нулевой байт - номер репорта, дальше ключ)

  2. Поток данных. После этого датчик сам начинает слать 8-байтные пакеты через interrupt-эндпоинт, которые читаются обычным read()

    При этом формат пакета следующий:

    Байт

    Назначение

    0

    Код величины

    1-2

    Значение (big-endian)

    3

    Контрольная сумма: (байт 0 + байт 1 + байт 2) mod 256

    4

    Всегда 0x0D

    5-7

    Не используются

    Величины бывают такие:

    Код

    Величина

    Перевод значения

    0x50

    CO2, ppm

    как есть

    0x42

    Температура, 1/16 K

    °C = значение / 16 - 273,15

    Некоторые ревизии датчика измеряют еще относительную влажность и шлют значение с кодом 0x41, но мой конкретный экземпляр так не умеет

  3. Расшифровка. Существует две ревизии контроллера датчика, с разной степенью дружелюбия. У ранних прошивок пакеты зашифрованы с использованием ключа: байты переставляются по фиксированной таблице, XOR-ятся с ключом, сдвигаются циклически на 3 бита между соседними байтами, затем из каждого вычитается байт константы "Htemp99e" с переставленными полубайтами. В новых ревизиях шифрование убрали, и данные приходят открытым текстом

То есть датчик - это обычное HID-устройство с опциональным несложным скремблингом данных и единственным interrupt-эндпоинтом: достаточно один раз отправить ему feature-репорт с ключом, и дальше он сам, без запросов, шлет 8-байтные пакеты с показаниями

Пишем драйвер

Чтобы интегрировать датчик углекислоты в мой умный дом, мне нужен драйвер

В этой статье я не буду делать различий между драйвером как софтом, который интегрируется в операционную систему, и драйвером как адаптером для чтения данных: для поста это неважно и задачу не меняет

Задача драйвера сводится к тому, чтобы устанавливать соединение с железкой, читать пакеты данных, при необходимости расшифровывать, проверять контрольную сумму и по коду величины раскладывать значения по нужным полям

Чтобы сосредоточиться на реализации самого драйвера, я вытащил из шкафа свободную Raspberry Pi и решил попробовать реализовать протокол обмена поверх полноценной ОС. Linux уже поддерживает USB-шину и умеет работать с HID-устройствами, так что останется реализовать только прикладную часть конвертера: читать данные и передавать их дальше

Я планировал использовать версию для малинки только для того, чтобы убедиться в возможности стянуть данные с датчика, так что какой-то экспорт данных дальше мне не был нужен

Работа с данными реализована в 3 этапа:

  1. Поиск устройства.
    Драйвер обходит /sys/class/hidraw/*, в device/uevent ищет строку HID_ID=0003:000004D9:0000A052 и подтягивает USB-путь порта, product и serial

  2. Открытие сессии.
    Найденный /dev/hidrawN открывается на чтение-запись и получает feature-репорт через ioctl HIDIOCSFEATURE со стартовым ключом c4 c6 c0 92 40 23 dc 96

    Новые ревизии датчика шлют данные и без этого рукопожатия, так что ошибки драйвер игнорирует

  3. Чтение кадров.
    Последовательно выполняется poll() с таймаутом, а затем read(), который принимает 8 байт. Кадр сначала расшифровывается алгоритмом выше, и если у результата сходится контрольная сумма, берется он. Иначе проверяется исходный кадр как открытый текст
    Далее из результата чтения вытягиваются данные и код величины, которые нам и нужны

В рабочем режиме драйвер публикует данные пользователю раз в 5 секунд, а при потере связи с датчиком заново проходит этапы 1-2 и переподключается к тому же USB-порту, постепенно увеличивая паузу между попытками

Я отправил нейронку его писать и тестить на уже подключенной железке, после чего немного прибрался в кодовой базе и запустил на Raspberry

Переносим драйвер на ESP32

В виде прототипа устройство работает. Малинка интегрирована с датчиком, умеет находить устройство на шине и стабильно шлет данные в консоль

Но я не хочу гонять целую Raspberry Pi как транспорт двух чисел между USB-шиной и умным домом

Моя оригинальная идея была в том, чтобы получить отдельное устройство на каком-нибудь микроконтроллере, достаточно мощное, чтобы подключаться к домашней Wi-Fi-сети, но при этом в несколько раз меньше и без пингвина на борту

Требований к контроллеру, на котором будет работать финальная версия драйвера, немного

Он должен иметь достаточно памяти, поддерживать работу с Wi-Fi и реализовывать полноценный режим хоста на USB-шине, чтобы иметь возможность работать с датчиком

Под все эти задачи с большим запасом подходит отладочная плата ESP32-S3-DevKitC-1 N16R8

На ней распаяно целых два порта Type-C: один - настоящий USB, разведенный от чипа по GPIO19/GPIO20, второй - USB-UART-преобразователь на CH9102

Для меня это невероятно удобная конфигурация: я могу подключить датчик углекислоты по USB-шине и прошивать чип или читать с него данные по последовательному порту без необходимости переключать периферию или зависеть от OTA-обновлений

Часть драйвера, написанного для Raspberry, можно перенести почти без изменений: сам конвертер работает с уже полученными кадрами и поэтому не зависит от платформы

Все остальное придется написать заново, под новую платформу, причем на уровень ближе к железке

Linux предоставлял готовую инфраструктуру: ядро само опознавало устройство при подключении и позволяло писать и читать его как обычный файл

Для ESP32 есть библиотека USB Host, которая позволяет делать что-то похожее, но без операционки и самостоятельно

Процесс работы с датчиком тогда выглядит так:

  1. Поиск устройства. Вместо обхода sysfs регистрируемся клиентом USB Host и ждем события подключения. Когда устройство появляется на шине, открываем его, читаем дескриптор и сверяем VID:PID с 04d9:a052, после чего захватываем HID-интерфейс

  2. Стартовый ключ. Вместо ioctl(HIDIOCSFEATURE) формируем управляющий запрос руками: SET_REPORT класса HID (bmRequestType = 0x21, bRequest = 0x09, wValue = 0x0300 - тип Feature, репорт 0) с теми же 8 байтами ключа в поле данных

  3. Чтение кадров. Вместо poll() и read() ставим в очередь interrupt-передачу на эндпоинт 0x81 длиной 8 байт. Когда датчик присылает кадр, срабатывает колбэк: отдаем данные и сразу ставим следующую передачу

  4. Отключение. Вместо POLLHUP приходит событие отключения устройства: освобождаем интерфейс, закрываем устройство и снова ждем подключения

Кроме этого, ESP должна поднимать собственный веб-сервер, чтобы отдавать данные не только по MQTT, через него же принимать конфигурацию поведения и настроек сети и делать всякие полезные мелочи вроде auto discovery, чтобы адаптер был максимально легким в настройке

Если версию драйвера для Linux я писал фронтирной Fable 5 еще летом, то адаптировать код под ESP32 я предложил локальному Qwen 3.8 Flash Next в Q5-кванте, который крутится на домашнем Ryzen AI Max+ 395

В обоих случаях я отдал написание кода только что вышедшим LLM, чтобы потестить их на реальных задачах, а после сделал ревью и вычитал код, чтобы иметь возможность гарантировать хоть какой-то результат

Локальная нейронка смогла адаптировать весь код и дописать часть с UI, но не справилась с получением сырых кадров от датчика

ZyTemp инициализировался, на шине USB появлялись флаги готовности, но кадры на хост передавать никто не собирался

В процессе отладки я выяснил, что датчик углекислоты не отдает данные автоматически при подключении. Вместо этого он ждет, пока хост прочитает у него report descriptor. Linux-ядро делает это автоматически, а у нейросети не было даташита на датчик, так что ей неоткуда было взять ожидаемое поведение

Зачем датчику дожидаться запроса дескриптора, я не понимаю, тем более что сам дескриптор ничего полезного не сообщает. Он описывает один безымянный vendor-defined репорт на 8 байт, а формат кадров и шифрование в нем никак не отражены

Я сделал мелкую правку, добавив вызов hid_host_get_report_descriptor() в задачу, которая обслуживает подключение датчика, и остальную часть прошивки нейронка дописала самостоятельно

После этого я предложил фронтирному Opus 5 построить мне релиз и дописать простенький web-flasher, чутка вручную поправил дизайн веб-странички и отправил релизную сборку прошивки на чип

Собираем железку

Поскольку ZyTemp, судя по разъему micro-USB, рассчитан на работу от сети, пытаться сделать ESP32-прокладку достаточно энергоэффективной, чтобы питать ее от батарейки, я не стал, а без требований к энергоэффективности мне ничего не мешало продолжать использовать отладочную плату в готовом устройстве

Тем более что текущая конфигурация позволяла мне вообще не решать задачу о сборке девайса: на шину настоящего USB я подвесил датчик углекислоты, а ESP32 запитал через второй разъем Type-C, который разведен как USB-UART

В итоге датчик оказался запитан транзитно, через адаптер. Я пару дней погонял эту сборку в спальне, убедился в ее стабильности и наскоро спроектировал какой-то корпус под 3D-печать

Девайс получился настолько простым, что его сборка заняла меньше минуты: отладочная платка ложится в кроватку на нижней части корпуса, прижимается верхней и собирается на четырех винтах M3

Остается только воткнуть два USB-кабеля: к питанию и датчику, зарегать железку в домашней сети и разрешить Home Assistant импортировать MQTT-автоконфигурацию

У меня получилось продолжить использовать свой датчик CO2 и интегрировать его в умный дом по цене отладочной платы на ESP32 - вместо того чтобы искать новый сенсор и выяснять, точно ли он измеряет именно углекислоту в воздухе, а не коррелирующие значения

Я снова, после большого перерыва, начал смотреть на качество воздуха дома и проветривать от показателей датчиков, а не от интуиции

Неожиданно снижение CO2 по ночам помогло мне поправить качество сна, после того как я не мог выспаться все лето и собирался идти с этим к доктору

Адаптер практически не занимает места на прикроватной тумбе, и я могу таскать его с собой при переездах так же, как таскаю сам датчик углекислого газа

Послесловие

Большая часть того, что я делаю, находится под свободными лицензиями в открытом доступе

Этот проект - не исключение. Поэтому ниже я оставлю ссылки на репозитории с кодом, STL-файлами и описанием, чтобы вы смогли собрать у себя такой же или похожий конвертер данных и вернуть к жизни полезные, но неудобные в 2026 году железки

https://github.com/GrakovNe/holtek-usb-zy-temp-driver - вариант драйвера для Linux. Там есть скрипт, который позволит автоматически установить все зависимости и сам драйвер. Сверх этого останется только обеспечить транспорт значений в удобное место

https://github.com/GrakovNe/esp_32_holtek-usb-zy-temp-driver - вариант для ESP32. Прошивка адаптирована практически под любой чип ESP32-S3, который имеет достаточно памяти, так что искать конкретно мой вариант не нужно. Кроме кода, там же лежит моделька корпуса

Нейропослесловие

Это мой первый проект, где я попробовал использовать агентов, в том числе локальных, для разработки физических устройств

В целом и фронтирные, и локальные модели лажают там, где не хватает ясности, или на редких задачах вроде инициализации штучного HID-устройства на USB-шине конкретного чипа

В остальном машины исправно программируют машины. Нейронки помогли мне начать и закончить работу не за три дня, а за 12 часов за счет делегирования им понятных, зато технически сложных задач

Я искренне считаю, что все, что может быть упрощено автоматизацией, должно быть упрощено автоматизацией, причем максимально удобным и простым, но не примитивным способом

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.