Skip to content

Hardware Setup

Judah Paul edited this page Sep 12, 2026 · 17 revisions

πŸ”Œ 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.

Hardware Requirements

Minimum Components

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

Optional Components

Component Purpose Example
I2C Display (128x32) Status display Amazon
UPS Battery Portable power Amazon
Case Protection Amazon
Standoffs & Screws Mounting Amazon

GPIO Pinout

                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                   β”‚   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
                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Display Setup

GPT Home supports two categories of displays:

Full Graphical 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.

SPI Displays

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.

Simple Text Displays (I2C)

Display Type Interface Resolution Notes
SSD1306 (I2C) I2C 128x32 Monochrome, text only
SSD1306 (I2C) I2C 128x64 Monochrome, text only

⚠️ Important: I2C displays are text-only displays. They do not support display modes (Smart, Clock, Weather, Gallery, etc.). Instead, they serve as a simple output device for:

  • 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.


PiScreen Display Setup (ILI9486)

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.

Supported PiScreen 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

Automatic Setup

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-build

The script will:

  1. Download and install piscreen.dtbo from the official RPi firmware repo
  2. Add dtparam=spi=on and dtoverlay=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.

Switching Between HDMI and PiScreen

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, sets max_framebuffers=0, disables dtoverlay=vc4-kms-v3d. Appends fbcon=map:11 to cmdline.txt.
  • HDMI mode: Disables the PiScreen overlay, SPI, and disable_fw_kms_setup. Re-enables vc4-kms-v3d, sets max_framebuffers=2. Removes fbcon=map:11 from 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.

Manual Setup

If the automatic setup didn't work (e.g., no internet during setup, custom display), follow these steps:

1. Install the overlay

sudo curl -fsSL https://github.com/raspberrypi/firmware/raw/master/boot/overlays/piscreen.dtbo \
  -o /boot/firmware/current/overlays/piscreen.dtbo

On non-Ubuntu systems the overlays directory may be at /boot/overlays/ or /boot/firmware/overlays/ instead.

2. Configure config.txt

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=0

And comment out:

#dtoverlay=vc4-kms-v3d

max_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').

3. Configure cmdline.txt

Append to the end of the single line in /boot/firmware/cmdline.txt:

fbcon=map:11

4. Reboot

sudo reboot

5. Verify Display

After reboot, check if the DRM device is created:

ls -la /sys/class/drm/ | grep -i spi
sudo dmesg | grep -i ili9486

Wiring Diagram (SPI)

Most 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.

Rotation Options

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Β°.

Troubleshooting PiScreen Displays

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-v3d is 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

References


Display Setup (I2C) - Text Output Only

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).

Wiring Diagram

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   SSD1306 I2C    β”‚         β”‚  Raspberry Pi  β”‚
β”‚   128x32 I2C     β”‚         β”‚                β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€         β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚      VCC  ───────┼──────────  Pin 4 (5V)    β”‚
β”‚      GND  ───────┼──────────  Pin 6 (GND)   β”‚
β”‚      SDA  ───────┼──────────  Pin 3 (GPIO2) β”‚
β”‚      SCL  ───────┼──────────  Pin 5 (GPIO3) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Enabling I2C

  1. Via raspi-config:
sudo raspi-config
# Navigate to: Interface Options β†’ I2C β†’ Enable
  1. Or manually:
# Edit /boot/config.txt
sudo nano /boot/config.txt

# Add or uncomment:
dtparam=i2c_arm=on

# Reboot
sudo reboot
  1. Verify I2C:
# Install tools
sudo apt-get install -y i2c-tools

# Detect devices (should show 0x3C for SSD1306)
sudo i2cdetect -y 1

Expected 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: -- -- -- -- -- -- -- --

Display Manager

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

Audio Configuration

Web UI Configuration (Recommended)

The easiest way to configure audio is through the web interface:

  1. Navigate to Settings β†’ Hardware section
  2. Under Audio Output, select your preferred device from the dropdown
  3. Available devices include:
    • bcm2835 Headphones - 3.5mm audio jack (default)
    • bcm2835 HDMI - HDMI audio output
    • USB Audio - USB speakers/DAC (if connected)
  4. Adjust volume using the slider
  5. The container will automatically restart when you change the audio device

HDMI Audio Auto-Selection

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.

ALSA Configuration

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.conf

For 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 SoftMaster control 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 plug instead of type hw provides automatic format conversion, improving compatibility with various audio applications.

Finding Audio Devices

# 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.wav

USB Microphone Setup

Most 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.wav

I2S Audio HATs (RaspiAudio MIC+, Google AIY Voice)

I2S 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:

  1. HAT EEPROM (/proc/device-tree/hat/vendor)
  2. 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).
  3. 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.

Volume Control

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 Configuration

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"

Architecture

  1. Authentication: The backend auto-provisions credentials at /root/.spotifyd/cache/oauth/credentials.json using the PKCE access token. No manual device selection required.

    • On first boot (no credentials): disable_discovery = false allows zeroconf provisioning
    • After credentials are cached: disable_discovery = true forces direct credential auth, which reliably exposes MPRIS D-Bus
  2. 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.

  3. Search: Backend uses Spotify Web API with PKCE OAuth tokens.

  4. Audio Output: Uses the same ALSA configuration as the backend via shared alsa-config Docker volume. The user's hardware page audio selection is respected by both containers.


Network Configuration

Wi-Fi Setup

Option 1: wpa_supplicant

# Edit config
sudo nano /etc/wpa_supplicant/wpa_supplicant.conf
ctrl_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"

mDNS (Avahi)

GPT Home advertises itself as gpt-home.local:

# Check Avahi status
sudo systemctl status avahi-daemon

# Test mDNS resolution
ping gpt-home.local

Configuration is in /etc/avahi/avahi-daemon.conf:

[server]
host-name=gpt-home
allow-interfaces=eth0,wlan0

Docker Service Architecture

GPT 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

Service Management

# 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 ps

Power Considerations

UPS Battery

⚠️ WARNING: Do not connect batteries directly to GPIO. Use a proper UPS HAT or power management board.

Recommended: Raspberry Pi UPS Power Supply

Safe Shutdown

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 Support

Compatibility Matrix

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

I2C Display Libraries

The project uses platform-agnostic libraries for I2C display support:

  • luma.oled for SSD1306/SH1106/SSD1309 displays (uses smbus2 + /dev/i2c-N directly)
  • 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.).


Troubleshooting

HDMI Display Not Detected

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:

  1. Check if framebuffer exists:
ls -la /dev/fb*
# Should show /dev/fb0 if HDMI is detected
  1. Check DRM status:
cat /sys/class/drm/card*/status
# Should show "connected" for active displays
  1. Use the Refresh button in Settings β†’ Display to re-detect displays without restarting the service.

  2. For Docker containers, ensure proper device access. The privileged: true setting in docker-compose.yml grants access to all devices including /dev/fb*.

Display Hotswap

GPT Home supports display hotswapping - you can connect/disconnect displays without restarting:

  1. Connect or disconnect the display physically
  2. Go to Settings β†’ Display in the web interface
  3. Click the Refresh button (πŸ”„) to re-detect displays
  4. 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=1 and hdmi_drive=2 in /boot/config.txt for HDMI hotplug support.

Display Driver

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.


KMS/DRM Display Setup

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.

Requirements

  • 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, which privileged: true provides

Console ownership

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).

Docker Configuration

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

Verifying KMS/DRM Setup

# 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 | jq

I2C displays do not require KMS/DRM and will work regardless.

Display Debug Information

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 | jq

This 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

Raspberry Pi Display Detection

GPT Home uses multiple detection methods for Raspberry Pi:

  1. Framebuffer (/dev/fb0) - Legacy and fkms drivers
  2. DRM/KMS (/sys/class/drm/card*-*) - vc4-kms-v3d driver
  3. 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-v3d

HDMI Still Not Detected

If HDMI is physically connected but still not detected after running setup.sh and rebooting:

  1. Check DRM connector status directly:
cat /sys/class/drm/card*-*/status
# Should show "connected" for the HDMI port
  1. Verify the display is powered on - Some monitors need to be on before the Pi boots

  2. Try forcing a resolution in /boot/config.txt:

hdmi_group=1
hdmi_mode=16  # 1080p 60Hz
  1. 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

I2C Display Not Working

  1. Check I2C is enabled:
ls /dev/i2c*  # Should show /dev/i2c-1
  1. Verify wiring and I2C address:
i2cdetect -y 1  # Should show 0x3c
  1. Check permissions:
sudo usermod -a -G i2c $USER

Audio Not Working

  1. Check ALSA devices on the host:
aplay -l
arecord -l
  1. 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.

  1. Verify volume:
amixer
alsamixer  # Interactive mixer
  1. Test with simple playback:
speaker-test -t sine -f 440 -c 2
  1. 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
  2. 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
  3. 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%

Microphone Not Detecting Speech

  1. Test recording:
arecord -d 5 -f cd test.wav && aplay test.wav
  1. Adjust sensitivity:
alsamixer  # Increase Capture level
  1. Check USB power:
lsusb -v  # Look for power warnings
  1. 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
  2. "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
  3. 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

Next Steps

Clone this wiki locally