forked from TillRepo/IrSender
gui
This commit is contained in:
@@ -0,0 +1,464 @@
|
|||||||
|
# Raspberry Pi IR Web API
|
||||||
|
|
||||||
|
Kleine Python Web API für Raspberry Pi zur Ausführung definierter IR-Kommandos mittels `ir-ctl`.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- REST API mit API-Key
|
||||||
|
- Kleine Weboberfläche ohne Login
|
||||||
|
- JSON-basierte Konfiguration
|
||||||
|
- Definierte Kommandos
|
||||||
|
- Keine Shell Injection (`shell=False`)
|
||||||
|
- Timeout pro Kommando
|
||||||
|
- Logging
|
||||||
|
- Geeignet für openHAB HTTP Binding
|
||||||
|
- Kein Docker notwendig
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Architektur
|
||||||
|
|
||||||
|
```text
|
||||||
|
+-------------------+
|
||||||
|
| openHAB / Browser |
|
||||||
|
+-------------------+
|
||||||
|
|
|
||||||
|
v
|
||||||
|
+-------------------+
|
||||||
|
| Python Web API |
|
||||||
|
| script-api.py |
|
||||||
|
+-------------------+
|
||||||
|
|
|
||||||
|
v
|
||||||
|
+-------------------+
|
||||||
|
| ir-ctl |
|
||||||
|
| Linux IR Subsystem|
|
||||||
|
+-------------------+
|
||||||
|
|
|
||||||
|
v
|
||||||
|
+-------------------+
|
||||||
|
| IR Sender GPIO |
|
||||||
|
+-------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Voraussetzungen
|
||||||
|
|
||||||
|
## Pakete installieren
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y ir-ctl python3
|
||||||
|
```
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install -y v4l-utils
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Dateien
|
||||||
|
|
||||||
|
```text
|
||||||
|
/home/timm/IRSender/
|
||||||
|
├── script-api.py
|
||||||
|
├── commands.json
|
||||||
|
├── apiout.log
|
||||||
|
├── blue.txt
|
||||||
|
├── brighter.txt
|
||||||
|
├── darker.txt
|
||||||
|
├── defaultColor.txt
|
||||||
|
├── fade.txt
|
||||||
|
├── green.txt
|
||||||
|
├── LichtAus.txt
|
||||||
|
├── LichtEin.txt
|
||||||
|
├── Nachtlicht.txt
|
||||||
|
├── off.txt
|
||||||
|
├── on.txt
|
||||||
|
├── purple.txt
|
||||||
|
├── red.txt
|
||||||
|
├── smooth.txt
|
||||||
|
├── white.txt
|
||||||
|
└── yellow.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Starten
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/timm/IRSender
|
||||||
|
|
||||||
|
chmod +x script-api.py
|
||||||
|
|
||||||
|
nohup python3 -u script-api.py ./commands.json >> apiout.log 2>&1 &
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Stoppen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pkill -f script-api.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Logs anzeigen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tail -f /home/timm/IRSender/apiout.log
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Health Check
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://<rpi-ip>:8080/health
|
||||||
|
```
|
||||||
|
|
||||||
|
Antwort:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Weboberfläche
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://<rpi-ip>:8080/
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Weboberfläche benötigt keinen Login.
|
||||||
|
|
||||||
|
Nur Commands mit:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"web_enabled": true
|
||||||
|
```
|
||||||
|
|
||||||
|
werden angezeigt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# REST API
|
||||||
|
|
||||||
|
## Kommando ausführen
|
||||||
|
|
||||||
|
### Request
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "X-API-Key: change-this-global-api-key" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
http://<rpi-ip>:8080/run/lirc-tv-power \
|
||||||
|
-d '{"args":{}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"request_id": "f8d7f6a1",
|
||||||
|
"command_id": "lirc-tv-power",
|
||||||
|
"exit_code": 0,
|
||||||
|
"timed_out": false,
|
||||||
|
"duration_ms": 124,
|
||||||
|
"stdout": "",
|
||||||
|
"stderr": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Kommandos anzeigen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://<rpi-ip>:8080/commands
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# commands.json
|
||||||
|
|
||||||
|
## Beispiel
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"port": 8080,
|
||||||
|
|
||||||
|
"commands": {
|
||||||
|
"lirc-tv-power": {
|
||||||
|
"description": "Send TV power button",
|
||||||
|
"web_title": "TV Power",
|
||||||
|
"web_enabled": true,
|
||||||
|
"api_key": "change-this-global-api-key",
|
||||||
|
"timeout_seconds": 10,
|
||||||
|
|
||||||
|
"command": [
|
||||||
|
"ir-ctl",
|
||||||
|
"--send",
|
||||||
|
"/home/timm/IRSender/LichtEin.txt"
|
||||||
|
],
|
||||||
|
|
||||||
|
"args": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Neues Kommando hinzufügen
|
||||||
|
|
||||||
|
## Beispiel
|
||||||
|
|
||||||
|
```json
|
||||||
|
"my-command": {
|
||||||
|
"description": "Example command",
|
||||||
|
"web_title": "Example",
|
||||||
|
"web_enabled": true,
|
||||||
|
"api_key": "change-this-global-api-key",
|
||||||
|
"timeout_seconds": 10,
|
||||||
|
|
||||||
|
"command": [
|
||||||
|
"ir-ctl",
|
||||||
|
"--send",
|
||||||
|
"/home/timm/IRSender/example.txt"
|
||||||
|
],
|
||||||
|
|
||||||
|
"args": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# openHAB Integration
|
||||||
|
|
||||||
|
## HTTP Thing
|
||||||
|
|
||||||
|
### Thing
|
||||||
|
|
||||||
|
```text
|
||||||
|
UID: http:url:ir_sender
|
||||||
|
label: IR Sender
|
||||||
|
thingTypeUID: http:url
|
||||||
|
|
||||||
|
configuration:
|
||||||
|
baseURL: http://<rpi-ip>:8080
|
||||||
|
commandMethod: POST
|
||||||
|
contentType: application/json
|
||||||
|
|
||||||
|
headers:
|
||||||
|
- X-API-Key=change-this-global-api-key
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Channel
|
||||||
|
|
||||||
|
```text
|
||||||
|
channels:
|
||||||
|
- id: lirc_tv_power
|
||||||
|
channelTypeUID: http:switch
|
||||||
|
label: TV Power
|
||||||
|
|
||||||
|
configuration:
|
||||||
|
mode: WRITEONLY
|
||||||
|
commandExtension: /run/lirc-tv-power
|
||||||
|
|
||||||
|
onValue: {"args":{}}
|
||||||
|
offValue: {"args":{}}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Item
|
||||||
|
|
||||||
|
```text
|
||||||
|
Switch IR_TV_Power "TV Power"
|
||||||
|
{ channel="http:url:ir_sender:lirc_tv_power" }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Systemd Service
|
||||||
|
|
||||||
|
## Service Datei
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=Raspberry Pi IR Web API
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
|
||||||
|
User=timm
|
||||||
|
WorkingDirectory=/home/timm/IRSender
|
||||||
|
|
||||||
|
ExecStart=/usr/bin/python3 /home/timm/IRSender/script-api.py /home/timm/IRSender/commands.json
|
||||||
|
|
||||||
|
Restart=always
|
||||||
|
RestartSec=3
|
||||||
|
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
|
||||||
|
ProtectSystem=full
|
||||||
|
ProtectHome=false
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nano /etc/systemd/system/rpi-ir-api.service
|
||||||
|
```
|
||||||
|
|
||||||
|
Service einfügen.
|
||||||
|
|
||||||
|
Dann:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable --now rpi-ir-api
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Service Status
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl status rpi-ir-api
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Service Logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
journalctl -u rpi-ir-api -f
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Sicherheit
|
||||||
|
|
||||||
|
## Implementiert
|
||||||
|
|
||||||
|
- Keine Shell-Ausführung
|
||||||
|
- Keine freien Kommandos
|
||||||
|
- API-Key Prüfung
|
||||||
|
- JSON Validierung
|
||||||
|
- Timeout pro Kommando
|
||||||
|
- Regex Validierung
|
||||||
|
- HTML Escaping
|
||||||
|
- CSRF Token für Web UI
|
||||||
|
- Security Headers
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Wichtig
|
||||||
|
|
||||||
|
## Niemals erlauben:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"command": ["bash", "-c", "{user_input}"]
|
||||||
|
```
|
||||||
|
|
||||||
|
oder:
|
||||||
|
|
||||||
|
```python
|
||||||
|
shell=True
|
||||||
|
```
|
||||||
|
|
||||||
|
Sonst wird aus der Lichtsteuerung sehr schnell eine Fernwartung mit Spezialeffekten.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Troubleshooting
|
||||||
|
|
||||||
|
## 403 invalid api key
|
||||||
|
|
||||||
|
API Key falsch oder Header fehlt.
|
||||||
|
|
||||||
|
Prüfen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
-H "X-API-Key: change-this-global-api-key"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 400 invalid json
|
||||||
|
|
||||||
|
Body ist kein gültiges JSON.
|
||||||
|
|
||||||
|
Korrekt:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"args":{}}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 500 command failed
|
||||||
|
|
||||||
|
`ir-ctl` oder Datei prüfen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ir-ctl --send /home/timm/IRSender/LichtEin.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Permission denied
|
||||||
|
|
||||||
|
GPIO / IR Rechte prüfen.
|
||||||
|
|
||||||
|
Test:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ir-ctl --send /home/timm/IRSender/LichtEin.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Falls nur mit sudo funktioniert:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo usermod -aG video timm
|
||||||
|
```
|
||||||
|
|
||||||
|
Danach neu anmelden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Backup
|
||||||
|
|
||||||
|
Wichtig sichern:
|
||||||
|
|
||||||
|
```text
|
||||||
|
script-api.py
|
||||||
|
commands.json
|
||||||
|
*.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Lizenz
|
||||||
|
|
||||||
|
Internes Projekt.
|
||||||
+32
@@ -241,3 +241,35 @@ ConnectionResetError: [Errno 104] Connection reset by peer
|
|||||||
192.168.178.49 [16/May/2026 10:27:25] "GET / HTTP/1.1" 200 -
|
192.168.178.49 [16/May/2026 10:27:25] "GET / HTTP/1.1" 200 -
|
||||||
192.168.178.49 [16/May/2026 10:27:25] "GET /favicon.ico HTTP/1.1" 404 -
|
192.168.178.49 [16/May/2026 10:27:25] "GET /favicon.ico HTTP/1.1" 404 -
|
||||||
192.168.178.49 [16/May/2026 10:27:37] "GET / HTTP/1.1" 200 -
|
192.168.178.49 [16/May/2026 10:27:37] "GET / HTTP/1.1" 200 -
|
||||||
|
192.168.178.149 [16/May/2026 11:22:14] "GET / HTTP/1.1" 200 -
|
||||||
|
request_id=c69ae4c0-2071-48ea-9500-235ae9568273 command_id=fade command=['ir-ctl', '--send', '/home/timm/IRSender/fade.txt']
|
||||||
|
192.168.178.149 [16/May/2026 11:22:20] "POST /web/run/fade HTTP/1.1" 200 -
|
||||||
|
192.168.178.149 [16/May/2026 11:22:28] "GET / HTTP/1.1" 200 -
|
||||||
|
request_id=8a4e9c5b-b93d-47a3-8a3a-79e38c35a454 command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
|
||||||
|
192.168.178.149 [16/May/2026 11:22:31] "POST /web/run/default-color HTTP/1.1" 200 -
|
||||||
|
192.168.178.149 [16/May/2026 11:22:35] "GET / HTTP/1.1" 200 -
|
||||||
|
request_id=09e16188-3704-4265-a1c7-9772611ff0d5 command_id=licht-aus command=['ir-ctl', '--send', '/home/timm/IRSender/LichtAus.txt']
|
||||||
|
192.168.178.149 [16/May/2026 11:22:42] "POST /web/run/licht-aus HTTP/1.1" 200 -
|
||||||
|
192.168.178.149 [16/May/2026 11:22:48] "GET / HTTP/1.1" 200 -
|
||||||
|
request_id=65cb85ac-7aa2-486b-b4f9-af87e9e9c68e command_id=licht-ein command=['ir-ctl', '--send', '/home/timm/IRSender/LichtEin.txt']
|
||||||
|
192.168.178.149 [16/May/2026 11:22:54] "POST /web/run/licht-ein HTTP/1.1" 200 -
|
||||||
|
192.168.178.149 [16/May/2026 11:22:57] "GET / HTTP/1.1" 200 -
|
||||||
|
----------------------------------------
|
||||||
|
Exception occurred during processing of request from ('192.168.178.149', 61580)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "/usr/lib/python3.9/socketserver.py", line 650, in process_request_thread
|
||||||
|
self.finish_request(request, client_address)
|
||||||
|
File "/usr/lib/python3.9/socketserver.py", line 360, in finish_request
|
||||||
|
self.RequestHandlerClass(request, client_address, self)
|
||||||
|
File "/usr/lib/python3.9/socketserver.py", line 720, in __init__
|
||||||
|
self.handle()
|
||||||
|
File "/usr/lib/python3.9/http/server.py", line 434, in handle
|
||||||
|
self.handle_one_request()
|
||||||
|
File "/usr/lib/python3.9/http/server.py", line 402, in handle_one_request
|
||||||
|
self.raw_requestline = self.rfile.readline(65537)
|
||||||
|
File "/usr/lib/python3.9/socket.py", line 704, in readinto
|
||||||
|
return self._sock.recv_into(b)
|
||||||
|
ConnectionResetError: [Errno 104] Connection reset by peer
|
||||||
|
----------------------------------------
|
||||||
|
Listening on http://0.0.0.0:8080
|
||||||
|
Config: commands.json
|
||||||
|
|||||||
Reference in New Issue
Block a user