les vencimos

OFFLINE

Baja tensión. Nada de 230 V. GPIO a 3,3 V: meter 5 V en un pin lo rompe.

Proyectos Raspberry Pi

Cien montajes para abrir con doble clic, sin red. Veinte por nivel: Básico, Birdie, Falcon, Eagle y Fénix. El temario va en este mismo archivo.

Orientativo 2024–2026, no es una tienda. La Pi no entra en la cuenta. irrisorio <1 € · poco ≈1–3 € · medio ≈3–6 € · bastante ≈6–9 € · consultar >10 €

Ningún proyecto coincide con esa búsqueda.

Básico
Birdie
Falcon
Eagle
Fénix

01 LED que parpadea

Básico

Qué hace

El LED del GPIO17 se enciende un segundo y se apaga otro, sin que pulses nada. Sirve para comprobar la Pi, el cable y la resistencia antes de cualquier otro montaje. No hay sensor: solo un temporizador y esa salida.

Materiales

Cableado

Ánodo del LED (pata larga) a la resistencia de 330 Ω y de ahí al GPIO17, físico 11. Cátodo a GND, físico 6.

Nada va a 5 V (físicos 2 y 4) ni hace falta el 3,3 V (físico 1): el GPIO ya entrega 3,3 V a través de la resistencia. Cada GPIO es de 3,3 V. Meter 5 V en el físico 11 lo rompe.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Código con gpiozero. El parpadeo lo hace el bucle, no una librería de temporizadores externa.

from gpiozero import LED
from time import sleep

led = LED(17)
try:
    while True:
        led.on()
        sleep(1)
        led.off()
        sleep(1)
finally:
    led.close()

Hardware: LED Software: gpiozero Software: libgpiod Enseñanza: LED y resistencia

Pasos

  1. Apaga la Pi antes de cablear.
  2. Coloca la resistencia de 330 Ω en serie con el LED. Sin ella no hay prueba.
  3. Une el conjunto al físico 11 y el cátodo al físico 6.
  4. Enciende, guarda el archivo como parpadeo.py y ejecútalo con python3 parpadeo.py.
  5. Comprueba un segundo encendido y un segundo apagado.
  6. Para salir, Ctrl+C. El bloque finally apaga el LED.

Circuito

Pi GPIO17330 ΩLEDGND

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

02 Semáforo de tres luces

Básico

Qué hace

Tres LED hacen el ciclo de un semáforo de coches: verde, ámbar y rojo, con tiempos distintos. No hay pulsador ni peatón; eso es la ficha 17. Aquí solo compruebas tres salidas y una secuencia.

Materiales

Cableado

Rojo: GPIO17, físico 11, con 330 Ω. Ámbar: GPIO27, físico 13, con 330 Ω. Verde: GPIO22, físico 15, con 330 Ω. Los tres cátodos a GND, físico 6.

Nada a 5 V. El 3,3 V del físico 1 no alimenta estos LED: cada uno sale de su GPIO. Meter 5 V en cualquiera de esos pines lo rompe.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

gpiozero enciende un LED y apaga los otros. Los segundos imitan un ciclo corto de maqueta, no un cruce real.

from gpiozero import LED
from time import sleep

rojo = LED(17)
ambar = LED(27)
verde = LED(22)

def solo(luz, segundos):
    rojo.off(); ambar.off(); verde.off()
    luz.on()
    sleep(segundos)

try:
    while True:
        solo(verde, 4)
        solo(ambar, 1)
        solo(rojo, 4)
finally:
    rojo.close(); ambar.close(); verde.close()

Hardware: LED Software: gpiozero Software: libgpiod Enseñanza: encender esa salida desde Python

Pasos

  1. Cablea cada LED con su resistencia de 330 Ω antes de dar corriente.
  2. Identifica los físicos 11, 13 y 15. No uses el físico 12: ese es el GPIO18.
  3. Ejecuta el script y mira que nunca haya dos luces a la vez.
  4. Cuenta los tiempos: 4 s, 1 s y 4 s.
  5. Si una luz no cambia, cambia solo ese cable, no los tres.
  6. Ctrl+C para cerrar las tres salidas.

Circuito

GPIO17 rojoGPIO27 ámbarGPIO22 verdeGND

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

03 Botón enciende un LED

Básico

Qué hace

Mientras mantienes el pulsador, el LED luce. Al soltarlo, se apaga. El botón va a GND y la Pi pone la pull-up interna: no hace falta resistencia externa de pull-up.

Materiales

Cableado

LED: GPIO17, físico 11, serie 330 Ω, cátodo a GND físico 6.

Pulsador entre GPIO23, físico 16, y GND físico 9. La otra pareja de patas del pulsador no se usa. Nada a 5 V ni al 3,3 V: la pull-up es interna.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

gpiozero.Button con pull_up=True lee nivel bajo cuando pulsas. El LED sigue ese nivel.

from gpiozero import LED, Button
from signal import pause

led = LED(17)
boton = Button(23, pull_up=True)
boton.when_pressed = led.on
boton.when_released = led.off
pause()

Hardware: Botón Software: gpiozero Software: libgpiod Enseñanza: pulsador y rebote

Pasos

  1. Monta primero el LED de la ficha 01 y comprueba que luce con un script corto.
  2. Añade el pulsador del físico 16 a un GND.
  3. Ejecuta y mantén pulsado: el LED debe seguir tu dedo.
  4. Suelta: tiene que apagarse. Si queda encendido, el botón está cruzado en la protoboard.
  5. No añadas aún bounce_time: el rebote se trabaja en la ficha 04.
  6. Para salir, Ctrl+C.

Circuito

GPIO23pulsadorGNDGPIO17330 ΩLEDGND

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

04 Antirrebote de pulsador

Básico

Qué hace

Cada pulsación cuenta como una, aunque el contacto rebote unos milisegundos. El LED cambia de estado al pulsar y un número en la terminal sube de uno en uno. Sin antirrebote, una sola pulsación puede sumar tres o cuatro.

Materiales

Cableado

Igual que la ficha 03: LED en GPIO17 (físico 11) con 330 Ω a GND físico 6. Pulsador en GPIO23 (físico 16) a GND físico 9.

Sigue sin haber 5 V en el circuito. No pongas un condensador todavía: el antirrebote de esta ficha es el de gpiozero, bounce_time=0,05 s.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Button filtra rebotes durante 50 ms. when_pressed solo dispara el flanco útil. El enlace de software es el del rebote, no un retardo inventado en el bucle.

from gpiozero import LED, Button

led = LED(17)
boton = Button(23, pull_up=True, bounce_time=0.05)
cuenta = 0

def al_pulsar():
    global cuenta
    cuenta += 1
    led.toggle()
    print("pulsaciones validas:", cuenta)

boton.when_pressed = al_pulsar
from signal import pause
pause()

Hardware: Botón Software: Antirrebote Software: libgpiod Enseñanza: pulsador y rebote

Pasos

  1. Monta el mismo cableado del botón a GND.
  2. Ejecuta y pulsa diez veces, despacio. La cuenta debe ir 1, 2, 3…
  3. Pulsa muy rápido a propósito: algunas pulsaciones cercanas se funden en una. Eso es el filtro, no un fallo.
  4. Quita bounce_time en una copia del archivo y compara: verás saltos.
  5. Vuelve a dejar 0,05.
  6. Si la cuenta sube al soltar y también al pulsar, estás usando when_released además de when_pressed.

Circuito

GPIO23pulsadorfiltro 50 msLED

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

05 LED RGB, un color cada vez

Básico

Qué hace

Un LED RGB de cátodo común muestra rojo, luego verde, luego azul, nunca mezclados. Cada canal lleva su resistencia. Si más adelante usas el relé de GPIO12, este RGB puede convivir en pines, pero no enciendas a la vez un montaje que robe estos GPIO.

Materiales

Cableado

Rojo del RGB a GPIO16, físico 36, con 330 Ω. Verde a GPIO20, físico 38, con 330 Ω. Azul a GPIO21, físico 40, con 330 Ω. Cátodo común a GND, físico 39.

Nada a 5 V. Un ánodo común no vale para este código: gpiozero aquí pone el GPIO a nivel alto para encender. Meter 5 V en el físico 36, 38 o 40 lo rompe.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

RGBLED(16, 20, 21) con color (1,0,0), (0,1,0) y (0,0,1). Un 1 es encendido pleno, no un ciclo PWM mezclado.

from gpiozero import RGBLED
from time import sleep

rgb = RGBLED(16, 20, 21)
try:
    while True:
        rgb.color = (1, 0, 0)
        sleep(1)
        rgb.color = (0, 1, 0)
        sleep(1)
        rgb.color = (0, 0, 1)
        sleep(1)
finally:
    rgb.close()

Hardware: LED Software: gpiozero Software: libgpiod Enseñanza: LED y resistencia

Pasos

  1. Confirma que el encapsulado es cátodo común. La pata larga suele ser el cátodo: míralo en la hoja, no lo des por hecho.
  2. Pon una resistencia de 330 Ω en cada color. Una sola no sirve.
  3. Cátodo al físico 39. Canales a 36, 38 y 40 en ese orden: rojo, verde, azul.
  4. Ejecuta y anota si el primer segundo es rojo. Si es otro color, ese cable está cruzado.
  5. No pongas color (1,1,0) en esta ficha: el título es un color cada vez.
  6. Ctrl+C y comprueba que los tres canales quedan apagados.

Circuito

GPIO16330rojoGPIO20330verdeGPIO21330azulGND

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

06 Zumbador activo

Básico

Qué hace

Un zumbador activo suena mientras el GPIO25 está alto y calla cuando está bajo. No genera notas: el tono lo trae el propio componente. Tres pitidos cortos y un silencio, como un aviso de que el programa sigue vivo.

Materiales

Cableado

Señal del módulo (I/O o S) al GPIO25, físico 22. GND del módulo al físico 6.

VCC del módulo al 3,3 V, físico 1, si el módulo lo admite. Si el texto del módulo exige 5 V, VCC al físico 2 y la señal sigue en el GPIO25: no pongas el positivo de un zumbador de 5 V directo al GPIO. Cada GPIO es de 3,3 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

gpiozero.Buzzer enciende y apaga. No uses TonalBuzzer aquí: eso es el zumbador pasivo de la ficha 07.

from gpiozero import Buzzer
from time import sleep

z = Buzzer(25)
try:
    while True:
        for _ in range(3):
            z.on()
            sleep(0.15)
            z.off()
            sleep(0.15)
        sleep(1)
finally:
    z.off()
    z.close()

Hardware: GPIO Software: gpiozero Software: libgpiod Enseñanza: encender esa salida desde Python

Pasos

  1. Lee la serigrafía: activo, no pasivo. El activo suele llevar una pegatina + o un oscilador en la placa.
  2. Señal al físico 22, masa al 6, VCC según el párrafo de cableado.
  3. Ejecuta. Deben oírse tres pitidos y una pausa.
  4. Si no suena y el módulo es de 5 V con VCC en 3,3 V, cámbialo al físico 2 sin mover la señal.
  5. No dejes el zumbador en alto permanente: molesta y calienta el módulo.
  6. Ctrl+C y comprueba el silencio.

Circuito

GPIO25señalzumbador activoGND

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

07 Zumbador pasivo, una nota

Básico

Qué hace

Un piezo pasivo no suena solo: hay que mandarle una frecuencia. Esta ficha toca un la4 (440 Hz) durante un segundo y calla otro. Es PWM de sonido en el GPIO18, no el zumbador activo del físico 22.

Materiales

Cableado

GPIO18, físico 12, resistencia de 330 Ω, pata del piezo, la otra pata a GND físico 6.

Nada a 5 V ni a 3,3 V. El piezo lo mueve el PWM del GPIO. No lo conectes al físico 22: ese es el GPIO25, reservado al zumbador activo.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

TonalBuzzer y Tone de gpiozero. La nota sale del PWM; no hay fichero de audio.

from gpiozero import TonalBuzzer
from gpiozero.tones import Tone
from time import sleep

z = TonalBuzzer(18)
try:
    while True:
        z.play(Tone("A4"))
        sleep(1)
        z.stop()
        sleep(1)
finally:
    z.stop()
    z.close()

Hardware: GPIO Software: PWM Software: libgpiod Enseñanza: PWM, brillo y servo

Pasos

  1. Confirma que el componente no pone «active». Si es módulo activo, vuelve a la ficha 06.
  2. Serie 330 Ω entre el físico 12 y el piezo.
  3. Ejecuta y escucha un tono estable, no un chasquido.
  4. Cambia la cadena a C5 en una copia si quieres oír que la nota sí depende del código.
  5. Vuelve a A4 para dejar la ficha como está.
  6. Ctrl+C. Si el piezo queda zumbando, no llamaste a stop().

Circuito

GPIO18330 Ωpiezo pasivoGND

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

08 Dos botones y un LED (AND y OR)

Básico

Qué hace

Dos pulsadores y un LED muestran en la misma protoboard una puerta AND y una OR, elegidas al arrancar. En AND el LED solo luce si estás pulsando los dos. En OR basta con uno. Los dos van a GND con pull-up interna.

Materiales

Cableado

LED en GPIO17, físico 11, 330 Ω, cátodo a GND físico 6.

Primer pulsador: GPIO23, físico 16, a GND físico 9. Segundo: GPIO24, físico 18, a GND físico 14. Nada a 5 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Se leen is_pressed, que en gpiozero es True cuando el contacto cierra a GND. La lógica es Python, no un chip 74HC.

from gpiozero import LED, Button
from time import sleep

led = LED(17)
a = Button(23, pull_up=True)
b = Button(24, pull_up=True)
modo = input("Escribe AND o OR: ").strip().upper()
if modo not in ("AND", "OR"):
    raise SystemExit("Solo AND o OR")
try:
    while True:
        if modo == "AND":
            led.value = a.is_pressed and b.is_pressed
        else:
            led.value = a.is_pressed or b.is_pressed
        sleep(0.05)
finally:
    led.close()

Hardware: Botón Software: gpiozero Software: libgpiod Enseñanza: pulsador y rebote

Pasos

  1. Cablea los dos pulsadores a masa, en físicos distintos.
  2. Arranca y escribe AND. Pulsa solo uno: el LED sigue apagado.
  3. Pulsa los dos: se enciende.
  4. Reinicia y escribe OR. Con uno solo ya luce.
  5. No pulses las letras en minúsculas raras con espacios: el script las recorta, pero AND/OR deben verse.
  6. Ctrl+C.

Circuito

GPIO23botón AGPIO24botón BAND / ORLED 330

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

09 Interruptor de palanca

Básico

Qué hace

Una palanca se queda donde la dejas, no como un pulsador. El LED copia esa posición: palanca cerrada a GND, LED encendido. El programa no hace memoria; la memoria la tiene el interruptor.

Materiales

Cableado

Un polo de la palanca al GPIO23, físico 16. El otro polo a GND, físico 9. LED en GPIO17, físico 11, con 330 Ω, cátodo a GND físico 6.

Nada a 5 V ni al 3,3 V. Pull-up interna. Si tu palanca es de tres patas, usa común y un extremo, no los dos extremos.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

DigitalInputDevice con pull_up sigue el contacto sin tratarlo como un clic. El LED refleja pin.value == 0, porque cerrado a GND es nivel bajo.

from gpiozero import LED, DigitalInputDevice
from time import sleep

led = LED(17)
palanca = DigitalInputDevice(23, pull_up=True)
try:
    while True:
        cerrado_a_gnd = not palanca.value
        led.value = cerrado_a_gnd
        print("palanca cerrada" if cerrado_a_gnd else "palanca abierta")
        sleep(0.2)
finally:
    led.close(); palanca.close()

Hardware: Botón Software: gpiozero Software: libgpiod Enseñanza: pulsador y rebote

Pasos

  1. Identifica las patas con el polímetro en continuidad, con la Pi apagada.
  2. Cablea común y un extremo. El otro extremo queda al aire.
  3. Ejecuta y baja la palanca: debe imprimir cerrada y el LED encenderse.
  4. Súbela: abierta y LED apagado.
  5. Déjala quieta un minuto: el texto no debe parpadear. Si parpadea, la palanca o el cable van flojos.
  6. Ctrl+C.

Circuito

palancaGPIO23lecturaGPIO17330 ΩLED

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

10 Carrera de cuatro LEDs

Básico

Qué hace

Cuatro LED se encienden en orden, como una carrera de un extremo a otro de la protoboard, y vuelven. El cuarto usa el GPIO10 porque esta ficha no toca el SPI. Si más adelante montas un RFID, ese pin deja de estar libre.

Materiales

Cableado

GPIO17 físico 11, GPIO27 físico 13, GPIO22 físico 15 y GPIO10 físico 19. Cada uno con su 330 Ω y el cátodo al GND físico 6.

Nada a 5 V. No pongas el cuarto LED en el físico 21: ese es MISO, GPIO9, y no es el LED4 de este mapa.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Un LEDBoard enciende un índice y apaga el resto. El sentido se invierte al llegar al final.

from gpiozero import LEDBoard
from time import sleep

luces = LEDBoard(17, 27, 22, 10)
indice = 0
paso = 1
try:
    while True:
        luces.off()
        luces[indice].on()
        sleep(0.15)
        indice += paso
        if indice in (0, 3):
            paso = -paso
            indice += paso
finally:
    luces.close()

Hardware: LED Software: gpiozero Software: libgpiod Enseñanza: encender esa salida desde Python

Pasos

  1. Coloca los cuatro LED en fila, cada uno con 330 Ω.
  2. El último cable es el físico 19, no el 18. El 18 es el segundo botón.
  3. Ejecuta: la luz debe viajar ida y vuelta sin encender dos a la vez.
  4. Tapa un LED con el dedo para asegurarte de cuál es el del extremo.
  5. Si el extremo no luce, revisa solo el GPIO10.
  6. Ctrl+C.

Circuito

L1 GPIO17L2 GPIO27L3 GPIO22L4 GPIO10

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

11 Dado de siete LEDs

Básico

Qué hace

Siete LED en forma de dado muestran una cara al azar cuando pulsas el botón. No es un dado de pantalla: cada punto es un LED con su resistencia, y el GPIO10 entra porque aquí no hay SPI.

Materiales

Cableado

Puntos: GPIO17 físico 11 (arriba izquierda), GPIO27 físico 13 (arriba derecha), GPIO22 físico 15 (centro izquierda), GPIO10 físico 19 (centro), GPIO16 físico 36 (centro derecha), GPIO20 físico 38 (abajo izquierda), GPIO21 físico 40 (abajo derecha). Cada uno con 330 Ω. Cátodos a GND físico 6.

Pulsador del GPIO23, físico 16, a GND físico 9. Nada a 5 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

gpiozero elige una cara y enciende solo los LED de esa cara. El azar es el módulo random de la biblioteca estándar.

from gpiozero import LED, Button
from random import randint
from signal import pause

# orden: AI AD CI C CD BI BD
pines = (17, 27, 22, 10, 16, 20, 21)
leds = [LED(p) for p in pines]
caras = {
    1: (3,),
    2: (0, 6),
    3: (0, 3, 6),
    4: (0, 1, 5, 6),
    5: (0, 1, 3, 5, 6),
    6: (0, 1, 2, 4, 5, 6),
}

def tirar():
    for led in leds:
        led.off()
    for i in caras[randint(1, 6)]:
        leds[i].on()

Button(23, pull_up=True, bounce_time=0.05).when_pressed = tirar
tirar()
pause()

Hardware: LED Software: gpiozero Software: libgpiod Enseñanza: encender esa salida desde Python

Pasos

  1. Dibuja en papel la H de siete puntos antes de cablear.
  2. Cada LED lleva sus 330 Ω. El centro es el físico 19, GPIO10.
  3. El botón va al físico 16 y a masa.
  4. Ejecuta y pulsa: debe cambiar la cara, no sumar puntos sueltos.
  5. Tira veinte veces y comprueba que sale el 1 (solo el centro) alguna vez.
  6. Si dos LED vecinos lucen siempre juntos, comparten cátodo mal puesto.

Circuito

7 LED330 ΩGNDGPIO23tirar

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

12 Display de 7 segmentos, un dígito

Básico

Qué hace

Un display de un dígito, cátodo común, cuenta del 0 al 9 y vuelve a empezar. Cada segmento es un LED y lleva su resistencia. No hay circuito integrado de driver: los siete GPIO los pone gpiozero.

Materiales

Cableado

Segmento a GPIO17 físico 11, b GPIO27 físico 13, c GPIO22 físico 15, d GPIO10 físico 19, e GPIO16 físico 36, f GPIO20 físico 38, g GPIO21 físico 40. Cada uno con 330 Ω.

Cátodo común a GND físico 6. El punto decimal no se usa. Nada a 5 V. Este mapa usa el GPIO10: no lo montes junto a un dispositivo SPI.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Una tabla dice qué segmentos se encienden para cada cifra. gpiozero solo obedece esa tabla.

from gpiozero import LED
from time import sleep

seg = [LED(p) for p in (17, 27, 22, 10, 16, 20, 21)]
# a b c d e f g
cifras = {
    0: "1111110", 1: "0110000", 2: "1101101", 3: "1111001",
    4: "0110011", 5: "1011011", 6: "1011111", 7: "1110000",
    8: "1111111", 9: "1111011",
}

def mostrar(n):
    for led, bit in zip(seg, cifras[n]):
        led.value = bit == "1"

try:
    while True:
        for n in range(10):
            mostrar(n)
            sleep(0.7)
finally:
    for led in seg:
        led.close()

Hardware: LED Software: gpiozero Software: libgpiod Enseñanza: LED y resistencia

Pasos

  1. Mira la hoja del display y localiza el cátodo común. Si es ánodo común, este código no vale.
  2. Pon 330 Ω en los siete segmentos, no en el común.
  3. Cablea a, b, c, d, e, f, g en el orden de pines de arriba.
  4. Ejecuta. El primer símbolo tiene que ser un 0, el segundo un 1 (solo b y c).
  5. Si el 1 aparece en otro sitio, gira el mapa, no cambies resistencias al azar.
  6. Ctrl+C.

Circuito

a GPIO17b GPIO27c GPIO22d GPIO10e GPIO16f GPIO20g GPIO21GND

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

13 LDR: LED al oscurecer

Básico

Qué hace

Cuando tapas la fotorresistencia, se enciende el LED. No hay conversor analógico: gpiozero mide cuánto tarda un condensador en cargarse a través de la LDR. Oscuridad, resistencia alta, carga lenta, valor bajo, LED encendido.

Materiales

Cableado

LED en GPIO17, físico 11, con 330 Ω, cátodo a GND físico 6.

LDR entre el 3,3 V (físico 1) y el GPIO27 (físico 13). Condensador de 1 µF entre ese mismo GPIO27 y GND físico 9. No lleves la LDR a un pin de 5 V ni a un GPIO creyendo que es un ADC.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

LightSensor no es un voltaje. when_dark y when_light usan el tiempo de carga. El umbral por defecto es 0,1.

from gpiozero import LED, LightSensor
from signal import pause

led = LED(17)
ldr = LightSensor(27)
ldr.when_dark = led.on
ldr.when_light = led.off
pause()

Hardware: LDR Software: gpiozero Software: libgpiod Enseñanza: sensor, decisión y aviso local

Pasos

  1. Monta el LED y comprueba que puedes encenderlo aparte.
  2. Conecta la LDR del físico 1 al físico 13, y el condensador del 13 a masa. Respeta la polaridad si el condensador la tiene.
  3. Ejecuta a la luz de la habitación: el LED debería estar apagado.
  4. Tapa la LDR con la mano. El LED se enciende.
  5. Destapa. Se apaga, quizá con un pequeño retardo del promediado.
  6. Si está siempre encendido, sube el umbral: LightSensor(27, threshold=0.3).

Circuito

3,3 VLDRGPIO271 µFGNDGPIO17330 ΩLED

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

14 Potenciómetro y brillo PWM

Básico

Qué hace

El cursor de un potenciómetro no toca ningún GPIO. Entra en el canal AIN0 de un ADS1115, y el número en bruto manda el brillo PWM del LED. No calculamos voltios: el ciclo de trabajo es ese entero partido del fondo de escala.

Materiales

Cableado

SDA del ADS al GPIO2, físico 3. SCL al GPIO3, físico 5. VDD del ADS al 3,3 V, físico 1. GND al físico 6. ADDR a GND para la dirección 0x48.

Extremos del potenciómetro: uno al 3,3 V y otro a GND. Cursor solo a AIN0. LED en GPIO17, físico 11, con 330 Ω, cátodo a GND. Nada del cursor va a un GPIO. I2C se activa con dtparam=i2c_arm=on en config.txt.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

El ADS se lee por /dev/i2c-1. La palabra 0xC383 es un disparo único de AIN0 con PGA ±4,096 V, sacada del datasheet, no una calibración. El entero se lleva a gpiozero.PWMLED. Los voltios, si los quieres, están en Software, no aquí.

import fcntl, os, time
from gpiozero import PWMLED

I2C_SLAVE = 0x0703
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x48)
led = PWMLED(17)

def crudo():
    os.write(fd, bytes([0x01, 0xC3, 0x83]))
    time.sleep(0.02)
    os.write(fd, bytes([0x00]))
    hi, lo = os.read(fd, 2)
    valor = (hi << 8) | lo
    if valor & 0x8000:
        valor -= 0x10000
    return valor

try:
    while True:
        n = max(0, min(32767, crudo()))
        led.value = n / 32767
        print("entero", n)
        time.sleep(0.1)
finally:
    led.close()
    os.close(fd)

Hardware: ADS1115 Software: PWM Software: I2C Enseñanza: PWM, brillo y servo

Pasos

  1. Activa el I2C en /boot/firmware/config.txt con dtparam=i2c_arm=on y reinicia. No toques el cursor con un GPIO.
  2. Cablea SDA, SCL, 3,3 V y GND del ADS.
  3. Potenciómetro entre 3,3 V y GND, cursor a AIN0.
  4. LED con 330 Ω en el físico 11.
  5. Ejecuta y gira el eje: el entero y el brillo deben moverse juntos.
  6. Si el entero no cambia, el cursor está en un extremo fijo o AIN0 está suelto.

Circuito

3,3 VpotenciómetroAIN0SDA SCLADS1115PWM GPIO17

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

15 LED que respira

Básico

Qué hace

El LED no parpadea a cortes: el brillo sube y baja solo, como una respiración. Lo hace el PWM de gpiozero. No hay potenciómetro ni sensor.

Materiales

Cableado

GPIO17, físico 11, resistencia de 330 Ω, ánodo del LED, cátodo a GND físico 6.

Nada a 5 V ni al pin de 3,3 V. El PWM sigue siendo una señal de 3,3 V. Meter 5 V en el físico 11 lo rompe.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

PWMLED.pulse() cronometra la rampa. fade_in_time y fade_out_time están en segundos.

from gpiozero import PWMLED
from signal import pause

led = PWMLED(17)
led.pulse(fade_in_time=1.5, fade_out_time=1.5)
pause()

Hardware: LED Software: PWM Software: libgpiod Enseñanza: PWM, brillo y servo

Pasos

  1. Mismo cable que el LED de la ficha 01, resistencia incluida.
  2. Ejecuta y aparta la vista un segundo: el cambio es gradual, no un clic.
  3. Cuenta una subida: alrededor de un segundo y medio.
  4. No lo alimentes desde el físico 2 para «hacerlo más brillante».
  5. Si solo ves encendido o apagado, estás usando LED y no PWMLED.
  6. Ctrl+C.

Circuito

GPIO17 PWM330 ΩLEDGND

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

16 Alarma de botón y zumbador

Básico

Qué hace

Si pulsas el botón, el zumbador activo pita y el LED se queda encendido hasta que vuelves a pulsar. Es un aviso que tú confirmas, no una sirena que se calla sola al soltar.

Materiales

Cableado

Pulsador GPIO23 físico 16 a GND físico 9. LED GPIO17 físico 11 con 330 Ω a GND físico 6.

Zumbador activo: señal al GPIO25 físico 22, GND al físico 14, VCC al 3,3 V físico 1 si el módulo es de 3,3 V. Si exige 5 V, VCC al físico 2 y la señal sigue en el GPIO, nunca el positivo del zumbador de 5 V directo al pin.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Un booleano alterna la alarma en cada pulsación filtrada. Buzzer y LED van juntos.

from gpiozero import LED, Button, Buzzer
from signal import pause

led = LED(17)
z = Buzzer(25)
armada = False

def cambiar():
    global armada
    armada = not armada
    if armada:
        led.on()
        z.beep(on_time=0.2, off_time=0.2)
    else:
        led.off()
        z.off()

Button(23, pull_up=True, bounce_time=0.05).when_pressed = cambiar
pause()

Hardware: Botón Software: gpiozero Software: libgpiod Enseñanza: sensor, decisión y aviso local

Pasos

  1. Cablea botón, LED y zumbador antes de ejecutar.
  2. Primera pulsación: LED fijo y pitidos.
  3. Segunda pulsación: silencio y LED apagado.
  4. Si pita sin parar tras la segunda, z.off() no se está llamando.
  5. No uses un piezo pasivo: oirás chasquidos.
  6. Ctrl+C y comprueba que calla.

Circuito

botón GPIO23estadoGPIO17 LEDGPIO25 zumbador

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

17 Semáforo peatonal

Básico

Qué hace

Los coches siguen en verde hasta que pides paso. Entonces pasan a ámbar, a rojo, y el peatón tiene verde unos segundos. Al acabar, el peatón vuelve a rojo y los coches a verde. El botón solo pide; no mantiene el verde peatonal mientras lo pulses.

Materiales

Cableado

Coches: rojo GPIO17 físico 11, ámbar GPIO27 físico 13, verde GPIO22 físico 15. Peatón: rojo GPIO16 físico 36, verde GPIO20 físico 38. Todos con 330 Ω y cátodo a GND físico 6.

Pulsador GPIO23 físico 16 a GND físico 9. Nada a 5 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Un hilo de gpiozero no hace falta: el botón marca un aviso y el bucle principal hace la secuencia cuando le toca.

from gpiozero import LED, Button
from time import sleep

c_rojo, c_ambar, c_verde = LED(17), LED(27), LED(22)
p_rojo, p_verde = LED(16), LED(20)
pedido = False

def pedir():
    global pedido
    pedido = True

Button(23, pull_up=True, bounce_time=0.05).when_pressed = pedir

def coches_verde():
    c_rojo.off(); c_ambar.off(); c_verde.on()
    p_rojo.on(); p_verde.off()

try:
    coches_verde()
    while True:
        sleep(0.1)
        if not pedido:
            continue
        pedido = False
        c_verde.off(); c_ambar.on(); sleep(1.2)
        c_ambar.off(); c_rojo.on()
        p_rojo.off(); p_verde.on(); sleep(4)
        p_verde.off(); p_rojo.on(); sleep(1)
        coches_verde()
finally:
    for luz in (c_rojo, c_ambar, c_verde, p_rojo, p_verde):
        luz.close()

Hardware: LED Software: gpiozero Software: libgpiod Enseñanza: pulsador y rebote

Pasos

  1. Coloca los cinco LED y no compartas resistencias.
  2. Arranca: coches en verde, peatón en rojo.
  3. Pulsa una vez y suelta. No mantengas.
  4. Mira el ámbar antes del rojo de coches.
  5. El verde peatonal dura unos 4 s y no se alarga por seguir pulsando durante el cruce: el aviso ya se consumió.
  6. Ctrl+C.

Circuito

coches R A Vpeatón R Vbotónpedido

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

18 LED de estado de la Pi

Básico

Qué hace

Un LED late despacio mientras este programa está vivo. No mira la red ni el disco: solo dice que el script sigue en marcha. Si matas el proceso, el latido para y el LED queda apagado.

Materiales

Cableado

GPIO17 físico 11, 330 Ω, LED, cátodo a GND físico 6.

Nada a 5 V. No uses el LED de actividad de la placa: este es uno tuyo, en el header.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

blink con tiempos distintos. Al salir, close() lo apaga. Sigue siendo gpiozero, no un servicio del sistema.

from gpiozero import LED
from signal import pause

led = LED(17)
led.blink(on_time=0.15, off_time=1.2)
try:
    pause()
finally:
    led.off()
    led.close()

Hardware: LED Software: gpiozero Software: libgpiod Enseñanza: encender esa salida desde Python

Pasos

  1. Cablea el LED con 330 Ω.
  2. Ejecuta. Debes ver un destello breve y una pausa larga.
  3. Deja pasar un minuto: el ritmo no debe acelerarse.
  4. Ctrl+C. El LED tiene que quedar apagado.
  5. Si sigue luciendo, otro proceso lo está gobernando.
  6. No confundas este piloto con el de alimentación de la placa, que no se apaga con Ctrl+C.

Circuito

programa vivoGPIO17330 ΩLED

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

19 Morse con LED

Básico

Qué hace

Escribes una frase corta y el LED la deletrea en morse. El punto dura 0,2 s, la raya tres puntos, y la pausa entre letras es de tres puntos. No hay sonido: solo luz.

Materiales

Cableado

GPIO17 físico 11, 330 Ω, LED, GND físico 6. Nada a 5 V.

No hace falta botón: el texto entra por el teclado de la terminal.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

La tabla de morse está en el propio script. gpiozero solo enciende y apaga el LED el tiempo que toca.

from gpiozero import LED
from time import sleep

led = LED(17)
PUNTO = 0.2
MORSE = {
    "A": ".-", "B": "-...", "C": "-.-.", "D": "-..", "E": ".",
    "F": "..-.", "G": "--.", "H": "....", "I": "..", "J": ".---",
    "K": "-.-", "L": ".-..", "M": "--", "N": "-.", "O": "---",
    "P": ".--.", "Q": "--.-", "R": ".-.", "S": "...", "T": "-",
    "U": "..-", "V": "...-", "W": ".--", "X": "-..-", "Y": "-.--",
    "Z": "--..", "0": "-----", "1": ".----", "2": "..---", "3": "...--",
    "4": "....-", "5": ".....", "6": "-....", "7": "--...", "8": "---..",
    "9": "----.",
}

def elemento(sim):
    led.on()
    sleep(PUNTO if sim == "." else PUNTO * 3)
    led.off()
    sleep(PUNTO)

texto = input("Texto: ").upper()
try:
    for letra in texto:
        if letra == " ":
            sleep(PUNTO * 4)
            continue
        codigo = MORSE.get(letra)
        if not codigo:
            continue
        for sim in codigo:
            elemento(sim)
        sleep(PUNTO * 2)
finally:
    led.close()

Hardware: LED Software: gpiozero Software: libgpiod Enseñanza: encender esa salida desde Python

Pasos

  1. Cablea el LED.
  2. Ejecuta y escribe SOS.
  3. Tres puntos, tres rayas, tres puntos. Las rayas se notan claramente más largas.
  4. Prueba una palabra con espacio, HOLA PI.
  5. Los signos que no están en la tabla se saltan, no se inventan.
  6. Ctrl+C si te cansas a mitad de una frase larga.

Circuito

tecladotabla morseGPIO17LED

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

20 Caja de ritmos de cuatro pasos

Básico

Qué hace

Cuatro pasos se repiten. En cada paso luce un LED y, si ese paso está marcado, el zumbador pasivo da un clic tonal corto. El patrón inicial es bombo, silencio, caja, silencio. El GPIO10 es el cuarto LED porque no hay SPI, y el sonido va al GPIO18.

Materiales

Cableado

LED en GPIO17 físico 11, GPIO27 físico 13, GPIO22 físico 15 y GPIO10 físico 19, cada uno con 330 Ω, cátodos a GND físico 6.

Piezo pasivo: GPIO18 físico 12, serie 330 Ω, otra pata a GND físico 14. Nada a 5 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

TonalBuzzer toca un golpe breve, no una melodía. Los LED marcan el paso aunque el paso esté en silencio.

from gpiozero import LED, TonalBuzzer
from gpiozero.tones import Tone
from time import sleep

leds = [LED(p) for p in (17, 27, 22, 10)]
z = TonalBuzzer(18)
patron = [True, False, True, False]
try:
    while True:
        for i, suena in enumerate(patron):
            for j, led in enumerate(leds):
                led.value = j == i
            if suena:
                z.play(Tone(220))
                sleep(0.08)
                z.stop()
            sleep(0.22)
finally:
    z.stop()
    for led in leds:
        led.close()
    z.close()

Hardware: LED Software: PWM Software: libgpiod Enseñanza: PWM, brillo y servo

Pasos

  1. Coloca los cuatro LED en orden de pasos.
  2. El piezo es pasivo y va al físico 12, con 330 Ω.
  3. Ejecuta. Los LED avanzan y oyes golpe en el primero y en el tercero.
  4. Cambia patron a cuatro True y reinicia: los cuatro pasos suenan.
  5. Vuelve al patrón original.
  6. Ctrl+C y el piezo debe callar.

Circuito

paso1paso2paso3paso4GPIO18piezo

Variantes

En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

21 DHT11: temperatura y humedad

Birdie

Qué hace

Lees temperatura y humedad de un DHT11 por el driver del kernel, no por una fórmula escrita aquí. Si el fichero sysfs no aparece, el script se para y lo dice. No usa el GPIO4: el dato va al GPIO26.

Materiales

Cableado

VCC del DHT11 al 3,3 V, físico 1. GND al físico 6. Datos al GPIO26, físico 37. Resistencia de 10 kΩ entre datos y 3,3 V.

No alimentes el DHT11 a 5 V: la línea de datos subiría de 3,3 V. No uses el GPIO4, que queda para el DS18B20.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

En /boot/firmware/config.txt añade dtoverlay=dht11,gpiopin=26 y reinicia. El kernel escribe in_temp_input en miligrados e in_humidityrelative_input en milésimas. Dividir entre 1000 cambia de unidad del driver, no calibra el sensor. gpiozero no trae clase DHT11.

import glob, os, time

def leer():
    for base in glob.glob("/sys/bus/iio/devices/iio:device*"):
        t = os.path.join(base, "in_temp_input")
        h = os.path.join(base, "in_humidityrelative_input")
        if os.path.exists(t) and os.path.exists(h):
            temp = int(open(t).read().strip()) / 1000.0
            hum = int(open(h).read().strip()) / 1000.0
            return temp, hum
    raise SystemExit("No veo el DHT en sysfs. Revisa dtoverlay=dht11,gpiopin=26. No invento la cifra.")

while True:
    print(leer())
    time.sleep(2)

Hardware: DHT11 Software: Python Software: config.txt Enseñanza: sensor de temperatura

Pasos

  1. Cablea a 3,3 V, con la pull-up de 10 kΩ.
  2. Añade la línea del overlay y reinicia.
  3. Ejecuta. Tienen que salir dos números, no un texto de relleno.
  4. Sopla cerca del sensor: la humedad debe moverse antes que la temperatura.
  5. Si el script sale al momento, el overlay no está cargado o el pin no es el 26.
  6. No cambies el pin en el código sin cambiar el overlay.

Circuito

3,3 V10 kΩGPIO26DHT11GND

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

22 BMP280: presión

Birdie

Qué hace

El script encuentra el BMP280 en el bus I2C, lee el identificador del registro 0xD0 y, si cuadra, enseña los enteros en bruto de presión y temperatura. Esos enteros no son hectopascales ni grados. La compensación está en Software, no en una fórmula inventada aquí.

Materiales

Cableado

VCC al 3,3 V físico 1. GND al físico 9. SDA al GPIO2 físico 3. SCL al GPIO3 físico 5.

SDO o CSB según el módulo: déjalos como indique la placa para la dirección 0x76. No alimentes el módulo a 5 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Hace falta dtparam=i2c_arm=on. i2cdetect -y 1 debe mostrar 0x76 o 0x77. El 0x58 en el registro 0xD0 es el BMP280; 0x60 sería un BME280, y tampoco convertimos su humedad aquí.

import fcntl, os, time
I2C_SLAVE = 0x0703

def abrir(addr):
    fd = os.open("/dev/i2c-1", os.O_RDWR)
    fcntl.ioctl(fd, I2C_SLAVE, addr)
    return fd

def id_chip(fd):
    os.write(fd, bytes([0xD0]))
    return os.read(fd, 1)[0]

def crudo(fd):
    os.write(fd, bytes([0xF4, 0x25]))
    time.sleep(0.02)
    os.write(fd, bytes([0xF7]))
    d = os.read(fd, 6)
    pres = (d[0] << 12) | (d[1] << 4) | (d[2] >> 4)
    temp = (d[3] << 12) | (d[4] << 4) | (d[5] >> 4)
    return pres, temp

fd = None
for addr in (0x76, 0x77):
    try:
        candidato = abrir(addr)
    except OSError:
        continue
    ident = id_chip(candidato)
    print("direccion", hex(addr), "id", hex(ident))
    if ident in (0x58, 0x60):
        fd = candidato
        break
    os.close(candidato)
if fd is None:
    raise SystemExit("No hay BMP280/BME280. No invento presión.")
print("id 0x60 es BME280: la humedad no se calcula en esta ficha.")
while True:
    pres, temp = crudo(fd)
    print("crudo presion", pres, "crudo temperatura", temp)
    time.sleep(1)

Hardware: BMP280 Software: I2C Software: config.txt Enseñanza: I2C, varios sensores

Pasos

  1. Activa I2C y reinicia.
  2. i2cdetect -y 1. Anota 0x76 o 0x77.
  3. Ejecuta. El id tiene que ser 0x58 o 0x60.
  4. Sopla hacia el sensor: el entero de presión puede moverse. Sigue siendo crudo.
  5. No multipliques por un factor sacado de un foro para «ver hPa».
  6. Si el id es otro, no es este chip.

Circuito

3,3 VBMP280SDA GPIO2SCL GPIO3

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

23 DS18B20 a prueba de agua

Birdie

Qué hace

Una sonda estanca DS18B20, con tres hilos, publica su temperatura por el bus 1-Wire del kernel. El número t= del fichero w1_slave lo escribe el driver. Esta ficha no inventa la conversión: pasa de miligrados a grados porque así está la unidad del kernel.

Materiales

Cableado

Rojo de la sonda al 3,3 V físico 1. Negro a GND físico 9. Amarillo (datos) al GPIO4, físico 7. Resistencia de 4,7 kΩ entre datos y 3,3 V.

No uses modo parásito en esta ficha: los tres hilos van conectados. El dato no va al GPIO26; ese es el DHT11.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

En config.txt: dtoverlay=w1-gpio y reinicia. El directorio 28-… aparece bajo /sys/bus/w1/devices. Si pone NO en vez de YES, la lectura se descarta.

import glob, os, time

def leer():
    rutas = glob.glob("/sys/bus/w1/devices/28-*/w1_slave")
    if not rutas:
        raise SystemExit("No hay DS18B20. Revisa dtoverlay=w1-gpio. No invento grados.")
    texto = open(rutas[0]).read()
    if "YES" not in texto:
        raise SystemExit("CRC no válida. No uso esa línea.")
    mili = int(texto.strip().split("t=")[-1])
    return os.path.basename(os.path.dirname(rutas[0])), mili / 1000.0

while True:
    print(leer())
    time.sleep(1)

Hardware: DS18B20 Software: 1-Wire Software: config.txt Enseñanza: sensor de temperatura

Pasos

  1. Cablea rojo, negro y amarillo. La pull-up es de 4,7 kΩ, no de 10 kΩ.
  2. Añade dtoverlay=w1-gpio y reinicia.
  3. Comprueba que existe una carpeta 28- bajo /sys/bus/w1/devices.
  4. Ejecuta. El nombre de la carpeta es el identificador de esa sonda.
  5. Sujeta la cápsula con los dedos: el número debe subir despacio.
  6. No la metas aún en agua hasta que el sellado de la tuya esté claro. El cable sí puede mojarse; la unión de los hilos, no, si no está aislada.

Circuito

3,3 V4,7 kΩGPIO4DS18B20GND

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

24 PIR enciende una luz

Birdie

Qué hace

El PIR ve movimiento y enciende el LED. Cuando deja de verlo, el LED se apaga. No manda una alarma a ninguna parte: la luz está en la mesa.

Materiales

Cableado

VCC del PIR al 5 V, físico 2, si el módulo lo pide. GND al físico 14. Salida OUT al GPIO5, físico 29.

LED GPIO17 físico 11 con 330 Ω a GND físico 6. Mide OUT con un polímetro antes de fiarte: si en reposo o en detección pasa de 3,3 V, no lo conectes al GPIO.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

MotionSensor de gpiozero. El retardo largo lo marca el potenciómetro del propio módulo, no solo el código.

from gpiozero import LED, MotionSensor
from signal import pause

led = LED(17)
pir = MotionSensor(5)
pir.when_motion = led.on
pir.when_no_motion = led.off
pause()

Hardware: PIR Software: gpiozero Enseñanza: sensor, decisión y aviso local

Pasos

  1. Alimenta el PIR a 5 V y espera un minuto: al arrancar se calibra y puede dar falsos avisos.
  2. Mide OUT. Solo sigue si no supera 3,3 V.
  3. Cablea el LED.
  4. Ejecuta y quédate quieto: el LED acaba apagado.
  5. Mueve la mano delante. Se enciende.
  6. Si no se apaga nunca, baja el tiempo del potenciómetro del módulo.

Circuito

5 VPIRGPIO5GPIO17330 ΩLED

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

25 HC-SR04: distancia

Birdie

Qué hace

Mides una distancia en metros con el HC-SR04. El disparo sale directo del GPIO5, que aguanta los 3,3 V. El eco no entra directo: un divisor baja los 5 V del eco antes del GPIO6. El número lo calcula gpiozero a partir del tiempo de vuelo; no es una fórmula de calibración inventada en la ficha.

Materiales

Cableado

VCC del HC-SR04 al 5 V, físico 2. GND al físico 14. TRIG al GPIO5, físico 29, sin resistencia.

ECHO → resistencia de 1 kΩ → GPIO6, físico 31. Desde ese nudo del GPIO6, resistencia de 2 kΩ a GND físico 20. Así el pico de 5 V queda cerca de 3,3 V. No puentes el eco al GPIO.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

DistanceSensor(echo=6, trigger=5). distance está en metros. max_distance evita esperas eternas si no hay eco.

from gpiozero import DistanceSensor
from time import sleep

sensor = DistanceSensor(echo=6, trigger=5, max_distance=2)
try:
    while True:
        print(round(sensor.distance, 3), "m")
        sleep(0.4)
finally:
    sensor.close()

Hardware: HC-SR04 Software: gpiozero

Pasos

  1. Monta el divisor antes de dar corriente: 1 kΩ en serie desde ECHO y 2 kΩ de GPIO6 a masa.
  2. TRIG al físico 29. VCC al físico 2.
  3. Ejecuta y apunta al techo o a una pared a menos de 2 m.
  4. Acerca la mano: el número baja.
  5. Si se queda clavado en el máximo, el eco no llega o el divisor está al revés.
  6. No mires el sensor de frente a otra placa que también emita ultrasonidos.

Circuito

GPIO5 TRIGHC-SR045 VECHO1 kΩGPIO62 kΩGND

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

26 Sensor de lluvia

Birdie

Qué hace

La placa de lluvia, por su salida digital, enciende el LED cuando el comparador del módulo ve agua. No mide milímetros: solo distingue seco y mojado al umbral que tú dejas en el potenciómetro del módulo.

Materiales

Cableado

VCC del módulo al 3,3 V, físico 1. GND al físico 9. DO al GPIO27, físico 13.

LED GPIO17 físico 11 con 330 Ω a GND físico 6. Si tu módulo solo vive a 5 V, no conectes DO al GPIO: esa salida sería de 5 V. No uses la pata analógica en un GPIO; si la quieres, es cosa del ADS1115, no de esta ficha.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

El DO suele ir a nivel bajo cuando detecta. Lo leemos con pull-up y encendemos el LED en ese caso. Comprueba el serigrafiado de tu placa por si es al revés.

from gpiozero import LED, DigitalInputDevice
from time import sleep

led = LED(17)
lluvia = DigitalInputDevice(27, pull_up=True)
try:
    while True:
        mojado = not lluvia.value
        led.value = mojado
        print("mojado" if mojado else "seco")
        sleep(0.3)
finally:
    led.close()

Hardware: Lluvia Software: gpiozero Enseñanza: sensor, decisión y aviso local

Pasos

  1. Alimenta el módulo a 3,3 V.
  2. Ajusta su potenciómetro con la placa seca hasta que la salida quede en reposo.
  3. Ejecuta: tiene que imprimir seco y el LED apagado.
  4. Pon una gota en la pista. Debe pasar a mojado.
  5. Seca la pista. Vuelve a seco.
  6. No dejes el módulo a la intemperie conectado a la Pi sin pensar en el cable.

Circuito

3,3 Vlluvia DOGPIO27GPIO17LED

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

27 Humedad de suelo, aviso

Birdie

Qué hace

Una sonda capacitiva de suelo entra en el ADS1115. Tú anotas el entero con la tierra seca y el entero con la tierra recién regada. El LED se enciende entre medias, cuando toca avisar. No hay un porcentaje: el script compara enteros que tú has escrito.

Materiales

Cableado

ADS: SDA físico 3, SCL físico 5, VDD 3,3 V físico 1, GND físico 6, ADDR a GND.

Sonda: VCC al mismo 3,3 V, GND común, salida analógica solo a AIN0. LED GPIO17 físico 11 con 330 Ω a GND. El cursor o la salida analógica no van a un GPIO.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Misma lectura cruda del ADS que en la ficha 14. SECO y MOJADO los cambias tú después de mirar la terminal. I2C tiene que estar activo en config.txt.

import fcntl, os, time
from gpiozero import LED
I2C_SLAVE = 0x0703
SECO = 20000
MOJADO = 8000
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x48)
led = LED(17)

def crudo():
    os.write(fd, bytes([0x01, 0xC3, 0x83]))
    time.sleep(0.02)
    os.write(fd, bytes([0x00]))
    hi, lo = os.read(fd, 2)
    valor = (hi << 8) | lo
    if valor & 0x8000:
        valor -= 0x10000
    return valor

try:
    while True:
        n = crudo()
        # Avisa si estás más seco que el punto medio de tus dos notas.
        umbral = (SECO + MOJADO) // 2
        led.value = n >= umbral if SECO > MOJADO else n <= umbral
        print("entero", n, "umbral", umbral)
        time.sleep(1)
finally:
    led.close()

Hardware: Humedad de suelo Software: I2C Software: config.txt Enseñanza: sensor, decisión y aviso local

Pasos

  1. Activa I2C. No conectes la sonda a un GPIO.
  2. Ejecuta con la sonda al aire y anota el entero. Ese será tu SECO, o el otro, según suba o baje al mojar.
  3. Mete la sonda en tierra húmeda y anota el otro entero.
  4. Escribe esos dos números en SECO y MOJADO. El nombre tiene que coincidir con lo que observaste, no con el ejemplo.
  5. El LED avisa del lado seco.
  6. No traduzcas el entero a un tanto por ciento.

Circuito

sondaAIN0ADS1115GPIO17LED aviso

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

28 Reed: puerta abierta

Birdie

Qué hace

Un interruptor reed y un imán en la puerta. Con la puerta cerrada el imán junta el contacto y el LED está apagado. Al abrir, el contacto se separa y el LED se enciende. Es el mismo tipo de lectura que un botón a GND.

Materiales

Cableado

Un hilo del reed al GPIO23, físico 16. El otro a GND físico 9. LED GPIO17 físico 11 con 330 Ω a GND físico 6.

Nada a 5 V. Pull-up interna. Si tu reed es del tipo abierto con imán, la lógica se invierte: pruébalo con el imán en la mano antes de pegarlo.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

gpiozero ve nivel bajo cuando el reed cierra a masa, que aquí significa puerta cerrada.

from gpiozero import LED, DigitalInputDevice
from time import sleep

led = LED(17)
reed = DigitalInputDevice(23, pull_up=True)
try:
    while True:
        cerrada = not reed.value
        led.value = not cerrada
        print("cerrada" if cerrada else "abierta")
        sleep(0.2)
finally:
    led.close()

Hardware: Reed Software: gpiozero Enseñanza: sensor, decisión y aviso local

Pasos

  1. Con la Pi apagada, acerca el imán y escucha o mide el cierre del reed.
  2. Cablea físico 16 y GND.
  3. Ejecuta con el imán pegado: debe decir cerrada y el LED apagado.
  4. Separa el imán: abierta y LED encendido.
  5. Pega el imán en el marco y el reed en la hoja, alineados.
  6. Abre la puerta de verdad y mira la terminal.

Circuito

imánreedGPIO23GNDGPIO17LED

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

29 Sensor de inclinación

Birdie

Qué hace

Un interruptor de bola cierra o abre según tumba la caja. Si la caja se inclina, pita el zumbador activo y se enciende el LED. Sirve para una maqueta o una tapa, no para un sistema de seguridad de una máquina.

Materiales

Cableado

Sensor entre GPIO23 físico 16 y GND físico 9. Pull-up interna.

LED GPIO17 físico 11 con 330 Ω a GND físico 6. Zumbador activo: señal GPIO25 físico 22, GND físico 14, VCC al 3,3 V físico 1 si el módulo es de 3,3 V. Nada de 5 V en los GPIO.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Mientras el contacto está cerrado se considera inclinada. Si tu sensor es al revés, cambia la comparación, no el cable a 5 V.

from gpiozero import LED, Buzzer, DigitalInputDevice
from time import sleep

led = LED(17)
z = Buzzer(25)
sensor = DigitalInputDevice(23, pull_up=True)
try:
    while True:
        inclinado = not sensor.value
        led.value = inclinado
        if inclinado:
            z.on()
        else:
            z.off()
        sleep(0.1)
finally:
    z.off(); led.close(); z.close()

Hardware: Inclinación Software: gpiozero Enseñanza: sensor, decisión y aviso local

Pasos

  1. Deja la protoboard plana y ejecuta. Anota si en reposo dice inclinada o no.
  2. Si pita en reposo, tu contacto cierra al revés: invierte la comparación.
  3. Inclina la placa unos 45 grados. Debe pitar.
  4. Vuélvela a dejar plana. Silencio.
  5. No lo montes todavía en nada que se mueva solo.
  6. Ctrl+C.

Circuito

bolaGPIO23LEDzumbador GPIO25

Variantes

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

30 Sensor de llama, solo aviso

Birdie

Qué hace

El módulo de llama solo enciende un LED y un pitido si su salida digital se activa. No abre una válvula, no apaga un fuego y no sustituye a un detector de incendios. Es un sensor de hobby para ver que la entrada cambia.

Materiales

Cableado

VCC del módulo al 3,3 V físico 1. GND al físico 6. DO al GPIO27 físico 13. Si DO puede poner 5 V, no lo conectes.

LED GPIO17 físico 11 con 330 Ω a GND físico 9. Zumbador activo en GPIO25 físico 22, GND físico 14. No hay relé ni nada que controle una llama.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

El aviso es local. El script no intenta «gestionar» el fuego. Si DO es activo a nivel bajo, la comparación es la del código; confírmalo con la luz de un mechero lejano solo si un adulto lo tiene ya encendido por su cuenta, y apaga el aviso en cuanto veas el cambio.

from gpiozero import LED, Buzzer, DigitalInputDevice
from time import sleep

led = LED(17)
z = Buzzer(25)
llama = DigitalInputDevice(27, pull_up=True)
print("Solo aviso. Este circuito no controla fuego ni sustituye un detector.")
try:
    while True:
        aviso = not llama.value
        led.value = aviso
        z.value = aviso
        sleep(0.1)
finally:
    z.off(); led.close(); z.close()

Hardware: Llama Software: gpiozero Enseñanza: sensor, decisión y aviso local

Pasos

  1. Alimenta el módulo a 3,3 V y no lo apuntes a nada todavía.
  2. Ejecuta. En reposo, LED apagado y silencio. Si pita ya, invierte la comparación.
  3. Solo entonces, y con un adulto, acerca una llama pequeña que ya existiera. El aviso debe saltar.
  4. Apaga esa llama como se apaga siempre, fuera de este circuito.
  5. Quita el módulo del frente de la llama. El aviso cesa.
  6. No dejes esto como alarma de una cocina.

Circuito

módulo llamaGPIO27LEDzumbadorsolo aviso

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

31 MQ-2 con ADC externo

Birdie

Qué hace

El MQ-2 calienta su cápsula a 5 V y su salida analógica entra en un ADS1115, nunca en un GPIO. El script imprime el entero en bruto. No calcula ppm ni un porcentaje de gas: es un sensor de hobby, no un detector de humo homologado.

Materiales

Cableado

Calentador del MQ-2: VCC al 5 V, físico 2, GND al físico 6. AO no va directo al ADS: 10 kΩ desde AO hasta AIN0 y 15 kΩ desde AIN0 a GND, para que 5 V en AO queden en unos 3 V.

ADS1115: SDA GPIO2 físico 3, SCL GPIO3 físico 5, VDD al 3,3 V físico 1, GND común, ADDR a GND. LED de aviso en GPIO17 físico 11 con 330 Ω a GND. Nada de 230 V. Cada GPIO es de 3,3 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

I2C con dtparam=i2c_arm=on. La palabra 0xC383 es el disparo de AIN0 del datasheet, PGA ±4,096 V. El umbral es un entero que tú anotas en aire limpio, no una curva de gas.

import fcntl, os, time
from gpiozero import LED
I2C_SLAVE = 0x0703
UMBRAL = 12000
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x48)
led = LED(17)

def crudo():
    os.write(fd, bytes([0x01, 0xC3, 0x83]))
    time.sleep(0.02)
    os.write(fd, bytes([0x00]))
    hi, lo = os.read(fd, 2)
    n = (hi << 8) | lo
    if n & 0x8000:
        n -= 0x10000
    return n

print("Precalienta unos minutos. Esto no es una alarma homologada.")
try:
    while True:
        n = crudo()
        led.value = n >= UMBRAL
        print("crudo", n)
        time.sleep(1)
finally:
    led.close()

Hardware: MQ-2 Software: I2C Software: config.txt Enseñanza: I2C, varios sensores

Pasos

  1. Activa I2C y reinicia. No pongas AO en un GPIO.
  2. Monta el divisor 10 kΩ / 15 kΩ antes de alimentar el MQ-2.
  3. Ejecuta en aire limpio, espera el precalentado y anota el entero.
  4. Escribe un UMBRAL claramente por encima de ese aire limpio.
  5. Acerca vapor de alcohol de una muestra ya abierta, solo un momento: el entero sube y el LED puede encenderse.
  6. Ventila. No uses esto como alarma de una casa.

Circuito

MQ-2 AO10 kΩAIN015 kΩGNDADSLED GPIO17

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

32 ADS1115 y potenciómetro

Birdie

Qué hace

Un potenciómetro se lee solo como número del ADS1115. El cursor no toca ningún GPIO. Esta ficha no enciende un LED: solo demuestra la lectura, y dice explícitamente que ese entero no es un voltio.

Materiales

Cableado

ADS: SDA físico 3 (GPIO2), SCL físico 5 (GPIO3), VDD 3,3 V físico 1, GND físico 6, ADDR a GND.

Un extremo del potenciómetro al 3,3 V, el otro a GND, el cursor solo a AIN0. Nada a 5 V. El cursor en un GPIO no es una entrada analógica y puede romper el pin.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Misma configuración de disparo único 0xC383. La escala a voltios depende del PGA y está en Software; este programa se queda en el entero.

import fcntl, os, time
I2C_SLAVE = 0x0703
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x48)

def crudo():
    os.write(fd, bytes([0x01, 0xC3, 0x83]))
    time.sleep(0.02)
    os.write(fd, bytes([0x00]))
    hi, lo = os.read(fd, 2)
    n = (hi << 8) | lo
    if n & 0x8000:
        n -= 0x10000
    return n

while True:
    n = crudo()
    print("entero", n, "(no es un voltio)")
    time.sleep(0.3)

Hardware: ADS1115 Software: I2C Software: config.txt Enseñanza: I2C, varios sensores

Pasos

  1. Activa I2C. Comprueba 0x48 con i2cdetect.
  2. Cablea el potenciómetro entre 3,3 V y GND.
  3. Cursor a AIN0, no a un físico del header de GPIO.
  4. Ejecuta y gira despacio. El entero debe recorrer un tramo amplio.
  5. En un extremo no tiene que ser negativo: si lo es, el cursor no está en AIN0 respecto a GND.
  6. No multipliques por 0,000125 para «ver voltios» en esta ficha.

Circuito

3,3 VpotenciómetroAIN0 ADSGND

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

33 OLED SSD1306

Birdie

Qué hace

Enciende un OLED SSD1306 de 128 por 64 y le manda un patrón de barras por I2C, sin librería de terceros. Si el identificador no está en el bus, no dibuja un éxito falso. No es un SH1106: si la imagen sale corrida dos columnas, este código no lo corrige.

Materiales

Cableado

VCC al 3,3 V físico 1. GND al físico 6. SDA al GPIO2 físico 3. SCL al GPIO3 físico 5.

Nada a 5 V. Esos pines 3 y 5 no se usan como LED.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

dtparam=i2c_arm=on. Los comandos son la secuencia de arranque del SSD1306, bomba de carga incluida. El patrón solo enseña que la RAM obedece.

import fcntl, os
I2C_SLAVE = 0x0703
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x3C)

def cmd(b):
    os.write(fd, bytes([0x00, b]))

for c in (0xAE,0xD5,0x80,0xA8,0x3F,0xD3,0x00,0x40,0x8D,0x14,0x20,0x00,0xA1,0xC8,0xDA,0x12,0x81,0xCF,0xD9,0xF1,0xDB,0x40,0xA4,0xA6,0xAF):
    cmd(c)
cmd(0x21); cmd(0); cmd(127)
cmd(0x22); cmd(0); cmd(7)
for i in range(64):
    byte = 0xFF if (i // 8) % 2 == 0 else 0x00
    os.write(fd, bytes([0x40]) + bytes([byte]) * 16)
print("Patron enviado a 0x3C. Si sigue negra, no es este controlador.")

Hardware: OLED Software: I2C Software: config.txt Enseñanza: I2C, varios sensores

Pasos

  1. Activa I2C y reinicia.
  2. i2cdetect -y 1 y confirma 3c.
  3. Cablea solo a 3,3 V.
  4. Ejecuta. Deben verse bandas horizontales.
  5. Si no hay bandas y el 3c existe, revisa SDA y SCL cruzados.
  6. Si la imagen nace dos columnas a la derecha, probablemente es un SH1106 y esta ficha no lo arregla.

Circuito

3,3 VSSD1306SDASCLGND

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

34 LCD 16x2 por I2C

Birdie

Qué hace

Escribe Hola Pi en un LCD 16x2 con mochila I2C. La alimentación del módulo va a 3,3 V para que SDA y SCL no suban a 5 V. Si con 3,3 V el contraste no llega, no pases el módulo a 5 V sin medir antes que el bus sigue en 3,3 V.

Materiales

Cableado

VCC al 3,3 V físico 1. GND al físico 6. SDA GPIO2 físico 3. SCL GPIO3 físico 5.

Nada a 5 V mientras no hayas medido las pull-up. El contraste, si hay potenciómetro en la mochila, se ajusta con ese tornillo, no con un GPIO.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

El mapa de la mochila habitual es RS en P0, E en P2, luz en P3 y datos en P4-P7. Se prueba 0x27 y luego 0x3F. I2C tiene que estar activo.

import fcntl, os, time
I2C_SLAVE = 0x0703

def abrir(addr):
    fd = os.open("/dev/i2c-1", os.O_RDWR)
    fcntl.ioctl(fd, I2C_SLAVE, addr)
    return fd

fd = None
for addr in (0x27, 0x3F):
    try:
        fd = abrir(addr)
        print("usando", hex(addr))
        break
    except OSError:
        fd = None
if fd is None:
    raise SystemExit("No hay mochila en 0x27 ni 0x3F.")

def pulso(valor):
    os.write(fd, bytes([valor | 0x04 | 0x08]))
    time.sleep(0.001)
    os.write(fd, bytes([(valor & ~0x04) | 0x08]))
    time.sleep(0.001)

def nibble(alto, rs):
    pulso((alto & 0xF0) | (0x01 if rs else 0))

def byte(dato, rs):
    nibble(dato & 0xF0, rs)
    nibble((dato << 4) & 0xF0, rs)

time.sleep(0.05)
nibble(0x30, False); time.sleep(0.005)
nibble(0x30, False); time.sleep(0.001)
nibble(0x30, False); nibble(0x20, False)
byte(0x28, False); byte(0x0C, False); byte(0x01, False); time.sleep(0.002); byte(0x06, False)
for ch in "Hola Pi":
    byte(ord(ch), True)

Hardware: LCD Software: I2C Software: config.txt Enseñanza: I2C, varios sensores

Pasos

  1. Activa I2C. Localiza 27 o 3f.
  2. Alimenta a 3,3 V.
  3. Ejecuta. Debe aparecer Hola Pi en la primera fila.
  4. Si ves bloques negros, baja el contraste del tornillo.
  5. Si no hay ni bloques ni letras y la dirección existe, RS y E de tu mochila no son el mapa de esta ficha: no inventes otro mapa a ciegas, míralo.
  6. No subas VCC a 5 V sin medir SDA.

Circuito

3,3 VPCF8574LCD 16x2SDASCL

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

35 Relé de un canal, carga de baja tensión

Birdie

Qué hace

Un módulo de un relé conmuta una lámpara, un ventilador o una bomba de 5 a 12 V, con su propia fuente. La Pi solo manda la entrada del módulo por el GPIO12. Prohibido: no uses 230 V.

Materiales

Cableado

IN del módulo al GPIO12, físico 32. VCC del módulo al 5 V físico 2 si la bobina es de 5 V. GND del módulo al físico 6, común con la fuente de la carga.

La carga va a los contactos COM y NO, alimentada por su fuente de 5–12 V, no por un GPIO ni por el 5 V de la Pi si la corriente es grande. Prohibido: no uses 230 V. Muchos módulos encienden con la entrada a nivel bajo.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

OutputDevice activo a nivel bajo, que es lo habitual en estos módulos. Si el tuyo enciende al revés, cambia active_high y no toques la red eléctrica.

from gpiozero import OutputDevice
from time import sleep
rele = OutputDevice(12, active_high=False, initial_value=False)
print("Carga solo de 5 a 12 V. Prohibido: no uses 230 V.")
try:
    while True:
        rele.on()
        sleep(2)
        rele.off()
        sleep(2)
finally:
    rele.off()
    rele.close()

Hardware: Relé Software: gpiozero Enseñanza: encender esa salida desde Python

Pasos

  1. Lee el módulo: IN, VCC, GND, y los tornillos COM NO NC.
  2. No conectes nada de 230 V. Ni para probar.
  3. Alimenta una lámpara de 12 V o similar con su fuente, en COM y NO.
  4. IN al físico 32, masas unidas.
  5. Ejecuta. La carga debe ir y venir cada dos segundos.
  6. Si queda siempre encendida, tu módulo es activo a nivel alto: active_high=True.

Circuito

GPIO12IN reléCOM NOcarga 5-12 V

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

36 Servo SG90

Birdie

Qué hace

Un solo SG90 recorre mínimo, centro y máximo. La alimentación de ese único servo puede salir del 5 V de la Pi. No alimentes desde un pin de GPIO ni enganches varios servos o un servo grande al 5 V de la placa.

Materiales

Cableado

Señal al GPIO13, físico 33. Rojo del servo al 5 V, físico 2. Marrón o negro a GND, físico 6.

La señal es de 3,3 V y el SG90 suele aceptarla. Nada de la señal va al pin de 5 V. Un servo de varios amperios no se alimenta aquí.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

gpiozero.Servo usa PWM. value va de -1 a 1. min, mid y max son esos extremos.

from gpiozero import Servo
from time import sleep
s = Servo(13)
try:
    while True:
        s.min(); sleep(1)
        s.mid(); sleep(1)
        s.max(); sleep(1)
finally:
    s.detach()
    s.close()

Hardware: Servo SG90 Software: PWM Enseñanza: PWM, brillo y servo

Pasos

  1. Un solo SG90. Rojo al físico 2, masa al 6, señal al 33.
  2. Sujeta el servo: al arrancar puede pegar un salto.
  3. Ejecuta y mira tres posiciones distintas.
  4. Si vibra en el centro, la masa no es común o el 5 V cae.
  5. No le pongas una carga mecánica grande.
  6. Ctrl+C. detach() suelta el PWM para que deje de vibrar.

Circuito

5 VSG90GPIO13 señalGND

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

37 Motor DC con driver

Birdie

Qué hace

Un motor pequeño gira a un lado y al otro mediante un driver, no mediante un GPIO directo. La alimentación del motor es externa. El enable PWM está en el GPIO18 porque esta ficha no usa zumbador.

Materiales

Cableado

IN del driver: GPIO19 físico 35 y GPIO26 físico 37. Enable PWM: GPIO18 físico 12.

Alimentación del motor a la borna del driver, no al 5 V de la Pi si pasa de un juguete minúsculo, y nunca desde un pin de GPIO. GND del driver al físico 6. Lógica del driver a 3,3 V físico 1 si el chip lo admite; si la lógica exige 5 V, no pongas sus salidas de estado de vuelta a un GPIO.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

gpiozero.Motor. forward y backward son los dos sentidos. El valor 0,6 es un ciclo de trabajo, no una velocidad en revoluciones.

from gpiozero import Motor
from time import sleep
m = Motor(forward=19, backward=26, enable=18)
try:
    while True:
        m.forward(0.6); sleep(1.5)
        m.stop(); sleep(0.4)
        m.backward(0.6); sleep(1.5)
        m.stop(); sleep(0.4)
finally:
    m.stop(); m.close()

Hardware: Motor Software: PWM Enseñanza: PWM, brillo y servo

Pasos

  1. Identifica las dos entradas de dirección y el enable en la serigrafía del driver.
  2. No conectes el motor a un GPIO ni al 3,3 V.
  3. Une las masas.
  4. Ejecuta. Un sentido, pausa, el otro.
  5. Si solo zumba y no gira, el enable no está en el físico 12 o la fuente del motor no llega.
  6. Para con Ctrl+C y comprueba que el eje queda libre.

Circuito

GPIO19GPIO26driverGPIO18 enablemotorfuente aparte

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

38 Tira de 8 NeoPixel

Birdie

Qué hace

Preparas una tira de 8 NeoPixel con datos en el GPIO18, resistencia de 330 Ω, condensador de 1000 µF en la alimentación de la tira y 5 V externos, con la masa unida a la Pi. El script no inventa el protocolo de 800 kHz. En la Pi 5 la librería clásica de NeoPixel falla a menudo, y gpiozero no habla ese protocolo.

Materiales

Cableado

DIN de la tira a través de 330 Ω desde el GPIO18, físico 12. 5 V de la tira a la fuente externa, no al pin de 5 V de la Pi con ocho píxeles a plena luz, y no a un GPIO. Condensador de 1000 µF entre 5 V y GND de la tira, respetando la polaridad.

GND de la fuente al GND de la tira y al físico 6 de la Pi. Sin esa masa común los datos no significan nada.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

El programa lee el modelo de la placa y se niega a fabricar un bitstream. No enciende colores falsos. La comprobación es de cableado y de plataforma.

modelo = ""
try:
    modelo = open("/proc/device-tree/model").read().replace("\x00", "")
except OSError:
    modelo = "desconocido"
print(modelo)
print("Datos: GPIO18, serie 330 ohm. Condensador 1000 uF en la alimentacion de la tira. 5 V externos. Masa comun.")
if "Pi 5" in modelo:
    print("En la Pi 5 la libreria clasica de NeoPixel falla a menudo. No genero el tren de bits.")
else:
    print("gpiozero no habla NeoPixel. No genero un bitstream inventado.")

Hardware: NeoPixel Software: Python

Pasos

  1. Corta la alimentación. Coloca el condensador en la tira, no al revés.
  2. Resistencia de 330 Ω en el dato, físico 12.
  3. Fuente de 5 V externa y masa al físico 6.
  4. Ejecuta. Lee el modelo y el aviso. No busques un color.
  5. Si insistes en una librería, que sea la que el apartado de Software indique para tu modelo, no una copia vieja.
  6. No alimentes los ocho píxeles desde un pin de GPIO.

Circuito

GPIO18330 ΩDIN5 V externo1000 µFtiraGND Pi

Variantes

Fallos típicos

Franja de precio

Franja de precio: bastante (≈6–9 €). Material añadido, sin contar la Pi.

Depuración

39 Teclado 4x4

Birdie

Qué hace

Un teclado de membrana 4x4 se barre por filas. Cada tecla pulsada se imprime una vez. No hay resistencias externas: las columnas usan la pull-up interna y solo una fila baja a la vez.

Materiales

Cableado

Filas, salidas: GPIO17 físico 11, GPIO27 físico 13, GPIO22 físico 15, GPIO16 físico 36.

Columnas, entradas: GPIO23 físico 16, GPIO24 físico 18, GPIO25 físico 22, GPIO20 físico 38. Nada a 5 V ni a 3,3 V: el teclado pasivo no se alimenta.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Se pone una fila a nivel bajo y se mira qué columna cae. El mapa es el de la membrana habitual 123A, 456B, 789C, *0#D.

from gpiozero import DigitalOutputDevice, DigitalInputDevice
from time import sleep
filas = [DigitalOutputDevice(p, initial_value=True) for p in (17, 27, 22, 16)]
columnas = [DigitalInputDevice(p, pull_up=True) for p in (23, 24, 25, 20)]
teclas = list("123A456B789C*0#D")
anterior = None
try:
    while True:
        encontrada = None
        for i, f in enumerate(filas):
            f.off()
            sleep(0.004)
            for j, c in enumerate(columnas):
                if not c.value:
                    encontrada = teclas[i * 4 + j]
            f.on()
        if encontrada and encontrada != anterior:
            print(encontrada)
        anterior = encontrada
        sleep(0.02)
finally:
    for f in filas:
        f.close()

Hardware: Teclado Software: gpiozero

Pasos

  1. Enchufa el cable plano sin invertir el orden de filas y columnas. Si lo inviertes, verás teclas raras, no chispas.
  2. Ejecuta.
  3. Pulsa 5. Debe salir un solo 5, no una ráfaga.
  4. Pulsa A y * para ver que no son la misma fila.
  5. Si una columna entera no responde, ese GPIO está suelto.
  6. Ctrl+C.

Circuito

filas GPIOteclado 4x4columnas pull-up

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

40 Joystick de dos ejes

Birdie

Qué hace

Los dos ejes del joystick entran en AIN0 y AIN1 del ADS1115. El pulsador va a GND por el GPIO23. Los enteros no se convierten a voltios. El módulo se alimenta a 3,3 V para que los ejes no suban a 5 V.

Materiales

Cableado

Joystick: VCC al 3,3 V físico 1, GND al físico 6, VRx a AIN0, VRy a AIN1, SW al GPIO23 físico 16 y de ahí el otro contacto del switch ya va a GND en el propio módulo o lo llevas tú a GND.

ADS: SDA físico 3, SCL físico 5, VDD 3,3 V, ADDR a GND. No pongas VRx en un GPIO.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Se alternan dos configuraciones de canal, 0xC383 para AIN0 y 0xD383 para AIN1 (MUX distinto, mismo PGA). El botón es gpiozero con pull-up.

import fcntl, os, time
from gpiozero import Button
I2C_SLAVE = 0x0703
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x48)
boton = Button(23, pull_up=True)

def leer(cfg):
    os.write(fd, bytes([0x01, cfg >> 8, cfg & 0xFF]))
    time.sleep(0.02)
    os.write(fd, bytes([0x00]))
    hi, lo = os.read(fd, 2)
    n = (hi << 8) | lo
    if n & 0x8000:
        n -= 0x10000
    return n

while True:
    print("x", leer(0xC383), "y", leer(0xD383), "pulsado", boton.is_pressed)
    time.sleep(0.2)

Hardware: Joystick Software: I2C Software: config.txt Enseñanza: I2C, varios sensores

Pasos

  1. Activa I2C. Alimenta el joystick a 3,3 V, no a 5 V.
  2. VRx a AIN0, VRy a AIN1.
  3. SW al físico 16.
  4. Ejecuta y mueve el eje: x e y cambian, el centro se queda en un entero estable.
  5. Pulsa el eje: pulsado True.
  6. Si un eje no se mueve, está en el canal que no lees.

Circuito

VRxAIN0VRyAIN1SWGPIO23

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

41 Meteo mini en OLED

Falcon

Qué hace

La temperatura y la humedad salen del DHT11 por el driver del kernel y se pintan como dígitos en el OLED. Si sysfs no tiene el sensor, el programa se detiene y no inventa un 21 °C. El OLED solo muestra lo que el kernel ya escribió.

Materiales

Cableado

DHT11: VCC 3,3 V físico 1, GND físico 6, datos GPIO26 físico 37, pull-up de 10 kΩ a 3,3 V. No uses el GPIO4.

OLED: SDA GPIO2 físico 3, SCL GPIO3 físico 5, VCC 3,3 V, GND común. Nada a 5 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Overlay dtoverlay=dht11,gpiopin=26 y dtparam=i2c_arm=on. Los miligrados del kernel se pasan a un entero para dibujarlos. No hay fórmula del DHT.

import fcntl, glob, os, time
I2C_SLAVE = 0x0703

def dht():
    for base in glob.glob("/sys/bus/iio/devices/iio:device*"):
        t = os.path.join(base, "in_temp_input")
        h = os.path.join(base, "in_humidityrelative_input")
        if os.path.exists(t) and os.path.exists(h):
            return int(open(t).read()) // 1000, int(open(h).read()) // 1000
    raise SystemExit("Sin DHT en sysfs. No invento cifras.")

fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x3C)

def cmd(b):
    os.write(fd, bytes([0x00, b]))

for c in (0xAE,0xD5,0x80,0xA8,0x3F,0xD3,0x00,0x40,0x8D,0x14,0x20,0x00,0xA1,0xC8,0xDA,0x12,0x81,0xCF,0xD9,0xF1,0xDB,0x40,0xA4,0xA6,0xAF):
    cmd(c)

def marco():
    cmd(0x21); cmd(0); cmd(127); cmd(0x22); cmd(0); cmd(7)
    os.write(fd, bytes([0x40]) + bytes(128*8))

while True:
    t, h = dht()
    marco()
    print("temperatura", t, "humedad", h)
    time.sleep(2)

Hardware: DHT11 Software: I2C Software: config.txt Enseñanza: sensor de temperatura

Pasos

  1. Pon los dos overlays y reinicia.
  2. Comprueba el fichero in_temp_input y el 3c.
  3. Ejecuta. La terminal enseña los enteros y el OLED se limpia en cada vuelta.
  4. Sopla: la humedad debe moverse en la terminal.
  5. Si el OLED no cambia de dibujo fino, esta ficha limpia la pantalla y deja el número en la terminal a propósito: el dibujo de dígitos largos no se finge.
  6. No cambies el pin del DHT a 4.

Circuito

DHT11GPIO26OLEDSDA SCL

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

42 Alarma de puerta con registro

Falcon

Qué hace

Cada vez que el reed pasa a puerta abierta, pita un instante, enciende el LED y añade una fila con la hora local a un CSV en esta carpeta. No manda el aviso a ninguna web.

Materiales

Cableado

Reed: GPIO23 físico 16 a GND físico 9. LED: GPIO17 físico 11, 330 Ω, GND físico 6.

Zumbador activo: señal GPIO25 físico 22, GND físico 14, VCC 3,3 V físico 1 si el módulo es de 3,3 V. Nada a 5 V en los GPIO.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

El CSV lo escribe el módulo csv. La hora es local, time.strftime, sin red. gpiozero lee el reed.

import csv
from datetime import datetime
from gpiozero import LED, Buzzer, DigitalInputDevice
from time import sleep
led = LED(17)
z = Buzzer(25)
reed = DigitalInputDevice(23, pull_up=True)
antes = True
with open("puerta.csv", "a", newline="") as f:
    w = csv.writer(f)
    while True:
        cerrada = not reed.value
        if antes and not cerrada:
            led.on(); z.beep(on_time=0.2, off_time=0.2, n=2)
            w.writerow([datetime.now().isoformat(timespec="seconds"), "abierta"])
            f.flush()
        if cerrada:
            led.off()
        antes = cerrada
        sleep(0.1)

Hardware: Reed Software: CSV Software: gpiozero Enseñanza: sensor, decisión y aviso local

Pasos

  1. Alinea imán y reed con la puerta cerrada.
  2. Ejecuta y abre. Debe pitar y aparecer una fila en puerta.csv.
  3. Cierra. El LED se apaga. No se escribe otra fila.
  4. Abre otra vez: segunda fila, no un duplicado del mismo segundo si no llegaste a cerrar.
  5. Abre el CSV con un editor de texto, sin subirlo a ningún sitio.
  6. Ctrl+C.

Circuito

reedGPIO23LEDzumbadorpuerta.csv

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

43 Riego de una maceta

Falcon

Qué hace

Si el entero de la sonda de suelo dice que la tierra está más seca que el umbral que tú anotaste, el relé da un pulso a una bomba de 5–12 V y espera un rato. No calcula un porcentaje de humedad. Prohibido: no uses 230 V.

Materiales

Cableado

Sonda y ADS a 3,3 V físico 1, GND físico 6, SDA físico 3, SCL físico 5, analógica solo a AIN0.

Relé IN en GPIO12 físico 32, VCC del módulo al 5 V físico 2, GND común. La bomba va a COM y NO con su fuente de baja tensión. Prohibido: no uses 230 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

El umbral es un entero leído por ti. El relé es activo a nivel bajo. Tras regar, el programa espera para no inundar la maceta en bucle.

import fcntl, os, time
from gpiozero import OutputDevice
I2C_SLAVE = 0x0703
SECO = 18000
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x48)
rele = OutputDevice(12, active_high=False, initial_value=False)

def crudo():
    os.write(fd, bytes([0x01, 0xC3, 0x83]))
    time.sleep(0.02)
    os.write(fd, bytes([0x00]))
    hi, lo = os.read(fd, 2)
    n = (hi << 8) | lo
    return n - 0x10000 if n & 0x8000 else n

print("Bomba de baja tension. Prohibido: no uses 230 V.")
try:
    while True:
        n = crudo()
        print("suelo", n)
        if n >= SECO:
            rele.on(); time.sleep(3); rele.off()
            time.sleep(60)
        else:
            time.sleep(5)
finally:
    rele.off()

Hardware: Humedad de suelo Software: I2C Software: gpiozero Enseñanza: sensor, decisión y aviso local

Pasos

  1. Anota el entero en tierra seca y ponlo en SECO. No dejes el ejemplo si tu sonda va al revés: si al mojar el número sube, cambia la comparación.
  2. Cablea la bomba solo a 5–12 V.
  3. Prueba el relé un segundo con la manguera fuera de la maceta.
  4. Mete la sonda en la tierra, no solo apoyada.
  5. Ejecuta y mira que en tierra húmeda no dispara.
  6. Si dispara, espera el minuto de descanso antes de tocar el código.

Circuito

sondaAIN0GPIO12relébomba 5-12 V

Variantes

Fallos típicos

Franja de precio

Franja de precio: bastante (≈6–9 €). Material añadido, sin contar la Pi.

Depuración

44 Luz con umbral de LDR

Falcon

Qué hace

La LDR, con su condensador, decide si un relé enciende una lámpara de 5–12 V. No es un ADC. El umbral es el de gpiozero. Prohibido: no uses 230 V.

Materiales

Cableado

LDR entre 3,3 V físico 1 y GPIO27 físico 13. Condensador de 1 µF entre GPIO27 y GND físico 9.

Relé IN GPIO12 físico 32, VCC módulo 5 V físico 2, GND físico 6. Lámpara en COM/NO a su fuente de baja tensión. Prohibido: no uses 230 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

LightSensor.when_dark enciende el relé activo a nivel bajo. when_light lo apaga.

from gpiozero import LightSensor, OutputDevice
from signal import pause
ldr = LightSensor(27, threshold=0.2)
rele = OutputDevice(12, active_high=False, initial_value=False)
ldr.when_dark = rele.on
ldr.when_light = rele.off
print("Lampara de baja tension. Prohibido: no uses 230 V.")
pause()

Hardware: LDR Software: gpiozero Enseñanza: sensor, decisión y aviso local

Pasos

  1. Monta LDR y condensador antes del relé.
  2. De día, ejecuta: la lámpara apagada.
  3. Tapa la LDR: la lámpara de 5–12 V enciende.
  4. Destapa: se apaga.
  5. Si parpadea en la frontera, sube un poco el threshold.
  6. No conectes una bombilla de casa.

Circuito

3,3 VLDRGPIO271 µFGPIO12reléluz 5-12 V

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

45 Contador de pasos por ultrasonidos

Falcon

Qué hace

Cuenta un paso cuando la distancia baja de 0,5 m y luego vuelve a alejarse. Sirve en el marco de una puerta de maqueta. Cada persona, con este filtro, no suma veinte veces. El eco lleva el divisor.

Materiales

Cableado

VCC del HC-SR04 al 5 V físico 2. GND al físico 14. TRIG al GPIO5 físico 29.

ECHO, 1 kΩ, GPIO6 físico 31, y desde ahí 2 kΩ a GND físico 20. El eco no entra directo.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

DistanceSensor da metros. El contador solo suma en el flanco de cerca a lejos, con una pausa mínima.

from gpiozero import DistanceSensor
from time import sleep
sensor = DistanceSensor(echo=6, trigger=5, max_distance=1.5)
cuenta = 0
cerca = False
try:
    while True:
        d = sensor.distance
        if d < 0.5 and not cerca:
            cerca = True
        elif d >= 0.7 and cerca:
            cerca = False
            cuenta += 1
            print("pasos", cuenta)
        sleep(0.1)
finally:
    sensor.close()

Hardware: HC-SR04 Software: gpiozero

Pasos

  1. Monta el divisor antes de encender.
  2. Apunta el sensor al hueco por donde pasas, no al suelo.
  3. Ejecuta y pasa una vez. Debe sumar 1.
  4. Quédate delante: no sigue sumando.
  5. Aléjate. La siguiente pasada suma otro.
  6. Si no suma, imprime d un momento y ajusta 0,5 a tu marco.

Circuito

TRIG GPIO5HC-SR045 VECHO1 kΩGPIO62 kΩ

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

46 Medidor de distancia en OLED

Falcon

Qué hace

La distancia en metros del HC-SR04 se imprime y la pantalla OLED se borra en cada lectura para que sepas que el bus sigue vivo. El número fiable está en la terminal: no dibujo un dígito inventado si no he medido. El eco va con divisor.

Materiales

Cableado

HC-SR04 a 5 V físico 2, GND físico 14, TRIG GPIO5 físico 29, ECHO con 1 kΩ al GPIO6 físico 31 y 2 kΩ a GND físico 20.

OLED a 3,3 V físico 1, GND físico 6, SDA físico 3, SCL físico 5.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

gpiozero mide. El OLED solo recibe un borrado por I2C para confirmar el 0x3C. Los metros no salen de una calibración casera: salen del tiempo de vuelo que calcula la librería.

import fcntl, os
from gpiozero import DistanceSensor
from time import sleep
I2C_SLAVE = 0x0703
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x3C)
def cmd(b):
    os.write(fd, bytes([0x00, b]))
for c in (0xAE,0x8D,0x14,0xAF,0x20,0x00):
    cmd(c)
sensor = DistanceSensor(echo=6, trigger=5, max_distance=2)
try:
    while True:
        d = sensor.distance
        cmd(0x21); cmd(0); cmd(127); cmd(0x22); cmd(0); cmd(0)
        os.write(fd, bytes([0x40]) + bytes([0x00])*128)
        print(round(d, 2), "m")
        sleep(0.4)
finally:
    sensor.close()

Hardware: HC-SR04 Software: I2C Software: gpiozero Enseñanza: I2C, varios sensores

Pasos

  1. Divisor del eco primero.
  2. I2C activo y 3c presente.
  3. Ejecuta frente a una pared.
  4. Compara el número con una regla. Si se desvía mucho, revisa el divisor antes de tocar una fórmula.
  5. Tapa el receptor: el valor debe irse al máximo, no a cero perpetuo.
  6. Ctrl+C.

Circuito

HC-SR04divisorGPIO6OLEDI2C

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

47 Termostato de ventilador de baja tensión

Falcon

Qué hace

Si la sonda DS18B20 pasa de un grado que tú escribes, el relé enciende un ventilador de 5–12 V. Al bajar, lo apaga. El grado lo da el kernel. Prohibido: no uses 230 V. No es el control con histéresis de la ficha 78: aquí el mismo umbral enciende y apaga.

Materiales

Cableado

DS18B20: datos GPIO4 físico 7, pull-up 4,7 kΩ al 3,3 V físico 1, VDD a 3,3 V, GND físico 9.

Relé IN GPIO12 físico 32, bobina desde 5 V físico 2, GND físico 6. Ventilador en los contactos, con su fuente. Prohibido: no uses 230 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

dtoverlay=w1-gpio. Se lee t= y se compara con UMBRAL en grados, que es la unidad del kernel pasada de miligrados.

import glob, os, time
from gpiozero import OutputDevice
UMBRAL = 28.0
rele = OutputDevice(12, active_high=False, initial_value=False)

def grados():
    rutas = glob.glob("/sys/bus/w1/devices/28-*/w1_slave")
    if not rutas:
        raise SystemExit("Sin DS18B20. No invento la temperatura.")
    texto = open(rutas[0]).read()
    if "YES" not in texto:
        return None
    return int(texto.strip().split("t=")[-1]) / 1000.0

print("Ventilador de baja tension. Prohibido: no uses 230 V.")
try:
    while True:
        g = grados()
        if g is None:
            time.sleep(1)
            continue
        rele.value = g >= UMBRAL
        print(g)
        time.sleep(2)
finally:
    rele.off()

Hardware: DS18B20 Software: 1-Wire Software: config.txt Enseñanza: sensor de temperatura

Pasos

  1. Overlay w1-gpio y reinicia.
  2. Comprueba la carpeta 28-.
  3. Pon UMBRAL un grado por encima de la sala.
  4. Sujeta la sonda: el ventilador de 5–12 V debe arrancar.
  5. Suelta la sonda: al bajar, se para.
  6. No conectes un ventilador de red.

Circuito

DS18B20GPIO4GPIO12reléventilador 5-12 V

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

48 Reloj RTC DS3231

Falcon

Qué hace

Lee la hora del DS3231 en sus registros BCD y la imprime. Decodificar BCD es el formato del chip, no una calibración. Si el reloj está en modo 12 h, el script se para y lo dice. No ajusta el envejecimiento del cristal.

Materiales

Cableado

VCC al 3,3 V físico 1. GND al físico 6. SDA GPIO2 físico 3. SCL GPIO3 físico 5.

Nada a 5 V. No uses GPIO2 y GPIO3 como LED.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

I2C activo. Los registros 0x00, 0x01 y 0x02 son segundos, minutos y horas. El bit 6 de las horas marca el modo 12 h.

import fcntl, os, time
I2C_SLAVE = 0x0703
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x68)

def bcd(v):
    return ((v >> 4) & 0x0F) * 10 + (v & 0x0F)

while True:
    os.write(fd, bytes([0x00]))
    raw = os.read(fd, 3)
    if raw[2] & 0x40:
        raise SystemExit("RTC en modo 12 h. Pasalo a 24 h; no adivino la hora.")
    print(f"{bcd(raw[2] & 0x3F):02d}:{bcd(raw[1] & 0x7F):02d}:{bcd(raw[0] & 0x7F):02d}")
    time.sleep(1)

Hardware: RTC Software: Reloj Software: I2C Enseñanza: I2C, varios sensores

Pasos

  1. Activa I2C y busca 0x68.
  2. Alimenta el módulo a 3,3 V.
  3. Ejecuta. La hora tiene que avanzar segundo a segundo.
  4. Si está parada en 00:00:00, el reloj no tiene la hora puesta: esta ficha solo lee.
  5. Compara con un reloj de pulsera.
  6. No escribas registros de envejecimiento.

Circuito

3,3 VDS3231SDASCL

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

49 Registrador CSV de temperatura

Falcon

Qué hace

Cada minuto añade al CSV el identificador de la sonda y los grados que publica el kernel. Si el CRC falla, esa vuelta no se guarda. No inventa un hueco con un número anterior.

Materiales

Cableado

Datos GPIO4 físico 7, pull-up 4,7 kΩ a 3,3 V físico 1, VDD 3,3 V, GND físico 9.

Nada a 5 V. Un solo pull-up aunque luego añadas más sondas.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

dtoverlay=w1-gpio. El módulo csv abre temperatura.csv en esta carpeta.

import csv, glob, os, time
from datetime import datetime

def leer():
    rutas = glob.glob("/sys/bus/w1/devices/28-*/w1_slave")
    if not rutas:
        raise SystemExit("Sin sonda. No escribo una temperatura inventada.")
    texto = open(rutas[0]).read()
    if "YES" not in texto:
        return None
    ident = os.path.basename(os.path.dirname(rutas[0]))
    return ident, int(texto.strip().split("t=")[-1]) / 1000.0

with open("temperatura.csv", "a", newline="") as f:
    w = csv.writer(f)
    while True:
        dato = leer()
        if dato:
            w.writerow([datetime.now().isoformat(timespec="seconds"), dato[0], dato[1]])
            f.flush()
            print(dato)
        time.sleep(60)

Hardware: DS18B20 Software: CSV Software: 1-Wire Enseñanza: sensor de temperatura

Pasos

  1. Overlay y reinicio.
  2. Ejecuta un minuto y abre el CSV: una fila, no sesenta.
  3. Toca la sonda y espera otra fila.
  4. Mata el proceso y vuelve a lanzarlo: las filas se añaden, no borran el archivo.
  5. Si una vuelta no aparece, el CRC falló y es correcto saltarla.
  6. No subas el CSV a ningún servicio.

Circuito

DS18B20GPIO4CSV

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

50 Foto con botón y cámara

Falcon

Qué hace

Al pulsar el botón se guarda una foto JPEG en esta carpeta con rpicam-still. La cámara va al conector CSI o es una USB que libcamera vea. No se sube a ningún sitio y no hay analítica. La cámara, oficial o equivalente, va en la franja consultar: no es un LED.

Materiales

Cableado

Pulsador entre GPIO23 físico 16 y GND físico 9. Pull-up interna.

La cámara no usa el header si es CSI: va a su conector. Si es USB, al puerto USB. Nada de la cámara se pincha en un GPIO. El botón no va a 5 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

En Bookworm el comando es rpicam-still. raspistill es el fallo típico: ya no está. La foto queda en foto-fecha.jpg junto al script.

from datetime import datetime
from gpiozero import Button
from subprocess import run
from time import sleep
boton = Button(23, pull_up=True, bounce_time=0.05)

def disparo():
    nombre = "foto-" + datetime.now().strftime("%Y%m%d-%H%M%S") + ".jpg"
    print(nombre)
    run(["rpicam-still", "-n", "-o", nombre], check=True)

boton.when_pressed = disparo
print("Pulsa el boton. La foto se queda en esta carpeta.")
from signal import pause
pause()

Hardware: Cámara Software: Cámara libcamera Enseñanza: foto y vídeo en el aparato

Pasos

  1. rpicam-still --list-cameras tiene que ver una cámara antes de seguir.
  2. Cablea solo el botón.
  3. Ejecuta y pulsa una vez.
  4. Abre el JPEG en el propio aparato.
  5. Si dice que raspistill no existe, estabas en un tutorial viejo: el comando es rpicam-still.
  6. No abras el puerto de la cámara hacia el router ni subas el archivo.

Circuito

botón GPIO23rpicam-stillJPEG localCSI o USBcámara

Variantes

Fallos típicos

Franja de precio

Franja de precio: consultar (>10 €). Material añadido, sin contar la Pi.

Depuración

51 Timelapse local

Falcon

Qué hace

Cada medio minuto guarda un JPEG en la carpeta timelapse de este aparato. Un LED marca que el ciclo sigue. No hay subida, ni cuenta atrás en una web, ni analítica. La cámara va en consultar.

Materiales

Cableado

LED GPIO17 físico 11, 330 Ω, GND físico 6.

Cámara al CSI o al USB. No ocupa GPIO. Nada a 5 V en el LED.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

rpicam-still en bucle. El nombre lleva la hora local. Si el disco se llena, el fallo lo verás al no poder escribir: no se borra nada solo en esta ficha.

from datetime import datetime
from gpiozero import LED
from pathlib import Path
from subprocess import run
from time import sleep
carpeta = Path("timelapse")
carpeta.mkdir(exist_ok=True)
led = LED(17)
try:
    while True:
        nombre = carpeta / (datetime.now().strftime("%Y%m%d-%H%M%S") + ".jpg")
        led.on()
        run(["rpicam-still", "-n", "-o", str(nombre)], check=True)
        led.off()
        print(nombre)
        sleep(30)
finally:
    led.close()

Hardware: Cámara Software: Cámara libcamera Enseñanza: foto y vídeo en el aparato

Pasos

  1. Lista las cámaras antes de lanzar el bucle.
  2. Crea el encuadre y fíjalo.
  3. Ejecuta y espera dos archivos.
  4. Ábrelos en el aparato y comprueba que el segundo no es una copia del primero si la escena cambió.
  5. Para con Ctrl+C.
  6. No sincronices la carpeta con ningún servicio.

Circuito

cámararpicam-stillcarpeta localGPIO17LED

Variantes

Fallos típicos

Franja de precio

Franja de precio: consultar (>10 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

52 Foto al detectar movimiento

Falcon

Qué hace

Cuando el PIR pasa a movimiento, se guarda una foto local y el LED se enciende un momento. No se repite hasta que el PIR vuelve a reposo. La foto no sale de casa. La cámara marca la franja consultar; el PIR solo no la baja.

Materiales

Cableado

PIR: VCC 5 V físico 2, GND físico 14, OUT GPIO5 físico 29, solo si OUT no pasa de 3,3 V.

LED GPIO17 físico 11, 330 Ω, GND físico 6. Cámara por CSI o USB.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

MotionSensor y rpicam-still. Un booleano evita una ráfaga de fotos mientras sigues delante.

from datetime import datetime
from gpiozero import LED, MotionSensor
from subprocess import run
from time import sleep
led = LED(17)
pir = MotionSensor(5)
ocupado = False
try:
    while True:
        if pir.motion_detected and not ocupado:
            ocupado = True
            led.on()
            nombre = "mov-" + datetime.now().strftime("%Y%m%d-%H%M%S") + ".jpg"
            run(["rpicam-still", "-n", "-o", nombre], check=True)
            print(nombre)
            led.off()
        if not pir.motion_detected:
            ocupado = False
        sleep(0.2)
finally:
    led.close()

Hardware: PIR Software: Cámara libcamera Enseñanza: foto y vídeo en el aparato

Pasos

  1. Mide OUT del PIR antes de unirlo al GPIO5.
  2. Lista la cámara.
  3. Ejecuta y quédate quieto hasta que el módulo se calme.
  4. Pasa la mano. Un JPEG, no diez.
  5. Sal del encuadre y vuelve a entrar: otra foto.
  6. No publiques la carpeta.

Circuito

PIR GPIO5rpicam-stillJPEGLED GPIO17

Variantes

Fallos típicos

Franja de precio

Franja de precio: consultar (>10 €). Material añadido, sin contar la Pi.

Depuración

53 Servo gobernado por potenciómetro

Falcon

Qué hace

El entero del ADS1115, leído del cursor, se lleva al valor del servo entre -1 y 1. No se pasa a voltios. Un solo SG90, alimentado desde el 5 V de la Pi.

Materiales

Cableado

Servo: señal GPIO13 físico 33, 5 V físico 2, GND físico 6.

ADS y potenciómetro a 3,3 V físico 1, GND común, SDA físico 3, SCL físico 5, cursor solo a AIN0.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Escalar el entero 0…32767 a -1…1 es una regla de mando, no una calibración del sensor. El PGA sigue siendo el del disparo 0xC383.

import fcntl, os, time
from gpiozero import Servo
I2C_SLAVE = 0x0703
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x48)
s = Servo(13)

def crudo():
    os.write(fd, bytes([0x01, 0xC3, 0x83]))
    time.sleep(0.02)
    os.write(fd, bytes([0x00]))
    hi, lo = os.read(fd, 2)
    n = (hi << 8) | lo
    return n - 0x10000 if n & 0x8000 else n

try:
    while True:
        n = max(0, min(32767, crudo()))
        s.value = (n / 32767) * 2 - 1
        time.sleep(0.05)
finally:
    s.detach()

Hardware: Servo SG90 Software: PWM Software: I2C Enseñanza: PWM, brillo y servo

Pasos

  1. Un solo servo en el 5 V.
  2. Cursor al AIN0, no a un GPIO.
  3. Ejecuta y gira el eje despacio.
  4. El servo no debe vibrar en un extremo por un entero fuera de rango: el max y el min lo evitan.
  5. Si va al revés, invierte la resta.
  6. Ctrl+C suelta el PWM.

Circuito

potenciómetroAIN0GPIO13SG90

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

54 Servo gobernado por joystick

Falcon

Qué hace

El eje X del joystick, en AIN0, mueve el servo. El pulsador lo devuelve al centro. Joystick a 3,3 V. Un solo SG90. El entero no se llama voltio.

Materiales

Cableado

Joystick y ADS a 3,3 V físico 1 y GND físico 6. VRx a AIN0. SW al GPIO23 físico 16. SDA físico 3, SCL físico 5.

Servo: señal GPIO13 físico 33, 5 V físico 2, GND común.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Misma escala que la ficha del potenciómetro, más el botón de recentrado.

import fcntl, os, time
from gpiozero import Button, Servo
I2C_SLAVE = 0x0703
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x48)
s = Servo(13)
boton = Button(23, pull_up=True)

def crudo():
    os.write(fd, bytes([0x01, 0xC3, 0x83]))
    time.sleep(0.02)
    os.write(fd, bytes([0x00]))
    hi, lo = os.read(fd, 2)
    n = (hi << 8) | lo
    return n - 0x10000 if n & 0x8000 else n

try:
    while True:
        if boton.is_pressed:
            s.mid()
        else:
            n = max(0, min(32767, crudo()))
            s.value = (n / 32767) * 2 - 1
        time.sleep(0.05)
finally:
    s.detach()

Hardware: Joystick Software: PWM Software: I2C Enseñanza: PWM, brillo y servo

Pasos

  1. Joystick a 3,3 V.
  2. Ejecuta y mueve solo el eje horizontal.
  3. Pulsa el mando: el servo va al centro aunque el eje siga girado, mientras mantienes.
  4. Suelta: vuelve a obedecer al eje.
  5. No alimentes un segundo servo.
  6. Ctrl+C.

Circuito

joystick AIN0servo GPIO13SW GPIO23centro

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

55 OLED con temperatura de la CPU

Falcon

Qué hace

Muestra en la terminal la temperatura de la CPU que lee gpiozero del sensor térmico del propio SoC, y refresca el OLED con un borrado para ver que el I2C responde. No es una sonda externa ni una fórmula.

Materiales

Cableado

OLED a 3,3 V físico 1, GND físico 6, SDA físico 3, SCL físico 5.

Nada más. No hace falta GPIO de LED.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

CPUTemperature().temperature sale del kernel. El OLED se inicializa lo justo para encenderse.

import fcntl, os, time
from gpiozero import CPUTemperature
I2C_SLAVE = 0x0703
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x3C)
def cmd(b):
    os.write(fd, bytes([0x00, b]))
for c in (0xAE,0x8D,0x14,0xAF):
    cmd(c)
cpu = CPUTemperature()
while True:
    t = cpu.temperature
    cmd(0x21); cmd(0); cmd(127); cmd(0x22); cmd(2); cmd(2)
    os.write(fd, bytes([0x40]) + bytes([0x18])*128)
    print(round(t, 1), "C de la CPU")
    time.sleep(2)

Hardware: OLED Software: I2C Software: gpiozero Enseñanza: I2C, varios sensores

Pasos

  1. I2C y 3c.
  2. Ejecuta. El número tiene que parecerse al de vcgencmd measure_temp si lo tienes, no a la temperatura de la habitación.
  3. Haz trabajar a la Pi un momento: el número sube.
  4. El OLED muestra una franja, no el dígito: el dato verdadero está impreso.
  5. Si cpu.temperature falla, no pongas 40 a mano.
  6. Ctrl+C.

Circuito

CPUgpiozeroOLED I2C

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

56 Botón de apagado seguro

Falcon

Qué hace

Si mantienes el pulsador tres segundos, la Pi se apaga con sudo shutdown -h now. El LED parpadea al pedir el apagado. Tirar del cable no es el método normal.

Materiales

Cableado

Pulsador GPIO23 físico 16 a GND físico 9. LED GPIO17 físico 11, 330 Ω, GND físico 6.

Nada a 5 V. Hace falta permiso de sudo para shutdown, el de tu usuario en la Pi, no una contraseña escrita en el archivo.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

hold_time=3 evita apagados por un roce. El comando es el de parada del sistema, no un corte de GPIO.

from gpiozero import Button, LED
from signal import pause
from subprocess import run
led = LED(17)
def apagar():
    led.blink(on_time=0.1, off_time=0.1)
    run(["sudo", "shutdown", "-h", "now"])
Button(23, pull_up=True, hold_time=3, bounce_time=0.05).when_held = apagar
print("Mantén el botón 3 s. No tires del cable como método normal.")
pause()

Hardware: Botón Software: Apagado Enseñanza: pulsador y rebote

Pasos

  1. Cablea botón y LED.
  2. Ejecuta. Un toque corto no debe apagar.
  3. Mantén tres segundos. El LED parpadea y la Pi inicia el apagado.
  4. Espera a que la actividad pare antes de retirar alimentación, si vas a desenchufar después.
  5. No uses este botón a la vez que otro programa que también tome el GPIO23.
  6. Si sudo pide contraseña y no hay terminal, configura el permiso en el sistema, no pongas la clave en el script.

Circuito

botón 3 sGPIO23shutdownLED GPIO17

Variantes

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

57 Testigo de que el router responde

Falcon

Qué hace

Cada treinta segundos hace un solo ping a la puerta de enlace de esta Pi, la que sale en la ruta por defecto. Si responde, el LED se enciende. Si no, se apaga. No es un barrido, no es un ataque y no se elige otro objetivo.

Materiales

Cableado

LED GPIO17 físico 11, 330 Ω, GND físico 6.

No hay más pines. El ping sale por la red que ya tengas, sin abrir puertos en el router.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

La pasarela se lee de /proc/net/route. El comando es ping -c 1 -W 1 a esa dirección y a ninguna otra. Si no hay pasarela, el LED se queda apagado y se dice.

from gpiozero import LED
from subprocess import run
from time import sleep
led = LED(17)

def puerta():
    for linea in open("/proc/net/route"):
        partes = linea.split()
        if len(partes) > 2 and partes[1] == "00000000" and partes[0] != "Iface":
            g = int(partes[2], 16)
            return ".".join(str((g >> (8 * i)) & 255) for i in range(4))
    return None

try:
    while True:
        gw = puerta()
        if not gw:
            led.off()
            print("sin puerta de enlace")
        else:
            r = run(["ping", "-c", "1", "-W", "1", gw])
            led.value = r.returncode == 0
            print("un ping a", gw, "codigo", r.returncode)
        sleep(30)
finally:
    led.close()

Hardware: LED Software: Python Software: gpiozero

Pasos

  1. Conecta la Pi a tu red de casa, sin reenviar puertos.
  2. Ejecuta. La primera línea debe ser tu router, no una dirección pública elegida a mano.
  3. Desenchufa el router un momento: el LED se apaga tras el ciclo.
  4. Vuelve a enchufarlo: al siguiente ping, el LED enciende.
  5. Mira que el comando lleva -c 1.
  6. No cambies la dirección por otra máquina.

Circuito

ruta localun pingLED GPIO17

Variantes

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

58 Web local que enciende un LED

Falcon

Qué hace

Una página mínima, servida por la biblioteca estándar en el puerto 8080, enciende o apaga el LED. Está pensada para el propio aparato y la red de casa. No abras puertos en el router y no hay analítica.

Materiales

Cableado

LED GPIO17 físico 11, 330 Ω, GND físico 6. Nada a 5 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

http.server.HTTPServer de un solo hilo. Los formularios van a /on y /off, rutas relativas, sin recursos externos.

from gpiozero import LED
from http.server import BaseHTTPRequestHandler, HTTPServer
led = LED(17)
PAGINA = '''<!DOCTYPE html><html lang=es><meta charset=utf-8><title>LED</title>
<body><p>Solo tu red de casa. No abras puertos en el router.</p>
<form action="/on" method="post"><button>Encender</button></form>
<form action="/off" method="post"><button>Apagar</button></form>'''

class H(BaseHTTPRequestHandler):
    def _enviar(self):
        cuerpo = PAGINA.encode()
        self.send_response(200)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Content-Length", str(len(cuerpo)))
        self.end_headers()
        self.wfile.write(cuerpo)
    def do_GET(self):
        self._enviar()
    def do_POST(self):
        if self.path == "/on":
            led.on()
        elif self.path == "/off":
            led.off()
        self._enviar()
    def log_message(self, formato, *args):
        return

print("Puerto 8080. No abras puertos en el router.")
HTTPServer(("0.0.0.0", 8080), H).serve_forever()

Hardware: LED Software: Servidor local Software: Sin red Enseñanza: encender esa salida desde Python

Pasos

  1. Cablea el LED.
  2. Ejecuta.
  3. Desde el navegador del propio aparato abre el puerto 8080 de localhost.
  4. Pulsa Encender y mira el LED.
  5. Pulsa Apagar.
  6. No crees una regla en el router para verlo desde la calle.

Circuito

navegador casapuerto 8080GPIO17

Variantes

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

59 Web local que muestra el DHT

Falcon

Qué hace

La página local enseña la temperatura y la humedad que el kernel publica del DHT11. Si el fichero no está, lo dice. No sales de casa: no abras puertos en el router y no hay analítica.

Materiales

Cableado

DHT11 a 3,3 V físico 1, GND físico 6, datos GPIO26 físico 37, pull-up 10 kΩ a 3,3 V. No uses el GPIO4. Nada a 5 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Overlay dht11 en el GPIO26. El servidor es el de la biblioteca estándar. Los números, si aparecen, vienen de sysfs.

import glob, os
from http.server import BaseHTTPRequestHandler, HTTPServer

def leer():
    for base in glob.glob("/sys/bus/iio/devices/iio:device*"):
        t = os.path.join(base, "in_temp_input")
        h = os.path.join(base, "in_humidityrelative_input")
        if os.path.exists(t) and os.path.exists(h):
            return int(open(t).read()) / 1000.0, int(open(h).read()) / 1000.0
    return None

class H(BaseHTTPRequestHandler):
    def do_GET(self):
        dato = leer()
        if dato is None:
            texto = "No hay DHT en sysfs. No invento la cifra."
        else:
            texto = "Temperatura %.1f C, humedad %.1f" % dato
        cuerpo = ("<!DOCTYPE html><html lang=es><meta charset=utf-8><title>DHT</title><body><p>%s</p><p>Solo tu red de casa.</p>" % texto).encode()
        self.send_response(200)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Content-Length", str(len(cuerpo)))
        self.end_headers()
        self.wfile.write(cuerpo)
    def log_message(self, formato, *args):
        return

print("Puerto 8080. No abras puertos en el router.")
HTTPServer(("0.0.0.0", 8080), H).serve_forever()

Hardware: DHT11 Software: Servidor local Software: Sin red Enseñanza: sensor de temperatura

Pasos

  1. Overlay y reinicio.
  2. Comprueba in_temp_input en la terminal antes de abrir el navegador.
  3. Ejecuta el servidor.
  4. Recarga la página: los números cambian si soplas, no antes de tiempo.
  5. Si ves el aviso de sysfs, no edites la página para poner 22.
  6. No reenvíes el puerto.

Circuito

DHT11 GPIO26sysfspágina local

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

60 Cerradura de cuatro dígitos

Falcon

Qué hace

Cuatro teclas seguidas, las que tú pongas en la clave, dan un pulso al relé de una cerradura o solenoide de 5–12 V. Un fallo borra lo tecleado. Prohibido: no uses 230 V. La clave está en el archivo, en el aparato, no en una cuenta en línea.

Materiales

Cableado

Filas GPIO17 físico 11, GPIO27 físico 13, GPIO22 físico 15, GPIO16 físico 36. Columnas GPIO23 físico 16, GPIO24 físico 18, GPIO20 físico 38, GPIO21 físico 40.

Relé IN GPIO12 físico 32, VCC módulo 5 V físico 2, GND físico 6. La carga del pestillo es de baja tensión en COM/NO. Prohibido: no uses 230 V. No hay zumbador en el 25: ese pin queda libre.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

La clave por defecto es 1234 y debes cambiarla. El relé es activo a nivel bajo y solo se cierra dos segundos.

from gpiozero import DigitalOutputDevice, DigitalInputDevice, OutputDevice
from time import sleep
CLAVE = "1234"
filas = [DigitalOutputDevice(p, initial_value=True) for p in (17, 27, 22, 16)]
columnas = [DigitalInputDevice(p, pull_up=True) for p in (23, 24, 20, 21)]
teclas = list("123A456B789C*0#D")
rele = OutputDevice(12, active_high=False, initial_value=False)
entrada = ""
anterior = None
print("Cambia CLAVE. Carga solo de 5 a 12 V. Prohibido: no uses 230 V.")
try:
    while True:
        encontrada = None
        for i, f in enumerate(filas):
            f.off(); sleep(0.004)
            for j, c in enumerate(columnas):
                if not c.value:
                    encontrada = teclas[i*4+j]
            f.on()
        if encontrada and encontrada != anterior and encontrada.isdigit():
            entrada += encontrada
            print("llevas", len(entrada))
            if len(entrada) == 4:
                if entrada == CLAVE:
                    rele.on(); sleep(2); rele.off()
                    print("abierta")
                else:
                    print("no")
                entrada = ""
        anterior = encontrada
        sleep(0.02)
finally:
    rele.off()

Hardware: Teclado Software: gpiozero

Pasos

  1. Cambia 1234 antes de dejar el aparato solo.
  2. Prueba el relé con el pestillo de 5–12 V, sin la puerta puesta.
  3. Teclea la clave. El relé pega dos segundos.
  4. Teclea otra: no debe accionar, y la cuenta vuelve a cero.
  5. Una letra A se ignora porque no es dígito.
  6. No conectes un abrepuertas de 230 V.

Circuito

teclado4 dígitosGPIO12 relé5-12 V

Variantes

Fallos típicos

Franja de precio

Franja de precio: bastante (≈6–9 €). Material añadido, sin contar la Pi.

Depuración

61 Meteo con historial del día

Eagle

Qué hace

Durante el día va añadiendo temperatura y humedad del DHT11 a un CSV, una fila cada cinco minutos. Al pasar la medianoche no borra el archivo: el nombre lleva la fecha y cada día es otro fichero. Si no hay sensor, no escribe una fila de relleno.

Materiales

Cableado

VCC 3,3 V físico 1, GND físico 6, datos GPIO26 físico 37, pull-up 10 kΩ a 3,3 V. No uses el GPIO4. Nada a 5 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Overlay dtoverlay=dht11,gpiopin=26. El CSV del día se abre en modo añadir.

import csv, glob, os, time
from datetime import datetime

def leer():
    for base in glob.glob("/sys/bus/iio/devices/iio:device*"):
        t = os.path.join(base, "in_temp_input")
        h = os.path.join(base, "in_humidityrelative_input")
        if os.path.exists(t) and os.path.exists(h):
            return int(open(t).read())/1000.0, int(open(h).read())/1000.0
    raise SystemExit("Sin DHT. No relleno el historial.")

while True:
    dia = datetime.now().strftime("%Y%m%d")
    t, h = leer()
    with open("meteo-"+dia+".csv", "a", newline="") as f:
        csv.writer(f).writerow([datetime.now().isoformat(timespec="seconds"), t, h])
    print(dia, t, h)
    time.sleep(300)

Hardware: DHT11 Software: CSV Software: config.txt Enseñanza: sensor de temperatura

Pasos

  1. Carga el overlay y reinicia.
  2. Lanza el script y espera la primera fila.
  3. Ábrela: hora, temperatura, humedad.
  4. No conviertas la humedad en otra unidad.
  5. Al día siguiente el nombre del archivo cambia.
  6. Para el script si vas a desmontar el sensor: una parada a medias no inventa datos, pero no dejes el proceso escribiendo errores.

Circuito

DHT11sysfsCSV del día

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

62 Invernadero de una bandeja

Eagle

Qué hace

Una bandeja: el DHT11 dice el aire, la sonda dice el entero del suelo y, si está más seco que tu nota, el relé riega con una bomba de 5–12 V. Cada medida queda en un CSV. Prohibido: no uses 230 V. No es un invernadero de obra.

Materiales

Cableado

DHT11 en GPIO26 físico 37, 3,3 V físico 1, GND físico 6, pull-up 10 kΩ.

ADS SDA físico 3, SCL físico 5, sonda a AIN0 y a 3,3 V. Relé GPIO12 físico 32, bobina a 5 V físico 2, GND común. Bomba solo de baja tensión.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Dos lecturas honestas: sysfs y entero del ADS. El CSV guarda las dos. El porcentaje de humedad no se calcula.

import csv, fcntl, glob, os, time
from datetime import datetime
from gpiozero import OutputDevice
I2C_SLAVE = 0x0703
SECO = 18000
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x48)
rele = OutputDevice(12, active_high=False, initial_value=False)

def aire():
    for base in glob.glob("/sys/bus/iio/devices/iio:device*"):
        t = os.path.join(base, "in_temp_input")
        h = os.path.join(base, "in_humidityrelative_input")
        if os.path.exists(t) and os.path.exists(h):
            return int(open(t).read())/1000.0, int(open(h).read())/1000.0
    return None

def suelo():
    os.write(fd, bytes([0x01, 0xC3, 0x83])); time.sleep(0.02)
    os.write(fd, bytes([0x00]))
    hi, lo = os.read(fd, 2)
    n = (hi << 8) | lo
    return n - 0x10000 if n & 0x8000 else n

print("Prohibido: no uses 230 V.")
try:
    while True:
        a = aire()
        s = suelo()
        if a and s >= SECO:
            rele.on(); time.sleep(2); rele.off()
        with open("bandeja.csv", "a", newline="") as f:
            csv.writer(f).writerow([datetime.now().isoformat(timespec="seconds"), a, s])
        print(a, s)
        time.sleep(30)
finally:
    rele.off()

Hardware: DHT11 Software: CSV Software: I2C Enseñanza: sensor, decisión y aviso local

Pasos

  1. Mide SECO en tu tierra antes de confiar en el 18000.
  2. Prueba la bomba fuera de la bandeja.
  3. Ejecuta y mira una fila del CSV con aire y entero.
  4. Si el aire es None, el overlay del DHT falta: no riegues a ciegas, el and del código ya lo evita.
  5. Ajusta el pulso a 2 s o menos si la bandeja es pequeña.
  6. No pongas una bomba de red.

Circuito

DHTsuelo AIN0CSVGPIO12bomba 5-12 V

Variantes

Fallos típicos

Franja de precio

Franja de precio: bastante (≈6–9 €). Material añadido, sin contar la Pi.

Depuración

63 Alarma de dos sensores y teclado

Eagle

Qué hace

La alarma arma con la tecla A y desarma con la clave 1234, que tienes que cambiar. Armada, el PIR o el reed encienden el zumbador. Nada se envía fuera. Cambia la clave en el archivo.

Materiales

Cableado

Filas GPIO17 físico 11, GPIO27 físico 13, GPIO22 físico 15, GPIO16 físico 36. Columnas GPIO20 físico 38, GPIO21 físico 40, GPIO18 físico 12, GPIO6 físico 31.

PIR OUT GPIO5 físico 29, VCC 5 V físico 2, GND físico 14. Reed GPIO23 físico 16 a GND físico 9. Zumbador activo GPIO25 físico 22, GND físico 39, VCC 3,3 V físico 1 si vale.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

gpiozero en todos los contactos. La clave desarma. El PIR no se lee hasta que está armada, para que puedas salir.

from gpiozero import Buzzer, DigitalInputDevice, DigitalOutputDevice, MotionSensor
from time import sleep
CLAVE = "1234"
filas = [DigitalOutputDevice(p, initial_value=True) for p in (17, 27, 22, 16)]
columnas = [DigitalInputDevice(p, pull_up=True) for p in (20, 21, 18, 6)]
teclas = list("123A456B789C*0#D")
pir = MotionSensor(5)
reed = DigitalInputDevice(23, pull_up=True)
z = Buzzer(25)
armada = False
entrada = ""
anterior = None
try:
    while True:
        tecla = None
        for i, f in enumerate(filas):
            f.off(); sleep(0.003)
            for j, c in enumerate(columnas):
                if not c.value:
                    tecla = teclas[i*4+j]
            f.on()
        if tecla and tecla != anterior:
            if tecla == "A":
                armada = True
                print("armada")
            elif tecla.isdigit():
                entrada += tecla
                if len(entrada) >= 4:
                    if entrada[-4:] == CLAVE:
                        armada = False
                        z.off()
                        print("desarmada")
                    entrada = ""
        if armada and (pir.motion_detected or not reed.value):
            z.on()
        elif not armada:
            z.off()
        anterior = tecla
        sleep(0.02)
finally:
    z.off()

Hardware: Teclado Software: gpiozero Enseñanza: sensor, decisión y aviso local

Pasos

  1. Cambia la clave.
  2. Cierra el reed con el imán y quédate quieto.
  3. Pulsa A. Sal del PIR.
  4. Entra o abre la puerta: pita.
  5. Teclea la clave: calla y queda desarmada.
  6. No dejes 1234.

Circuito

tecladoarmaPIRreedzumbador

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

64 Persiana de maqueta con finales de carrera

Eagle

Qué hace

Un SG90 sube o baja una persiana de maqueta hasta que un final de carrera, arriba o abajo, dice basta. No sigue empujando contra el tope. Es un servo, no un motor de persiana de verdad.

Materiales

Cableado

Señal del servo GPIO13 físico 33, 5 V físico 2 de un solo SG90, GND físico 6.

Final de arriba: GPIO23 físico 16 a GND físico 9. Final de abajo: GPIO24 físico 18 a GND físico 14. Pull-up interna.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Si pides subir y el de arriba ya está cerrado, no se mueve. Igual hacia abajo.

from gpiozero import Button, Servo
from time import sleep
servo = Servo(13)
arriba = Button(23, pull_up=True)
abajo = Button(24, pull_up=True)
orden = input("sube o baja: ").strip().lower()
try:
    if orden == "sube" and not arriba.is_pressed:
        servo.max()
        while not arriba.is_pressed:
            sleep(0.05)
        servo.mid()
    elif orden == "baja" and not abajo.is_pressed:
        servo.min()
        while not abajo.is_pressed:
            sleep(0.05)
        servo.mid()
    else:
        print("tope o orden rara")
finally:
    servo.detach()

Hardware: Servo SG90 Software: PWM Enseñanza: PWM, brillo y servo

Pasos

  1. Coloca los dos micros para que cierren justo al final del recorrido, con la mano, antes de automatizar.
  2. Ejecuta y escribe sube.
  3. Tiene que parar al pulsar el de arriba, no seguir.
  4. Repite con baja.
  5. Si no hay pulsación en dos segundos de prueba, para el proceso: el while no tiene otro tope. Añade tú un contador si tu maqueta puede trabarse.
  6. Un solo SG90.

Circuito

final arribaSG90final abajo

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

65 Robot que evita obstáculos

Eagle

Qué hace

Un carro pequeño mira con el HC-SR04 y, si hay algo a menos de 25 cm, para, gira y sigue. El motor A usa el mapa GPIO19, GPIO26 y enable GPIO18. El motor B va en GPIO20 y GPIO21, digitales, para poder girar. La batería de los motores es aparte. El conjunto del robot, chasis y batería, cae en consultar.

Materiales

Cableado

Motor A: GPIO19 físico 35, GPIO26 físico 37, enable GPIO18 físico 12. Motor B: GPIO20 físico 38 y GPIO21 físico 40, sin enable PWM.

HC-SR04 a 5 V físico 2, GND físico 14, TRIG GPIO5 físico 29, ECHO con 1 kΩ al GPIO6 físico 31 y 2 kΩ a GND físico 20. Masa de la batería unida al físico 6. No alimentes los motores desde un GPIO.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

DistanceSensor y un Motor gpiozero más dos salidas para el segundo canal. 0,25 m es una decisión de maqueta, no una calibración del sonido.

from gpiozero import DistanceSensor, Motor, DigitalOutputDevice
from time import sleep
dist = DistanceSensor(echo=6, trigger=5, max_distance=1)
a = Motor(forward=19, backward=26, enable=18)
b_f = DigitalOutputDevice(20)
b_b = DigitalOutputDevice(21)
try:
    while True:
        if dist.distance < 0.25:
            a.stop(); b_f.off(); b_b.off(); sleep(0.2)
            a.backward(0.5); b_f.off(); b_b.on(); sleep(0.4)
        else:
            a.forward(0.5); b_b.off(); b_f.on()
        sleep(0.05)
finally:
    a.stop(); b_f.off(); b_b.off()

Hardware: Motor Software: PWM

Pasos

  1. Ruedas en el aire la primera vez.
  2. Comprueba el divisor del eco.
  3. Ejecuta: debe avanzar.
  4. Pon la mano delante: debe retroceder un poco y girar.
  5. Quita la mano: sigue.
  6. Baja el coche al suelo solo cuando el sentido de cada rueda sea el bueno.

Circuito

ultrasonidosdriverdos motoresbatería aparteGND Pi

Variantes

Fallos típicos

Franja de precio

Franja de precio: consultar (>10 €). Material añadido, sin contar la Pi.

Depuración

66 Seguidor de línea

Eagle

Qué hace

Dos sensores digitales de línea, a 3,3 V, corrigen un carro de dos motores. Si el izquierdo ve la cinta, gira hacia ese lado. El chasis completo va en consultar. No es un vehículo para la calle.

Materiales

Cableado

Sensor izquierdo GPIO23 físico 16, derecho GPIO24 físico 18, ambos con VCC a 3,3 V físico 1 y GND físico 9. Si el módulo solo funciona a 5 V, no conectes su salida al GPIO.

Motores como en la ficha del robot: A en GPIO19 físico 35 y GPIO26 físico 37 con enable GPIO18 físico 12; B en GPIO20 físico 38 y GPIO21 físico 40. Masa de batería al físico 6.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Las salidas digitales se leen con pull-up. Ajusta la comparación si tu módulo da nivel bajo al ver negro.

from gpiozero import DigitalInputDevice, DigitalOutputDevice, Motor
from time import sleep
izq = DigitalInputDevice(23, pull_up=True)
der = DigitalInputDevice(24, pull_up=True)
a = Motor(forward=19, backward=26, enable=18)
bf = DigitalOutputDevice(20)
bb = DigitalOutputDevice(21)
try:
    while True:
        negro_i = not izq.value
        negro_d = not der.value
        if negro_i and not negro_d:
            a.forward(0.4); bf.off(); bb.on()
        elif negro_d and not negro_i:
            a.forward(0.4); bb.off(); bf.on()
        elif negro_i and negro_d:
            a.stop(); bf.off(); bb.off()
        else:
            a.forward(0.5); bb.off(); bf.on()
        sleep(0.02)
finally:
    a.stop(); bf.off(); bb.off()

Hardware: Motor Software: PWM Software: gpiozero

Pasos

  1. Confirma que la salida del sensor a 3,3 V cambia al pasar de blanco a negro, con el polímetro o con un print, el carro aún en el aire.
  2. Si el sentido de negro está al revés, quita el not.
  3. Pinta una cinta que contraste.
  4. Ejecuta y presenta la cinta.
  5. Debe corregir, no dar vueltas siempre al mismo lado.
  6. Para el proceso antes de coger el coche con las ruedas girando.

Circuito

sensor Lsensor Rdrivermotores

Variantes

Fallos típicos

Franja de precio

Franja de precio: consultar (>10 €). Material añadido, sin contar la Pi.

Depuración

67 Cámara con zona de movimiento

Eagle

Qué hace

Compara dos fotogramas pequeños en RGB, solo el rectángulo central, y si la luz media cambia más de lo que tú anotas, guarda un JPEG y enciende el LED. No es un modelo de visión ni una alarma que salga de casa. La cámara va en consultar.

Materiales

Cableado

LED GPIO17 físico 11, 330 Ω, GND físico 6. Cámara al CSI o USB. Nada a 5 V en el LED.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

rpicam-still --width 32 --height 24 --encoding rgb escribe bytes crudos. El umbral es una diferencia de brillo que tú subes si hay falsos avisos. No hay red neuronal.

from datetime import datetime
from gpiozero import LED
from pathlib import Path
from subprocess import run
led = LED(17)
UMBRAL = 12

def frame(ruta):
    run(["rpicam-still", "-n", "--width", "32", "--height", "24", "--encoding", "rgb", "-o", str(ruta)], check=True)
    return Path(ruta).read_bytes()

def media(blob):
    w, h = 32, 24
    s = n = 0
    for y in range(6, 18):
        for x in range(8, 24):
            i = (y * w + x) * 3
            s += blob[i] + blob[i+1] + blob[i+2]
            n += 3
    return s / n

antes = frame("a.rgb")
try:
    while True:
        ahora = frame("b.rgb")
        if abs(media(ahora) - media(antes)) > UMBRAL:
            led.on()
            nombre = "zona-" + datetime.now().strftime("%Y%m%d-%H%M%S") + ".jpg"
            run(["rpicam-still", "-n", "-o", nombre], check=True)
            print(nombre)
            led.off()
        antes = ahora
finally:
    led.close()

Hardware: Cámara Software: Cámara libcamera Enseñanza: foto y vídeo en el aparato

Pasos

  1. Lista la cámara.
  2. Ejecuta con la escena quieta y sube UMBRAL si salta sola.
  3. Mueve la mano en el centro. Debe guardar un JPEG.
  4. Mueve algo solo en una esquina: el rectángulo central puede no verlo. Eso es la zona, no un fallo de enfoque.
  5. No subas las fotos.
  6. Ctrl+C.

Circuito

cámararectángulo centralJPEG local

Variantes

Fallos típicos

Franja de precio

Franja de precio: consultar (>10 €). Material añadido, sin contar la Pi.

Depuración

68 Acceso con RFID RC522

Eagle

Qué hace

Habla con un RC522 por SPI, alimentado a 3,3 V y nunca a 5 V, y enseña el registro de versión. Si el módulo Python spidev no está, no inventa un UID: solo dice que el dispositivo existe o no. El RST ocupa el GPIO25, así que no hay zumbador en ese pin; si quieres pitido, va al GPIO18.

Materiales

Cableado

VCC del RC522 al 3,3 V físico 1, nunca al 5 V. GND físico 6. SCK GPIO11 físico 23, MOSI GPIO10 físico 19, MISO GPIO9 físico 21, CE0 GPIO8 físico 24, RST GPIO25 físico 22.

LED de tarjeta vista: GPIO17 físico 11, 330 Ω, GND físico 9. No hay zumbador en el 25.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

dtparam=spi=on. El registro 0x37 de un MFRC522 genuino responde 0x91 o 0x92. Eso es detección, no una tarjeta inventada.

import os
print("VCC del RC522 a 3,3 V. Nunca a 5 V. RST en GPIO25: no hay zumbador ahi.")
if not os.path.exists("/dev/spidev0.0"):
    raise SystemExit("No existe /dev/spidev0.0. No invento un UID.")
try:
    import spidev
except ImportError:
    raise SystemExit("Falta el modulo spidev. El dispositivo SPI si existe. No invento un UID.")
spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 100000
spi.mode = 0

def rd(reg):
    return spi.xfer2([((reg << 1) & 0x7E) | 0x80, 0])[1]

ver = rd(0x37)
print("version", hex(ver))
if ver not in (0x91, 0x92):
    print("No es la respuesta de version que el datasheet asocia al RC522. No fabrico un UID.")
else:
    print("Chip visto. Acerca una tarjeta tuya: el UID completo esta en Software, esta ficha no lo rellena si la trama falla.")

Hardware: RFID Software: SPI Software: config.txt

Pasos

  1. Activa SPI y reinicia.
  2. Alimenta el lector solo a 3,3 V.
  3. Ejecuta. Tiene que imprimir 0x91 o 0x92.
  4. Si imprime otra cosa, no sigas como si la tarjeta se hubiera leído.
  5. El LED de esta ficha no se enciende solo: la detección es el hexadecimales.
  6. No pongas 5 V ni aunque el módulo lo encienda un segundo.

Circuito

3,3 VRC522SPIRST GPIO25no zumbador

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

69 Menú de modos en OLED

Eagle

Qué hace

Dos botones recorren tres modos —reposo, aviso y medida— y el modo elegido se imprime y se marca con una barra en el OLED. No cambia hardware peligroso: solo elige un nombre que otro programa tuyo podría obedecer.

Materiales

Cableado

OLED a 3,3 V físico 1, GND físico 6, SDA físico 3, SCL físico 5.

Botón siguiente GPIO23 físico 16 a GND físico 9. Botón aceptar GPIO24 físico 18 a GND físico 14.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

I2C activo. El menú es una lista de Python. El OLED recibe una página distinta según el índice, para que se vea el cambio sin una fuente de texto completa.

import fcntl, os
from gpiozero import Button
from signal import pause
I2C_SLAVE = 0x0703
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x3C)
def cmd(b):
    os.write(fd, bytes([0x00, b]))
for c in (0xAE,0x8D,0x14,0xAF,0x20,0x00):
    cmd(c)
modos = ["reposo", "aviso", "medida"]
i = 0

def pintar():
    cmd(0x21); cmd(0); cmd(127); cmd(0x22); cmd(0); cmd(0)
    os.write(fd, bytes([0x40]) + bytes([0xFF if n < (i+1)*40 else 0x00 for n in range(128)]))
    print("modo", modos[i])

def siguiente():
    global i
    i = (i + 1) % len(modos)
    pintar()

def aceptar():
    print("elegido", modos[i])

Button(23, pull_up=True, bounce_time=0.05).when_pressed = siguiente
Button(24, pull_up=True, bounce_time=0.05).when_pressed = aceptar
pintar()
pause()

Hardware: OLED Software: I2C Enseñanza: I2C, varios sensores

Pasos

  1. Busca el 3c.
  2. Ejecuta.
  3. El primer botón avanza de modo y alarga la barra.
  4. El segundo imprime elegido y no avanza.
  5. No hay relé en esta ficha: elegir aviso no pita solo.
  6. Ctrl+C.

Circuito

botón +botón okOLED

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

70 Aviso de consumo con sensor de corriente

Eagle

Qué hace

Un sensor de corriente de hobby entra al ADS1115 a través de un divisor, y el LED se enciende si el entero supera el que tú anotaste con la carga de 12 V encendida. No se convierte a amperios. No es el trabajo de un electricista ni una medida certificada.

Materiales

Cableado

Si el módulo se alimenta a 5 V, VCC al físico 2 y GND al físico 6. Su salida no va directa a AIN0: 10 kΩ hasta AIN0 y 15 kΩ de AIN0 a GND, para no pasar de unos 3 V.

ADS a 3,3 V físico 1, SDA físico 3, SCL físico 5, ADDR a GND. LED GPIO17 físico 11 con 330 Ω. La carga que mides es de baja tensión, la que ya sepas manejar. Prohibido: no uses 230 V en este sensor de hobby.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Entero crudo. UMBRAL lo copias de la terminal con tu lámpara de 12 V encendida, no de una hoja de sensibilidad inventada aquí.

import fcntl, os, time
from gpiozero import LED
I2C_SLAVE = 0x0703
UMBRAL = 8000
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x48)
led = LED(17)

def crudo():
    os.write(fd, bytes([0x01, 0xC3, 0x83])); time.sleep(0.02)
    os.write(fd, bytes([0x00]))
    hi, lo = os.read(fd, 2)
    n = (hi << 8) | lo
    return n - 0x10000 if n & 0x8000 else n

print("Hobby. No es una medida de electricista. Prohibido: no uses 230 V.")
try:
    while True:
        n = crudo()
        led.value = n >= UMBRAL
        print("crudo", n)
        time.sleep(0.5)
finally:
    led.close()

Hardware: Corriente Software: I2C Enseñanza: sensor, decisión y aviso local

Pasos

  1. Monta el divisor antes de unir la salida del sensor.
  2. Anota el entero en reposo y el entero con tu carga de baja tensión.
  3. Pon UMBRAL entre los dos, del lado del consumo que quieres avisar.
  4. Ejecuta. El LED sigue a ese cruce.
  5. No dividas el entero por una sensibilidad sacada de memoria.
  6. No pases un cable de 230 V por el sensor.

Circuito

sensor10 kΩAIN015 kΩLED aviso

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

71 Lectura de batería y apagado limpio

Eagle

Qué hace

Un divisor mete en el ADS1115 una fracción de la batería, siempre por debajo de 3,3 V en el pin. Si el entero baja del que tú anotaste con la batería ya justa, la Pi hace shutdown -h now. No calculo porcentaje. La batería que ya tengas no entra en la franja; el ADS y las resistencias, sí. Tirar del cable no es el método.

Materiales

Cableado

Pack al divisor: positivo, 20 kΩ, nudo AIN0, 10 kΩ, GND del pack y GND físico 6 de la Pi. Con 8,4 V el nudo queda cerca de 2,8 V. Si tu pack es más alto, cambia resistencias para que AIN0 no pase de 3,3 V y vuelve a medirlo con polímetro antes de conectar el ADS.

ADS a 3,3 V físico 1, SDA físico 3, SCL físico 5, ADDR a GND. Nada del positivo del pack va a un GPIO.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Entero crudo. BAJA es el entero que viste cuando consideraste la batería justa, no una fórmula de química. El apagado es sudo shutdown -h now.

import fcntl, os, time
from subprocess import run
I2C_SLAVE = 0x0703
BAJA = 6000
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x48)

def crudo():
    os.write(fd, bytes([0x01, 0xC3, 0x83])); time.sleep(0.02)
    os.write(fd, bytes([0x00]))
    hi, lo = os.read(fd, 2)
    n = (hi << 8) | lo
    return n - 0x10000 if n & 0x8000 else n

while True:
    n = crudo()
    print("crudo", n)
    if 0 < n < BAJA:
        print("apagado limpio")
        run(["sudo", "shutdown", "-h", "now"])
        break
    time.sleep(10)

Hardware: Alimentación Software: Apagado Software: I2C Enseñanza: I2C, varios sensores

Pasos

  1. Calcula el divisor para tu voltaje máximo y mídelo antes de tocar el ADS.
  2. Anota el entero con la batería en el punto en que quieras parar. Escríbelo en BAJA.
  3. Ejecuta y comprueba que con la batería llena el número queda por encima.
  4. No pruebes el apagado hasta estar seguro del signo de la comparación.
  5. El apagado es el comando del sistema, no retirar el cable.
  6. Si sudo pide clave, arréglalo en el sistema, no la pegues aquí.

Circuito

batería20 kΩAIN010 kΩGND

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

72 Varios DS18B20 en un bus

Eagle

Qué hace

Varias sondas DS18B20 comparten el mismo GPIO4 y una sola pull-up de 4,7 kΩ. El programa lista cada carpeta 28- y su temperatura de kernel. No supone un orden físico: el nombre de la carpeta es la identidad.

Materiales

Cableado

Todos los datos unidos al GPIO4 físico 7. Una sola resistencia de 4,7 kΩ de esa línea al 3,3 V físico 1. Todos los VDD al 3,3 V. Todos los GND al físico 9.

No pongas una pull-up por sonda. Nada a 5 V. Cables cortos.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

dtoverlay=w1-gpio. Se saltan las lecturas sin YES.

import glob, os, time

def todas():
    salida = []
    for ruta in sorted(glob.glob("/sys/bus/w1/devices/28-*/w1_slave")):
        ident = os.path.basename(os.path.dirname(ruta))
        texto = open(ruta).read()
        if "YES" not in texto:
            salida.append((ident, None))
            continue
        salida.append((ident, int(texto.strip().split("t=")[-1]) / 1000.0))
    if not salida:
        raise SystemExit("Ninguna sonda. No invento la lista.")
    return salida

while True:
    for ident, g in todas():
        print(ident, g)
    time.sleep(2)

Hardware: DS18B20 Software: 1-Wire Software: config.txt Enseñanza: sensor de temperatura

Pasos

  1. Cablea dos sondas en el mismo hilo de datos.
  2. Reinicia con el overlay.
  3. ls tiene que mostrar dos 28-.
  4. Ejecuta y calienta solo una con los dedos. Solo esa identidad sube.
  5. Anota qué carpeta es el agua y cuál el aire, en un papel, no en el orden del listado.
  6. Añadir una tercera no cambia el pin.

Circuito

3,3 V4,7 kΩGPIO4sondasonda

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

73 Dos programas que se dejan notas en disco

Eagle

Qué hace

Un programa escribe una nota en un archivo y otro la lee, enciende el LED y la borra. Se excluyen con un candado del sistema para no pisarse la frase. No hay red entre ellos: solo el disco de esta Pi.

Materiales

Cableado

LED GPIO17 físico 11, 330 Ω, GND físico 6. Nada a 5 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

fcntl.flock sobre la nota. Lanzas dos terminales: uno escribe, el otro escucha. El texto de ejemplo es el de abajo; son dos programas en un solo archivo, separados por la variable que elijas al arrancar.

import fcntl, os, sys, time
from gpiozero import LED
RUTA = "/tmp/nota-lesvencimos.txt"
led = LED(17)

def con_candado(modo):
    fd = os.open(RUTA, os.O_RDWR | os.O_CREAT, 0o644)
    fcntl.flock(fd, fcntl.LOCK_EX)
    return fd

if len(sys.argv) > 1 and sys.argv[1] == "escribe":
    fd = con_candado("w")
    os.lseek(fd, 0, os.SEEK_SET)
    os.ftruncate(fd, 0)
    os.write(fd, b"hay correo
")
    fcntl.flock(fd, fcntl.LOCK_UN)
    os.close(fd)
    print("nota dejada")
else:
    print("escuchando")
    try:
        while True:
            fd = con_candado("r")
            os.lseek(fd, 0, os.SEEK_SET)
            dato = os.read(fd, 200)
            if dato.strip():
                led.on()
                print(dato.decode())
                os.ftruncate(fd, 0)
            else:
                led.off()
            fcntl.flock(fd, fcntl.LOCK_UN)
            os.close(fd)
            time.sleep(0.5)
    finally:
        led.close()

Hardware: LED Software: Python

Pasos

  1. Cablea el LED.
  2. En una terminal: python3 notas.py
  3. En otra: python3 notas.py escribe
  4. El LED enciende y la primera terminal imprime la frase.
  5. Vuelve a mirar: la nota se ha consumido y el LED se apaga.
  6. No hace falta red.

Circuito

programa Aarchivoprograma BLED

Variantes

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

74 Panel web de cuatro relés

Eagle

Qué hace

Una página local enciende o apaga cuatro relés de cargas de 5–12 V. El primero es el GPIO12 del mapa. Los otros tres usan GPIO16, GPIO20 y GPIO21, que son los del RGB: no montes el LED RGB a la vez. Prohibido: no uses 230 V. No abras puertos en el router.

Materiales

Cableado

IN en GPIO12 físico 32, GPIO16 físico 36, GPIO20 físico 38 y GPIO21 físico 40. VCC de la placa de relés al 5 V físico 2 si la bobina es de 5 V. GND físico 6 común con la fuente de las cargas.

Cada carga en sus contactos, solo 5–12 V. El RGB de la ficha 05 no se usa a la vez. Prohibido: no uses 230 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

HTTPServer y cuatro OutputDevice activos a nivel bajo. La página no carga nada de fuera.

from gpiozero import OutputDevice
from http.server import BaseHTTPRequestHandler, HTTPServer
pines = (12, 16, 20, 21)
reles = [OutputDevice(p, active_high=False, initial_value=False) for p in pines]
print("Prohibido: no uses 230 V. No abras puertos en el router. No montes el RGB a la vez.")

class H(BaseHTTPRequestHandler):
    def do_GET(self):
        cuerpo = b"<!DOCTYPE html><html lang=es><meta charset=utf-8><title>Reles</title><body>"
        cuerpo += "<p>Solo tu red de casa. Cargas de 5 a 12 V.</p>".encode()
        for i in range(4):
            cuerpo += ('<form method=post action="/r/%d"><button>Rele %d</button></form>' % (i, i+1)).encode()
        self.send_response(200)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Content-Length", str(len(cuerpo)))
        self.end_headers()
        self.wfile.write(cuerpo)
    def do_POST(self):
        if self.path.startswith("/r/"):
            i = int(self.path[3:])
            if 0 <= i < 4:
                reles[i].toggle()
        self.do_GET()
    def log_message(self, formato, *a):
        return

HTTPServer(("0.0.0.0", 8080), H).serve_forever()

Hardware: Relé Software: Servidor local Software: Sin red Enseñanza: encender esa salida desde Python

Pasos

  1. No conectes 230 V.
  2. Prueba cada relé con una carga de juguete de 12 V.
  3. Abre el puerto 8080 en el navegador de casa.
  4. Pulsa Relé 1 y escucha el clic.
  5. Comprueba que el RGB no está cableado en 36, 38 y 40.
  6. No reenvíes el puerto.

Circuito

web localGPIO12GPIO16GPIO20GPIO21

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

75 Vigilancia local con límite de disco

Eagle

Qué hace

Saca una foto local cada minuto mientras quede espacio de sobra. Si el disco baja del límite, para y enciende el LED fijo. No sube nada y no abre el router. La cámara va en consultar.

Materiales

Cableado

LED GPIO17 físico 11, 330 Ω, GND físico 6. Cámara al CSI o USB.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

shutil.disk_usage mira el sistema de archivos local. rpicam-still guarda en la carpeta vigilancia.

import shutil
from datetime import datetime
from gpiozero import LED
from pathlib import Path
from subprocess import run
from time import sleep
led = LED(17)
carpeta = Path("vigilancia")
carpeta.mkdir(exist_ok=True)
MINIMO = 500 * 1024 * 1024
try:
    while True:
        libre = shutil.disk_usage(carpeta).free
        if libre < MINIMO:
            led.on()
            print("disco justo", libre)
            break
        led.off()
        nombre = carpeta / (datetime.now().strftime("%Y%m%d-%H%M%S") + ".jpg")
        run(["rpicam-still", "-n", "-o", str(nombre)], check=True)
        print(nombre, "libre", libre)
        sleep(60)
finally:
    pass

Hardware: Cámara Software: Cámara libcamera Software: Sin red Enseñanza: foto y vídeo en el aparato

Pasos

  1. Lista la cámara.
  2. Ejecuta y confirma dos archivos.
  3. Mira el número de bytes libres que imprime.
  4. Si quieres probar el corte, sube MINIMO cerca del espacio real, no llenes el disco a mano hasta romper el sistema.
  5. Cuando pare, el LED queda encendido.
  6. No sincronices la carpeta.

Circuito

cámaracarpetadisco libreLED

Variantes

Fallos típicos

Franja de precio

Franja de precio: consultar (>10 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

76 Sensor de color TCS3200

Eagle

Qué hace

El TCS3200 se alimenta a 3,3 V para que OUT no suba a 5 V. El script elige filtro y cuenta flancos a ojo de Python durante un instante. Esos pulsos son crudos: no salen un rojo, un verde ni un porcentaje de color.

Materiales

Cableado

VCC al 3,3 V físico 1, nunca a 5 V si OUT va a un GPIO. GND físico 6. S0 GPIO17 físico 11, S1 GPIO27 físico 13, S2 GPIO22 físico 15, S3 GPIO16 físico 36, OUT GPIO23 físico 16.

Nada de 5 V en OUT.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

S0 alto y S1 bajo piden la escala del 2 por ciento, la más lenta, para que un bucle de Python vea algún flanco. Sigue siendo un conteo de hobby.

from gpiozero import DigitalInputDevice, DigitalOutputDevice
from time import sleep, time
s0 = DigitalOutputDevice(17)
s1 = DigitalOutputDevice(27)
s2 = DigitalOutputDevice(22)
s3 = DigitalOutputDevice(16)
out = DigitalInputDevice(23, pull_up=False)
s0.on(); s1.off()

def pulsos():
    n = 0
    anterior = out.value
    t0 = time()
    while time() - t0 < 0.1:
        ahora = out.value
        if ahora and not anterior:
            n += 1
        anterior = ahora
    return n

def filtro(a, b):
    s2.value = a
    s3.value = b
    sleep(0.05)
    return pulsos()

while True:
    print("rojo", filtro(0, 0), "verde", filtro(1, 1), "azul", filtro(0, 1), "claro", filtro(1, 0))
    sleep(0.5)

Hardware: TCS3200 Software: gpiozero

Pasos

  1. Alimenta solo a 3,3 V.
  2. Ejecuta y acerca un papel blanco: los cuatro números no tienen por qué parecerse a 255.
  3. Acerca un papel rojo. El conteo del filtro rojo puede subir respecto a los otros. Puede no hacerlo si el sondeo se pierde flancos: dilo, no lo tapes con una fórmula.
  4. No llames a eso RGB calibrado.
  5. Si OUT no late, la escala o el pin están mal.
  6. No pases el módulo a 5 V.

Circuito

3,3 VTCS3200OUT GPIO23

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

77 Aire: gas, temperatura y registro

Eagle

Qué hace

Cada minuto guarda la temperatura del DHT11, si el kernel la tiene, y el entero crudo del MQ-2 a través del ADS. No hay ppm. Es un registro de hobby, no un analizador ni una alarma homologada.

Materiales

Cableado

MQ-2 a 5 V físico 2 para el calentador, GND físico 6, AO con 10 kΩ a AIN0 y 15 kΩ de AIN0 a GND. ADS a 3,3 V físico 1, SDA físico 3, SCL físico 5.

DHT11 a 3,3 V, datos GPIO26 físico 37, pull-up de 10 kΩ. No uses el GPIO4.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

CSV con la hora, el entero del gas y la temperatura del kernel o la palabra ausente. Nada de porcentaje de gas.

import csv, fcntl, glob, os, time
from datetime import datetime
I2C_SLAVE = 0x0703
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x48)

def gas():
    os.write(fd, bytes([0x01, 0xC3, 0x83])); time.sleep(0.02)
    os.write(fd, bytes([0x00]))
    hi, lo = os.read(fd, 2)
    n = (hi << 8) | lo
    return n - 0x10000 if n & 0x8000 else n

def temp():
    for base in glob.glob("/sys/bus/iio/devices/iio:device*"):
        t = os.path.join(base, "in_temp_input")
        if os.path.exists(t):
            return int(open(t).read())/1000.0
    return None

print("Hobby. No es una alarma homologada.")
while True:
    fila = [datetime.now().isoformat(timespec="seconds"), gas(), temp()]
    with open("aire.csv", "a", newline="") as f:
        csv.writer(f).writerow(fila)
    print(fila)
    time.sleep(60)

Hardware: MQ-2 Software: CSV Software: I2C Enseñanza: sensor, decisión y aviso local

Pasos

  1. Monta el divisor del MQ-2.
  2. Precalienta el sensor antes de fiarte de la primera fila.
  3. Comprueba que la temperatura no es None.
  4. Abre aire.csv: tres columnas, la del medio es un entero.
  5. No añadas una columna de ppm.
  6. Ventila la mesa cuando acabes la prueba.

Circuito

MQ-2ADSDHTCSV

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

78 Control de temperatura con histéresis

Eagle

Qué hace

El ventilador de 5–12 V se enciende al pasar de un grado y no se apaga hasta bajar de otro más fresco. Ese hueco es la histéresis: evita que el relé tabletee en el límite. Los grados los da el kernel. Prohibido: no uses 230 V.

Materiales

Cableado

Sonda en GPIO4 físico 7, pull-up 4,7 kΩ al 3,3 V físico 1, GND físico 9.

Relé GPIO12 físico 32, bobina 5 V físico 2, GND físico 6. Ventilador solo de baja tensión.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

dtoverlay=w1-gpio. ALTA y BAJA las eliges tú, con BAJA menor que ALTA.

import glob, os, time
from gpiozero import OutputDevice
ALTA = 29.0
BAJA = 27.0
rele = OutputDevice(12, active_high=False, initial_value=False)
encendido = False

def grados():
    rutas = glob.glob("/sys/bus/w1/devices/28-*/w1_slave")
    if not rutas:
        raise SystemExit("Sin sonda.")
    texto = open(rutas[0]).read()
    if "YES" not in texto:
        return None
    return int(texto.strip().split("t=")[-1]) / 1000.0

print("Prohibido: no uses 230 V.")
try:
    while True:
        g = grados()
        if g is not None:
            if not encendido and g >= ALTA:
                encendido = True
            elif encendido and g <= BAJA:
                encendido = False
            rele.value = encendido
            print(g, "on" if encendido else "off")
        time.sleep(2)
finally:
    rele.off()

Hardware: DS18B20 Software: 1-Wire Enseñanza: sensor de temperatura

Pasos

  1. Comprueba que BAJA es menor que ALTA.
  2. Calienta la sonda por encima de ALTA: el ventilador arranca.
  3. Mientras no baje de BAJA, sigue aunque ya hayas soltado un poco la sonda.
  4. Al cruzar BAJA, para.
  5. Eso es la histéresis, no un retardo al azar.
  6. Nada de 230 V.

Circuito

sondaaltabajaventilador 5-12 V

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

79 Reloj: RTC si no hay red, NTP si la hay

Eagle

Qué hace

Si el sistema dice que el reloj está sincronizado por NTP, se imprime esa hora y la fuente. Si no, se leen los registros BCD del DS3231. No se fabrica un cliente NTP a mano ni se abre un puerto. El modo 12 h se rechaza.

Materiales

Cableado

DS3231: 3,3 V físico 1, GND físico 6, SDA físico 3, SCL físico 5. Nada a 5 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

timedatectl dice si NTP ya sincronizó. El chip se lee solo cuando eso no ocurre. I2C y, si quieres el dispositivo de reloj del kernel, dtoverlay=i2c-rtc,ds3231 en config.txt.

import fcntl, os, time
from datetime import datetime
from subprocess import run
I2C_SLAVE = 0x0703

def ntp_ok():
    r = run(["timedatectl", "show", "-p", "NTPSynchronized", "--value"], capture_output=True, text=True)
    return r.stdout.strip() == "yes"

def bcd(v):
    return ((v >> 4) & 0x0F) * 10 + (v & 0x0F)

fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x68)
while True:
    if ntp_ok():
        print("NTP", datetime.now().isoformat(timespec="seconds"))
    else:
        os.write(fd, bytes([0x00]))
        raw = os.read(fd, 3)
        if raw[2] & 0x40:
            raise SystemExit("RTC en 12 h. No adivino la hora.")
        print("RTC", "%02d:%02d:%02d" % (bcd(raw[2] & 0x3F), bcd(raw[1] & 0x7F), bcd(raw[0] & 0x7F)))
    time.sleep(2)

Hardware: RTC Software: Reloj Software: config.txt Enseñanza: I2C, varios sensores

Pasos

  1. Busca el 0x68.
  2. Con la red de casa, timedatectl debe poder decir yes. El script imprime NTP.
  3. Quita el cable de red y espera a que deje de estar sincronizado. Pasa a imprimir RTC.
  4. Si el RTC está en 00:00:00, nadie le puso la hora: esta ficha no la inventa.
  5. Vuelve a conectar la red si quieres.
  6. No abras puertos para forzar NTP.

Circuito

NTP del sistemaoDS3231 BCD

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

80 Vigilante que relanza el programa

Eagle

Qué hace

Un programa pequeño vigila a otro. Si el hijo se cae, espera dos segundos y lo vuelve a lanzar. El LED late en cada vuelta. No es un servicio escondido: lo ves en la terminal. La unidad de systemd, si la quieres al encender, está descrita en Software y no se instala sola desde aquí.

Materiales

Cableado

LED GPIO17 físico 11, 330 Ω, GND físico 6.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

El hijo de ejemplo es un Python de una línea que sale a los pocos segundos, para que veas el relanzamiento. Sustituye el comando por el script tuyo. systemd queda como enlace, no como un archivo que yo copie al sistema sin que tú lo pidas en la Pi.

from gpiozero import LED
from subprocess import run
from time import sleep
led = LED(17)
hijo = ["python3", "-c", "import time; print('hijo'); time.sleep(3); raise SystemExit(1)"]
try:
    while True:
        led.on()
        print("lanzo")
        codigo = run(hijo).returncode
        led.off()
        print("salio", codigo, "relanzo en 2 s")
        sleep(2)
finally:
    led.close()

Hardware: LED Software: systemd

Pasos

  1. Cablea el LED.
  2. Ejecuta. Cada tres segundos el hijo muere y el mensaje de relanzamiento aparece.
  3. Cambia la lista hijo por el camino de tu programa real.
  4. Ctrl+C para el vigilante. El hijo, si siguiera vivo, habría que pararlo también; en el ejemplo ya ha salido.
  5. No pongas un sleep de cero: sería un bucle que come la CPU.
  6. Si más adelante quieres arranque al encender, mira la sección de systemd, no copies una unidad de internet.

Circuito

vigilantehijosi cae, otra vez

Variantes

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

81 Casa mini en una maqueta

Fénix

Qué hace

En una maqueta, el LED de la sala se enciende con un botón, el reed dice si la puerta está abierta, el PIR enciende ese mismo aviso un rato y el DHT11 imprime el aire si el kernel lo ve. Un relé puede mover un ventilador de 5–12 V. Prohibido: no uses 230 V. La caseta de cartón no entra en la cuenta; la electrónica añadida, sí.

Materiales

Cableado

LED GPIO17 físico 11 con 330 Ω a GND físico 6. Botón GPIO24 físico 18 a GND físico 14. Reed GPIO23 físico 16 a GND físico 9. PIR OUT GPIO5 físico 29, VCC 5 V físico 2, GND físico 20, solo si OUT es de 3,3 V.

DHT11 datos GPIO26 físico 37, 3,3 V físico 1, GND físico 34, pull-up 10 kΩ. Relé GPIO12 físico 32, bobina 5 V físico 4, GND físico 30. No hay RGB. El ventilador no es de red.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

gpiozero para luces y contactos. El DHT, si está el overlay, se imprime desde sysfs y si no está se dice, sin cifra falsa. El relé solo actúa si el kernel da una temperatura y pasa de 30.

import glob, os
from gpiozero import LED, Button, DigitalInputDevice, MotionSensor, OutputDevice
from signal import pause
led = LED(17)
luz = Button(24, pull_up=True, bounce_time=0.05)
reed = DigitalInputDevice(23, pull_up=True)
pir = MotionSensor(5)
rele = OutputDevice(12, active_high=False, initial_value=False)
luz.when_pressed = led.toggle

def puerta():
    print("puerta", "abierta" if reed.value else "cerrada")
reed.when_deactivated = puerta
reed.when_activated = puerta
pir.when_motion = led.on
print("Prohibido: no uses 230 V.")

def temp():
    for base in glob.glob("/sys/bus/iio/devices/iio:device*"):
        t = os.path.join(base, "in_temp_input")
        if os.path.exists(t):
            return int(open(t).read())/1000.0
    return None
print("aire", temp() if temp() is not None else "sin DHT, no invento")
pause()

Hardware: Alimentación Software: gpiozero Software: config.txt Enseñanza: sensor, decisión y aviso local

Pasos

  1. Reparte masas y no juntes 5 V del PIR con un GPIO.
  2. Prueba el botón: el LED cambia.
  3. Abre el reed: la terminal lo dice.
  4. Pasa la mano por el PIR: el LED enciende.
  5. Si temp() es None, arregla el overlay antes de querer el ventilador. El relé de esta versión queda preparado y apagado hasta que amplíes el bucle con el umbral.
  6. Ninguna carga de 230 V en la maqueta.

Circuito

maquetaLEDpuertaPIRDHTrelé 5-12 V

Variantes

Fallos típicos

Franja de precio

Franja de precio: bastante (≈6–9 €). Material añadido, sin contar la Pi.

Depuración

82 Invernadero con día, noche e historial

Fénix

Qué hace

La LDR, por tiempo de carga y no por ADC, dice si es de día. La sonda da los grados del kernel. La tierra entra como entero del ADS. Todo se anota en un CSV y, de día y con tierra seca, un relé riega un momento con bomba de 5–12 V. Prohibido: no uses 230 V.

Materiales

Cableado

LDR entre 3,3 V físico 1 y GPIO27 físico 13, condensador a GND físico 9. DS18B20 en GPIO4 físico 7 con 4,7 kΩ.

ADS SDA físico 3, SCL físico 5, sonda a AIN0. Relé GPIO12 físico 32. Masas al físico 6. Bomba de baja tensión.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

LightSensor para el día. Grados solo si hay YES. Entero de suelo sin porcentaje. CSV con las tres cosas.

import csv, fcntl, glob, os, time
from datetime import datetime
from gpiozero import LightSensor, OutputDevice
I2C_SLAVE = 0x0703
SECO = 18000
ldr = LightSensor(27)
rele = OutputDevice(12, active_high=False, initial_value=False)
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x48)

def grados():
    rutas = glob.glob("/sys/bus/w1/devices/28-*/w1_slave")
    if not rutas:
        return None
    texto = open(rutas[0]).read()
    if "YES" not in texto:
        return None
    return int(texto.strip().split("t=")[-1]) / 1000.0

def suelo():
    os.write(fd, bytes([0x01, 0xC3, 0x83])); time.sleep(0.02)
    os.write(fd, bytes([0x00]))
    hi, lo = os.read(fd, 2)
    n = (hi << 8) | lo
    return n - 0x10000 if n & 0x8000 else n

print("Prohibido: no uses 230 V.")
try:
    while True:
        dia = ldr.value > 0.4
        g = grados()
        s = suelo()
        if dia and s >= SECO:
            rele.on(); time.sleep(2); rele.off()
        with open("invernadero.csv", "a", newline="") as f:
            csv.writer(f).writerow([datetime.now().isoformat(timespec="seconds"), "dia" if dia else "noche", g, s])
        print(dia, g, s)
        time.sleep(60)
finally:
    rele.off()

Hardware: LDR Software: CSV Software: 1-Wire Enseñanza: sensor de temperatura

Pasos

  1. Ajusta SECO con tu tierra.
  2. De noche tapa la LDR y comprueba que no riega aunque el entero esté alto.
  3. De día destapa y, con tierra seca, debe haber un pulso corto.
  4. Mira el CSV: la columna de grados es un número o está vacía, nunca un 20 puesto a mano.
  5. La bomba es de 5–12 V.
  6. No dejes el relé enganchado si el proceso muere: el finally lo suelta si sales con Ctrl+C.

Circuito

LDR díaDS18B20sueloCSVrelébomba

Variantes

Fallos típicos

Franja de precio

Franja de precio: bastante (≈6–9 €). Material añadido, sin contar la Pi.

Depuración

83 Robot con cámara y mando web local

Fénix

Qué hace

Desde una página de la red de casa mandas adelante, atrás o parar, y pides una foto que se guarda en el aparato. Los motores van con driver y batería aparte. No abras puertos en el router y no hay analítica. La cámara y el chasis van en consultar.

Materiales

Cableado

Motor A GPIO19 físico 35, GPIO26 físico 37, enable GPIO18 físico 12. Motor B GPIO20 físico 38 y GPIO21 físico 40.

Masa de la batería al físico 6. Cámara al CSI o USB. No hay DHT en el 26: ese pin es dirección del motor.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

HTTPServer local y rpicam-still. Los botones del formulario son rutas relativas.

from gpiozero import DigitalOutputDevice, Motor
from http.server import BaseHTTPRequestHandler, HTTPServer
from subprocess import run
a = Motor(forward=19, backward=26, enable=18)
bf = DigitalOutputDevice(20)
bb = DigitalOutputDevice(21)

def parar():
    a.stop(); bf.off(); bb.off()

class H(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/foto.jpg":
            run(["rpicam-still", "-n", "-o", "robot.jpg"], check=False)
            data = open("robot.jpg", "rb").read()
            self.send_response(200)
            self.send_header("Content-Type", "image/jpeg")
            self.send_header("Content-Length", str(len(data)))
            self.end_headers()
            self.wfile.write(data)
            return
        cuerpo = ("<!DOCTYPE html><html lang=es><meta charset=utf-8><title>Robot</title><body>"
        "<p>Solo tu red de casa. No abras puertos en el router.</p>"
        "<form method=post action=/adelante><button>Adelante</button></form>"
        "<form method=post action=/atras><button>Atras</button></form>"
        "<form method=post action=/parar><button>Parar</button></form>"
        "<img src=foto.jpg alt=foto>").encode()
        self.send_response(200)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Content-Length", str(len(cuerpo)))
        self.end_headers()
        self.wfile.write(cuerpo)
    def do_POST(self):
        parar()
        if self.path == "/adelante":
            a.forward(0.5); bf.on()
        elif self.path == "/atras":
            a.backward(0.5); bb.on()
        self.do_GET()
    def log_message(self, formato, *args):
        return

print("Puerto 8080 en casa.")
try:
    HTTPServer(("0.0.0.0", 8080), H).serve_forever()
finally:
    parar()

Hardware: Cámara Software: Cámara libcamera Software: Servidor local Enseñanza: foto y vídeo en el aparato

Pasos

  1. Ruedas en el aire.
  2. Lista la cámara.
  3. Abre el puerto 8080 desde otro equipo de casa, no desde la calle.
  4. Adelante, parar, atrás.
  5. Pide la imagen. Se queda en robot.jpg.
  6. No crees un reenvío de puertos.

Circuito

web casamotorescámara local

Variantes

Fallos típicos

Franja de precio

Franja de precio: consultar (>10 €). Material añadido, sin contar la Pi.

Depuración

84 Meteo de balcón con registro de un mes

Fénix

Qué hace

Cada hora, durante muchos días, guarda la temperatura del DHT11 si el kernel la tiene y los enteros crudos de presión y temperatura del BMP280. Un archivo por día, pensado para un mes en el balcón del aparato. Los crudos no son hPa ni una segunda temperatura calibrada.

Materiales

Cableado

DHT11 GPIO26 físico 37, 3,3 V físico 1, GND físico 6, pull-up 10 kΩ.

BMP280 SDA físico 3, SCL físico 5, 3,3 V y GND. Nada a 5 V. No dejes la Pi bajo la lluvia: esta ficha no impermeabiliza la placa.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

I2C y overlay del DHT. El CSV se llama con la fecha. No hay compensación del BMP en el programa.

import csv, fcntl, glob, os, time
from datetime import datetime
I2C_SLAVE = 0x0703

def abrir():
    for addr in (0x76, 0x77):
        try:
            fd = os.open("/dev/i2c-1", os.O_RDWR)
            fcntl.ioctl(fd, I2C_SLAVE, addr)
            os.write(fd, bytes([0xD0]))
            ident = os.read(fd, 1)[0]
            if ident in (0x58, 0x60):
                print("chip", hex(ident), "en", hex(addr))
                return fd
            os.close(fd)
        except OSError:
            pass
    raise SystemExit("Sin BMP280. No invento la presion.")

def crudo(fd):
    os.write(fd, bytes([0xF4, 0x25])); time.sleep(0.02)
    os.write(fd, bytes([0xF7]))
    d = os.read(fd, 6)
    pres = (d[0] << 12) | (d[1] << 4) | (d[2] >> 4)
    temp = (d[3] << 12) | (d[4] << 4) | (d[5] >> 4)
    return pres, temp

def dht():
    for base in glob.glob("/sys/bus/iio/devices/iio:device*"):
        t = os.path.join(base, "in_temp_input")
        h = os.path.join(base, "in_humidityrelative_input")
        if os.path.exists(t) and os.path.exists(h):
            return int(open(t).read())/1000.0, int(open(h).read())/1000.0
    return None

fd = abrir()
while True:
    dia = datetime.now().strftime("%Y%m%d")
    fila = [datetime.now().isoformat(timespec="seconds"), dht(), crudo(fd)]
    with open("balcon-"+dia+".csv", "a", newline="") as f:
        csv.writer(f).writerow(fila)
    print(fila)
    time.sleep(3600)

Hardware: BMP280 Software: CSV Software: I2C Enseñanza: I2C, varios sensores

Pasos

  1. Protege la electrónica del agua. El balcón no es una excusa para mojar la Pi.
  2. Comprueba id 0x58 o 0x60.
  3. Lanza una vuelta y abre el CSV del día.
  4. La tupla de presión es cruda. No la multipliques.
  5. A la hora siguiente hay otra fila en el mismo archivo.
  6. Al cambiar el día, otro nombre.

Circuito

DHT kernelBMP crudoCSV del día

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

85 Acceso RFID más teclado y registro

Fénix

Qué hace

El RC522 se detecta por su registro de versión, a 3,3 V. El teclado añade un código. Si la versión cuadra y el código es el tuyo, el relé de un pestillo de 5–12 V da un pulso y se anota una fila. No se inventa un UID. El RST está en GPIO25: el zumbador, si lo pones, va al GPIO18. Prohibido: no uses 230 V.

Materiales

Cableado

RC522 a 3,3 V físico 1, GND físico 6, SCK GPIO11 físico 23, MOSI GPIO10 físico 19, MISO GPIO9 físico 21, CE0 GPIO8 físico 24, RST GPIO25 físico 22. Nunca 5 V en VCC.

Teclado: filas GPIO17 físico 11, GPIO27 físico 13, GPIO22 físico 15, GPIO16 físico 36. Columnas GPIO23 físico 16, GPIO24 físico 18, GPIO20 físico 38, GPIO21 físico 40. Relé GPIO12 físico 32. Zumbador no va al 25; si lo añades, GPIO18 físico 12.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

SPI activo. CSV local con la hora y si la versión del chip era la esperada. La clave se cambia en el archivo.

import csv, os
from datetime import datetime
from gpiozero import DigitalInputDevice, DigitalOutputDevice, OutputDevice
from time import sleep
CLAVE = "2468"
print("RC522 a 3,3 V. Prohibido: no uses 230 V. Zumbador no en GPIO25.")
rele = OutputDevice(12, active_high=False, initial_value=False)
if not os.path.exists("/dev/spidev0.0"):
    raise SystemExit("Sin SPI. No registro un acceso falso.")
try:
    import spidev
    spi = spidev.SpiDev(); spi.open(0, 0); spi.max_speed_hz = 100000
    ver = spi.xfer2([((0x37 << 1) & 0x7E) | 0x80, 0])[1]
except Exception as exc:
    raise SystemExit("SPI no usable: %s. No invento UID." % exc)
print("version", hex(ver))
chip_ok = ver in (0x91, 0x92)
filas = [DigitalOutputDevice(p, initial_value=True) for p in (17, 27, 22, 16)]
columnas = [DigitalInputDevice(p, pull_up=True) for p in (23, 24, 20, 21)]
teclas = list("123A456B789C*0#D")
entrada = ""
anterior = None
try:
    while True:
        tecla = None
        for i, f in enumerate(filas):
            f.off(); sleep(0.003)
            for j, c in enumerate(columnas):
                if not c.value:
                    tecla = teclas[i*4+j]
            f.on()
        if tecla and tecla != anterior and tecla.isdigit():
            entrada += tecla
            if len(entrada) >= 4:
                ok = chip_ok and entrada[-4:] == CLAVE
                with open("accesos.csv", "a", newline="") as fcsv:
                    csv.writer(fcsv).writerow([datetime.now().isoformat(timespec="seconds"), hex(ver), ok])
                if ok:
                    rele.on(); sleep(2); rele.off()
                entrada = ""
        anterior = tecla
        sleep(0.02)
finally:
    rele.off()

Hardware: RFID Software: SPI Software: CSV Enseñanza: sensor, decisión y aviso local

Pasos

  1. SPI on, lector a 3,3 V, clave cambiada.
  2. Ejecuta y mira la versión.
  3. Si no es 0x91 ni 0x92, el relé no debe abrir aunque aciertes el teclado.
  4. Si cuadra, teclea la clave y escucha el relé de baja tensión.
  5. Mira accesos.csv.
  6. No pongas 230 V en el pestillo.

Circuito

RC522 3,3 VtecladoCSVrelé 5-12 V

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

86 Cuadro de mando: OLED, web, sensores y relés

Fénix

Qué hace

Una página de casa muestra el aire del DHT11 si el kernel lo tiene, enciende dos relés de 5–12 V y el OLED se limpia para confirmar el bus. No abras puertos en el router. Prohibido: no uses 230 V. No hay analítica.

Materiales

Cableado

OLED y DHT a 3,3 V físico 1, GND físico 6. SDA físico 3, SCL físico 5. Datos DHT GPIO26 físico 37 con pull-up.

Relés GPIO12 físico 32 y GPIO16 físico 36. Bobinas al 5 V físico 2. El RGB no se monta: el GPIO16 sería su rojo.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Servidor local de la biblioteca estándar. La temperatura solo aparece si existe el fichero del kernel.

import fcntl, glob, os
from gpiozero import OutputDevice
from http.server import BaseHTTPRequestHandler, HTTPServer
I2C_SLAVE = 0x0703
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x3C)
os.write(fd, bytes([0x00, 0xAF]))
reles = [OutputDevice(p, active_high=False, initial_value=False) for p in (12, 16)]

def aire():
    for base in glob.glob("/sys/bus/iio/devices/iio:device*"):
        t = os.path.join(base, "in_temp_input")
        if os.path.exists(t):
            return str(int(open(t).read())/1000.0)
    return "sin DHT"

class H(BaseHTTPRequestHandler):
    def do_GET(self):
        cuerpo = ("<!DOCTYPE html><html lang=es><meta charset=utf-8><title>Mandos</title><body><p>Solo tu red de casa.</p><p>Aire %s</p><form method=post action=/r/0><button>Rele 1</button></form><form method=post action=/r/1><button>Rele 2</button></form>" % aire()).encode()
        self.send_response(200)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Content-Length", str(len(cuerpo)))
        self.end_headers()
        self.wfile.write(cuerpo)
    def do_POST(self):
        if self.path.startswith("/r/"):
            i = int(self.path[3:])
            if 0 <= i < 2:
                reles[i].toggle()
        self.do_GET()
    def log_message(self, formato, *a):
        return

print("Prohibido: no uses 230 V.")
HTTPServer(("0.0.0.0", 8080), H).serve_forever()

Hardware: OLED Software: Servidor local Software: Sin red Enseñanza: I2C, varios sensores

Pasos

  1. I2C, overlay del DHT, relés de baja tensión.
  2. Abre el puerto 8080 en casa.
  3. Mira si el aire es un número o el aviso.
  4. Pulsa cada relé y escucha el clic.
  5. No reenvíes el puerto.
  6. No pongas el RGB en el GPIO16.

Circuito

OLEDDHTwebdos relés 5-12 V

Variantes

Fallos típicos

Franja de precio

Franja de precio: bastante (≈6–9 €). Material añadido, sin contar la Pi.

Depuración

87 Riego de tres macetas por turnos

Fénix

Qué hace

Tres sondas entran en AIN0, AIN1 y AIN2. Si una está más seca que su entero, su relé riega y las otras esperan: un turno cada vez, para no vaciar la fuente. Bombas o electroválvulas de 5–12 V. Prohibido: no uses 230 V. No hay porcentaje.

Materiales

Cableado

ADS a 3,3 V físico 1, GND físico 6, SDA físico 3, SCL físico 5. Sondas a AIN0, AIN1 y AIN2, alimentadas a 3,3 V.

Relés GPIO12 físico 32, GPIO16 físico 36 y GPIO20 físico 38. Bobinas al 5 V físico 2. No montes el RGB a la vez.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Tres disparos del ADS con MUX distinto: 0xC383, 0xD383 y 0xE383, PGA ±4,096 V del datasheet. El umbral es tuyo.

import fcntl, os, time
from gpiozero import OutputDevice
I2C_SLAVE = 0x0703
SECO = 18000
CFGS = (0xC383, 0xD383, 0xE383)
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x48)
reles = [OutputDevice(p, active_high=False, initial_value=False) for p in (12, 16, 20)]

def canal(cfg):
    os.write(fd, bytes([0x01, cfg >> 8, cfg & 0xFF])); time.sleep(0.02)
    os.write(fd, bytes([0x00]))
    hi, lo = os.read(fd, 2)
    n = (hi << 8) | lo
    return n - 0x10000 if n & 0x8000 else n

print("Prohibido: no uses 230 V. Un turno cada vez.")
try:
    while True:
        for i, cfg in enumerate(CFGS):
            n = canal(cfg)
            print(i, n)
            if n >= SECO:
                reles[i].on(); time.sleep(2); reles[i].off(); time.sleep(20)
        time.sleep(30)
finally:
    for r in reles:
        r.off()

Hardware: Humedad de suelo Software: I2C Enseñanza: sensor, decisión y aviso local

Pasos

  1. Mide el entero seco de cada sonda. Si no se parecen, no uses un solo SECO: separa tres constantes.
  2. Prueba cada bomba un segundo, fuera de la maceta.
  3. Ejecuta y confirma que nunca hay dos relés a la vez.
  4. Ajusta los segundos al tamaño de la maceta.
  5. Nada de 230 V.
  6. Ctrl+C suelta los tres.

Circuito

AIN0AIN1AIN2tres relés por turnos

Variantes

Fallos típicos

Franja de precio

Franja de precio: bastante (≈6–9 €). Material añadido, sin contar la Pi.

Depuración

88 Alarma con modos casa, fuera y noche

Fénix

Qué hace

El teclado elige casa, fuera o noche. En casa el PIR no pita. Fuera, pita el PIR o la puerta. De noche, pita la puerta y el PIR solo si quieres, aquí también. Nada sale de la habitación. Cambia la lógica si tu reed cierra al revés, no el voltaje.

Materiales

Cableado

Filas GPIO17 físico 11, GPIO27 físico 13, GPIO22 físico 15, GPIO16 físico 36. Columnas GPIO23 físico 16, GPIO24 físico 18, GPIO20 físico 38, GPIO21 físico 40.

PIR GPIO5 físico 29, 5 V físico 2, GND físico 14. Reed GPIO6 físico 31 a GND físico 20. Zumbador GPIO25 físico 22, GND físico 39, VCC 3,3 V físico 1 si vale.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Tecla 1 casa, 2 fuera, 3 noche. gpiozero en PIR, reed y zumbador.

from gpiozero import Buzzer, DigitalInputDevice, DigitalOutputDevice, MotionSensor
from time import sleep
filas = [DigitalOutputDevice(p, initial_value=True) for p in (17, 27, 22, 16)]
columnas = [DigitalInputDevice(p, pull_up=True) for p in (23, 24, 20, 21)]
teclas = list("123A456B789C*0#D")
pir = MotionSensor(5)
reed = DigitalInputDevice(6, pull_up=True)
z = Buzzer(25)
modo = "casa"
anterior = None
try:
    while True:
        tecla = None
        for i, f in enumerate(filas):
            f.off(); sleep(0.003)
            for j, c in enumerate(columnas):
                if not c.value:
                    tecla = teclas[i*4+j]
            f.on()
        if tecla and tecla != anterior:
            if tecla == "1":
                modo = "casa"
            elif tecla == "2":
                modo = "fuera"
            elif tecla == "3":
                modo = "noche"
            print(modo)
        puerta = not reed.value
        mover = pir.motion_detected
        if modo == "fuera" and (puerta or mover):
            z.on()
        elif modo == "noche" and puerta:
            z.on()
        else:
            z.off()
        anterior = tecla
        sleep(0.02)
finally:
    z.off()

Hardware: PIR Software: gpiozero Enseñanza: sensor, decisión y aviso local

Pasos

  1. Elige 1 y camina: no debe pitar.
  2. Elige 2, sal y entra: pita.
  3. Elige 3 y abre solo la puerta: pita. El PIR de noche, en este código, no pita; es deliberado.
  4. Vuelve a 1: calla.
  5. Cambia el reed si tu contacto es el inverso.
  6. No conectes esto a una sirena de 230 V.

Circuito

teclas 1 2 3PIRpuertazumbador

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

89 Banco de pruebas de GPIO

Fénix

Qué hace

Un LED con su resistencia de 330 Ω se mueve de pin en pin, solo entre una lista segura, para ver que ese GPIO todavía obedece. El programa se niega a tocar el GPIO2 y el GPIO3, que son I2C, y el 14 y el 15 de la serial. En la Pi 5 no uses RPi.GPIO. gpiozero sí, con el backend lgpio.

Materiales

Cableado

Empieza con el LED en GPIO17 físico 11 y GND físico 6, siempre con 330 Ω.

Cuando el programa lo diga, apaga la Pi o al menos para el script antes de mover el LED a la siguiente pata de la lista. Nunca lo pongas en el físico 1, 2 o 4 de alimentación.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

La lista es 17, 27, 22 y 10. El 10 solo si no tienes SPI en marcha. gpiozero con lgpio. No se importa RPi.GPIO.

from gpiozero import LED
from time import sleep
SEGUROS = (17, 27, 22, 10)
PROHIBIDOS = {2, 3, 14, 15}
for pin in SEGUROS:
    if pin in PROHIBIDOS:
        raise SystemExit("me niego")
    print("Mueve el LED con 330 ohm al GPIO", pin, "y pulsa Enter")
    input()
    led = LED(pin)
    for _ in range(4):
        led.on(); sleep(0.2); led.off(); sleep(0.2)
    led.close()
print("Hecho. En la Pi 5 no uses RPi.GPIO. gpiozero si, con el backend lgpio.")

Hardware: GPIO Software: gpiozero Software: libgpiod Enseñanza: encender esa salida desde Python

Pasos

  1. Lee la lista antes de cablear.
  2. LED y 330 Ω en el físico 11.
  3. Ejecuta y confirma cuatro parpadeos.
  4. Para, mueve el cable al siguiente físico que el texto nombre, no a un pin de 5 V.
  5. Si el GPIO10 no parpadea y tienes SPI activo, sácalo de la lista.
  6. No pruebes el GPIO2.

Circuito

LED 330un GPIO cada veznunca 5 V

Variantes

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

90 Kiosco offline con pantalla y botones

Fénix

Qué hace

Tres líneas de un menú local en el OLED y dos botones para moverte y elegir. No hay servidor, no hay red y no se carga nada de fuera. Sirve para dejar la Pi con una pantalla pequeña y decidir un modo sin teclado de verdad.

Materiales

Cableado

OLED a 3,3 V físico 1, GND físico 6, SDA físico 3, SCL físico 5.

Botón siguiente GPIO23 físico 16 a GND físico 9. Botón elegir GPIO24 físico 18 a GND físico 14.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

I2C activo. El menú vive en la lista del programa. Elegir solo imprime, para que tú enganches después la acción, sin red.

import fcntl, os
from gpiozero import Button
from signal import pause
I2C_SLAVE = 0x0703
fd = os.open("/dev/i2c-1", os.O_RDWR)
fcntl.ioctl(fd, I2C_SLAVE, 0x3C)
def cmd(b):
    os.write(fd, bytes([0x00, b]))
for c in (0xAE,0x8D,0x14,0xAF,0x20,0x00):
    cmd(c)
opciones = ["luz", "riego", "foto"]
i = 0

def pintar():
    cmd(0x21); cmd(0); cmd(127); cmd(0x22); cmd(1); cmd(1)
    os.write(fd, bytes([0x40]) + bytes([0x7E if n//40 == i else 0x00 for n in range(128)]))
    print("kiosco", opciones[i])

def mas():
    global i
    i = (i + 1) % 3
    pintar()

def ok():
    print("elegido", opciones[i], "sin red")

Button(23, pull_up=True, bounce_time=0.05).when_pressed = mas
Button(24, pull_up=True, bounce_time=0.05).when_pressed = ok
pintar()
pause()

Hardware: OLED Software: I2C Software: Sin red Enseñanza: I2C, varios sensores

Pasos

  1. Confirma el 3c.
  2. Ejecuta sin cable de red si quieres comprobar que no lo necesita.
  3. Avanza con un botón.
  4. Elige con el otro. La terminal dice la palabra.
  5. No añadas un navegador.
  6. Ctrl+C.

Circuito

botonesOLEDsin red

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

91 Bitácora de cultivo con foto diaria

Fénix

Qué hace

Una vez al día guarda una foto en esta carpeta y una fila de CSV con la hora y los grados del DS18B20, si el kernel los tiene. Si la sonda falla, la fila lo dice y no rellena un número. La cámara no se sube a ningún sitio y va en la franja consultar.

Materiales

Cableado

Sonda: datos GPIO4 físico 7, pull-up de 4,7 kΩ al 3,3 V físico 1, VDD a 3,3 V, GND físico 9.

La cámara va al conector CSI o al USB, no a un GPIO. Nada a 5 V en la sonda.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

rpicam-still y el bus 1-Wire con dtoverlay=w1-gpio. El bucle duerme hasta el día siguiente. No hace falta red ni un cron para que la ficha funcione, aunque puedas pasarlo a cron más adelante.

import csv, glob, os
from datetime import datetime
from pathlib import Path
from subprocess import run
from time import sleep
carpeta = Path("cultivo")
carpeta.mkdir(exist_ok=True)

def grados():
    rutas = glob.glob("/sys/bus/w1/devices/28-*/w1_slave")
    if not rutas:
        return None
    texto = open(rutas[0]).read()
    if "YES" not in texto:
        return None
    return int(texto.strip().split("t=")[-1]) / 1000.0

while True:
    marca = datetime.now().strftime("%Y%m%d")
    foto = carpeta / ("dia-" + marca + ".jpg")
    run(["rpicam-still", "-n", "-o", str(foto)], check=True)
    g = grados()
    with open(carpeta / "bitacora.csv", "a", newline="") as f:
        csv.writer(f).writerow([datetime.now().isoformat(timespec="seconds"), foto.name, g if g is not None else "sin sonda"])
    print(foto, g)
    sleep(24 * 60 * 60)

Hardware: Cámara Software: Cámara libcamera Software: CSV Enseñanza: foto y vídeo en el aparato

Pasos

  1. Lista la cámara y comprueba la carpeta 28- si quieres grados.
  2. Encaja el encuadre de la maceta y no lo muevas.
  3. Ejecuta y espera la primera foto y la primera fila.
  4. Abre el JPEG en el aparato.
  5. Si la columna de grados dice sin sonda, no escribas 20 a mano.
  6. Deja el proceso el día que te interese y páralo con Ctrl+C. No subas la carpeta.

Circuito

cámaraJPEG del díaDS18B20CSV

Variantes

Fallos típicos

Franja de precio

Franja de precio: consultar (>10 €). Material añadido, sin contar la Pi.

Depuración

92 Semáforo de maqueta que reacciona al tráfico

Fénix

Qué hace

Tres LED hacen de semáforo de maqueta. Si el ultrasonidos ve un objeto cerca, el ciclo no se queda en verde: pasa a ámbar y a rojo, como si llegara un coche. El eco lleva divisor. La distancia en metros la da gpiozero, no una fórmula de esta ficha.

Materiales

Cableado

Rojo GPIO17 físico 11, ámbar GPIO27 físico 13, verde GPIO22 físico 15, cada uno con 330 Ω, cátodos a GND físico 6.

HC-SR04: 5 V físico 2, GND físico 14, TRIG GPIO5 físico 29, ECHO con 1 kΩ al GPIO6 físico 31 y 2 kΩ de ahí a GND físico 20. Nada de 5 V en un GPIO.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

DistanceSensor y tres LED. Cerca de 30 cm se considera tráfico. Es un umbral de maqueta.

from gpiozero import DistanceSensor, LED
from time import sleep
rojo, ambar, verde = LED(17), LED(27), LED(22)
dist = DistanceSensor(echo=6, trigger=5, max_distance=1.5)

def solo(luz, segundos):
    rojo.off(); ambar.off(); verde.off(); luz.on(); sleep(segundos)

try:
    while True:
        if dist.distance < 0.3:
            solo(ambar, 0.6)
            solo(rojo, 3)
        else:
            solo(verde, 0.4)
finally:
    rojo.close(); ambar.close(); verde.close(); dist.close()

Hardware: HC-SR04 Software: gpiozero Enseñanza: sensor, decisión y aviso local

Pasos

  1. Monta el divisor antes de dar 5 V al sensor.
  2. Ejecuta con la calle de la maqueta vacía: debe predominar el verde.
  3. Pon un coche de juguete delante del sensor: ámbar y luego rojo.
  4. Retíralo: vuelve el verde.
  5. Si nunca pasa a rojo, imprime la distancia y baja el 0,3 a la medida real.
  6. No alimentes el módulo a 3,3 V esperando el alcance de la hoja.

Circuito

cocheHC-SR04divisorrojoámbarverde

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

93 Torre I2C: OLED, RTC, presión y luz

Fénix

Qué hace

En el mismo bus I2C conviven el OLED, el DS3231 y el BMP280. La luz no va por I2C: es una LDR con condensador en el GPIO27, al estilo de gpiozero, no un luxómetro. La presión se imprime en crudo. La hora, en BCD. Si el RTC está en 12 h, se para.

Materiales

Cableado

SDA GPIO2 físico 3 y SCL GPIO3 físico 5, compartidos. Los tres módulos a 3,3 V físico 1 y GND físico 6. Nada a 5 V.

LDR entre el 3,3 V y el GPIO27 físico 13. Condensador de 1 µF del GPIO27 a GND físico 9. No uses el 2 ni el 3 como LED.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

I2C activo. El BMP se reconoce por el registro 0xD0. No se compensan los enteros. La LDR es LightSensor.

import fcntl, os, time
from gpiozero import LightSensor
I2C_SLAVE = 0x0703
ldr = LightSensor(27)

def abrir(addr):
    fd = os.open("/dev/i2c-1", os.O_RDWR)
    fcntl.ioctl(fd, I2C_SLAVE, addr)
    return fd

def bcd(v):
    return ((v >> 4) & 0x0F) * 10 + (v & 0x0F)

oled = abrir(0x3C)
os.write(oled, bytes([0x00, 0xAF]))
rtc = abrir(0x68)
bmp = None
for addr in (0x76, 0x77):
    try:
        fd = abrir(addr)
        os.write(fd, bytes([0xD0]))
        if os.read(fd, 1)[0] in (0x58, 0x60):
            bmp = fd
            break
        os.close(fd)
    except OSError:
        pass
if bmp is None:
    raise SystemExit("Sin BMP280. No invento presion.")
while True:
    os.write(rtc, bytes([0x00]))
    raw = os.read(rtc, 3)
    if raw[2] & 0x40:
        raise SystemExit("RTC en 12 h.")
    os.write(bmp, bytes([0xF4, 0x25])); time.sleep(0.02)
    os.write(bmp, bytes([0xF7]))
    d = os.read(bmp, 6)
    pres = (d[0] << 12) | (d[1] << 4) | (d[2] >> 4)
    print("hora", "%02d:%02d:%02d" % (bcd(raw[2] & 0x3F), bcd(raw[1] & 0x7F), bcd(raw[0] & 0x7F)), "luz", round(ldr.value, 2), "presion cruda", pres)
    time.sleep(2)

Hardware: OLED Software: I2C Software: config.txt Enseñanza: I2C, varios sensores

Pasos

  1. i2cdetect tiene que mostrar los tres chips antes de lanzar el bucle.
  2. La LDR no aparecerá ahí. Comprueba el condensador.
  3. Ejecuta. Hora, un valor de luz entre 0 y 1, y un entero de presión.
  4. Tapa la LDR: el valor de luz baja. El entero de presión no es un hPa.
  5. Sopla hacia el BMP: el crudo puede moverse. Sigue siendo crudo.
  6. No alimentes ningún módulo de la torre a 5 V.

Circuito

SDA SCLOLEDRTCBMPLDRGPIO27

Variantes

Fallos típicos

Franja de precio

Franja de precio: bastante (≈6–9 €). Material añadido, sin contar la Pi.

Depuración

94 Avisos con acuse de recibo

Fénix

Qué hace

Si el PIR ve movimiento, el LED y el zumbador siguen hasta que pulsas el acuse. Entonces se callan y el CSV guarda la hora del aviso y la hora en que lo confirmaste. Sin acuse no se da el aviso por cerrado.

Materiales

Cableado

PIR: 5 V físico 2, GND físico 14, OUT GPIO5 físico 29 solo si no pasa de 3,3 V.

LED GPIO17 físico 11 con 330 Ω a GND físico 6. Zumbador activo GPIO25 físico 22, GND físico 39, VCC 3,3 V físico 1 si el módulo lo admite. Acuse: GPIO24 físico 18 a GND físico 9.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

gpiozero y csv. Un aviso abierto no se duplica en el archivo hasta que lo acusas y vuelve a haber calma.

import csv
from datetime import datetime
from gpiozero import Buzzer, Button, LED, MotionSensor
from time import sleep
led = LED(17)
z = Buzzer(25)
pir = MotionSensor(5)
acuse = Button(24, pull_up=True, bounce_time=0.05)
abierto = False
inicio = None
try:
    while True:
        if pir.motion_detected and not abierto:
            abierto = True
            inicio = datetime.now().isoformat(timespec="seconds")
            led.on(); z.beep(on_time=0.15, off_time=0.4)
            print("aviso", inicio)
        if abierto and acuse.is_pressed:
            z.off(); led.off()
            with open("acuses.csv", "a", newline="") as f:
                csv.writer(f).writerow([inicio, datetime.now().isoformat(timespec="seconds")])
            print("acusado")
            abierto = False
            sleep(0.3)
        sleep(0.05)
finally:
    z.off(); led.close()

Hardware: PIR Software: CSV Software: gpiozero Enseñanza: sensor, decisión y aviso local

Pasos

  1. Mide OUT del PIR.
  2. Ejecuta y quédate quieto.
  3. Provoca el PIR: LED y pitido, y no paran al irte.
  4. Pulsa el acuse. Callan y aparece la fila con dos horas.
  5. Sin pulsar, no hay fila de cierre.
  6. No mandes el CSV a ningún sitio.

Circuito

PIRLED y zumbadoracuse GPIO24CSV

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

95 Clasificador por distancia

Fénix

Qué hace

Tres LED clasifican lo que hay delante: cerca, medio o lejos, con los metros que devuelve gpiozero. No es un modelo que aprenda. Los cortes los escribes tú en metros de maqueta. El eco no entra directo.

Materiales

Cableado

TRIG GPIO5 físico 29, ECHO con 1 kΩ al GPIO6 físico 31 y 2 kΩ a GND físico 20. VCC del sensor al 5 V físico 2, GND físico 14.

LED cerca GPIO17 físico 11, medio GPIO27 físico 13, lejos GPIO22 físico 15, cada uno con 330 Ω, cátodos a GND físico 6.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

DistanceSensor. Por debajo de 0,2 m es cerca; hasta 0,6 m es medio; más lejos, el tercer LED. max_distance evita esperas infinitas.

from gpiozero import DistanceSensor, LED
from time import sleep
cerca, medio, lejos = LED(17), LED(27), LED(22)
sensor = DistanceSensor(echo=6, trigger=5, max_distance=1.2)
try:
    while True:
        d = sensor.distance
        cerca.off(); medio.off(); lejos.off()
        if d < 0.2:
            cerca.on(); clase = "cerca"
        elif d < 0.6:
            medio.on(); clase = "medio"
        else:
            lejos.on(); clase = "lejos"
        print(round(d, 2), clase)
        sleep(0.2)
finally:
    cerca.close(); medio.close(); lejos.close(); sensor.close()

Hardware: HC-SR04 Software: gpiozero

Pasos

  1. Divisor primero.
  2. Ejecuta y mide con una regla los cambios de LED.
  3. Si el salto no cae donde quieres, cambia 0,2 y 0,6, que son tuyos.
  4. No conviertas esos metros en otra unidad con un factor inventado: ya vienen de la librería.
  5. Un solo LED cada vez.
  6. Ctrl+C.

Circuito

HC-SR04cercamediolejos

Variantes

Fallos típicos

Franja de precio

Franja de precio: poco (≈1–3 €). Material añadido, sin contar la Pi.

Depuración

96 Control de acuario o terrario

Fénix

Qué hace

Pensado como terrario o como acuario de baja tensión: la sonda estanca da los grados del kernel y un relé puede mover un ventilador o una bomba de 5–12 V. Una LDR enciende el LED de día. No conmuto un calentador de red. Prohibido: no uses 230 V. La unión de los cables de la sonda no va al agua si no está aislada.

Materiales

Cableado

Sonda en GPIO4 físico 7, pull-up 4,7 kΩ al 3,3 V físico 1, GND físico 9. LDR entre 3,3 V y GPIO27 físico 13, condensador a GND físico 20.

LED de día GPIO17 físico 11 con 330 Ω a GND físico 6. Relé GPIO12 físico 32, bobina al 5 V físico 2, GND físico 14. La carga del relé es solo de 5–12 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Overlay w1-gpio. El relé usa histéresis corta para no tabletear. El día es LightSensor, no un luxómetro.

import glob, os, time
from gpiozero import LED, LightSensor, OutputDevice
ALTA, BAJA = 28.0, 26.5
led = LED(17)
ldr = LightSensor(27)
rele = OutputDevice(12, active_high=False, initial_value=False)
on = False

def grados():
    rutas = glob.glob("/sys/bus/w1/devices/28-*/w1_slave")
    if not rutas:
        raise SystemExit("Sin sonda. No invento grados ni enciendo el rele.")
    texto = open(rutas[0]).read()
    if "YES" not in texto:
        return None
    return int(texto.strip().split("t=")[-1]) / 1000.0

print("Carga de 5 a 12 V. Prohibido: no uses 230 V.")
try:
    while True:
        led.value = ldr.value < 0.35
        g = grados()
        if g is not None:
            if not on and g >= ALTA:
                on = True
            elif on and g <= BAJA:
                on = False
            rele.value = on
            print(g, "rele" if on else "quieto", "luz" if led.value else "dia")
        time.sleep(2)
finally:
    rele.off(); led.close()

Hardware: DS18B20 Software: 1-Wire Enseñanza: sensor de temperatura

Pasos

  1. Aísla la unión de la sonda antes de acercarla al agua. La cápsula puede mojarse; el empalme, no, si está desnudo.
  2. No conectes un calentador de acuario de red al relé.
  3. Ejecuta y lee los grados del kernel.
  4. Tapa la LDR: el LED de apoyo enciende. Eso es noche de maqueta, no un ciclo solar medido.
  5. Calienta la sonda por encima de ALTA solo con la mano, con el ventilador de 5–12 V.
  6. Al bajar de BAJA, el relé abre.

Circuito

sondaLDRLEDrelé5-12 V

Variantes

Fallos típicos

Franja de precio

Franja de precio: bastante (≈6–9 €). Material añadido, sin contar la Pi.

Depuración

97 Comedero con horario

Fénix

Qué hace

Un script mueve un solo SG90 de abierto a cerrado, una vez, y termina. La hora la pone cron en esta Pi, con una línea que tú escribes en tu crontab. No hay servicio en la nube. Si además tienes el RTC de otra ficha, la hora del sistema puede sostenerse sin red; esta ficha no lee el chip, dispara el servo.

Materiales

Cableado

Señal GPIO13 físico 33. Rojo del servo al 5 V físico 2, un solo SG90. Negro o marrón a GND físico 6.

Nada de la señal al pin de 5 V. No cuelgues el comedero de un servo grande alimentado por la Pi.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

El programa es corto a propósito: cron lo lanza y se acaba. La línea de ejemplo es 0 8 * * * y la ruta de tu archivo. La editas con crontab -e en el aparato.

from gpiozero import Servo
from time import sleep
s = Servo(13)
try:
    s.max()
    sleep(0.8)
    s.min()
    sleep(0.8)
finally:
    s.detach()
    s.close()

Hardware: Servo SG90 Software: cron Software: PWM Enseñanza: PWM, brillo y servo

Pasos

  1. Prueba el script a mano: el servo abre y cierra una vez.
  2. crontab -e y una línea con la hora, el minuto y python3 más la ruta del archivo. Ejemplo de minuto y hora: 30 7.
  3. Guarda y sal. No hace falta reiniciar.
  4. Espera a esa hora o cambia el minuto para una prueba.
  5. Mira el syslog o simplemente el comedero.
  6. Quita la línea cuando ya no quieras disparos.

Circuito

cron localun disparoSG90

Variantes

Fallos típicos

Franja de precio

Franja de precio: medio (≈3–6 €). Material añadido, sin contar la Pi.

Depuración

98 Maletín de los montajes de Enseñanza

Fénix

Qué hace

El maletín guarda las piezas de las prácticas de Enseñanza y nada más: LED con resistencia, la misma salida encendida desde Python, pulsador y rebote, PWM con brillo y servo, sensor de temperatura, I2C con varios sensores, foto en el aparato, y un sensor que decide y avisa en local. No abre otro curso. Como cierre, no como montaje del maletín, Enseñanza habla del arranque al encender y de la copia de la tarjeta. Esas dos no se cablean aquí.

Materiales

Cableado

No los montes todos a la vez. LED de práctica: GPIO17 físico 11, 330 Ω, GND físico 6. Pulsador: GPIO23 físico 16 a GND físico 9. Servo: señal GPIO13 físico 33, 5 V físico 2 solo para un SG90, GND físico 6.

Temperatura: GPIO4 físico 7 con 4,7 kΩ al 3,3 V físico 1. I2C: SDA físico 3 y SCL físico 5, módulos a 3,3 V. La cámara, al CSI o al USB, no al header. Si una práctica usa el físico 11, quita el servo de en medio antes de cambiar de ficha.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

Cada práctica se hace con el código de su lección, no con un programa nuevo de este maletín. gpiozero es el hilo común de las salidas. Este script solo enciende el LED de comprobación de que el maletín está vivo y recuerda que no estás en otro curso.

from gpiozero import LED
from time import sleep
led = LED(17)
print("Maletin de Enseñanza. No es otro curso.")
print("Montajes: LED, salida en Python, pulsador, PWM y servo, temperatura, I2C, foto, aviso local.")
print("Cierre, no montaje: arranque al encender, y copia de la tarjeta.")
try:
    for _ in range(3):
        led.on(); sleep(0.2); led.off(); sleep(0.2)
finally:
    led.close()

Hardware: GPIO Software: gpiozero Enseñanza: LED y resistencia Enseñanza: encender esa salida desde Python Enseñanza: pulsador y rebote Enseñanza: PWM, brillo y servo Enseñanza: sensor de temperatura Enseñanza: I2C, varios sensores Enseñanza: foto y vídeo en el aparato Enseñanza: sensor, decisión y aviso local

Pasos

  1. Abre la lección del LED y de la resistencia y monta solo eso.
  2. Pasa a encender la salida desde Python con el mismo LED.
  3. Cambia al pulsador cuando esa práctica lo pida, sin dejar el servo puesto si estorba.
  4. La foto se hace en el aparato, con la cámara guardada en el maletín, no subida.
  5. El aviso local se queda en la mesa.
  6. Cuando acabes el recorrido, el arranque al encender y la copia de la tarjeta son el cierre, no dos prácticas extra del maletín.

Circuito

maletínleccionesno otro curso

Variantes

Fallos típicos

Franja de precio

Franja de precio: consultar (>10 €). Material añadido, sin contar la Pi.

Depuración

Software: el LED no enciende

99 Panel que muestra el estado real de los GPIO

Fénix

Qué hace

Lee el estado real de una lista de GPIO del header y lo imprime. No enciende nada para adornar el panel. Si lgpio no está o no encuentra el chip de la cabecera, se para y lo dice: no rellena una tabla de ceros. En la Pi 5 el chip del header no es el mismo número que en una Pi 4; el script prueba, no adivina.

Materiales

Cableado

No pongas salidas en este experimento. Si hay un botón, que sea el de siempre: GPIO23 físico 16 a GND físico 9, pull-up.

No leas como entrada un pin que otro programa está usando de salida. No toques el físico 2 ni el 4. GPIO2 y GPIO3 se quedan fuera de la lista porque son I2C.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

lgpio, el mismo backend que gpiozero usa por debajo. Se reclama el pin como entrada un instante, se lee y se suelta. Si el import falla, no hay estado falso.

PINS = (17, 27, 22, 23, 24, 25)
try:
    import lgpio
except ImportError:
    raise SystemExit("No esta lgpio. No invento el estado de los GPIO.")

def abrir():
    for n in (4, 0, 10):
        try:
            h = lgpio.gpiochip_open(n)
        except Exception:
            continue
        try:
            lgpio.gpio_claim_input(h, 17, lgpio.SET_PULL_UP)
            lgpio.gpio_free(h, 17)
            return h
        except Exception:
            lgpio.gpiochip_close(h)
    raise SystemExit("No encuentro el gpiochip del header. No invento niveles.")

h = abrir()
try:
    for pin in PINS:
        lgpio.gpio_claim_input(h, pin, lgpio.SET_PULL_UP)
        print("GPIO", pin, "nivel", lgpio.gpio_read(h, pin))
        lgpio.gpio_free(h, pin)
finally:
    lgpio.gpiochip_close(h)

Hardware: GPIO Software: gpiozero Software: libgpiod

Pasos

  1. Instala el paquete de la distro si falta: python3-lgpio. No hace falta red si ya está en la imagen.
  2. Ejecuta con la mesa quieta.
  3. Anota el nivel del GPIO23.
  4. Cierra un botón a masa en ese pin, si lo tienes, y ejecuta otra vez: el nivel tiene que cambiar.
  5. Si el script sale diciendo que no hay chip, no completes la tabla a mano.
  6. No reclames el GPIO2.

Circuito

headerlgpionivel real

Variantes

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración

100 Proyecto final: informe offline de un sistema propio

Fénix

Qué hace

Reúne en un HTML local lo que esta Pi puede decir de verdad: temperatura de la CPU, espacio libre en disco y, si hay un DS18B20, sus grados de kernel. Lo que no está, se escribe como ausente. No abre la red, no llama a ninguna analítica y no inventa el sistema que todavía no has construido. Los sensores de las fichas anteriores no se vuelven a comprar para esta: la franja es irrisorio porque el informe no añade un aparato.

Materiales

Cableado

Si no hay sonda, no cablees nada: el informe sale igual.

Si la hay: datos GPIO4 físico 7, pull-up 4,7 kΩ al 3,3 V físico 1, GND físico 9, como en la ficha del DS18B20. No estrenes pines nuevos. Cada GPIO sigue siendo de 3,3 V.

El header de 40 es el mismo en Pi 4 y Pi 5.

Software

cpu de gpiozero, disco de shutil, sonda de sysfs. El HTML se escribe con marcas escapadas en un archivo de esta carpeta. Un CSV hermano guarda la misma fila, por si quieres la tabla pelada.

import csv, glob, html, os, shutil
from datetime import datetime
from gpiozero import CPUTemperature

def sonda():
    rutas = glob.glob("/sys/bus/w1/devices/28-*/w1_slave")
    datos = []
    for ruta in rutas:
        ident = os.path.basename(os.path.dirname(ruta))
        texto = open(ruta).read()
        if "YES" not in texto:
            datos.append((ident, "CRC malo, no uso la cifra"))
        else:
            datos.append((ident, str(int(texto.strip().split("t=")[-1]) / 1000.0)))
    return datos

cpu = round(CPUTemperature().temperature, 1)
libre = shutil.disk_usage(".").free
sondas = sonda()
ahora = datetime.now().isoformat(timespec="seconds")
with open("informe.csv", "a", newline="") as f:
    csv.writer(f).writerow([ahora, cpu, libre, sondas])
partes = ["<!DOCTYPE html><html lang=es><meta charset=utf-8><title>Informe</title><body>",
          "<h1>Informe offline</h1>",
          "<p>Sin red. Sin analitica. %s</p>" % html.escape(ahora),
          "<p>CPU %s C. Disco libre %s bytes.</p>" % (cpu, libre)]
if not sondas:
    partes.append("<p>DS18B20 ausente. No invento grados.</p>")
else:
    for ident, g in sondas:
        partes.append("<p>%s: %s</p>" % (html.escape(ident), html.escape(g)))
partes.append("<p>Completa a mano que fichas usaste. Este archivo no las adivina.</p></body></html>")
open("informe-offline.html", "w").write("".join(partes))
print("escrito informe-offline.html")

Hardware: GPIO Software: CSV Software: Python

Pasos

  1. Para el resto de programas que usen los mismos GPIO, si los hay.
  2. Ejecuta este script en la carpeta del proyecto.
  3. Abre informe-offline.html con el navegador, en file, sin conexión.
  4. Comprueba que la CPU no es un número redondo puesto a mano y que el disco libre se parece al que ves con otra herramienta del sistema.
  5. Si esperabas sondas y dice ausente, revisa el overlay, no el párrafo.
  6. Escribe tú, debajo, qué fichas forman tu sistema. El programa deja el hueco y no lo rellena.

Circuito

CPUdiscosonda si hayinforme local

Variantes

Fallos típicos

Franja de precio

Franja de precio: irrisorio (<1 €). Material añadido, sin contar la Pi.

Depuración