-
-
Notifications
You must be signed in to change notification settings - Fork 68
Hardware Setup
This page covers the hardware requirements, wiring, and configuration for running GPT Home on a Raspberry Pi with optional peripherals like an display.
| Component | Purpose | Example |
|---|---|---|
| Raspberry Pi 3B+ or 4B | Main compute | Amazon |
| MicroSD Card (32GB+) | Storage | Amazon |
| USB Microphone | Voice input | Amazon |
| Speaker (USB or 3.5mm) | Audio output | Amazon |
| Power Supply | 5V 3A | Official RPi PSU |
| Component | Purpose | Example |
|---|---|---|
| I2C Display (128x32) | Status display | Amazon |
| UPS Battery | Portable power | Amazon |
| Case | Protection | Amazon |
| Standoffs & Screws | Mounting | Amazon |
βββββββββββββββββββββββ
β Raspberry Pi 4B β
β GPIO Header β
βββββββββββββββββββββββ
3V3 (1) (2) 5V
SDA1 GPIO2 (3) (4) 5V ββββ I2C Display VCC
SCL1 GPIO3 (5) (6) GND ββββ I2C Display GND
GPIO4 (7) (8) GPIO14
GND (9) (10) GPIO15
GPIO17 (11) (12) GPIO18
GPIO27 (13) (14) GND
GPIO22 (15) (16) GPIO23
3V3 (17) (18) GPIO24
GPIO10 (19) (20) GND
GPIO9 (21) (22) GPIO25
GPIO11 (23) (24) GPIO8
GND (25) (26) GPIO7
GPIO0 (27) (28) GPIO1
GPIO5 (29) (30) GND
GPIO6 (31) (32) GPIO12
GPIO13 (33) (34) GND
GPIO19 (35) (36) GPIO16
GPIO26 (37) (38) GPIO20
GND (39) (40) GPIO21
βββββββββββββββββββββββ
β I2C Connections β
βββββββββββββββββββββββ€
β Pin 3 (GPIO2) = SDA β ββββ I2C Display SDA
β Pin 5 (GPIO3) = SCL β ββββ I2C Display SCL
βββββββββββββββββββββββ
GPT Home supports two categories of displays:
Full displays support all display modes and animations:
| Display Type | Interface | Resolution | Notes |
|---|---|---|---|
| ILI9341 (SPI) | SPI | 320x240 | Full color, recommended |
| HDMI Display | Framebuffer | Various | Full color, any resolution |
Available Display Modes (full displays only):
-
Smart (Contextual) - Intelligent display that adapts to activity:
- Shows clock when idle
- Real-time waveform visualization during user speech and TTS responses
- Automatically returns to clock after 3 seconds of silence
- Shows tool-specific animations (weather, music, lights, timer, etc.)
- Smooth transitions between states
- Clock - Digital clock display with date
- Weather - Current weather conditions with animations
- Gallery - Rotating image slideshow
- Waveform - Audio visualization (standalone mode)
- Off - Display disabled
Host IP Display: All display modes show the host IP address in the top-left corner. This is the Raspberry Pi's LAN IP (e.g., 192.168.x.x), not a Docker internal address, allowing easy access to the web interface at http://<displayed-ip>/settings. The IP is obtained via `hostname -I.
| Display Type | Interface | Resolution | Notes |
|---|---|---|---|
| ST7789 (SPI) | SPI | 240x280 | Full color, e.g. WhisPlay HAT |
SPI displays are auto-detected when the SPI device (/dev/spidev0.0) is present and pin configuration exists in settings. No enable toggle required β they work like I2C displays (always on if connected). SPI display parameters (width, height, rotation, GPIO pins, SPI bus/CS, speed) are configurable via the web UI under Settings β Hardware β SPI Display.
WhisPlay HAT auto-configuration: When a WM8960 audio codec is detected alongside an SPI device, the backend automatically writes default ST7789 settings (240x280, rotation 180Β°, DC=27, RST=4, BL=22, SPI bus 0, CS 0, 62.5MHz) to settings.json on startup. This means WhisPlay HATs work out of the box with no manual configuration.
SPI displays show a dashboard with clock, host IP, and CPU temperature when idle. During activity they show Spotify now-playing with album art, waveform visualization, and assistant responses. Screensaver styles (starfield, matrix, bounce, fade) apply to SPI displays in the same way as full displays.
| Display Type | Interface | Resolution | Notes |
|---|---|---|---|
| SSD1306 (I2C) | I2C | 128x32 | Monochrome, text only |
| SSD1306 (I2C) | I2C | 128x64 | Monochrome, text only |
- Showing the assistant's text responses
- Displaying status messages (Listening, Thinking, etc.)
- Showing host IP address and CPU temperature in the header
Host IP Display: The I2C display shows the host IP address in the top-left corner of the header. This is the Raspberry Pi's LAN IP (e.g., 192.168.x.x), not a Docker internal address, allowing easy access to the web interface at http://<displayed-ip>/settings. The IP is obtained via hostname -I.
This separation exists because the small monochrome I2C display screens are not suitable for complex graphics and animations. If you want the full display experience with modes and animations, use an HDMI or PiScreen display.
GPT Home supports 3.5" ILI9486-based SPI displays (commonly called PiScreen) which provide full graphical display capabilities. These displays are affordable and provide good resolution for the display modes.
The display driver uses the official piscreen overlay with the modern DRM/tinydrm driver (dtoverlay=piscreen,drm), which is maintained by the Raspberry Pi Foundation and supports ILI9486-based displays including Waveshare 3.5" and compatible models.
| Display | Controller | Resolution | Notes |
|---|---|---|---|
| Waveshare 3.5" (A) | ILI9486 | 480x320 | Most common, well supported |
| Waveshare 3.5" (B) | ILI9486 | 480x320 | Supports touch |
| Goodtft 3.5" MHS35 | ILI9486 | 480x320 | Budget option |
| Generic 3.5" RPi | ILI9486/ILI9341 | 480x320 | Check pinout carefully |
The setup script automatically installs the PiScreen display overlay and enables SPI on every Raspberry Pi. No flags or prompts required β just run the setup script:
curl -sSL https://raw.githubusercontent.com/judahpaul16/gpt-home/main/contrib/setup.sh | bash -s -- --no-buildThe script will:
- Download and install
piscreen.dtbofrom the official RPi firmware repo - Add
dtparam=spi=onanddtoverlay=piscreen,drm(commented out) to/boot/firmware/config.txt
The overlay and SPI are installed but not activated. To enable the PiScreen, use the web UI (Settings β Hardware β Hardware Display Mode β PiScreen). This properly configures all required settings (max_framebuffers=0, disable_fw_kms_setup=1, disabling vc4-kms-v3d) and reboots.
Use the web interface at Settings β Hardware β Hardware Display Mode to switch between HDMI and PiScreen. This modifies /boot/firmware/config.txt and /boot/firmware/cmdline.txt, then reboots:
-
PiScreen mode: Enables
dtoverlay=piscreen,drm,dtparam=spi=on,disable_fw_kms_setup=1, setsmax_framebuffers=0, disablesdtoverlay=vc4-kms-v3d. Appendsfbcon=map:11to cmdline.txt. -
HDMI mode: Disables the PiScreen overlay, SPI, and
disable_fw_kms_setup. Re-enablesvc4-kms-v3d, setsmax_framebuffers=2. Removesfbcon=map:11from cmdline.txt.
The piscreen overlay is incompatible with vc4-kms-v3d β both cannot be active simultaneously. If both are detected, the system reports a "conflict" state.
I2C displays work independently regardless of mode.
If the automatic setup didn't work (e.g., no internet during setup, custom display), follow these steps:
sudo curl -fsSL https://github.com/raspberrypi/firmware/raw/master/boot/overlays/piscreen.dtbo \
-o /boot/firmware/current/overlays/piscreen.dtboOn non-Ubuntu systems the overlays directory may be at /boot/overlays/ or /boot/firmware/overlays/ instead.
Add these lines to /boot/firmware/config.txt (or /boot/config.txt on older Pi OS):
dtparam=spi=on
dtoverlay=piscreen,drm
disable_fw_kms_setup=1
max_framebuffers=0And comment out:
#dtoverlay=vc4-kms-v3dmax_framebuffers=0 prevents the firmware from allocating a BCM2708 framebuffer at fb0, which would conflict with the ili9486 DRM driver's own fb0 registration and cause a kernel crash (sysfs: cannot create duplicate filename '/class/graphics/fb0').
Append to the end of the single line in /boot/firmware/cmdline.txt:
fbcon=map:11
sudo rebootAfter reboot, check if the DRM device is created:
ls -la /sys/class/drm/ | grep -i spi
sudo dmesg | grep -i ili9486Most 3.5" PiScreen displays connect directly to the GPIO header. If your display uses a breakout board:
ββββββββββββββββββββ ββββββββββββββββββ
β ILI9486 PiScreen β β Raspberry Pi β
β 480x320 SPI β β β
ββββββββββββββββββββ€ ββββββββββββββββββ€
β VCC ββββββββΌββββββββββ€ Pin 2 (5V) β
β GND ββββββββΌββββββββββ€ Pin 6 (GND) β
β CS ββββββββΌββββββββββ€ Pin 24 (CE0) β
β RST ββββββββΌββββββββββ€ Pin 22 (GPIO25)β
β DC ββββββββΌββββββββββ€ Pin 18 (GPIO24)β
β MOSI ββββββββΌββββββββββ€ Pin 19 (MOSI) β
β SCK ββββββββΌββββββββββ€ Pin 23 (SCLK) β
β LED ββββββββΌββββββββββ€ Pin 12 (GPIO18)β
β MISO ββββββββΌββββββββββ€ Pin 21 (MISO) β
ββββββββββββββββββββ ββββββββββββββββββ
Note: HAT-style displays plug directly onto the GPIO header and don't require manual wiring.
PiScreen/HDMI rotation is set via the web UI at Settings β Hardware β Display Rotation β PiScreen / HDMI. This modifies the rotate= parameter on the overlay line in config.txt and requires a reboot.
| Value | Orientation |
|---|---|
| 0Β° | Portrait (default) |
| 90Β° | Landscape (most common) |
| 180Β° | Portrait inverted |
| 270Β° | Landscape inverted |
Manual config.txt alternative:
dtoverlay=piscreen,drm,rotate=90
I2C display rotation is set via the web UI at Settings β Hardware β Display Rotation β I2C Display. This is applied live without a reboot. Values are 0Β° (normal), 90Β°, 180Β° (default, flipped), 270Β°.
Display shows white screen:
- Verify the overlay is loaded:
sudo dmesg | grep -i ili9486 - Check SPI is enabled:
ls /dev/spi* - Ensure correct overlay for your display model
- Try different rotation values
Display not detected:
- Check DRM devices:
ls /sys/class/drm/(look for non-HDMI connectors) - Verify overlay is in config.txt:
grep piscreen /boot/firmware/config.txt(should be uncommented) - Verify SPI is enabled:
grep "dtparam=spi=on" /boot/firmware/config.txt - Verify
vc4-kms-v3dis commented out:grep vc4-kms /boot/firmware/config.txt(must be disabled for PiScreen) - Ensure the overlay file exists in the overlays directory
- Check kernel messages:
sudo dmesg | grep -i -E "ili9486|drm|spi" - Use the debug endpoint:
curl http://gpt-home.local/api/display/debug | jq
Touch not working:
- Install evdev driver:
sudo apt install xserver-xorg-input-evdev - Calibrate touch:
sudo apt install xinput-calibrator && xinput_calibrator
Low frame rate:
- Increase SPI speed in overlay:
dtoverlay=piscreen,drm,speed=32000000 - Note: Too high speed may cause artifacts
The I2C display is an optional peripheral for displaying text responses. It's a simple, low-power way to see what the assistant is saying without looking at the web interface.
Note: The I2C display does not support display modes (Smart, Clock, Weather, Gallery). Those features require a full graphical display (HDMI or PiScreen).
ββββββββββββββββββββ ββββββββββββββββββ
β SSD1306 I2C β β Raspberry Pi β
β 128x32 I2C β β β
ββββββββββββββββββββ€ ββββββββββββββββββ€
β VCC ββββββββΌββββββββββ€ Pin 4 (5V) β
β GND ββββββββΌββββββββββ€ Pin 6 (GND) β
β SDA ββββββββΌββββββββββ€ Pin 3 (GPIO2) β
β SCL ββββββββΌββββββββββ€ Pin 5 (GPIO3) β
ββββββββββββββββββββ ββββββββββββββββββ
- Via raspi-config:
sudo raspi-config
# Navigate to: Interface Options β I2C β Enable- Or manually:
# Edit /boot/config.txt
sudo nano /boot/config.txt
# Add or uncomment:
dtparam=i2c_arm=on
# Reboot
sudo reboot- Verify I2C:
# Install tools
sudo apt-get install -y i2c-tools
# Detect devices (should show 0x3C for SSD1306)
sudo i2cdetect -y 1Expected output:
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
The DisplayManager in src/display/manager.py handles full graphical displays only (HDMI, PiScreen, SPI). It does not manage I2C displays.
from src.display.manager import DisplayManager
from src.display.base import DisplayMode
# Get the display manager instance
manager = DisplayManager.get_instance()
# Initialize (auto-detects full displays, ignores I2C displays)
await manager.initialize()
# Check if a full display is available
if manager.supports_modes:
# Set display mode (only works with full displays)
await manager.set_mode(DisplayMode.SMART)
# Show a message with animation
await manager.show_user_message("Hello!", duration=3)Display Architecture:
| Component | Manages | Features |
|---|---|---|
DisplayManager |
HDMI, PiScreen | Display modes, animations, tool visualizations |
AuxiliaryDisplay |
SPI (ST7789), I2C (SSD1306) | Dashboard/clock, screensavers, Spotify with album art (SPI), text output (I2C), waveform |
The easiest way to configure audio is through the web interface:
- Navigate to Settings β Hardware section
- Under Audio Output, select your preferred device from the dropdown
- Available devices include:
- bcm2835 Headphones - 3.5mm audio jack (default)
- bcm2835 HDMI - HDMI audio output
- USB Audio - USB speakers/DAC (if connected)
- Adjust volume using the slider
- The container will automatically restart when you change the audio device
When an HDMI display is connected, the system automatically:
- Detects the HDMI audio device
- Configures audio output to use HDMI
- This overrides the default headphone jack setting
If you prefer to use the 3.5mm jack even with HDMI connected, manually select "bcm2835 Headphones" in the web UI after startup.
The system uses ALSA for audio. The web UI automatically manages /etc/asound.conf, but you can also configure it manually:
sudo nano /etc/asound.confFor 3.5mm Headphone Jack (default):
pcm.!default {
type plug
slave.pcm {
type hw
card Headphones
device 0
}
}
ctl.!default {
type hw
card Headphones
}
For HDMI Audio Output (with software volume control):
HDMI audio devices don't have hardware mixer controls, so we use ALSA's softvol plugin to enable volume adjustment:
# Software volume control wrapper for HDMI
pcm.softvol {
type softvol
slave.pcm "hdmi_hw"
control {
name "SoftMaster"
card 0
}
min_dB -51.0
max_dB 0.0
}
# Direct hardware access to HDMI
pcm.hdmi_hw {
type hw
card 0
}
# Default PCM uses softvol for volume control
pcm.!default {
type plug
slave.pcm "softvol"
}
ctl.!default {
type hw
card 0
}
Note: The
SoftMastercontrol is created automatically after the first sound is played through the device. The web UI volume slider controls this software mixer.
Simple HDMI config (no volume control):
pcm.!default {
type plug
slave.pcm {
type hw
card vc4hdmi0
device 0
}
}
ctl.!default {
type hw
card vc4hdmi0
}
Note: Using
type pluginstead oftype hwprovides automatic format conversion, improving compatibility with various audio applications.
# List playback devices
aplay -l
# Example output:
# card 0: Headphones [bcm2835 Headphones], device 0: bcm2835 Headphones [bcm2835 Headphones]
# card 1: vc4hdmi0 [vc4-hdmi-0], device 0: MAI PCM i2s-hifi-0 [MAI PCM i2s-hifi-0]
# card 2: vc4hdmi1 [vc4-hdmi-1], device 0: MAI PCM i2s-hifi-0 [MAI PCM i2s-hifi-0]
# List recording devices
arecord -l
# Test speaker (replace card number as needed)
speaker-test -c 2 -D plughw:0,0 # Headphones
speaker-test -c 2 -D plughw:1,0 # HDMI
# Test microphone
arecord -d 5 test.wav
aplay test.wavMost USB microphones work out of the box. Verify with:
# List USB devices
lsusb
# Check audio devices
cat /proc/asound/cards
# Test recording
arecord -D plughw:1,0 -f cd -d 5 test.wavI2S audio HATs combine a DAC and microphone on a single board (e.g., RaspiAudio MIC+ uses MAX98357A DAC + ICS-43432 MEMS mic). These HATs use the googlevoicehat-soundcard overlay.
Detection: setup.sh detects I2S HATs via:
- HAT EEPROM (
/proc/device-tree/hat/vendor) - I2S capture analysis β records 1s, counts unique 16-bit samples (
od -An -tx2 -w2 | sort -u | wc -l). Real hardware produces >20 unique values (noise floor); a floating I2S bus produces 1-5 (pull-up). - If the kernel module is already loaded, probes the existing card. Otherwise, temporarily loads the overlay for probing, then unloads.
Runtime phantom detection: The backend detects phantom I2S devices at startup (_detect_phantom_i2s_cards()). It probes cards matching keywords: googlevoicehat, voicehat, wm8960. If a card produces β€20 unique samples, it's filtered from all device list APIs and the overlay is commented out in config.txt. _detect_missing_audio_overlays() only acts on ACTIVE (uncommented) overlays using three-layer detection: EEPROM (definitive for Google AIY), real non-phantom ALSA card presence via _has_real_i2s_card() (covers RaspiAudio MIC+ which has no EEPROM), and I2C address for WM8960. Already-commented overlays are never re-enabled automatically β this prevents false positives on devices where hardware was legitimately removed. A reboot notification appears in the frontend.
No hardware mixer controls: These cards have no I2C codec, so amixer scontrols returns empty. The backend automatically adds ALSA softvol plugins (SoftMaster for output volume, Capture for mic gain) to provide software volume control via the web UI.
The googlevoicehat-soundcard.dtbo overlay is not shipped by Ubuntu β setup.sh downloads it from the RPi firmware repository if missing.
Via Web UI:
- Navigate to Settings β Hardware β Audio Output
- Use the volume slider to adjust output level
Via Command Line:
# Open mixer
alsamixer
# Set volume (tries multiple mixer controls for compatibility)
amixer sset Master 80% # General
amixer sset PCM 80% # PCM output
amixer sset Headphone 80% # Headphones
amixer sset HDMI 80% # HDMI audio
# Set microphone volume
amixer sset Capture 80%Tip: Different audio cards use different mixer control names. If one command doesn't work, try another.
Spotifyd runs as a Spotify Connect audio endpoint with MPRIS D-Bus support. Configuration is generated at runtime by the startup script in compose/spotify/Dockerfile.
The generated /root/.config/spotifyd/spotifyd.conf:
[global]
backend = "alsa"
device_name = "GPT Home"
bitrate = 320
cache_path = "/root/.spotifyd/cache"
disable_discovery = true # false only when no cached credentials exist
zeroconf_port = 1234
use_mpris = true
dbus_type = "system"
initial_volume = 90
volume_controller = "softvol"
autoplay = true
enable_volume_normalisation = false
device_type = "computer"-
Authentication: The backend auto-provisions credentials at
/root/.spotifyd/cache/oauth/credentials.jsonusing the PKCE access token. No manual device selection required.- On first boot (no credentials):
disable_discovery = falseallows zeroconf provisioning - After credentials are cached:
disable_discovery = trueforces direct credential auth, which reliably exposes MPRIS D-Bus
- On first boot (no credentials):
-
Playback Control: MPRIS D-Bus is the primary control path (play, pause, next, previous, volume). Falls back to Spotify Web API when D-Bus is unavailable.
-
Search: Backend uses Spotify Web API with PKCE OAuth tokens.
-
Audio Output: Uses the same ALSA configuration as the backend via shared
alsa-configDocker volume. The user's hardware page audio selection is respected by both containers.
Option 1: wpa_supplicant
# Edit config
sudo nano /etc/wpa_supplicant/wpa_supplicant.confctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=US
network={
ssid="YourWiFiSSID"
psk="YourWiFiPassword"
key_mgmt=WPA-PSK
}
Option 2: nmcli
# List available networks
nmcli device wifi list
# Connect
sudo nmcli device wifi connect "YourSSID" password "YourPassword"GPT Home advertises itself as gpt-home.local:
# Check Avahi status
sudo systemctl status avahi-daemon
# Test mDNS resolution
ping gpt-home.localConfiguration is in /etc/avahi/avahi-daemon.conf:
[server]
host-name=gpt-home
allow-interfaces=eth0,wlan0GPT Home uses a microservices architecture with Docker Compose. Each service runs in its own container:
| Service | Purpose | Container |
|---|---|---|
db |
PostgreSQL + pgvector for memory storage | gpt-home-db-1 |
nginx |
Reverse proxy routing | gpt-home-nginx-1 |
backend |
Voice assistant + FastAPI backend | gpt-home-backend-1 |
frontend |
React frontend (production) | gpt-home-frontend-1 |
spotify |
Spotify Connect + Avahi mDNS | gpt-home-spotify-1 |
# View container logs
docker compose logs -f backend
docker compose logs -f spotify
# Restart individual services
docker compose restart backend
docker compose restart spotify
# Check container status
docker compose psRecommended: Raspberry Pi UPS Power Supply
When using battery power, implement safe shutdown on low battery:
# Example low battery detection (if UPS supports I2C)
import smbus
def check_battery_level():
# UPS-specific implementation
pass
def safe_shutdown():
subprocess.run(["shutdown", "now"])| Peripheral | Interface | Support | Notes |
|---|---|---|---|
| SSD1306 I2C (128x32) | I2C | β | Default display |
| SSD1306 I2C (128x64) | I2C | β | Modify init code |
| USB Microphone | USB | β | Plug and play |
| USB Speaker | USB | β | Configure ALSA |
| 3.5mm Speaker | Audio Jack | β | Use Headphones device |
| RaspiAudio MIC+ | I2S | β | Auto-detected, softvol |
| WhisPlay HAT (WM8960 + ST7789) | I2S+I2C+SPI | β | Audio auto-detected at 0x1a, SPI display auto-configured |
| Google AIY Voice HAT | I2S | β | Uses googlevoicehat overlay |
| Bluetooth Speaker | Bluetooth | Manual pairing required | |
| GPIO LEDs | GPIO | β | Custom implementation |
The project uses platform-agnostic libraries for I2C display support:
-
luma.oled for SSD1306/SH1106/SSD1309 displays (uses
smbus2+/dev/i2c-Ndirectly) - gpiod (libgpiod v2) for GPIO pin control on SPI displays
These work on any Linux SBC with standard I2C and GPIO kernel interfaces (Raspberry Pi, Orange Pi, etc.).
The setup script automatically configures HDMI settings. When you run setup.sh, it adds the following to /boot/config.txt:
-
hdmi_force_hotplug=1- Enables HDMI even without display at boot -
hdmi_drive=2- Forces HDMI mode (not DVI) for proper signal detection -
hdmi_blanking=0- Prevents HDMI from going to standby
If you're still having issues after running setup.sh and rebooting:
- Check if framebuffer exists:
ls -la /dev/fb*
# Should show /dev/fb0 if HDMI is detected- Check DRM status:
cat /sys/class/drm/card*/status
# Should show "connected" for active displays-
Use the Refresh button in Settings β Display to re-detect displays without restarting the service.
-
For Docker containers, ensure proper device access. The
privileged: truesetting in docker-compose.yml grants access to all devices including/dev/fb*.
GPT Home supports display hotswapping - you can connect/disconnect displays without restarting:
- Connect or disconnect the display physically
- Go to Settings β Display in the web interface
- Click the Refresh button (π) to re-detect displays
- The display manager will reinitialize with the newly detected display
Auto-Detection: GPT Home runs a background monitor that checks for display changes every 5 seconds. If pyudev is available, displays are detected instantly via udev events. If you connect a new display, it will be automatically detected and initialized without manual intervention.
Note: The setup script automatically configures
hdmi_force_hotplug=1andhdmi_drive=2in/boot/config.txtfor HDMI hotplug support.
GPT Home uses KMS/DRM for full graphical displays:
| Display Type | Driver | Notes |
|---|---|---|
| HDMI / PiScreen | KMS/DRM (SDL2) | Direct rendering via /dev/dri
|
| I2C Display | I2C (ssd1306) | Text-only, no compositor needed |
No Wayland compositor or X11 is required - the application renders directly to the display hardware.
GPT Home uses SDL2 with the KMS/DRM backend to render directly to display hardware via /dev/dri. This approach does not require a Wayland compositor or X11 - the application renders directly to the framebuffer.
- DRM device access (
/dev/dri/card*) - Proper permissions (container runs privileged or has device access)
- KMS-compatible display driver
- The active virtual console (
/dev/tty1, read from/sys/class/tty/tty0/active) visible to the container, whichprivileged: trueprovides
The kernel console shares the same screen. While the display renders, src/display/console.py holds the active virtual console in graphics mode (KDSETMODE KD_GRAPHICS), which stops the login prompt, cursor blink, kernel messages and console blanking from drawing over the animations. The console returns to text mode when the display mode is set to OFF and on shutdown. contrib/setup.sh disables getty@tty1.service so no login prompt runs on that console, and adds consoleblank=0 and vt.global_cursor_default=0 to cmdline.txt so the console stays lit and cursorless before the backend takes over. Log in over SSH or on another console (Ctrl+Alt+F2).
The docker-compose.yml is already configured for KMS/DRM:
services:
backend:
environment:
SDL_VIDEODRIVER: kmsdrm
SDL_KMSDRM_REQUIRE_DRM_MASTER: "0"
XDG_RUNTIME_DIR: /tmp
devices:
- /dev/dri:/dev/dri
privileged: true# Check for DRM devices
ls -la /dev/dri/
# Check connector status
cat /sys/class/drm/card*-*/status
# Use the API to check display status
curl http://gpt-home.local/api/display/status | jqI2C displays do not require KMS/DRM and will work regardless.
If displays aren't being detected, use the debug endpoint to see detailed information:
# From the host or via the web interface
curl http://gpt-home.local/api/display/debug | jqThis shows:
- Framebuffer devices (
/dev/fb*) - DRM connectors and their status
- DRI devices (
/dev/dri/*) - HDMI configuration from
/boot/config.txt
Common DRM connector status values:
-
connected- Display is connected and should work -
disconnected- No display connected -
unknown- Status couldn't be determined
GPT Home uses multiple detection methods for Raspberry Pi:
-
Framebuffer (
/dev/fb0) - Legacy and fkms drivers -
DRM/KMS (
/sys/class/drm/card*-*) - vc4-kms-v3d driver - kmsprint - KMS debug tool
If using the newer vc4-kms-v3d driver (default on Pi 4/5), ensure:
# Check current driver in /boot/config.txt
cat /boot/config.txt | grep dtoverlay
# For KMS driver (recommended):
dtoverlay=vc4-kms-v3d
# For legacy driver (if KMS doesn't work):
dtoverlay=vc4-fkms-v3dIf HDMI is physically connected but still not detected after running setup.sh and rebooting:
- Check DRM connector status directly:
cat /sys/class/drm/card*-*/status
# Should show "connected" for the HDMI port-
Verify the display is powered on - Some monitors need to be on before the Pi boots
-
Try forcing a resolution in
/boot/config.txt:
hdmi_group=1
hdmi_mode=16 # 1080p 60Hz- For Docker, ensure the container has device access:
# docker-compose.yml
backend:
privileged: true
# or explicitly:
devices:
- /dev/fb0:/dev/fb0
- /dev/dri:/dev/dri- Check I2C is enabled:
ls /dev/i2c* # Should show /dev/i2c-1- Verify wiring and I2C address:
i2cdetect -y 1 # Should show 0x3c- Check permissions:
sudo usermod -a -G i2c $USER- Check ALSA devices on the host:
aplay -l
arecord -l- Check ALSA devices inside the container (shows ALSA lib errors hidden from Python):
docker exec gpt-home-backend-1 bash -c 'arecord -l 2>&1; echo "---"; aplay -l 2>&1'If you see cannot access file /etc/alsa/conf.d/... or hooks failed, removing configuration, the backend container is missing the /etc/alsa mount. Both /usr/share/alsa and /etc/alsa must be mounted from the host β the host's alsa.conf references hook files in /etc/alsa/conf.d/ that must exist for ALSA to initialize.
- Verify volume:
amixer
alsamixer # Interactive mixer- Test with simple playback:
speaker-test -t sine -f 440 -c 2-
Audio coming from wrong device (e.g., headphones instead of HDMI):
- The container must be restarted after changing audio devices in the web UI
- Check current ALSA config:
docker exec gpt-home-backend-1 cat /etc/asound.conf- Restart the container:
docker compose restart backend
-
HDMI audio not working:
- Ensure HDMI is set to output audio in
/boot/firmware/config.txt:
hdmi_drive=2- Check HDMI audio device exists:
aplay -l | grep -i hdmi- Test HDMI audio directly:
speaker-test -c 2 -D plughw:1,0 # Adjust card number as needed - Ensure HDMI is set to output audio in
-
Volume changes not taking effect:
- Different cards use different mixer controls (Master, PCM, Headphone, HDMI)
- Try setting volume on specific control:
amixer sset PCM 80% amixer sset Headphone 80%
- Test recording:
arecord -d 5 -f cd test.wav && aplay test.wav- Adjust sensitivity:
alsamixer # Increase Capture level- Check USB power:
lsusb -v # Look for power warnings-
Microphone device not found automatically:
The system auto-detects USB microphones by searching for devices with keywords like "usb", "microphone", "mic" in priority order. If your microphone isn't detected:
# List all audio input devices arecord -l # Check container logs for device selection docker logs gpt-home-backend-1 2>&1 | grep -i "microphone\|AudioCapture"
Look for messages like (enable DEBUG filter in Event Logs):
-
DEBUG:audio_capture:Found microphone via ALSA: card N- Device was found -
DEBUG:audio_capture:Selected microphone: <name>- PyAudio device mapped - No microphone messages at all - No suitable device detected
-
-
"No Default Input Device Available" error:
This means no default ALSA input device is configured. The system will still try to find USB microphones automatically. Ensure your USB microphone is connected:
# Verify USB device is connected lsusb | grep -i audio # Check if it appears as an ALSA device cat /proc/asound/cards
-
ALSA assertion failures or crashes:
If you see errors like
PaAlsaStream_Initialize: Assertion failed, the device may be misconfigured or in use by another process:# Check for processes using audio fuser -v /dev/snd/* # Restart the container to release audio devices docker compose restart backend
- See Configuration for environment variables
- Check Development Guide for local development
- Review Architecture for system overview