Controlar bridge HUE Philips con Raspberry Pi Zero y Touch PHAT

  • 3 Feb 2020
  • Raspberry, Python, Philips Hue

En precio ridículamente alto del dimmer Philips (25 euros por 4 botones) despertó un día mi curiosidad de como poder controlar las luces (de Philips o de otro sistema Zybee integrado en el bridge HUE Philips) con otra cosa que no sea un smartphone. Así fue que me topé con el trabajo del señor Tim Richardson: PiHue. PiHue es un script en Python para controlar un bridge con los botones de un Touch pHAT

Ingredientes

Para llevar esta tarea a cabo, necesitamos:

  • Raspberry PI Zero (€30)
  • SD card (€10)
  • Touch PHAT (€8)
  • Cargador de celular 5V. (€10)

La Raspberry Pi Zero con su sombrero touch

Como cualquier matemático podrá apreciar, la solución requerirá no solo más dinero si no también más tiempo y complicaciones para obtener un producto seguramente menos eficiente, menos amigable. Pero seguramente más entretenido.

Preparación

Tengo más buenas noticias. El Touch PHAT llega en una bolsita con el conector listo para soldar. Así que si no tienen soldador ni estaño, hay que agregarlo a la lista de ingredientes... con su relativo costo. 

En la micro SD vamos a instalar Raspbian, que puede descargarse acá. Se puede seguir esta guía oficial para la instalación de Raspbian. Para configurar la conexión a una red wireless, explorar el contenido de la microSD y crear el siguiente archivo:

/etc/wpa_supplicant/wpa_supplicant.conf

con el siguiente código (modificar los parámetros country, ssid y psk con los que correspondan):

country=IT
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
        ssid="nombre_red"
        psk="password"
        key_mgmt=WPA-PSK
}

Bien, ahora podemos conectar los botones, insertar la micro SD y conectar el alimentador de 5V. 2A. Una vez que nuestro DHCP gentilmente le presta una dirección, entramos pos SSH con el usurio pi. La contraseña predeterminada es raspberry. Una vez adentro, instalamos todo:

sudo apt-get update
sudo apt-get install python3 python3-pip git
sudo apt-get install python3-touchphat
sudo pip3 install phue
cd /home/pi 
git clone https://github.com/GeekyTim/PiHue

En el sitio de Touch pHAT explica como instalar la librería touchphat para Python. El comando es el siguiente:

curl https://get.pimoroni.com/touchphat  | bash

Responder y o n como sea necesario y reiniciar el sistema. Una vez completado todo, ya se debería poder probar el script de Tim. Si se llegara a ver un error como este:

ImportError: No module named phue

es seguramente porque se ejecutó usando python en vez de python3

/usr/bin/python3 /home/pi/PiHue/PiHueLightList.py

Personalizar PiHueLightList.py

Antes de comenzar, recomiendo una buena lectura de estos dos archivos:

  • /usr/lib/python3/dist-packages/cap1xxx.py (código)
  • /usr/lib/python3/dist-packages/touchphat/__init__.py (código)

Ahora sí, dentro PiHueLightList.py hay 3 variables que sí o sí tenemos que configurar:

  • bridgeip: La ip de nuestro bridge HUE Philips
  • lights: Una lista con los nombres de las luces tal cual aparecen en la app de Philips

La primera vez que ejecutamos PiHueLightList.py tenemos que descomentar b.connect(), tocar el botón del bridge y correr python PiHueLightList.py.

Se me occurrió agregarle la funcionalidad de asociar cada botón a una lámpara y encender su led si la lámpara está apagada y viceversa. Agrego entonces la lista pads

pads = ["A", "B", "C", "D", 'Enter']

Pero después vi que el script actualiza todo el tiempo el tipo de lámpara (que puede ser Extended color light o Dimmable light). Entonces por qué no hacer una lista donde tener todo:

  • Pad o botón
  • Lámpara
  • Tipo de lámpara

y llamarlo elements:

elements = {
    "Back" : {
        "lamp": "Hue color lamp 1",
        "status": {},
        },
    "A" : {
        "lamp": "Studio",
        "status": {},
        },
    "B" : {
        "lamp": "Ripostiglio",
        "status": {},
        },
    "C" : {
        "lamp": "Cucina",
        "status": {},
        },
    "D" : {
        "lamp": "Osram 3",
        "status": {},
        },
    "Enter" : {
        "lamp": "Osram 2",
        "status": {},
        },
    }

# append light type to each element
for pad in elements:
    elements[pad].update( {'type' : b.get_light(elements[pad]['lamp'], 'type')} )

Otro aspecto poco performate es la función getlightstatus() que recibe el estado de todas las luces y que es llamada dentro un for tantas veces como botones se hayan configurado. Como alternativa, se podría llamar una sola vez y agregar su resultado al diccionario elements.

Además, se puede agregar un loop eterno que se ocupe de actualizar el estado de los leds para garantizar su consistencia con el estado de las luces.. ya que las luces podrían ser comandadas por otros dispositivos.

Otro punto interesante es el del estado de los pads que pueden ser dos: press o release. Tim, en su trabajo usa solo el evento on_touch. Por qué no utilizar también on_release y establecer que on_touch enciende las luces y on_release las apaga. Para que esto funcione igual para todas las luces, será necesario establecer el estado inicial de los pads, de la siguiente manera:

# luz encendida, el pad está press
touchphat.dh.input_status[pad] = 'press'
# luz apagada, el pad está release
touchphat.dh.input_status[pad] = 'release'

De este modo, las funciones para los eventos on_touch y on_release ya no necesitan conocer el estado de las luces.

Bien, el script modificado quedó así:

#!/usr/bin/python3

'''
    File name: PiHueLightList.py
    Author: Tim Richardson (modified by Eduardo Viegas / minestron.it)
    Date created: 04/07/2017
    Date last modified: 26/02/2020
    Python Version: 3.4

	Description:
	Control Philips Hue lights using a Pimoroni TouchpHAT - individual light version

    Requirements:
    * Raspberry Pi (http://raspberrypi.org/)
    * Philips Hue (http://www2.meethue.com)
    * Pimoroni TouchpHAT (https://shop.pimoroni.com/products/touch-phat)

    The Raspberry Pi must be on the same network as the Hue bridge
    You must set the bridgeip to be the IP address of your bridge
    and edit the list of lights in your network in 'lights[]'

    You can edit/expand the colour 'xy' values and the alerts
'''

from phue import Bridge
import touchphat
import time
import logging, sys

import cap1xxx

logging.basicConfig(stream=sys.stderr, level=logging.INFO)

# ==============================================================================================
# Setup
# ==============================================================================================
touchphat.all_off()
touchphat.auto_leds = False

# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# Stuff you need to change!
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# The IP address of the Hue bridge and a list of lights you want to use
bridgeip = '1792.168.40.200'  # <<<<<<<<<<<


# -----------------------------------------------------------------------------------------------
# Do some internal setup
# -----------------------------------------------------------------------------------------------
# Connect to the bridge
b = Bridge(bridgeip)

# IMPORTANT: If running for the first time:
#    Uncomment the b.connect() line
#    Ppress button on bridge
#    Run the code
# This will save your connection details in /home/pi/.python_hue
# Delete that file if you change bridges
# b.connect() # <<<<<<<<<<

# An array of the light name and the light type.
# Expand if you have something different - please let me know if you do!
lighttypecolour = 'Extended color light'
lighttypedimmable = 'Dimmable light'

elements = {
    "Back" : {
        "lamp": "Hue color lamp 1",
        "status": {},
        },
    "A" : {
        "lamp": "Studio",
        "status": {},
        },
    "B" : {
        "lamp": "Ripostiglio",
        "status": {},
        },
    "C" : {
        "lamp": "Cucina",
        "status": {},
        },
    "D" : {
        "lamp": "Osram 3",
        "status": {},
        },
    "Enter" : {
        "lamp": "Osram 2",
        "status": {},
        },
    }

# append light type to each element
for pad in elements:
    elements[pad].update( {'type' : b.get_light(elements[pad]['lamp'], 'type')} )


# Hue 'xy' colours - expand at will
redxy = (0.675, 0.322)
greenxy = (0.4091, 0.518)
bluexy = (0.167, 0.04)
yellowxy = (0.4325035269415173, 0.5007488105282536)
bluevioletxy = (0.2451169740627056, 0.09787810393609737)
orangexy = (0.6007303214398861, 0.3767456073628519)
whitexy = (0.32272672086556803, 0.3290229095590793)

# Alert Patterns
# Used to change the status of lights
# First two numbers are the number of repeat cycles, and the delay between changes
# Followed by dictionaries of the change of light status.
# Use any valid HUE setting - e.g. on, bri, xy, ct, sat, hue, transformationtime
redAlert = [6, 0.5,
            {lighttypecolour: {'on': True, 'bri': 255, 'xy': redxy, 'transitiontime': 0},
             lighttypedimmable: {'on': True, 'bri': 255, 'transitiontime': 0}},
            {lighttypecolour: {'on': False, 'bri': 255, 'xy': redxy, 'transitiontime': 0},
             lighttypedimmable: {'on': False, 'bri': 255, 'transitiontime': 0}}]

amberAlert = [6, 0.5,
              {lighttypecolour: {'on': True, 'bri': 255, 'xy': orangexy, 'transitiontime': 0},
               lighttypedimmable: {'on': True, 'bri': 255, 'transitiontime': 0}},
              {lighttypecolour: {'on': False, 'bri': 255, 'xy': orangexy, 'transitiontime': 0},
               lighttypedimmable: {'on': False, 'bri': 255, 'transitiontime': 0}}]

blueAlert = [6, 0.5,
             {lighttypecolour: {'on': True, 'bri': 255, 'xy': bluexy, 'transitiontime': 0},
              lighttypedimmable: {'on': True, 'bri': 255, 'transitiontime': 0}},
             {lighttypecolour: {'on': False, 'bri': 255, 'xy': bluexy, 'transitiontime': 0},
              lighttypedimmable: {'on': False, 'bri': 255, 'transitiontime': 0}}]

greenAlert = [6, 0.5,
              {lighttypecolour: {'on': True, 'bri': 255, 'xy': greenxy, 'transitiontime': 0},
               lighttypedimmable: {'on': True, 'bri': 255, 'transitiontime': 0}},
              {lighttypecolour: {'on': False, 'bri': 255, 'xy': greenxy, 'transitiontime': 0},
               lighttypedimmable: {'on': False, 'bri': 10, 'transitiontime': 0}}]

allwhite = {lighttypecolour: {'on': True, 'bri': 255, 'xy': whitexy, 'transitiontime': 1},
            lighttypedimmable: {'on': True, 'bri': 255, 'transitiontime': 1}}

# If an alert is ongoing, this will be True
inalert = False

# Wait time between sending messages to the bridge - to stop congestion
defaultwaittime = 1


# = End of Setup ============================================================================

# -------------------------------------------------------------------------------------------
# Functions
# -------------------------------------------------------------------------------------------

# Append the the status of every light in the list of lights into elements
def getlightstatus():

    for pad in elements:
        light = elements[pad]['lamp']
        lighttype = elements[pad]['type']

        if lighttype == lighttypecolour:
            elements[pad]['status'].update({'on': b.get_light(light, 'on'),
                                        'bri': b.get_light(light, 'bri'),
                                        'xy': b.get_light(light, 'xy')})
        elif lighttype == lighttypedimmable:
            elements[pad]['status'].update({'on': b.get_light(light, 'on'),
                                        'bri': b.get_light(light, 'bri')})



def isthislampon(lamp):
    logging.info('isthislampon %s', lamp)
    global inalert

    # get the pad that commands this lamp
    light_pad = ""
    for pad in elements:
        if elements[pad]['lamp'] == lamp:
            light_pad = pad


    if elements[light_pad]['status']['on']:
        return True

    return False


@touchphat.on_release(["Back", "A", "B", "C", "D", "Enter"])
def handle_release(event):
    global inalert

    logging.info(' ON_RELEASE_ %s, id: %s', event.name, event.pad)

    light = elements[event.name]['lamp']

    if not inalert:
        inalert = True
        touchphat.led_on(event.pad)
        b.set_light(light, 'on', False)
        time.sleep(1)
        inalert = False


@touchphat.on_touch(["Back", "A", "B", "C", "D", "Enter"])
def handle_touch(event):
    global inalert

    logging.info(' ON_TOUCH_ %s, id: %s', event.name, event.pad)

    light = elements[event.name]['lamp']

    if not inalert:
        inalert = True
        touchphat.led_off(event.pad)
        b.set_light(light, 'on', True)
        time.sleep(1)
        inalert = False

LEDMAP = [5, 4, 3, 2, 1, 0]
captouch = cap1xxx.Cap1166(i2c_addr=0x2c)
touchphat.all_off()

# set initial status for leds and pad status (pressed?)
getlightstatus()

for pad in elements:
    light = elements[pad]['lamp']
    if inalert:
        continue
    if isthislampon(light):
        # turn off led and set the pad status to be released
        touchphat.led_off(pad)
        touchphat.dh.input_status[touchphat._pad_to_channel(pad)] = 'press'
    else:
        touchphat.led_on(pad)
        touchphat.dh.input_status[touchphat._pad_to_channel(pad)] = 'release'

# ================================================================
# Main loop - keep going forever
# ================================================================

while True:
    if inalert:
        time.sleep(1)
    else:
        getlightstatus()
        for pad in elements:
            light = elements[pad]['lamp']
            if inalert:
                continue
            if isthislampon(light):
                touchphat.led_off(pad)
                touchphat.dh.input_status[touchphat._pad_to_channel(pad)] = 'press'
            else:
                touchphat.led_on(pad)
                touchphat.dh.input_status[touchphat._pad_to_channel(pad)] = 'release'
            time.sleep(1)

Otra modificación que puede ser interesante es en la configuración del scrip como servicio. En su desarrollo, Tim explica muy bien como hacerlo, el código es el siguiente:

[Unit]
Description=PiHue Service
After=multi-user.target

[Service]
Type=idle
User=pi
Restart=always
RestartSec=5
ExecStart=/usr/bin/python3 /home/pi/PiHue/PiHueRoom.py

[Install]
WantedBy=multi-user.target

Se me ocurrió además agregar un script para apagar los leds una vez que el servicio es detenido.

ExecStopPost=/usr/bin/python3 /home/pi/PiHue/PiTouchOff.py

El script es simplemente:

import touchphat

touchphat.all_off()

Por ahora eso es todo. Para el futuro podría ser interesante agregar otras funciones y manejar otros eventos como hold o multitouch.

Enlaces externos