The term "XBMC" was officially retired in 2014 and replaced by Kodi. However, if you are searching for an "XBMC Raspberry Pi" build today, you are looking for a dedicated, low-power, embedded media center. The direct answer for a modern build is to use a Raspberry Pi 5 (8GB variant) running Raspberry Pi OS Lite (64-bit Bookworm) with Kodi installed in standalone mode. To manage the Pi 5's thermal output during 4K HEVC decoding, the Python code provided below targets the Pi 5 8GB and uses the gpiozero library to drive a PWM 5V fan on physical Pin 12 (GPIO 18).
The Hardware Decision Path: Which Pi for Your Media Center?
Do not waste time trying to force a modern Kodi (v21 Omega) installation onto legacy hardware. Use this decision matrix to select your board. We terminate on the Pi 5 8GB for a future-proof embedded build.
| If your requirement is... | And your budget is... | Choose this board variant | Why? |
|---|---|---|---|
| 1080p H.264 only, basic digital signage | < $40 (Used market) | Raspberry Pi 3B+ (1GB) | Cheap, but lacks hardware H.265 decoding. UI will stutter on modern skins. |
| 4K H.265 (HEVC), single display, budget build | $55 - $75 | Raspberry Pi 4 Model B (4GB) | Capable of 4K60, but runs hot and lacks AV1 support. Nearing end-of-life for smooth UI. |
| 4K AV1, dual 4K displays, fast UI, Python GPIO scripts | $80 (Board only) | Raspberry Pi 5 (8GB) | The Winner. Dedicated video decoder, PCIe Gen 2 for NVMe boot, dual 4K@60Hz, and native lgpio support. |
Parts List & Spec Sheet
This is the exact bill of materials (BOM) for a reliable, bench-tested Pi 5 media center. Total cost is approximately $135.
- Compute: Raspberry Pi 5 (8GB RAM) - $80
- Power: Official Raspberry Pi 27W USB-C PD Power Supply (White/Black) - $12 (Do not use third-party phone chargers; the Pi 5 requires 5V/5A to prevent USB/GPIO current limiting).
- Storage: Samsung PRO Endurance 64GB microSD (MB-MJ64GA) - $14 (Endurance cards survive the constant log writes of a media center OS).
- Thermal: Noctua NF-A4x10 5V PWM (40mm fan) - $15 (Specifically the 5V PWM version, which officially supports 3.3V logic thresholds on the PWM pin).
- Video: UGREEN Micro-HDMI to HDMI 2.1 Cable (6ft) - $8
- Wiring: 22 AWG silicone wire, 2N2222 NPN transistor (optional if your fan doesn't accept 3.3V logic), 1kΩ resistor.
apt to retain full access to the lgpio backend for our fan controller.
Wiring the GPIO PWM Fan (Pin Mapping & Assembly)
The Raspberry Pi 5 SoC will throttle at 80°C. While the official Active Cooler is plug-and-play, building a custom PWM fan controller teaches you hardware-software thermal management. We use physical Pin 12 (GPIO 18) because it is tied to the Pi's hardware PWM0 channel, preventing the CPU jitter and audio interference caused by software PWM.
Pin Mapping Table
| Fan Wire Color | Function | Raspberry Pi 5 Physical Pin | BCM GPIO / Rail |
|---|---|---|---|
| Yellow (or Blue) | PWM Signal | Pin 12 | GPIO 18 (PWM0) |
| Red | VCC (Power) | Pin 4 | 5V Rail |
| Black | GND | Pin 6 | Ground |
Assembly Steps
- De-energize: Ensure the Pi 5 is completely unplugged from the 27W PSU before touching the GPIO header.
- Connect Power: Route the fan's Red wire to Physical Pin 4 (5V) and the Black wire to Physical Pin 6 (GND).
- Connect PWM Logic: Connect the fan's PWM wire directly to Physical Pin 12 (GPIO 18). The Noctua NF-A4x10 5V PWM accepts a 3.3V logic high natively. If using a generic PC fan that strictly requires 5V logic, you must route GPIO 18 through a 1kΩ resistor into the base of a 2N2222 transistor, using the transistor to switch the 5V signal.
- Verify: Use a multimeter in continuity mode to verify no shorts exist between Pin 4 (5V) and Pin 6 (GND) before applying power.
Python PWM Fan Control Code (Targeting Pi 5)
This script reads the CPU thermal zone and scales the fan duty cycle proportionally between 50°C and 70°C. It targets the Raspberry Pi 5 8GB running a 64-bit OS with the python3-gpiozero and python3-lgpio packages installed.
#!/usr/bin/env python3
"""
Pi 5 PWM Thermal Fan Controller
Targets: Raspberry Pi 5 (8GB) on Pi OS Lite 64-bit (Bookworm)
Hardware: 5V PWM Fan on Physical Pin 12 (GPIO 18)
"""
import time
import signal
import sys
from gpiozero import CPUTemperature, PWMOutputDevice
# --- PIN & THRESHOLD DEFINITIONS ---
FAN_PIN = 18 # BCM GPIO 18 (Physical Pin 12, Hardware PWM0)
MIN_TEMP = 50.0 # Temp (C) where fan starts spinning
MAX_TEMP = 70.0 # Temp (C) where fan hits 100%
MIN_FAN_SPEED = 0.25 # 25% duty cycle to overcome fan stall voltage
MAX_FAN_SPEED = 1.0 # 100% duty cycle
POLL_INTERVAL = 5 # Seconds between temp checks
# Initialize PWM device at 25kHz (standard for PC PWM fans)
try:
fan = PWMOutputDevice(FAN_PIN, frequency=25000, initial_value=0)
cpu = CPUTemperature(min_temp=MIN_TEMP, max_temp=MAX_TEMP)
except Exception as e:
print(f"[FATAL] Hardware init failed: {e}")
sys.exit(1)
def calculate_fan_speed(temp):
if temp <= MIN_TEMP:
return 0.0
elif temp >= MAX_TEMP:
return MAX_FAN_SPEED
else:
# Linear interpolation between min and max
ratio = (temp - MIN_TEMP) / (MAX_TEMP - MIN_TEMP)
speed = MIN_FAN_SPEED + (ratio * (MAX_FAN_SPEED - MIN_FAN_SPEED))
return round(speed, 2)
def graceful_exit(signum, frame):
print("\n[INFO] Caught exit signal. Spinning down fan...")
fan.value = 0
fan.close()
sys.exit(0)
# Catch Ctrl+C and systemd stop signals
signal.signal(signal.SIGINT, graceful_exit)
signal.signal(signal.SIGTERM, graceful_exit)
if __name__ == "__main__":
print(f"[INFO] Fan controller active on GPIO {FAN_PIN}.")
try:
while True:
current_temp = cpu.temperature
target_speed = calculate_fan_speed(current_temp)
fan.value = target_speed
print(f"[DEBUG] CPU: {current_temp}°C | Fan PWM: {target_speed * 100}%")
time.sleep(POLL_INTERVAL)
except Exception as e:
print(f"[ERROR] Runtime loop failed: {e}")
fan.value = 1.0 # Failsafe: run at 100% if script crashes
sys.exit(1)
Debugging: Boot Failures and GPIO Errors
When moving from older Pi models to the Pi 5, the GPIO backend changed from RPi.GPIO to lgpio. This causes specific, highly quoted errors if your environment isn't configured correctly.
Exact Error String: gpiozero.exc.BadPinFactory: Unable to load any default pin factory!
Ranked Causes:
- Missing
lgpiobackend (Most Likely): You are on Pi OS Bookworm but haven't installed the new GPIO bindings. Fix: Runsudo apt install python3-gpiozero python3-lgpio. - Running inside a restricted container: If you are trying to run this on LibreELEC or inside a Docker container without passing
--privilegedand mapping/dev/gpiochip0, the library cannot access the hardware. Fix: Move the script to the host OS, or use a dedicated GPIO Docker image. - 32-bit OS mismatch: You flashed a 32-bit legacy OS onto the Pi 5. The Pi 5 requires a 64-bit kernel for proper
lgpiomemory mapping. Fix: Reflash with Pi OS Lite 64-bit.
The First Three Things to Check When the Fan Fails to Spin
- Verify the Pin Numbering Scheme: Did you wire physical Pin 12, but define
FAN_PIN = 12in the code? The code uses BCM numbering (GPIO 18). If you wire Pin 12 but code it as 12, you are sending PWM to GPIO 12 (Physical Pin 32), and your fan will sit dead. - Check the PSU Wattage: The Pi 5 firmware will actively disable the 5V GPIO rail or limit it to 600mA if it does not detect a 5A-capable USB-C PD power supply. If you are using a standard 15W phone charger, the fan won't get enough current to overcome the stall voltage.
- Measure the PWM Signal: Hook an oscilloscope or a multimeter with a frequency/duty-cycle mode to the PWM wire. You should see a 25kHz square wave. If it reads a flat 0V or 3.3V, your software PWM initialization failed.
Extending and Simplifying the Build
You now have a functional, thermally managed media center. Here is how to adapt the project based on your final deployment environment.
How to Simplify (The "No-Code" Route)
If you decide writing Python daemons and configuring systemd services is overkill for a living room TV, abandon the custom wiring. Purchase the Official Raspberry Pi 5 Active Cooler ($5). It plugs directly into the dedicated 4-pin JST fan header on the Pi 5 PCB, and the onboard firmware manages the PWM curve automatically based on SoC temperature. No code required.
How to Extend (HDMI-CEC Integration)
To make this feel like a commercial "XBMC" box, you need to control Kodi with your existing TV remote. The Pi 5 supports HDMI-CEC (Consumer Electronics Control) natively on its micro-HDMI ports.
- Ensure your TV's CEC feature is enabled (Samsung calls it Anynet+, LG calls it SimpLink, Sony calls it BRAVIA Sync).
- In your Kodi
settings.xml, ensure CEC is not blacklisted. - Map your TV remote's directional pad to Kodi's navigation keys via the Kodi CEC adapter settings.
- Hardware Extension: If your TV's CEC implementation is buggy (common in older Vizio and TCL sets), wire a TSOP38238 IR receiver to GPIO 17 (Physical Pin 11) and configure
lircto decode standard NEC remote protocols, bypassing the TV entirely.






