Елочка, зажгись! Часть 3: веб-интерфейс и приложение для Android
Этим текстом мастер Гамбс завершает описание своей новой ёлочной гирлянды. 2015 г. МоскваПривет, Хабр!
Итак, мы добрались до финального этапа: раз у нас есть гирлянда, которой управляет нанокомпьютер Black Swift со встроенным Wi-Fi, то логично сделать для неё веб-интерфейс и смартфонное приложение, чтобы помигать светодиодом, если вы понимаете, о чём я.
Гирлянда, подключение Black Swift и среда сборки под OpenWRT на C/C++ Софт на C, работа с GPIO и программная ШИМ Веб-интерфейс и приложение для Android Но сначала — по просьбам читателей публикуем видео работающей ёлочной гирлянды. Не думаю, что кто-то не видел ёлочных гирлянд, думаю, что просто не все верят, что я правда 28–29 декабря пошёл за светодиодами, чтобы украсить ёлку…[embedded content]За кадром сижу я и одной рукой держу фотоаппарат, а другой переключаю режимы её работы, тыкая мышкой в браузер.
Теперь же, когда последние следы недоверия испарились, продолжим. В предыдущих сериях мы получили работающий контроллер гирлянды, умеющий принимать команды через UNIX-сокет — в них задаётся режим работы, а также скорость и яркость гирлянды. Проще всего прослойку между вебом и сокетом сделать на банальном PHP — это буквально несколько строчек.
Веб-сервер и PHP5 на нанокомпьютереУ нас в Black Swift уже стоит стандартный веб-сервер uhttpd, обслуживающий штатный веб-интерфейс LuCI. Чтобы работать с PHP, мы поставим второй веб-сервер — lighttpd (я вот думаю, в финальную прошивку его и php5 надо просто по умолчанию включить), а также удобный текстовый редактор nano:
opkg update opkg install nano opkg install lighttpd lighttpd-mod-cgi opkg install php5 php5-cgi /etc/init.d/lighttpd enable Веб-сервер и PHP подтянут с собой свои зависимости сами. Средние три команды, я думаю, очевидны, первая же подтянет из репозитория обновлённый список пакетов, а последняя включит для lighttpd автозапуск при старте системы.
Теперь чуть-чуть подправим конфиги:
nano /etc/config/uhttpd В первых строчках ищем директивы «list listen_http», которых там две штуки, и меняем в них порт :80 на :8080 (если ещё какой-нибудь). Потом перезапускаем uhttpd командой /etc/init.d/uhttpd restart.
Аналогичным образом правим /etc/lighttpd/lighttpd.conf (он длинный, для поиска нужного текста в nano используется комбинация Ctrl-W):
server.modules = ( «mod_cgi» ) В конфиге есть длинный закомментированный список подключаемых модулей, нам нужен только mod_cgi, который будет работать с php_cgi.
server.document-root = »/www/tree»
index-file.names = («index.html», «default.html», «index.htm», «default.htm», «index.php»)
cgi.assign = (».php» => »/usr/bin/php-cgi») Здесь всё достаточно очевидно для всех, кто хоть раз видел веб-сервер на линуксе: корневая папка, корневые файлы (добавляем в список index.php) и привязка к файлам *.php конкретного обработчика.
Теперь открываем /etc/php.ini и правим одну строчку:
doc_root = »/www/tree» И финальный штрих — /etc/init.d/lighttpd start
Теперь мы имеем на порту 80 веб-сервер с работающим PHP, так что остаётся только создать каталог /www/tree и положить в него файл index.php. Который, конечно, сначала надо написать.
index.php Задача также предельно банальная, скажем прямо.
Писать в файловый сокет из PHP не просто, а очень просто:
$sockf = fsockopen («unix:///tmp/treelights.sock», 0, $errno, $errstr); if ($sockf) { $command = $cmd.» » . $val; fwrite ($sockf, $command); fclose ($sockf); } Где $cmd — команда, которую мы хотим передать, например, brightness, а $val — соответствующее значение, например, 2.
Далее всё очевидно: пользователь двигает ползунок (в HTML5 появились ползунки, ура-ура), javascript выдёргивает его положение и передаёт в PHP-файл:
и function displayBrightness (brightness) { document.querySelector ('#bLevel').value = brightness; }
function setBrightness (brightness) { url = 'index.php? cmd=brightness&val='; location.href = url.concat (brightness); } Первая функция при перемещении ползунка сразу же меняет численное значение рядом с ним, вторая передаёт команду и это значение в PHP-файл сразу, как только пользователь ползунок отпустит. NB: первые реализации HTML5 страдали тем, что onchange и oninput в range работали одинаково, выскакивая при каждом сдвиге ползунка, но сейчас нам это уже не очень важно.
Вы уже наверняка обратили внимание на два момента: JS вызывает тот же index.php, в котором он написан, только с параметрами, а в HTML есть вставки на PHP, подставляющие при генерации кода некое определённое ранее положение ползунка.
Первое сделано потому, что для простоты демонстрации я не использовал AJAX, а второе — чтобы при открытии странички она показала текущее состояние гирлянды, если таковое ранее устанавливалось.
Обработка переданных с файлом параметров проста:
$cmd=($_GET['cmd']); $val=($_GET['val']); if (! empty ($cmd)) { $sockf = fsockopen («unix:///tmp/treelights.sock», 0, $errno, $errstr); /* дальше вы уже знаете */ } Здесь всё столь же банально: если параметры передали, то мы сначала запихнём их в сокет, а потом покажем веб-интерфейс, если не передали — просто покажем веб-интерфейс.
Настроек гирлянды у нас негусто, поэтому хранить их логично в обычном файле. Однако тут стоит вспомнить, что программу на C мы писали без учёта сохранения параметров, поэтому и PHP после перезапуска системы должен показывать параметры по умолчанию, а не сохранённые ранее. Сделать это в OpenWRT очень просто — сохраняйте всё ненужное в /tmp, он живёт в ОЗУ и при перезагрузке исчезает навсегда.
if (file_exists (»/tmp/tree.set»)) { $settings = file_get_contents (»/tmp/tree.set»); // Mode, Brightness, Speed $values = explode (»,», $settings); } else { $values[0] = »0»; $values[1] = »1»; $values[2] = »1»; } Ну и после установки новых параметров гирлянды, конечно, их надо записать:
$settings = $values[0] .»,» . $values[1] .»,» . $values[2]; file_put_contents (»/tmp/tree.set», $settings); В общем, на этом со строительством веб-интерфейса фактически всё — добавляем аналогичным образом прочие кнопки и полузнки и получаем результат: https://github.com/olegart/treelights/blob/master/php/index.php
Теперь на нашу ёлочку можно зайти из браузера.
Приложение для Android и Network Service Discovery Disclaimer: вообще я сам под Android умею писать примерно никак, так что не судите строго. С другой стороны, тот факт, что у меня получилось и оно работает, многое говорит о простоте реализации подобных применений Black Swift, когда прототипы всех основных частей системы можно сделать в прямом смысле слова на коленке.
Итак, у нас есть веб-интерфейс по некоему IP-адресу. Банальным способом было бы показать на смартфоне его содержимое в компоненте WebView, но мы пойдём чуть дальше и сделаем автоопределение этого адреса (ну право слово, не будете же вы жене диктовать «Дорогая, выключи гирлянду, она на 192.168.1.158, если DHCP ей что-то новое не дал») с помощью сервиса Network Service Discovery. NSD нормально работает в Android начиная с чего-то типа 4.1 или 4.2, но вряд ли нас это сейчас остановит.
В свете дисклеймера не буду рассказывать, как писать под Android, а сразу дам ссылку: приложение, которое я делал для своего интерфейса «умного дома». Его надо скачать, положить куда-нибудь аккуратно, потом поставить Android Studio, открыть в нём проект и немного поправить.
Открываем Gradle Scripts → build.gradle (Module: app) и меняем в applicationId «lightcontrol» на что-нибудь более адекватное новогодней ёлке. Хотя вообще можете и «lightcontrol» оставить, у вас-то наверняка путаницы с софтом «умного дома» с таким же названием не будет.
Такая же косметика: в Manifests → AndroidManifest.xml меняем android: label на что-нибудь про ёлку (NB: com.example.lightcontrol.app здесь и во всех остальных местах, кроме build.gradle, мы не трогаем!). Аналогично идём в res → values → strings.xml и меняем значение app_name на что-нибудь про Рождество.
Наконец, открываем основной код приложения и в его начале меняем значение переменной TAG на что-нибудь своё. Это слово надо запомнить, оно нам пригодится на следующем шагу — дело в том, что по этому имени приложение будет искать нужный сервис в локальной сети. Пусть будет «Treelights», например.
Всё поменяли? Можно ещё пройтись по выводим приложениям сообщениям (я не заморачивался с локализацией, всё забито прямо в код) и поменять для красоты фразы в духе «управление светом в доме» на «управление гирляндой на ёлке».
Теперь финал: Build → Generate signed APK, создаём свой ключ для подписи приложения и собственно компилируем всё. С точки зрения отзывчивости пользовательского интерфейса Android Studio абсолютно кошмарен, но сборка проекта занимает секунд десять, не больше, после чего вам либо вываливается ошибка в логе, либо предложение открыть в explorer.exe папку с готовым APK. Открываем, копируем app-release.apk на смартфон и устанавливаем (в настройка Android надо включить установку из всех подряд источников).
Теперь возвращаемся к ёлочке и настраиваем там сервис avahi, который и будет рассылать уведомления, получаемые компонентом NSD:
opkg update opkg install avahi-daemon nano /etc/avahi/services/http.service Меняем ровно один пункт: в теге name проставляем имя, содержащее слово, ранее вписанное нами в переменную TAG в мобильном приложении (это было слово «Treelights»):
Сохраняем, открываем /etc/avahi/avahi-daemon.conf и вписываем в первую секцию строку enable-dbus=no (у нас нет DBUS, поэтому без неё avahi при старте будет ругаться матом).
Финальный шаг:
/etc/init.d/avahi-daemon enable /etc/init.d/avahi-daemon start Снова берём в руки смартфон, запускаем наше приложение и радуемся, видя, как через долю секунды поисков оно открывает веб-интерфейс ёлочной гирлянды.
Оставшееся до Нового года время можно потратить на рисование красивого веб-интерфейса с крупными кнопочками.
Вместо заключения Я сразу предвижу два вопроса из серии «зачем ты это сделал»: 1) зачем вообще нужна гирлянда с Wi-Fi и 2) зачем её делать на Black Swift, а не на том же Raspberry, так как габариты тут роли не играют.
На самом деле, конечно, гирлянде не очень нужен Wi-Fi, а замена BSB на RPi ничего в данном не изменит. Но знаете такую работу Акерлофа «Market for Lemons», он в ней показывал, как рынок при свободной конкуренции может самостоятельно скатиться в продажу дешёвой дряни, вот прямо как те новогодние гирлянды, лежащие в магазинах? Акерлоф получил за неё Нобелевку по экономике, а сама работа стала наиболее известна по иллюстрации процесса на примере автомобильного рынка — хотя в предисловии и сказано, что пример не является ни важным, ни реалистичным, а выбран просто из-за простоты и наглядности объяснения.
Так вот, в моём случае ситуация ровно такая же, разве что из Нобелевского комитета мне пока не звонили (Акерлоф, впрочем, тоже 31 год звонка ждал). Я хотел показать, насколько просто использовать Black Swift и с точки зрения подключения, и с точки зрения программирования в довольно-таки комплексном проекте, имеющем и специализированную аппаратную часть, и веб-интерфейс, и мобильное приложение. Фактически, это — нормальный, годный пример автоматизации устройства в рамках популярнейшей ныне концепции «Интернета вещей».
При этом, хотя я написал три статьи и много букв в них, если посмотреть на объём итоговой работы — фактически это «проект выходного дня», один вечер в котором уйдёт на пайку гирлянды, а второй — на написание всего ПО.
В следующий же раз я покажу пример разработки, в которой Black Swift критичен и труднозаменим — потому что габариты того же Raspberry Pi будут сравнимы с внешними размерами корпуса всего финального устройства.