Active Speakers for Rasberry Pi3 Setup

This is an odd post as I had a working set up with a RP2 used as a media centre. Its connected to a 1080p Plasma (Pioneer Kuro) and an AV Amp wire to a 7.1 active speaker setup. This all works fine and the picture and sound are perfect. So I thought updating a smaller set up would be simpler, it wasn't! But the problems I encountered opened doors to technology that is simple to develop, deploy, test and monitor. Turns out the simple setup was more complex than my existing complex setup!


Take a look at how the design of the Node-Red flows developed as I refined the system.
This is the final (for now!) version, but as you read the post you will see how the design evolved.





In another room, I have a more modest setup. A cheap LCD TV connected to an Orange Pi PC running OpenElec. The analogue audio out of the Orange Pi and HDMI output for audio were both enabled. This worked fine as the TV speakers could be used or the active external studio monitor speakers. There were a couple of problems
  • The Software for OpenElec on the Orange Pi One is not well supported and has bugs causing video playback of video files to crash.
  • HD playback from TV headend would crash sometimes say after 20 minutes or less
  • The Apps on the TV like Youtube, iPlayer etc would only use the TV speakers, not the active speakers connected to the analogue audio out of the Orange Pi PC.
  • The speakers would power off after 2 hours of use. This was a feature of the Wemo switched I'd enabled to ensure they were not left on indefinitely. We seldom used the speakers for more than 2 hours but when we did they'd power off and someone would have to press the assigned button on the media centre remote connected to the USB port of the Orange Pi PC.
To address these concerns I thought I'd get an RP3 and use that as a drop-in replacement for the Orange Pi. Sound simple?

When I did this it all went to plan and I had Libreelec up and running with TVHeadend and a bonus was the TV remote worked to control Kodi over HDMI using the CEC controls. I enabled the audio out on the 3.5mm jack and used the cabling that had worked fine on the Orange Pi.

The sound over HDMI was fine but the analogue out from the RP3 was bad, really bad. I tried all the normal fixes, disable low noise audio for keep-alive etc. No luck still bad sound.

Turns out the Orange Pi has a crude analogue PWM audio out - Ok for really basic speakers but on anything more its a no go due to the lack of a good DAC. It's strange that the Orange Pi audio was flawless, must have a real DAC.

So where could I get a good audio signal?  The headphone jack of the TV. The TV had good audio on board and the headphone output was basically the Line out for audio with none of the DSP processing for the built-in TV speakers. This worked well and the sound was perfect once again. An additional benefit was the audio for the internal TV Apps also worked well.

I had been using a single Wemo plug to control the power for the active speakers:
  • Wemo add-on for Kodi
  • Link unused key on the Kodi remote connect via the USB port of the RP3 to run a shell script to invoke the addon
While this was OK I thought it could be improved as the TV remote now worked with Kodi via the HDMC CEC. So I used the keymap program to link the Wemo Toggle to a spare button on the TV remote. It worked - not need for 2 remotes anymore as the TV remote work well.

This initial setup had issues:
  1. I noticed the spare key I'd used only worked when the TV was not running its own built-in apps. 
  2. To turn off the speakers you had to remember to turn them off before you powered down the TV.
  3. If you wanted to turn off the TV while using an App it was worse: 
    1. Quit the app, wait a few seconds for the app to close and the TV CEC to become responsive
    2. Power down the speakers using the Wemo toggle 
    3. Power off the TV.
  4. Turning on the speakers was also a chore: Turn on the TV, wait for 20 seconds for it to have booted enough for the unused button push to be sent via HDMI CEC.
I looked at the options for automating the speakers so they would work automatically: Power on when the TV was switched on, power off when the TV was turned off.
  1. Current sense the power used by the TV and when on power up the speakers e.g. using a relay
  2. Use the USB out of the TV (+5V when the TV is on 0V when the TV is off)
  3. Power up an ESP8266 on the TVs USB port and then use network connections to send 433mHz commands to remote plugs that were already installed.
I looked at 1 and it seemed a good generic approach but overkill. Option 2 was similar. I went with options 3.
  • ESP8266 Wemos D1 clone that powers up makes a web service call to the local DIY home automation web server - Another Wemos clone with an 8266 transmitter used to control lights and sockets around the house). Once the Wemos has made the request to power on the speakers it started a web server that can be polled.
  • Ok so the TV now powers up and the speakers come on before the boot logo has even disappeared - Excellent
  • I have an Orange Pi One I use for transmission and a few python scripts / cron jobs used for home automation, this was given a new cron task - Every minute all a web service on the ESP8266 powered by the TV if its there do nothing, if its gone - POWER OFF the speakers.
This was a simple and cheap option
  1. No custom hardware as it was just a Wemos D1 Mini on a short USB cable.
  2. No mains hardware just used a spare RF socket
  3. Speakers come on within seconds of the TV powering up and power off within ten minutes of the TV having been switched off.
# This retries for around 40s this is long enough for the TV to power on 
# and the ESP8266 to boot up, turn on the speakers and start its web server
# This check is run every 10 minutes from a cron job

wget --timeout=2 --tries=7 -O/dev/null http://192.168.0.182/SHARPTVON
if  [ $? -ne 0 ]; then
        wget -O/dev/null http://192.168.0.237/CONSPEAKERSOFF >> /root/tvspeakersoff.log
fi


This initial setup though simple (bash script above) was hard to debug. I had problems with the TV speakers not always powering up when the TV was turned on. The setup worked fine when I tested it but there were intermittent problems which were hard to debug.

I installed Node-Red using the info on this site:

https://diyprojects.io/install-node-red-orange-pi-running-armbian/

I set up Node-Red to autostart on reboot opened the web URL and wrote a "flow" for what I needed. I did not read any documentation just clicked around and had things working in a few minutes. Very impressive and genuinely easy to use - Amazed.



Using Node-Red has many advantages
  1. The code on the ESP8266 was much simpler it just had to boot up, connect to Wi-Fi and tell the Node-Red server that the TV was powered up by calling an HTTP URL on the Node-Red Server (Code below)
  2. Changes in logic and workflow moved from the ESP8266 to Node-Red so changes were quick and simple to make. 
  3. The interactive debugger was amazing I could use it on my phone or tablet and see the message flow IN REAL TIME. It turned out the 433mHz socket was in a poor reception area - hence the intermittent fault. I moved the sockets close to the transmitter -an ESP8266 that covers the ground and first floors of the house.
I had one odd problem with my initial setup the speakers would power on, then turn off after a few seconds and then power on and stay on. I traced this to the default property of the "trigger" tool it always sends one message by default if you don't want this behaviour you set the initial message payload to nothing. This was in the on-screen help for the trigger control and the change was just a drop-down that defaulted to "1" and I clicked the drop-down selected "Nothing" and then hit "OK" and "Deploy".

Now the TV can be powered on with the remote when it powers on the USB port on the rear powers up and the ESP8266 - Wemos D2 Mini wakes up and tells the Node-Red server the TV is on. The Node-Red flow powers on the speakers and keeps them on until it sees a minute of inactivity from the ESP8266 at which point the Node-Red server turns off the power to the speakers.

When the TV powers on the speakers come on before the TV has even booted. In fact, I had to add a 4-second delay in the ESP8266 code to avoid an audio click heard as the amps were on before the TV had booted its own internal MCU's audio out! Ok, I could have put the delay in the Node-Red code ;)

Again the ability to see the message flow in real time, combined with the ease of use, ease of updating the node red code is refreshing. It opens the door to developing vendor and protocol neutral automation in a scalable, self-documenting manner.

Again some unplanned technical problems taught me some valuable lessons!

Code on the ESP8266

#include
#include
#include
const char* ssid = "wibble";
const char* password = "secret";
void setup() {
  HTTPClient http; // This is the HTTP client that calls the web server to ask for the speakers to be turned on. This only happens once in the setup code
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);     // Initialize the LED_BUILTIN pin as an output
  WiFi.begin(ssid, password);
  Serial.println("");   
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  // Sleep to allow the TV time to power up the Audio so we don't hear a click
  delay(4000); 
}
void loop() {
  HTTPClient http; 
  // This is the HTTP client that calls the node redserver to ask for 
  // the speakers to be turned on. This only happens once in the setup code
  Serial.print("[HTTP] Node Red call begin...\n");
    http.begin("http://192.168.0.251:1880/TVOPERATING/"); //HTTP
    Serial.print("[HTTP] GET...\n");
    // start connection and send HTTP header
    int httpCode = http.GET();
    // httpCode will be negative on error
    if(httpCode > 0) {
        // HTTP header has been send and Server response header has been handled
        Serial.printf("[HTTP] GET... code: %d\n", httpCode); 
        // file found at server
        if(httpCode == HTTP_CODE_OK) {
            String payload = http.getString();
            Serial.println(payload);   
        }
    } else {
        Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
   }  
     http.end();
     delay(10000);   



Removing unneeded Radio transmission
I looked at the finished code and noticed that when the TV was on the ESP8266 was calling the Node-Red web service every few seconds. This, in turn, was blindly calling the 433mHz transmitter enabled ESP8266 based web server which transmitted a radio signal to power on the socket for the speakers EVEN when the speakers were already ON! I modified the Node-Red code to make a note of the state of the TV and only send the Power On radio signal once. 

When the TV was powered off this status was set to 0. This value once set meant the next call from the ESP8266 would trigger the code to send a power on command. After the power on radio signal has been sent, the status of the flow local variable set to 1. 

The switch node that decodes the state of this variable as 3 tests for zero, 1 or any other value. This will handle edge cases e.g. when the unit is rebooted and the variable is null etc.

As you can see the visual programming is self-documenting and easy to follow.






Recently I had issues with the TV needing a periodic reboot. The TV runs Andriod and the Smart part of the TV would become unresponsive. To address this as part of the code to turn off the TV was extended to power off the TV for 20 seconds. This is enough time for the TV to power down, then the TV is turned on again.