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

в 8:44, , рубрики: Black Swift, diy или сделай сам, OpenWrt, Блог компании Black Swift, микрокомпьютер, Электроника для начинающих

Этим текстом мастер Гамбс завершает описание своей новой ёлочной гирлянды. 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, открыть в нём проект и немного поправить.

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

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

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

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

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

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

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

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

Автор: olartamonov

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js