Roku (HFN) Plugin

I’m getting the message “no public method for the specified argument exists” when I try to synchronize. This is for the 08-04 version as well as the 04-28 version.

Is there a line number associated with the error? What does the console log show?

Wow, how have I never noticed the log at the bottom of the designer. That’s going to make my journey so much easier. Here’s the output:

onConnect called...
  Host: 192.168.0.13
onSynchronizeDevices called...
  done syncing
onDisconnect called...
onConnect called...
  Host: 192.168.0.13
  building apps list...
  setting apps...
  set (12):
No public methods with the specified arguments were found.
onDisconnect called...

The console log is really helpful when trying to debug plugins. If you have the screen space (or a second monitor), it’s really nice to make it a floating window and pull it off to the side so it’s easier to watch while interacting with your plugin in the simulator.

With respect to the specific problem you’re having, the console log was exceedingly helpful in finding where the problem was. As for why it was a problem, I’m a little less clear, because the particular code hasn’t been changed in a while and I think it used to work. Perhaps @bill has some insights on this–javascript is not my strong suit, but the issue was that there was a line in onConnect() (2 of them actually, but you weren’t getting to the second one) that was doing:

console.log(device.SupportedInputSources)

And apparently javascript does not accept the array as-is as the input parameter to the logging function and also did not automatically convert it to a string. I normally run my plugins with the plugin logging stuff I created here (since it is very useful for debugging on the device itself), and since it prefixes every line with a common string, it was always doing the array->string conversion. Still, I was pretty sure I had tested the version posted as-is as well. Sorry about that. I’ll fix the posted plugin shortly–it’s a really simple fix–and hopefully you’ll be good to go!

Thanks so much for your help on this. It would synchronize correctly the first time but would error on subsequent times so I commented out lines 252 and 269:

console.log(" set (" + device.SupportedInputSources.length + "): " + device.SupportedInputSources);

console.log(" set (" + device.SupportedMediaCommands.length + "): " + device.SupportedMediaCommands);

and it works great! That keyboard is fantastic too! I love the way you coded the shift key.

1 Like

Glad you were able to get it (and the keyboard) working! Regarding having to comment out those lines, that was going to be my next suggestion, although it really shouldn’t be necessary–you don’t need to synchronize devices more than once (or at all, I don’t believe, with the new plugin import feature).

UPDATE: this was due to a bug in Home Remote that will be fixed in v3.6.0.

1 Like

I made a couple changes to your plugin.

The block of code that sets SupportedInputSources on the device needs to be called on every connect. Not just on initial connect when building the app list. Devices are completely reset on disconnect so that’s why it needs to be updated again.

I also added the PowerOff command. My Roku Streaming Stick+ supports this command. When it is off, the network is still active so you can still make API calls. It just turns the video output off so you’ll get a black screen.

Roku-HFN-2020-09-12.plugin (9.5 KB)

2 Likes

Thanks, Bill! I incorporated your changes and updated my initial post. I also went ahead and added several other commands that some Roku devices support, just in case you or others are interested in them.

One thing I didn’t add was for input select on a device like Roku TV. Interestingly, those are handled as key-presses, but I think in Home Remote they should be part of the MediaInputSource capability. It should be relatively straightforward to add those, but it does involve some additional logic and, since I can’t test it, I held off. If anyone is interested, let me know, and we can make it work.

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.