Добрый день, уважаемыее. В этой статье я несколько отойду от своего традиционного подхода к DYI – нашей основной целью станет быстрое и эффективное получение результата, а не изобретение велосипедов с целью самообучения, поэтому даже люди, впервые держащие паяльник, смогут все это повторить и получить готовое устройство за ~1000 рублей и один день.
Введение
Говорят, лень – двигатель прогресса. Это, в принципе, верное высказывание – лично меня всегда напрягала необходимость вставать и идти гасить свет, когда уже почти заснул. Поэтому еще на втором курсе университета я собрал простое устройство на AVRке, позволяющее включать и выключать люстру с помощью любого пульта дистанционного управления. Оно до сих пор висит в одной комнате и работает в режиме 24/5 уже 5 лет.
После была попытка избавиться от пульта – система распознавания голосовых команд, на такой же AVRке, которую я заодно сдал как курсовую в универе – она даже заработала, но не настолько удовлетворительно, как хотелось бы, поэтому менять шило на мыло я не стал.
Через некоторое время сформировалось понимание, что в подавляющем большинстве случаев, когда мне требуется включить или выключить свет, у меня в пределах досягаемости и непосредственной близости имеются либо ПК, либо планшет, либо коммуникатор – следовательно, наиболее удобным способом будет управление через них.
Я набросал вариант реализации на специальных радиомодулях, NRF24L01P – действительно потрясающих, так, например, размер SMD-варианта этого модуля составляет всего 10х15 мм! При этом ими удобно управлять, у них есть собственный протокол, который хендлит адресацию, мультиприем, авто-подтверждение приема и т.п.
Соответственно, по моим прикидкам, должен был быть какой-то USB-донгл с этим модулем, в перспективе – подключенный к компьютеру с веб-сервером, чтобы дать возможность управлять с мобильных устройств, и исполнительные девайсы с ответными трансиверами.
Очень долго у меня не доходили руки до всех этих систем – уже год у меня из стены торчали провода, которые я каждый раз замыкал руками, включая люстру, и говоря себе «ну вот разгребу дела на работе, и сяду разведу платы».
К счастью, последние проекты на работе были связаны с мобильными устройствами, которые должны были уметь несколько больше, чем может дать STM32 и любой другой Cortex M3, и я достаточно много времени провел работая с OpenWRT и устройствами типа всеми любимого TL-MR3020 и TL-WR703, о настройке которых я недавно написал статью. Вместе с этим опытом пришло осознание того, что подобные задачи решаются невероятно быстро и очень эффективно при помощи устройств с linux и wi-fi на борту. Хотя бы потому, что Wi-Fi дает куда более удобные (а в случае с необходимостью передачи видео/аудио – практически единственные доступные) средства беспроводной связи, поддерживаемые всеми современными устройствами.
Конечно, если объектов управления десятки, имеет смысл сделать более тупые исполнительные устройства на кастомных модулях, и оставить серверам с линуксом роль узлов. Но в случае, когда мы делаем продукт для реального использования у себя дома, а не в расчете на гипотетические объекты, такой подход приведет только к жуткой потере времени и денег.
Итак, давайте соберем за день устройство, которое позволит включать-выключать бытовой прибор (до 1.6КВт потреблением) по Wi-Fi, с красивым фронтэндом и без долгой возни с платами, прошивками и драйверами.
Программная часть
Начнем, вопреки традиции, не с железа, а с программной части, ибо она достаточно простая и проверить ее вы сможете сразу же после покупки роутера, даже без его разборки.
Надеюсь, к этому моменту вы уже успели купить TL-MR3020 и, может быть, даже прочитали мою статью по пересборке ядра. На самом деле, можно обойтись даже без пересборки, просто перешейте его прошивкой с официальной вики проекта OpenWRT – если потом возникнет желание, вы легко сможете сделать свою прошивку, встроив в нее необходимые скрипты.
Итак, как будет устроен наш прибор?
- Так как нам важно сохранить минимальное количество деталей, мы не будем использовать никаких внешних флешек и хабов, поэтому размер доступной нам памяти ограничен 4 МБ, из которых свободно, обычно, около 1-1.5 МБ.
- Учитывая пункт 1 выберем самый легковесный веб-сервер, поддерживающий CGI – идеальным вариантом стал 20-килобайтный mini-httpd безо всяких надстроек.
- В качестве cgi-скрипта воспользуемся обычными башевскими скриптами. В принципе, место позволяет поставить минимальную сборку питона. Правда, боюсь, без пересборки ядра вы это не осуществите – т.к. все, что вы ставите после прошивки устанавливается в JFFS, то и занимает оно намного больше, чем если бы было вбилдено изначально в SquashFS. В порядке эксперимента, я вбилдил и python-mini и mini-httpd прямо в образ, выкинув оттуда все лишнее – при билде JFFS образа система кинула эрроры о превышении размера, оставив на память недособранные куски, но SquashFS-образ собрался нормально, оставив мне еще ~250 КБ свободного места – для наших нужд вполне хватит. Еще одно замечание – старт питона с внутренней флешки выполняется намного медленнее, чем старт башевского интерпретатора (и явно медленнее, чем с внешней USB-флешки, сказывается SPI-интерфейс), поэтому при использовании питоновских скриптов для CGI отклик будет составлять около 1 секунды – не очень критично, но слегка раздражает. Впрочем, если бы я писал что-то более сложное, чем дрыганье одним GPIO, меня бы куда больше раздражал огромный и мерзкий баш-скрипт, и я бы предпочел смириться с секундным стартом интерпретатора питона
- Баш-скрипт будет дергать один из доступных нам GPIO, на котором висит светодиод. Светодиод мы с вами потом отпаяем и привесим на него твердотельное реле, управляющая цепь которого является по сути таким же светодиодом.
- Index.html будет содержать в себе небольшой js-код для отображения красивой картинки с лампочкой, которая будет зажигаться/гаснуть при нажатии, выполняя асинхронный запрос к CGI-скрипту, а также будет дергать его каждые N секунд, на случай если кто уже зажег лампочку с другого компьютера – чтобы обновить картинку в соответствии со статусом.
- Запитаем мы это все от идущего в комплекте блока питания, предварительно его разобрав, вынув из корпуса сам модуль, чтобы уменьшить размеры итогового девайса. Этот шаг опционален, если хотите – можете питать как вам удобнее.
Давайте начнем. На данный момент вы должны уже перешить роутер на OpenWRT и подключиться к нему по Ethernet-кабелю.
Заходим по SSH/WinSCP на роутер и идем в /etc
Настраиваем wi-fi
для этого идем в /etc/config/network и пишем там
config interface 'wlan'
option proto 'dhcp'
Это обеспечит получение wi-fi контроллером роутера IP от DHCP нашего домашнего раздатчика вай-фай в лице другого роутера.
В /etc/config/wireless заботливые first-start скрипты уже сгенерили настройки радио, осталось закомментить строку #option disabled 1 включая модуль wi-fi и в секции ниже, описателе интерфейса, написать что-то типа
config wifi-iface
option device radio0
option network wlan
option mode sta
option ssid тут-имя-вашей-точки
option encryption psk2
option key тут-ваш-пароль
Данные настройки приведены для WPA2-PSK. Для WPA-PSK encryption будет psk, для открытой точки none, для вепа, соответственно, wep – более подробно лучше прочитать на официальной вики тут и тут.
Проверяем что все хорошо при помощи
wifi down
wifi
iwconfig wlan0
Если все ОК, iwconfig сообщит вам что вы присоединены к вашей точке доступа, после чего от эзернета его следует отрубить и продолжить настройку по wi-fi.
Настраиваем GPIO
Теперь нам надо дать доступ к GPIO пинам из юзер-спейса. Ядро это умеет само по себе, ничего делать для этого не нужно, но GPIO уже захвачены драйвером мигания светодиодами. При желании можно не трогать светодиоды и их драйвер, так как, согласно вики, есть несколько незадействованных GPIO, подтянутых к земле, подтяжку которых можно отпаять, после чего использовать их в собственных целях. В этом случае выгружать драйвер не нужно, достаточно только экспортировать GPIO (показано ниже). Но мы воспользуемся пинами со светодиодами, чтобы сразу увидеть результат трудов:
Пишем в /etc/rc.local
rmmod leds_gpio
echo 0 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio0/direction
echo 0 > /sys/class/gpio/gpio0/value
Этим мы выгружаем драйвер, захвативший ресурсы, после говорим, что хотим использовать в юзерспейсе пин 0, настраиваем его на выход и выводим туда 0, выключая светодиод. Теперь зажечь его можно командой
echo 1 > /sys/class/gpio/gpio0/value
А погасить – вот этой:
echo 0 > /sys/class/gpio/gpio0/value
Только не забываем, что наш скрип еще не выполнился, поэтому сначала перезагрузим роутер командой reboot, заодно проверив как после перезагрузки поднимается wi-fi.
Настраиваем mini-httpd
Если вы пересобрали ядро и встроили в него mini-httpd, то переходите сразу к настройке, если нет — ставим его из репозитория
opkg update
opkg install mini-httpd
Теперь внесем небольшие правки в /etc/mini-httpd.conf, так чтобы его первая строка выглядела как
cgipat=cgi-bin/**.sh
Теперь сервер будет пинать все что лежит в /www/cgi-bin/ и чье имя оканчивается на .sh как команду, транслируя ее ответ браузеру.
Настраиваем аппаратную кнопку
На случай если нам не захочется тянуться к wi-fi, повесим дублирующее действие на единственную кнопку: в /etc/hotplug2.rules убираем галочку перед словом buttons, чтобы нажатие на кнопки вызывало скрипты-обработчкики из соответствующей папки, строка теперь должна выглядеть так:
SUBSYSTEM ~~ (^net$|^input$|button$|^usb$|^ieee1394$|^block$|^atm$|^zaptel$|^tty$) {
После этого идем в /etc/hotplug.d/, создаем там папку button, а в ней – скрипт buttons (не забывая выставить ему права на выполнение командой chmod +x buttons!) со следующим содержимым:
#!/bin/sh
if [ "$BUTTON" = "wps" ]; then
if [ "$ACTION" = "released" ]; then
state=`cat /sys/class/gpio/gpio0/value`
if [ "$state" = "0" ]; then
echo 1 > /sys/class/gpio/gpio0/value
else
echo 0 > /sys/class/gpio/gpio0/value
fi
fi
fi
Теперь нажатие на кнопку будет переключать состояние светодиода. При желании можно сделать чтобы светодиод под этой кнопкой светился в противофазе с управляющим, то есть чтобы при погашенной люстре кнопка аппаратного включения была подсвечена для упрощения ее поиска в потемках – для этого просто экспортируйте пин 26 в rc.local также, как экспортировали пин 0, и добавьте аналогичные строки вывода 1/0 в его value рядом с выводом в value нулевого пина.
Это можно проверить прямо сразу – если вы все сделали правильно, то нажатие на кнопку вызовет изменение состояния светодиода.
Пишем CGI-скрипт
Теперь покидаем /etc и идем в /www, где создаем директорию cgi-bin, а в ней – скрипт light-control.sh, которому делаем chmod +x, со следующим содержимым:
#!/bin/sh
echo "content-type: text/html"
echo ""
input=$(echo $QUERY_STRING | tr "&" "n")
for param in $input
do
name=`echo $param | cut -f 1 -d "="`
value=`echo $param | cut -f 2 -d "="`
if [ "$name" = "action" ]; then
if [ "$value" = "on" ]; then
echo 1 > /sys/class/gpio/gpio0/value
elif [ "$value" = "off" ]; then
echo 0 > /sys/class/gpio/gpio0/value
fi
fi
done
cat /sys/class/gpio/gpio0/value
Первые две строки после #!/bin/sh обязательны для CGI-скрипта – говорим браузеру что все ок и мы сейчас выдадим хтмл, после чего шлем два rn в соответствии с требованиями стандарта.
Потом берем из переменной $QUERRY_STRING параметры, куда их поместил заботливый mini-httpd, и разбираем каждый на name и value. Вообще, тут будет использоваться только один параметр, но пусть код будет в более общем виде, на случай если решим расширить.
Дальше проверяем – если нам передали action=on, то зажигаем диод, если action=off – гасим.
А после всегда возвращаем то, что прочитали из value – 1 если диод горит, 0 – если нет. Таким образом, если мы дернем наш скрипт извне с каким-либо параметром кроме action=on/off или вообще без параметра, мы получим от него текущий статус светодиода.
Пишем фронтэнд
Кидаем в директорию /www скаченный с офф. сайта jquerry — с ним действительно удобно делать аяксовые запросы – я видел все эти java-script’ы впервые в жизни (т.к. это самая дальняя от моих занятий и интересов область), но за 5 минут при помощи гугла осилил нужные мне действия. Сохраняем jquerry под именем jquerry.js
Туда же сохраняем представленные ниже картинки под именами lamp_on.jpg и lamp_off.jpg, символизирующие нашу люстру – их сделал из стащенной с гугла картинки путем разрезания ее на две части и легкой подгонки по размеру друг к другу. При желании можно использовать любые картинки.
Там же создаем index.html, в который пишем
<html>
<head>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
function LampImgOn()
{
$('#lamp_off').hide();
$('#lamp_on').show();
}
function LampImgOff()
{
$('#lamp_on').hide();
$('#lamp_off').show();
}
function ProcessResult(data)
{
if(data=="1n")
LampImgOn();
else
LampImgOff();
}
$(document).ready(function()
{
$.ajaxSetup({
cache: false
});
$('#lamp_on').hide();
jQuery.get('/cgi-bin/light-control.sh', {'action': 'none'}, ProcessResult);
$('#lamp_on').click(function()
{
jQuery.get('/cgi-bin/light-control.sh', {'action': 'off'});
LampImgOff();
})
$('#lamp_off').click(function()
{
jQuery.get('/cgi-bin/light-control.sh', {'action': 'on'});
LampImgOn();
})
window.setInterval("jQuery.get('/cgi-bin/light-control.sh', {'action': 'none'}, ProcessResult)",2000);
});
</script>
</head>
<body bgcolor="#000000">
<img id = 'lamp_off' src = 'lamp_off.jpg' height = '40%' style='display:none'>
<img id = 'lamp_on' src = 'lamp_on.jpg' height = '40%' style='display:none'>
</body>
</html>
Тут мы на размещаем на страничке две изначально невидимые картинки с зажженной и погашенной лампой, а ява-скрипт после старта запрашивает аяксом от CGI (при помощи параметра action=none) текущий статус, а результат передаем на ProcessResult, который активирует ту или иную картинку. Эта же функция зовется после асинхронных запросов на переключение статуса (которые выполняются в обработчиках события клика на картинки), а также заводится таймер, которые каждые 2000 мс дергает скрипт на предмет статуса, чтобы отображать актуальные данные в случае, когда кто-то изменил их с другой страницы или аппаратной кнопкой.
Вот, собственно, и все – теперь можно зайти браузером на IP нашего роутера и насладиться красивой страничкой, управляющей светодиодом. Если вы нажмете на кнопку роутера при открытой страничке и подождете 2 секунды, картинка изменится сама за счет запроса, выполненного таймером.
На этом программная часть закончена, переходим к железке.
Железо
Что касается аппаратной части, то тут ситуация двоякая. Собственно схема у нас простейшая – управляющая цепь твердотельного реле по сути – тот же светодиод, и все что нужно – обеспечить протекание через него достаточного для включения тока. У S202S02 это не меньше 8 мА.
К сожалению, напрямую завести его от GPIO не получится – напряжение на GPIO 2.6В, после чего стоит резистор в очень маленьком корпусе номиналом 260 Ом и светодиод с падением напряжения около 1.5В. Таким образом, через него течет около 4-5 мА. Падение на S202S02 от 1.2 до 1.4В, так что то там будет примерно такой же. Можно, конечно, поискать резистор в таком же корпусе, но меньшего номинала, перепаять его, и нагрузить несчастный GPIO на 10 мА, но лучше пойти более простым путем – роль светодиода у нас исполнит переход база-эммитер NPN-транзистора, через который при падении в 0.75В потечет ток в 0.7 мА а в коллектор мы ему включим управляющую цепь реле.
Проблемы же состоят в другом. Их, собственно, две:
- Нужно отпаять светодиод (у нас это светодиод LED4) и припаять вместо него наш транзистор
- Нужно как-то упихать в корпус реле (ну это нетрудно, оно мелкое) и внутренности блока питания.
Что касается пункта 1. Текстолит, на котором выполнена схема, попросту говоря дерьмовый.
Дорожки и контактные площадки срывает моментально, припой – подстать текстолиту – начинает плавиться когда этот самый текстолит уже начинает гореть.
Что касается пункта 2. Будь корпус на 5 мм выше – проблем бы не было. Но увы, конденсаторы блока питания и импульсный трансформатор слишком высоки, чтоб корпус закрылся.
К сожалению я не нашел какого-то идеального волшебного решения, позволяющего красиво и быстро запихать в корпус невпихуемое. Костыльных вариантов много – начиная от покупки пластикового корпуса, в который это все влезет (нормальный, в общем-то, вариант), и заканчивая синей изолентой и резиночками для удержания в целости исходного корпуса.
Я выбрал нечто среднее – т.к. у меня оставался полиморф-пластик, я проложил узкую его полоску по периметру исходного корпуса и вдавил в нее крышку.
Так что в этом вопросе я рекомендую каждому найти устраивающее его решение самостоятельно. Разумеется, если вы будете использовать большой корпус, либо приделаете БП вне этого мелкого, вам совсем не обязательно отпаивать от платы разъемы, я просто расскажу что сделал я.
Чтобы плата могла влезть в корпус вместе с блоком питания, я отпаял от нее все разъемы (Ethernet, USB-Host, micro-USB-Power), а также развязку от эзернета. Плата резко стала плоской. Если будете это делать – будьте очень, очень внимательны. Я поплатился за свою невнимательность, установив слишком сильный поток из фена, в результате чего отпаялся критичный конвертер U5, дающий 3.3 В системе. Более того, его сдуло на пол и пришлось его долго искать – но, к счастью, удалось припаять его обратно.
Обращаем внимание на перемычку R113 (обведена красным) – по умолчанию она разомкнута. Замкнув ее мы сможем подавать питание на роутер через разъем USB-Host – а туда подпаяться на порядок удобнее и надежнее, т.к. он не SMD. Поэтому замыкаем R113 и спокойно паяем два провода для питания в отверстия из-под разъема.
Заметили, как тяжело паять провод в «земляное» отверстие? Оно соединено с полигонами земли на всех слоях, где они есть – теплоотвод бешеный, мой паяльник еле справился.
А вот так выглядят внутренности блока питания (рядом с платой роутера для масштаба)
Отпаиваем два хилых провода, которые раньше шли к вилке, и припаиваем туда что-то более надежное, что потом соединим с сетью 220В. Заодно отпаиваем его USB-разъем, так как соединим его напрямую с нашими проводами питания роутера.
Готовим реле, паяя так, чтобы потом силовые провода ничего не коснулись. Я подпаял вот так:
После пайки запихиваем в термоусадку, либо заматываем изолентой, либо покрываем сверху каким-нибудь герметиком или чем-то аналогичным (кстати, на фото блока питания видно этот самый герметик — он был изначально, китайцы тоже заботятся о ТБ).
Далее паяем транзистор – Я взял первый попавшийся под руку 2N6517 и подпаял его базой к той площадке, где у LED4 было написано +, эммитером – ко второй.
Так вот. Так делать не надо. Потому что достаточно одного неверного движения и вы сорвете обе площадки, придется потом паяться к резистору. Лучше подпаяйте тоненькие проводки к обеим площадкам, закрепите их на плате и выведите в безопасное место. И – да – если вы будете паять неаккуратно, то рискуете, как я, коснуться паяльником выводов микросхемы памяти и закоротить их. После чего будете проклинать свои кривые руки и час пытаться очистить их оловоотсосами, оплетками для выпайки, иголками – проверено на себе. К счастью, очистить таки удалось, но напряжно было неимоверно.
Осталось совсем немного – теперь у нас есть выход типа «открытый коллектор» (оставшийся вывод транзистора), к которому паяем резистор из расчета падения 1.3 вольта на оптопаре – то есть, при питании от 3.3В (их проще всего взять, на нераспаяном разъеме P1 справа на рисунке выше это четвертый пин), мы должны обеспечить ток где-то в 10 мА, что означает ровно 200 Ом.
Теперь аккуратно кладем блок питания кондерами вниз на плату роутера, не забыв проложить бумажкой – металлический верх кондера не должен быть под напряжением, но перемкнуть что-нибудь может, так что пусть будет бумажка. Провода питания роутера запаиваем в отверстия от разъема USB на блоке, плюсовой пин реле припаиваем к 3.3В (отверстие 4 на P1), минусовой – через 200 Ом резистор к коллектору транзистора.
Спиливаем одну из бобышек крышки, чтобы она не упиралась в блок питания, выступ, предназначенный для нажатия на кнопку наращиваем при помощи обрезка стержня от ручки или того же полиморфа.
Ну и прижимаем это все крышкой, закрепляя ее как вам больше нравится – полиморфом, изолентой, резиночками – на что хватит фантазии и материалов.
Осталось подвести 220В к проводам питания роутера, а коммутируемые провода реле включить вместо выключателя люстры или любого другого бытового электроприбора.
Получилось? Поздравляю, теперь в вашей люстре есть веб-сервер с CGI под управлением Linux, отдающий пользователям веб-фронтэнд на JavaScript, и все это – примерно за 1000 рублей и один день возни.
Автор: Ariman