LEDs remotos (2/3)

Back-end: Control de los leds mediante Python y PHP.

Contenido:

Introducción

En esta entrada vamos a programar un módulo de Python para controlar el integrado SN74HC595 y dos scripts para apagar y encender los leds. También vamos a programar dos scripts de PHP que se encargaran de recibir las peticiones de la aplicación de gestión de los leds y ejecutar los scripts de Python.

La primera entrada de esta serie se puede consultar aquí y la tercera aquí.

En caso de usar el directorio por defecto de Apache para alojar ficheros, la estructura de ficheros, quedaría así:

Scripts de Python

El módulo shifter74hc595

Para el manejo del SN74HC595N vamos a crear un módulo de Python para usar tanto en esta aplicación como en posteriores que podamos realizar. Un módulo es un archivo que contiene funciones, clases, … que importarémos en otros ficheros para su uso. En este caso, el módulo contendrá una clase.

Esta clase contendrá una matriz donde se guardaran los estados (HIGH o LOW) de los leds en un momento determinado. La información de esta matriz se enviará al integrado cuando se quiera encender o apagar los leds, según convenga.

Para poder manejar los leds necesitaremos el módulo RPi.GPIO, lo importamos

import RPi.GPIO as GPIO

Declaramos la clase y cuatro propiedades de esta: tres para los pines de datos, reloj y fijar, y la matriz para guardar el estado de los 8 pines.

class shifter74hc595():

    data = None;
    clock = None;
    latch = None;

    leds = [None, None, None, None, None, None, None, None]

El método __init__

El método que inicializa el objeto al instanciarlo recibirá como parámetros los tres pins para los datos, reloj y fijar, y se encargará de asignar estos valores a sus propiedades, inicializar la GPIO (en modo BOARD) y los pines (en modo OUT). También ejecutará un par de métodos (se verán más adelante) para establecer todos los pins con un valor bajo (apagados).

def __init__(self, data, clock, latch):    
  self.data = data
  self.clock = clock
  self.latch = latch
    
  GPIO.setmode(GPIO.BOARD)
  GPIO.setwarnings(False)    
  GPIO.setup(self.data, GPIO.OUT)
  GPIO.setup(self.clock, GPIO.OUT)
  GPIO.setup(self.latch, GPIO.OUT)

  self.setAll(GPIO.LOW)
  self.sendData()

El método __del__

Añadimos un método __del__, para que cuando se destruya el objeto, apague los leds y limpie la configuración de la GPIO establecida en el método de inicio.

def __del__(self):    
  self.setAll(GPIO.LOW)
  self.sendData()
  GPIO.cleanup()

El método setLed

Este método permite establecer el valor para uno de los leds en concreto, representado por su índice en la matriz. Recibe como parámetros el índice del led a modificar y el valor.

def setLed(self, led, value):
  self.leds[led] = value

El método setAll

Este método permite establecer un valor común para todos los leds. El parámetro recibido es el valor a aplicar a los leds.

def setAll(self, value):
  for i in range(len(self.leds)):
    self.leds[i] = value

El método setOnLed

Este método permite encender led, modificando su estado. Recibe como parámetro el índice del led a modificar.

def setOnLed(self, led):
  self.setLed(led, GPIO.HIGH) 

El método setOffLed

Este método permite apagar led, modificando su estado. Recibe como parámetro el índice del led a modificar.

def setOffLed(self, led):
  self.setLed(led, GPIO.LOW)

El método __setDataPin

Este método envía un estado para la posición actual en la entrada de datos del integrado. Para esto envía un valor bajo al reloj para desactivarlo, posteriormente envía el estado que ha recibido como parámetro (HIGH o LOW) y, finalmente, pone en alto al reloj para que el dato enviado se inserte en la lista.

def __setDataPin(self, state):
  GPIO.output(self.clock, GPIO.LOW)
  GPIO.output(self.data, state)
  GPIO.output(self.clock, GPIO.HIGH)

El método sendData

Este método envía los datos alojados en la matriz de estados al integrado. Para esto, pone el pin para fijar en bajo y envía los estados al integrado. Cuando ya se han enviado todos activa el pin para fijar, de manera que se envían los datos a la salida paralela.

Se invierte el orden de la matriz ya que, al enviar un valor, este empuja al anteriormente enviado, quedando, una lista como la enviada pero en orden inverso.

Ejemplo de como se distribuyen los valores de una matriz según se van enviando al integrado.
def sendData(self):
  GPIO.output(self.latch, GPIO.LOW)
  for i in reversed(self.leds):
    self.__setDataPin(i)
  GPIO.output(self.latch, GPIO.HIGH)

Final del módulo shifter74hc595

El módulo siempre se va a importar desde otro script, por lo que no es necesario que haga nada en caso de ser ejecutado desde la línea de comandos. Para esto, incluimos al final del fichero una comprobación de la variable especial __name__. Si su valor es __main__, significa que se ha ejecutado desde la línea de comando, por lo que llamamos a pass para que no haga nada.

if __name__ == "__main__":
  pass

El script ledson.py

Este script se encarga de encender y apagar los leds según la información de la secuencia recibida desde la aplicación de gestión.

Como primer paso, importamos el módulo para el manejo del SN74HC595 y algunos otros:

  • sys: nos da funcionalidades y variables respecto al interprete de Python.
  • os: nos da funcionalidades y variables respecto al sistema operativo.
  • time: nos da funcionalidades para el manejo de fechas, horas y tiempo.
  • json: nos da funcionalidades para el manejo de datos en formato JSON.

También definimos una variable a la que le asignamos un objeto de la clase shifter74hc595, pasándole como parámetros los tres pines para datos, reloj y fijar.

Para finalizar, añadimos una comprobación de la variable especial __name__. Si es igual a __main__ (se esta ejecutando desde la línea de comandos), ejecutamos las ordenes necesarias para encender y apagar los leds según la secuencia.

Dentro de la condición de __name__ vamos a realizar lo siguiente:

Obtendremos, mediante al módulo os, el id del proceso que se ha generando al ejecutar el script y lo guardaremos en un fichero con el nombre leds.pid.txt, esto lo usaremos en el momento de apagar los leds.

Abrimos el fichero data.json, que contiene los datos sobre la secuencia a ejecutar, y, mediante el módulo json, creamos un objeto de Python con los datos.

Creamos un bloque try / except, para poder parar la ejecución del script mediante una interrupción de teclado (Ctrl + C), al ejecutar el script desde la línea de comandos.

Dentro del try, creamos un bloque While infinito donde recorremos el contenido de la propiedad steps del objeto, una matriz que contiene la información de estado de los leds en cada paso de la secuencia.

Por cada paso, recorremos la matriz guardada en la propiedad states, que contiene el estado de cada uno de los leds, y lo establecemos en la matriz del objeto shifter74hc595 mediante setLed, para, al finalizar, enviarlo al integrado con sendData.

Finalmente, usando el función sleep de time, hacemos una pausa que durará lo que indique la propiedad sleep del paso que se esta ejecutando.

#!/usr/bin/env python3

from shifter74hc595.shifter74hc595 import shifter74hc595
import sys
import os
from time import sleep
import json

shifter = shifter74hc595(11, 15, 13)

if __name__ == '__main__': 

  # get the pid
  pid = os.getpid()
  file = open('leds.pid.txt', 'w')
  file.write(str(pid))
  file.close()

  # open json data file
  with open('data.json', 'r') as file:
    leds_steps = json.load(file)
  
  try:
    while True:
      for step in leds_steps['steps']:
        for idx, state in enumerate(step['states']):
          shifter.setLed(idx, state)
        shifter.sendData()
        sleep(float(step['sleep']))

  except KeyboardInterrupt:
    del(shifter)
    sys.exit()   

El script ledsoff.py

Este script se encarga de apagar todos los leds.

Se importan los módulos shifter74hc595 y sys.

Después de comprobar si la variable especial __name__ contiene __main__, se establecen todos los estados de los leds a LOW, mediante setAll, se envían al integrado y se sale del script.

#!/usr/bin/env python3

from shifter74hc595.shifter74hc595 import shifter74hc595
import sys

shifter = shifter74hc595(11, 15, 13)

if __name__ == '__main__':
  shifter.setAll(0)
  shifter.sendData()
  del(shifter)
  sys.exit()

Scripts de PHP

El script onleds.php

Este script se encargará de recibir la información de la secuencia de leds desde la aplicación, escribir un fichero JSON con la información y llamar al script de Python.

Si al realizar alguna de las acciones se produce un error, se devolverá a la aplicación un objeto JSON con dos propiedades: una con nombre result y valor false, y otra, con nombre message y, como valor, el motivo del error.

Si todo ocurre correctamente, se devolverá a la aplicación un objeto JSON con una propiedad de nombre result y con un valor true.

En un primer paso comprueba si se han recibido los datos vía POST y se crea una matriz con un campo que contiene la información recibida.

A continuación se intenta guardar la información en un fichero JSON, que será el que abrirá, posteriormente, el script de Python.

Finalmente se ejecuta el script de Python, enviando al salida del script a segundo plano, de manera que el script de PHP finalice y envíe el resultado a la aplicación.

<?php  
  // gets, from the received post data, the information to play the sequence
  if(!array_key_exists('data', $_POST)) {
    die(json_encode(array('result' => false, 'message' => 'no data key')));
  } else {
    $data = $_POST['data'];
    $steps = json_decode($_POST['data']);
    $objectForFile =  array('steps' => $steps);
  } 

  // saves the information into a json file, this file is used
  // by the python script that play the sequence  
  try {
    $fp = fopen('data.json', 'w');
    if(!$fp) {
      die(json_encode(array('result' => false, 'message' => 'error encoding data')));
    }    
    $result = fwrite($fp, json_encode($objectForFile));
    if(!$result) {
      die(json_encode(array('result' => false, 'message' => 'error saving data')));
    }
    fclose($fp);
  } catch(Exception $e) {
    // exception when trying to save JSON file with data
    die(json_encode(array('result' => false, 'message' => $e->getMessage())));
  }

  // execute the python script,        
  try {   
    $command = 'sudo python3 ledson.py >/dev/null &';
    system($command, $result);  
    if($result > 0) {
      die(json_encode(array('result' => false, 'message' => 'error on turn on leds')));
    }
  } catch(Exception $e) {
    // exception when trying to execute python script
    die(json_encode(array('result' => false, 'message' => $e->getMessage())));
  }
  
  // all OK, sends true to front-end
  echo json_encode(array('result' => true));   
?>

El script offleds.php

Este script se encargará de parar la reproducción de la secuencia de leds. Al igual que el script anterior, devuelve un objeto JSON, con el resultado.

Primero intentar abrir el fichero leds.pid.txt, que contiene el identificador del proceso que esta ejecutando el script de Python ledson.py, y leer su contenido.

En caso de obtener el contenido del fichero, lo usa para finalizar el proceso, mediante el comando kill del sistema.

Por último, ejecuta el script de Python ledsoff.py para apagar todos los leds. Así, si algún led estaba encendido en el momento de matar el proceso de reproducción de la secuencia, quedará apagado.

<?php
  // opens the file that contains the process id
  // of python script that is running the leds sequence
  try {
    $pidfile = 'leds.pid.txt';
    $fp = fopen($pidfile, 'r');
    if(!$fp) {
      die(json_encode(array('result' => false, 'message' => 'error getting pid')));
    }    
    $pid = fread($fp, filesize($pidfile));
    fclose($fp);
  } catch (Exception $e) {
    die(json_encode(array('result' => false, 'message' => $e->getMessage())));
  }

  // it executes a command to kill the process
  try {
    $command = 'sudo kill -9 ' . $pid;
    system($command, $result);
    if($result > 0) {
      die(json_encode(array('result' => false, 'message' => 'error on kill process')));  
    }
  } catch (Exception $e) {
    die(json_encode(array('result' => false, 'message' => $e->getMessage())));
  }

  // it executes a python script that get off all the leds
  try {    
    $command = 'sudo python3 ledsoff.py';
    system($command, $result);    
    if($result > 0) {
      die(json_encode(array('result' => false, 'message' => 'error on turn off leds')));  
    }
  } catch (Exception $e) {
    die(json_encode(array('result' => false, 'message' => $e->getMessage())));
  }

  echo json_encode(array('result' => true));
?>

Repositorio

El código de estos scripts está disponible en el siguiente repositorio:

https://github.com/theguitxo/remote-leds-back-end
Esta web utiliza cookies propias para su correcto funcionamiento. Contiene enlaces a sitios web de terceros con políticas de privacidad ajenas que podrás aceptar o no cuando accedas a ellos. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Ver
Privacidad