В этой заметке я расскажу про мой самопальный музыкальный центр с веб-интерфейсом и усилками, который я некоторое время поставил себе в кабинет на даче (или, эту комнату лучше будет назвать «лабораторией»?) Исходники всего этого безобразия прилагаются в конце заметки.
Собственно, что хотим получить?
Идея проста, как незнамо что. Одноплатник с линуксом, который хотим подключить к вайфаю и рулить воспроизведением музыки на большие динамики (достаточно большие, чтобы звучали громче работающего 3д-принтера). Для этого берем дешевую usb 5.1 звуковуху (У интегрированных в одноплатники только два канала обычно). Добавляем жесткий диск для храниния музыки. Выглядит неплохо? Ну, поехали.
Что нам потребуется
- 1 x CS102-II TV Stick
- 1 x Мой самопальный usb хаб с релешками
- 1 x USB 5.1 Звуковая карта
- 1 x USB2SATA переходник
- 8 x 3.5mm джеки для звука
Все хозяйство вышло где-то в ~40-50$, наверное. Точно посчитать не смогу, так как все это дело валялось на чердаке годами, собирая пыль, и ничего не зная про курс доллара и инфляцию.
Корпус
Пожалуй, самая долгая в плане изготовления часть. Но необходимое, ибо не хочется получить порнуху из проводов где-то в углу? Короб состоит из трех этажей скрепленных вместе М3 винтами. по размерам они 150x150mm каждый и печатались что-то около десяти часов (каждый!). Это дело пришлось повторять несколько раз, пока я не выловил все ошибки. Начну с нижнего этажа. На рендере оно выглядит так:
А вот так выглядел в реальной жизни первый прототип (из зеленого пластика, который не пошел в итоге «в прождакшн».
Содержит 30mm вентлятор (Лучше бы я сразу вешал 120mm вентилятор, это исчадие ада гудит так, что хоть вешайся!), USB<-->SATA мост для жесткого диска HDD, приклеенный термоклеем, так как китайцы поскупились на крепежные отверстия. А так же отверстия для крепления к стене. На жесткий диск печатается и крепится специальная скоба, за которую просто ухватиться, чтобы достать диск.
На втором этаже обитает мой usb хаб с интегрированным 12->5V DC-DC преобразователем и TV-стик. Плюс этихстиков в том, что из-за простой схематики их можно запитывать через usb хост разъем, что я и делаю тут. Так же в корпусе прорези для uSD карты и microusb разъема. Постоянно включенный порт хаба выведен наружу, чтобы в него можно было втыкать флешки и жесткие диски, и копировать с них музыку в коллекцию.
Вот так оно выглядит на рендере:А вот так в реальной жизни:
Наконец, на самом верхнем этаже у нас живет VMA2016 усилители. Вход line-in и mic-in выведены на корпус. (Чтобы можно было воткнуть, например, гитару или микрофон, на случай если я захочу отомстить соседям на даче за регулярное пьяное караоке в стиле «шансон»)
Наконец, вот так все это выглядит в сборе. Красить зеленый пластик я не хотел, потому финальный варант перепечатал в черном варианте:
Внизу специальная скоба для крепления фильтра от пыли. От него в итоге пришлось отказаться, как и от 30мм вентилятора, в пользу 120мм, и очень тихого, который я разместил на верхней крышке конструкции.
Для крепления к стене использовались обычные стальные уголки, к которым все это крепится М3 винтами.
А вот так оно выглядит, если закрепить на стене:
Софт
Вот примерный список программных компонентов, которые я использовал в этой поделке:
- skyforge — собирает кастомизированную корневую фс на базе debian
- groovebasin — Плеер с веб-интерфейсом, написанный на node.js. Дополнительно умеет прикидываться mpd
- alsamixer-webui — веб интерфейс для микшера
- nginx — служит обратным прокси для всего вышеуказанного
- aura — моя библиотека для RPC c привязками к луа. lua скрипт под названием shard-ctl ее использует для того, чтобы щелкать питанием портов и релешками
- Кучка баш скриптов и unit файлов для systemd, которые управляют охлаждемнием, запускают службы и т.п.
WIFI & Время
Вайфай настраивается штатным механизмом в debian через /etc/network/interfaces.d/. Так как у ТВ стика отсутствует RTC то сразу после настройки соединения я получаю точное время по NTP. Вот конфиг /etc/network/interfaces.d/wlan0
auto wlan0 iface wlan0 inet dhcp wpa-ssid frostgate wpa-psk hackmedude post-up ntpdate -s arvale.lab
N.B. Лучше заменить arvale.lab на ближайший ntp сервер. Я использую имеющийся в локальной сети OpenWRT роутер для этого дела.
Unit’ы для Systemd
Последовательность старта достаточно сложная. После того, как все стандартные службы вроде ssh и сети загрузились, надо подать питание на usb звуковуху и usb<->sata мост, дождатся, пока мост определится и подмонтировать диск с коллекцией музыки. Только после этого можно запускать groovebasin и подавать питание на усилители.
Это разруливается тремя unit файлами для systemd (которые были переделаны из скриптов с до-systemd эпохи):
groove-mount (Хреновое имя, осталось от до-systemd скриптов. Сейчас он только подает питание на диски)
[Unit] Description=powers on disks [Service] Type=oneshot User=root ExecStart=/usr/local/bin/groovemount WorkingDirectory=/ [Install] WantedBy=groovebasin.service
Вот так тупо выглядит скрипт /usr/local/bin/groovemount. Таймауты гарантируют, пиковый ток не будет большим.
#!/bin/bash -x export PATH=$PATH:/usr/local/bin:/usr/bin/:/usr/sbin shard-ctl --usb 1 on sleep 3 shard-ctl --usb 2 on sleep 3 shard-ctl --relay 1 on sleep 1 shard-ctl --relay 2 on exit 0 |
srv.mount (Монтирует жесткий диск с коллекцией)
[Unit] After=groove-mount.service [Mount] What=/dev/disk/by-uuid/2B234EA2293F197A Where=/srv [Install] WantedBy=groovebasin.service Wants=groove-mount.service
Наконец, groovebasin.service (В debian пакете оного скрипта не было, пришлось написать быстренько). Обращаю внимание на директиву working-directory которая указывает на каталог, где хранится config.json. Он у меня лежит на внешнем жестком диске.
[Unit] Description=groovebasin music player Documentation=http://groovebasin.com/ [Service] User=root ExecStart=/usr/bin/groovebasin WorkingDirectory=/srv/groovebasin/ [Install] WantedBy=multi-user.target
Эти скрипты связаны между собой зависимостями, и работает (на вскидку) надежнее, чем старые скрипты со времен до SystemD.
groovebasin config.json
{ "host": "0.0.0.0", "port": 8081, "dbPath": "groovebasin.db", "musicDirectory": "/srv/", "mpdHost": "0.0.0.0", "mpdPort": 6600, "encodeQueueDuration": 8, "sslKey": null, "sslCert": null }
Ничего особенного тут нет, я только прописал путь к музыке и еще я убрал отсюда API ключи. Их надо воткнуть свои, следуя руководству groovebasin.
/etc/asound.conf
Так как в большей части моей музыкальной коллекции всего два канала, а динамиков больше двух, то необходимо делать апмикс. Это решается прописыванием конфига asound.conf. До кучи, даже с отключенной в ядре встроенной звуковухой, usb звук регистрировался с индексом 1, а не 0. Потому из коробки, без прописывания asound.conf нифига не завелось.
Вот мой /etc/asound.conf который решает эти проблемы:
pcm.!default { type plug slave { pcm "ch51up" } } ctl.!default { type hw card 1 } # for 5.1 speakers pcm.ch51up { slave.pcm "hw:1,0" slave.channels 6 type route ttable.0.0 1 ttable.1.1 1 ttable.0.2 1 ttable.1.3 1 ttable.0.4 1 ttable.1.4 1 ttable.0.5 1 ttable.1.5 1 }
Управление вентилятором и усилителями
На моей платке-хабе 3 реле. Как и внутренними usb портами, ими можно управлять из консили через утилиту shard-ctl.
В моем случае реле 1 и 2 включают передние/сабвуфер усилители и задние соответственно. Третье реле управляет вентилятором.
Я использую smartctl, обрабатываю выхлоп башем и включаю/выключаю вентилятор в зависимости от полученных данных. Да, вот такой вот костыль, господа и дамы.
#!/bin/bash TEMP=`smartctl -A /dev/sda|grep Temperature_Celsius|awk '{print $10}'` if [ $TEMP -lt "35" ]; then shard-ctl --relay 3 off fi if [ $TEMP -gt "40" ]; then shard-ctl --relay 3 on fi echo "Current temp: $TEMP" |
Этот скрипт вызывает cron каждую минуту.
Web морда
На девайсе работают две независимые апликухи: Groovebasin (написан на node.js) and alsa-mixer (написан на python). Чтобы заставить «ужа» и «ежа» делить между собой 80й порт, нам потребуется nginx в режиме reverse proxy.
OpenWRT роутер работающий у меня выдает этому устройству доменное имя ‘iceshard.lab’, а две строчки в конфигах заставили направлять на этот хост так же запросы к player.iceshard.lab и mixer.iceshard.lab.
Далее, остается только добавить несколько строчек в nginx.conf
Here’s my nginx.conf:
server { listen 80; server_name player.iceshard.lab; client_max_body_size 64m; location / { proxy_pass http://127.0.0.1:8081; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } server { listen 80; server_name mixer.iceshard.lab; location / { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
После этого groovebasin доступен по адресу player.iceshard.lab, а alsamixer по адресу mixer.iceshard.lab.
Автоматическое отключение питания усилителей
Когда музыка не играет из динамиков можно услышать едва слышимый белый шум. Обычно его не слышно, а если включить вывод даже очень тихой музыки он пропадает. После замены блока питания на менее шумный различить шум стало практически невозможно (особенно если работает 3д-принтер или ЧПУ станок). Но когда в комнате тишина, то это раздражает и жутко. Пришлось сделать еще один скрипт. Он использует консольную утилиту mpc чтобы получать события от groovebasin и опрашивает его статус. Когда уже 120 секунд, как ничего не играет — выключаем усилители. Если что-то начало играть — включаем их снова.
Скриншоты веб-интерфейса
Десктоп приложение и «горячие» клавиши
Я использовал electron чтобы быстро сделать приложение, которое сидит в трее и выскакивает по сочетанию «Win+A». Так же в выпадающей менюшке можно вызвать окно микшера. Electron штука стремная, прожорливая, и для чего-то более важного я стараюсь держаться от нее подальше. Но в данном случае это было самым быстрым и удобным решением проблемы.
Прочее
Эта заметка оказалась очень жирной, даже несмотря на то, что я документировал только моменты, которые могут вызвать проблемы. Заранее прошу извинить меня, что не документировал некоторые вещи подробнее. Тем не менее, если Вам захочется собрать себе примерно такой же бокс, и не лень улучшить что-то из того, что есть — pull реквесты на github’е всегда приветствуются 😉
Opensource
- Скрипты, которые используются чтобы сделать корневую фс можно взять в этом репозитории на github. Skyforge соберет корневую файловую систему, установит в нее пакеты ядром, сгенерирует initramfs и положит в /boot готовый boot.scr. Результат будет в архиве, который можно будет распаковать на SD карту (См. linux-sunxi wiki, там описано как сделать загрузочную SD карту)
- Electron приложение можно at найти тут
- А все механические части корпуса у меня на thingiverse
- А еще можно поставит пару лайков проекту на hackaday.io (Хотя я едва ли буду дальше улучшать его, так как эта штука делает ровно то, что должна и делает это хорошо)