Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.DS_Store
__pycache__
96 changes: 48 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,61 +31,58 @@
**iRemote** is a web-based universal IR blaster interface powered by a Python backend. Plug in a USB IR blaster, run the server, and control any infrared device from any browser — no WebUSB or special browser extensions required.

Works with cheap USB IR blasters you can find on AliExpress for $3–8:

- **Tiqiaa** (uses the ZaZa Remote app on mobile)
- **Ocrustar / ElkSmart** (uses the Smart IR Blaster app on mobile)

## Features

| Feature | Description |
|---------|-------------|
| 🎮 **Universal Remote** | TV remote with multi-blast (Samsung + LG + Sony + Philips simultaneously) |
| 🏨 **Hotel TV Toolkit** | Pre-built macros for hotel/hospitality TV menus — Samsung, LG, Sony, Philips |
| Feature | Description |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| 🎮 **Universal Remote** | TV remote with multi-blast (Samsung + LG + Sony + Philips simultaneously) |
| 🏨 **Hotel TV Toolkit** | Pre-built macros for hotel/hospitality TV menus — Samsung, LG, Sony, Philips |
| 📚 **IR Database Browser** | Browse 300,000+ IR codes from [Flipper-IRDB](https://github.com/Lucaslhm/Flipper-IRDB) and [irdb](https://github.com/probonopd/irdb) |
| 📂 **File Import** | Flipper Zero `.ir`, IRDB `.csv`, Pronto hex, raw pulse data, iRemote Android `.json` |
| 🔴 **IR Learning** | Capture signals from physical remotes (point and press) |
| ⚡ **Batch Learn** | Pre-define button names, learn them one-by-one — fast full remote capture |
| ❄️ **AC / Fan Remotes** | Temperature control, mode switching, fan speed |
| 📡 **Protocol Sender** | Manual NEC / Samsung32 / RC5 / Sony SIRC with hex address + command |
| 💾 **Saved Remotes** | Persist custom remotes, export/import as JSON |
| 🔧 **Custom Macros** | Build your own IR button sequences with delays |
| 📋 **Full Logging** | See every USB frame, protocol encode, and device response |
| 📂 **File Import** | Flipper Zero `.ir`, IRDB `.csv`, Pronto hex, raw pulse data, iRemote Android `.json` |
| 🔴 **IR Learning** | Capture signals from physical remotes (point and press) |
| ⚡ **Batch Learn** | Pre-define button names, learn them one-by-one — fast full remote capture |
| ❄️ **AC / Fan Remotes** | Temperature control, mode switching, fan speed |
| 📡 **Protocol Sender** | Manual NEC / Samsung32 / RC5 / Sony SIRC with hex address + command |
| 💾 **Saved Remotes** | Persist custom remotes, export/import as JSON |
| 🔧 **Custom Macros** | Build your own IR button sequences with delays |
| 📋 **Full Logging** | See every USB frame, protocol encode, and device response |

## Supported Devices

| Device | VID:PID | Interface | Buy |
|--------|---------|-----------|-----|
| **Tiqiaa** | `10C4:8468` | pyusb (bulk transfers) | [AliExpress](https://www.aliexpress.com/w/wholesale-tiqiaa-ir-blaster.html) ~$5 |
| **Ocrustar / ElkSmart** | `045C:02AA` + 10 others | pyusb (libusb) | [AliExpress](https://www.aliexpress.com/w/wholesale-usb-ir-blaster.html) ~$3 |
| Device | VID:PID | Interface | Buy |
| ----------------------- | ----------------------- | ---------------------- | ------------------------------------------------------------------------------- |
| **Tiqiaa** | `10C4:8468` | pyusb (bulk transfers) | [AliExpress](https://www.aliexpress.com/w/wholesale-tiqiaa-ir-blaster.html) ~$5 |
| **Ocrustar / ElkSmart** | `045C:02AA` + 10 others | pyusb (libusb) | [AliExpress](https://www.aliexpress.com/w/wholesale-usb-ir-blaster.html) ~$3 |

Both devices support **transmit and receive** (learn mode).

## Quick Start

```bash
# 1. Clone
git clone https://github.com/deadboy18/iRemote.git
cd iRemote

# 2. Install dependencies
pip install -r requirements.txt

# 3. Run
python server.py

# 4. Open any browser
# http://localhost:7890
```

### Windows Extra Step
Then open **http://localhost:7890** in any browser.

### Platform notes

For **Ocrustar**: install [Zadig](https://zadig.akeo.ie/), select your device (`SMART`), and replace the driver with **WinUSB**.
**macOS** — install libusb:

For **Tiqiaa**: also needs WinUSB via Zadig (despite being HID — it uses bulk transfers internally).
```bash
brew install libusb
```

### Linux
**Linux** — install libusb and grant USB access:

```bash
# Allow non-root USB access
sudo apt install libusb-1.0-0 # if not installed
sudo cp 99-irblaster.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules
```
Expand All @@ -99,16 +96,19 @@ SUBSYSTEM=="usb", ATTR{idVendor}=="10c4", ATTR{idProduct}=="8468", MODE="0666"
# Ocrustar
SUBSYSTEM=="usb", ATTR{idVendor}=="045c", MODE="0666"
```

</details>

**Windows** — install [Zadig](https://zadig.akeo.ie/), select your device, and replace the driver with **WinUSB** (required for both Ocrustar and Tiqiaa).

## Screenshots

| Remote | Hotel TV | IR Database |
|--------|----------|-------------|
| Remote | Hotel TV | IR Database |
| --------------------------------- | --------------------------------------- | --------------------------------------------- |
| ![Remote](screenshots/Remote.png) | ![Hotel TV](screenshots/Hotel%20Tv.png) | ![IR Database](screenshots/IR%20Database.png) |

| File Import | Learn Mode | Log |
|-------------|------------|-----|
| File Import | Learn Mode | Log |
| --------------------------------------------- | ------------------------------------------- | --------------------------- |
| ![File Import](screenshots/File%20Import.png) | ![Learn Mode](screenshots/Learn%20Mode.png) | ![Log](screenshots/Log.png) |

## How It Works
Expand Down Expand Up @@ -140,25 +140,25 @@ The UI is hosted on [GitHub Pages](https://deadboy18.github.io/iRemote/) so you

Built-in sequences for accessing hospitality TV service menus:

| Brand | Macros |
|-------|--------|
| **Samsung** | Hotel Menu, Hotel Menu (Alt), Smart Remote Unlock, Service Menu, Factory Service, PIN Reset |
| **LG** | Installer Menu (9876), Installer Menu (1105), Service Menu |
| **Philips** | Hotel Mode (BDS), Service Menu (SAM) |
| **Sony** | Service Menu |
| **Universal** | Power Off All (6 brands at once) |
| Brand | Macros |
| ------------- | ------------------------------------------------------------------------------------------- |
| **Samsung** | Hotel Menu, Hotel Menu (Alt), Smart Remote Unlock, Service Menu, Factory Service, PIN Reset |
| **LG** | Installer Menu (9876), Installer Menu (1105), Service Menu |
| **Philips** | Hotel Mode (BDS), Service Menu (SAM) |
| **Sony** | Service Menu |
| **Universal** | Power Off All (6 brands at once) |

You can also build and save custom macros with the macro builder.

## File Format Support

| Format | Source | Extension |
|--------|--------|-----------|
| Flipper Zero IR | [Flipper-IRDB](https://github.com/Lucaslhm/Flipper-IRDB) | `.ir` |
| IRDB CSV | [probonopd/irdb](https://github.com/probonopd/irdb) | `.csv` |
| iRemote Android JSON | iRemote Android app | `.json` |
| Pronto Hex | Various | `.txt` |
| Raw Pulse Data | Any | `.txt` |
| Format | Source | Extension |
| -------------------- | -------------------------------------------------------- | --------- |
| Flipper Zero IR | [Flipper-IRDB](https://github.com/Lucaslhm/Flipper-IRDB) | `.ir` |
| IRDB CSV | [probonopd/irdb](https://github.com/probonopd/irdb) | `.csv` |
| iRemote Android JSON | iRemote Android app | `.json` |
| Pronto Hex | Various | `.txt` |
| Raw Pulse Data | Any | `.txt` |

## Dependencies

Expand Down Expand Up @@ -196,4 +196,4 @@ MIT
<p align="center">
Made by <a href="https://github.com/deadboy18">deadboy18</a><br>
<sub>Protocol reverse engineering, USB drivers, and web interface</sub>
</p>
</p>
49 changes: 25 additions & 24 deletions server.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,26 @@
from flask import Flask, send_from_directory, send_file
from flask_socketio import SocketIO, emit
except ImportError:
print("ERROR: pip install flask flask-socketio"); sys.exit(1)
print("ERROR: Missing Python web packages. Run: pip install flask flask-socketio")
sys.exit(1)

try:
import usb.core, usb.util, usb.backend.libusb1
except ImportError:
print("ERROR: Missing Python USB packages. Run: pip install pyusb libusb-package")
sys.exit(1)

usb_available = False
usb_backend = None
try:
import usb.core, usb.util
try:
import libusb_package; usb_backend = libusb_package.get_libusb1_backend()
print("[OK] libusb-package backend")
except ImportError:
print("[OK] system libusb")
usb_available = True
import libusb_package; usb_backend = libusb_package.get_libusb1_backend()
except ImportError:
print("ERROR: pip install pyusb libusb-package")
pass
if usb_backend is None:
usb_backend = usb.backend.libusb1.get_backend()
if usb_backend is None:
print("ERROR: libusb not found. Run: brew install libusb (macOS) or apt install libusb-1.0-0 (Linux)")
sys.exit(1)
print("[OK] USB ready")

app = Flask(__name__, static_folder='.')
app.config['SECRET_KEY'] = 'iremote'
Expand Down Expand Up @@ -276,7 +282,6 @@ def parse_learn(raw):

# ═══════════════════════════════════════════════════
def list_usb_devices():
if not usb_available: return []
devices = []
try:
for dev in usb.core.find(find_all=True, backend=usb_backend):
Expand All @@ -300,7 +305,7 @@ def serve_static(filename): return send_from_directory('.', filename)
@socketio.on('connect')
def on_connect():
emit('status', {'connected': connected_device is not None, 'type': device_type,
'variant': ocrustar_variant, 'usb_available': usb_available})
'variant': ocrustar_variant})

@socketio.on('list_usb')
def on_list_usb():
Expand All @@ -312,7 +317,6 @@ def on_list_usb():
@socketio.on('connect_tiqiaa')
def on_connect_tiqiaa():
global connected_device, device_type, tiq_cmd_id, tiq_pkt_idx
if not usb_available: emit('error', {'msg': 'pyusb not installed'}); return
with device_lock:
dev = usb.core.find(idVendor=TIQ_VID, idProduct=TIQ_PID, backend=usb_backend)
if not dev:
Expand Down Expand Up @@ -369,7 +373,6 @@ def on_connect_tiqiaa():
def on_connect_ocrustar():
"""Mirrors elksmart_v15.py Dev.connect() + Dev.handshake() exactly."""
global connected_device, device_type, ocrustar_variant, ep_in, ep_out
if not usb_available: emit('error', {'msg': 'pyusb not installed'}); return
with device_lock:
dev = None; found_pid = None
for pid in OCRU_PIDS:
Expand Down Expand Up @@ -570,15 +573,13 @@ def on_learn(data=None):
print("="*52)
print(" iRemote Server — USB IR Blaster Backend")
print("="*52)
print(f" pyusb: {'OK' if usb_available else 'NOT INSTALLED'}")
if usb_available:
devs = list_usb_devices()
if devs:
print(f" {len(devs)} USB devices:")
for d in devs:
m = ''
if d['vid']==f'0x{OCRU_VID:04X}': m=' ← OCRUSTAR'
if d['vid']==f'0x{TIQ_VID:04X}' and d['pid']==f'0x{TIQ_PID:04X}': m=' ← TIQIAA'
print(f" {d['vid']}:{d['pid']} — {d['product']}{m}")
devs = list_usb_devices()
if devs:
print(f" {len(devs)} USB devices:")
for d in devs:
m = ''
if d['vid']==f'0x{OCRU_VID:04X}': m=' ← OCRUSTAR'
if d['vid']==f'0x{TIQ_VID:04X}' and d['pid']==f'0x{TIQ_PID:04X}': m=' ← TIQIAA'
print(f" {d['vid']}:{d['pid']} — {d['product']}{m}")
print(f"\n http://localhost:7890\n{'='*52}")
socketio.run(app, host='0.0.0.0', port=7890, debug=False, allow_unsafe_werkzeug=True)