forked from TillRepo/IrSender
174 lines
5.3 KiB
Python
Executable File
174 lines
5.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
IR Capture Script
|
||
- Führt mode2 --device /dev/lirc1 aus
|
||
- Wandelt pulse/space/timeout in mark/space JSON um
|
||
- Speichert die JSON-Datei im gewünschten Format
|
||
- Fügt optional einen neuen Command in die Config ein
|
||
"""
|
||
|
||
import subprocess
|
||
import json
|
||
import sys
|
||
import re
|
||
from pathlib import Path
|
||
|
||
# === Anpassbare Pfade ===
|
||
DEVICE = "/dev/lirc1"
|
||
IR_SENDER_DIR = Path("/home/till/IrSender2")
|
||
CONFIG_FILE = IR_SENDER_DIR / "commands.json" # ggf. anpassen
|
||
IR_SEND_SCRIPT = IR_SENDER_DIR / "ir_send.py"
|
||
CARRIER = 38000
|
||
REPEAT = 1
|
||
|
||
def capture_mode2():
|
||
"""Startet mode2 und liest pulse/space/timeout-Daten."""
|
||
print(f"Starte mode2 auf {DEVICE} ...")
|
||
print("Drücke jetzt die Taste auf der Fernbedienung.")
|
||
print("Warte auf Signal (timeout = Ende). Mit Ctrl+C abbrechen.\n")
|
||
|
||
try:
|
||
proc = subprocess.Popen(
|
||
["mode2", "--device", DEVICE],
|
||
stdout=subprocess.PIPE,
|
||
stderr=subprocess.STDOUT,
|
||
text=True,
|
||
bufsize=1
|
||
)
|
||
except FileNotFoundError:
|
||
print("Fehler: 'mode2' nicht gefunden. Ist LIRC installiert?")
|
||
sys.exit(1)
|
||
except PermissionError:
|
||
print(f"Fehler: Keine Berechtigung für {DEVICE}. Mit sudo ausführen?")
|
||
sys.exit(1)
|
||
|
||
signals = []
|
||
|
||
try:
|
||
for line in proc.stdout:
|
||
line = line.strip()
|
||
if not line:
|
||
continue
|
||
|
||
m = re.match(r"^(pulse|space|timeout)\s+(\d+)$", line, re.IGNORECASE)
|
||
if not m:
|
||
continue
|
||
|
||
kind, duration = m.group(1).lower(), int(m.group(2))
|
||
|
||
if kind == "timeout":
|
||
signals.append({"type": "space", "duration": duration})
|
||
print(f" space {duration} (timeout → Signalende)")
|
||
print("\nTimeout erkannt – Signal aufgenommen.")
|
||
break
|
||
|
||
typ = "mark" if kind == "pulse" else "space"
|
||
signals.append({"type": typ, "duration": duration})
|
||
print(f" {typ:5} {duration}")
|
||
|
||
except KeyboardInterrupt:
|
||
print("\nAbbruch durch Benutzer – vorhandene Daten werden verwendet.")
|
||
finally:
|
||
proc.terminate()
|
||
try:
|
||
proc.wait(timeout=1)
|
||
except subprocess.TimeoutExpired:
|
||
proc.kill()
|
||
|
||
return signals
|
||
|
||
def save_json(signals, filename: Path):
|
||
"""
|
||
Speichert das Signal im gewünschten Format:
|
||
- Jedes { "type": "...", "duration": N } steht in EINER Zeile
|
||
- Kein Zeilenumbruch zwischen type und duration
|
||
"""
|
||
lines = []
|
||
lines.append('{')
|
||
lines.append(f' "carrier": {CARRIER},')
|
||
lines.append(f' "repeat": {REPEAT},')
|
||
lines.append(' "signal": [')
|
||
|
||
for i, s in enumerate(signals):
|
||
comma = "," if i < len(signals) - 1 else ""
|
||
# Wichtig: alles in einer Zeile
|
||
lines.append(f' {{ "type": "{s["type"]}", "duration": {s["duration"]} }}{comma}')
|
||
|
||
lines.append(' ]')
|
||
lines.append('}')
|
||
|
||
content = "\n".join(lines) + "\n"
|
||
|
||
with open(filename, "w", encoding="utf-8") as f:
|
||
f.write(content)
|
||
|
||
print(f"\nJSON gespeichert: {filename}")
|
||
|
||
def add_command_to_config(command_name: str, json_filename: str, web_title: str):
|
||
"""Fügt einen neuen Command in die Config-Datei ein."""
|
||
if not CONFIG_FILE.exists():
|
||
print(f"Warnung: Config-Datei {CONFIG_FILE} nicht gefunden – Command wird nicht hinzugefügt.")
|
||
return
|
||
|
||
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
||
config = json.load(f)
|
||
|
||
if "commands" not in config:
|
||
config["commands"] = {}
|
||
|
||
if command_name in config["commands"]:
|
||
print(f"Warnung: Command '{command_name}' existiert bereits – wird überschrieben.")
|
||
|
||
config["commands"][command_name] = {
|
||
"description": f"Send IR command {json_filename}",
|
||
"web_title": web_title,
|
||
"web_enabled": True,
|
||
"timeout_seconds": 10,
|
||
"command": [
|
||
"python3",
|
||
str(IR_SEND_SCRIPT),
|
||
str(IR_SENDER_DIR / json_filename)
|
||
]
|
||
}
|
||
|
||
# Backup anlegen
|
||
backup = CONFIG_FILE.with_suffix(".json.bak")
|
||
CONFIG_FILE.rename(backup)
|
||
print(f"Backup der Config erstellt: {backup}")
|
||
|
||
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
||
json.dump(config, f, indent=2, ensure_ascii=False)
|
||
|
||
print(f"Command '{command_name}' zur Config hinzugefügt.")
|
||
|
||
def main():
|
||
print("=== IR Capture → JSON + Config ===\n")
|
||
|
||
signals = capture_mode2()
|
||
|
||
if not signals:
|
||
print("Keine Daten empfangen. Abbruch.")
|
||
sys.exit(1)
|
||
|
||
print(f"\n{len(signals)} Einträge aufgenommen.")
|
||
|
||
while True:
|
||
name = input("Dateiname (ohne .json, z.B. hektagonYellow): ").strip()
|
||
if name and re.match(r"^[a-zA-Z0-9_\-]+$", name):
|
||
break
|
||
print("Nur Buchstaben, Zahlen, _ und - erlaubt.")
|
||
|
||
json_path = IR_SENDER_DIR / f"{name}.json"
|
||
save_json(signals, json_path)
|
||
|
||
add = input("\nSoll ein neuer Command in die Config eingetragen werden? [j/N]: ").strip().lower()
|
||
if add in ("j", "y", "ja", "yes"):
|
||
cmd_name = input(f"Command-Name (Standard: {name}): ").strip() or name
|
||
web_title = input(f"Web-Titel (Standard: {name}): ").strip() or name
|
||
add_command_to_config(cmd_name, f"{name}.json", web_title)
|
||
|
||
print("\nFertig!")
|
||
|
||
if __name__ == "__main__":
|
||
main()
|