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.