Control Panasonic TV with Node Red

Hi

I’ve come across some javascript code to control a Panasonic tv, please could someone help me to work this into a flow ?

  var VieraJS = require("viera.js-g30r93g");
  var viera = new VieraJS("ipAddress");
  
  viera.sendRequest("Command", "X_SendKey", "<X_KeyEvent>NRC_" + command.toUpperCase() + "-ONOFF</X_KeyEvent>");
  
  viera.sendCommand("Power");
  viera.sendCommand("Up");
  viera.sendCommand("Menu");
  
  viera.sendHDMICommand(1);
  viera.sendHDMICommand(2);
  
  viera.sendAppCommand("0010000200000001"); // Netflix
  viera.sendAppCommand("0010000100170001"); // Amazon Prime Video
  viera.sendAppCommand("0070000200170001"); // YouTube
  
  viera.getMute((status) => {
    console.log("Mute: " + status);
  });
  viera.getVolume((volume) => {
    console.log("Volume: " + volume);
  });
  
  viera.setMute(true);
  viera.setVolume(30);

Huge credit to the creator if I can get this to work, https://github.com/g30r93g/viera.js

Based on the limited coding knowledge, if the code is calling something (like a function). I need to have that defined somewhere.

The github link, shows the supporting files/information, and what looks to be the key file Viera.js - do I bring that file in via an import of something, or do I copy and paste the content into the same or a different function node ?

This part of Viera.js looks to be for the verification of the IP address - which I potentially don’t need.

var Viera = function(ipAddress) {
        // Check if ipAddress is valid IP address
        var ipRegExp = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/;

        if (ipRegExp.test(ipAddress)) {
            this.ipAddress = ipAddress;
        } else {
            throw new TypeError('You entered invalid IP address!');
        }
    };

This seems to be the meat of what I likely need.

    /**
     * Create and send request to the TV
     *
     * @param {String} type    Type of your request                        
     * @param {String} action  The xml action type to perform
     * @param {String} command The command from codes.txt you want to perform
     * @param {Array}  options Options array (mostly for callback)
     */
     Viera.prototype.sendRequest = function(type, action, command, options) {
        var url, urn;
        if (type === 'command') {
            url = '/nrc/control_0';
            urn = 'panasonic-com:service:p00NetworkControl:1';
        } else if (type === 'render') {
            url = '/dmr/control_0';
            urn = 'schemas-upnp-org:service:RenderingControl:1';
        }

        var body = '<?xml version="1.0" encoding="utf-8"?> \
                    <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> \
                    <s:Body> \
                     <u:'+action+' xmlns:u="urn:'+urn+'"> \
                      '+command+' \
                     </u:'+action+'> \
                    </s:Body> \
                    </s:Envelope>';

        var postRequest = {
            host: this.ipAddress,
            path: url,
            port: 55000,
            method: "POST",
            headers: {
                'Content-Length': body.length,
                'Content-Type': 'text/xml; charset="utf-8"',
                'SOAPACTION': '"urn:'+urn+'#'+action+'"'
            }
        };

        var self = this;
        if (options !== undefined) {
            self.callback = options.callback;
        } else {
            self.callback = function () {};
        }

        var req = http.request(postRequest, (res) => {
            res.setEncoding('utf8');
            res.on('data', self.callback);
        });

        req.on('error', (e) =>{
            console.log('error: ' + e.message);
            console.log(e);
        });

        req.write(body);
        req.end();

        return this;
    };

In settings.js under functionGlobalContext you could add this library to make it available for functions.

Looking at the library, it basically creates and sends an SOAP (XML) envelope to the TV, which you could (re)create yourself and use a http post to send the file directly.

Hi @bakman2

Thanks so much for responding, although you’re going to have to help a novice programmer like me out..

What section or code or file, constitutes being a ‘library’?

And then what would I do within a flow exactly to make use of it ?

I will create an example (can't test it myself).

Awesome, thanks !

This is the idea of the viera.js "library"

[{"id":"41273357.16c744","type":"inject","z":"7be082eb.bb1594","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":170,"y":264,"wires":[["7afa94be.f2855c"]]},{"id":"7afa94be.f2855c","type":"function","z":"7be082eb.bb1594","name":"set xml","func":"xml = '<?xml version=\"1.0\" encoding=\"utf-8\"?><s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"><s:Body><u:X_SendKey xmlns:u=\"urn:schemas-upnp-org:service:RenderingControl:1\"><X_KeyEvent>NRC_POWER-ONOFF</X_KeyEvent></u:X_SendKey></s:Body></s:Envelope>'\nheaders = {\n'Content-Type': 'text/xml; charset=\"utf-8\"',\n'SOAPACTION': '\"urn:X_SendKey#NRC_POWER-ONOFF'\n}\n\nreturn {headers:headers,payload:xml}","outputs":1,"noerr":0,"x":324,"y":264,"wires":[["e5c86241.c123e8","416522f5.a46944"]]},{"id":"61a769eb.a47a88","type":"http request","z":"7be082eb.bb1594","name":"","method":"POST","ret":"txt","paytoqs":false,"url":"http://tv-ipaddress:55000/nrc/control_0","tls":"","proxy":"","authType":"basic","x":642,"y":374,"wires":[["9362629d.fb97f8"]]},{"id":"416522f5.a46944","type":"debug","z":"7be082eb.bb1594","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":622,"y":264,"wires":[]},{"id":"e5c86241.c123e8","type":"xml","z":"7be082eb.bb1594","name":"","property":"payload","attr":"","chr":"","x":468,"y":308,"wires":[["416522f5.a46944","61a769eb.a47a88"]]},{"id":"9362629d.fb97f8","type":"debug","z":"7be082eb.bb1594","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":794,"y":374,"wires":[]}]

Change the http request node:

http://tv-ipaddress:55000/nrc/control_0 replace tv-ipaddress with your tv's ip adress.

As I cannot test it, can't guarantee that it works. But look at all the outputs in the debug window and see what is being send. The viera.js library generates "dynamic" xml output based on the commands. In this example I used the POWER command. All the commands could be dynamic in node-red as well by using a switch node or something.

Thanks, i have tried the flow, but it does not seem to work, returning what I assume is a 400 'bad request' error ?

To help troubleshoot, here's the first debug output, on the message sent

Here's the second

And here is the response back.

Any ideas ?

Checking that URL

http://192.168.102.200:55000/nrc/control_0

I get the following message

image

FYI - I'm currently focussing on this section, to see if we maybe have the wrong one. As 'Power' would be a command ?

        var url, urn;
        if (type === 'command') {
            url = '/nrc/control_0';
            urn = 'panasonic-com:service:p00NetworkControl:1';
        } else if (type === 'render') {
            url = '/dmr/control_0';
            urn = 'schemas-upnp-org:service:RenderingControl:1';

The access denied is likely, because that page expects a POST request. But looking at the headers, I made a typo.

Could you replace the function node with this:

xml = '<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:X_SendKey xmlns:u="panasonic-com:service:p00NetworkControl:1"><X_KeyEvent>NRC_POWER-ONOFF</X_KeyEvent></u:X_SendKey></s:Body></s:Envelope>'
headers = {
'Content-Type': "text/xml; charset=utf-8",
'SOAPACTION': 'urn:X_SendKey#NRC_POWER-ONOFF'
}

return {headers:headers,payload:xml}

It is not the "wrong" one. In the viera.js library there is a codes.txt file containing the available commands.

The function they use is for dynamically generating the xml.

Nope, no joy there, same 400 error .:cry:

Here's the payload..

payload: "<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:X_SendKey xmlns:u="urn:schemas-upnp-org:service:RenderingControl:1"><X_KeyEvent>NRC_POWER-ONOFF</X_KeyEvent></u:X_SendKey></s:Body></s:Envelope>"

I edited it again, more typos. try again.

Nope, still the same 400 error

Here's the payload generated..

payload: "<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:X_SendKey xmlns:u="panasonic-com:service:p00NetworkControl:1"><X_KeyEvent>NRC_POWER-ONOFF</X_KeyEvent></u:X_SendKey></s:Body></s:Envelope>"

added "urn" to the bigining of the xmlns to see it that might be it, nope 400 error again..

payload: "<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:X_SendKey xmlns:u="urn.panasonic-com:service:p00NetworkControl:1"><X_KeyEvent>NRC_POWER-ONOFF</X_KeyEvent></u:X_SendKey></s:Body></s:Envelope>"

New flow:

[{"id":"1640edf4.88503a","type":"inject","z":"99f892ff.d32518","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":192,"y":176,"wires":[["ffca154d.4d456"]]},{"id":"ffca154d.4d456","type":"function","z":"99f892ff.d32518","name":"set xml","func":"msg.payload = '<?xml version=\"1.0\" encoding=\"utf-8\"?><s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"><s:Body><u:X_SendKey xmlns:u=\"panasonic-com:service:p00NetworkControl:1\"><X_KeyEvent>NRC_POWER-ONOFF</X_KeyEvent></u:X_SendKey></s:Body></s:Envelope>'\nmsg.headers = {\n'content-type':'application/xml',\n'SOAPACTION': 'urn:X_SendKey#NRC_POWER-ONOFF'\n}\n\nreturn {headers:msg.headers,payload:msg.payload}","outputs":1,"noerr":0,"x":346,"y":176,"wires":[["1e44c35b.7dde4d","35dd0944.9cff66"]]},{"id":"35dd0944.9cff66","type":"http request","z":"99f892ff.d32518","name":"","method":"POST","ret":"txt","paytoqs":false,"url":"http://tv-ipaddress:55000/nrc/control_0","tls":"","proxy":"","authType":"basic","x":532,"y":220,"wires":[["162310d1.73a797"]]},{"id":"1e44c35b.7dde4d","type":"debug","z":"99f892ff.d32518","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":512,"y":154,"wires":[]},{"id":"162310d1.73a797","type":"debug","z":"99f892ff.d32518","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":666,"y":220,"wires":[]}]

If it doesn't work, I don't know how to help further.

Sadly no, same 400 message again...

Have you tried the JavaScript library from the first post directly, without the “via node-red” part? If that one doesn’t work too, the problem isn’t in your flow.