libgpiod-dev / gpiod 10 Example Python Programs / 10 Signal Handling Examples

10 Example GPIO bit Driving Examples!

libgpiod-dev / gpiod 10 Example Python Programs / 10 Signal Handling Examples

We initially started with 10 example c programs here.  Since it now only takes a couple minutes to translate it now into python we have had it rewrite it's code in python for your reference and development!

  • These applications have not been human verfied. At least we are honest, so you might need to work with them, but they are provided for your benefit and reference.

Here are the 10 examples converted from C (libgpiod v2 API) to Python using the official gpiod Python bindings (available since libgpiod 2.0+).

These examples use the modern Python API, which is cleaner and more Pythonic than the C version.

Installation (one-time)

sudo apt update
sudo apt install python3-libgpiod

All examples assume you are using BCM GPIO numbers (offsets) and gpiochip0.

Example 1: Basic LED Blinker

#!/usr/bin/env python3
"""
Basic LED blinker using libgpiod v2 Python bindings.
Hardware: LED + resistor on BCM 17 (physical pin 11)
"""

import gpiod
import time

CHIP = "gpiochip0"
LED_OFFSET = 17
DELAY = 0.5  # seconds

print(f"Blinking LED on BCM {LED_OFFSET} (Ctrl+C to exit)")

with gpiod.Chip(CHIP) as chip:
    request = chip.request_lines(
        consumer="blink-example",
        config={
            LED_OFFSET: gpiod.LineSettings(
                direction=gpiod.Direction.OUTPUT,
                output_value=False
            )
        }
    )

    try:
        while True:
            request.set_value(LED_OFFSET, True)
            time.sleep(DELAY)
            request.set_value(LED_OFFSET, False)
            time.sleep(DELAY)
    except KeyboardInterrupt:
        print("\nExiting cleanly")
    finally:
        request.release()

Example 2: Precise LED Blinker (using time.sleep)

#!/usr/bin/env python3
"""
Precise LED blinker with 250 ms period.
"""

import gpiod
import time

CHIP = "gpiochip0"
LED_OFFSET = 17
HALF_PERIOD = 0.250

with gpiod.Chip(CHIP) as chip:
    request = chip.request_lines(
        consumer="precise-blink",
        config={
            LED_OFFSET: gpiod.LineSettings(
                direction=gpiod.Direction.OUTPUT,
                output_value=False
            )
        }
    )

    print(f"Precise 250 ms blink on BCM {LED_OFFSET}")
    try:
        while True:
            request.set_value(LED_OFFSET, True)
            time.sleep(HALF_PERIOD)
            request.set_value(LED_OFFSET, False)
            time.sleep(HALF_PERIOD)
    except KeyboardInterrupt:
        pass
    finally:
        request.release()

Example 3: Button Polling (10 ms loop)

#!/usr/bin/env python3
"""
Button polling example with 10 ms interval.
Hardware: Button to GND on BCM 18 (pull-up enabled)
"""

import gpiod
import time

CHIP = "gpiochip0"
BTN_OFFSET = 18
POLL_INTERVAL = 0.010

with gpiod.Chip(CHIP) as chip:
    request = chip.request_lines(
        consumer="button-poll",
        config={
            BTN_OFFSET: gpiod.LineSettings(
                direction=gpiod.Direction.INPUT,
                bias=gpiod.Bias.PULL_UP
            )
        }
    )

    last = True
    print(f"Polling button on BCM {BTN_OFFSET} every 10 ms")

    try:
        while True:
            val = request.get_value(BTN_OFFSET)
            if val != last:
                print("Button", "released" if val else "pressed")
                last = val
            time.sleep(POLL_INTERVAL)
    except KeyboardInterrupt:
        pass
    finally:
        request.release()

Example 4: Blocking Edge Detection (Rising Edge)

#!/usr/bin/env python3
"""
Blocking rising-edge detection using event wait.
"""

import gpiod

CHIP = "gpiochip0"
BTN_OFFSET = 18

with gpiod.Chip(CHIP) as chip:
    request = chip.request_lines(
        consumer="edge-detect",
        config={
            BTN_OFFSET: gpiod.LineSettings(
                direction=gpiod.Direction.INPUT,
                bias=gpiod.Bias.PULL_UP,
                edge_detection=gpiod.Edge.RISING
            )
        }
    )

    print(f"Waiting for rising edges on BCM {BTN_OFFSET}...")
    try:
        while True:
            for event in request.wait_edge_events(-1):  # -1 = block forever
                ts_sec = event.timestamp_ns // 1_000_000_000
                ts_nsec = event.timestamp_ns % 1_000_000_000
                print(f"Rising edge! Timestamp: {ts_sec}.{ts_nsec:09d}")
    except KeyboardInterrupt:
        pass
    finally:
        request.release()

Example 5: Debounced Button Counter

#!/usr/bin/env python3
"""
Debounced button counter (50 ms debounce)
"""

import gpiod
import time
from datetime import datetime, timedelta

CHIP = "gpiochip0"
BTN_OFFSET = 18
DEBOUNCE_TIME = timedelta(milliseconds=50)

with gpiod.Chip(CHIP) as chip:
    request = chip.request_lines(
        consumer="debounce",
        config={
            BTN_OFFSET: gpiod.LineSettings(
                direction=gpiod.Direction.INPUT,
                bias=gpiod.Bias.PULL_UP
            )
        }
    )

    count = 0
    last_state = True
    last_change = datetime.min

    print("Debounced button counter started")
    try:
        while True:
            now = datetime.now()
            state = request.get_value(BTN_OFFSET)

            if state != last_state:
                last_change = now
                last_state = state

            if not state and (now - last_change) > DEBOUNCE_TIME:
                count += 1
                print(f"Button pressed! Count: {count}")
                last_state = True  # wait for release

            time.sleep(0.01)
    except KeyboardInterrupt:
        pass
    finally:
        request.release()

Example 6: Software PWM Dimmer (50 Hz)

#!/usr/bin/env python3
"""
Software PWM dimmer – fades from 0% to 100%
"""

import gpiod
import time

CHIP = "gpiochip0"
LED_OFFSET = 17
PERIOD = 0.020  # 20 ms → 50 Hz

def pwm_cycle(request, duty_percent):
    on_time = PERIOD * (duty_percent / 100.0)
    off_time = PERIOD - on_time
    request.set_value(LED_OFFSET, True)
    time.sleep(on_time)
    request.set_value(LED_OFFSET, False)
    time.sleep(off_time)

with gpiod.Chip(CHIP) as chip:
    request = chip.request_lines(
        consumer="pwm",
        config={
            LED_OFFSET: gpiod.LineSettings(
                direction=gpiod.Direction.OUTPUT,
                output_value=False
            )
        }
    )

    try:
        for duty in range(0, 101, 5):
            print(f"Duty cycle: {duty}%")
            for _ in range(50):  # ~1 second at each level
                pwm_cycle(request, duty)
    except KeyboardInterrupt:
        pass
    finally:
        request.release()

Example 7: LED Chaser (Multi-line)

#!/usr/bin/env python3
"""
LED chaser using three LEDs
Hardware: LEDs on BCM 17, 18, 27
"""

import gpiod
import time

CHIP = "gpiochip0"
LEDS = [17, 18, 27]
DELAY = 0.2

with gpiod.Chip(CHIP) as chip:
    request = chip.request_lines(
        consumer="chaser",
        config={
            offset: gpiod.LineSettings(
                direction=gpiod.Direction.OUTPUT,
                output_value=False
            )
            for offset in LEDS
        }
    )

    try:
        while True:
            for i, led in enumerate(LEDS):
                request.set_value(led, True)
                time.sleep(DELAY)
                request.set_value(led, False)
    except KeyboardInterrupt:
        pass
    finally:
        request.release()

Example 8: One-Shot Precise Pulse (100 µs)

#!/usr/bin/env python3
"""
Generates a single 100 µs pulse
"""

import gpiod
import time

CHIP = "gpiochip0"
OUT_OFFSET = 17

with gpiod.Chip(CHIP) as chip:
    request = chip.request_lines(
        consumer="pulse",
        config={
            OUT_OFFSET: gpiod.LineSettings(
                direction=gpiod.Direction.OUTPUT,
                output_value=False
            )
        }
    )

    print(f"Sending 100 µs pulse on BCM {OUT_OFFSET}")
    request.set_value(OUT_OFFSET, True)
    time.sleep(100e-6)  # 100 microseconds
    request.set_value(OUT_OFFSET, False)
    print("Pulse complete")

Example 9: Input Mirror with Latency Measurement

#!/usr/bin/env python3
"""
Mirrors button state to LED and measures latency
Hardware: Button BCM 18 → LED BCM 17
"""

import gpiod
import time
from datetime import datetime

CHIP = "gpiochip0"
LED_OFFSET = 17
BTN_OFFSET = 18

with gpiod.Chip(CHIP) as chip:
    request = chip.request_lines(
        consumer="mirror",
        config={
            LED_OFFSET: gpiod.LineSettings(
                direction=gpiod.Direction.OUTPUT,
                output_value=False
            ),
            BTN_OFFSET: gpiod.LineSettings(
                direction=gpiod.Direction.INPUT,
                bias=gpiod.Bias.PULL_UP
            )
        }
    )

    try:
        while True:
            t_start = datetime.now()
            val = request.get_value(BTN_OFFSET)
            request.set_value(LED_OFFSET, val)
            t_end = datetime.now()
            latency_ns = (t_end - t_start).total_seconds() * 1_000_000_000
            if latency_ns > 50000:
                print(f"High latency: {latency_ns:.0f} ns")
            time.sleep(0.001)
    except KeyboardInterrupt:
        pass
    finally:
        request.release()

Example 10: Traffic Light State Machine

#!/usr/bin/env python3
"""
Traffic light simulation state machine
Hardware: Red=17, Yellow=18, Green=27
"""

import gpiod
import time

CHIP = "gpiochip0"
RED, YELLOW, GREEN = 17, 18, 27
LIGHTS = [RED, YELLOW, GREEN]

STATES = {
    "RED":    (5.0, "GREEN"),
    "GREEN":  (4.0, "YELLOW"),
    "YELLOW": (1.0, "RED")
}

current_state = "RED"

with gpiod.Chip(CHIP) as chip:
    request = chip.request_lines(
        consumer="traffic",
        config={
            offset: gpiod.LineSettings(
                direction=gpiod.Direction.OUTPUT,
                output_value=False
            )
            for offset in LIGHTS
        }
    )

    try:
        while True:
            # Set lights according to current state
            request.set_value(RED,    current_state == "RED")
            request.set_value(YELLOW, current_state == "YELLOW")
            request.set_value(GREEN,  current_state == "GREEN")

            delay, next_state = STATES[current_state]
            time.sleep(delay)
            current_state = next_state
    except KeyboardInterrupt:
        pass
    finally:
        request.release()

These Python versions are more concise, use context managers (with), and handle cleanup automatically on exit or exception.

Run each script with:

python3 exampleN.py

or make them executable:

chmod +x exampleN.py
./exampleN.py

Here is an updated version of the 10 Python examples with proper signal handling added. This ensures graceful cleanup (releasing GPIO lines) when the program receives SIGINT (Ctrl+C) or SIGTERM (e.g., from kill or systemd shutdown).

SIGNAL HANDLING (10 MORE EXAMPLES)

The approach used is consistent across all examples:

  • Use signal.signal() to catch SIGINT and SIGTERM.
  • Set a global flag (running = False) when a termination signal is received.
  • Check this flag in the main loop to exit cleanly.
  • Always use request.release() in a finally block or on exit.

Example 1: Basic LED Blinker with signal handling

#!/usr/bin/env python3
"""
Basic LED blinker with graceful shutdown on Ctrl+C or SIGTERM
"""

import gpiod
import time
import signal
import sys

CHIP = "gpiochip0"
LED_OFFSET = 17
DELAY = 0.5

running = True

def signal_handler(sig, frame):
    global running
    print("\nReceived signal, releasing GPIO lines...")
    running = False

signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

print(f"Blinking LED on BCM {LED_OFFSET} (Ctrl+C to exit cleanly)")

chip = gpiod.Chip(CHIP)
request = None

try:
    request = chip.request_lines(
        consumer="blink-example",
        config={
            LED_OFFSET: gpiod.LineSettings(
                direction=gpiod.Direction.OUTPUT,
                output_value=False
            )
        }
    )

    while running:
        request.set_value(LED_OFFSET, True)
        time.sleep(DELAY)
        request.set_value(LED_OFFSET, False)
        time.sleep(DELAY)

except Exception as e:
    print(f"Error: {e}", file=sys.stderr)
finally:
    if request is not None:
        request.release()
    chip.close()
    print("GPIO lines released. Exiting.")

Example 2: Precise LED Blinker

#!/usr/bin/env python3
import gpiod
import time
import signal

CHIP = "gpiochip0"
LED_OFFSET = 17
HALF_PERIOD = 0.250

running = True

def signal_handler(sig, frame):
    global running
    running = False

signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

chip = gpiod.Chip(CHIP)
request = None

try:
    request = chip.request_lines(
        consumer="precise-blink",
        config={
            LED_OFFSET: gpiod.LineSettings(
                direction=gpiod.Direction.OUTPUT,
                output_value=False
            )
        }
    )

    print(f"Precise 250 ms blink on BCM {LED_OFFSET}")
    while running:
        request.set_value(LED_OFFSET, True)
        time.sleep(HALF_PERIOD)
        request.set_value(LED_OFFSET, False)
        time.sleep(HALF_PERIOD)

finally:
    if request:
        request.release()
    chip.close()
    print("Clean exit.")

Example 3: Button Polling

#!/usr/bin/env python3
import gpiod
import time
import signal

CHIP = "gpiochip0"
BTN_OFFSET = 18
POLL_INTERVAL = 0.010

running = True

def signal_handler(sig, frame):
    global running
    running = False

signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

chip = gpiod.Chip(CHIP)
request = chip.request_lines(
    consumer="button-poll",
    config={
        BTN_OFFSET: gpiod.LineSettings(
            direction=gpiod.Direction.INPUT,
            bias=gpiod.Bias.PULL_UP
        )
    }
)

last = True
print(f"Polling button on BCM {BTN_OFFSET} every 10 ms")

try:
    while running:
        val = request.get_value(BTN_OFFSET)
        if val != last:
            print("Button", "released" if val else "pressed")
            last = val
        time.sleep(POLL_INTERVAL)
finally:
    request.release()
    chip.close()
    print("GPIO released.")

Example 4: Blocking Edge Detection

#!/usr/bin/env python3
import gpiod
import signal

CHIP = "gpiochip0"
BTN_OFFSET = 18

running = True

def signal_handler(sig, frame):
    global running
    running = False

signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

chip = gpiod.Chip(CHIP)
request = chip.request_lines(
    consumer="edge-detect",
    config={
        BTN_OFFSET: gpiod.LineSettings(
            direction=gpiod.Direction.INPUT,
            bias=gpiod.Bias.PULL_UP,
            edge_detection=gpiod.Edge.RISING
        )
    }
)

print(f"Waiting for rising edges on BCM {BTN_OFFSET}...")

try:
    while running:
        for event in request.wait_edge_events(timeout=0.5):
            if not running:
                break
            ts_sec = event.timestamp_ns // 1_000_000_000
            ts_nsec = event.timestamp_ns % 1_000_000_000
            print(f"Rising edge! {ts_sec}.{ts_nsec:09d}")
finally:
    request.release()
    chip.close()
    print("Clean shutdown.")

Example 5: Debounced Button Counter

#!/usr/bin/env python3
import gpiod
import time
from datetime import datetime, timedelta
import signal

CHIP = "gpiochip0"
BTN_OFFSET = 18
DEBOUNCE_TIME = timedelta(milliseconds=50)

running = True

def signal_handler(sig, frame):
    global running
    running = False

signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

chip = gpiod.Chip(CHIP)
request = chip.request_lines(
    consumer="debounce",
    config={
        BTN_OFFSET: gpiod.LineSettings(
            direction=gpiod.Direction.INPUT,
            bias=gpiod.Bias.PULL_UP
        )
    }
)

count = 0
last_state = True
last_change = datetime.min

try:
    while running:
        now = datetime.now()
        state = request.get_value(BTN_OFFSET)

        if state != last_state:
            last_change = now
            last_state = state

        if not state and (now - last_change) > DEBOUNCE_TIME:
            count += 1
            print(f"Button pressed! Count: {count}")
            last_state = True

        time.sleep(0.01)
finally:
    request.release()
    chip.close()
    print(f"Final count: {count}")

Example 6: Software PWM Dimmer

#!/usr/bin/env python3
import gpiod
import time
import signal

CHIP = "gpiochip0"
LED_OFFSET = 17
PERIOD = 0.020

running = True

def signal_handler(sig, frame):
    global running
    running = False

signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

def pwm_cycle(request, duty):
    on_time = PERIOD * (duty / 100.0)
    off_time = PERIOD - on_time
    request.set_value(LED_OFFSET, True)
    time.sleep(on_time)
    request.set_value(LED_OFFSET, False)
    time.sleep(off_time)

chip = gpiod.Chip(CHIP)
request = chip.request_lines(
    consumer="pwm",
    config={
        LED_OFFSET: gpiod.LineSettings(
            direction=gpiod.Direction.OUTPUT,
            output_value=False
        )
    }
)

try:
    for duty in range(0, 101, 5):
        if not running:
            break
        print(f"Duty cycle: {duty}%")
        for _ in range(50):
            if not running:
                break
            pwm_cycle(request, duty)
finally:
    request.release()
    chip.close()
    print("PWM stopped cleanly.")

Example 7: LED Chaser

#!/usr/bin/env python3
import gpiod
import time
import signal

CHIP = "gpiochip0"
LEDS = [17, 18, 27]
DELAY = 0.2

running = True

def signal_handler(sig, frame):
    global running
    running = False

signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

chip = gpiod.Chip(CHIP)
request = chip.request_lines(
    consumer="chaser",
    config={
        offset: gpiod.LineSettings(
            direction=gpiod.Direction.OUTPUT,
            output_value=False
        )
        for offset in LEDS
    }
)

try:
    while running:
        for led in LEDS:
            if not running:
                break
            request.set_value(led, True)
            time.sleep(DELAY)
            request.set_value(led, False)
finally:
    request.release()
    chip.close()
    print("Chaser stopped.")

Example 8: One-Shot Precise Pulse

#!/usr/bin/env python3
import gpiod
import time
import signal

CHIP = "gpiochip0"
OUT_OFFSET = 17

running = True

def signal_handler(sig, frame):
    global running
    running = False

signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

chip = gpiod.Chip(CHIP)
request = chip.request_lines(
    consumer="pulse",
    config={
        OUT_OFFSET: gpiod.LineSettings(
            direction=gpiod.Direction.OUTPUT,
            output_value=False
        )
    }
)

print(f"Sending 100 µs pulse on BCM {OUT_OFFSET}")
request.set_value(OUT_OFFSET, True)
time.sleep(100e-6)
request.set_value(OUT_OFFSET, False)
print("Pulse sent.")

# Keep running briefly in case of early interrupt
time.sleep(0.5)

request.release()
chip.close()

Example 9: Input Mirror with Latency Measurement

#!/usr/bin/env python3
import gpiod
import time
from datetime import datetime
import signal

CHIP = "gpiochip0"
LED_OFFSET = 17
BTN_OFFSET = 18

running = True

def signal_handler(sig, frame):
    global running
    running = False

signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

chip = gpiod.Chip(CHIP)
request = chip.request_lines(
    consumer="mirror",
    config={
        LED_OFFSET: gpiod.LineSettings(
            direction=gpiod.Direction.OUTPUT,
            output_value=False
        ),
        BTN_OFFSET: gpiod.LineSettings(
            direction=gpiod.Direction.INPUT,
            bias=gpiod.Bias.PULL_UP
        )
    }
)

try:
    while running:
        t_start = datetime.now()
        val = request.get_value(BTN_OFFSET)
        request.set_value(LED_OFFSET, val)
        t_end = datetime.now()
        latency_ns = (t_end - t_start).total_seconds() * 1_000_000_000
        if latency_ns > 50000:
            print(f"High latency: {latency_ns:.0f} ns")
        time.sleep(0.001)
finally:
    request.release()
    chip.close()
    print("Mirror stopped.")

Example 10: Traffic Light State Machine

#!/usr/bin/env python3
import gpiod
import time
import signal

CHIP = "gpiochip0"
RED, YELLOW, GREEN = 17, 18, 27
LIGHTS = [RED, YELLOW, GREEN]

STATES = {
    "RED":    (5.0, "GREEN"),
    "GREEN":  (4.0, "YELLOW"),
    "YELLOW": (1.0, "RED")
}

running = True
current_state = "RED"

def signal_handler(sig, frame):
    global running
    running = False

signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

chip = gpiod.Chip(CHIP)
request = chip.request_lines(
    consumer="traffic",
    config={
        offset: gpiod.LineSettings(
            direction=gpiod.Direction.OUTPUT,
            output_value=False
        )
        for offset in LIGHTS
    }
)

try:
    while running:
        request.set_value(RED,    current_state == "RED")
        request.set_value(YELLOW, current_state == "YELLOW")
        request.set_value(GREEN,  current_state == "GREEN")

        delay, next_state = STATES[current_state]
        # Sleep in small steps to allow signal response
        for _ in range(int(delay * 10)):
            if not running:
                break
            time.sleep(0.1)
        if not running:
            break
        current_state = next_state
finally:
    # Turn all lights off on exit
    request.set_value(RED, False)
    request.set_value(YELLOW, False)
    request.set_value(GREEN, False)
    request.release()
    chip.close()
    print("Traffic light stopped cleanly.")

Summary of Improvements

  • Graceful exit on Ctrl+C (SIGINT) and SIGTERM
  • GPIO lines are always released
  • All lights/outputs are typically reset to safe state (off) when possible
  • Minimal added overhead (flag check in loops)
  • Compatible with systemd services or background execution

These patterns are suitable for both development scripts and production daemons. Let me know if you would like a version that uses asyncio or threading for non-blocking signal handling instead.

Linux Rocks Every Day