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).