Media

Roku: Simple Protocol, Complicated Reality

Local control, silent firmware changes, discovery traps, and the limitation that grew into something bigger

home automationhomeseermediarokuecphdmi-cecnetworksoftwareaudio-videodiscoverysoftware-automation

Roku: Simple Protocol, Complicated Reality

Available now in the HomeSeer plugin store.

Of all the devices I have integrated so far, Roku may have the most refreshingly straightforward local-control protocol.

Its External Control Protocol, or ECP, is plain HTTP on port 8060.

No account.

No pairing ceremony.

No cloud round trip.

Once the Roku is on the local network and permits the command you are asking for, the conversation is wonderfully direct.

After working with some considerably more imaginative protocols, finding one that basically says:

“Send me an HTTP request and I’ll do the thing.”

felt almost suspiciously pleasant.

It was also one of the easiest integrations in the suite to get working.

Which, naturally, means most of the interesting parts of this story are about all the ways it managed to become complicated anyway.

The update that broke half of it

Beginning with Roku OS 14.1, Roku tied portions of ECP control to a per-device setting:

Settings → System → Advanced system settings → Control by mobile apps → Network access

Roku’s current documentation explicitly requires that setting to be Enabled for several classes of ECP commands, including remote-control keypresses.

What made this particularly interesting was what I actually observed on the devices here after the change.

With Network access set to Limited, I saw behavior along these lines:

/query/device-info      allowed
/query/active-app       allowed
/launch/<id>            allowed

/keypress/*             403
/query/apps             403
/query/media-player     403

That is an observed matrix from the Roku versions and devices I tested, not something I want to claim Roku guarantees universally.

Their currently published list of commands requiring full network access is not identical, and firmware behavior can change.

But the practical result here was striking.

A plugin that had worked perfectly could still:

Find the Roku.

Identify it.

Report the active application.

Launch something.

And then suddenly fail to press a single remote-control button.

Nothing in my code had changed.

The Roku had.

That is a particularly unpleasant kind of support problem because it looks exactly like a plugin regression.

So the plugin checks for this condition and explains it.

If a command receives the response associated with restricted network control on the devices I tested, it does not hammer the Roku with retries as though persistence will improve its mood.

It reports something useful:

The Roku’s Network access setting may be limiting ECP control.

And, more importantly:

Here is where to change it.

The difference between:

“The plugin is broken.”

and:

“This Roku setting changed; here is where it lives.”

is roughly forty minutes of somebody’s evening.

I would rather give them the forty minutes back.

Discovery, and the Windows trap

Roku supports SSDP, which is the standard way ECP devices announce themselves on a local network.

Send a multicast discovery request.

Rokus answer with their location.

Everybody shakes hands.

Elegant.

Except on Windows, there is another participant in the conversation.

Windows Firewall.

The SSDP discovery request goes out from the plugin.

The Roku’s response comes back as inbound UDP.

If Windows does not have an appropriate firewall rule for the plugin executable, that reply can disappear before the application ever sees it.

There is no useful application-level error.

The socket simply receives nothing.

And unfortunately:

“Windows discarded every response.”

looks exactly like:

“There are no Rokus on this network.”

That is not a distinction I want discovery to depend upon.

The deliberately boring fallback

So SSDP is not the only path.

If necessary, the plugin can fall back to probing addresses on the local subnet for an ECP responder.

That is much less elegant.

It is also wonderfully difficult for Windows Firewall to misunderstand because those requests are outbound TCP.

I was careful not to turn the fallback into an enthusiastic LAN cannon.

The plugin limits concurrency to 32 addresses at a time, and automatic probing is restricted to networks of /24 or smaller.

That is plenty for a typical home network.

It also means accidentally pointing it at a large business subnet does not cause HomeSeer to celebrate by firing thousands of HTTP requests across the network.

A little restraint is healthy.

What became especially interesting is that I have now encountered essentially this same discovery problem several times.

Roku SSDP.

Plex GDM.

Wemo SSDP.

Different devices.

Different discovery protocols.

Same underlying pattern:

Outbound discovery succeeds. Inbound UDP reply disappears. Application sees nothing.

And the same practical answer:

Try an outbound-only path before concluding the network contains no devices.

That has now become one of the first things I check when discovery mysteriously returns an empty room.

Identity is not an IP address

A Roku is identified internally by its serial number.

Never by its IP address.

That sounds obvious once it is stated, but network software has a natural temptation to confuse:

where I connect to something

with:

what the thing is.

Rokus are DHCP clients.

Today:

192.168.1.42

Next month:

192.168.1.87

Still the same Roku.

If HomeSeer treated the IP address as identity, a lease change could create a new device while leaving the old one behind.

Every event, automation, and reference attached to the original HomeSeer device would now point at a ghost.

So the serial number is the identity.

The IP address merely answers:

Where is this Roku today?

That same rule now appears throughout several of these plugins.

Wemo.

Hubspace.

Roku.

And anywhere else the physical thing remains the same while its network address is allowed to wander.

Identity should describe the thing, not DHCP’s opinion of where it lives this morning.

The artwork problem

Once media information was available, displaying session artwork looked straightforward.

If Plex or another media server says:

Here is the poster URL

why not simply hand that URL to HomeSeer and let the browser display it?

Because the browser is not the plugin.

That distinction turned out to matter.

The plugin machine may already have an authenticated relationship with the media server and communicate with it all day.

The browser displaying the HomeSeer page may not.

So pointing the HomeSeer card directly at the server’s artwork URL can fail when the server refuses the browser’s unauthenticated request.

Everything involved may be sitting on the same LAN.

The authorization boundary still exists.

The plugin itself can fetch the image successfully.

So that is what it does.

It retrieves the thumbnail once and gives HomeSeer a local copy.

Problem solved.

Almost.

The square that ate the poster

Then there was the HomeSeer device-card artwork slot.

It is square.

Movie posters generally are not.

A typical movie poster is vertical, somewhere around a 2:3 aspect ratio.

Hand that directly to a square slot and the interface can stretch it to fit.

The result is technically a poster.

Just one whose cast appears to have experienced an unfortunate encounter with image geometry.

I tried preserving native sizing.

The square had other plans.

So the plugin now places the original artwork onto a square canvas while preserving its proportions.

The card remains square.

The poster remains shaped like a poster.

And nobody’s face has to be redesigned by CSS.

Sometimes software engineering is orbital mechanics.

Sometimes it is convincing a rectangle that it is allowed to remain a rectangle.

The wall that started something bigger

Then I ran into the limitation that eventually created an entirely different plugin.

I wanted HomeSeer to know the room’s actual volume.

The Roku can send volume commands.

So at first it seems reasonable to assume it must know the volume.

A Roku streaming player, however, is often not the device actually controlling the speakers.

When connected to a television or AV system, it can hand volume-control commands to the downstream equipment through HDMI-CEC.

It can essentially say:

Volume up.

The television or receiver handles the request.

The Roku does not necessarily know the resulting volume level.

That is an important distinction.

Newer Roku firmware can report whether TV power control or audio-volume control is supported or enabled.

That still does not give me:

The television is currently at volume 27.

The actual state belongs to the device that owns the audio.

So the Roku can forward:

Volume Up

without having any authoritative answer to:

How loud is the room now?

It is sending a command downstream.

The result happens somewhere else.

The only thing that knows is the thing doing it

That realization changed the way I looked at the integration.

If I want the television’s actual volume:

Talk to the television.

If an AV receiver owns the speakers:

Talk to the receiver.

The Roku is not doing anything wrong.

I was simply asking one component for state owned by another.

And once I stopped thinking of the Roku as the AV device and started thinking of it as one participant in an AV system, a much larger architecture appeared.

A room might contain:

A Roku.

A television.

An AV receiver.

A media server.

An infrared-controlled device.

Something serial.

Something networked.

Each one knows a different part of the room.

No single box necessarily knows the complete state.

What I actually wanted was a layer capable of understanding the room, rather than merely one device inside it.

And that became the AV plugin.

What I like about this one

Roku’s ECP is still one of the nicest protocols in this whole collection.

It is local.

It is simple.

It is documented.

It uses ordinary HTTP.

The interesting lessons came mostly from everything surrounding it.

A firmware update can change permission behavior without changing your code.

Discovery can fail because Windows silently discards the answer.

An IP address is not an identity.

The browser and the plugin do not necessarily have the same authorization.

A square UI element can apparently have very strong opinions about movie posters.

And a device capable of sending a command does not necessarily know the state produced by that command.

That last one is the lesson that escaped this plugin entirely.

The Roku plugin did not fail because it could not tell me the television’s volume.

It exposed the fact that I was asking the wrong device.

And that small limitation eventually became the starting point for one of the largest projects in the suite.

In my experience, that is how quite a few of the interesting ones begin.

You hit a wall.

Then discover it was actually a door.