Roku (HFN) Plugin

Here I made another change. I’ve also added PowerOn & PowerToggle commands.

@amingle sent me a message the other day showing his use of the Power/PowerToggle command on a Roku TV. Just out of curiosity, I tested it on my Roku Streaming Stick+ & it worked on there too.

I was incorrect earlier when I said it was turning the video off. It’s actually turning the TV off. I assume it’s using HDMI CEC to incorporate these power controls.

Roku-HFN-2020-09-16-B.plugin (10.4 KB)

1 Like

Thanks again, Bill! I went ahead and also added in all of the other documented commands as well as adding the input commands. The input commands work through the InputSource attribute, just like the apps do, so, for example, you can InputSource = "InputHDMI1". Internally, it checks to see whether the input you’re switching is one of the predefined ones or an app and then sends the appropriate request.

I’m not able to test the input commands because I just have a regular Roku, but I’d appreciate it if someone could let me know if they work on a Roku TV.

As I was typing this up, it occurred to me that more work may still be needed because when reading InputSource, it will only show the current app, not the current TV input. I can’t really think of an easy fix for that problem. It would probably be better to have a second capability/attribute, or maybe even a second device, like a multi-zone receiver, one for the streamer portion and one for the TV portion, each having it’s own associated input… Without someone to at least assist in testing, however, I don’t want to go down that road blind (since I don’t have a Roku TV to test against). It’s an interesting problem, though.

1 Like

I have a Roku TV, so I can take a look, hopefully / maybe this weekend. I ended up taking your original Roku plugin and modified it to add in some things about current TV input / live TV channel / etc., so I’ll see what else I did that may help round it out for TVs. I integrated a “now playing” functionality that has to do different things based on whether it is playing an app or playing OTA TV. If I understand your post right, that’s getting at the heart of what you’re talking about here.

Thanks for all your work with this.

Thanks so much for testing it out–I hate not being able to test code before I publish it, but couldn’t be helped in this case. I’d also be very interested to see what other features you worked into your modified plugin and figure out a good way to incorporate them in so we can have a good one-stop-shop Roku plugin.

Overall, everything seems to work well. I have a couple of comments about your Power implementation and your Input implementation:

Power

These edits are part of the ‘if(mc)’ statement in Line 228. I added Wake-on-LAN functionality for a TV that is ‘deep sleeping’ (code borrowed from the WOL plugin floating around the forum).

if(mc) {
	if(mc !== 'Power') {
		//Regular commands
		console.log("  sending keypress command: " + mc);

		http.post("http://" + host + ":8060/keypress/" + mc);
	}	else {
		console.log("  sending power command");

		try {
			var keyResponse = http.post("http://" + host + ":8060/keypress/Power", "", { timeout: 1000 });
			console.log("  no WOL needed...powered on / off normally");
		}	catch (e) {
			console.log('  TV is most likely powered off in deep sleep...trying WOL');
			
			var mac = plugin.Settings["MACAddr"];;
			var ip = host;

			WOL.wake(mac);
			sleep(500);
			WOL.wake(mac, { address: ip });
			sleep(500);
			//WOL.wake(mac, { address: ip, port: parseInt(9) });
			WOL.wake(mac, { address: ip, port: 9 });
		};
	}
}

Input Source
Using keypress for those 6 inputs works ok, as long as someone did not rename the HDMI ports, for instance. What I would recommend is exclusively using apps to call up the correct input. Here is my list of apps:

AV receiver,Wall Jack,HDMI 3,Live TV,Prime Video,The Roku Channel,Pandora,PBS KIDS,ESPN,4K Spotlight,MLB,NBA,NASA TV,Roku Media Player,ABC,YouTube

AV receiver is my renamed HDMI 1, Wall Jack is my renamed HDMI 2, and Live TV is what the TV automatically calls Tuner. All of those can be called up using the ‘/launch/’ syntax, so I have found no need to use keypress for this.

Current TV channel
Your plugin does seem to be pulling the correct app in for InputSource. I take it one step further if the current app is Live TV to determine the current channel and use /query/tv-active-channel: Link to the Roku ECP docs.

Hope that helps.

@amingle Thank you so much for the feedback and testing! Couple of follow-up questions, just so I’m sure I understand things:

Power

Out of curiosity, will the TV always power on if you send it WOL? That is, is there any reason to ever actually try to send the Power command first? I implemented WOL functionality in my Sony BD Player plugin and just use the WOL message as the PowerOn action. I’m wondering if that would work for the Roku TV as well? It has the advantage of being simpler and faster (since you don’t have to wait for the initial Power command to timeout first).

Input Source

Are you saying that the apps list also includes all of the input names and that “launching” those works fine? If so, I will gladly pull all of that input code out. I just assumed from reading the docs that those were separately handled, but treating them all as apps is much cleaner.

Current TV channel

Have you already written the code to determine the active TV channel? If so, how are you exposing that information? That is, what attribute do you use to provide the name of the channel when the InputSource is “Live TV”? I’m hesitant to overwrite the InputSource itself for that situation. @bill I see that there is a TvChannel capability–would that be the appropriate place to put it?

Yes. You can use the TvChannel capability. When setting the attributes you’ll have to do it like this:

device["TvChannel.Name"] = "Channel Name";
device["TvChannel.Number"] = "Channel Number";

That was a good question that I had to test. But, surprisingly, no - it does not seem to work if it is in ‘Display Off’ mode (which is about 15 minutes before entering Deep Sleep). My guess is that it already considers the device powered on.

Exactly right. It works your way too, but I have left them all as apps.

Here is the code I use, adapting it to use the TvChannel capability Bill mentioned (which I had not previously been using):

var host = plugin.Settings["Host"];

var httpConfig = { responseType: 'xml', timeout: 1000 };
var npResponse = http.get("http://" + host + ":8060/query/active-app", httpConfig);
var npElement = npResponse.data;

var tvChannel_number = '';
var tvChannel_name = '';
var tvProgram_name = '';
var validSignal = true;
npElement.elements.forEach(function (node) {
	if(node.attributes) {
		//If an app is active
		
		//If Live TV, get the channel information
		if(node.attributes.id === 'tvinput.dtv') {
			var httpConfig = { responseType: 'xml', timeout: 1000 };
			var response = http.get("http://" + host + ":8060/query/tv-active-channel", httpConfig);
			var element = response.data;
			
			element.elements.forEach(function (channel) {
				channel.elements.forEach(function (item) {
					switch (item.name) {
						case 'number': 
							tvChannel_number = item.text;
							break;
						case 'name': 
							tvChannel_name = item.text;
							break;
						case 'program-title': 
							tvProgram_name = item.text;
							break;
						case 'signal-state':
						
							if(item.text === 'none') {
								validSignal = false;
							};
							break;
						default:
					};
				});
			});

			if(validSignal !== true) {
				tvProgram_name = '';
			};
		};
	}	else {
		//No app active - TV is likely powered off or display off, or on the Home screen
	};
});

device["TvChannel.Name"] = tvChannel_name;
device["TvChannel.Number"] = tvChannel_number;
//NOTE: currently nothing is being done with the TV program name (tvProgram_name)

Let me know what else I can help with!

Awesome, thanks! I am swamped this week, but will try to get these changes in later in the week or over the weekend.

Ahh don’t you hate it when real life gets in the way!

One addition that I just stumbled across tonight. When you are saving the appId and appName, they can come in with non-breaking spaces, which causes issues if you are later trying to compare with values typed in Home Remote that have a regular space. So, I modified the block of code to look like this:

//Need to replace nbsp with a regular space
var re = new RegExp(String.fromCharCode(160), "g");

apps = {};
appsElement.elements.forEach(function (node) {
	var appId = node.attributes.id.replace(re, " ");
	var appName = node.text.replace(re, " ");
	apps[appName] = appId;
});

Hopefully that makes sense. It was a weird issue, where I was trying to use HomeRemote to call a specific input, and it was not working. I ended up using charCodeAt to look at each character to see what was going on. Mystery solved.

Thanks, Bill! I saw you added TvChannel.ProgramTitle as well–very cool! I’m still doing some fine-tuning and testing before making another release of the plugin, but I had a quick question for you, @bill: in onConnect(), we store off the apps list into a plugin global variable (that was how it was done in your original plugin). I was thinking about it some more and, in theory, someone could add apps and then that list would be out of date until the remote was restarted. Is there any particular reason not to retrieve and regenerate the apps list every time onConnect() is called? Or is my understanding of how state is maintained incorrect?

That’s correct. You can update in onConnect if you want. I’m sure any performance improvement by skipping the call is negligible.

I’ve integrated the various changes discussed above, sometimes taking my best guess on how to handle a few situations. Please let me know if you have any problems with the latest version. One thing, @amingle, I wanted to call out is that I put in the valid signal check, but rather than clearing the ProgramTitle, I set it to “NO SIGNAL”. I’m open to change how that works, but I though it was a little more explicit/informative than simply clearing ProgramTitle.

In addition, one thing that is kind of bugging me is the WOL/PowerToggle portion of the code. Since the plugin does not currently “know” power state of the device, it in theory could send WOL for a PowerToggle even if you are trying to turn it off. That isn’t super-likely to happen because the WOL only gets sent if the Roku doesn’t respond within a second to the power command, but it’s just kind of ugly and I would recommend staying away from PowerToggle if possible.

It does raise a broader question, however, of how Switch should be handled in plugins. I’ve never incorporated it into any of mine because I use the explicit PowerOn/PowerOff Media Commands, but I thought I’d see, @bill, if you had any thoughts on the preferred convention for the Switch capability? I’m guessing in order to implement it correctly, you would have to continuously poll for power state information? If we did have reliable power state info, the WOL/PowerToggle thing could probably be cleaned up some more.

I probably wouldn’t worry about incorporating the Switch capability because, yeah, there really isn’t a reliable way to determine the current power state. None that I’m aware of anyway.

This all looks good to me. I don’t see any issues with how you implemented anything here. Thanks for your work on this.

Came across an interesting part of this plugin this week. I changed the above line to be:
device.SupportedInputSources = Object.keys(apps).sort();

This way, the list of Supported Input Sources is alphabetized (handy for a dropdown menu).

However, the default JS sort() is case sensitive. I ran into this with discovery+, which has a lower case app name. So, I changed it to:

device.SupportedInputSources = Object.keys(apps).sort(
	function(a, b) {
		if (a.toLowerCase() < b.toLowerCase()) return -1;
		if (a.toLowerCase() > b.toLowerCase()) return 1;
		return 0;
	}
);

Now, the list is alphabetized regardless of case.

That’s an interesting point about sorting the sources, I assume you put them in a ComboBox to get the drop-down list? I haven’t actually used that control before. @bill, I’m curious, from a design/HR usability standpoint, does it make more sense to add a sort capability to the control itself (with an option to do a case-insensitive sort)? That way you could do it for any set of inputs (or other values you were putting in the ComboBox, regardless of whether they were from a plugin device or a built-in device. Obviously the plugin device code can always be modified, but with a built-in device, there’s no way to get a sorted list if you wanted to, correct? Is that a reasonable thing to put in a feature request?

That’s correct. I can switch inputs using the combo box options. Interesting thought about putting that at the designer level instead of within the plugin.

I don’t know if this is something I want to add to the ComboBox control. At least not now anyway. If the user really wants to customize the order they could just manually assign the Items instead of Binding to the ItemsSource. That or they can customize the plugin.

But manually assigning would only work if the item contents were known ahead of time, right? Otherwise, if the data from the binding varied over time and was not necessarily predictable, that wouldn’t be an option, would it? I’ve never really done anything with the ComboBox, so I may be off base here/not understanding exactly what you’re proposing.