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

Vista de la segunda versión del programa.
Vista de la segunda versión del programa.

En esta segunda parte del proyecto desarrollaremos una nueva versión del programa que hemos visto en la primera parte. El aspecto gráfico de la interfaz es parecido a la versión anterior pero este nuevo código es más elegante y permite una mayor modularidad, útil si necesitamos expandirlo para controlar más entradas o salidas.

A continuación transcribo el programa completo y después lo divido en partes dando la respectiva explicación. A este programa lo llamaremos "in_out_gui_v2.py" y lo guardaremos en la misma carpeta de la versión anterior.


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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""-------------------------------------------------------------------------------
 Project:       In/Out with GUI (graphical user interface)
 File:          in_out_gui_v2.py
 Version:       2.0
 Purpose:       Controlar 3 entradas y 3 salidas
 Author:        Gabriel Rapetti to Inventable.eu
 Created:       11/12/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         # Importa una extension de la libreria grafica

pin_out = (17,27,22)            # Tupla con los pin de salida del micro
pin_in = (23,24,25)             # Tupla con los pin de entrada del micro

COLOR_ON = '#FD4'               # Color botón o indicador encendido
COLOR_OFF_OSCURO = '#BBB'       # Color indicador apagado
COLOR_OFF_CLARO = '#DDD'        # Color botón apagado

push_button = []                # Lista (matriz) para los objetos pulsadores  
labels = []                     # Lista (matriz) para los objetos indicadores (labels)

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

# Configuración de las entradas y las salidas del micro
# -----------------------------------------------------
for indice in range(3):                 # Ciclo de 3 veces
    io.setup(pin_in[indice],io.IN)      # que configura los pines del micro
    io.setup(pin_out[indice],io.OUT)    # como entradas y como salidas

# Función que lee periodicamente las entradas
# -------------------------------------------
def check_inputs():
    for indice in range(3):                        # Ciclo que lee las entradas
        if io.input(pin_in[indice]):               # La entrada del índice es alta?
            labels[indice].config(bg = COLOR_ON)   # Enciende el indicador respectivo
        else:
            labels[indice].config(bg = COLOR_OFF_OSCURO) # por el contrario lo apaga
    root.after(50, check_inputs)                   # Programa el timer a 50mSeg.

# Funcion que activa las salidas cuando se apreta un pulsador
# -----------------------------------------------------------
def on_off(button_number):
    if push_button[button_number].cget("bg") == COLOR_ON:       # Si ya está encendido
        push_button[button_number].config(bg = COLOR_OFF_CLARO) # lo apaga
        io.output(pin_out[button_number],0)                     # y pone la salida a 0
    else:
        push_button[button_number].config(bg = COLOR_ON)        # Si está apagado lo
        io.output(pin_out[button_number],1)                     # enciende y salida a 1

root = tk.Tk()                                # Crea objeto Tkinter llamado root
root.title("In/Out Panel v2")                 # Título de nuesto programa
root.geometry("280x160")                      # Dimensiones de la ventana

# Crea los marcos de Input y de Output
# -------------------------------------------------------------
fra_out = ttk.Labelframe(root, text="Outputs", width = 260, height = 70)
fra_out.place(x = 10, y = 5)
fra_in = ttk.Labelframe(root, text="Inputs", width = 260, height = 65)
fra_in.place(x = 10, y = 80)

# Ciclo que crea los botones y los indicadores
# -----------------------------------------------
for indice in range(3):
    push_button.append(tk.Button(fra_out, text = "OUT " + str(indice + 1), width = 6 ))
    push_button[indice].place(x = 15 + indice * 78, y = 6 )
    push_button[indice].bind('',lambda event, nm = indice: on_off(nm))

    labels.append(tk.Label(fra_in, text = "IN " + str(indice + 1), width = 8, bg = '#CCC'))
    labels[indice].place(x = 20 + indice * 78, y = 10)  

# Primera lectura de las entradas y loop principal  
# ------------------------------------------------
check_inputs()                      # Primera lectura de las entradas
root.mainloop()                     # Ciclo principal

# FIN DEL PROGRAMA

Descripción del programa

Ahora, veamos el programa por partes:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""-------------------------------------------------------------------------------
 Project:       In/Out with GUI (graphical user interface) 
 File:          in_out_gui_v2.py
 Version:       2.0
 Purpose:       Controlar 3 entradas y 3 salidas
 Author:        Gabriel Rapetti to Inventable.eu
 Created:       11/12/2017
 Copyright:     Creative Commons (CC) BY-NC-SA  
-------------------------------------------------------------------------------
"""

Esta primera parte es igual a la versión 1

pin_out = (17,27,22)            # Tupla con los pin de salida del micro
pin_in = (23,24,25)             # Tupla con los pin de entrada del micro

Declaramos dos tuplas (que son matrices con datos no modificables) con los puertos de entrada y salida del micro que usaremos.

COLOR_ON = '#FD4'
COLOR_OFF_OSCURO = '#BBB'
COLOR_OFF_CLARO = '#DDD'

Declaramos 3 constantes para los colores de las teclas y de los indicadores (labels) porque es más claro usar nombres que números RGB hexadecimales. Además, si quisiéramos modificar los colores podemos hacerlo en un solo punto del programa y no en varios como en el programa anterior. Como en Python no existen las constantes usamos variables comunes pero sus nombre los escribimos con mayúsculas para recordarnos que son constantes.

push_button = []                
labels = []                    

Creamos 2 matrices vacías (en Python se llaman listas) que después llenaremos con los botones (push_button)  y los indicadores (labels) de nuestro panel.  Tanto los botones como los indicadores serán objetos. En las listas de Python se puede meter cualquier cosa, inclusive objetos.

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

for indice in range(3):
    io.setup(pin_in[indice],io.IN)
    io.setup(pin_out[indice],io.OUT)

Configuramos los puertos del micro como entradas y salidas usando un ciclo for. Este ciclo se repetirá 3 veces gracias a la instrucción in range (3). Podemos usar un ciclo for en lugar de instrucciones "fijas" como en el programa anterior porque disponemos de las matrices pin_out y pin_in declaradas anteriormente. Como pueden ver, el código es más compacto y flexible. Si quisiéramos aumentar el número de entradas y salidas deberíamos simplemente agregar los nuevos números en las matrices y aumentar el valor de interacciones con la instrucción range() en el ciclo for.

def check_inputs():
    for indice in range(3):
        if io.input(pin_in[indice]):
            labels[indice].config(bg = COLOR_ON)
        else:
            labels[indice].config(bg = COLOR_OFF_OSCURO)
    root.after(50, check_inputs)

Esta es la nueva versión de la función que lee las entradas. Por el hecho que las entradas del micro y los indicadores (labels) se encuentran al interno de matrices, aquí también podemos aplicar un eficiente y flexible ciclo for. Como en la versión anterior del programa, el renglón al final, con root.after(50, check_inputs) dice al programa de retornar automáticamente a esta función después de 50 milisegundos (50 es el tiempo en milisegundos y check_inputs es el nombre de la función que será llamada) .

def on_off(button_number):
    if push_button[button_number].cget("bg") == COLOR_ON:
        push_button[button_number].config(bg = COLOR_OFF_CLARO)
        io.output(pin_out[button_number],0)
    else:
        push_button[button_number].config(bg = COLOR_ON)
        io.output(pin_out[button_number],1)

Por el hecho que los botones son objetos instanciados y se encuentran dentro de matrices, podemos hacer una función sola para la conmutación de ellos en vez de hacer funciones separadas y "rígidas" como en el caso de la primera versión del programa. A esta nueva función pasamos como parámetro el número de botón que hemos activado.

root = tk.Tk()
root.title("In/Out Panel")
root.geometry("280x160")

Aquí creamos la ventana, le damos la dimensión que queremos (280 pixels por 160 pixels) y le agregamos el título del programa. Con root = tk.Tk() creamos una instancia de la librería gráfica Tkinter y que es nuestra aplicación. Por lo tanto, para llamar (mejor dicho "para referenciar") las  funciones internas de la interfaz gráfica Tkinter que usaremos en nuestro programa, llamadas "funciones miembro", debemos usar la palabra root seguida por un punto (por ejemplo root.after). Si en lugar de root = tk.Tk() hubiéramos escrito raíz = tk.Tk(), para llamar a las funciones miembro tendríamos que escribir por ejemplo: raíz.after. 

fra_out = ttk.Labelframe(root, text="Outputs", width = 260, height = 70)
fra_out.place(x = 10, y = 5)
fra_in = ttk.Labelframe(root, text="Inputs", width = 260, height = 65)
fra_in.place(x = 10, y = 80)

Para dar un aspecto más ordenado a nuestra ventana, creamos dos marcos visibles que dividen las entradas de las salidas  Para ello usamos una extensión llamada ttk de la misma librería Tkinter.

for indice in range(3):
    push_button.append(tk.Button(fra_out, text = "OUT " + str(indice + 1), width = 6 ))
    push_button[indice].place(x = 15 + indice * 78, y = 6 )
    push_button[indice].bind('',lambda event, nm = indice: on_off(nm))

    labels.append(tk.Label(fra_in, text = "IN " + str(indice + 1), width = 8, bg = '#CCC'))
    labels[indice].place(x = 20 + indice * 78, y = 10)

Esta parte quizás es la más complicada. En ella creamos gráficamente los botones y las labels usando las matrices push_button[] y labels[], que habíamos creado antes, escribimos los textos que se verán en ellas, le damos la ubicación en nuestra ventana y las dimensiones, todo ello usando siempre un ciclo for. La función miembro "bind" dice al programa que cuando hacemos click con el mouse sobre un botón en particular, se llamará automáticamente la función "on_off" que hemos descrito anteriormente, pasando como paramento el número del botón que hemos apretado. Para poder pasarle este parámetro usamos una función lambda como callback. Los callbacks de Python y las funciones "lambda" son temas relativamente complejos que van más allá de los objetivos de este artículo. Sinteticamente podemos decir que un callback es una función pasada como parámetro a otra función y una función lamba es una función "al vuelo" que podemos crear sin necesidad de ponerle un nombre y que su código debe estar en el mismo renglón donde la hemos declarado.  En nuestro caso, lo importante es que este código funciona correctamente, así como está escrito.

check_inputs()
root.mainloop()   

El primer renglón llama por primera vez la función que lee las entradas, después no será más necesario porque la misma función de lectura se llamará a si misma gracias a "root.after" que se encuentra dentro de ella.

Para terminar root.mainloop() es el ciclo principal del programa, que 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.

Bueno, he descrito con bastante detalle como funciona este programa. La programación en Python es un argumento extenso, complejo pero muy interesante. Es difícil, en un breve artículo, poder explicarla. Yo mismo soy un principiante en materia. El objetivo de estos artículos es mas bien mostrarles que es posible empezar el camino de la programación a través de ejemplos prácticos y útiles que integran perfectamente electrónica y software.

En el próximo artículo de esta serie entraré en un territorio muy interesante: usar la Raspberry como Server, controlando las entradas y las salidas a través de una conexión WiFi, por ejemplo usando nuestro teléfono para encender luces u otros aparatos de casa, siempre usando el lenguaje Python.
Hasta la próxima!!

Gabriel

logo_descarga

Programa In/Out en Python para Raspberry (88 descargas)

Artículos relacionados:

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

Controlando leds con una Raspberry PI

Como controlar un relé con un transistor

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 2 ultima modifica: 2018-02-26T09:15:08+00:00 da inventable

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

  1. Hola Gabriel, muy bueno.
    Tengo que esperar hasta el 28 para poder comprar la placa. Apenas lo haga y pruebe te cuento los resultados.
    Espero con ansia el tema del Wifi.

    Saludos,
    Juan

  2. Gabriel, ya me ha picado el gusanillo desde que estás publicando entradas sobre la Raspberry Pi. Ya me he comprado una… 😉

    Saludos.

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.