Reverse-Engineering Samsung TV Protocols for a Remote Control App
How I built a dual-mode TV remote that works over both IR blaster and Wi-Fi — by decoding NEC protocols, discovering UPnP devices, and speaking WebSocket to Samsung TVs.
My most technically challenging project wasn't a video processor or a math engine — it was a TV remote. Building Samsung TV Remote taught me about infrared protocols, network discovery, WebSocket communication, and the absolute chaos of consumer electronics standards.
Here's what it took to make a phone control a TV.
Two Ways to Control a TV
Samsung TVs can be controlled in two ways. Old-school: blast infrared signals from a phone's IR blaster (some Android phones have one built in). New-school: connect over Wi-Fi and send commands through Samsung's Smart TV API.
I wanted to support both, which meant building two completely different communication systems that share one UI. The user taps "Volume Up" and the app decides whether to fire an IR signal or send a network packet based on how the TV was connected. Simple in theory, complex in practice.
The IR Blaster: NEC Protocol Deep Dive
Android provides ConsumerIrManager for IR blasting. The API is deceptively simple — you call transmit(frequency, pattern) where frequency is the carrier frequency in Hertz and pattern is an array of integers representing alternating on/off durations in microseconds.
The hard part is knowing what pattern to send. Samsung TVs use the NEC infrared protocol. In NEC, a signal starts with a 9000-microsecond burst followed by a 4500-microsecond space (the "leader"). Then comes the data: 8 bits of address, 8 bits of inverted address, 8 bits of command, and 8 bits of inverted command. A logical "1" is a 562-microsecond burst followed by a 1687-microsecond space. A logical "0" is 562 microseconds on, 562 microseconds off.
There's no official database of Samsung IR codes. I found partial lists on hobbyist forums, LIRC remote databases, and by recording signals from a physical Samsung remote using an IR receiver and an Arduino. The Samsung TV address byte is 0x07 and the device code is 0x07. Power is command 0x02, volume up is 0x07, channel up is 0x12, and so on.
I built an encoder that takes a command byte, constructs the full NEC frame with address and inverted check bytes, converts it into the microsecond timing pattern Android expects, and transmits it at 38 kHz. Encoding a single button press generates an array of about 67 integers.
The Wi-Fi Path: UPnP Discovery
For Wi-Fi control, the first challenge is finding the TV on the network. Samsung Smart TVs advertise themselves using UPnP (Universal Plug and Play), specifically the SSDP (Simple Service Discovery Protocol) layer.
SSDP works over UDP multicast. You send an M-SEARCH request to 239.255.255.250:1900 with a specific search target, and any matching device responds with its location URL and service information. Samsung TVs respond with a device description XML that includes the model name, unique device name, and the URL for the control endpoint.
The implementation uses a DatagramSocket with a 3-second timeout. I send the discovery packet, then listen for responses on a background coroutine. Each response is parsed to extract the TV's IP address, friendly name, and model identifier. The discovery runs automatically when the app opens and can be triggered manually if the TV wasn't found.
One gotcha: some routers block UDP multicast between devices. In those cases, SSDP doesn't work and the user has to enter the TV's IP address manually. I added a fallback that tries a direct HTTP connection to common Samsung TV ports (8001, 8002) on every IP in the local subnet. It's slow (takes about 10 seconds to scan a /24 network) but it works when multicast fails.
WebSocket Communication
Once you find the TV, you connect over WebSocket. Samsung Smart TVs from 2016 onward expose a WebSocket server on port 8001 (HTTP) or 8002 (HTTPS). The connection URL includes your app's name (base64 encoded), which the TV displays in a pairing prompt the first time.
After the initial pairing, the TV remembers your app and connects without prompting. The WebSocket communication is JSON-based. To send a key press, you emit a message like:
{"method":"ms.remote.control","params":{"Cmd":"Click","DataOfCmd":"KEY_VOLUP","Option":"false","TypeOfRemote":"SendRemoteKey"}}
The TV responds with an acknowledgment. There's no documentation for the full command set — I discovered available keys by decompiling Samsung's own SmartThings app and extracting the key constants. There are over 150 different key codes, from basic navigation to obscure ones like KEY_CONVERGENCE and KEY_MTS.
The Dual-Mode Architecture
The interesting engineering challenge was making both control methods feel identical to the user. I created a RemoteController interface with methods like sendCommand(command: TvCommand). Two implementations — IrRemoteController and WifiRemoteController — handle the actual transmission.
A ConnectionManager class determines which controller to use. If the phone has an IR blaster and the user hasn't set up Wi-Fi, it defaults to IR. If a Wi-Fi TV is connected, it prefers that (because it's more reliable and supports more commands). The user can also force a specific mode in settings.
The tricky part was timing. IR signals are fire-and-forget — you don't get confirmation that the TV received the command. Wi-Fi commands get an acknowledgment, but with 50-200ms latency. For repeat commands (holding the volume button), IR can fire every 100ms but Wi-Fi needs throttling to avoid overwhelming the TV's command queue. I built a rate limiter that adjusts based on the active connection type.
The Pairing Problem
Samsung TVs have an annoying pairing flow for Wi-Fi control. The first WebSocket connection triggers a popup on the TV asking the user to "Allow" or "Deny" the connection. This only happens once per app name, but if the user denies it (or if the popup times out), the TV blocks that app permanently.
I handle this with a guided setup flow. The app tells the user "Look at your TV — you should see a permission popup. Press Allow on your TV remote." Meanwhile, the app retries the WebSocket connection every 2 seconds for 30 seconds. If the connection succeeds (we receive a ms.channel.connect event), the pairing is complete. If it times out, the app explains how to reset the TV's external device list.
This was one of those features where the code was simple but the UX required a lot of thought. The actual WebSocket retry is ten lines of Kotlin. The tutorial screens, error messages, troubleshooting steps, and edge case handling around it took two days.
What I Learned About Hardware Projects
Building a TV remote was a crash course in a world where documentation is scarce, protocols are proprietary, and every device behaves slightly differently. Samsung TVs from 2014 use different IR codes than 2020 models. Some TV firmware versions have broken WebSocket implementations. Some devices report capabilities they don't actually support.
The biggest lesson: when building on top of proprietary hardware, your debugging skills matter more than your coding skills. I spent more time with Wireshark capturing WebSocket frames and with an oscilloscope verifying IR timing than I did writing Kotlin. The code itself was straightforward — understanding what the code needed to do was the real challenge.