Этим текстом мастер Гамбс завершает описание своей новой ёлочной гирлянды. 2015 г. Москва
Привет!
Итак, мы добрались до финального этапа: раз у нас есть гирлянда, которой управляет нанокомпьютер Black Swift со встроенным Wi-Fi, то логично сделать для неё веб-интерфейс и смартфонное приложение, чтобы помигать светодиодом, если вы понимаете, о чём я.
- Гирлянда, подключение Black Swift и среда сборки под OpenWRT на C/C++
- Софт на C, работа с GPIO и программная ШИМ
- Веб-интерфейс и приложение для 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 будут сравнимы с внешними размерами корпуса всего финального устройства.
Автор: olartamonov