Skip to main content

Remote Proxy

Turns an off-the-shelf universal remote into a GEM remote. The driver opens a small HTTP listener that speaks the Roku ECP keypress shape, so any remote with a Roku device profile — Sofabaton, One For All, a Harmony replacement, a control app — can drive GEM activities without a GEM-specific integration.

Incoming key presses are matched against the Remotes table by the caller's IP address, then translated through that remote's key bindings.

Prerequisites

  • A remote (or app) that can be pointed at an arbitrary host and port using a Roku device profile.
  • A static DHCP reservation for each remote. This is not a convenience — the remote's IP is its identity. If a remote's address changes it stops matching its row, and if it picks up an address belonging to a different remote it inherits that remote's bindings.
  • A free port on the GEM host. Default is 8060 to match Roku ECP.

Setup steps

  1. Go to System → Devices → Add Device and choose the Remote Proxy driver (key remote_proxy).
  2. Set the Port attribute, or leave it at 8060. If a real Roku is on the same network, pick something else to avoid a discovery collision.
  3. Under UI → Remotes, add one row per physical remote. Set Address to that remote's LAN IP and pick the AV Zone it controls.
  4. Bind keys on each remote row — a key can select a source, run a command, run a macro, or fall through to the AV zone's current source.
  5. Point the remote's Roku profile at the GEM host IP and the port from step 2.

Attribute reference

ScopeAttributeDirectionNotes
deviceportoptionalListener port. Default 8060.
deviceconnectedread-onlyTrue once the listener is bound.

Address handling and reverse proxies

The listener binds all interfaces and has no credential of its own — the source address is the only thing identifying a caller, so the driver takes it from the TCP peer.

The x-forwarded-for header is ignored by default. It is client-supplied, and because a caller's address selects which remote's authority the request inherits, honoring it from any peer would let anything that can reach the port name itself as a registered remote.

If you front this listener with nginx or another reverse proxy, set trusted_proxies so the header is honored from that proxy — the same setting the rest of GEM uses. Configure it under Server → Network → Reverse proxies, or see Roles for the full description. Remotes on the LAN need no proxy and no setting.

Key bindings vs. caller-named targets

A key press can also carry an explicit JSON payload — {"macro_id":42} or {"zone_id":87,"command":"open"} — instead of a bound key name. Those name a target the caller chose rather than one the integrator configured, so they are always treated as non-elevated: a macro, zone, or device marked Requires elevation will refuse them, and a raw address argument is confined to the target's mapped addresses.

Key bindings are not affected. To drive an elevated macro or a locked-down zone from a remote, bind it to a key on the remote's row — that binding is your configuration, so it carries the remote's authority.

Troubleshooting

SymptomCheck
Listener fails to start with EADDRINUSEPort 8060 is already in use — change the port attribute or stop the conflicting service (often a real Roku discovery agent).
unknown http remote connection: <ip> in the logNo Remotes row has that address. Confirm the remote's current IP and its DHCP reservation.
ignoring x-forwarded-for from untrusted peer in the logA proxy is forwarding to the listener but isn't named in trusted_proxies. Add it, or remove the proxy from the path.
Key does nothing, log shows no actionThe key isn't bound on that remote row and command passthrough found no current source on the AV zone.
caller-named macros are never elevated in the logA JSON payload named a macro that requires elevation. Bind the macro to a key instead.