Как стать автором
Обновить
0

Елочка, зажгись! Часть 3: веб-интерфейс и приложение для Android

Время на прочтение 7 мин
Количество просмотров 20K
Этим текстом мастер Гамбс завершает описание своей новой ёлочной гирлянды. 2015 г. Москва

Привет, Хабр!

Итак, мы добрались до финального этапа: раз у нас есть гирлянда, которой управляет нанокомпьютер Black Swift со встроенным Wi-Fi, то логично сделать для неё веб-интерфейс и смартфонное приложение, чтобы помигать светодиодом, если вы понимаете, о чём я.

  1. Гирлянда, подключение Black Swift и среда сборки под OpenWRT на C/C++
  2. Софт на C, работа с GPIO и программная ШИМ
  3. Веб-интерфейс и приложение для Android

Но сначала — по просьбам читателей публикуем видео работающей ёлочной гирлянды. Не думаю, что кто-то не видел ёлочных гирлянд, думаю, что просто не все верят, что я правда 28-29 декабря пошёл за светодиодами, чтобы украсить ёлку…



За кадром сижу я и одной рукой держу фотоаппарат, а другой переключаю режимы её работы, тыкая мышкой в браузер.

Теперь же, когда последние следы недоверия испарились, продолжим. В предыдущих сериях мы получили работающий контроллер гирлянды, умеющий принимать команды через 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-файл:

<p style="text-align: center;"><label for="brightness">Brightness</label>
<input type="range" min="1" max="10" value="<?php echo (11 - $values[1]);  ?>" id="brightness" step=1 onchange="setBrightness(value)" oninput="displayBrightness(value)">
<output for="brightness" id="bLevel"><?php echo (11 - $values[1]);  ?></output>

и

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 будут сравнимы с внешними размерами корпуса всего финального устройства.
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
+24
Комментарии 8
Комментарии Комментарии 8

Публикации

Информация

Сайт
www.black-swift.ru
Дата регистрации
Дата основания
Численность
2–10 человек
Местоположение
Россия

Истории