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
8060to match Roku ECP.
Setup steps
- Go to System → Devices → Add Device and choose the Remote Proxy driver (key
remote_proxy). - 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. - Under UI → Remotes, add one row per physical remote. Set Address to that remote's LAN IP and pick the AV Zone it controls.
- 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.
- Point the remote's Roku profile at the GEM host IP and the port from step 2.
Attribute reference
| Scope | Attribute | Direction | Notes |
|---|---|---|---|
| device | port | optional | Listener port. Default 8060. |
| device | connected | read-only | True 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.
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
| Symptom | Check |
|---|---|
Listener fails to start with EADDRINUSE | Port 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 log | No Remotes row has that address. Confirm the remote's current IP and its DHCP reservation. |
ignoring x-forwarded-for from untrusted peer in the log | A 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 action | The 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 log | A JSON payload named a macro that requires elevation. Bind the macro to a key instead. |