Елочка, зажгись! Часть 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, открыть в нём проект и немного поправить.

336768b102264074b9a7b936eb05eef5.png

Открываем Gradle Scripts → build.gradle (Module: app) и меняем в applicationId «lightcontrol» на что-нибудь более адекватное новогодней ёлке. Хотя вообще можете и «lightcontrol» оставить, у вас-то наверняка путаницы с софтом «умного дома» с таким же названием не будет.

ffc6aff3b1b7421f85d214b5a202dea1.png

Такая же косметика: в Manifests → AndroidManifest.xml меняем android: label на что-нибудь про ёлку (NB: com.example.lightcontrol.app здесь и во всех остальных местах, кроме build.gradle, мы не трогаем!). Аналогично идём в res → values → strings.xml и меняем значение app_name на что-нибудь про Рождество.

860323cdf65e4840a7b4322e1139acbd.png

Наконец, открываем основной код приложения и в его начале меняем значение переменной 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»):

2932e5b596f0410f8cb8aafdb45606db.png

Сохраняем, открываем /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 будут сравнимы с внешними размерами корпуса всего финального устройства.

© Habrahabr.ru