Controlemos entradas y salidas de una Raspberry PI en Python – Parte 1

Vista de la interfaz para raspberry PI con pulsadores y relés

En el proyecto precedente, "Controlando leds con una Raspberry PI", hemos visto como trabajar con las entradas y las salidas de una Raspberry PI a través de un pequeño programa de tipo terminal. Esta vez veremos un programa con interfaz gráfica, que permite de activar 3 relés y ver el estado de 3 entradas digitales.
Como en el caso anterior este proyecto es mas bien un ejemplo de integración entre electrónica y software, pueden estudiarlo para conocer la tecnología empleada, modificarlo o usarlo como inspiración para desarrollar proyectos más complejos.

Si necesitan instalar desde 0 il sistema operativo en vuestra Raspberry PI les aconsejo de leer nuevamente mi artículo anterior  "Controlando leds con una Raspberry PI" donde explico en modo detallado como bajar el software NOOBS, instalarlo y actualizarlo.

El programa del proyecto.
Vista del programa que desarrollaremos en este proyecto.

Antes de proseguir les cuento que actualmente estoy usando una Raspberry PI modelo 3, sin necesidad de conectar un monitor, teclado o mouse porque la veo y la controlo en modo remoto a través de mi red WiFi con el programa VNC. Es muy cómodo de usar y además es gratis. En la Raspberry, el VNC está ya instalado mientras que en el PC hay que bajarlo e instalarlo. Una vez instalado y activado en la Raspberry, les aconsejo de ir a la configuración del modem / router WiFi y de dar una dirección IP fija a la Raspberry (en mi caso por ejemplo puse 192.168.0.90) en modo tal que el DHCP asigne siempre la misma dirección a la Raspberry y no tener que buscar el IP cada vez que la encendemos.

VNC Viewer para trabajar con la Raspberry PI en modo remoto a través de la WiFi.
VNC Viewer para trabajar con la Raspberry PI en modo remoto a través de la WiFi.

Volviendo al proyecto, hacer un programa con interfaz gráfica (GUI: graphical user interface) tiene su complejidad. Para lograrlo conviene apoyarse a una librería software ya existente. Existen librerías gráficas de muchos tipos pero al final de cuentas, todas ellas sirven para crear las clásicas ventanas que estamos acostumbrados a ver y a usar en las pantallas de nuestros ordenadores.

Hay librerías que están proyectadas para funcionar con un determinado sistema operativo mientras que otras pueden funcionar con más de uno de ellos. Adoptar una librería en lugar de otra depende de varios factores como por ejemplo el lenguaje de programación que queremos usar, el sistema operativo, si están ya incorporadas en ambientes de desarrollo visual, la complejidad y la potencia que necesitamos, la "portabilidad del código" y otros factores.

Librerías gráficas para Python.
Algunas librerías gráficas para Python.

Si usamos Python con Linux, que es el sistema operativo de la Raspberry PI, la elección se simplifica bastante: las librerías más populares son PyQt (derivada de QT), PyGTK (derivada de GTK+), wxPython (derivada de wxWidgets) e Tkinter (derivada de Tcl/Tk). Esta última es la menos sofisticada pero al mismo tiempo la más liviana y fácil de usar. Tkinter funciona con Linux, iOS y también con Windows. Además, se encuentra ya incorporada en la distribución de Python, sin necesidad de ser instalada aparte. Por estos motivos, la adoptaremos para nuestro primer programa gráfico.

El hardware

 

Circuito de conexión de las entradas con pulsadores.
Circuito de conexión de las entradas con pulsadores.

Empecemos con el hardware: 3 botones y 3 relés. Para los botones usaremos el mismo circuito del proyecto anterior pero esta vez las resistencias estarán conectadas a masa mientras que los pulsadores a positivo como podemos ver en la figura. De esta forma la lógica será positiva: en reposo las entradas de la Raspberry se encuentran a masa y cuando activamos los botones, pasan a positivo. La lógica de la Raspberry PI es de 3,3V por lo tanto nuestro positivo no debe superar esta tensión. El modo más simple es el de usar los 3,3V ya disponibles en el pin 17 del conector principal de la Raspberry.

Circuito de conexión de las salidas con relés.
Circuito de conexión de las salidas con relés.

Para los relés necesitamos una alimentación separada cuya tensión dependerá de la bobina de los relés. Yo he usado relés de 12V. Para activarlos podemos usar transistores comunes como explico detalladamente en mi artículo "controlar un relé con un transistor" o con un circuito integrado ULN2003 que consiste en 7 drivers hechos con transistores en configuración Darlington y del cual escribiré próximamente un artículo.

Circuito integrado ULN2003 para controlar los relés de salida.
Circuito integrado ULN2003 para controlar los relés de salida.

Ambas soluciones son correctas pero con el ULN2003 el circuito es más simple de hacer, especialmente en el caso de necesitar de muchos canales de salida. El ULN2003 lleva ya a bordo los diodos de protección para la activación de los relés por lo que no es necesario instalarlos externamente, en paralelo con las bobinas. Debemos solamente conectar el pin 9 del integrado al positivo que alimenta los relés.

Para escribir el programa usaremos un editor de texto pensado para Python, que se llama Python3 IDLE y que ya se encuentra instalado en nuestra Raspberry PI. Podemos abrirlo desde:

menu principal -> Programming -> Python 3 (IDLE) como podemos ver en la figura siguiente.

Menú para abrir directamente Python 3 IDLE
Menú para abrir directamente Python 3 IDLE

Existe mucha documentación on-line sobre IDLE por lo tanto me limitaré solo a los elementos necesarios para escribir y hacer funcionar el programa. Si lo hemos abierto para verlo, ahora podemos cerrarlo porque para llegar a él usaremos otro modo que a continuación describo.

Preparando el ambiente de trabajo

Cuando abrimos el file manager de Linux (el tercer icono en alto de la pantalla, con el diseño de una carpeta amarilla) el sistema operativo nos muestra el contenido de la carpeta del usuario principal que es "pi". La primera cosa que haremos es la de crear una carpeta llamada "Inventable" donde pondremos los files de trabajo que iremos desarrollando ahora y en el futuro. Para ello abrimos el menú:

File - > Create News -> Folder

Nos aparecerá una ventana de dialogo en la cual escribiremos "Inventable" y después hacemos click sobre "OK".

Nueva carpeta Inventable
Nueva carpeta Inventable

Al interno de la carpeta "pi" aparecerá la nueva carpeta llamada "Inventable". Hacemos doble click sobre ella para ver su contenido. Estará vacía. Con el mismo método anterior, dentro de "Inventable" podemos crear la carpeta "in_out_gui". En las figuras de este artículo verán otras carpetas que he creado y que servirán para otros proyectos.

Nueva carpeta "in_out_gui" dentro de Inventable donde escribiremos nuestros programas.
Nueva carpeta "in_out_gui" dentro de Inventable donde escribiremos nuestros programas.

Por ahora no sirven, usaremos solo la carpeta llamada "in_out_gui". Hacemos doble click sobre "in_out_gui" para posicionarnos dentro de ella y después, apretamos el botón derecho del mouse, se abrirá un menú contextual en el cual elegiremos:

Create New -> Empty File.

A este nuevo file lo llamaremos "in_out_gui_v1.py".

Creación de un archivo vacío donde escribiremos el programa.
Creación de un archivo vacío donde escribiremos el programa.

Haciendo doble click sobre él se abrirá automáticamente el editor IDLE que nos mostrará lógicamente, un documento vacío porque todavía no hemos escrito nada. El sistema operativo eligió automáticamente IDLE como editor porque nuestro archivo tiene la extensión "py", típica de los programas Python.

File vacío llamado in_out_gui_v1.py abierto con Python 3 IDLE.
File vacío llamado in_out_gui_v1.py abierto con Python 3 IDLE.

Ahora escribimos el siguiente texto en el file vacío para verificar que los componentes software necesarios para nuestro proyecto estén correctamente instalados (la librería gráfica Tkinter y la librería de gestión de las entradas y las salidas RPi.GPIO).

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import RPi.GPIO as io           
import tkinter as tk
from tkinter import ttk

Una vez hecho esto probamos a ejecutarlo desde el menú RUN -> Run Module del editor o directamente usando la tecla de función F5.

Si en la pantalla nos aparece el mensaje de la figura, sin indicaciones de error, quiere decir que todo está funcionando bien y podemos escribir nuestro programa.

Imagen que muestra el interprete Python que ha cargado correctamente las librerías que usaremos.
Imagen que muestra el interprete Python que ha cargado correctamente las librerías que usaremos.

A continuación les muestro la primera versión completa del programa y sucesivamente explicaré cada parte de él. Ustedes pueden escribirlo todo a mano (es un buen ejercicio para familiarizarse con el lenguaje) o copiarlo y pegarlo en el documento vacío.

Para mayor claridad, he usado instrucciones muy simples y evitando técnicas de programación avanzadas (como por ejemplo objetos). Les recuerdo que Python divide lógicamente los bloques de un programa con dos puntos seguidos por la identación del código (sangría) y que es mejor que esta esté hecha con una cantidad igual de espacios (por norma 4 espacios). No mezclemos espacios y tabulaciones en el mismo programa porque nos dará un error.

Primera versión del programa


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""-------------------------------------------------------------------------------
 Project:       In/Out with GUI (graphical user interface)
 File:          in_out_gui_v1.py
 Version:       1.0
 Purpose:       Controlar 3 entradas y 3 salidas
 Author:        Gabriel Rapetti / Inventable.eu
 Created:       21/10/2017
 Copyright:     Creative Commons (CC) BY-NC-SA  
-------------------------------------------------------------------------------
"""

import RPi.GPIO as io           # Importa libreria de I/O (entradas / salidas)
import tkinter as tk            # Importa la libreria grafica (GUI)  
from tkinter import ttk

# Configuracion de las entradas y las salidas del micro
# -----------------------------------------------------
in1 = 23                        # en las variables in1, in2, in3
in2 = 24                        # memoriza los números de los puertos
in3 = 25                        # de entrada

out1 = 17                       # en las variables out1, out2 y out3
out2 = 27                       # memoriza los números de los puertos
out3 = 22                       # de salida

io.setmode(io.BCM)              # modo in/out pin del micro
io.setwarnings(False)           # no señala advertencias de pin ya usados

io.setup(in1,io.IN)             # configura en el micro las entradas
io.setup(in2,io.IN)
io.setup(in3,io.IN)

io.setup(out1,io.OUT)           # configura en el micro las salidas
io.setup(out2,io.OUT)
io.setup(out3,io.OUT)

# Funcion de lectura de las entradas
# -----------------------------------
def check_inputs():
    if io.input(in1):
        label1.config(bg = '#FD4')        # si la entrada 1 es a 1 enciende la label
    else:
        label1.config(bg = '#BBB')        # si no lo es la apaga

    if io.input(in2):
        label2.config(bg = '#FD4')        # si la entrada 2 es a 1 enciende la labe2
    else:
        label2.config(bg = '#BBB')        # si no lo es la apaga

    if io.input(in3):
        label3.config(bg = '#FD4')        # si la entrada 1 es a 1 enciende la labe3
    else:
        label3.config(bg = '#BBB')        # si no lo es la apaga

    root.after(50, check_inputs)          # activa un timer de 50mSeg.
                                          # para retornar a esta función

# Funciones de las salidas activadas por los pulsadores
# -----------------------------------------------------
def on_off1():
    if push_button1.cget("bg")=='#FD4':   # si el botón 1 está encendido
        push_button1.config(bg = '#DDD')  # lo apaga y
        io.output(out1,0)                 # pone a 0 el pin de salida
    else:                                
        push_button1.config(bg = '#FD4')  # de otro modo  lo enciende y
        io.output(out1,1)                 # pone a 1 el pin de salida      

def on_off2():
    if push_button2.cget("bg")=='#FD4':   # si el botón 2 está encendido
        push_button2.config(bg = '#DDD')  # lo apaga y
        io.output(out2,0)                 # pone a 0 el pin de salida
    else:
        push_button2.config(bg = '#FD4')  # de otro modo  lo enciende y
        io.output(out2,1)

def on_off3():
    if push_button3.cget("bg")=='#FD4':   # si el botón 3 está encendido
        push_button3.config(bg = '#DDD')  # lo apaga y
        io.output(out3,0)                 # pone a 0 el pin de salida
    else:
        push_button3.config(bg = '#FD4')  # de otro modo lo enciende y
        io.output(out3,1)                 # pone a 1 el pin de salida

# Creación de la ventana principal
# -----------------------------------------------------

root = tk.Tk()                  # Crea una instancia de la librería
                                # Tkinter con el nombre "root"
root.title("In/Out Panel")      # Título del programa
root.geometry("270x110")        # Dimensiones de la ventana

# Creación de los indicadores para las entradas (labels)
# -----------------------------------------------------

label1 = tk.Label(root, text = "IN 1", width = 9, bg = '#CCC')
label1.place(x = 20, y = 15)

label2 = tk.Label(root, text = "IN 2", width = 9, bg = '#CCC')
label2.place(x = 100, y = 15)

label3 = tk.Label(root, text = "IN 3", width = 9, bg = '#CCC')
label3.place(x = 180, y = 15)

# Creación de los botones (buttons)
# -----------------------------------------------------
push_button1 = tk.Button(root, text = "OUT 1", width = 6, command = on_off1)
push_button1.place(x = 20, y = 60)

push_button2 = tk.Button(root, text = "OUT 2", width = 6, command = on_off2)
push_button2.place(x = 100, y = 60)

push_button3 = tk.Button(root, text = "OUT 3", width = 6, command = on_off3)
push_button3.place(x = 180, y = 60)

# Ciclo principal del programa
# -----------------------------

check_inputs()     # Llama por primera vez la función que lee las entradas
root.mainloop()    # Loop principal

# Fin del Programa

Para probar el programa apretamos la tecla F5 (RUN). Debería aparecer una ventana como la que vemos en la figura siguiente:

Ejecución correcta del programa.
Ejecución correcta del programa.

Haciendo click con el mouse sobre los botones de la ventana podemos activar o desactivar los relés mientras que accionando los pulsadores físicos veremos el cambio de estado de las entradas respectivas en el programa.

El programa del proyecto.
El programa del proyecto.

Descripción de la primera versión del programa

Ahora, veamos el programa por partes seguido por una descripción:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

Estos dos renglones sirven para decirle a linux que el file en cuestión es un programa Python (también llamado script) y que debe ser ejecutado a través del interprete Python. El segundo renglón dice al interprete de codificar el programa en formato UTF-8 (Unicode Transformation Format, 8 bit).

"""----------------------------------------------------------------------------
Project: In/Out with GUI (graphical user interface)
File: in_out_gui_v1.py
Version: 1.0
Purpose: Controlar 3 entradas y 3 salidas
Author: Gabriel Rapetti / Inventable.eu
Created: 21/10/2017
Copyright: Creative Commons (CC) BY-NC-SA
-------------------------------------------------------------------------------
"""

Los renglones entre grupos de tres comillas (""") son una zona para escribir comentarios y serán ignorados durante la ejecución del programa por parte del interprete Python. También se pueden hacer comentarios poniendo el símbolo # al inicio de cada renglón pero la comillas permiten de crear comentarios que después generarán automáticamente documentación técnica del proyecto. Por este motivo se usan en las descripciones más importantes del programa. Yo generalmente, creo un espacio de presentación inicial donde agrego datos generales del proyecto (nombre, autor, fecha, etc.).

import RPi.GPIO as io           
import tkinter as tk            
from tkinter import ttk

Estos tres renglones importan las librerías que usaremos para nuestro programa: RPi.GPIO para las entradas y salidas de las Raspberry, Tkinter para la parte gráfica y ttk que es una extensión de Tkinter.

in1 = 23
in2 = 24
in3 = 25

out1 = 17
out2 = 27
out3 = 22

io.setmode(io.BCM)              
io.setwarnings(False)           

io.setup(in1,io.IN)
io.setup(in2,io.IN)
io.setup(in3,io.IN)

io.setup(out1,io.OUT)
io.setup(out2,io.OUT)
io.setup(out3,io.OUT)

Esta parte del programa configura los puertos 23, 24 y 25 del microcontrolador como entradas y los puertos 17, 27 y 22 como salidas.

def check_inputs():
    if io.input(in1):
        label1.config(bg = '#FD4')
    else:
        label1.config(bg = '#BBB')

    if io.input(in2):
        label2.config(bg = '#FD4')
    else:
        label2.config(bg = '#BBB')

    if io.input(in3):
        label3.config(bg = '#FD4')
    else:
        label3.config(bg = '#BBB')

    root.after(50, check_inputs)

Aquí tenemos una función de Python. Se reconoce por la palabra clave "def" seguida por el nombre de la función y una zona entre paréntesis donde poder pasar parámetros. En este caso no hay nada entre paréntesis porque no es necesario pasar algún parámetro. Esta función controla las entradas in1, in2, in3 del micro (que son variables asignadas a los puertos 23, 24 y 25 del micro) y en base a su estado lógico (alto o bajo) cambia el color de fondo de las label1, label2 y label3. Las labels son rectángulos con un texto dentro. Pueden ser de color o trasparentes. Serán creadas más adelante. El último renglón (root.after) dice al programa de retornar a esta función después de 50 milisegundos ("50" pasado como parámetro y que se calcula en milisegundos) . De esta forma se logra obtener un control periódico de las entradas.

def on_off1():
    if push_button1.cget("bg")=='#FD4':
        push_button1.config(bg = '#DDD')
        io.output(out1,0)
    else:
        push_button1.config(bg = '#FD4')
        io.output(out1,1)        

def on_off2():
    if push_button2.cget("bg")=='#FD4':
        push_button2.config(bg = '#DDD')
        io.output(out2,0)
    else:
        push_button2.config(bg = '#FD4')
        io.output(out2,1)

def on_off3():
    if push_button3.cget("bg")=='#FD4':
        push_button3.config(bg = '#DDD')
        io.output(out3,0)
    else:
        push_button3.config(bg = '#FD4')
        io.output(out3,1)

Estas son tres funciones casi iguales, con la diferencia que cada una de ellas trabaja con cada botón que activan los relés. Son llamadas automáticamente cuando apretamos uno de los botones diseñados en la pantalla. Como tenemos un solo botón para activar y desactivar cada relé, este trabaja como un flip flop de tipo "T" (o toggle), es decir, si el relé está activado, cuando lo tocamos lo desactivamos y viceversa. Para obtener este resultado, el programa lee el color del botón, si este es '#FD4' (encendido) lo apaga poniendo el color '#DDD' (apagado) y al mismo tiempo cambia el estado lógico de la salida, por ejemplo io.output(out1,0). Si por el contrario está apagado (color = '#DDD') lo enciende. Existen diferentes modos de trabajar con los colores en Python, en este programa uso la notación hexadecimal simplificada donde cada dígito es uno de los colores RGB, por lo tanto '#000' es negro y '#FFF' es blanco.

root = tk.Tk()
root.title("In/Out Panel")
root.geometry("270x110")        

label1 = tk.Label(root, text = "IN 1", width = 9, bg = '#BBB')
label1.place(x = 20, y = 15)

label2 = tk.Label(root, text = "IN 2", width = 9, bg = '#BBB')
label2.place(x = 100, y = 15)

label3 = tk.Label(root, text = "IN 3", width = 9, bg = '#BBB')
label3.place(x = 180, y = 15)

En esta parte creamos la ventana principal de nuestro programa que mide 270 pixel por 110 pixel y las tres labels que servirán para ver el estado de las entradas. El color inicial de ellas será "desactivado" (bg = '#BBB'). La sigla "bg" significa background color.

push_button1 = tk.Button(root, text = "OUT 1", width = 6, command = on_off1)
push_button1.place(x = 20, y = 60)

push_button2 = tk.Button(root, text = "OUT 2", width = 6, command = on_off2)
push_button2.place(x = 100, y = 60)

push_button3 = tk.Button(root, text = "OUT 3", width = 6, command = on_off3)
push_button3.place(x = 180, y = 60)

Aquí creamos los botones que activarán los relés. Observen el comando "command = on_off" que sirve para hacer la llamada automática cuando hacemos "click" con el mouse a una de las tres funciones "on_off" que hemos visto antes y que gestionan los botones.

check_inputs() 
root.mainloop()

El primer renglón es una llamada a la función chek_inputs() que lee las entradas por primera vez, después no será más necesario llamarla desde aquí porque la misma función de lectura de las entradas se llamará a si misma.

Para terminar root.mainloop() es el ciclo principal del programa. Este lo mantiene vivo, gestionando los eventos que llegan a él (por ejemplo las acciones del mouse o el timer para leer las entradas). El ciclo se interrumpe definitivamente cuando hacemos click sobre la cruz, en alto de nuestra ventana, terminando el programa.

En la segunda parte de este artículo veremos una nueva versión del programa, más flexible y modular, gracias al uso de matrices y de ciclos "for".

Hasta la próxima!!

Gabriel

Artículos relacionados:

- Controlemos entradas y salidas de una raspberry pi en python - parte 2

- Controlando leds con una Raspberry PI

- Como controlar un relé con un transistor

- Interfaz experimental para Raspberry PI

Indice de todos los artículos de Inventable

Los contenidos de este blog son originales y están bajo una licencia Creative Commons BY_NC_ND

Controlemos entradas y salidas de una Raspberry PI en Python – Parte 1 ultima modifica: 2018-02-01T17:09:30+00:00 da inventable

7 comentarios sobre “Controlemos entradas y salidas de una Raspberry PI en Python – Parte 1”

  1. Clarísimo como siempre. Estoy esperando que me compren la placa para empezar 🙂 .
    Me gusta mucho la inclusión del ULN2003. Si te ponés a contar, para tres relés necesitás dos soldaduras en la alimentación (comunes a todos) y solo dos por cada relé (8 soldaduras en este caso), mientras que si usás transistores son necesarias nueve por cada uno. Ya con tener que controlar solo dos relés te conviene usar el chip, tanto en soldaduras como en tamaño.
    Bueno, me fui del tema.
    Muy buen artículo Gabriel. A mí me está sirviendo mucho, aunque todavía no probé nada (no veo la hora). Espero el resto.

    Abrazo,
    Juan

    1. Gracias Juan, espero que vos también empieces a experimentar con la Raspberry. Respecto al ULN, lo uso desde hace muchos años, con sus variantes: el ULN2803 de 8 canales y el ULN2068 de 4 canales pero más potentes. Es muy versátil y no pierdo ocasión cuando tengo que hacer una placa con relés o con tiras de leds no muy largas. Estoy escribiendo un artículo, sintético.
      Un abrazo.

      Gabriel

  2. Te queria comentar que el pinout de la figura “Circuito de conexión de las entradas con pulsadores” no lo tienes bien.
    Los pines a usar no son los 14, 16, 18, 20.
    Son los 16, 18, 20, 22.
    Compáralo con tu otro pinout (controlando-leds-con-una-raspberry-pi) y verás que no son iguales.

    Un saludo.

    1. Hola Mateo, es correcto lo que dices. Ya he modificado los diseños.
      Muchas gracias por tu aguda observación. 🙂
      Hasta pronto.

      Gabriel

  3. Muy bueno Gabriel, este artículo me ha servido para terminarme de decidir: me voy a comprar la Raspberry Pi.
    Saludos.
    CacHarrytos.

    1. Hola Harry, lo mismo me decían HJ y Juan 🙂
      Andan medio ocupados con otras cosas y todavía no empezaron.
      Me gusta la idea, podría ser un territorio común sobre el cual experimentar y compartir nuestras experiencias.
      La segunda parte la publicaré dentro de un par de semanas así les doy el tiempo para familiarizarse.
      Hasta pronto.
      Gabriel

Deja un comentario

Tu dirección de correo electrónico no será publicada.

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.