Panasonic TV Plugin

This is a plugin for 2017 Panasonic TVs and older. 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 plugin uses one setting: IP Address of the TV. You need version 3.9.0 of Home Remote (Designer) or later.

I also created a new TVDetails page that incorporates a TV-app browser.

PanasonicTV.plugin (12.0 KB) PanasonicTV.hrp (102.4 KB)

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" };

var http = new HTTPClient();

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":
            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;
            break;                       
        case "MediaCommand":
            if (value == 'PowerToggle') {
                //set Status to value after Toggle
                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, set Status to value after Toggle
                        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 == '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 {            
                //Send command to TV
                response = sendIPCMD(mediaCommandMappings[value]);
            }
            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
        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;

    // 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;
    }  

    var panasonicDevice = plugin.Devices["PanasonicTV"];
    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.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 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;
}
1 Like