Wednesday, October 30, 2019

Waking up devices in (Home Assistant)

This is a quick post on a challenge I had to overcome while integrating my SmartTV (an LG TV 55UJ620V) with

I wanted to be able to turn on the TV (as such allowing automations to be built on top of it, or for example turning it on through a voice command via Google Assistant). As such I first resorted to using the Home Assistant Wake on LAN built in integration ( It kind of worked, but was not very reliable (perhaps 2 out of 5 times it would work).

I knew that by definition, the way that (Wake-up On LAN) WOL is implemented is inherently unreliable: essentially the target (dormant) device is expecting a frame with a specific pattern of bytes. If it receives that frame, it wakes up the host, otherwise nothing happens. The device will normally scan for that pattern of bytes in the frame regardless of the type of transport level protocol it may be on top of. In the case of WiFi in particular, there is the probability (high or low, depending on the network conditions) of that single frame not reaching the destination. This probability increases with the more hops we have in between.

With this implementation I was requiring the magic packet to be sent from a host (the Raspberry Pi where I keep running) that is in a separate router vlan, from where the TV is (these are connected via Ethernet and WiFi respectively):

In order to target reliability I scrapped this approach altogether. As both my router and repeater are OpenWRT based, I started by installing the etherwake package onto the repeater, given that this is the closest note to the TV set:

Tested the tool manually and confirmed that it worked:

It was now a matter of scripting it, and for improved reliability, make sure that the script tests the connection after it tries to turn on the TV.

As a quick dirty approach and knowing that the OpenWRT repeater runs a web server, I went for writing a simple cgi-bin shell script that could be called from another host (making a very basic REST web service). This script would just have to send the WOL packet, and for a while check if the TV comes online. If it doesn't it sends the WOL packet a few more times, and tests the TV again:

root@griffinnet-zh-repeater:/www/cgi-bin# cat


echo "Content-type: application/json"
echo ""

if [ -z "$QUERY_STRING" ]; then
    echo "{\"status\":\"error\"; \"message\":\"MAC and IP addresses not specified.\"}"
    exit 1
    MAC_ADDR=$(echo "$QUERY_STRING" | sed -n 's/^.*mac=\([^&]*\).*$/\1/p' | sed "s/%20/ /g")
    IP_ADDR=$(echo "$QUERY_STRING" | sed -n 's/^.*ip=\([^&]*\).*$/\1/p' | sed "s/%20/ /g")


while [ $i -lt "$MAX_RETRIES" ]; do
    WOL_RESULT=$(etherwake -D -i br-lan $MAC_ADDR)

    if [ ! $? -eq 0 ]; then
        echo "{\"status\":\"error\"; \"message\":\"Wake-up On LAN command failed.\"}"
        exit 1

    ping -c $NUM_PINGS $IP_ADDR > /dev/null


    if [ $PING_RESULT -eq 0 ]; then
        echo "{ \"status\" : \"success\"; \"message\" : \"WOL: $WOL_RESULT; Ping Result: $PING_RESULT\" }"
        exit 0

echo "{\"status\":\"error\"; \"message\":\"Exhausted retries. Unable to wake up device.\"}"
exit 1

On the side, it was a matter of registering for this REST endpoint, and use it as the service that turns on the TV, instead of the built-in Wake on LAN integration:

    url: '{{tv_mac}}&ip={{tv_ip}}'
    method: GET
      accept: 'application/json, text/html'
    content_type: 'application/json; charset=utf-8'
    timeout: 60

  - platform: webostv
    name: TV Sala
    filename: webostv.conf
    timeout: 5
      - service: rest_command.wake_up_device
          tv_mac: 'B4:E6:2A:??:??:??'
          tv_ip: ''
        - livetv
        - youtube
        - netflix

Given that the this REST endpoint takes some time to respond (due to the number of pings it performs to test the connection to the TV), the response timeout was increased to 60 seconds.

This is all that it was needed, and so far the feature have been working flawlessly.

No comments: