Pip-Boy на Raspberry Pi — стильный и практичный элемент интерьера
Вступление
Давно мечтал собрать футуристичный компьютер из вселенной Fallout, но понимал, что проект потребует немало времени. Готовые варианты, в которые достаточно вставить смартфон с приложением, меня не устраивали: хотелось чего-то посерьёзнее. Главным препятствием сначала казалось изготовление корпуса. Когда у меня появился 3D-принтер, я вспомнил об этой задумке и решил воплотить её в жизнь. Затем были сборка и покраска, пайка и немало «мучений» со старой Raspberry Pi.
Работу над проектом я начал давно и собирал устройство постепенно, в свободное время. Дополнительные трудности возникли из-за того, что я не покупал компоненты строго по инструкции: в некоторых местах пришлось импровизировать и использовать обходные решения. Когда сборка была завершена, выяснилось, что готовое ПО из выбранного репозитория обладает весьма скромными возможностями. Самостоятельно переписывать программу тогда было некогда, поэтому проект надолго отправился на полку. Вернуться к нему помогли ИИ-агенты и их возможности. За одни выходные я адаптировал под свои задачи Python-репозиторий, который почти десять лет не обновлялся на GitHub, — и устройство наконец заняло достойное место в интерьере.
За основу я взял проект с сайта Adafruit: там опубликованы файлы деталей и инструкция по сборке. Однако в процессе выяснилось, что руководство во многом устарело. Энкодер, например, предусмотрен лишь для внешнего вида: схемы подключения и кода для него нет, а программное обеспечение давно нуждается в обновлении. Но обо всём по порядку:
Корпус
Печать основной детали корпуса заняла 17 часов — без учёта неудачных попыток: некоторые элементы пришлось перепечатывать, поскольку я неправильно рассчитал поддержки. После печати детали потребовали подгонки: из-за перегрева платформы самые крупные из них слегка деформировались. Я нарастил материал содой и суперклеем, а затем обработал поверхности напильником и наждачной бумагой. Это оказалось быстрее и проще, чем заново расходовать пластик и время на печать. Корпус покрасил обычной тёмной краской, а ржавые оттенки добавил кистью. Кажется, получилось создать нужное настроение пустоши, хотя результат, конечно, можно улучшить.

Первая неудачная попытка запуска: экран и звук
В инструкции используется Raspberry Pi Model B+, а у меня много лет без дела лежала Raspberry Pi 1. Основным ограничением казалось меньшее количество GPIO на моей плате, но я подсчитал, сколько выводов потребуется для периферии, и решил попробовать — всё равно устройство простаивало. Сначала я запустил готовый образ Adafruit, но на этой плате он не загрузился: процесс зависал при инициализации экрана. Возможно, образ рассчитан на другую ревизию платы или просто неработоспособен. Разбираться я не стал и решил настроить систему самостоятельно — тем более что дисплей из инструкции найти не удалось. В итоге выбрал более свежий образ Raspbian, совместимый со старой платой: 2020-02-13-raspbian-buster (его можно скачать из FTP-архива Raspbian). Карту отформатировал с помощью SD Formatter, а образ записал через BalenaEtcher — на этом этапе всё прошло без проблем.
2.1. TFT-дисплей 3,5 дюйма
Найти предусмотренный инструкцией Adafruit PiTFT Plus — 3,5-дюймовый TFT-дисплей с разрешением 480×320 и сенсорным экраном — не удалось. Поэтому я выбрал аналог из ассортимента местного радиомагазина: 3,5-дюймовый TFT с тачскрином XPT2046 и разрешением 480×320. Для начала хотелось просто включить его и проверить, появляется ли изображение. Первым делом настроил SSH, чтобы выполнять дальнейшие действия через терминал.
Сразу дисплей, разумеется, не заработал. Тогда, до появления ИИ-помощников, пришлось перелопатить обсуждения похожих проблем на Stack Overflow. Драйвер устанавливаем из репозитория 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 выглядел убедительнее, ему не хватало звукового сопровождения. Усилитель я тоже выбрал из наличия в радиомагазине: УНЧ PAM8403 мощностью 2×3 Вт с питанием 2,5–5 В постоянного тока. Raspberry Pi этой версии выдаёт монофонический звук, а вход для микрофона мне не нужен. Я отрезал штекер 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. Переключатель и кнопки
Найти пятипозиционный переключатель не получилось, поэтому я взял доступный шестипозиционный галетный. Один его контакт можно просто оставить незадействованным. При работе с Arduino можно рассчитывать на встроенные подтягивающие резисторы, но Raspberry Pi я подключал к кнопкам впервые и не был уверен в особенностях настройки подтяжки. Поэтому решил сделать её аппаратно. Использовал классическую схему: резистор на 10 кОм подключил к питанию рядом с переключателем, а резистор на 300 Ом установил последовательно в провод, идущий к GPIO одноплатника.
Внутри корпуса не хватало места, чтобы установить экран отдельно. Выпаивать заводскую гребёнку феном — сомнительное удовольствие, а переходников, похожих на те, что показаны в инструкции Adafruit, у нас в продаже не было. Поэтому я припаял провода непосредственно к началу гребёнки GPIO самой «малины». Экран без проблем подключается сверху, и система работает. Выглядит не слишком аккуратно, зато соединение получилось надёжным.

Для проверки кнопок воспользовался штатной утилитой raspi-gpio. После подключения пяти контактов переключателя выполняем команду:
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
Если передвинуть переключатель и снова выполнить команду, состояние выводов изменится. Утилита позволяет также настраивать GPIO, но эту задачу удобнее поручить Python-скрипту при запуске. Кстати, я не сразу заметил, что GPIO 17 занят экраном 3,5 дюйма для прерываний сенсора: подключённая к нему кнопка не работала. Пришлось перенести её на GPIO 18.
3.2. Энкодер
Инкрементальный энкодер я подключил по этой схеме. Выводы A и B сначала присоединил к контактам 14 и 15, поскольку UART не использую. Перед подключением нужно проверить, что 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
Затем задаём статические адреса для проводного и беспроводного интерфейсов в /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
После перезагрузки оба интерфейса успешно подключились. Я разобрал адаптер и припаял его непосредственно внутри корпуса, но после этого он перестал работать. Почти час ушёл на проверку соединений, пока я не вспомнил, что здесь, вероятно, как и в Ethernet, используется дифференциальная пара. Не ожидал, что даже десять сантиметров провода могут повлиять на работу. Скрутил провода МГТФ вручную, образовав витую пару для data + и data -, — и адаптер заработал. Учтите: этот способ настройки подходит для Raspbian Buster. В новых версиях Raspberry Pi OS сетью управляет NetworkManager, поэтому конфигурация выполняется иначе.

3.4. Первый запуск оригинального Pip-Boy
Исходная оболочка Pip-Boy находится в репозитории sabas1080/pypboy. Программа написана на pygame для Python 2.7. Образ Raspbian я выбрал не случайно: в нём 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 — тогда меню можно будет проверить с клавиатуры, иначе возможны ложные срабатывания. Уже при первом запуске я был в восторге от тёплой цветовой гаммы интерфейса. Правда, впечатление быстро сменилось разочарованием: по сути, работал только один экран, а функций почти не было. Внутри корпуса всё разместилось очень плотно, но даже при запущенном приложении температура процессора с радиатором держалась в пределах 50–60 градусов, что выглядит вполне приемлемо.

Часть 4. Программная часть: Python 3 и новые экраны
Разочаровавшись в возможностях оригинальной программы, я решил изучить несколько форков, адаптированных к современным условиям. Даже самые свежие из них не предлагали ничего принципиально нового — в основном добавляли пару экранов. Поэтому проще оказалось взять исходный код sabas1080 и доработать его под себя. Затем проект снова надолго отложился. Когда появились ИИ-инструменты, я решил посвятить ему выходной и добавить несколько экранов. Pip-Boy интересен мне прежде всего как интерьерный объект, поэтому в программу захотелось включить часы, погоду и курсы валют.
4.1. Управление: клавиатура, кнопки GPIO и энкодер
Для удобства тестирования на компьютере я унифицировал ввод: клавиатура, GPIO-кнопки и энкодер теперь формируют одинаковые действия (knob_1..5, dial_up/down). На ПК клавиши 1–5 переключают вкладки, а вместо энкодера используются стрелки; на Raspberry Pi работают физические органы управления. Пять кнопок переключателя подключены к GPIO 4, 18, 27, 22 и 23 — их соответствия задаются в GPIO_ACTIONS файла config.py. Опрос кнопок реализован с защитой от дребезга контактов. Энкодер тоже теперь работает: для него написан полноценный квадратурный декодер на основе таблицы переходов. При срабатывании прерываний на выводах A и B таблица возвращает +1 для вращения по часовой стрелке (действие dial_up) и -1 для вращения против часовой стрелки (dial_down). С помощью энкодера можно перебирать варианты на разных экранах, а на экране погоды — переключать города.
4.2. Новые экраны
-
Status — классический экран. В нижнем правом углу EFF я разместил температуру процессора; она отображается только на Raspberry Pi, но не в Windows.

-
Часы — экран с логотипом Nuka-Cola и датой. В настройках можно указать разные города.
-
Валюты — курсы USD, EUR и CNY к рублю по данным ЦБ, а также BTC, ETH и TON к доллару. Для валют добавлены PNG-иконки. Все данные поступают из открытых API, не требующих токена.

-
Погода — текущие условия и прогноз на пять дней от Open-Meteo. Города переключаются энкодером, а их список задаётся в конфигурационном файле.
-
Карты — фрагмент карты города на основе OSM. Идея интересная, но на первой Raspberry Pi всё работает очень медленно. Я добавил кэш XML-файлов в каталог
cache/и увеличил масштаб, однако это почти не помогло: отрисовка по-прежнему даётся плате с трудом. Поэтому функции прокрутки и перемещения карты я реализовывать не стал.
Экраны, которым требуется доступ к сети, получают данные в фоновых потоках с 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 Pi: оптимизировать работу карты на моей плате так и не получилось. Да и сама эта функция скорее развлечение. В целом я рад, что довёл проект до рабочего состояния.
Стоимость компонентов:
|
Компонент |
Примерная цена |
|---|---|
|
Raspberry Pi 1 B+, б/у |
~2000 ₽ (уже была в наличии) |
|
Карта памяти microSD 16 ГБ, Class 10 |
~400 ₽ |
|
TFT-дисплей 3,5 дюйма с тачскрином XPT2046 |
~1500 ₽ |
|
Усилитель PAM8403 |
~150 ₽ |
|
Динамик 3 Вт |
~200 ₽ |
|
Шестипозиционный галетный переключатель |
~300 ₽ |
|
Инкрементальный энкодер |
~150 ₽ |
|
Wi-Fi-адаптер TP-Link WN727N, б/у |
~500 ₽ (уже был в наличии) |
|
Провод МГТФ, резисторы и термоклей |
~300 ₽ |
|
Пластик для корпуса, около 400 г |
~1000 ₽ |
|
Жёлтый светодиод — индикатор питания |
~20 ₽ |
|
Неодимовые магниты 5×2 мм, около 20 шт. |
~100 ₽ |
|
Итого |
~6520 ₽ |
Полезные ссылки:
Короткое видео работы устройства:
Эксклюзивность не помешала энтузиасту портировать Gothic Classic на Android
Экс-глава PlayStation посоветовал Sony выбрать между PS6 и портативной консолью
Бывший глава PlayStation объяснил, зачем ремейки нужны даже популярным игровым сериям
Among Us помогла раскрыть похищение 17-летней девушки, которую удерживали более года
Ремейк The Legend of Zelda: Ocarina of Time возглавил рейтинг Metacritic по оценкам игроков
Глава CI Games подтвердил выход Lords of the Fallen II на физических носителях
Древние божества среди радиоактивных пустошей: самые мрачные тайны вселенной Fallout
На Nintendo Switch 2 появилась бесплатная Call of Duty: Warzone