0

Bravia TV Bias Lighting with a Pi

How it started

I decided to treat myself to a new Sony Bravia XH81 a few days ago because I wanted something that was half-decent and ran Android TV, plus I need an IPS panel due to the wide angles in my living room.

Anyway, I was looking into bias lighting and read constant reports of a long-standing issue with Sony’s Android TVs where the USB power not only doesn’t turn off properly during standby, it randomly turns off and back on, causing flickering of any lights that you have plugged in. I ordered a Luminoodle bias lighting strip to see what all the fuss was about.

I’ve just stopped using my Logitech Harmony, and would rather just use the Sony remote since I now don’t need any others, so running the bias lighting from mains and using a remote to toggle the power is out of the question – as is manually switching at the wall. Not to worry though – I found a dodgy workaround!

I did an nmap of my TV and found that it had a web server running:

$ nmap 192.168.0.14
Starting Nmap 7.80 ( https://nmap.org ) at 2021-01-17 17:42 GMT
Nmap scan report for 192.168.0.14
Host is up (0.023s latency).
Not shown: 994 closed ports
PORT     STATE SERVICE
80/tcp   open  http
7000/tcp open  afs3-fileserver
8008/tcp open  http
8009/tcp open  ajp13
8443/tcp open  https-alt
9000/tcp open  cslistener

Nmap done: 1 IP address (1 host up) scanned in 0.20 seconds

Thankfully, this turns out to be for a REST API which happens to have a method to get the power status of the TV (active/standby)!

I remembered that I have a Raspberry Pi in the cabinet under my TV which doesn’t have anything plugged into the USB sockets (it’s just running Pi-Hole so is headless). Looks like a good enough lighting controller for me and only needs a USB extension cable to make it viable.

I decided to knock-up a script and systemd service whilst I waited for my lights to arrive, and sure enough, they work! So if you have a spare Raspberry Pi or other Linux box knocking around and want to re-purpose it as a lighting controller, read on!

Doing this yourself

First up, you’ll need to get the Pi running headless and SSH to it. The Pi can’t turn individual USB ports on or off, only the whole lot of them, so using a keyboard and mouse isn’t going to be good for your mental health here.

Once you’ve got that out of the way, install uhubctl:

apt install uhubctl

Then you’ll need the script to do the check and toggle the USB power accordingly. This will need updating with the IP address of your TV, and if you’re using a Pi 4B or something that isn’t a Pi, will need the port that uhubctl manages changing as per this link (I don’t have one to test).

Write the below to /usr/local/bin/bravia-bias-lighting and update as required (note the BRAVIA_PSK variable is unset – it seems that this method works without authentication even when the PSK is defined on the TV, but I added support in case Sony changes that in a later update):

#!/bin/bash

BRAVIA_IPADDR="192.168.0.14"
BRAVIA_PSK=""

# This doesn't seem to be required even when set on the TV at the time of writing
# but Sony could change this behaviour.
if [[ ! -z ${BRAVIA_PSK} ]]; then
  PSK_HEADER="-H \"X-Auth-PSK: ${BRAVIA_PSK}\""
fi

# Get TV power state
API_CALL=`curl -H "Content-Type: application/json" ${PSK_HEADER} \
-X POST -m 2 -s \
-d '{"id": 50, "method": "getPowerStatus", "version": "1.0", "params": [""]}' \
http://${BRAVIA_IPADDR}/sony/system`

# Record current USB power state
/usr/sbin/uhubctl -l 1-1 -p 2 | grep -q "0100 power"
if [ $? -eq 0 ]; then
  USB_STATE=on
else
  USB_STATE=off
fi

# Decide if power toggle is required
if echo ${API_CALL} | grep active; then
  if [ "${USB_STATE}" == "off" ]; then
    /usr/sbin/uhubctl -l 1-1 -p 2 -a on
    echo NOTICE: Lights enabled due to active state
  fi
  echo > /var/run/bravia-bias-lighting.failures # reset failure count
elif echo ${API_CALL} | grep standby; then
  if  [ "${USB_STATE}" == "on" ]; then
    /usr/sbin/uhubctl -l 1-1 -p 2 -a off
    echo NOTICE: Lights disabled due to standby state
  fi
  echo > /var/run/bravia-bias-lighting.failures # reset failure count
else
  # Turn off if API is unreachable for 1 minute (powered off?)
  date >> /var/run/bravia-bias-lighting.failures
  echo ERROR: Unexpected API response
  if [ $(cat /var/run/bravia-bias-lighting.failures | wc -l) -gt 6 ] && [ "${USB_STATE}" == "on" ]; then
    echo ERROR: Lights disabled due to no or bad API response for 6 attempts
    /usr/sbin.uhubctl -l 1-1 -p 2 -a off
  fi
fi

Copy the below to /etc/systemd/system/bravia-bias-lighting.service:

[Unit]
Description=Checks Bravia TV power state and toggles local USB power
Wants=bravia-bias-lighting.timer

[Service]
Type=oneshot
ExecStart=/usr/local/bin/bravia-bias-lighting

[Install]
WantedBy=multi-user.target

and copy this to /etc/systemd/system/bravia-bias-lighting.timer:

[Unit]
Description=Checks Bravia TV power state and toggles local USB power
Requires=bravia-bias-lighting.service

[Timer]
OnCalendar=*:*:0,10,20,30,40,50
Unit=bravia-bias-lighting.service
AccuracySec=1s

[Install]
WantedBy=timers.target  

Now you’ll need to make the script executable and get that systemd timer on the go:

chmod +x /usr/local/bin/bravia-bias-lighting
systemctl daemon-reload
systemctl enable bravia-bias-lighting.service
systemctl enable bravia-bias-lighting.timer
systemctl start bravia-bias-lighting.timer

At this point, your the script will now be run every 10 seconds (you can change that in the timer unit if you like), and the USB ports should be turned on or off accordingly. The USB ports will be disabled after a minute of an invalid or no response from the API (in case the TV is off at the wall rather than in standby mode). You can monitor the script using journalctl -f -xe -u bravia-bias-lighting.service. Plug in your lights and enjoy – they’ve made a huge difference to viewing in the dark for me!

A thought about logging

Since I’m running this on a Pi using an SD card, I want to avoid unnecessary writes and wear. Systemd’s journal doesn’t support the suppression of logging for timers, so you may want to think about the journal storage once you’ve got this up and running. I’ve opted to disable journal storage by setting Storage=none in /etc/systemd/journald.conf to avoid unnecessary wear on the card. I also added the below lines to my /etc/rsyslog.conf and restarted rsyslog:

###############
#### RULES ####
###############

:msg, contains, "Bravia" stop
:msg, contains, "bravia-bias-lighting" stop
:programname, contains, "bravia-bias-lighting" stop

This obviosuly makes any issues on the Pi a bit harder to diagnose, but I feel like it’s a worthwhile trade-off in my use-case, and I can always enable logging again later.

Martin

Network / Systems Engineer, spends a lot of time working (and playing) with Cisco, Juniper and Linux.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.