В комментариях к статье об опыте изготовления «интернет розетки» мое внимание привлекли два комментария. Один, в котором утверждалось, что такая штука, по сути, бесполезна, показался мне несправедливым — хорошо помню, как мне однажды понадобилось, например, дистанционно «ресетить» одну хитрую штучку и думаю, что я не одинок. А вот идея о том, что можно решить подобный вопрос проще и дешевле, использовав, например, TP-LINK TL-MR3020 + OpenWRT показалась мне дельной. Я решил к тому же обойтись без разборки устройства, программирования и микроконтроллеров — короче, сделать решение как можно доступнее. И у меня это почти получилось!
В качестве устройства вывода используем обычную клавиатуру USB. Встречая в супермаркетах клавиатуры отвратительного качества по смешным ценам, я всегда удивлялся — для чего же она может понадобиться? Да вот же для чего! В клавиатуре три светодиода, которыми можно программно управлять с любого устройства, эту клавиатуру поддерживающего. Заменив светодиоды цепью управления — получаем управление тремя нагрузками. В качестве бонуса – возможность дальнейшей доводки устройства для использования как устройства ввода. Конечно, можно не стрелять из пушки по воробьям, собрать (или приобрести) простейшее устройство на микроконтроллере, у которого будут неосопоримые преимущества (большее количество входов/выходов, нормальные логические уровни и т.п.). Но у «клавиатурного» подхода тоже есть плюсы — он доступен людям, незнакомым с МК, не имеющим программатора. Можно просто взять и сделать работающее устройство сегодня, сейчас. Кроме этого, среди плюсов:
- решение легко переносимо на другие платформы: я не проверял, но в теории это можно воткнуть в любой «ASUS» или «D-Link», мирно раздающий инет из своего угла (если у него есть USB) — и будет работать;
- роутер при этом можно использовать по прямому назначению;
- не требуется разборки роутера и вмешательства в его схему (а TL-MR3020, кстати, нелегко разобрать);
- не тебуется программирования (если не считать программой небольшой шелл-скрипт) и компиляции;
- все компоненты можно приобрести в компьютерном магазине (это если просто «помигать светодиодом», для реального управления придется купить пару доступных радиокомпонентов), при этом общие затраты составят менее 1500 рублей.
Ставим OpenWRT и настраиваем сетевой интерфейс
Для этого шага понадобится, собственно, роутер. Мой мне обошелся в 850р.
Страница модели на openwrt.org дает исчерпывающие инструкции по перепрошивке и разрешает нам пользоваться снэпшотом транка. Сам же робко предложу взять бету Attitude Adjustment 12.09. Тем более, что на момент публикации снэпшоты недоступны на сайте openwrt, а A.A.12.09 наконец-то выложили.
Установка состоит из нескольких элементарных операций:
- Настройте сетевое подключение вашего ПК на автоматическое получение IP-адреса и подключите роутер Ethernet-кабелем (после перепрошивки WiFi-интерфейс будет отключен)
- Войдите на страницу администрирования роутера (адрес по умолчанию 192.168.0.254 пользователь:admin, пароль:admin )
- На вкладке System Tools > Firmware upgrade выберите скачаную ранее прошивку, нажмите «Upgrade» и дождитесь, пока индикатор загрузки дважды дойдет до 100%.
- Так как сетевые параметры OpenWRT по умолчанию отличаются от TP-LINK'овских — самый простой способ переинициализировать интерфейс — отсоединить и снова подсоединить ethernet-кабель.
- Заходим на роутер по telnet (192.168.1.1 — адрес «свежей» OpenWRT по умолчанию) и задаем пароль командой passwd — теперь роутер доступен по SSH (и недоступен по telnet).
Для установки дополнительных пакетов нужен доступ к репозиторию через интернет, что требует его конфигурации как клиента локальной сети.
Например:
Для настройки статического IP в сегменте 192.168.1.x сделаем следующее:
Где 192.168.1.1 — адрес шлюза в нашей сети, а 192.168.1.222 — незанятый IP, который и будет присвоен нашей коробочке. Команда «uci changes» предоставляет возможность просмотреть все внесенные изменения. Я стараюсь не пренебрегать этой возможностью, так как устройство с неправильно настроенным интерфейсом, будучи недоступным извне, превращается в «зомби». На случай, если все таки неприятность произошла, в OpenWRT предусмотрен «режим восстановления»: при загрузке, как только кнопка «WPS» начнет мигать, зажмите ее — MR3020 загрузится с сетевыми параметрами по умолчанию.
Если в сети работает DHCP, можно задать автоматическую конфигурацию:
Вторая строчка необязательна, но может быть крайне полезна для поиска устройства. Можно будет обращаться к устройству не по IP, а по hostname, если сеть это поддерживает. Если хотите на 100% избежать поиска устройства в сети — применяйте статическую конфигурацию.
Полезно также запретить работу встроенного в наш TP-LINK dhcp-сервера:
Все, можно отключать нашу коробочку от компьютера (все равно ssh-сессия уже оборвалась после последней команды) и подключать ее к роутеру или свитчу.
uci set network.lan.proto=static
uci set network.lan.ipaddr=192.168.1.222
uci set network.lan.netmask=255.255.255.0
uci set network.lan.gateway=192.168.1.1
uci set network.lan.dns=8.8.8.8
uci changes
uci commit
/etc/init.d/network restart
Где 192.168.1.1 — адрес шлюза в нашей сети, а 192.168.1.222 — незанятый IP, который и будет присвоен нашей коробочке. Команда «uci changes» предоставляет возможность просмотреть все внесенные изменения. Я стараюсь не пренебрегать этой возможностью, так как устройство с неправильно настроенным интерфейсом, будучи недоступным извне, превращается в «зомби». На случай, если все таки неприятность произошла, в OpenWRT предусмотрен «режим восстановления»: при загрузке, как только кнопка «WPS» начнет мигать, зажмите ее — MR3020 загрузится с сетевыми параметрами по умолчанию.
Если в сети работает DHCP, можно задать автоматическую конфигурацию:
uci set network.lan.proto=dhcp
uci set network.lan.hostname=etherelay
uci commit
/etc/init.d/network restart
Вторая строчка необязательна, но может быть крайне полезна для поиска устройства. Можно будет обращаться к устройству не по IP, а по hostname, если сеть это поддерживает. Если хотите на 100% избежать поиска устройства в сети — применяйте статическую конфигурацию.
Полезно также запретить работу встроенного в наш TP-LINK dhcp-сервера:
uci set dhcp.lan.ignore=1
uci commit
/etc/init.d/dnsmasq restart
Все, можно отключать нашу коробочку от компьютера (все равно ssh-сессия уже оборвалась после последней команды) и подключать ее к роутеру или свитчу.
Подключение клавиатуры и проверка управления светодиодами
Сайт H-WRT информирует нас, что для установки клавиатуры нужен лишь модуль kmod-usb-hid.
Установим его:
opkg update
opkg install kmod-usb-hid
Самое время подключить клавиатуру и посмотреть, опозналась ли она:
root@OpenWrt:~# dmesg | tail
[ 66.380000] hub 1-0:1.0: connect-debounce failed, port 1 disabled
[ 68.780000] hub 1-0:1.0: connect-debounce failed, port 1 disabled
[ 71.180000] hub 1-0:1.0: connect-debounce failed, port 1 disabled
root@OpenWrt:~#
Облом! это совсем не то, что я ожидал. В чем же дело?
Схожий багрепорт быстро находится — разработчики объясняют такое поведение аппаратными ограничениями примененного чипсета. Похоже, находящийся «на борту» USB-хаб не жалует low-speed устройства. Хотя на этом моменте дух Дзен бесследно испарился — не будем опускать руки и попробуем решить проблему подключением клавиатуры через внешний USB-хаб:
root@OpenWrt:~# dmesg | tail
[ 143.120000] usb 1-1: new high-speed USB device number 2 using ehci-platform
[ 143.270000] hub 1-1:1.0: USB hub found
[ 143.270000] hub 1-1:1.0: 4 ports detected
[ 143.580000] usb 1-1.2: new low-speed USB device number 3 using ehci-platform
[ 143.730000] input: Generic USB Keyboard as /devices/platform/ehci-platform/usb1/1-1/1-1.2/1-1.2:1.0/input/input0
[ 143.730000] generic-usb 0003:040B:2000.0001: input: USB HID v1.10 Keyboard [Generic USB Keyboard] on usb-ehci-platform-1.2/input0
[ 143.770000] input: Generic USB Keyboard as /devices/platform/ehci-platform/usb1/1-1/1-1.2/1-1.2:1.1/input/input1
[ 143.780000] generic-usb 0003:040B:2000.0002: input: USB HID v1.10 Mouse [Generic USB Keyboard] on usb-ehci-platform-1.2/input1
root@OpenWrt
Гораздо лучше. Пускай из-за этого пришлось написать «почти получилось» в начале статьи и «способ сервировки» на фото, но так наша «Generic USB Keyboard» опозналась. На клавиатуру всегда создается два «устройства», но даже и не спрашивайте, почему эта конкретная клавиатура назвалась еще и мышью… Так или иначе — мы готовы к «аппаратному хеллоуворлду» — включить светодиод.
cat /dev/input/event0 > /dev/null &
printf "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x01\x00\x00\x00\x01" > /dev/input/event0
Здесь должен торжественно зажечься светодиод «Caps Lock». Те, кого просто радует этот факт — могут переходить к следующему шагу. Те, кто не может двинуться дальше, не узнав, что за бредовое заклинание приведено выше — заглядывают в
сумбурные пояснения:
На все, что происходит с клавиатурой (или другим устройством ввода) генерируется событие ввода, видимое в соответствующем ему файле (в нашем случае /dev/input/event0, но это частный случай, обусловленный тем, что других устройств ввода не подключено). Структура события определяется в заголовочном файле input.h:
Где type сигнализирует о типе элемента ввода (кнопка клавиатуры или перемещение мыши/джойстика и т.п ), code — код элемента, специфичный для каждого типа (например, для клавиатурного события EV_KEY здесь будет передан номер клавиши), а value — это, соответственно, какое воздействие и какой величины (для устройств, поддерживающих это) было произведено. Например, при нажатии клавиши «Q» на клавиатуре мы получим:
где первые 8 байт (505b 0ed9 000* ****) — время события, следующие два байта (0001) — тип события (EV_KEY), затем два байта номера клавиши (0010), которые для алфавитно-цифровых клавиш соответствуют скан -коду в Set 1. Кстати, нажатия всяких «прокачанных» клавиш вроде регулировки громкости, управления воспроизведением и т.п. отправляются во второй обработчик, созданный для клавиатуры (в нашем случае /dev/input/event1). Для них не генерируется событие автоповтора, что может оказаться весьма кстати — помните, я писал о «бонусной» возможности устройства работать на ввод? Последние 4 байта (0000 0001 или 0000 0000) указывают на то, что произошло нажатие или отпускание соответственно. Пакеты, состоящие из нулей — это специальные разделительные события EV_SYN, а зачем нужны события с типом 4 я не знаю. Определения для типов и кодов заданы в том же input.h, и подробнее описаны в этом документе. Дальше начинается самое интересное. Хотя большая часть событий передается от устройства в пространство пользователя, некоторые события могут идти в обратном направлении. Это сделано для поддержки, например, джойстиков с отдачей или (та-дам!) светодиодов. То есть, записав в /dev/event/input0 событие с типом EV_LED, кодом LED_CAPSL и значением 1, мы скомандуем клавиатуре зажечь светодиод «Caps Lock». Достаточно подробно о работе с устройствами ввода, особенно USB, рассказывают статьи Брэда Хардса (Brad Hards) The Linux USB Input Subsystem и Using the Input Subsystem (продолжение первой статьи, даже с примером кода для управления светодиодом). Жаль что я их нашел слишком поздно, когда задача уже была решена экспериментальным путем. Между прочим, мне очень повезло — последние снэпшоты, а так же бета 12.09 имеют одну особенность, из за чего отсылка событий работает нестабильно, если файл устройства постоянно не читать. Именно для этого предназначена команда
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
Где type сигнализирует о типе элемента ввода (кнопка клавиатуры или перемещение мыши/джойстика и т.п ), code — код элемента, специфичный для каждого типа (например, для клавиатурного события EV_KEY здесь будет передан номер клавиши), а value — это, соответственно, какое воздействие и какой величины (для устройств, поддерживающих это) было произведено. Например, при нажатии клавиши «Q» на клавиатуре мы получим:
root@OpenWrt:~# cat /dev/input/event0 | hexdump
0000000 505b 0ed9 0009 6bdd 0004 0004 0007 0014
0000010 505b 0ed9 0009 6be6 0001 0010 0000 0001
0000020 505b 0ed9 0009 6bec 0000 0000 0000 0000
0000030 505b 0ed9 000a 2756 0004 0004 0007 0014
0000040 505b 0ed9 000a 275e 0001 0010 0000 0000
0000050 505b 0ed9 000a 2762 0000 0000 0000 0000
где первые 8 байт (505b 0ed9 000* ****) — время события, следующие два байта (0001) — тип события (EV_KEY), затем два байта номера клавиши (0010), которые для алфавитно-цифровых клавиш соответствуют скан -коду в Set 1. Кстати, нажатия всяких «прокачанных» клавиш вроде регулировки громкости, управления воспроизведением и т.п. отправляются во второй обработчик, созданный для клавиатуры (в нашем случае /dev/input/event1). Для них не генерируется событие автоповтора, что может оказаться весьма кстати — помните, я писал о «бонусной» возможности устройства работать на ввод? Последние 4 байта (0000 0001 или 0000 0000) указывают на то, что произошло нажатие или отпускание соответственно. Пакеты, состоящие из нулей — это специальные разделительные события EV_SYN, а зачем нужны события с типом 4 я не знаю. Определения для типов и кодов заданы в том же input.h, и подробнее описаны в этом документе. Дальше начинается самое интересное. Хотя большая часть событий передается от устройства в пространство пользователя, некоторые события могут идти в обратном направлении. Это сделано для поддержки, например, джойстиков с отдачей или (та-дам!) светодиодов. То есть, записав в /dev/event/input0 событие с типом EV_LED, кодом LED_CAPSL и значением 1, мы скомандуем клавиатуре зажечь светодиод «Caps Lock». Достаточно подробно о работе с устройствами ввода, особенно USB, рассказывают статьи Брэда Хардса (Brad Hards) The Linux USB Input Subsystem и Using the Input Subsystem (продолжение первой статьи, даже с примером кода для управления светодиодом). Жаль что я их нашел слишком поздно, когда задача уже была решена экспериментальным путем. Между прочим, мне очень повезло — последние снэпшоты, а так же бета 12.09 имеют одну особенность, из за чего отсылка событий работает нестабильно, если файл устройства постоянно не читать. Именно для этого предназначена команда
cat /dev/input/event0 > /dev/null &
Я экспериментировал сначала с прошивкой r31214, где таких особенностей не было, поэтому светодиод у меня загорелся сразу — если бы этого не произошло, то я бы еще долго искал верный путь.Скрипт
Чтобы автоматизировать управление светодиодами нам нужен скрипт. Для того, чтобы на следующем шаге мигать ими по HTTP — сразу положим его в папку /www/cgi-bin:
cd /www
mkdir cgi-bin
cd cgi-bin
wget http://etherelay.googlecode.com/files/ctlrelay
chmod +x ctlrelay
Вот его текст и описание:
Константы EV_LED, LED_NUML, LED_CAPSL, LED_SCROLLL, как я уже говорил, определены в input.h. Время нас не волнует — забиваем его нулями. Конечно, код получится компактнее, если формировать «события» «на лету», исходя из полученной команды, а не просто отсылать заранее набитые шаблоны в свитче, но мне показалось, что так понятнее основная идея. Нам все так же необходимо чтение из файла событий во время записи в него, откуда и возник пресловутый костыль. Повторюсь, в этом не было необходимости в более ранних сборках, и, скорее всего, не понадобится в будущем. Но пока придется мириться. Последний фрагмент модифицирует файл текущего состояния светодиодов, который состоит из одной примерно такой строчки:
#!/bin/sh
KB_LEDS=/dev/input/event0
EV_LED="\x00\x11"
LED_NUML="\x00\x00"
LED_CAPSL="\x00\x01"
LED_SCROLLL="\x00\x02"
TURN_ON="\x00\x00\x00\x01"
TURN_OFF="\x00\x00\x00\x00"
DT_DUMMY="\x00\x00\x00\x00\x00\x00\x00\x00"
#формируем 16-байтовые посылки для отсылки в файл клавиатуры
NUM_ON=$DT_DUMMY$EV_LED$LED_NUML$TURN_ON
NUM_OFF=$DT_DUMMY$EV_LED$LED_NUML$TURN_OFF
CAPS_ON=$DT_DUMMY$EV_LED$LED_CAPSL$TURN_ON
CAPS_OFF=$DT_DUMMY$EV_LED$LED_CAPSL$TURN_OFF
SCROLL_ON=$DT_DUMMY$EV_LED$LED_SCROLLL$TURN_ON
SCROLL_OFF=$DT_DUMMY$EV_LED$LED_SCROLLL$TURN_OFF
#"костыль", обеспечивающий чтение файла клавиатуры на время записи
if ! ps | grep -qe "[c]at $KB_LEDS"; then cat $KB_LEDS > /dev/null & fi
#берем команду из "переменной" command GET-запроса
#если эапроса нет - берем команду из первого аргумента
if [ -z "$QUERY_STRING" ]; then COMMAND=$1;
else
COMMAND=`echo "$QUERY_STRING" | sed -n 's/^.*command=\([^&]*\).*$/\1/p'`
printf "Content-type: text/plain\r\n\r\n"
fi
#посылка события в файл обработчика
case $COMMAND in
num_on)
printf $NUM_ON > $KB_LEDS;;
num_off)
printf $NUM_OFF > $KB_LEDS;;
caps_on)
printf caps_on > /var/rrr
printf $CAPS_ON > $KB_LEDS;;
caps_off)
printf caps_off > /var/rrr
printf $CAPS_OFF > $KB_LEDS;;
scroll_on)
printf $SCROLL_ON > $KB_LEDS;;
scroll_off)
printf $SCROLL_OFF > $KB_LEDS;;
num_pulse)
printf $NUM_ON > $KB_LEDS
sleep 1
printf $NUM_OFF > $KB_LEDS
;;
caps_pulse)
printf $CAPS_ON > $KB_LEDS
sleep 1
printf $CAPS_OFF > $KB_LEDS
;;
scroll_pulse)
printf $SCROLL_ON > $KB_LEDS
sleep 1
printf $SCROLL_OFF > $KB_LEDS
;;
*) WRONG_ARG=1;;
esac
#обновляем информацию в файле текущего состояния светодиодов
if [ -z $WRONG_ARG ]
then
STATE_FILE=/var/ledstate
DEFAULT_STATE={\"num\":false,\"caps\":false,\"scroll\":false}
if ! [ -e $STATE_FILE ]; then echo $DEFAULT_STATE > $STATE_FILE; fi
AFFECTED_LED=`echo $COMMAND | sed -r -e 's/_[a-z]+$//'`
NEW_STATE=`echo $COMMAND | sed -r -e 's/^[a-z]+_//' -e 's/on/true/' -e 's/off|pulse/false/'`
sed -i -r 's/"'"$AFFECTED_LED"'":[a-z]+/"'"$AFFECTED_LED"'":'"$NEW_STATE"'/' $STATE_FILE
fi
Константы EV_LED, LED_NUML, LED_CAPSL, LED_SCROLLL, как я уже говорил, определены в input.h. Время нас не волнует — забиваем его нулями. Конечно, код получится компактнее, если формировать «события» «на лету», исходя из полученной команды, а не просто отсылать заранее набитые шаблоны в свитче, но мне показалось, что так понятнее основная идея. Нам все так же необходимо чтение из файла событий во время записи в него, откуда и возник пресловутый костыль. Повторюсь, в этом не было необходимости в более ранних сборках, и, скорее всего, не понадобится в будущем. Но пока придется мириться. Последний фрагмент модифицирует файл текущего состояния светодиодов, который состоит из одной примерно такой строчки:
{"num":true,"caps":false,"scroll":false}Если такого файла нет — он будет создан со всеми значениями «false». В OpenWRT директория /var/ — в оперативке, а значит — файл создается после каждой перезагрузки, когда все светодиоды как раз выключены. Уютненькая схемка! Как вытащить параметры из HTTP — запроса я прочитал, наверное здесь (прочитал давно и ссылку не сохранил, только строчку кода, но здесь очень похоже). Скрипт выводит «Content-type: text/plain», если вызван через CGI, потому что иначе результат запроса будет 502 (Bad Gateway), а не 200(OK), что не смертельно, но некрасиво.
Теперь управлять светодиодами легко. Чтобы включить, скажем, светодиод «Scroll Lock», пишем:
./ctlrelay scroll_on
Выключаем:
./ctlrelay scroll_off
Можно еще «мигнуть» (scroll_pulse). Как говорит уже многими здесь уважаемый Anant Agarwal: «I could do this all day. This is so much fun!». Но все же, перейдем к следующему этапу и создадим…
Веб-интерфейс
Какой же веб-интерфейс без веб-сервера? Проверим его наличие:
opkg status uhttpd
Если в выводе есть строка Status: install user installed (а это будет так, если вы используете Attitude Adjustment 12.09 beta) то сервер уже установлен. Иначе установим его и настроим его запуск:
opkg update
opkg install uhttpd
/etc/init.d/uhttpd enable
/etc/init.d/uhttpd start
Дальше нам нужна веб-страница:
Сначала я хотел использовать JQuery.get(), но потом посчитал излишним привлекать JQuery ради всего лишь пары GET-запросов. У нас есть две JavaScript функции. Первая, command(action), запускает наш скрипт, передавая «команду» в праметре «command» GET-запроса. Какую именно — определяется в событиях onclick элементов управления. Это выглядит не очень элегантно, спору нет, но дает хорошее представление о том, как все работает. Вторая функция, get_status() запускается по событию onload, запрашивает файл с текущим состоянием светодиодов (тот, который формируется в конце скрипта) и отражает его на элементах управления страницы. По невероятному стечению обстоятельств файл состояния представляет из себя JSON-представление ассоциативного массива, и доступ к данным мы очень просто получаем с помощью JSON.parse(). Повтор запроса каждые полсекунды позволяет отслеживать измения состояния, произведенные с другого клиента. Добавка в url fuie=Math.random() нужна для того, чтобы адрес каждый раз был уникальным — тогда браузер не сможет отказаться от запроса страницы, посчитав, что она уже есть в кэше. Как можно догадаться из названия переменной, особенно склонен к такому поведению Internet Explorer.
<!DOCTYPE html>
<html>
<head>
<title>Relay control</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<script type="text/javascript">
function command(action)
{
url="/cgi-bin/ctlrelay?command="+action;
url=url+"&fuie=" + Math.random();
var xmlhttp=new XMLHttpRequest();
xmlhttp.open("GET",url,false);
xmlhttp.send();
}
function get_status()
{
var xmlhttp=new XMLHttpRequest();
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
ledstate = JSON.parse(xmlhttp.responseText)
for (led in ledstate)
{
document.getElementById(led).checked=ledstate[led]
}
}
}
xmlhttp.open("GET","/ledstate?fuie="+ Math.random(),false);
xmlhttp.send();
setTimeout("get_status()",500);
}
</script>
</head>
<body onload="get_status()">
<div class="controls">
<div class="control">
<label for="scroll">Scroll Lock LED</label>
<input id="scroll" type="checkbox" onclick="if (this.checked) command ('scroll_on'); else command ('scroll_off')">
</div>
<div class="control">
<label for="caps">Caps Lock LED</label>
<input id="caps" type="checkbox" onclick="if (this.checked) command ('caps_on'); else command ('caps_off')">
</div>
<div class="control">
<label for="num">Num Lock LED</label>
<button id="num" type="button" onclick="command('num_pulse')">1s blink</button>
</div>
</div>
</body>
</html>
Сначала я хотел использовать JQuery.get(), но потом посчитал излишним привлекать JQuery ради всего лишь пары GET-запросов. У нас есть две JavaScript функции. Первая, command(action), запускает наш скрипт, передавая «команду» в праметре «command» GET-запроса. Какую именно — определяется в событиях onclick элементов управления. Это выглядит не очень элегантно, спору нет, но дает хорошее представление о том, как все работает. Вторая функция, get_status() запускается по событию onload, запрашивает файл с текущим состоянием светодиодов (тот, который формируется в конце скрипта) и отражает его на элементах управления страницы. По невероятному стечению обстоятельств файл состояния представляет из себя JSON-представление ассоциативного массива, и доступ к данным мы очень просто получаем с помощью JSON.parse(). Повтор запроса каждые полсекунды позволяет отслеживать измения состояния, произведенные с другого клиента. Добавка в url fuie=Math.random() нужна для того, чтобы адрес каждый раз был уникальным — тогда браузер не сможет отказаться от запроса страницы, посчитав, что она уже есть в кэше. Как можно догадаться из названия переменной, особенно склонен к такому поведению Internet Explorer.
Маленький нюанс — браузер, конечно, не имеет доступа к папке /var на сервере, поэтому предоставим ему возможность прочитать файл ledstate оттуда, куда он «дотягивается», с помощью симлинка:
ln -s /var/ledstate /www/ledstate
Ссылку на скачивание этой странички, немного измененной, можно найти почти в самом в конце статьи. Теперь можно мигать светодиодами клавиатуры, тыкая в чекбоксы. Но, даже не учитывая того, что мигающим светодиодом сейчас никого не удивить, придется согласиться с тем, что это абсолютно бесполезно с практической точки зрения. Поэтому примемся за железо!
Схема
Уже можно разобрать клавиатуру и вытащить из нее плату:
Схему включения светодиодов клавиатуры представим следующей (весьма упрощенной) моделью (для включенного состояния):
Схема 1. Включение светодиода клавиатуры
Три проверенных «клавы» из «помоечного» сегмента имели именно такое включение светодиодов, отличаются только их токи — обычно около 18мА, но бывает и 3мА. Надо иметь ввиду, что возможны и другие схемы.
Самое простое — если необходимо коммутировать небольшое напряжение и ток (дистанционный «Reset» какого — либо устройства или эмуляция других кнопок/логических сигналов). Можно обойтись обычной оптопарой:
Схема 2. Формирование логического сигнала
Для управления устройством с питанием от сети берем твердотельное реле:
Схема 3. Управление твердотельным реле
Я использовал отечественнное реле К293КП13П из-за цены в 170 рублей. Максимальный ток в нагрузке, на которое оно рассчитано — 1А. Ток через управляющий светодиод не должен быть ниже минимального (в документации он обозначается IFmin) для используемого типа реле. Если плата клавиатуры дает меньший ток (отпаяйте один вывод светодиода и измерьте ток в разрыве) — поможет дополнительный транзисторный ключ:
Схема 4. Подключение твердотельного реле через ключ
Он же спасет, если используется твердотельное реле, управляемое напряжением или «классическое» электромеханическое реле:
Схема 5. Управление электромеханическим реле
Твердотельное реле — штука достаточно дорогая, и чем мощнее — тем дороже. Обычное реле дает больше «ампер на рубль», но имеет свои минусы. Среди них — большой потребляемый ток во включенном состоянии. Если одновременно включить три реле такой же модели, как на схеме — потребляемый от USB ток будет составлять почти 300 мА. Если не хотите так сильно «доить» порт — подключайте эмиттер не к нему (пунктирная линия на схеме), а к независимому источнику.
Схемы 4 и 5, само собой, работают только на клавиатурах где СД включены именно по схеме с «общим анодом» (проверьте сопротивление между анодами диодов и «плюсом» платы — должно быть 0 Ом). Если неохота с этим заморачиваться — можно использовать вместо ключа маломощное оптореле. Включается вместо штатного светодиода и работает при токе на светодиод от 5 до 25 мА, то есть подойдет для всех вариантов клавиатур с вероятностью 99%. Если вам надо коммутировать напряжение до 60 в и ток до 300 мА — можно убрать реле и пользоваться оптореле напрямую.
Схема 6. Управление электромеханическим реле при помощи оптореле
В схеме и ее реализации много упрощений:
Что всегда стоит принимать в расчет — это элементарая электробезопасность. Даже тестовое устройство — особенно тестовое устройство, которое наверняка будет болтаться где-нибудь на проводках или будет забыто включенным среди прочего хлама на столе — надо защитить от случайного касания токоведущих частей, если оно предусматривает подключение к сети. Иначе по вам может потечь абсолютно реальный электрический ток. Неплохо добавить в коммутируемую цепь предохранитель. Транзистор в схеме 5 работает с почти максимальным коллекторным током — желательно взять с током срабатывания поменьше, или хотя бы транзистор помощнее. На выходы твердотельного реле нужно поставить демпфирующую RC-цепочку при работе на двигатель или другую индуктивную нагрузку. Компоненты подбирались лишь по критерию доступности и цены в ближайшем (ко мне) оЧень Дорогом радиомагазине — при наличии выбора можно подобрать у нормального продавца «твердотелку» на нужный ток с широким диапазоном IF и на том закрыть вопрос с коммутацией. Кроме того, представленная модель включения неточна: плата клавиатуры ведет себя как источник тока по отношению к нашему «светодиоду» только при напряжении на аноде 2.5В и выше — я старался, по возможности, обеспечить падение напряжения, сходное с таковым у заменяемого светодиода, чтобы плата «не почувствовала» подмены.
Я собрал варианты 3 и 6 на макетке, получилось так:
Несмотря на то, что это не более чем «proof of concept», мне захотелось хоть немного «навести красоту», пускай и в собственной извращенной трактовке. Такие уж нынче времена: внешний вид ценится не меньше, а порой и беспричинно больше функциональной нагрузки. Что и приводит нас к следующему этапу.
Косметика и демонстрация
От HTML — интерфейса за километр несет ботанщиной, байковыми рубашками с катышками и макаронами из стеклянной банки:
Попытаемся это хоть как-то скрасить.
- Так как художественные способности у меня на нуле, а вкус где то рядом с ними —
идею я стырил отсюдая почерпнул вдохновение (и немного css) здесь: http://www.seanslinsky.com/demo/ios-toggle-switches/. - С помощью генератора градиентов mudcu.be/bg сделал
нескучные обоизадник. - Генератор «клевых тумблерков» proto.io/freebies/onoff создает кнопки, которые работают почти во всех браузерах — даже в Опере, если сделать их квадратными, и даже в IE, если он >=9.
- Так как на разных системах по разному работает увеличение — делаем кнопки масштаба внутри страницы davidwalsh.name/change-text-size-onclick-with-javascript
CSS не имеет смысла приводить, потому что на 90% сгенерирован автоматически, а HTML — на 90% повтор вышеприведенного. Поэтому просто пишу как из скачать:
cd /www
mkdir luci
mv index.html luci
wget http://etherelay.googlecode.com/files/index.html
wget http://etherelay.googlecode.com/files/style.css
Вторая и третья строчка «перепрятывают» файл веб-интерфейса роутера (если он установлен) в отдельную папку, теперь он доступен по адресу _адрес_роутера_/luci. А «главной» страничкой, доступной по адресу сервера, становится наша.
Ну и, напоследок — говорят, что картинка стоит тысячи слов. В этом видео почти тысяча картинок. Надеюсь, оно хоть как-то уравновесит мою многословность.
Update: nm11 подсказал, как добавить авторизацию на веб-сервер (например, с логином wizard и паролем lumos ):
uci set uhttpd.main.config=/etc/httpd.conf
uci commit uhttpd
echo "/:wizard:lumos" > $(uci get uhttpd.main.config)
/etc/init.d/uhttpd restart