HTMLRemote Logo

Capture IP/IR Commands

This page explains how to use a small Python script together with Wireshark to capture IP-based control commands (TCP) that are sent by controllers such as MyURemote, Roomie or any other app.

The idea is simple: instead of sending commands directly to a device (for example a Global Caché or an Onkyo receiver), you temporarily send them to a capture PC. On that PC a fake device server is running that logs everything and optionally forwards it. Wireshark can then be used to inspect the packets in detail.

This is very useful if:

  • You want to reverse-engineer commands for devices you don't own yourself.
  • You want to debug Global Caché IR commands (sendir,...).
  • You want to compare what different apps send for the same function.

Requirements

  • Capture PC (Windows, macOS or Linux) on the same LAN/WiFi as your controller (iPad, iPhone, …).
  • Python 3 installed on the capture PC.
  • Wireshark installed on the capture PC.
  • A controller that sends TCP commands (e.g. MyURemote, Roomie, …).

For Global Caché IR commands we typically use TCP port 4998. For fake receivers (e.g. Onkyo IP control) you can choose the port according to the original device protocol.

Python Capture Script

Save the following script as fake_device_server.py on your capture PC. It listens on a TCP port (and UDP on the same port), logs all incoming data in both HEX and ASCII, and sends a minimal OK reply for TCP.

#!/usr/bin/env python3
import socket
import socketserver
import datetime
import sys
import threading

def get_local_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.connect(("8.8.8.8", 80))
        ip = s.getsockname()[0]
    except Exception:
        ip = "127.0.0.1"
    finally:
        s.close()
    return ip

def log_packet(data, client_ip, client_port, proto):
    ts = datetime.datetime.now().isoformat(timespec="seconds")
    print(f"[{ts}] {proto} {client_ip}:{client_port} -> {len(data)} bytes")
    hex_str = " ".join(f"{b:02X}" for b in data)
    print(f"  HEX:   {hex_str}")
    ascii_str = "".join(chr(b) if 32 <= b < 127 else "." for b in data)
    print(f"  ASCII: {ascii_str}")
    sys.stdout.flush()

class TCPHandler(socketserver.BaseRequestHandler):
    def handle(self):
        client_ip, client_port = self.client_address
        print(f"\n=== New TCP connection from {client_ip}:{client_port} ===")
        while True:
            data = self.request.recv(4096)
            if not data:
                print(f"--- TCP connection closed ({client_ip}:{client_port}) ---")
                break
            log_packet(data, client_ip, client_port, "TCP")
            try:
                self.request.sendall(b"OK\r\n")
            except Exception:
                pass

class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    allow_reuse_address = True
    daemon_threads = True

class UDPHandler(socketserver.BaseRequestHandler):
    def handle(self):
        data = self.request[0]
        client_ip, client_port = self.client_address
        log_packet(data, client_ip, client_port, "UDP")

class UDPServer(socketserver.UDPServer):
    allow_reuse_address = True

def main():
    print("=== Fake Device Server (TCP + UDP) ===")
    port = int(input("Port to listen on (e.g. 4998): "))

    local_ip = get_local_ip()
    print(f"\nLocal server IP: {local_ip}")
    print(f"Set this IP and port in your controller (MyURemote, Roomie, ...).")
    print(f"Wireshark filter example: host {local_ip} and tcp.port == {port}")
    print(f"Or for UDP: host {local_ip} and udp.port == {port}\n")

    host = "0.0.0.0"

    tcp_server = ThreadedTCPServer((host, port), TCPHandler)
    tcp_thread = threading.Thread(target=tcp_server.serve_forever, daemon=True)
    tcp_thread.start()
    print(f"TCP server listening on {host}:{port}")

    udp_server = UDPServer((host, port), UDPHandler)
    udp_thread = threading.Thread(target=udp_server.serve_forever, daemon=True)
    udp_thread.start()
    print(f"UDP server listening on {host}:{port}")

    print("\nWaiting for traffic... (Ctrl+C to stop)\n")

    try:
        while True:
            threading.Event().wait(3600)
    except KeyboardInterrupt:
        print("\nStopping fake server...")
        tcp_server.shutdown()
        udp_server.shutdown()
        tcp_server.server_close()
        udp_server.server_close()
        print("Stopped.")

if __name__ == "__main__":
    main()

Setup Capture PC

  1. Install Python 3 (from python.org).
  2. Save the script above as fake_device_server.py on your capture PC.
  3. Open a terminal / command prompt in that folder.
  4. Start the server:
    python fake_device_server.py (Windows) or python3 fake_device_server.py (macOS/Linux).
  5. Enter the port you want to use, e.g. 4998 for Global Caché commands.
  6. Note the IP address shown by the script (e.g. 192.168.0.179).

On the first run, your OS firewall may ask to allow incoming connections for Python. Make sure to allow this, otherwise your iPad/iPhone will not be able to connect.

Configure MyURemote / Roomie

On your controller (for example an iPad running MyURemote or Roomie):

  • Make sure the iPad is on the same LAN/SSID as the capture PC.
  • Open the device configuration (Global Caché, receiver, …).
  • Set the IP address to the capture PC IP shown by the script (e.g. 192.168.0.179).
  • Set the port to the port you entered in the script (e.g. 4998).

Now, when you press a button in MyURemote or Roomie, the command will be sent to the fake device server on your capture PC instead of the real device.

Wireshark Capture

  1. Install and start Wireshark on the same capture PC.
  2. Select the network interface that is connected to your LAN/WiFi.
  3. Use a display filter such as:
    host 192.168.0.179 and tcp.port == 4998
    (replace with your capture PC IP and chosen port).
  4. Press some buttons in MyURemote / Roomie and watch the packets appear.

You now have two views of the same data:

  • The terminal running fake_device_server.py shows HEX and ASCII.
  • Wireshark shows the full TCP/IP packet with all details.

Examples

Example 1 – Global Caché IR command

A typical captured Global Caché IR command looks like this in ASCII:

sendir,2:1,1,37000,1,1,12,11,12,22,12,11,24,22,12,11,24,22,24,22,12,11,24,22,24,22,24,22,12,740.

This contains module and connector (2:1), the carrier frequency (37000 Hz) and all burst-pairs that define the IR waveform. You can store this as-is in your own database or convert it to another format.

Example 2 – Fake Onkyo receiver

For an IP-controlled receiver (for example Onkyo), you can configure MyURemote or Roomie to send commands to the capture PC instead of the real receiver. Even if the receiver is not present, the app will try to send its IP commands and the fake server will log them. This is very useful to reverse-engineer eISCP strings or other proprietary protocols.

Troubleshooting

  • No output in the Python window: check that the controller really uses the capture PC IP and port, and that the firewall on the capture PC allows incoming connections for Python.
  • No packets in Wireshark: verify that you are capturing on the correct interface and that your display filter matches the IP and port reported by the script.
  • Controller “hangs” on connect: some apps expect a quick response from the device. The script sends a simple OK\r\n for TCP connections to avoid timeouts.