[JavaScript, Разработка под Arduino, Разработка на Raspberry Pi, DIY или Сделай сам] Умная квартира на JavaScript. От светодиода до распознавания лица в камере домофона

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
30-Мар-2021 14:37


Привет! Меня зовут Антон, я ведущий разработчик в команде рекламного фронтенда ВКонтакте. Мои рабочие задачи связаны с развитием рекламного кабинета и возможностей для продвижения сообществ в приложении VK. Здесь результаты можно видеть только в браузере и телефоне, но мне давно хотелось научиться управлять объектами и в реальном мире — например, в своей квартире. Таким опытом я и хочу поделиться в этой статье: опишу, как создал и развивал свой умный дом, с какими проблемами столкнулся по ходу проекта и как их решал.Первая часть будет максимально простой: датчики, свет, диммирование, контроллеры. Если вы уже достаточно на это насмотрелись, то переходите сразу к части II. Там я рассказываю, как интегрировался с обычным многоквартирным домофоном, чтобы открывать дверь с помощью распознавания лица и не только.Часть IРазбирая старые вещи, наткнулся на коробку с датчиками Arduino: пять лет назад при переезде в новую квартиру хотел сделать себе умный дом. Тогда не сложилось, получился только небольшой прототип: Arduino + датчик движения + геркон. Всё это успешно интегрировалось для управления светом в туалете у деда — к слову, до сих пор работает. И вот я снова подумал, не попробовать ли. И понеслась...ЦельИзначально я хотел реализовать вот что:
  • полностью автоматизировать управление светом в коридоре, ванной и туалете (то есть совсем исключить человека и выключатели из этой системы);
  • написать весь код самому на NodeJS (давно хотел через JavaScript управлять объектами в реальном мире), чтобы максимально настроить под себя всю логику работы;
  • сделать управление через бота ВКонтакте;
  • внедрить всё без штробления стен, с минимальными манипуляциями на уровне отделки и как можно незаметнее.
Из стартовых знаний у меня пятилетний микропроект на Arduino, школьные и университетские познания в электротехнике — ну и большой опыт в JS, так как это моя основная работа.За основу решил взять Raspberry PI 2. Из датчиков использовать, как и в прототипе, инфракрасный датчик движения HC-SR501 и геркон для входной двери. Это магнитный размыкатель: когда дверь закрыта, геркон замкнут, ток идёт; а при открытии контакты размыкаются. Причём идея была такая: установить по одному датчику движения внутрь каждого помещения (в туалет, ванную и коридор) и ещё по одному во все дверные проёмы, чтобы фиксировать, когда и где человек проходит. Для этого я планировал в проёмах ставить PIR-элемент без линзы (ИК-датчик движения) — была гипотеза, что так он будет работать в очень узком диапазоне.
Тут меня поджидал первый сюрприз: оказывается, PIR-элемент не может работать без линзы Френеля, да и сам HC-SR501 — это громоздкая бандура, которая некрасиво смотрится в интерьере. Погуглил и нашёл ещё ряд более компактных датчиков (HC-SR505, AM312, SR602). У всех линза примерно 10 мм в диаметре. Я остановился на AM312 и подобрал высоту таким образом, чтобы он не реагировал на передвижения кошек по полу. Самая очевидная часть решена, но как определить пересечение линии дверного проёма?Датчик определения пересечения линииПытаясь подобрать что-то, отвечающее моим требованиям, я перебирал все возможные датчики на AliExpress — надеялся найти что-то готовое, но не получалось. Максимально подходящим по свойствам оказался датчик обнаружения препятствия: он состоит из инфракрасных светодиода и фотодиода. Когда появляется препятствие, ИК-свет отражается от него и фиксируется через фотодиод.
ИК-датчик препятствия HW-201Этот датчик был простым, недорогим и показывал отличное быстродействие. Единственным недостатком была дальность срабатывания: порядка 15 см. Дальше я начал экспериментировать: нужно было заставить его корректно работать на расстоянии 1 м. Первое, что я сделал, — просто отпаял светодиод и начал светить напрямую в фотодиод. Это увеличило расстояние до 30–40 см. Затем поиграл с разным номиналом сопротивлений для ИК-светодиода. Опытным путём установил, что при сопротивлении примерно в 50 омов дальность срабатывания становится около 110 см — то что нужно. Но при таком токе очень сильно греется резистор.
Мне подсказали, что ИК-светодиоды можно использовать с таким током в импульсном режиме. Главное ограничение — средний ток должен быть в диапазоне допустимых для этого светодиода (15–20 мА). Пришло время небольших расчётов: из закона Ома (I = U / R) получаем, что ток у нас порядка 60 mA = (5 V − 2 V) / (50 Om), где 2 V — это потребление ИК-светодиода. Получилась следующая схема импульсной работы: включаю светодиод на 20 мс (за это время фотодиод успевает зафиксировать свет), считываю значение датчика и выключаю светодиод на 50 мс. Имеем цикл в 14 Гц, со средним током 17 мА и холодным резистором, отлично!Но оказалось, что нельзя просто так подключить к Raspberry этот датчик, потому что у GPIO-пинов ограничен ток на 30 мА (а мы имеем в пике 60 × 4 = 240 мА). Что делать? Оказывается, есть транзисторы, которые могут запросто решить мою задачу. Несколько дней ушло на то, чтобы понять, что такое транзисторы и с чем их едят. На выходе получилось следующее: основная нагрузка легла на пин питания Raspberry (который выдерживает до 500 мА), а через GPIO-пин мы только открываем и закрываем транзистор.
Схема управления пульсацией группы ИК-светодиодовПервый прототипИтак, у нас есть датчик пересечения дверного проёма и датчик движения. На этом уже можно сделать прототип, чтобы в реальности поэкспериментировать с данными и алгоритмом. Для коммутирования нагрузки и света я использую твёрдотельное реле OMRON G3MB-202P. В саму проводку решил врезаться параллельно основному выключателю: это обеспечит полноценную работу механического выключателя в случае отказа автоматики. Вот что получилось:
Первый прототип автоматизации освещения в туалетеОтдельным вызовом была пайка контактов SMD-транзистора: я не знал, что такое SMD-корпус, когда заказывал компоненты, да и паять не умел :) И ещё важное замечание: не любой транзистор подойдёт. Необходим такой, чтобы пороговое напряжение было не больше 3 В, так как пины у Raspberry выдают всего 3,3 В.На этом этапе я оттачивал алгоритмы включения и выключения света. Причём в туалете вместе со светом включается и вытяжка (достаточно громкая), поэтому основных задач было несколько:
  • Включить свет максимально быстро, как только обнаружен человек.
  • Не выключать свет, пока человек внутри помещения.
  • Выключить свет сразу после выхода человека.
  • Свет не должен реагировать на кошек.
  • Исключить ложные срабатывания.
В итоге алгоритм сводился к настройке тайм-аутов выключения света при разных событиях с учётом текущего состояния. Например, если сработал датчик пересечения линии и свет выключен — значит, входим и нужно включить свет. Задача кажется простой, но на практике это не так: нужно учитывать много разных ситуаций. Вот некоторые:
  • входя, человек шёл широкими шагами или размахивал руками — получим подряд два срабатывания датчика пересечения;
  • человек зашёл, а потом потянулся закрыть за собой дверь — снова два срабатывания;
  • человек может уже выйти, а датчик движения до сих пор активен;
  • и самый сложный кейс: человек зашёл и находится какое-то время без движения (датчик не фиксирует). По алгоритму надо выключать свет, так как прошёл тайм-аут с момента последнего движения. Как быть?
Таких вопросов накопилось достаточно много. И для всех надо было найти решение, чтобы автоматика работала ровно так, как интуитивно хочется.ArduinoЭто был мой первый опыт работы с Raspberry, так что о некоторых особенностях я узнавал в процессе. О чём речь:
  • на Raspberry пинов общего назначения всего 11 (из 40 доступных), то есть их можно использовать, не отключая никакие заложенные возможности и протоколы;
  • все пины цифровые — чтобы подключить аналоговые датчики, как в Arduino, надо использовать не самую удобную схему с 1-wire;
  • через JS неудобно оперировать задержками в микросекунды — а они уже маячили на горизонте для управления диммированием и для считывания показаний датчика тока (но об этом позже).
Учитывая все эти моменты, я решил разделить логику: всю работу с датчиками перенести на Arduino, а через Raspberry управлять только бизнес-логикой (нагрузкой). Rasberry 15 раз в секунду по протоколу I2C запрашивает у Arduino текущее состояние датчиков. Оно состоит всего из 5 байт: первые 2 — это стейт всех 14 цифровых пинов, а ещё 3 байта распределяются по одному на каждый из трёх аналоговых датчиков, которые будут использованы (здесь я нормирую диапазон 0-1023 аналоговых значений к диапазону 0-255). Изначально использовался SPI-протокол — и это более правильно. Но что-то у меня не завелось, поэтому я задаунгрейдился до I2C. Вот он отлично завёлся, и в целом всё стабильно работает.Код Arduino получился очень простым: в основном цикле опрашиваем все датчики и складываем результат в 5 байтов ответа. По прерыванию запроса I2C отправляем их на Rasberry. Я соединил Raspberry и Arduino через USB, чтобы можно было удалённо прошивать Arduino прямо из Raspberry, а не бегать с ноутбуком.ДиммированиеОчень хотелось сделать включение света плавным, чтобы ночью он не слепил. Изначально казалось, что это вообще ерунда: в каждом магазине продают механические регуляторы яркости. В моей обывательской картине мира существовал «чёрный ящик», куда передаёшь уровень яркости, а он выполняет роль такого регулятора. Но на практике всё оказалось иначе.Потратив немало времени, я таки нашёл на AliExpress девайс за 500 рублей, который выполняет нужную мне функцию: позволяет программно управлять яркостью светодиодной лампы (если она поддерживает диммирование).
ШИМ-контроллер 3,3V ~ 5VОн принимает на вход ШИМ от контроллера и преобразовывает его в ШИМ уже для нагрузки. Единственный нюанс — маркировки L и N у него перепутаны. На столе всё заработало, но при установке в стенд-прототип я умудрился его спалить — так что в боевых условиях проверить не смог. Заказал ещё новых, но когда они пришли, устанавливать в блок управления не стал — там уже не было места. Оставил для следующего проекта.Датчики, сборка и результат первого этапаКогда прототип повисел пару недель, я обработал и устранил большинство ошибок — и решил, что пора расширяться на всю площадь. Всего я использовал:
  • 4 ИК-датчика пересечения линии дверного проёма;
  • 3 ИК-датчика движения;
  • 2 датчика тока;
  • 1 геркон (для индикации открытия входной двери);
  • 1 лазерный дальномер.
С ИК-датчиками и герконом всё понятно, расскажу про остальное. Чтобы алгоритм управления светом в коридоре был максимально адаптивным, необходимо знать о состояниях в соседних комнатах. Например, если свет включён и на кухне, и в зале, то в коридоре (промежуточном помещении) он тоже горит постоянно. Если освещается только зал или кухня, то на коридор устанавливается определённый таймаут (я задал 30 минут). Если свет вообще выключен, то в зависимости от времени суток включается или ненадолго основной свет, или ночное освещение. Поэтому внутрь выключателей света в зале и на кухне, в разрыв цепи, я поставил вот такие датчики тока.Ночное освещения я реализовал просто как пять параллельно соединённых белых светодиодов — их вполне хватает. А так как это всего 100 мА тока, то я через GPIO-пин и транзистор сделал диммирование — и ночью свет включается плавненько и красиво.Лазерный дальномер я установил в туалет, чтобы решить проблему с выключением света, если человек не двигается. То есть дальномер помогает определить, есть ли кто на унитазе :) Сам прибор работает по протоколу UART. Расстояние от контроллера до датчика порядка 5 метров, и для надёжности я хотел поставить на линию передачи данных преобразователи в RS232. Но модули, которые я купил на AliExpress, оказались нерабочими, поэтому в итоге я оставил провода как есть. Повезло, что особо ошибок не наблюдается и датчик работает.Ещё я хотел поэкспериментировать с 3D-печатью. Поэтому корпус блока управления спроектировал сам и отдал на печать.
После расстановки всех начальных компонентов получилось так:
Изначально я собирался напрямую втыкать провода в пины контроллера, но мне подсказали, что так лучше не делать. Из доступного я выбрал клеммники. Но, как покажет дальше практика, это решение тоже не супер и лучше использовать быстросъёмные коннекторы. Например, мне показалось, что удобным будет коннектор RJ45, так как все провода — это витые пары UTP 4 Cat 5e. Но это была ошибка, и я потратил немало сил и нервов, пытаясь что-либо замерить или изменить в блоке. Дело в том, что он установлен в шкафу 18 см шириной — и невозможно его оттуда вынуть, не отвинчивая все клеммники.
И вот как в итоге выглядят мои датчики (дальномер оставил пока так, его надо аккуратно врезать в наличник двери, пока руки не дошли):
Часть II. ДомофонКогда у нас появился ребёнок, домофон перешёл в режим «тихо» — то есть стал фактически беззвучным. Поэтому, работая над умным домом, я решил интегрировать в него и домофон. Сначала хотел просто сделать уведомление в боте о том, что идёт звонок, но в процессе список задач расширился до такого:
  • сохранить полную работоспособность основного домофона;
  • детектировать звонок и отправлять уведомление;
  • делать фотографию с подъездной камеры и отправлять в бота VK;
  • распознавать лицо звонящего и, если это я или жена, сразу открывать дверь;
  • добавить команду «курьер», по которой голосом Алисы будут воспроизводиться быстрые инструкции («этаж такой-то, затем направо») и откроется дверь подъезда;
  • задать команду «открыть», по которой воспроизводим «Заходите!» и открываем дверь;
  • реализовать автоответчик. Если звонок длится больше заданного интервала, воспроизводим текст («Никто не может подойти, оставьте сообщение после сигнала»), записываем 10–15 секунд видео и отправляем его сообщением в бота.
АудиоДля детектирования звонка сделал такую схему:
Параллельно входной линии домофона подключаем эту схему. При звонке, то есть наличии сигнала в линии, имеем положительный потенциал на затворе n-канального полевого транзистора. Я использовал делитель напряжения, так как у меня транзистор с максимальным напряжением (затвор-исток) 10 В, а в линии потенциально может быть до 12 В. Открываясь, он меняет потенциал в точке Sout с высокого на низкий, что мы и детектируем в Raspberry. Входной сигнал — это звуковой сигнал звонка, так что мы детектируем множественные срабатывания и фильтруем их программно. В процессе ещё возникла идея установить конденсатор, чтобы сгладить пульсации, но я уже не стал лишний раз лезть в корпус.Чтобы открывать дверь, записывать аудиосигнал и передавать его обратно в линию, я решил использовать схему самой дешёвой домофонной трубки. То есть просто купил ещё одну трубку (в моём случае — УПК-7 VIZIT), выпаял кнопку открывания, динамик и микрофон, а вместо них сделал пины, через которые подключаюсь уже к контроллеру. К сожалению, отдельного фото итоговой схемы нет, поэтому покажу промежуточный вариант — тут ещё динамик и микрофон на месте:
Модернизированная трубка УПК-7При поступлении сигнала я отправляю сообщение в бота. Далее, чтобы выполнить любую логику, необходимо через реле отключить основной домофон и подключить мою модернизированную трубку.Вместо кнопки открытия я поставил тоже n-канальный полевик и по команде подаю на затвор высокий потенциал (+3,3 на пине). С этим проблем не возникло — всё заработало с первого раза (большая редкость).Затем я научился передавать звук. Аудиовыход Raspberry соединил со входом M− бывшего микрофона. Это важно, так как изначально я интуитивно соединил M+ c аудиовыходом, а M− c audio GND. И долго искал проблему: звук воспроизводился, но был очень тихим. А дело в том, что через audio GND получился делитель напряжения, то есть в схему трубки сигнал почти не попадал. Воспроизвожу звук я просто через вызов утилиты aplay из кода NodeJS.C записью звука пришлось немного попотеть. В Raspberry нет микрофонного входа, поэтому пришлось купить за 300 рублей аудиокарточку с USB. Затем передавать выход S+ через конденсатор (это важно) в микрофонный вход этой карты. Опять же: только один провод, потому что гальванической развязки нет и GND общая. Записываю через утилиту arecord. Качество не очень высокое (есть наводка 50 Гц) — но раз это нужно только для кейса с автоответчиком, пока решил оставить как есть.
arecord -D hw:2,0 --format=S16_LE --rate=16000 --file-type=wav -d 10 out.wav
ВидеоОтвинтив домофонную трубку от стены, я увидел, что в квартиру подведены четыре провода: два задействованы (LN+ и LN−), а два нет (те, что отвечают за видеосигнал). Сразу захотелось их использовать. Почитал в интернете и выяснил, что в нашем домофоне аналоговый видеосигнал стандарта PAL. Поэтому пришлось купить ещё один внешний USB-девайс для захвата аналогового видео. В целом он работает, но не без сюрпризов: пришлось несколько раз переподключать его. Плюс в том, что никаких драйверов для него не потребовалось — ни на маке, ни на Raspberry. Захват кадра делаю через утилиту ffmpeg:
ffmpeg -loglevel error  -i /dev/video0 -vframes 2 -r 5  output/grab-%d.jpg
Вот я протестировал всё на столе, собираю демостенд и хочу увидеть уже реальную картинку с домофона. Но не тут-то было: оказывается, ко мне не подведён видеосигнал. Пришлось звонить в управляющую компанию, чтобы мастер скоммутировал его в мою квартиру.Чтобы проверить стабильность, я решил написать программу, которая будет раз в секунду делать захват кадра, распознавать там лица и отправлять мне картинку, если лицо найдено. Я думал, что сделаю это за 5 секунд, ведь есть face-api.js. Запустить это на Raspberry было не очень просто, но получилось. Однако спустя рандомное время работы программы Raspberry просто крашилась без внятных логов. Я решил: ок, попробую что-то на питоне (OpenCV). Нашёл, запустил — эффект тот же. Но одна фотка всё же успела распознаться:
Потерпев неудачу, я решаю немного изменить формулировку задачи. Теперь я хочу не распознавать лицо, а определять движение в кадре с помощью библиотеки sharp-phash и после этого отправлять изображение. Получилось очень чётко:
Этот алгоритм можно применять и интереснее: например, выявлять движения в больших помещениях, не монтируя десятки датчиков. Можно поставить одну камеру, разбить картинку на зоны и в каждой определять движение. В следующей квартире я обязательно использую этот подход.Идея с распознаванием лица меня не отпускала, поэтому решил поискать веб-сервисы. И сразу наткнулся на Amazon Rekognitoin, который впоследствии и использовал. Но для интеграции с их API потребовалось чуть больше времени, чем я ожидал: даже по всем туториалам не сразу всё получилось. Самый жёсткий прикол, который там меня ожидал: если отправляешь две фотографии на сравнение и в одной из них нет лица, то AWS отвечает: «Invalid request params!» WTF?!В итоге получилась следующая схема: поступает звонок, я делаю две фотографии с интервалом 200 мс. Отправляю обе на сравнение с предзагруженными снимками в AWS (на которых я и жена). Если получил совпадение больше 95% в обоих случаях, открываю дверь. На все манипуляции в среднем уходит 2,4 секунды, из них 1,3 — это захват кадра. Если знаете, как можно ускорить, — подскажите :)
В первой итерации для автоответчика я записывал только звук — но раз у меня уже есть видео, грех не записать его. Так как видео и аудио идут из разных устройств, то для совместной записи я хотел использовать такую команду:
ffmpeg -y -i /dev/video0 -t 10 -f alsa -ac 1 -i hw:2,0 -t 10 -pix_fmt yuv420p output/video.mp4
То есть указываю для ffmpeg два input: видео и аудио. Ожидаю, что на выходе будет видео со звуком — но это так и не получилось завести, ffmpeg упорно отказывался (а вот если указать в качестве аудиоинпута просто файл, то всё работает). Поэтому пришлось сделать небольшой workaround:
await Promise.all([
  execute(`ffmpeg -y -i /dev/video0 -t 10 -pix_fmt yuv420p output/video.mp4`),
  execute(`ffmpeg -y -f alsa -ac 1 -i hw:2,0 -t 10 output/audio.mp3`)
]);
await execute('ffmpeg -y -i output/video.mp4 -i output/audio.mp3 -c:v copy -c:a copy output/merge.mp4');
Записываю отдельно в два потока видео и аудио, конвертирую их по ходу дела в mp4 и mp3 соответственно и следующим проходом объединяю эти два файла. Ещё нюанс: флаг -pix_fmt yuv420p важен, чтобы видео в приложении отображалось и воспроизводилось корректно.Отдельно хочу описать проблему, на понимание которой ушло много времени. Протестировав всё по отдельности, я добавил новые элементы в блок управления. Всё работало, но вдруг мне приходит сообщение о звонке в домофон... И я ничего не понимаю. Смотрю на домашний видеофон (установил его вместо трубки): стоит незнакомая девушка, звонит. И тут я понял, что это не случайность: абсолютно все звонки коммутировались в нашу квартиру. Я быстро отключил нововведения, и шквал прекратился. Я долго искал проблему и в итоге установил, что дело в отсутствии гальванической развязки на видеосигнале. Поэтому GND видеосигнала через USB объединяется с LN−, и это даёт такой неожиданный результат. Устранить это получилось, установив в разрыв видеосигнала видеотрансформатор. Финальный аккорд в эпопее — все поставленные цели выполнены!Вот упрощённая схема моего умного дома:
Вот упрощённая схема моей умной квартиры:И небольшое видео с демонстрацией работы.Извините, данный ресурс не поддреживается. :( Рад, что вы дочитали до конца, и надеюсь, статья была для вас полезной. Спасибо за внимание!
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_javascript, #_razrabotka_pod_arduino (Разработка под Arduino), #_razrabotka_na_raspberry_pi (Разработка на Raspberry Pi), #_diy_ili_sdelaj_sam (DIY или Сделай сам), #_umnyj_dom (умный дом), #_domofon (домофон), #_arduino (ардуино), #_raspberrypi, #_pervye_shagi_v_elektronike (первые шаги в электронике), #_raspoznavanie_litsa (распознавание лица), #_blog_kompanii_vkontakte (
Блог компании ВКонтакте
)
, #_javascript, #_razrabotka_pod_arduino (
Разработка под Arduino
)
, #_razrabotka_na_raspberry_pi (
Разработка на Raspberry Pi
)
, #_diy_ili_sdelaj_sam (
DIY или Сделай сам
)
Профиль  ЛС 
Показать сообщения:     

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

Текущее время: 22-Ноя 02:46
Часовой пояс: UTC + 5