Pip-Boy на Raspberry Pi как полезный предмет интерьера

Всегда хотел себе собрать футуристический компьютер из вселенной Fallout, но понимал, что это может занять много времени. Готовые поделки, куда можно просто вставить телефон с приложением, меня не вдохновляли, хотелось чего-то более хардкорного. Первоначально самой большой проблемой было создание корпуса. С появлением 3D-принтера, вспомнил про эту идею и решил реализовать. Далее последовала сборка, покраска, работа паяльником и много "страданий" со старой Raspberry Pi.

Проект начал делать давно, собирал устройство по мере свободного времени. Добавляло сложности, что я не закупал специально под проект детали как в мануале, поэтому где-то пришлось выкручиваться костылями. После сборки понял, что готовое ПО из репозитория, которое я хотел использовать, имеет крайне ограниченный функционал, а времени переписывать самому не было и проект был отправлен в долгий ящик. Пинок к доделке дали ИИ-агенты и их возможности. В итоге, репозиторий на питоне 2, который на гите не обновлялся почти 10 лет, за выходные был переделан под свои нужды и устройство заняло свое почетное место в интерьере.
Изначальный проект взял с сайта Adafruit - там есть архив с деталями и шагами сборки. Инструкция, по мере сборки проекта, как оказалась, не особо актуальная. Оказалось что энкодер стоит чисто для красоты, без схемы подключения и кода для него, а само ПО очень устаревшее. Но обо всем по порядку:
Корпус
На основную деталь корпуса ушло 17 часов печати (без учета брака, некоторые детали пришлось печатать повторно, неверно рассчитал поддержки сразу). Когда всё было распечатано, пришлось подгонять детали: где-то от перегрева платформы самые большие элементы немного выгнулись. Наращивал их содой с суперклеем, потом подгонял напильником и наждачной бумагой. Это оказалось проще и быстрее чем снова тратить пластик и время. Далее модель была покрашена обычной тёмной краской, а «ржавые» оттенки придавал кисточкой. По итогу, вроде получилось передать ту самую атмосферу пустоши, но конечно, можно сделать и лучше.

Первый неудачный запуск. Экран и звук
Внутри по мануалу должна использоваться Raspberry Pi Model B+, у меня же была в наличии Raspberry Pi 1, лежавшая без дела много лет. Главным ограничением я видел меньшее число GPIO (у моей версии), но посчитав сколько чего нужно для периферии, было принято решение попробовать на ней, все равно лежит без дела. Первым делом попробовал готовый настроенный образ adafruit, но он не стартанул на ней - загрузка умирала где-то на инициализации экрана. Похоже, образ просто заточен под другую ревизию платы или, возможно, вообще неработоспособен, решил не разбираться с этим и настроить самому (как минимум, я не мог найти дисплей из мануала). Подобрал в итоге образ raspbian посвежее, который без проблем заведётся на старой плате. Остановился на 2020-02-13-raspbian-buster (качается с ftp-архивов Raspbian). Форматировал карточку через SD-formatter, записывал образ через BalenaEtcher, здесь все прошло без сюрпризов.
2.1. Дисплей 3.5" TFT
Экран adafruit PiTFT Plus 480x320 3.5" TFT+Touchscreen, как в мануале, в наличии я не нашел. Поэтому пришлось выбирать из наличия в нашем радиомагазине, остановился на таком аналоге - 3.5" TFT с тачем XPT2046 и разрешением 480×320. Что же, пора запустить и хотя бы проверить изображение. Первым делом включил и настроил SSH, чтобы дальнейшие действия делать через терминал.
Сразу экран, конечно, не заработал. На тот момент, во времена без ИИ пришлось поковырять архивы со схожей проблемой на stackoverflow. Драйвер дисплея ставим из репозитория Waveshare:
git clone https://github.com/waveshare/LCD-show.git
cd LCD-show/
chmod +x LCD35-show
./LCD35-showПлата перезагрузится сама (если нет - sudo reboot). Дисплей и тач должны заработать. Хотя тач нам и не нужен в этом проекте. Не удержался, поставил внутрь корпуса и запустил прогу, посмотреть некое превью, что должно получиться в итоге.

2.2. Подключаем звук: усилитель PAM8403 и динамик
Чтобы Pip-Boy был более реалистичным, нужно добавить ему звука. Усилитель, опять же, купил из наличия в радиомагазине УНЧ 2x3W PAM8403 с питанием 2.5–5V DC. Звук с Raspberry этой версии моно, а контакт микрофона мне и вовсе не нужен. Порезал джек 3.5 от старой гарнитуры - объединил контакт микрофона с землёй, каналы «левый-правый» тоже закоротил. Всё посадил на термоклей, потому что на этом усилителе нет отверстий под саморезы.
Проверить звук просто:
wget https://download.samplelib.com/wav/sample-6s.wav
aplay sample-6s.wav
Часть 3. Переключатели, энкодер и Wi-Fi
Перед тем как проверять ПО, хотелось подключить все аппаратные штуки. Всё спаять, закрыть корпус, поставить на стол и проверять/модернизировать ПО через wi-fi. Просмотрев код от Adafruit, я понял: программа почти не имеет функций, и GPIO почти всё закомментировано. Про энкодер в оригинальном руководстве прямо написано:
You’ll notice at this point our rotary encoder actually isn’t wired to anything. That’s because we haven’t written any custom code to make it work with the pypboy software. This is more of a prop...
В любом случае я решил подключить и переключатель, и энкодер - этого должно хватить для базовых функций.
3.1. Переключатель и кнопки
С поиском 5-позиционного переключателя не заладилось, в итоге, в наличии нашёл только 6-позиционный галетный переключатель. Просто можно не задействовать один контакт. Если в случае arduino ещё можно понадеяться на встроенные pull-up, к Raspberry я подключал кнопки первый раз и не знал особенностей настройки подтяжек. Решил, что надёжнее распаять подтяжку аппаратным способом. Схему выбрал классическую: 10 кОм подтяжку к питанию распаял прямо около переключателя, 300 Ом в разрыв провода прямо на GPIO одноплатника.
В корпусе не хватило высоты, чтобы распаять экран отдельно, а выпаивать родную гребёнку феном - то ещё развлечение. Модных переходников, как в инструкции Adafruit, у нас не продаётся. Поэтому припаялся прямо в начало гребёнки GPIO самой «малины» - экран спокойно подключается сверху, и всё работает. Выглядит непрезентабельно, зато надежно.

Для проверки кнопок использую родную утилиту raspi-gpio. Спаяли 5 контактов переключателя - вводим:
raspi-gpio get 4,18,27,22,23Видим текущую конфигурацию. Level=0 как раз обозначает нажатую кнопку.
pi@raspberrypi:~ $ raspi-gpio get 4,18,27,22,23
GPIO 4: level=0 fsel=0 func=INPUT
GPIO 18: level=1 fsel=0 func=INPUT
GPIO 22: level=1 fsel=0 func=INPUT
GPIO 23: level=1 fsel=0 func=INPUT
GPIO 27: level=1 fsel=0 func=INPUTМеняем положение переключателя, запрашиваем команду заново - видим изменения. Через эту же программу можно поменять конфигурацию пинов, но лучше пусть это делает питон-скрипт на старте. Кстати, изначально я не заметил, что 17-й пин занят экраном 3.5 TFT под прерывания тача - кнопка на нём не работала. Пришлось перепаять на 18-й.
3.2. Энкодер
По такой схеме подключил инкрементальный энкодер: A и B временно подключил на 14 и 15 пины - ведь UART я не использую (нужно предварительно проверить, что он отключен, мне пришлось отключить).

3.3. Wi-Fi для Raspberry Pi 1
Следующим шагом я уже хотел всё упаковать, но смущало, что в Raspberry Pi 1 нет Wi-Fi. Заказал самый простой китайский адаптер за 350 рублей - и меня ждало разочарование. Как я ни собирал разные драйвера, не смог заставить его работать. Оказалось, такие проблемы есть у многих и существует список «одобренных» адаптеров для Raspbian. Один из таких - TP-Link wn727n - давно лежал, купленный для настольного ПК, пока ethernet еще не успел вывести под стол во время ремонта. Было решено пустить его в дело.
Wi-Fi настраивается просто. В /etc/wpa_supplicant/wpa_supplicant.conf прописываем сеть:
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
ssid="имя сети"
psk="пароль сети"
key_mgmt=WPA-PSK
}
В /etc/network/interfaces добавляем беспроводной интерфейс:
allow-hotplug wlan0
iface wlan0 inet manual
wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf
И прописываем статику для проводного и wlan-интерфейса в /etc/dhcpcd.conf:
interface eth0
static ip_address=192.168.1.30/24
static routers=192.168.1.1
static domain_name_servers=8.8.8.8
interface wlan0
static ip_address=192.168.1.31/24
static routers=192.168.1.1
static domain_name_servers=8.8.8.8
Reboot - и всё успешно соединяется. Дальше разбираю адаптер и распаиваю напрямую внутрь корпуса. Включаю - не работает. Потратил почти час на прозвонку и только потом додумался, что здесь, как и в ethernet, скорее всего дифференциальная пара. Я и подумать не мог, что 10 см провода могут сыграть роль. Только заплёл МГТФ на глаз в витую пару (на data + и data -), запустил - всё работает! Примечание: этот способ актуален для Raspbian Buster. В новых версиях Raspberry Pi OS управление сетью перешло к NetworkManager, и настройки задаются иным способом.

3.4. Первый запуск оригинального Pip-Boy
Оригинальная игра-оболочка лежит в репозитории sabas1080/pypboy. Написана на pygame, ещё на Python 2.7. Образ я выбрал неспроста - в нём Python 2.7 ещё есть из коробки. Скачиваем репозиторий и ставим зависимости (виртуальное окружение не делал - кроме этой игры запускать ничего не планировалось):
git clone https://github.com/sabas1080/pypboy.git
cd pypboy
pip install -r requirements.txtНастраиваем графику под наш экран в config.py, почему-то изначально в настройках разрешение ниже и его пришлось поменять:
WIDTH = 480
HEIGHT = 320Тут есть интересный момент: если запустить main.py через ssh - ничего не выйдет, получите ошибку pygame.error: Unable to open a console terminal. Чтобы запустить приложение с графикой через SSH, нужно указать дисплей, куда выводить графику:
export DISPLAY=:0
python main.pyЗапускать скрипт нужно из родительской директории репозитория. На данном этапе всё должно запуститься с изображением и звуком. Если вы еще не успели припаять кнопки переключателя, можно временно закомментировать GPIO в config.py и посмотреть меню с клавиатуры (иначе будут фантомные нажатия на GPIO). Уже здесь я испытал восторг от этого тёплого меню. Но радость быстро проходит - по сути работает один экран и функций практически нет. Внутри все получилось очень плотно, но температура цпу с радиатором не превышала 50-60 градусов при работе приложения, что, кажется, вполне норм.

Часть 4. Программная часть: Python 3 и новые экраны
После разочарования с функциями исходной программы - хотел опробовать пару форков проекта, которые обновляли под современные реалии. Форки посмотрел, даже самые свежие не представляли большой ценности (максимум добавлено пара экранов) - проще оказалось взять исходник sabas1080 и переделать под себя. Далее проект долго пролежал "в столе". Но с появлением ИИ решил потратить выходной и добавить пару экранов. Я решил, что мне пипбой интересен как элемент интерьера, поэтому захотелось добавить часы, погоду и курсы валют.
4.1. Управление: клавиатура, GPIO-кнопки, энкодер
Ввод сделал единообразный для быстрого тестирования на ПК: клавиатура, GPIO и энкодер порождают одни и те же действия (knob_1..5, dial_up/down). На ПК это клавиши 1–5 (вкладки) и стрелки вместо энкодера, на Raspberry - физическая периферия. Пять кнопок переключателя я припаял на GPIO 4, 18, 27, 22, 23 (GPIO_ACTIONS в config.py), опрос реализован с защитой от дребезга. Теперь работает энкодер. Реализован честный квадратурный декодер на таблице переходов. По прерываниям на пинах A/B таблица даёт +1 для CW (действие dial_up) и -1 для CCW (dial_down). Энкодером прокручиваются варианты на разных экранах, а на экране погоды - переключение городов.
4.2. Новые экраны
Status - здесь классика. На экране EFF, внизу справа добавил температуру CPU (отображается только на Raspberry, не на win).

Часы - с логотипом Nuka-Cola и датой. Можно загрузить в настройки разные города.
Валюты - USD/EUR/CNY к рублю (ЦБ) и BTC/ETH/TON к доллару с PNG-иконками валют. Везде используется openApi сервисы без токена

Погода - текущие условия и прогноз на 5 дней через Open-Meteo, город переключается энкодером. Список городов заносится в config файл
Карты - OSM с отображением фрагмента карты города. Прикольная штука, но очень тормозит на первой rapberry. Добавил кэш XML после загрузки в
cache/и увеличил масштаб, что не сильно спасло ситуацию, отрисовка все равно дается тяжело. Поэтому скролл и сдвиг карты делать не стал.
Сетевые экраны ходят в сеть в daemon-потоках с TTL 600 секунд и аккуратно показывают «NO CONNECTION» офлайн.

4.3. Запуск: разбираемся с X
Грузить полноценный рабочий стол на Raspberry Pi 1 с её 512 МБ оперативной памяти нет смысла — он съедает до трети ресурсов, которые критически нужны самому приложению. Поэтому я его отключил.
sudo systemctl disable lightdm
sudo rebootЧтобы потом скрипт мог работать по SSH, нужны некоторые изменения. Изначально main.py на Linux всегда выставлял SDL_VIDEODRIVER=fbcon, из-за чего падала инициализация под X/SSH (Couldn't open X11 display).
Нужно убрать принудительную установку fbcon из кода. Теперь Pygame по умолчанию использует драйвер x11 и корректно цепляется к переменной DISPLAY=:0 Также сделал скрипт быстрого запуска run.sh. Он поднимает «голый» X-сервер, если тот ещё не запущен, и ждёт, пока Pygame сможет к нему подключиться.
4.4. Автозапуск: systemd-сервис
Чтобы Pip-Boy включался сам при подаче питания, без логина и SSH, добавил новый systemd-сервис /etc/systemd/system/pipboy.service:
[Unit]
Description=Pip-Boy 3000
After=systemd-user-sessions.service
[Service]
Environment=DISPLAY=:0
Environment=PYTHONPATH=/home/pi/.local/lib/python3.7/site-packages
WorkingDirectory=/home/pi/pypboy
ExecStart=/home/pi/pypboy/run.sh
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.targetВключаем:
sudo systemctl daemon-reload
sudo systemctl enable pipboy
sudo rebootСервис стартует от root, поэтому sudo X внутри run.sh работает без пароля, а PYTHONPATH указывает на пользовательские пакеты (xmltodict и т.п.).
Итоги
Основные идеи - Raspberry Pi в принципе избыточна под этот функционал и наверное, в роли часов и курса валют подошел бы возможно и esp32 (или что-то подобное с wi-fi). Проще было бы распаять периферию и внутри начинка заняла бы меньше места. Для отображения карты и ее прокрутки/скейла скорее всего потребуется Raspberry мощнее, так у меня и не получилось оптимизировать карту, да и баловство это. В целом, рад, что довел проект до какого-то жизнеспособного состояния.
Стоимость компонентов:
Компонент | Цена (примерно) |
|---|---|
Raspberry Pi 1 B+ Б/У | ~2000 ₽ (была в наличии) |
Карта памяти microSD 16 ГБ (Class 10) | ~400 ₽ |
TFT 3.5" с тачем XPT2046 | ~1500 ₽ |
Усилитель PAM8403 | ~150 ₽ |
Динамик 3 Вт | ~200 ₽ |
Галетный переключатель 6-позиционный | ~300 ₽ |
Энкодер инкрементальный | ~150 ₽ |
Wi-Fi адаптер TP-Link WN727N Б/У | ~500 ₽ (был в наличии) |
МГТФ, резисторы, термоклей | ~300 ₽ |
Пластик для корпуса (~400 г) | ~1000 ₽ |
Светодиод жёлтый (индикатор питания) | ~20 ₽ |
Магниты неодимовые (5×2 мм, ~20 шт.) | ~100 ₽ |
Итого | ~6520 ₽ |
Полезные ссылки:
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.