Raspberry Pi GPIO Emulator

You can test much of a Raspberry Pi GPIO program on an ordinary computer—without pretending that software can reproduce the electronics. The practical 2026 route is GPIO Zero’s open-source mock pin factory. It lets the same application use simulated pins in automated tests and real pins on a Raspberry Pi.

This maintained guide replaces the old download-first recommendations in the 2019 post. The original article remains intact in an archive at the end, so its provenance is not lost.

Decide what the emulator should prove

A mock GPIO layer is useful for testing:

  • application decisions, state transitions, and error handling;
  • which virtual output turns on after a virtual input changes;
  • cleanup and repeated test runs;
  • code on a laptop, CI runner, or development container.

It cannot validate:

  • physical pin numbers or wiring;
  • voltage, current, noise, pull resistors, or switch bounce;
  • electrical timing and hardware-specific backend behavior;
  • whether an LED, relay, motor driver, or sensor is safe and correctly connected.

Treat desktop tests as the fast first gate, not as a replacement for a short test on the real device.

Install the current toolchain

GPIO Zero is normally available on Raspberry Pi OS. On a Pi, prefer the distribution package:

sudo apt update
sudo apt install python3-gpiozero

On a development computer, keep the dependencies in a virtual environment:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install gpiozero pytest

The examples below use GPIO Zero’s BCM GPIO numbers: 17 means GPIO17, not physical header pin 17. Run pinout on a Raspberry Pi before wiring.

Put GPIO access behind a small boundary

Keep the decision logic independent from the hardware where possible, and give the GPIO-owning object an explicit cleanup method. Save this as indicator.py:

from gpiozero import Button, LED


def output_for_button(button_pressed):
    return bool(button_pressed)


class Indicator:
    def __init__(self, led_pin=17, button_pin=23):
        self.led = LED(led_pin)
        self.button = Button(button_pin, pull_up=True)

    def update(self):
        if output_for_button(self.button.is_pressed):
            self.led.on()
        else:
            self.led.off()

    def close(self):
        self.button.close()
        self.led.close()

The pure output_for_button() function is trivial here on purpose. Real projects can move filtering, interlocks, state machines, and alarms into similarly testable functions.

Test it without a Raspberry Pi

Save this as test_indicator.py:

from gpiozero import Device
from gpiozero.pins.mock import MockFactory

from indicator import Indicator, output_for_button


def test_output_decision():
    assert output_for_button(False) is False
    assert output_for_button(True) is True


def test_button_drives_led():
    factory = MockFactory()
    Device.pin_factory = factory
    indicator = Indicator()

    try:
        indicator.button.pin.drive_high()  # pull-up button released
        indicator.update()
        assert indicator.led.value == 0

        indicator.button.pin.drive_low()  # button pressed to ground
        indicator.update()
        assert indicator.led.value == 1
    finally:
        indicator.close()
        factory.close()

Run the tests:

pytest -q

MockPin.drive_high() and drive_low() simulate the electrical level seen by an input. With a pull-up button, high means released and low means pressed.

Select mock pins from the environment

For a quick smoke test of a script that does not inject its own factory, select the mock backend before Python imports the devices:

GPIOZERO_PIN_FACTORY=mock python your_program.py

Do this explicitly in development or CI. Do not silently fall back to mock pins in production: a program that appears to run while controlling no hardware can be more dangerous than one that stops with a clear error.

Test PWM and connected virtual devices

Basic MockPin objects do not support PWM. Choose the PWM-capable mock class when a test creates PWMLED, Servo, or another PWM device:

GPIOZERO_PIN_FACTORY=mock \
GPIOZERO_MOCK_PIN_CLASS=mockpwmpin \
pytest -q

GPIO Zero can also connect mock pins so one virtual output drives another virtual input. For code that uses background source threads, allow a little more than the configured source_delay before asserting the result. These features test software interactions; they still do not model the load, frequency accuracy, or waveform seen on a real board.

What to do with old RPi.GPIO programs

The original article used a handwritten testRPiGPIO.py module and named two third-party simulator downloads. A tiny local stub can still prove that a few calls were made, but it easily drifts away from the real API and usually has no useful input, event, PWM, or cleanup semantics.

For maintained code, prefer one of these boundaries:

  1. migrate device-level code to GPIO Zero and use MockFactory;
  2. isolate legacy RPi.GPIO calls in one adapter, then fake that adapter in unit tests;
  3. retain the old program unchanged, but test only its pure decision logic on the desktop and run the adapter on a Pi.

Do not treat a matching function name as proof of hardware compatibility. The archived SourceForge and GPIOSimulator instructions below are historical references, not current installation recommendations.

Add a real-hardware gate

Before connecting anything, check the board’s official pinout and the component datasheet. Raspberry Pi GPIO inputs and outputs use 3.3 V logic. Do not put 5 V into a GPIO pin. Use a series resistor with an LED, and never drive a motor directly from a GPIO pin; use a suitable driver or H-bridge and an appropriate power supply.

On the Pi, repeat a small, observable test:

  • confirm BCM versus physical numbering;
  • power off before changing wiring;
  • start with one input or output;
  • verify the inactive state and pull direction;
  • exercise every safety interlock and cleanup path;
  • only then connect the full load.

Troubleshooting

Symptom Likely cause Check
BadPinFactory or no default pin factory The program is running away from a Pi without a selected backend Set GPIOZERO_PIN_FACTORY=mock for tests or run on the Pi with a supported real backend
PWMLED fails under mock pins The default mock pin lacks PWM support Set GPIOZERO_MOCK_PIN_CLASS=mockpwmpin
A pull-up button reads backwards Pressing connects the pin to ground Drive high for released and low for pressed; test both states
Tests pass but the circuit fails Mocks do not verify wiring or electricity Check pinout, common ground, resistor values, voltage, and the real device datasheet
Tests affect one another Global devices or the pin factory were not closed Close devices and the factory in a finally block or a pytest fixture

Original 2019 article archive

The following is the original English body published in 2019. It is preserved verbatim except for trailing whitespace normalization. Its downloads and installation commands may now be obsolete; use the maintained guide above for current work.

Method 1

Create a file named testRPiGPIO.py and import this file in your projects

import testRPiGPIO as GPIO

#!/usr/bin/python
BOARD = “board”
BCM = “bcm”
OUT = “out”
IN = “in”
HIGH = 1
LOW = 0

def setwarnings(mode):
print(mode)

def output(pin,value):
print(pin, “:”, value)

def setmode(mode):
print(mode)

def setup(pin,value):
print(pin, “:”, value)

def cleanup():
print(“clean-up”)

#End


Method 2

Download [this file](https://sourceforge.net/projects/pi-gpio-emulator/) and extract it in the folder of your project.

from EmulatorGUI import GPIO


If you want to install it using pip,

pip install GPIOSimulator


Import it to your project using

from RPiSim.GPIO import GPIO


Supported methods

- GPIO.setmode()
- GPIO.setwarnings()
- GPIO.setup()
- GPIO.input()
- GPIO.output()

An example

from EmulatorGUI import GPIO
#import RPi.GPIO as GPIO
import time
import traceback

def Main():
try:
GPIO.setmode(GPIO.BCM)

GPIO.setwarnings(False)

GPIO.setup(4, GPIO.OUT)
GPIO.setup(17, GPIO.OUT, initial = GPIO.LOW)
GPIO.setup(18, GPIO.OUT, initial = GPIO.LOW)
GPIO.setup(21, GPIO.OUT, initial = GPIO.LOW)
GPIO.setup(23, GPIO.IN, pull_up_down = GPIO.PUD_UP)
GPIO.setup(15, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
GPIO.setup(24, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
GPIO.setup(26, GPIO.IN)

while(True):
if (GPIO.input(23) == False):
GPIO.output(4,GPIO.HIGH)
GPIO.output(17,GPIO.HIGH)
time.sleep(1)

if (GPIO.input(15) == True):
GPIO.output(18,GPIO.HIGH)
GPIO.output(21,GPIO.HIGH)
time.sleep(1)

if (GPIO.input(24) == True):
GPIO.output(18,GPIO.LOW)
GPIO.output(21,GPIO.LOW)
time.sleep(1)

if (GPIO.input(26) == True):
GPIO.output(4,GPIO.LOW)
GPIO.output(17,GPIO.LOW)
time.sleep(1)

except Exception as ex:
traceback.print_exc()
finally:
GPIO.cleanup() #this ensures a clean exit

Main()

Official references

Leave a Reply