Setting up a Raspberry Pi as Google Home used to rely on the official Google Assistant SDK. Google deprecated that SDK, leaving thousands of DIY smart speaker projects dead in the water. In 2026, the functional equivalent—and frankly, a much more capable one—is building a custom voice pipeline. By combining the Google Gemini API for conversational intelligence, Picovoice Porcupine for local wake-word detection, and an I2S audio HAT for low-latency sound, you get a smart speaker that outperforms the legacy hardware.

This guide targets the Raspberry Pi 5 (8GB) running Raspberry Pi OS (Bookworm/64-bit). We will wire the hardware, configure the I2S audio bus, write the Python orchestration code, and debug the inevitable ALSA/PipeWire audio routing errors that plague modern Pi audio projects.

Hardware Spec Sheet & Pin Mapping

Before writing any code, you need the right silicon. The Raspberry Pi 5's PCIe bus and updated I2S clocking make it vastly superior to the Pi 4 for real-time audio processing, but it requires specific power delivery to prevent brownouts when the speaker amplifier kicks in.

Bill of Materials (2026 Pricing)

ComponentExact Model / VariantEst. CostPurpose & Notes
ComputeRaspberry Pi 5 (8GB)$80Main compute. 8GB RAM handles local wake-word and audio buffering without swapping.
Audio HATReSpeaker 2-Mics Pi HAT (V2.0)$12I2S Audio input/output, WM8960 codec, user button, APA102 RGB LEDs.
Speaker3W 4-Ohm Speaker (JST-PH 2.0)$5Plugs directly into the ReSpeaker HAT's onboard mono amplifier.
Power Supply27W USB-C PD Power Supply$12Official Pi 27W PSU. Prevents brownouts when audio amp draws peak current.
Wake WordPicovoice Porcupine (Free Tier)$0Local, offline wake-word engine (e.g., 'Computer').

ReSpeaker to Pi 5 GPIO Pin Mapping

The ReSpeaker 2-Mics HAT uses the primary I2S bus and a few auxiliary GPIO pins for control. Here is the exact pinout mapping you need to reference when debugging hardware conflicts.

Pi 5 GPIO (BCM)Physical PinReSpeaker FunctionProtocol / Notes
GPIO 1812PCM_CLKI2S Bit Clock
GPIO 1935PCM_FSI2S Frame Sync (LRCLK)
GPIO 2038PCM_DINI2S Data In (Microphone to Pi)
GPIO 2140PCM_DOUTI2S Data Out (Pi to Speaker)
GPIO 1232PWM0 / User LEDMapped in our code as the 'Listening' status LED.
GPIO 1711User ButtonPhysical push-button on the HAT for manual trigger.
GPIO 2 / 33 / 5I2C SDA / SCLUsed to configure the WM8960 audio codec registers.

Assembly and I2S Audio Configuration

Raspberry Pi OS Bookworm uses PipeWire as the default audio server, which aggressively hijacks I2S devices and causes massive headaches for raw Python audio scripts. We need to load the I2S overlay and configure ALSA to bypass PipeWire for our Python environment.

⚠️ Safety & Power Callout: Never attach or remove the ReSpeaker HAT while the Pi is powered. The I2S and I2C pins are directly tied to the Pi 5's SoC. A misaligned HAT shorting 5V to GPIO 18 will instantly fry the I2S peripheral block.
  1. Seat the HAT: Align the ReSpeaker 2-Mics HAT with the 40-pin header. Ensure no pins are bent. Secure with the included standoffs.
  2. Enable I2S and I2C: Open a terminal and run sudo raspi-config. Navigate to Interface Options and enable I2C. Then, edit the boot config: sudo nano /boot/firmware/config.txt.
  3. Add the Overlay: Add the following line to the bottom of config.txt to load the WM8960 codec driver:
    dtparam=i2s=on
    dtoverlay=seeed-2mic-voicecard
  4. Reboot and Verify: Run sudo reboot. After rebooting, type aplay -l. You must see card 1: seeed2micvoicec in the output. If you only see the HDMI audio card, the I2S overlay failed to load.
  5. Bypass PipeWire for ALSA: Create a local ALSA config to force Python's PyAudio to use the HAT directly. Run nano ~/.asoundrc and paste:
    pcm.!default {
      type asym
      playback.pcm "hw:1,0"
      capture.pcm "hw:1,0"
    }
    

Python Voice Assistant Code (Gemini API)

This script targets the Raspberry Pi 5 and ReSpeaker HAT. It listens for a wake word, records your command, sends it to the Google Gemini API, and plays the synthesized response. It includes explicit GPIO pin definitions and error handling for both the audio stream and the network API.

Prerequisites: pip install pyaudio google-generativeai pvporcupine RPi.GPIO

import pyaudio
import wave
import io
import os
import RPi.GPIO as GPIO
import pvporcupine
import google.generativeai as genai
import time

# --- PIN DEFINITIONS ---
LED_LISTENING_PIN = 12  # BCM 12 (Physical 32) - PWM0 for status LED
BUTTON_PIN = 17         # BCM 17 (Physical 11) - ReSpeaker User Button

# --- AUDIO CONFIGURATION ---
# ReSpeaker WM8960 strictly prefers 16kHz for voice capture
RATE = 16000
CHANNELS = 1
CHUNK = 1024
FORMAT = pyaudio.paInt16
RECORD_SECONDS = 5

# --- API SETUP ---
# Store your key in an environment variable, never hardcode it
genai.configure(api_key=os.environ.get('GEMINI_API_KEY'))
model = genai.GenerativeModel('gemini-1.5-flash')

def setup_gpio():
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(LED_LISTENING_PIN, GPIO.OUT)
    GPIO.setup(BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.output(LED_LISTENING_PIN, GPIO.LOW)

def listen_and_record(audio_interface):
    GPIO.output(LED_LISTENING_PIN, GPIO.HIGH) # Turn on LED
    print('Listening...')
    
    frames = []
    stream = None
    try:
        stream = audio_interface.open(
            format=FORMAT,
            channels=CHANNELS,
            rate=RATE,
            input=True,
            frames_per_buffer=CHUNK
        )
        for _ in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
            data = stream.read(CHUNK, exception_on_overflow=False)
            frames.append(data)
    except OSError as e:
        print(f'Audio Stream Error: {e}')
        return None
    finally:
        if stream and stream.is_active():
            stream.stop_stream()
            stream.close()
            
    GPIO.output(LED_LISTENING_PIN, GPIO.LOW) # Turn off LED
    
    # Save to in-memory WAV
    wav_buffer = io.BytesIO()
    with wave.open(wav_buffer, 'wb') as wf:
        wf.setnchannels(CHANNELS)
        wf.setsampwidth(audio_interface.get_sample_size(FORMAT))
        wf.setframerate(RATE)
        wf.writeframes(b''.join(frames))
    wav_buffer.seek(0)
    return wav_buffer

def query_gemini(audio_data):
    # Note: Gemini 1.5 Flash accepts audio natively via the API in 2026
    try:
        prompt = 'Transcribe this audio and answer the user\'s smart home query concisely.'
        audio_part = genai.upload_file(audio_data, mime_type='audio/wav')
        response = model.generate_content([prompt, audio_part])
        return response.text
    except Exception as e:
        print(f'Gemini API Error: {e}')
        return 'I encountered an error processing your request.'

def main():
    setup_gpio()
    audio = pyaudio.PyAudio()
    
    # Initialize Porcupine Wake Word (requires free AccessKey from Picovoice console)
    access_key = os.environ.get('PICOVOICE_ACCESS_KEY')
    porcupine = pvporcupine.create(access_key=access_key, keywords=['computer'])
    
    mic_stream = audio.open(
        format=FORMAT, channels=1, rate=porcupine.sample_rate, 
        input=True, frames_per_buffer=porcupine.frame_length
    )
    
    print('Wake word engine active. Say "Computer".')
    
    try:
        while True:
            pcm = mic_stream.read(porcupine.frame_length, exception_on_overflow=False)
            keyword_index = porcupine.process(pcm)
            
            if keyword_index >= 0:
                print('Wake word detected!')
                mic_stream.stop_stream() # Free the mic for recording
                
                wav_data = listen_and_record(audio)
                if wav_data:
                    answer = query_gemini(wav_data)
                    print(f'Gemini says: {answer}')
                    # Add TTS playback logic here (e.g., gTTS or Piper)
                
                mic_stream.start_stream() # Resume wake word listening
                
    except KeyboardInterrupt:
        print('Shutting down...')
    finally:
        mic_stream.stop_stream()
        mic_stream.close()
        audio.terminate()
        porcupine.delete()
        GPIO.cleanup()

if __name__ == '__main__':
    main()

Debugging: Audio Failures and API Errors

When building a Raspberry Pi as Google Home, 90% of your debugging time will be spent on Linux audio routing. If your script crashes immediately upon trying to open the microphone stream, here is how to fix it.

The "ALSA unable to open slave" Error

Exact Error String: ALSA lib pcm_dmix.c:1032:(snd_pcm_dmix_open) unable to open slave followed by OSError: [Errno -9997] Invalid sample rate.

Ranked Causes & Fixes:

  1. Sample Rate Mismatch (Most Likely): The WM8960 codec on the ReSpeaker HAT hardware-locks to specific sample rates (usually 16000Hz or 48000Hz). If your Python script requests 44100Hz, ALSA fails to open the device. Fix: Ensure RATE = 16000 in the Python script.
  2. PipeWire Hijacking: PipeWire claims the I2S card on boot, blocking PyAudio's direct ALSA access. Fix: Verify your ~/.asoundrc is correctly pointing to hw:1,0 as shown in the assembly steps, or temporarily kill PipeWire with systemctl --user stop pipewire to test.
  3. Missing I2S Overlay: The kernel doesn't know the HAT exists. Fix: Check dmesg | grep i2s. If it's empty, your dtoverlay in config.txt has a typo.

The First Three Things to Check When It Fails

If the hardware is assembled but no audio is passing, run through this exact diagnostic triad before rewriting code:

  1. Check I2C Enumeration: Run i2cdetect -y 1. You should see the WM8960 codec at address 0x1a. If the grid is empty, your I2C is disabled or the HAT is unseated.
  2. Check ALSA Card Index: Run arecord -l. If the ReSpeaker is listed as card 2 instead of card 1 (because a USB webcam stole card 1), your ~/.asoundrc pointing to hw:1,0 will fail. Update the config to match the actual card number.
  3. Check Power Delivery: Run vcgencmd get_throttled. If it returns 0x50000 or similar, your Pi 5 is brownouting. The ReSpeaker's amplifier draws up to 1.5W; a weak phone charger will cause the audio bus to drop out under load. Use the official 27W PD brick.

Gemini API Invalid Argument Error

Exact Error String: google.api_core.exceptions.InvalidArgument: 400 Request contains an invalid argument.

Cause: You are passing raw PCM bytes to the Gemini API instead of a properly formatted WAV file with headers. The listen_and_record() function in the code above uses the wave module to wrap the raw bytes in a WAV header and sets the MIME type to audio/wav. Ensure you aren't bypassing this step.

Extending or Simplifying the Build

Depending on your use case, you might want to strip this project down to its bare essentials or expand it into a full home automation hub.

How to Simplify: Drop the HAT

If you don't want to deal with I2S overlays and ALSA configs, simplify the build by using a USB Mini Microphone and a standard 3.5mm USB Audio Adapter.
Trade-off: You lose the physical user button, the RGB LEDs, and the low-latency hardware echo cancellation of the WM8960 codec. However, USB audio is plug-and-play with PipeWire, eliminating the need for ~/.asoundrc hacks. You will need to change the PyAudio input device index to match the USB mic.

How to Extend: Add Relay Control

To make this a true smart home controller, wire a 4-Channel 5V Relay Module to the Pi's remaining GPIO pins (e.g., GPIO 5, 6, 13, 19) to switch mains-voltage desk lamps or fans.

⚡ Mains Voltage Warning: Switching 120V/240V AC with relays requires strict safety protocols. De-energize the circuit, verify dead with a CAT III multimeter, and use proper enclosure isolation. Never route low-voltage Pi GPIO wires in the same conduit as AC mains. If you are not comfortable with mains wiring, use a smart plug API (like Kasa or Matter) instead of physical relays.

To integrate relays, modify the Gemini system prompt to return structured JSON (e.g., {"action": "light_on", "device": "desk_lamp"}). Parse this JSON in your Python script and trigger GPIO.output(RELAY_PIN, GPIO.LOW) to activate the optocoupler on the relay board. This turns your Raspberry Pi Google Home clone from a simple conversationalist into a physical controller for your workspace.