Raspberry Piなしで動かす!gpiozeroのMockFactoryでLED・人感センサーをPCでテスト
「Raspberry Piで電子工作を始めたいけど、まだ本体が届いていない…」「配線してから動かないと、プログラムが悪いのか配線が悪いのか分からない…」──そんな経験、ありませんか?
実は、Raspberry Pi用の定番ライブラリgpiozeroには、本物のGPIOピンの代わりに「偽物のピン」を使える仕組み(MockFactory)が用意されています。これを使うと、普段使っているWindowsやMacのPCだけで、LED・ボタン・人感センサーのプログラムを動かして確認できます。
難易度はPython初〜中級者向け。Raspberry Piを持っていなくても最後まで手を動かせます😊 この記事では、ボタンでLEDを点ける基本から、人感センサーライトのプログラム、そしてpytestでの自動テストまで、実際に動かした結果と一緒に見ていきましょう!
gpiozeroのMockFactoryとは?

gpiozeroは、LED(17) や Button(2) のように「部品の名前」でGPIOを扱えるPythonライブラリです。Raspberry Pi OSには最初から入っています。
gpiozeroは、実際にピンを読み書きする部分を「ピンファクトリー」という部品に任せています。MockFactoryはその「偽物版」です。イメージとしては、本物の基板の代わりに、ピンの電圧(HIGH / LOW)をメモリ上で覚えておくだけの仮想基板を差し込む感じです。
- LEDを
on()すると、仮想ピンの状態が HIGH になる - ボタンやセンサーの入力は、
pin.drive_low()/pin.drive_high()で自分で電圧を変えて再現する - 実機に持っていくときは、MockFactoryを指定している行を消すだけ
公式ドキュメントでも「Piが手元にないときにスクリプトを開発するのにも便利」と紹介されている、れっきとした公式機能です(gpiozero公式:API – Pins)。
準備:PCにgpiozeroを入れる
PCではふつうにpipでインストールできます。テストで使うpytestも一緒に入れておきましょう。
pip install gpiozero pytest
この記事のコードは、Windows 11・Python 3.13・gpiozero 2.0.1・pytest 9.1.1 で動作を確認しています。
ステップ1:ボタンを押している間だけLEDを点ける
まずは電子工作の「Hello World」、ボタンとLEDです。実機なら GPIO17 にLED、GPIO2 にボタンをつなぐ想定です。
ここが重要です👇
Device.pin_factory = MockFactory()で、以降の部品はすべて仮想ピンを使うButtonは標準でプルアップ(押すとGNDにつながる配線)なので、LOW = 押されたになるled.pin.statesで、LEDのピンがどう変化してきたかの履歴を確認できる
from gpiozero import Device, LED, Button
from gpiozero.pins.mock import MockFactory
# ★ここがポイント:本物のGPIOの代わりに「偽物のピン」を使う
Device.pin_factory = MockFactory()
led = LED(17) # GPIO17 に LED をつないだつもり
button = Button(2) # GPIO2 にボタンをつないだつもり(押すとGNDにつながる)
# ボタンを押している間だけLEDを点ける
button.when_pressed = led.on
button.when_released = led.off
print("最初の状態 :", "押されている" if button.is_pressed else "離れている",
"/ LED", "ON" if led.is_lit else "OFF")
# --- ここから「指でボタンを押す」代わりの操作 ---
button.pin.drive_low() # プルアップなので LOW = 押された
print("ボタンを押した :", "押されている" if button.is_pressed else "離れている",
"/ LED", "ON" if led.is_lit else "OFF")
button.pin.drive_high() # HIGH = 離した
print("ボタンを離した :", "押されている" if button.is_pressed else "離れている",
"/ LED", "ON" if led.is_lit else "OFF")
# LEDのピン(GPIO17)の電圧の変化を確認
print("GPIO17の状態の履歴:", [s.state for s in led.pin.states])

実行すると、指でボタンを押す代わりに drive_low() を呼んだ瞬間、when_pressed に登録した led.on が動いてLEDがONになります。実際の結果はこちらです。
LEDのピンの履歴が OFF → ON → OFF(False → True → False) になっていますね。配線もRaspberry Piもなしで、イベントの登録が正しいかを確認できました🚀
ステップ2:人感センサーライトを作る(つまずきポイントあり)
次はもう少し実用的に、人が来たらライトを点け、いなくなったら消すプログラムです。人感センサー(HC-SR501など)は、人を検知すると出力がHIGHになります。gpiozeroでは MotionSensor クラスで扱えます。
後でテストしやすいように、「ライトを制御する仕組み」を関数 setup_motion_light() に分けておくのがポイントです。
import time
from datetime import datetime
from gpiozero import Device, LED, MotionSensor
def setup_motion_light(sensor, light, log):
"""人を検知したらライトON、いなくなったらOFFにして、ログに記録する"""
def on_motion():
light.on()
log.append(f"{datetime.now():%H:%M:%S} 人を検知 → ライトON")
def on_no_motion():
light.off()
log.append(f"{datetime.now():%H:%M:%S} 人がいない → ライトOFF")
sensor.when_motion = on_motion
sensor.when_no_motion = on_no_motion
if __name__ == "__main__":
from gpiozero.pins.mock import MockFactory
Device.pin_factory = MockFactory() # ← 実機で動かすときはこの2行を消すだけ
sensor = MotionSensor(4) # 人感センサー(HC-SR501など)の出力 → GPIO4
light = LED(17) # ライト代わりのLED → GPIO17
log = []
setup_motion_light(sensor, light, log)
sensor.wait_for_no_motion() # ★センサーが「人がいない」状態を一度読むまで待つ
# --- センサーの反応をシミュレーション(実機では人が通ると自動でこうなる) ---
for i in range(2):
sensor.pin.drive_high() # センサー出力がHIGH = 人がいる
time.sleep(0.5) # センサーは0.1秒ごとに値を読むので少し待つ
print(f"{i + 1}回目 人がいる → ライト", "ON" if light.is_lit else "OFF")
sensor.pin.drive_low() # LOW = 人がいなくなった
time.sleep(0.5)
print(f"{i + 1}回目 人がいない → ライト", "ON" if light.is_lit else "OFF")
print("--- ログ ---")
print("\n".join(log))

ポイントをまとめるとこんな感じです。
when_motion/when_no_motionに関数を登録すると、センサーの変化で自動的に呼ばれるMotionSensorは裏側のスレッドで0.1秒ごと(sample_rate=10)にピンを読んでいるので、drive_high()のあとに少し待つ- ★印の
sensor.wait_for_no_motion()が今回のつまずきポイント
実は最初、★の1行がない状態で動かしたところ、1回目の「人がいる」でライトがOFFのままでした。3回実行しても毎回同じです。これ、なぜだか分かりますか?
gpiozeroのソースを確認すると、センサーが最初に読んだ値は「基準の状態」として記録するだけで、イベントは呼ばない作りになっていました。センサーを作った直後にすぐ drive_high() したため、「人がいる」が基準になってしまい、when_motion が呼ばれなかったわけです。
そこで、センサーが一度「人がいない」状態を読むまで wait_for_no_motion() で待ってから動かすようにしました。実機でも、起動直後の人感センサーは出力が安定しないことがあるので、この1行は入れておいて損はありません。修正後の実行結果はこちらです。
1回目からきちんとONになり、ログにも時刻つきで記録されました😊 こういう「実機だと原因が分かりにくい挙動」をPC上で再現して調べられるのも、MockFactoryの大きなメリットです。
ステップ3:pytestで自動テストにする
毎回目で確認するのは大変なので、pytestの自動テストにしましょう。motion_light.py と同じフォルダに test_motion_light.py として保存します。
ここが重要です👇
@pytest.fixtureで、テストごとに新しい仮想ピンとセンサー・LEDを用意し、終わったらclose()で片付ける- LEDのピンの履歴を
[False, True, False]とリストで丸ごと比較すると、余計なON/OFFが起きていないかまで確認できる
import time
import pytest
from gpiozero import Device, LED, MotionSensor
from gpiozero.pins.mock import MockFactory
from motion_light import setup_motion_light
@pytest.fixture
def devices():
Device.pin_factory = MockFactory() # テストごとに新しい偽ピンを用意
sensor = MotionSensor(4)
light = LED(17)
yield sensor, light
sensor.close() # 後片付け(ピンを解放)
light.close()
Device.pin_factory.reset()
def test_人を検知したらライトがつく(devices):
sensor, light = devices
log = []
setup_motion_light(sensor, light, log)
sensor.wait_for_no_motion()
sensor.pin.drive_high()
time.sleep(0.3)
assert light.is_lit
assert "人を検知" in log[-1]
def test_人がいなくなったらライトが消える(devices):
sensor, light = devices
log = []
setup_motion_light(sensor, light, log)
sensor.wait_for_no_motion()
sensor.pin.drive_high()
time.sleep(0.3)
sensor.pin.drive_low()
time.sleep(0.3)
assert not light.is_lit
# LEDのピンが OFF → ON → OFF の順に変化したか
assert [s.state for s in light.pin.states] == [False, True, False]
python -m pytest test_motion_light.py -v で実行すると、筆者の環境では次のように 2 passed になりました。
test_motion_light.py::test_人を検知したらライトがつく PASSEDtest_motion_light.py::test_人がいなくなったらライトが消える PASSED2 passed in 1.16s
ちなみに、試しにテスト側の sensor.wait_for_no_motion() の行を2か所とも消して実行してみたところ、2つのテストがどちらもFAILになりました。ステップ2のつまずきを、テストでちゃんと検出できるわけですね。
実機のRaspberry Piで動かすときは
Raspberry Piが届いたら、motion_light.py の MockFactoryを指定している2行を消すだけで、本物のGPIOを使うようになります。setup_motion_light() の中身は一切変えなくてOKです。
- シミュレーション用の
drive_high()/drive_low()の部分は、実機ではpause()(from signal import pause)などに置き換えて待ち続ける形にします - テストファイルは実機でもPCでもそのまま動くので、残しておきましょう
まとめ
- gpiozeroのMockFactoryを使うと、Raspberry PiなしでGPIOのプログラムをPC上で動かせる
- 入力(ボタン・センサー)は
pin.drive_low()/pin.drive_high()で再現し、出力はis_litやpin.statesで確認する - センサーは最初に読んだ値ではイベントが呼ばれない。
wait_for_no_motion()で落ち着くのを待ってから動かす - 制御部分を関数に分けておけば、pytestで自動テストでき、実機では2行消すだけで動く
「配線してから悩む」を「PCで確認してから配線する」に変えるだけで、電子工作のつまずきはぐっと減ります。「むずかしそう」を「できそう」に変える第一歩として、ぜひ試してみてください🚀
こちらも読まれています
📡 Arduinoをもっと深く学ぼう!
Arduino・ラズパイ・ロボットプログラミングを体系的に学びたい方へ。おすすめのUdemyコースや電子部品もまとめています。
Arduinoロボット入門のおすすめコース・部品を見る →📚 関連商品・おすすめ書籍
※本記事にはアフィリエイトリンクが含まれます。





