Equipos y materiales > Openwrt & LEDE

[Script] Manejo de enchufes inteligentes TP-Link HS100 HS110 con OpenWrt

(1/3) > >>

Tki2000:
Manejo de enchufes inteligentes TP-Link HS100 HS110 con OpenWrt


Desde hace tiempo vengo ejecutando con openwrt scripts para el manejo de enchufes inteligentes. Los más asequibles para el manejo desde ash han sido los Belkin Wemo. Desde hace algún tiempo vienen escaseando, y los que se encuentran, están a precio desorbitado. Por esa razón me he decidido a buscar una alternativa barata a los Belkin Wemo.

Los enchufes TP-Link HS100 y HS110 parecen buenos candidatos. Son baratos y por ahí circula un script (Author George Georgovassilis, https://github.com/ggeorgovassilis/linuxscripts) para manejarlos desde bash. La única pega es que el script directamente no es compatible con ash bajo openwrt.

Después de tener cierto tiempo y estudiar el script, he realizado una adaptación del script que corre bajo ash, con lo que podemos manejar el enchufe inteligente desde la línea de comandos de openwrt. En mi caso es útil para encender todo tipo de aparatos conectados a los enchufes una vez que se ha ido la luz, y que cuando vuelve se quedan todos apagados. Al volver la luz, sólo necesito acceso al router con openwrt, ya que los enchufes estarán preparados y conectados a la wifi del router. Accediendo empiezo a encender la secuencia de enchufes y todos los aparatos conectados a él. Lo utilizo principalmente en una casa con una persona que no puede valerse por sí misma.

Manejar los enchufes bajo luci lo podemos realizar incorporando el módulo luci-app-commands. Con este módulo podemos definir comandos a ejecutar con un botón en la interfaz gráfica de luci. Es una forma primitiva de automatización, pero muy efectiva, y recordemos que sólo necesitamos el router openwrt. Nada de servidores de automatización (aunque a esos evidentemente también se puede incorporar).

Para hacerlo compatible necesitamos instalar también una serie de módulos para usar comandos completos, que en busybox no están al completo:

coreutils-base64
coreutils-od
coreutils-printf


Ahora subimos el siguiente código al router. Yo lo suelo poner en /etc para que al hacer un backup de la configuración, el script también vaya incluído. En este caso he escogido /etc/hs100_control.sh


--- Código: ---#!/bin/sh
##
#  Controls TP-LINK HS100,HS110, HS200 wlan smart plugs
#  Tested with HS100 firmware 1.0.8
#
#  Credits to Thomas Baust for the query/status/emeter commands
#
#  Author George Georgovassilis, https://github.com/ggeorgovassilis/linuxscripts
#
#  2020-06-22 : Tki2000 modifications for running on openwrt ash command shell via a tmp file
#               Needed modules: coreutils-base64 coreutils-od coreutils-printf
#               Working on: Hw:4.0 Fw: 1.0.2

_tmpfile=/tmp/tplink.txt

#echo args are $@
ip=$1
port=$2
cmd=$3

# encoded (the reverse of decode) commands to send to the plug

# encoded {"system":{"set_relay_state":{"state":1}}}
payload_on="AAAAKtDygfiL/5r31e+UtsWg1Iv5nPCR6LfEsNGlwOLYo4HyhueT9tTu36Lfog=="

# encoded {"system":{"set_relay_state":{"state":0}}}
payload_off="AAAAKtDygfiL/5r31e+UtsWg1Iv5nPCR6LfEsNGlwOLYo4HyhueT9tTu3qPeow=="

# encoded { "system":{ "get_sysinfo":null } }
payload_query="AAAAI9Dw0qHYq9+61/XPtJS20bTAn+yV5o/hh+jK8J7rh+vLtpbr"

# the encoded request { "emeter":{ "get_realtime":null } }
payload_emeter="AAAAJNDw0rfav8uu3P7Ev5+92r/LlOaD4o76k/6buYPtmPSYuMXlmA=="

# tools

check_dependencies() {
  command -v nc >/dev/null 2>&1 || { echo >&2 "The nc program for sending data over the network isn't in the path, communication with the plug will fail"; exit 2; }
  command -v base64 >/dev/null 2>&1 || { echo >&2 "The base64 program for decoding base64 encoded strings isn't in the path, decoding of payloads will fail"; exit 2; }
  command -v od >/dev/null 2>&1 || { echo >&2 "The od program for converting binary data to numbers isn't in the path, the status and emeter commands will fail";}
  command -v read >/dev/null 2>&1 || { echo >&2 "The read program for splitting text into tokens isn't in the path, the status and emeter commands will fail";}
  command -v printf >/dev/null 2>&1 || { echo >&2 "The printf program for converting numbers into binary isn't in the path, the status and emeter commands will fail";}
}

show_usage() {
  echo Usage: $0 IP PORT COMMAND
  echo where COMMAND is one of on/off/check/status/emeter/toggle
  exit 1
}


check_arguments() {
   check_arg() {
    name="$1"
    value="$2"
    if [ -z "$value" ]; then
       echo "missing argument $name"
       show_usage
    fi
   }
   check_arg "ip" $ip
   check_arg "port" $port
   check_arg "command" $cmd
}

send_to_plug() {
   ip="$1"
   port="$2"
   payload="$3"
   echo -n "$payload" | base64 --decode | nc $ip $port || echo couldn''t connect to $ip:$port, nc failed with exit code $?
}

decode(){
   code=171
   offset=4
   input_num=`od -j $offset -An -t u1 -v | tr "\n" " "`
   args_for_printf=""
   for element in $input_num
   do
     output=$(( $element ^ $code ))
     args_for_printf="$args_for_printf\x$(printf %x $output)"
     code=$element
   done
   printf "$args_for_printf"
}

query_plug(){
   payload=$1
   send_to_plug $ip $port "$payload" > $_tmpfile
   cat $_tmpfile | decode
   echo
}

# plug commands

cmd_print_plug_relay_state(){
   send_to_plug $ip $port "$payload_query" | decode > $_tmpfile
   output=`cat $_tmpfile | egrep -o 'relay_state":[0,1]' | egrep -o '[0,1]'`
   #echo $output
   if [[ $output -eq 0 ]]; then
     echo OFF
   elif [[ $output -eq 1 ]]; then
     echo ON
   else
     echo Couldn''t understand plug response $output
   fi
}

cmd_print_plug_status(){
     query_plug "$payload_query"
}

cmd_print_plug_consumption(){
     query_plug "$payload_emeter"
}

cmd_switch_on(){
     send_to_plug $ip $port $payload_on > /dev/null
     cmd_print_plug_relay_state
}

cmd_switch_off(){
     send_to_plug $ip $port $payload_off > /dev/null
     cmd_print_plug_relay_state
}

cmd_switch_toggle() {
   output=`cmd_print_plug_relay_state`
   if [[ $output == OFF ]]; then
     cmd_switch_on
   elif [[ $output == ON ]]; then
     cmd_switch_off
   else
     echo $output
   fi
}

##
#  Main program
##


check_dependencies
check_arguments

case "$cmd" in
  on)
     cmd_switch_on
     ;;
  off)
     cmd_switch_off
     ;;
  toggle)
     cmd_switch_toggle
     ;;
  check)
     cmd_print_plug_relay_state
     ;;
  status)
     cmd_print_plug_status
     ;;
  emeter)
     cmd_print_plug_consumption
     ;;
  *)
     show_usage
     ;;
esac

--- Fin del código ---

Le damos permisos de ejecución con chmod +x /etc/hs100_control.sh

Y ahora podemos apagar/encender/manejar el enchufe tal que así:

/etc/hs100_control.sh 192.168.x.x 9999 on
/etc/hs100_control.sh 192.168.x.x 9999 off
/etc/hs100_control.sh 192.168.x.x 9999 check
/etc/hs100_control.sh 192.168.x.x 9999 toggle
/etc/hs100_control.sh 192.168.x.x 9999 emeter

Cambiad la IP 192.168.x.x por la que tenga el enchufe asignada por DHCP. Para distinguir varios enchufes podemos cambiar la IP por el nombre del host, y asignar la MAC del enchufe a un nombre de host distinto en las opciones DHCP de luci.

Podemos meter cada uno de los comandos anteriores en los comandos propios de luci que nos sale al instalar el módulo luci-app-commands.

El puerto siempre parece ser 9999, así que si alguien lo quiere incorporar al script directamente sin ser argumento, es libre de hacerlo.

Por ahora lo que he probado con la última versión de hardware 4.0 y firmware 1.0.2 ha funcionado perfecto. ==>> Actualizado a 1.1.1 sigue funcionando perfecto.

No sé qué manía tienen en hacer que estos aparatejos sólo funcionen con aplicaciones de móvil o chorradas por el estilo. Cuanto más genéricos sean, más aplicaciones podrán tener.

La única pega, al igual que los wemo, es que para configurarlos hay que hacerlo con la aplicación del móvil. Una vez que estén configurados, y yo lo hago para trabajar exclusivamente en local (nada de nubes ni chorradas de esas) son completamente autónomos en la conexión.

Con el tiempo veré si son igual de estables que los wemo, y son capaces de aguantar las conexiones de ON durante meses, sin microcortes.

He medido el cosumo de un HS100 en estado de reposo y me ha dado unos 0.3W. Un Belkin Wemo me da unos 1.3W. La ganancia en consumo es sustancial.

Por ahora van bien.

Usad estos conocimientos bajo vuestra propia responsabilidad.

Tki2000:
Tras unos días trasteando con el script anterior, me pude dar cuenta que los enchufes HS110 con hardware 3.1 no son totalmente compatibles con el script.
No sé por qué razón, la conexión se parte y no devuelve datos con el comando nc de busybox.
El comando completo netcat, sí espera a que devuelva datos, pero la conexión se queda sin cerrar, así que no devuelve los datos hasta que se hace un timeout de la conexión, lo cual puede durar muchos segundos. Para evitar esto me apoyo en a utilidad timeout.
Al final he encontrado una solución que funciona con todos los tipos de enchufe que tengo, pero hay que incorporar un par más de módulos al asunto.

Módulos de openwrt a cargar : coreutils-base64 coreutils-od coreutils-printf coreutils-timeout netcat

He renombrado el comando "status" a "info", que me parece más correcto.

Script:


--- Código: ---#!/bin/sh
##
#  Controls TP-LINK HS100,HS110, HS200 wlan smart plugs
#  Tested with HS100 firmware 1.0.8
#
#  Credits to Thomas Baust for the query/status/emeter commands
#
#  Author George Georgovassilis, https://github.com/ggeorgovassilis/linuxscripts
#
#  2020-06-22 : Tki2000 modifications for running on openwrt ash command shell via a tmp file
#               Needed modules: coreutils-base64 coreutils-od coreutils-printf
#               Working on: HS100 Hw:4.0 Fw: 1.1.1
#  2020-06-26 : Tki2000 modifications for running on openwrt ash command shell.
#               tmp file obsoleted.
#               "status" command renamed to "info"
#               Needed modules: coreutils-base64 coreutils-od coreutils-printf coreutils-timeout netcat
#               Working on: HS110 Hw:3.1 Fw: 1.5.6
#               Working on: HS110 Hw:4.0 Fw: 1.0.4


#echo args are $@
ip=$1
port=$2
cmd=$3

# encoded (the reverse of decode) commands to send to the plug

# encoded {"system":{"set_relay_state":{"state":1}}}
payload_on="AAAAKtDygfiL/5r31e+UtsWg1Iv5nPCR6LfEsNGlwOLYo4HyhueT9tTu36Lfog=="

# encoded {"system":{"set_relay_state":{"state":0}}}
payload_off="AAAAKtDygfiL/5r31e+UtsWg1Iv5nPCR6LfEsNGlwOLYo4HyhueT9tTu3qPeow=="

# encoded { "system":{ "get_sysinfo":null } }
payload_query="AAAAI9Dw0qHYq9+61/XPtJS20bTAn+yV5o/hh+jK8J7rh+vLtpbr"

# the encoded request { "emeter":{ "get_realtime":null } }
payload_emeter="AAAAJNDw0rfav8uu3P7Ev5+92r/LlOaD4o76k/6buYPtmPSYuMXlmA=="


# tools

check_dependencies() {
  command -v nc >/dev/null 2>&1 || { echo >&2 "The nc programme for sending data over the network isn't in the path, communication with the plug will fail"; exit 2; }
  command -v base64 >/dev/null 2>&1 || { echo >&2 "The base64 programme for decoding base64 encoded strings isn't in the path, decoding of payloads will fail"; exit 2; }
  command -v od >/dev/null 2>&1 || { echo >&2 "The od programme for converting binary data to numbers isn't in the path, the status and emeter commands will fail";}
  command -v read >/dev/null 2>&1 || { echo >&2 "The read programme for splitting text into tokens isn't in the path, the status and emeter commands will fail";}
  command -v printf >/dev/null 2>&1 || { echo >&2 "The printf programme for converting numbers into binary isn't in the path, the status and emeter commands will fail";}
}

show_usage() {
  echo Usage: $0 IP PORT COMMAND
  echo where COMMAND is one of on/off/check/info/emeter/toggle
  exit 1
}

check_arguments() {
   check_arg() {
    name="$1"
    value="$2"
    if [ -z "$value" ]; then
       echo "missing argument $name"
       show_usage
    fi
   }
   check_arg "ip" $ip
   check_arg "port" $port
   check_arg "command" $cmd
}

decode(){
   code=171
   offset=4
   input_num=`od -j $offset -An -t u1 -v | tr "\n" " "`
   #echo $input_num > /tmp/control1.txt
   args_for_printf=""
   for element in $input_num
   do
     output=$(( $element ^ $code ))
     args_for_printf="$args_for_printf\x$(printf %x $output)"
     code=$element
   done
   printf "$args_for_printf"
   #echo $args_for_printf > /tmp/control2.txt
}

send_to_plug_withtimeout() {
   ip="$1"
   port="$2"
   payload="$3"
   echo -n "$payload" | base64 --decode | timeout 0.3 nc $ip $port
}

query_plug_withtimeout(){
   ip="$1"
   port="$2"
   payload="$3"
   echo -n "$payload" | base64 --decode | timeout 1.0 nc $ip $port | decode
   echo
}

# plug commands

cmd_print_plug_relay_state(){
   output=`send_to_plug_withtimeout $ip $port "$payload_query" | decode | egrep -o 'relay_state":[0,1]' | egrep -o '[0,1]'`
   #echo $output
   if [[ $output -eq 0 ]]; then
     echo OFF
   elif [[ $output -eq 1 ]]; then
     echo ON
   else
     echo Couldn\'t understand plug response $output
   fi
}

cmd_print_plug_status(){
     query_plug_withtimeout "$ip" "$port" "$payload_query"
}

cmd_print_plug_consumption(){
     query_plug_withtimeout "$ip" "$port" "$payload_emeter"
}

cmd_switch_on(){
     send_to_plug_withtimeout $ip $port $payload_on > /dev/null
     cmd_print_plug_relay_state
}

cmd_switch_off(){
     send_to_plug_withtimeout $ip $port $payload_off > /dev/null
     cmd_print_plug_relay_state
}

cmd_switch_toggle() {
   output=`cmd_print_plug_relay_state`
   if [[ $output == OFF ]]; then
     cmd_switch_on
   elif [[ $output == ON ]]; then
     cmd_switch_off
   else
     echo $output
   fi
}

##
#  Main program
##


check_dependencies
check_arguments

case "$cmd" in
  on)
     cmd_switch_on
     ;;
  off)
     cmd_switch_off
     ;;
  toggle)
     cmd_switch_toggle
     ;;
  check)
     cmd_print_plug_relay_state
     ;;
  info)
     cmd_print_plug_status
     ;;
  emeter)
     cmd_print_plug_consumption
     ;;
  *)
     show_usage
     ;;
esac

--- Fin del código ---

Usad estos conocimientos bajo vuestra propia responsabilidad.

Edito: actualizado código para reflejar que también funciona con HS110 Hw: 4.0 y Fw: 1.0.4

raphik:
Gracias por compartir.

Nunca he usado enchufes inteligentes. Supongo que cada fabricante utilizará un protocolo propio y misterioso a fin de generar dependencia de sus soluciones.

Las dos funciones básicas de un enchufe son conectar y desconectar. A riesgo de simplificar excesivamente su funcionamiento, me pregunto si es posible utilizar directamente los comandos

# encender
echo -n "AAAAKtDygfiL/5r31e+UtsWg1Iv5nPCR6LfEsNGlwOLYo4HyhueT9tTu36Lfog==" | base64 --decode | timeout 0.3 nc 192.168.x.x 9999

# apagar
echo -n "AAAAKtDygfiL/5r31e+UtsWg1Iv5nPCR6LfEsNGlwOLYo4HyhueT9tTu3qPeow==" | base64 --decode | timeout 0.3 nc 192.168.x.x 9999

Saludos.

Tki2000:

--- Cita de: raphik en 26-06-2020, 19:46 (Viernes) ---[..]
Nunca he usado enchufes inteligentes. Supongo que cada fabricante utilizará un protocolo propio y misterioso a fin de generar dependencia de sus soluciones.
[..]

--- Fin de la cita ---

Ni que lo digas. Yo sólo uso los que puedo manejar desde la línea de comandos. Lo demás es ba-su-ra.
Hay muchos protocolos que se pueden descifrar y utilizar desde línea de comandos. Yo incluso enciendo y apago el aire acondicionado (DAIKIN) desde los botones de luci...


--- Cita de: raphik en 26-06-2020, 19:46 (Viernes) ---Las dos funciones básicas de un enchufe son conectar y desconectar. A riesgo de simplificar excesivamente su funcionamiento, me pregunto si es posible utilizar directamente los comandos

# encender
echo -n "AAAAKtDygfiL/5r31e+UtsWg1Iv5nPCR6LfEsNGlwOLYo4HyhueT9tTu36Lfog==" | base64 --decode | timeout 0.3 nc 192.168.x.x 9999

# apagar
echo -n "AAAAKtDygfiL/5r31e+UtsWg1Iv5nPCR6LfEsNGlwOLYo4HyhueT9tTu3qPeow==" | base64 --decode | timeout 0.3 nc 192.168.x.x 9999

Saludos.

--- Fin de la cita ---

Si sólo quieres mandar el comando sin esperar respuesta de datos, sí, lo puedes hacer.
Peo sinceramente, no me veo tecleando la parte de base64 en la línea de comandos de bash...  ;D

Si sólo quieres hacer un on / off y no te interesa el comando de estado, puedes usar el nc de busybox. No te hace falta instalar el netcat completo.


Por cierto, hoy los enchufes HS100 están a 13,59€ en Amaz...es

raphik:
Desde luego, nadie querría teclear el payload a pelo. Pero por largo que parezca el comando se podría utilizar en un script, simplificando enormemente el código. O incluso se podría poner directamente en luci-app-commands.

Dando un giro más de tuerca, igual se puede enviar la salida del base64 a un fichero que después podría leerse, con lo que se podría prescindir de la utilidad base64.

echo -n "AAAAKtDygfiL/5r31e+UtsWg1Iv5nPCR6LfEsNGlwOLYo4HyhueT9tTu36Lfog==" | base64 --decode > menudaocurrencia.txt
cat menudaocurrencia.txt | timeout 0.3 nc 192.168.x.x 9999

En fin, me está dando ganas hacerme con uno de estos enchufes.

Navegación

[0] Índice de Mensajes

[#] Página Siguiente

Ir a la versión completa