Conectar monitor de consumo eléctrico con emoncms.org (II)

Estándar

En la última entrada, habíamos visto cómo conectar el medidor de consumo OWL-CM160 a un PC, para descargar el consumo. También habíamos visto que EmonCMS era una web muy chula a la que subir nuestros consumos, pero que no tenía una opción para subir directamente un fichero CSV. Además, esto de tener que subir manualmente ficheros CSV cada cierto tiempo no nos gustaba demasiado, y la idea era montar algo para tener siempre el consumo disponible en tiempo real.

La opción que parece mejor, de lejos, es tener la estación base del medidor de consumo conectada a algún ordenador que tengamos conectado las 24h, y trabajar desde ahí. Pero muchas veces eso no es posible, ya sea porque no tenemos ningún ordenador así, o bien porque la ubicación no nos vaya bien. En mi caso, era esto último, ya que si ponía la estación base enchufada a mi mini-servidor la señal de la pinza amperimétrica no llegaba bien (el contador queda lejos). Por tanto, y aprovechando que tenía una Raspberry Pi tirada por ahí, decidí aprovecharla para la tarea. Le puse una tarjetilla SD adecuada y un adaptador Wifi, le instalé una Raspbian, y alimentándola con cualquier cargador de móvil un poco bueno (que dé 1-1,5 A por lo menos, aunque yo me compré el oficial), pues ya tengo un ordenador listo para colocarlo en cualquier parte. A partir de aquí, todo lo que cuento funciona exactamente igual para la Raspberry Pi que para cualquier ordenador con Linux, ya que Raspbian no deja de ser una distribución Linux como otra cualquiera.

A continuación explico más o menos lo que hice, paso a paso.

Instalar eagle-owl

Esto no tiene mucho misterio. Lo primero es bajarse la última versión desde el repositorio Git, por ejemplo creando el directorio ~/git, y bajándolo allí:

mkdir ~/git
cd ~/git
git clone https://github.com/cornetp/eagle-owl.git

Una vez está aquí, hay que compilarlo. Para ello hay que instalar previamente un par de paquetes (con el compilador y demás), que suelen estar agrupados en el paquete virtual build-essential:

cd eagle-owl
sudo apt-get install build-essential
cd src
make

Tardará un ratito (especialmente si es una raspberry), y al acabar ya tendremos los ejecutables compilados (cm160 y db_import). Siempre siguiendo el manual, ahora hay que copiar estos ficheros a algún sitio (yo los puse en /opt/eagleowl), y luego copiar el fichero eagleowl.conf a /etc, editándolo para poner la ruta que hemos creado en  install_path:

mkdir /opt/eagleowl
cp cm160 db_import /opt/eagleowl
sudo cp eagleowl.conf /etc
Así debe quedar el fichero de configuración de eagle-owl

Así debe quedar el fichero de configuración de eagle-owl

Otra cosa que hay que configurar es que eagle-owl se arranque automáticamente cada vez que arranca el ordenador. El problema es que el script de arranque que viene en el repositorio es para upstart (Ubuntus y derivados), por lo que, entre que mi Raspbian está basada en Debian y que quiero hacerlo un pelín diferente, me he hecho mi propio script, que hay que copiar en /etc/init.d:

#!/bin/bash                                                                                                                                                                                                                                  
### BEGIN INIT INFO                                                                                                                                                                                                                          
# Provides:          eagleowl                                                                                                                                                                                                                
# Required-Start:    $remote_fs $syslog $time                                                                                                                                                                                                
# Required-Stop:     $remote_fs $syslog                                                                                                                                                                                                      
# Default-Start:     2 3 4 5                                                                                                                                                                                                                 
# Default-Stop:      0 1 6                                                                                                                                                                                                                   
# Short-Description: Programa para descargar logs de OWL-CM160
# Description:       Ejecuta eagle-owl dentro de una sesión screen
### END INIT INFO

# /etc/init.d/eagleowl

if [ -f /lib/lsb/init-functions ]; then
        . /lib/lsb/init-functions
fi

source /etc/eagleowl.conf

# Carry out specific functions when asked to by the system
case "$1" in
        start)
                echo "Starting eagleowl "
                start-stop-daemon --start -x $install_path/cm160 --chuid pi --chdir $install_path --exec /usr/bin/screen -- -d -m $install_path/cm160
                ;;
        stop)
                echo "Stopping eagleowl"
                start-stop-daemon --stop -R5 --name cm160
                ;;
        restart|reload)
                $0 stop
                $0 start
                ;;
        status)
#               status_of_proc cm160
                pid=$(pgrep cm160 | head -n1)
                if [ $pid ]; then
                        echo "eagleowl funcionando con PID $pid"
                else
                        echo "eagleowl no está activo"
                fi
                ;;
        *)
                echo "Usage: /etc/init.d/eagleowl {start|stop|restart|status}"
                exit 1
                ;;
esac

exit 0

Lo que he modificado respecto a un script de arranque «estándar» son un par de cositas:

  • Al principio leo el fichero de configuración de eagleowl (/etc/eagleowl.conf), de manera que tengo acceso a la ruta en la que está instalado ($install_path).
  • He indicado que el comando se ejecute con el usuario pi (que es el por defecto en la raspberry)
  • No arranco el comando cm160 tal cual, sino que lo hago dentro de una sesión screen. Para el que no lo conozca, screen es una especie de consola «virtual» que se ejecuta en segundo plano. La he usado para ejecutar eagleowl porque no he encontrado manera de redirigir la salida de texto del comando cm160 sin que dé problemas. De esta manera, se ejecuta en su propia sesión, y todo va bien.

Una vez el script está copiado a /etc/init.d con el nombre «eagleowl», le indicamos al sistema operativo que lo ejecute en cada arranque:

update-rc.d eagleowl defaults

Y ya lo tenemos todo configurado. De todas formas, antes de hacer este último paso conviene asegurarse de que eagleowl funciona correctamente. Para ello, ejecutamos tal cual el comando cm160, y si todo va bien veremos cómo, en primer lugar, se descarga el consumo que tenga acumulado el aparato, y a partir de ahí va mostrando por pantalla los consumos en tiempo real (una línea cada minuto, más o menos). Al mismo tiempo, los va guardando dentro del fichero /opt/eagleowl/eagleowl.db, que es el que realmente nos interesa.

Enviar consumos a EmonCMS

Ahora que ya tenemos a eagleowl funcionando, y guardando los consumos en tiempo real, es ahora de enviar el consumo a EmonCMS. Para ello, lo primero es extraer el consumo de la base de datos SQlite que usa eagleowl. Eso es tan fácil como ejecutar el siguiente comando:

sqlite3 -csv eagleowl.db "select year||'-'||substr('0'||month,-2,2)||'-'||substr('0'||day,-2,2)||' '||substr('0'||hour,-2,2)||':'||substr('0'||min,-2,2) date,cast(round(60*ch1_kw_avg) as int) w from energy_history where year=2015 and month=03 and day=16 order by date desc limit 1;"

Esto nos daría el último valor registrado en W para la fecha indicada (16 de marzo de 2015).

Y para enviar los datos a EmonCMS, basta con acceder a la URL:

http://emoncms.org/input/post.json?json={$key:$wattios}&apikey=$apikey

donde hay 3 valores a rellenar:

  • key: Es el identificador de lo que estamos midiendo, y es necesario porque EmonCMS soporta muchas entradas de datos diferentes (p. ejemplo, podríamos tener varios electrodomésticos monitorizados). Como yo estoy midiendo el consumo global, lo he llamado «kwgeneral».
  • wattios: Este es fácil, y es simplemente el consumo en wattios que hemos extraído antes de la base de datos de eagleowl
  • apikey: Esta es la clave de identificación para que EmonCMS nos reconozca. Se puede consultar en las opciones de EmonCMS, y hay dos, una de lectura y otra de escritura. Como en este caso estamos enviando datos para que se guarden, debe ser la de escritura.
Desde el apartado "Mi cuenta" de EmonCMS se pueden consultar las claves de API, tanto de lectura como de escritura

Desde el apartado «Mi cuenta» de EmonCMS se pueden consultar las claves de API, tanto de lectura como de escritura

Para enviar la información en tiempo real, lo suyo es crear una tarea cron que se ejecute cada minuto, y hacerlo todo allí. El siguiente script es el que uso yo:

#!/bin/bash

# Sube a emoncms el último valor de consumo
# Debería ejecutarse en un cron cada minuto

fichdb="/opt/eagleowl/eagleowl.db"
nodeid=0
apikey="efbda7b8s7f8c8e9a9f80f333aab2320"
key="kwgeneral"
diaactual=`date +%d`
mesactual=`date +%m`
anyoactual=`date +%Y`
medida=$(sqlite3 -csv $fichdb "select year||'-'||substr('0'||month,-2,2)||'-'||substr('0'||day,-2,2)||' '||substr('0'||hour,-2,2)||':'||substr('0'||min,-2,2) date,cast(round(60*ch1_kw_avg) as int) w from energy_history where year=$anyoactual and month=$mesactual and day=$diaactual order by date desc limit 1;")
wattios=$(echo $medida | cut -d, -f2)
url="http://emoncms.org/input/post.json?json={$key:$wattios}&apikey=$apikey"
if curl --retry 3 -s -S -o /dev/null "$url"; then
    logger -t EMON "Actualizado consumo en emoncms.org con valor $wattios W"
else
    logger -t EMON "Error actualizando consumo en emoncms.org ($wattios W)"
fi

Y con esto ya lo tenemos todo. Si vamos a EmonCMS, deberíamos ver ya en el apartado «Inputs» como tenemos una nueva entrada llamada kwgeneral, a la que van llegando datos periódicamente.

Aquí se ve la nueva entrada "kwgeneral", que se actualiza cada minuto. La otra entrada es otra que tengo para registrar la temperatura

Aquí se ve la nueva entrada «kwgeneral», que se actualiza cada minuto. La otra entrada es otra que tengo para registrar la temperatura

A partir de aquí habría que configurar EmonCMS, pero ese es otro tema bastante más amplio, y lo dejo para algún futuro artículo.

Inhibir la suspensión del ordenador cuando no nos interese (IV)

Estándar

Cuando con la entrada anterior pensaba que había resuelto todos mis problemas con lo de evitar suspender la máquina cuando hacemos algo delicado, van los señores de Ubuntu y cambian el sistema de arranque de upstart a systemd, dejando obsoletos mis pobres (y eficientes) scripts. Y como ahora mismo me da pereza estudiarme systemd para ver cómo hacer lo mismo, toca volver al método anterior.

Eso sí, con una mejora interesante que he descubierto. Resulta que yo simulaba la actividad del usuario enviando una pulsación inofensiva (la tecla CTRL o algo así) al servidor X. Eso, aparte de guarrote, no siempre funciona bien, y he visto que una forma más limpia es enviándole un mensaje DBUS (un protocolo muy chachi para comunicación entre aplicaciones) al servicio de salvapantallas, simulando la actividad. Concretamente, en KDE se haría con el comando:

qdbus org.kde.screensaver /ScreenSaver org.freedesktop.ScreenSaver.SimulateUserActivity

Con esto, se resetea el contador interno de inactividad, y la cuenta atrás hasta la suspensión vuelve a empezar. Así las cosas, es todo muy fácil. Si, pongamos, tenemos configurada la suspensión a los 15 minutos de inactividad, basta poner un cron cada 5-10 minutos que mire si algo impide suspender (audio en marcha, conexiones SSH a la máquina, etc.), y si por lo que sea no se puede suspender que ejecute el comando de arriba. Pues no, la cosa no es tan sencilla.

Por ejemplo, si no hay ninguna sesión iniciada en la máquina, tampoco habrá ningún servicio DBUS al que poder enviar comandos. Bueno, eso tampoco es grave. Al fin y al cabo, las opciones de ahorro de energía no se aplican si no hay sesión iniciada, por lo que peor no estamos. Pero, ¿qué pasa si pongo el cron en un usuario de la máquina, pero el que tiene sesión abierta es otro? Pues que tampoco funciona, porque cada sesión tiene su propio servicio DBUS, y el cron que se ejecute fallará porque no encontrará un servicio DBUS activo (el suyo), pese a que sí hay uno en marcha. La solución parece fácil: ejecutamos el script como root, y palante. Pues directamente tampoco va, porque, por mucho usuario logueado que haya en el sistema, root como tal no tiene sesión abierta, y por tanto no tiene DBUS al que hablar.

Al final, todo tiene solución en esta vida, y la cosa puede resolverse. Pero es un poco intrincado:

  1. Ejecutar el cron como root
  2. Buscar procesos que indiquen que hay algún usuario con sesión abierta. Sirven muchos posibles (kdeinit, nautilus), cualquiera sirve. Averiguamos su PID, y a qué usuario pertenece.
  3. Una vez tenemos el PID, vamos a /proc/<PID>/environ, desde donde podemos consultar todas las variables de entorno de ese proceso. La que nos interesa es la variable DBUS_SESSION_BUS_ADDRESS, que es la referencia al servicio DBUS de ese usuario.
  4. Con toda esta información, ya podemos ejecutar el comando qdbus que indicábamos arriba, pero haciendo un su previamente al usuario que sea, y estableciendo la variable DBUS_SESSION_BUS_ADDRESS. Ahora sí que debería funcionar

Suena todo muy complicado, pero al final son simplemente cuatro líneas en vez de una. Pongo a continuación el script (creo que) final. Lo único que no me funciona es la detección de audio en marcha, ya que como root no se puede acceder a la instancia de pulseaudio de un usuario concreto, y habrá que buscar otra manera, o hacer una pirula similar a la que he hecho arriba, buscando instancias de pulseaudio y ejecutando el comando como el usuario que toque, pero como tampoco es algo vital me ha dado pereza. Aquí va:

!/bin/bash

# Procesos que evitarán que se suspenda el sistema si están presentes
COMANDOS="xbmc vlc wine shotwell kdenlive"

nosusp=0

# Mirar si hay audio reproduciendo (NO FUNCIONA COMO ROOT)
#numaudio=`pactl list| grep -i estado | grep -i running | wc -l`

#if [ $numaudio -gt 0 ]; then
#   logger -t EVISUSP "Audio en reproducción, evitamos suspensión"
#   nosusp=1
#fi

# Mirar si hay conexiones SSH activas
numcon=`netstat -t -n | tr -s ' ' | cut -d" " -f4 | grep ":22" | wc -l`
if [ $numcon -gt 0 ]; then
    logger -t EVISUSP "Conexiones SSH activas, evitamos suspensión"
    nosusp=1
fi

# Mirar si hay algun proceso de la lista blanca en marcha
com=`echo $COMANDOS | tr ' ' '|'`
comlb=`ps -A | grep -E -o "$com" | head -n1`

if [ $comlb ]; then
   logger -t EVISUSP "Encontrado proceso de lista blanca ($comlb) en ejecución, evitamos suspensión"
   nosusp=1
fi

# Si hay que evitar la suspensión, actuar
if [ $nosusp -gt 0 ]; then
    logger -t EVISUSP "Simulando actividad para evitar suspensión..."
    # Recuperar sesión DBUS y simular actividad
    piddbus=$(pgrep "kdeinit|kded4|trackerd|nautilus" | head -n1)
    usudbus=$(ps -e -o pid,user | sed 's/^ *//g' | grep "^$piddbus " | cut -d" " -f2)
    dbusaddr=$(cat /proc/$piddbus/environ | grep -z "^DBUS_SESSION_BUS_ADDRESS=" | cut -d"=" -f2-)
    if [ $dbusaddr ]; then
        su -c "DBUS_SESSION_BUS_ADDRESS=$dbusaddr qdbus org.kde.screensaver /ScreenSaver org.freedesktop.ScreenSaver.SimulateUserActivity" $usudbus
    else
        logger -t EVISUSP "No hay entorno de ventanas activo, no se puede simular actividad"
    fi
else
    logger -t EVISUSP "No haciendo nada, la suspensión sigue en curso"
fi