Panasonic TV Plugin

This is a plugin for 2017 Panasonic TVs and older (version 2). Unfortunately Panasonic added encryption to their 2018 and newer models and it’s not possible to use encryption libraries within a plugin. Maybe it will be added to The Home Remote in the future. The normal plugin should work on Panasonic TVs that use Firefox OS (later renamed in My Home Screen). For older TVs one could use the WoL version. Older TVs can’t be powered on with the Power command.

The normal plugin uses one setting: IP Address of the TV. You need version 3.9.0 of Home Remote (Designer) or later. The WoL version also needs a MAC address in the settings.

I also created a new TVDetails page that incorporates a Volume slider (mostly for illustration that it can be done) and a TV-app browser.

PanasonicTV_v2.plugin (15.5 KB)
PanasonicTV_WoL.plugin (16.6 KB)

PanasonicTV_Plugin.hrp (100.2 KB)
PanasonicTV_Plugin_WoL.hrp (100.4 KB)

Normal plugin:

plugin.Name = "PanasonicTV";
plugin.OnChangeRequest = onChangeRequest;
plugin.OnConnect = onConnect;
plugin.OnDisconnect = onDisconnect;
plugin.OnPoll = onPoll;
plugin.OnSynchronizeDevices = onSynchronizeDevices;
plugin.PollingInterval = 1000;
plugin.DefaultSettings = { "Host": "192.168.1.100" };

var http = new HTTPClient();
var volumeLevel = 0;

var inputSourceMappings = {
    "HDMI1": "HDMI1",
    "HDMI2": "HDMI2",
    "HDMI3": "HDMI3",
    "HDMI4": "HDMI4",
    "TV": "TV",
};

var mediaCommandMappings = {
    "PowerOff": "POWER",
    "PowerOn": "POWER",
    "PowerToggle": "POWER",
    "Menu": "MENU",
    "Guide": "EPG",
    "Info": "INFO",
    "Option": "SUBMENU",
    "Exit": "CANCEL",
    "VolumeUp": "VOLUP",
    "VolumeDown": "VOLDOWN",
    "Mute": "MUTE",
    "DirectionUp": "UP",
    "DirectionLeft": "LEFT",
    "DirectionDown": "DOWN",
    "DirectionRight": "RIGHT",
    "Select": "ENTER",
    "Back": "RETURN",
    "ChannelUp": "CH_UP",
    "ChannelDown": "CH_DOWN",
    "PrevChannel": "R_TUNE",
    "Rewind": "REW",
    "Play": "PLAY",
    "Pause": "PAUSE",
    "FastForward": "FF",
    "SkipBackward": "SKIP_PREV",
    "SkipForward": "SKIP_NEXT",
    "Stop": "STOP",
    "Record": "REC",
    "Red": "RED",
    "Green": "GREEN",
    "Yellow": "YELLOW",
    "Blue": "BLUE",
    "Number1": "D1",
    "Number2": "D2",
    "Number3": "D3",
    "Number4": "D4",
    "Number5": "D5",
    "Number6": "D6",
    "Number7": "D7",
    "Number8": "D8",
    "Number9": "D9",
    "Number0": "D0",
    "Return": "RETURN",
    "Home": "HOME",

//Panasonic specific commands
    "30Skip": "30S_SKIP",
    "Toggle3D": "3D",
    "Apps": "APPS",
    "Aspect": "ASPECT",
    "Cancel": "CANCEL",
    "CC": "CC",
    "ChatMode": "CHAT_MODE",
    "DigaControl": "DIGA_CTL",
    "Display": "DISP_MODE",
    "EZSync": "EZ_SYNC",
    "Favorite": "FAVORITE",
    "GameMode": "GAME",
    "Help": "GUIDE",
    "Hold": "HOLD",
    "Index": "INDEX",
    "InputAV": "INPUT",
    "Network": "NETWORK",
    "Mpx": "MPX",
    "NetBS": "NET_BS",
    "NetCS": "NET_CS",
    "NetTD": "NET_TD",
    "OffTimer": "OFFTIMER",
    "Pictai": "PICTAI",
    "PNr": "P_NR",
    "Program": "PROG",
    "RScreen": "R_SCREEN",
    "Sap": "SAP",
    "ToggleSDcard": "SD_CARD",
    "Split": "SPLIT",
    "Subtitles": "STTL",
    "Surround": "SURROUND",
    "Swap": "SWAP",
    "Text": "TEXT",
    "VieraLink": "VIERA_LINK",
    "VTools": "VTOOLS"
};

function onChangeRequest(device, attribute, value) {
    var response, powerStatus, muteStatus, tvAppList = [];
    switch (attribute) {
        case "Switch":
            // Since we don't know if TV is On or Off, set status first to Off
            powerStatus = "Off";

            // Use bug to determine if TV is On or Off
            try {
                // Send the volume command
                    response = sendGetVolume();
            } catch(err) {
            // Send didn't work, so TV is Off, new Status will be On
                powerStatus = "On";
            }
            if ( (value == "On" && powerStatus == "On") || (value == "Off" && powerStatus == "Off") ) {
                device.Switch = powerStatus;
		        response = sendIPCMD("POWER");
            }    
            break;                       
        case "MediaCommand":
            if (value == 'PowerToggle') {
                // Since we don't know if TV is On or Off, set status first to Off
                powerStatus = "Off";

                // Use bug to determine if TV is On or Off
                try {
                    // Send the volume command
                      response = sendGetVolume();    
                } catch(err) {
                // Send didn't work, so TV is Off, new Status will be On
                    powerStatus = "On";
                }
                response = sendIPCMD("POWER");
                device.Switch = powerStatus;

                if (powerStatus == "On") {  
                    // Get available Apps from TV
                    sleep(2000);
                    tvAppList = createAppList();
                    //Set App List in Home Remote
                    var panasonicDevice = plugin.Devices["PanasonicTV"];
                    if (panasonicDevice != null) {
                        panasonicDevice.AppList = tvAppList;
                    }
                } 
            } else if (value == "PowerOn") { 
                // Set status to On
                powerStatus = "On";

                // Use bug to determine if TV is On or Off
                try {
                    // Send the volume command
                    response = sendGetVolume();
                    // Send did work, so TV is already On
                } catch(err) {
                    // Send didn't work, so TV is Off
                    response = sendIPCMD("POWER");
                    
                    // Get available Apps from TV
                    sleep(2000);
                    tvAppList = createAppList();
                    //Set App List in Home Remote
                    var panasonicDevice = plugin.Devices["PanasonicTV"];
                    if (panasonicDevice != null) {
                        panasonicDevice.AppList = tvAppList;
                    }
                }
            } else if (value == "PowerOff") { 
                // Set status to Off
                powerStatus = "Off";

                // Use bug to determine if TV is On or Off
                try {
                    // Send the volume command
                    response = sendGetVolume();
                    //TV is On so send PowerOff command
                    response = sendIPCMD("POWER");
                } catch(err) {
                // Send didn't work, so TV is already Off
                }
                device.Switch = powerStatus;   
            } else if (value == "Mute") { 
                //Send Mute command to TV
                response = sendIPCMD(mediaCommandMappings[value]); 
                //Set Mute status in Home Remote 
                response = sendGetMute();
                muteStatus = response.data.elements[0].elements[0].text; 
                var panasonicDevice = plugin.Devices["PanasonicTV"];
                if (panasonicDevice != null) {
                    panasonicDevice.Mute = (muteStatus == 1) ? "Muted" : "Unmuted";
                }  
            } else if (value == "VolumeUp" || value == "VolumeDown") { 
                //Send Volume command to TV
                response = sendIPCMD(mediaCommandMappings[value]); 
                //Set Volume level in Home Remote 
                response = sendGetVolume();
                volumeLevel = response.data.elements[0].elements[0].text; 
                var panasonicDevice = plugin.Devices["PanasonicTV"];
                if (panasonicDevice != null) {
                    panasonicDevice.Volume = volumeLevel;
                }               
            } else {            
                //Send command to TV
                response = sendIPCMD(mediaCommandMappings[value]);
            }
            break;
        case "Volume":
            //Set specific Volume level
            sendSetVolume(value);
            response = sendGetVolume();
            volumeLevel = response.data.elements[0].elements[0].text; 
            var panasonicDevice = plugin.Devices["PanasonicTV"];
            if (panasonicDevice != null) {
                panasonicDevice.Volume = volumeLevel;
            } 
            break;
        case "InputSource":
            //Select Input on TV
            response = sendIPCMD(inputSourceMappings[value]);
            break;
        case "AppSelection":
            //Start selected App
            launchApp(value);
            break;
        default:
            break;
    }
}

function onConnect() {
    var powerStatus = "On", muteStatus, response, tvAppList = [];

    // Use bug to determine if TV is On or Off
    try {
        // Send the volume command
            response = sendGetVolume();
        } catch(err) {
        // Send didn't work, so TV is off
            powerStatus = "Off"
        }

    if (powerStatus == "On") {
        // TV is on and read Volume level
        volumeLevel = response.data.elements[0].elements[0].text;
        // Get mute status
        response = sendGetMute();
        muteStatus = response.data.elements[0].elements[0].text;        
        // Get available Apps from TV
        tvAppList = createAppList();
    }   

    var panasonicDevice = plugin.Devices["PanasonicTV"];
    if (panasonicDevice != null) {
        panasonicDevice.SupportedMediaCommands = Object.keys(mediaCommandMappings);
        panasonicDevice.SupportedInputSources = Object.keys(inputSourceMappings);
        panasonicDevice.Switch = powerStatus;
        panasonicDevice.Volume = volumeLevel;
        panasonicDevice.Mute = (muteStatus == 1) ? "Muted" : "Unmuted";
        panasonicDevice.AppList = tvAppList;
    }
}

function onDisconnect() {
}

function onPoll() {
    var powerStatus = "On", volumeLevel, muteStatus;
    var panasonicDevice = plugin.Devices["PanasonicTV"];
    
    if (panasonicDevice.HasSubscribers()) {
        // Use bug to determine if TV is On or Off
        try {
            // Send the volume command
            response = sendGetVolume();
        } catch(err) {
        // Send didn't work, so TV is off
            powerStatus = "Off"
        }

        if (powerStatus == "On") {
            // TV is on and read Volume level
            volumeLevel = response.data.elements[0].elements[0].text;
            // Get mute status
            response = sendGetMute();
            muteStatus = response.data.elements[0].elements[0].text;
        }
        if (panasonicDevice != null) {
            panasonicDevice.Switch = powerStatus;
            panasonicDevice.Volume = volumeLevel;
            panasonicDevice.Mute = (muteStatus == 1) ? "Muted" : "Unmuted";
        }  
    }
}

function onSynchronizeDevices() {
    var pluginDevice = new Device();

    pluginDevice.Id = "PanasonicTV";
    pluginDevice.Name = "TV";
    pluginDevice.DisplayName = "TV";
    pluginDevice.Icon = "TV";
    pluginDevice.DeviceType = "TV";
    pluginDevice.TileTemplate = "TVTile.xaml";
    pluginDevice.DetailsTemplate = "TVDetails.xaml";
    pluginDevice.Capabilities = ["MediaControl", "MediaInputSource", "Switch", "AudioMute", "AudioVolume"];
    pluginDevice.Attributes = ["AppList", "AppSelection"];
    plugin.Devices[pluginDevice.Id] = pluginDevice;
}

function sendIPCMD(code) {
    var body = "<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_" + code + "-ONOFF</X_KeyEvent></u:X_SendKey></s:Body></s:Envelope>";
    var httpOptions = {
            headers: {
            'SOAPACTION': "\"urn:panasonic-com:service:p00NetworkControl:1#X_SendKey\"",
            'Content-Type': "text/xml"
            }
    };
    return http.post('http://' + plugin.Settings["Host"] + ':55000/nrc/control_0', body, httpOptions);
}

function sendGetVolume() {
    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><m:GetVolume xmlns:m=\"urn:schemas-upnp-org:service:RenderingControl:1\"><InstanceID>0</InstanceID><Channel>Master</Channel></m:GetVolume></s:Body></s:Envelope>";
    var httpOptions = {
        responseType: "xml",
        headers: {
            'SOAPACTION': "\"urn:schemas-upnp-org:service:RenderingControl:1#GetVolume\"",
            'Content-Type': "text/xml"
            }
    };
    return http.post('http://' + plugin.Settings["Host"] + ':55000/dmr/control_0', body, httpOptions);
}

function sendSetVolume(volume) {
    if ( volume < 0 ) {
        volume = 0;       
    } else if ( volume > 100 ) {
        volume = 100;
    }
    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><m:SetVolume xmlns:m=\"urn:schemas-upnp-org:service:RenderingControl:1\"><InstanceID>0</InstanceID><Channel>Master</Channel><DesiredVolume>" + volume + "</DesiredVolume></m:SetVolume></s:Body></s:Envelope>";
    var httpOptions = {
        responseType: "xml",
        headers: {
            'SOAPACTION': "\"urn:schemas-upnp-org:service:RenderingControl:1#SetVolume\"",
            'Content-Type': "text/xml"
            }
    };
    return http.post('http://' + plugin.Settings["Host"] + ':55000/dmr/control_0', body, httpOptions);
}

function sendGetMute() {
    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><m:GetMute xmlns:m=\"urn:schemas-upnp-org:service:RenderingControl:1\"><InstanceID>0</InstanceID><Channel>Master</Channel></m:GetMute></s:Body></s:Envelope>";
    var httpOptions = {
        responseType: "xml",
        headers: {
            'SOAPACTION': "\"urn:schemas-upnp-org:service:RenderingControl:1#GetMute\"",
            'Content-Type': "text/xml"
            }
    };
    return http.post('http://' + plugin.Settings["Host"] + ':55000/dmr/control_0', body, httpOptions);
}

function launchApp(appID) {
    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:X_LaunchApp xmlns:u=\"urn:panasonic-com:service:p00NetworkControl:1\"><X_AppType>vc_app</X_AppType><X_LaunchKeyword>product_id=" + appID + "</X_LaunchKeyword></u:X_LaunchApp></s:Body></s:Envelope>";
    var httpOptions = {
        responseType: "xml",
        headers: {
            'SOAPACTION': "\"urn:panasonic-com:service:p00NetworkControl:1#X_LaunchApp\"",
            'Content-Type': "text/xml"
            }
    };
    return http.post('http://' + plugin.Settings["Host"] + ':55000/nrc/control_0', body, httpOptions);
}

function getAppList() {
    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><m:X_GetAppList xmlns:u=\"urn:panasonic-com:service:p00NetworkControl:1\">None</m:X_GetAppList></s:Body></s:Envelope>";
    var httpOptions = {
        responseType: "xml",
        headers: {
            'SOAPACTION': "\"urn:panasonic-com:service:p00NetworkControl:1#X_GetAppList\"",
            'Content-Type': "text/xml"
            }
    };
    return http.post('http://' + plugin.Settings["Host"] + ':55000/nrc/control_0', body, httpOptions);
}

function createAppList() {
    var appProductID, appName, appThumbnail, appList = [], str, patt1, indexList = [];

    // Get available Apps from TV
    var response = getAppList();
    var appResponseText = response.data.elements[0].elements[0].text;
    var appListRaw = appResponseText.split("vc_app");
    var numberOfApps = appListRaw.length - 1;

    //Convert result into values for Home Remote
    for (var i=1; i<= numberOfApps; i++) {  
        str = appListRaw[i];
        patt1 = /'/g;
        indexList = [];

        while (patt1.test(str)==true)  {
  
            indexList.push(patt1.lastIndex);
        }
        appProductID = str.substring(indexList[1]+11,indexList[2]-1);
        appName = str.substring(indexList[2],indexList[3]-1);
        appThumbnail = str.substring(indexList[3],indexList[4]-1);
        appList[i-1] = { //creates arrray with apps - used for gridview
            appID: appProductID,
            Name: appName,
            Thumbnail: appThumbnail 
        };
    }
    return appList;
}

WoL plugin code can be found below (wouldn’t fit into this post).

1 Like

Continuing the discussion from Panasonic TV Plugin:

Hi, im trying to use this. Only problem is i cant turn on my tv with THR. If i turn it on with my tv remote, then it works fine. Is there a way to turn it on via this app over ip? My tv is a panasonic plasma zt60.
Thank you

I just checked the manual for the ZT60 and under Network Settings > Viera Remote Settings there should be a setting called Wake On Lan. Did you set that one to On?

Yes that worked. I feel dumb…
Thank you very much.
Now i just need to know how to control my bell fibe tv via IP.

Well, it worked on my panasonic app which wasnt working before. But i still cant get it to turn on with Home remote.
Everything else works.

On another site (home assistant) I saw that another Panasonic Plasma (ST50) apparently also doesn’t power on.

What you could try is the Wake on Lan plugin from this site: Wake on Lan Plugin . If that one works I could implement it in the plugin for powering on.

Yes, just tried it and its working. I can turn on the tv!!
Can you implement it to your pluggin?
That would be great.
Thanx for helping

Since I use a trick/bug to determine if the TV is on or off, I’m not sure if that works with your TV. Could you check if the value of the Switch attribute is “On” when the TV is on. Either check with the project above: Tile should be blue if TV is on. Or create a label with the Switch attribute on your own project.

I need that information to determine what I have to change in the plugin.

Hi, yes its working. Tile turns blue when tv is on

Attached is a version of the plugin, that uses WoL to turn the TV on. Depending on how you update the plugin, it could be that you have to add a “Mac” item under Settings with the appropriate value.

PanasonicTV_WoL.plugin (13.4 KB)

Cant seem to get this one to works. It can power off but wont power on.

Made some changes to the original plugin (added volume slider capability) and created a second version that uses Wake On Lan for powering up the TV. Mainly for older TVs. Download files can found in first post.

Code for WoL version:

plugin.Name = "PanasonicTV";
plugin.OnChangeRequest = onChangeRequest;
plugin.OnConnect = onConnect;
plugin.OnDisconnect = onDisconnect;
plugin.OnPoll = onPoll;
plugin.OnSynchronizeDevices = onSynchronizeDevices;
plugin.PollingInterval = 2000;
plugin.DefaultSettings = { "Host": "192.168.1.100", "Mac": "11:22:33:44:AA:BB" };

var http = new HTTPClient();
var volumeLevel = 0;

var inputSourceMappings = {
    "HDMI1": "HDMI1",
    "HDMI2": "HDMI2",
    "HDMI3": "HDMI3",
    "HDMI4": "HDMI4",
    "TV": "TV",
};

var mediaCommandMappings = {
    "PowerOff": "POWER",
    "PowerOn": "POWER",
    "PowerToggle": "POWER",
    "Menu": "MENU",
    "Guide": "EPG",
    "Info": "INFO",
    "Option": "SUBMENU",
    "Exit": "CANCEL",
    "VolumeUp": "VOLUP",
    "VolumeDown": "VOLDOWN",
    "Mute": "MUTE",
    "DirectionUp": "UP",
    "DirectionLeft": "LEFT",
    "DirectionDown": "DOWN",
    "DirectionRight": "RIGHT",
    "Select": "ENTER",
    "Back": "RETURN",
    "ChannelUp": "CH_UP",
    "ChannelDown": "CH_DOWN",
    "PrevChannel": "R_TUNE",
    "Rewind": "REW",
    "Play": "PLAY",
    "Pause": "PAUSE",
    "FastForward": "FF",
    "SkipBackward": "SKIP_PREV",
    "SkipForward": "SKIP_NEXT",
    "Stop": "STOP",
    "Record": "REC",
    "Red": "RED",
    "Green": "GREEN",
    "Yellow": "YELLOW",
    "Blue": "BLUE",
    "Number1": "D1",
    "Number2": "D2",
    "Number3": "D3",
    "Number4": "D4",
    "Number5": "D5",
    "Number6": "D6",
    "Number7": "D7",
    "Number8": "D8",
    "Number9": "D9",
    "Number0": "D0",
    "Return": "RETURN",
    "Home": "HOME",

//Panasonic specific commands
    "30Skip": "30S_SKIP",
    "Toggle3D": "3D",
    "Apps": "APPS",
    "Aspect": "ASPECT",
    "Cancel": "CANCEL",
    "CC": "CC",
    "ChatMode": "CHAT_MODE",
    "DigaControl": "DIGA_CTL",
    "Display": "DISP_MODE",
    "EZSync": "EZ_SYNC",
    "Favorite": "FAVORITE",
    "GameMode": "GAME",
    "Help": "GUIDE",
    "Hold": "HOLD",
    "Index": "INDEX",
    "InputAV": "INPUT",
    "Network": "NETWORK",
    "Mpx": "MPX",
    "NetBS": "NET_BS",
    "NetCS": "NET_CS",
    "NetTD": "NET_TD",
    "OffTimer": "OFFTIMER",
    "Pictai": "PICTAI",
    "PNr": "P_NR",
    "Program": "PROG",
    "RScreen": "R_SCREEN",
    "Sap": "SAP",
    "ToggleSDcard": "SD_CARD",
    "Split": "SPLIT",
    "Subtitles": "STTL",
    "Surround": "SURROUND",
    "Swap": "SWAP",
    "Text": "TEXT",
    "VieraLink": "VIERA_LINK",
    "VTools": "VTOOLS"
};

function onChangeRequest(device, attribute, value) {
    var response, powerStatus, muteStatus, tvAppList = [], mac, ip;
    switch (attribute) {
        case "Switch":
            // Since we don't know if TV is On or Off, set status first to Off
            powerStatus = "Off";

            // Use bug to determine if TV is On or Off
            try {
                // Send the volume command
                    response = sendGetVolume();
                } catch(err) {
                // Send didn't work, so TV is Off, new Status will be On
                    powerStatus = "On";
                }

            if ( value == "On" && powerStatus == "On" ) {
                // Turn TV On with WoL
                mac = plugin.Settings["Mac"];
                ip = plugin.Settings["Host"];
                WOL.wake(mac);
                sleep(500);
                WOL.wake(mac, { address: ip });
                sleep(500);
                WOL.wake(mac, { address: ip, port: 9 });
            } else if ( value == "Off" && powerStatus == "Off" ) {    
                // Turn TV Off        
                response = sendIPCMD("POWER");
            }
            device.Switch = powerStatus;    
            break;                       
        case "MediaCommand":
            if (value == 'PowerToggle') {
                // Since we don't know if TV is On or Off, set status first to Off
                powerStatus = "Off";

                // Use bug to determine if TV is On or Off
                try {
                    // Send the volume command
                      response = sendGetVolume();    
                } catch(err) {
                    // Send didn't work, so TV is Off, new Status will be On
                        powerStatus = "On";
                }

                if (powerStatus == "On") {
                    // Turn TV On with WoL
                    mac = plugin.Settings["Mac"];
                    ip = plugin.Settings["Host"];
                    WOL.wake(mac);
                    sleep(500);
                    WOL.wake(mac, { address: ip });
                    sleep(500);
                    WOL.wake(mac, { address: ip, port: 9 });
                } else {  
                    // Turn TV Off                  
                    response = sendIPCMD("POWER");
                }
                device.Switch = powerStatus;

                if (powerStatus == "On") {  
                    // Get available Apps from TV
                    sleep(2000);
                    try {
                    tvAppList = createAppList();
                    } catch(err) {
                        //Could not retrieve Apps
                        tvAppList = [];
                    }
                    //Set App List in Home Remote
                    var panasonicDevice = plugin.Devices["PanasonicTV"];
                    if (panasonicDevice != null) {
                        panasonicDevice.AppList = tvAppList;
                    }
                }  
            } else if (value == "PowerOn") { 
                powerStatus = "On";
                // Turn TV On with WoL
                mac = plugin.Settings["Mac"];
                ip = plugin.Settings["Host"];
                WOL.wake(mac);
                sleep(500);
                WOL.wake(mac, { address: ip });
                sleep(500);
                WOL.wake(mac, { address: ip, port: 9 });
                device.Switch = powerStatus;

                // Get available Apps from TV
                sleep(2000);
                try {
                tvAppList = createAppList();
                } catch(err) {
                    //Could not retrieve Apps
                    tvAppList = [];
                }
                //Set App List in Home Remote
                var panasonicDevice = plugin.Devices["PanasonicTV"];
                if (panasonicDevice != null) {
                    panasonicDevice.AppList = tvAppList;
                }
            } else if (value == "PowerOff") { 
                powerStatus = "Off";
                response = sendIPCMD("POWER"); 
                device.Switch = powerStatus; 
            } else if (value == 'Mute') { 
                //Send Mute command to TV
                response = sendIPCMD(mediaCommandMappings[value]); 
                //Set Mute status in Home Remote 
                response = sendGetMute();
                muteStatus = response.data.elements[0].elements[0].text; 
                var panasonicDevice = plugin.Devices["PanasonicTV"];
                if (panasonicDevice != null) {
                    panasonicDevice.Mute = (muteStatus == 1) ? "Muted" : "Unmuted";
                }
            } else if (value == "VolumeUp" || value == "VolumeDown") { 
                //Send Volume command to TV
                response = sendIPCMD(mediaCommandMappings[value]); 
                //Set Volume level in Home Remote 
                response = sendGetVolume();
                volumeLevel = response.data.elements[0].elements[0].text; 
                var panasonicDevice = plugin.Devices["PanasonicTV"];
                if (panasonicDevice != null) {
                    panasonicDevice.Volume = volumeLevel;
                }                                
            } else {            
                //Send command to TV
                response = sendIPCMD(mediaCommandMappings[value]);
            }
            break;
        case "Volume":
            //Set specific Volume level
            sendSetVolume(value);
            response = sendGetVolume();
            volumeLevel = response.data.elements[0].elements[0].text; 
            var panasonicDevice = plugin.Devices["PanasonicTV"];
            if (panasonicDevice != null) {
                panasonicDevice.Volume = volumeLevel;
            } 
            break;            
        case "InputSource":
            //Select Input on TV
            response = sendIPCMD(inputSourceMappings[value]);
            break;
        case "AppSelection":
            //Start selected App
            launchApp(value);
            break;
        default:
            break;
    }
}

function onConnect() {
    var powerStatus = "On", volumeLevel, muteStatus, response, tvAppList = [];

    // Use bug to determine if TV is On or Off
    try {
        // Send the volume command
            response = sendGetVolume();
        } catch(err) {
        // Send didn't work, so TV is off
            powerStatus = "Off"
        }

    if (powerStatus == "On") {
        // TV is on and read Volume level
        volumeLevel = response.data.elements[0].elements[0].text;
        // Get mute status
        response = sendGetMute();
        muteStatus = response.data.elements[0].elements[0].text;        
        // Get available Apps from TV
        try {
            tvAppList = createAppList();
        } catch(err) {
            //Could not retrieve Apps
            tvAppList = [];
        }
    }   

    var panasonicDevice = plugin.Devices["PanasonicTV"];
    if (panasonicDevice != null) {
        panasonicDevice.SupportedMediaCommands = Object.keys(mediaCommandMappings);
        panasonicDevice.SupportedInputSources = Object.keys(inputSourceMappings);
        panasonicDevice.Switch = powerStatus;
        panasonicDevice.Volume = volumeLevel;
        panasonicDevice.Mute = (muteStatus == 1) ? "Muted" : "Unmuted";
        panasonicDevice.AppList = tvAppList;
    }
}

function onDisconnect() {
}

function onPoll() {
    var powerStatus = "On", volumeLevel, muteStatus;
    var panasonicDevice = plugin.Devices["PanasonicTV"];
    
    if (panasonicDevice.HasSubscribers()) {
        // Use bug to determine if TV is On or Off
        try {
            // Send the volume command
            response = sendGetVolume();
        } catch(err) {
        // Send didn't work, so TV is off
            powerStatus = "Off"
        }

        if (powerStatus == "On") {
            // TV is on and read Volume level
            volumeLevel = response.data.elements[0].elements[0].text;
            // Get mute status
            response = sendGetMute();
            muteStatus = response.data.elements[0].elements[0].text;
        }
        if (panasonicDevice != null) {
            panasonicDevice.Switch = powerStatus;
            panasonicDevice.Volume = volumeLevel;
            panasonicDevice.Mute = (muteStatus == 1) ? "Muted" : "Unmuted";
        }  
    }
}

function onSynchronizeDevices() {
    var pluginDevice = new Device();

    pluginDevice.Id = "PanasonicTV";
    pluginDevice.Name = "TV";
    pluginDevice.DisplayName = "TV";
    pluginDevice.Icon = "TV";
    pluginDevice.DeviceType = "TV";
    pluginDevice.TileTemplate = "TVTile.xaml";
    pluginDevice.DetailsTemplate = "TVDetails.xaml";
    pluginDevice.Capabilities = ["MediaControl", "MediaInputSource", "Switch", "AudioMute", "AudioVolume"];
    pluginDevice.Attributes = ["AppList", "AppSelection"];
    plugin.Devices[pluginDevice.Id] = pluginDevice;
}

function sendIPCMD(code) {
    var body = "<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_" + code + "-ONOFF</X_KeyEvent></u:X_SendKey></s:Body></s:Envelope>";
    var httpOptions = {
            headers: {
            'SOAPACTION': "\"urn:panasonic-com:service:p00NetworkControl:1#X_SendKey\"",
            'Content-Type': "text/xml"
            },
        timeout: 200
        };
    return http.post('http://' + plugin.Settings["Host"] + ':55000/nrc/control_0', body, httpOptions);
}

function sendGetVolume() {
    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><m:GetVolume xmlns:m=\"urn:schemas-upnp-org:service:RenderingControl:1\"><InstanceID>0</InstanceID><Channel>Master</Channel></m:GetVolume></s:Body></s:Envelope>";
    var httpOptions = {
        responseType: "xml",
        headers: {
            'SOAPACTION': "\"urn:schemas-upnp-org:service:RenderingControl:1#GetVolume\"",
            'Content-Type': "text/xml"
            },
        timeout: 200
        };
    return http.post('http://' + plugin.Settings["Host"] + ':55000/dmr/control_0', body, httpOptions);
}

function sendSetVolume(volume) {
    if ( volume < 0 ) {
        volume = 0;       
    } else if ( volume > 100 ) {
        volume = 100;
    }
    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><m:SetVolume xmlns:m=\"urn:schemas-upnp-org:service:RenderingControl:1\"><InstanceID>0</InstanceID><Channel>Master</Channel><DesiredVolume>" + volume + "</DesiredVolume></m:SetVolume></s:Body></s:Envelope>";
    var httpOptions = {
        responseType: "xml",
        headers: {
            'SOAPACTION': "\"urn:schemas-upnp-org:service:RenderingControl:1#SetVolume\"",
            'Content-Type': "text/xml"
            }
    };
    return http.post('http://' + plugin.Settings["Host"] + ':55000/dmr/control_0', body, httpOptions);
}

function sendGetMute() {
    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><m:GetMute xmlns:m=\"urn:schemas-upnp-org:service:RenderingControl:1\"><InstanceID>0</InstanceID><Channel>Master</Channel></m:GetMute></s:Body></s:Envelope>";
    var httpOptions = {
        responseType: "xml",
        headers: {
            'SOAPACTION': "\"urn:schemas-upnp-org:service:RenderingControl:1#GetMute\"",
            'Content-Type': "text/xml"
            },
        timeout: 200
        };
    return http.post('http://' + plugin.Settings["Host"] + ':55000/dmr/control_0', body, httpOptions);
}

function launchApp(appID) {
    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:X_LaunchApp xmlns:u=\"urn:panasonic-com:service:p00NetworkControl:1\"><X_AppType>vc_app</X_AppType><X_LaunchKeyword>product_id=" + appID + "</X_LaunchKeyword></u:X_LaunchApp></s:Body></s:Envelope>";
    var httpOptions = {
        responseType: "xml",
        headers: {
            'SOAPACTION': "\"urn:panasonic-com:service:p00NetworkControl:1#X_LaunchApp\"",
            'Content-Type': "text/xml"
        },
        timeout: 1000
        };
    return http.post('http://' + plugin.Settings["Host"] + ':55000/nrc/control_0', body, httpOptions);
}

function getAppList() {
    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><m:X_GetAppList xmlns:u=\"urn:panasonic-com:service:p00NetworkControl:1\">None</m:X_GetAppList></s:Body></s:Envelope>";
    var httpOptions = {
        responseType: "xml",
        headers: {
            'SOAPACTION': "\"urn:panasonic-com:service:p00NetworkControl:1#X_GetAppList\"",
            'Content-Type': "text/xml"
            },
        timeout: 1000
        };
    return http.post('http://' + plugin.Settings["Host"] + ':55000/nrc/control_0', body, httpOptions);
}

function createAppList() {
    var appProductID, appName, appThumbnail, appList = [], str, patt1, indexList = [];

    // Get available Apps from TV
    var response = getAppList();
    var appResponseText = response.data.elements[0].elements[0].text;
    var appListRaw = appResponseText.split("vc_app");
    var numberOfApps = appListRaw.length - 1;

    //Convert result into values for Home Remote
    for (var i=1; i<= numberOfApps; i++) {  
        str = appListRaw[i];
        patt1 = /'/g;
        indexList = [];

        while (patt1.test(str)==true)  {
  
            indexList.push(patt1.lastIndex);
        }
        appProductID = str.substring(indexList[1]+11,indexList[2]-1);
        appName = str.substring(indexList[2],indexList[3]-1);
        appThumbnail = str.substring(indexList[3],indexList[4]-1);
        appList[i-1] = { //creates arrray with apps - used for gridview
            appID: appProductID,
            Name: appName,
            Thumbnail: appThumbnail 
        };
    }
    return appList;
}

Just wanted to add my support/interest in supporting the newer Panasonic TVs.

Unfortunately that won’t work (for now). The newer TVs use encryption and the necessary libraries aren’t available within THR. There is a solution in python and that one would have to be converted into a JavaScript one. Which isn’t an easy task :wink:

There is a solution though, but for that you would have to use Home Assistant too. Home Assistant can connect to the newer Panasonic TVs and THR has support for Home Assistant. It really works nice. I have connected two 2021 Panasonic TVs that way.

Thanks @RedGrant - for now I’ll continue to try and build my own plugin via my Vera Home Control, but what with all the encryption stuff it’s proving a challenge - I’m making slow progress