thermostat

This commit is contained in:
2025-11-05 18:26:36 +01:00
parent 478450794f
commit cb555a1f67
10 changed files with 1293 additions and 11 deletions

View File

@@ -0,0 +1,190 @@
# Device Simulator
Unified MQTT device simulator für das Home Automation System.
## Übersicht
Dieser Simulator ersetzt die einzelnen Simulatoren (`sim_test_lampe.py`, `sim_thermo.py`) und vereint alle Device-Typen in einer einzigen Anwendung.
## Unterstützte Geräte
### Lampen (3 Geräte)
- `test_lampe_1` - Mit Power und Brightness
- `test_lampe_2` - Mit Power und Brightness
- `test_lampe_3` - Mit Power und Brightness
**Features:**
- `power`: "on" oder "off"
- `brightness`: 0-100
### Thermostaten (1 Gerät)
- `test_thermo_1` - Vollständiger Thermostat mit Temperatur-Simulation
**Features:**
- `mode`: "off", "heat", oder "auto"
- `target`: Soll-Temperatur (5.0-30.0°C)
- `current`: Ist-Temperatur (wird simuliert)
- `battery`: Batteriestand (90%)
- `window_open`: Fensterstatus (false)
**Temperatur-Simulation:**
- Alle 5 Sekunden wird die Ist-Temperatur angepasst
- **HEAT/AUTO Mode**: Drift zu `target` (+0.2°C pro Intervall)
- **OFF Mode**: Drift zu Ambient-Temperatur 18°C (-0.2°C pro Intervall)
## MQTT-Konfiguration
- **Broker**: 172.16.2.16:1883 (konfigurierbar via ENV)
- **QoS**: 1 für alle Publishes
- **Retained**: Ja für alle State-Messages
- **Client ID**: device_simulator
### Topics
Für jedes Gerät:
- Subscribe: `vendor/{device_id}/set` (QoS 1)
- Publish: `vendor/{device_id}/state` (QoS 1, retained)
## Verwendung
### Starten
```bash
poetry run python tools/device_simulator.py
```
Oder im Hintergrund:
```bash
poetry run python tools/device_simulator.py > /tmp/simulator.log 2>&1 &
```
### Umgebungsvariablen
```bash
export MQTT_BROKER="172.16.2.16" # MQTT Broker Host
export MQTT_PORT="1883" # MQTT Broker Port
```
## Testen
Ein umfassendes Test-Skript ist verfügbar:
```bash
./tools/test_device_simulator.sh
```
Das Test-Skript:
1. Stoppt alle laufenden Services
2. Startet Abstraction Layer, API und Simulator
3. Testet alle Lampen-Operationen
4. Testet alle Thermostat-Operationen
5. Verifiziert MQTT State Messages
6. Zeigt Simulator-Logs
## Beispiele
### Lampe einschalten
```bash
curl -X POST http://localhost:8001/devices/test_lampe_1/set \
-H "Content-Type: application/json" \
-d '{"type":"light","payload":{"power":"on"}}'
```
### Helligkeit setzen
```bash
curl -X POST http://localhost:8001/devices/test_lampe_1/set \
-H "Content-Type: application/json" \
-d '{"type":"light","payload":{"brightness":75}}'
```
### Thermostat Mode setzen
```bash
curl -X POST http://localhost:8001/devices/test_thermo_1/set \
-H "Content-Type: application/json" \
-d '{"type":"thermostat","payload":{"mode":"heat","target":22.5}}'
```
### State abfragen via MQTT
```bash
# Lampe
mosquitto_sub -h 172.16.2.16 -t 'vendor/test_lampe_1/state' -C 1
# Thermostat
mosquitto_sub -h 172.16.2.16 -t 'vendor/test_thermo_1/state' -C 1
```
## Architektur
```
Browser/API
↓ POST /devices/{id}/set
API Server (Port 8001)
↓ MQTT: home/{type}/{id}/set
Abstraction Layer
↓ MQTT: vendor/{id}/set
Device Simulator
↓ MQTT: vendor/{id}/state (retained)
Abstraction Layer
↓ MQTT: home/{type}/{id}/state (retained)
↓ Redis Pub/Sub: ui:updates
UI / Dashboard
```
## Logs
Der Simulator loggt alle Aktivitäten:
- Startup und MQTT-Verbindung
- Empfangene SET-Commands
- State-Änderungen
- Temperature-Drift (Thermostaten)
- Publizierte State-Messages
Log-Level: INFO
## Troubleshooting
### Simulator startet nicht
```bash
# Prüfe ob Port bereits belegt
lsof -ti:1883
# Prüfe MQTT Broker
mosquitto_sub -h 172.16.2.16 -t '#' -C 1
```
### Keine State-Updates
```bash
# Prüfe Simulator-Log
tail -f /tmp/simulator.log
# Prüfe MQTT Topics
mosquitto_sub -h 172.16.2.16 -t 'vendor/#' -v
```
### API antwortet nicht
```bash
# Prüfe ob API läuft
curl http://localhost:8001/devices
# Prüfe API-Log
tail -f /tmp/api.log
```
## Integration
Der Simulator integriert sich nahtlos in das Home Automation System:
1. **Abstraction Layer** empfängt Commands und sendet sie an Simulator
2. **Simulator** reagiert und publiziert neuen State
3. **Abstraction Layer** empfängt State und publiziert zu Redis
4. **UI** empfängt Updates via SSE und aktualisiert Dashboard
Alle Komponenten arbeiten vollständig asynchron über MQTT.

285
tools/device_simulator.py Executable file
View File

@@ -0,0 +1,285 @@
#!/usr/bin/env python3
"""
Unified Device Simulator for Home Automation.
Simulates multiple device types:
- Lights (test_lampe_1, test_lampe_2, test_lampe_3)
- Thermostats (test_thermo_1)
Each device:
- Subscribes to vendor/{device_id}/set
- Maintains local state
- Publishes state changes to vendor/{device_id}/state (retained, QoS 1)
- Thermostats simulate temperature drift every 5 seconds
"""
import asyncio
import json
import logging
import os
import signal
import sys
from datetime import datetime
from typing import Dict, Any
from aiomqtt import Client, MqttError
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
# Configuration
BROKER_HOST = os.getenv("MQTT_BROKER", "172.16.2.16")
BROKER_PORT = int(os.getenv("MQTT_PORT", "1883"))
DRIFT_INTERVAL = 5 # seconds for thermostat temperature drift
# Device configurations
LIGHT_DEVICES = ["test_lampe_1", "test_lampe_2", "test_lampe_3"]
THERMOSTAT_DEVICES = ["test_thermo_1"]
class DeviceSimulator:
"""Unified simulator for lights and thermostats."""
def __init__(self):
# Light states
self.light_states: Dict[str, Dict[str, Any]] = {
"test_lampe_1": {"power": "off", "brightness": 50},
"test_lampe_2": {"power": "off", "brightness": 50},
"test_lampe_3": {"power": "off", "brightness": 50}
}
# Thermostat states
self.thermostat_states: Dict[str, Dict[str, Any]] = {
"test_thermo_1": {
"mode": "auto",
"target": 21.0,
"current": 20.5,
"battery": 90,
"window_open": False
}
}
self.client = None
self.running = True
self.drift_task = None
async def publish_state(self, device_id: str, device_type: str):
"""Publish device state to MQTT (retained, QoS 1)."""
if not self.client:
return
if device_type == "light":
state = self.light_states.get(device_id)
elif device_type == "thermostat":
state = self.thermostat_states.get(device_id)
else:
logger.warning(f"Unknown device type: {device_type}")
return
if not state:
logger.warning(f"Unknown device: {device_id}")
return
state_topic = f"vendor/{device_id}/state"
payload = json.dumps(state)
await self.client.publish(
state_topic,
payload=payload,
qos=1,
retain=True
)
logger.info(f"[{device_id}] Published state: {payload}")
async def handle_light_set(self, device_id: str, payload: dict):
"""Handle SET command for light device."""
if device_id not in self.light_states:
logger.warning(f"Unknown light device: {device_id}")
return
state = self.light_states[device_id]
updated = False
if "power" in payload:
old_power = state["power"]
state["power"] = payload["power"]
if old_power != state["power"]:
updated = True
logger.info(f"[{device_id}] Power: {old_power} -> {state['power']}")
if "brightness" in payload:
old_brightness = state["brightness"]
state["brightness"] = int(payload["brightness"])
if old_brightness != state["brightness"]:
updated = True
logger.info(f"[{device_id}] Brightness: {old_brightness} -> {state['brightness']}")
if updated:
await self.publish_state(device_id, "light")
async def handle_thermostat_set(self, device_id: str, payload: dict):
"""Handle SET command for thermostat device."""
if device_id not in self.thermostat_states:
logger.warning(f"Unknown thermostat device: {device_id}")
return
state = self.thermostat_states[device_id]
updated = False
if "mode" in payload:
new_mode = payload["mode"]
if new_mode in ["off", "heat", "auto"]:
old_mode = state["mode"]
state["mode"] = new_mode
if old_mode != new_mode:
updated = True
logger.info(f"[{device_id}] Mode: {old_mode} -> {new_mode}")
else:
logger.warning(f"[{device_id}] Invalid mode: {new_mode}")
if "target" in payload:
try:
new_target = float(payload["target"])
if 5.0 <= new_target <= 30.0:
old_target = state["target"]
state["target"] = new_target
if old_target != new_target:
updated = True
logger.info(f"[{device_id}] Target: {old_target}°C -> {new_target}°C")
else:
logger.warning(f"[{device_id}] Target out of range: {new_target}")
except (ValueError, TypeError):
logger.warning(f"[{device_id}] Invalid target value: {payload['target']}")
if updated:
await self.publish_state(device_id, "thermostat")
def apply_temperature_drift(self, device_id: str):
"""
Simulate temperature drift for thermostat.
Max change: ±0.2°C per interval.
"""
if device_id not in self.thermostat_states:
return
state = self.thermostat_states[device_id]
if state["mode"] == "off":
# Drift towards ambient (18°C)
ambient = 18.0
diff = ambient - state["current"]
else:
# Drift towards target
diff = state["target"] - state["current"]
# Apply max ±0.2°C drift
if abs(diff) < 0.1:
state["current"] = round(state["current"] + diff, 1)
elif diff > 0:
state["current"] = round(state["current"] + 0.2, 1)
else:
state["current"] = round(state["current"] - 0.2, 1)
logger.info(f"[{device_id}] Temperature drift: current={state['current']}°C (target={state['target']}°C, mode={state['mode']})")
async def thermostat_drift_loop(self):
"""Background loop for thermostat temperature drift."""
while self.running:
await asyncio.sleep(DRIFT_INTERVAL)
for device_id in THERMOSTAT_DEVICES:
self.apply_temperature_drift(device_id)
await self.publish_state(device_id, "thermostat")
async def handle_message(self, message):
"""Handle incoming MQTT message."""
try:
# Extract device_id from topic (vendor/{device_id}/set)
topic_parts = message.topic.value.split('/')
if len(topic_parts) != 3 or topic_parts[0] != "vendor" or topic_parts[2] != "set":
logger.warning(f"Unexpected topic format: {message.topic}")
return
device_id = topic_parts[1]
payload = json.loads(message.payload.decode())
logger.info(f"[{device_id}] Received SET: {payload}")
# Determine device type and handle accordingly
if device_id in self.light_states:
await self.handle_light_set(device_id, payload)
elif device_id in self.thermostat_states:
await self.handle_thermostat_set(device_id, payload)
else:
logger.warning(f"Unknown device: {device_id}")
except json.JSONDecodeError as e:
logger.error(f"Invalid JSON: {e}")
except Exception as e:
logger.error(f"Error handling message: {e}")
async def run(self):
"""Main simulator loop."""
try:
async with Client(
hostname=BROKER_HOST,
port=BROKER_PORT,
identifier="device_simulator"
) as client:
self.client = client
logger.info(f"✅ Connected to MQTT broker {BROKER_HOST}:{BROKER_PORT}")
# Publish initial states
for device_id in LIGHT_DEVICES:
await self.publish_state(device_id, "light")
logger.info(f"💡 Light simulator started: {device_id}")
for device_id in THERMOSTAT_DEVICES:
await self.publish_state(device_id, "thermostat")
logger.info(f"🌡️ Thermostat simulator started: {device_id}")
# Subscribe to all SET topics
all_devices = LIGHT_DEVICES + THERMOSTAT_DEVICES
for device_id in all_devices:
set_topic = f"vendor/{device_id}/set"
await client.subscribe(set_topic, qos=1)
logger.info(f"👂 Subscribed to {set_topic}")
# Start thermostat drift loop
self.drift_task = asyncio.create_task(self.thermostat_drift_loop())
# Listen for messages
async for message in client.messages:
await self.handle_message(message)
# Cancel drift loop on disconnect
if self.drift_task:
self.drift_task.cancel()
except MqttError as e:
logger.error(f"❌ MQTT Error: {e}")
except KeyboardInterrupt:
logger.info("⚠️ Interrupted by user")
finally:
self.running = False
if self.drift_task:
self.drift_task.cancel()
logger.info("👋 Simulator stopped")
async def main():
"""Entry point."""
simulator = DeviceSimulator()
await simulator.run()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\n👋 Simulator terminated")
sys.exit(0)

205
tools/sim_thermo.py Executable file
View File

@@ -0,0 +1,205 @@
#!/usr/bin/env python3
"""
MQTT Simulator für test_thermo_1 Thermostat.
Funktionalität:
- Subscribt auf vendor/test_thermo_1/set
- Hält internen Zustand (mode, target, current, battery, window_open)
- Reagiert auf SET-Commands (mode/target)
- Simuliert Temperatur-Drift zu target (alle 5s, max ±0.2°C)
- Publiziert vendor/test_thermo_1/state (retained, QoS 1)
Usage:
poetry run python tools/sim_thermo.py
Environment Variables:
MQTT_BROKER: MQTT broker hostname (default: 172.16.2.16)
MQTT_PORT: MQTT broker port (default: 1883)
Test Commands:
# Start simulator
poetry run python tools/sim_thermo.py &
# Test SET command
curl -X POST http://localhost:8001/devices/test_thermo_1/set \
-H "Content-Type: application/json" \
-d '{"type":"thermostat","payload":{"mode":"heat","target":22.5}}'
# Monitor state
mosquitto_sub -h 172.16.2.16 -t 'vendor/test_thermo_1/state' -v
"""
import asyncio
import json
import os
import sys
from datetime import datetime
from aiomqtt import Client, MqttError
# Configuration
BROKER_HOST = os.getenv("MQTT_BROKER", "172.16.2.16")
BROKER_PORT = int(os.getenv("MQTT_PORT", "1883"))
DEVICE_ID = "test_thermo_1"
SET_TOPIC = f"vendor/{DEVICE_ID}/set"
STATE_TOPIC = f"vendor/{DEVICE_ID}/state"
DRIFT_INTERVAL = 5 # seconds
class ThermostatSimulator:
"""Simulates a thermostat device with temperature regulation."""
def __init__(self):
self.state = {
"mode": "auto",
"target": 21.0,
"current": 20.5,
"battery": 90,
"window_open": False
}
self.client = None
self.running = True
def log(self, msg: str):
"""Log with timestamp."""
timestamp = datetime.now().strftime("%H:%M:%S")
print(f"[{timestamp}] {msg}", flush=True)
async def publish_state(self):
"""Publish current state to MQTT (retained, QoS 1)."""
if not self.client:
return
payload = json.dumps(self.state)
await self.client.publish(
STATE_TOPIC,
payload=payload,
qos=1,
retain=True
)
self.log(f"📤 Published state: {payload}")
def apply_temperature_drift(self):
"""
Simulate temperature drift towards target.
Max change: ±0.2°C per interval.
"""
if self.state["mode"] == "off":
# In OFF mode, drift slowly towards ambient (assume 18°C)
ambient = 18.0
diff = ambient - self.state["current"]
else:
# In HEAT/AUTO mode, drift towards target
diff = self.state["target"] - self.state["current"]
# Apply max ±0.2°C drift
if abs(diff) < 0.1:
# Close enough, small adjustment
self.state["current"] = round(self.state["current"] + diff, 1)
elif diff > 0:
self.state["current"] = round(self.state["current"] + 0.2, 1)
else:
self.state["current"] = round(self.state["current"] - 0.2, 1)
self.log(f"🌡️ Temperature drift: current={self.state['current']}°C (target={self.state['target']}°C)")
async def handle_set_command(self, payload: dict):
"""
Handle SET command from MQTT.
Payload can contain: mode, target
"""
self.log(f"📥 Received SET: {payload}")
changed = False
if "mode" in payload:
new_mode = payload["mode"]
if new_mode in ["off", "heat", "auto"]:
self.state["mode"] = new_mode
changed = True
self.log(f" Mode changed to: {new_mode}")
else:
self.log(f" ⚠️ Invalid mode: {new_mode}")
if "target" in payload:
try:
new_target = float(payload["target"])
if 5.0 <= new_target <= 30.0:
self.state["target"] = new_target
changed = True
self.log(f" Target changed to: {new_target}°C")
else:
self.log(f" ⚠️ Target out of range: {new_target}")
except (ValueError, TypeError):
self.log(f" ⚠️ Invalid target value: {payload['target']}")
if changed:
await self.publish_state()
async def drift_loop(self):
"""Background loop for temperature drift simulation."""
while self.running:
await asyncio.sleep(DRIFT_INTERVAL)
self.apply_temperature_drift()
await self.publish_state()
async def mqtt_loop(self):
"""Main MQTT connection and message handling loop."""
try:
async with Client(
hostname=BROKER_HOST,
port=BROKER_PORT,
identifier=f"sim_{DEVICE_ID}"
) as client:
self.client = client
self.log(f"✅ Connected to MQTT broker {BROKER_HOST}:{BROKER_PORT}")
# Publish initial state
await self.publish_state()
self.log(f"📡 Thermo sim started for {DEVICE_ID}")
# Subscribe to SET topic
await client.subscribe(SET_TOPIC, qos=1)
self.log(f"👂 Subscribed to {SET_TOPIC}")
# Start drift loop in background
drift_task = asyncio.create_task(self.drift_loop())
# Listen for messages
async for message in client.messages:
try:
payload = json.loads(message.payload.decode())
await self.handle_set_command(payload)
except json.JSONDecodeError as e:
self.log(f"❌ Invalid JSON: {e}")
except Exception as e:
self.log(f"❌ Error handling message: {e}")
# Cancel drift loop on disconnect
drift_task.cancel()
except MqttError as e:
self.log(f"❌ MQTT Error: {e}")
except KeyboardInterrupt:
self.log("⚠️ Interrupted by user")
finally:
self.running = False
self.log("👋 Simulator stopped")
async def run(self):
"""Run the simulator."""
await self.mqtt_loop()
async def main():
"""Entry point."""
simulator = ThermostatSimulator()
await simulator.run()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\n👋 Simulator terminated")
sys.exit(0)

154
tools/test_device_simulator.sh Executable file
View File

@@ -0,0 +1,154 @@
#!/bin/bash
# Test script for device_simulator.py
set -e # Exit on error
echo "=== Device Simulator Test Suite ==="
echo ""
# 1. Stop all running services
echo "1. Stoppe alle laufenden Services..."
pkill -f "device_simulator" 2>/dev/null || true
pkill -f "uvicorn apps" 2>/dev/null || true
pkill -f "apps.abstraction" 2>/dev/null || true
sleep 2
echo " ✓ Services gestoppt"
echo ""
# 2. Start services
echo "2. Starte Services..."
poetry run python -m apps.abstraction.main > /tmp/abstraction.log 2>&1 &
ABSTRACTION_PID=$!
echo " Abstraction Layer gestartet (PID: $ABSTRACTION_PID)"
sleep 2
poetry run uvicorn apps.api.main:app --host 0.0.0.0 --port 8001 > /tmp/api.log 2>&1 &
API_PID=$!
echo " API Server gestartet (PID: $API_PID)"
sleep 2
poetry run python tools/device_simulator.py > /tmp/simulator.log 2>&1 &
SIM_PID=$!
echo " Device Simulator gestartet (PID: $SIM_PID)"
sleep 2
echo " ✓ Alle Services laufen"
echo ""
# 3. Test API reachability
echo "3. Teste API Erreichbarkeit..."
if timeout 3 curl -s http://localhost:8001/devices > /dev/null; then
echo " ✓ API antwortet"
else
echo " ✗ API antwortet nicht!"
echo " API Log:"
tail -10 /tmp/api.log
exit 1
fi
echo ""
# 4. Test Light Operations
echo "4. Teste Lampen-Operationen..."
# 4.1 Power On
echo " 4.1 Lampe einschalten (test_lampe_1)..."
RESPONSE=$(timeout 3 curl -s -X POST http://localhost:8001/devices/test_lampe_1/set \
-H "Content-Type: application/json" \
-d '{"type":"light","payload":{"power":"on"}}')
echo " Response: $RESPONSE"
sleep 1
# 4.2 Check state via MQTT
echo " 4.2 Prüfe State via MQTT..."
STATE=$(timeout 2 mosquitto_sub -h 172.16.2.16 -t 'vendor/test_lampe_1/state' -C 1)
echo " State: $STATE"
if echo "$STATE" | grep -q '"power": "on"'; then
echo " ✓ Power ist ON"
else
echo " ✗ Power nicht ON!"
fi
# 4.3 Brightness
echo " 4.3 Helligkeit setzen (75%)..."
RESPONSE=$(timeout 3 curl -s -X POST http://localhost:8001/devices/test_lampe_1/set \
-H "Content-Type: application/json" \
-d '{"type":"light","payload":{"brightness":75}}')
echo " Response: $RESPONSE"
sleep 1
STATE=$(timeout 2 mosquitto_sub -h 172.16.2.16 -t 'vendor/test_lampe_1/state' -C 1)
echo " State: $STATE"
if echo "$STATE" | grep -q '"brightness": 75'; then
echo " ✓ Brightness ist 75"
else
echo " ✗ Brightness nicht 75!"
fi
echo ""
# 5. Test Thermostat Operations
echo "5. Teste Thermostat-Operationen..."
# 5.1 Set mode and target
echo " 5.1 Setze Mode HEAT und Target 22.5°C..."
RESPONSE=$(timeout 3 curl -s -X POST http://localhost:8001/devices/test_thermo_1/set \
-H "Content-Type: application/json" \
-d '{"type":"thermostat","payload":{"mode":"heat","target":22.5}}')
echo " Response: $RESPONSE"
sleep 1
STATE=$(timeout 2 mosquitto_sub -h 172.16.2.16 -t 'vendor/test_thermo_1/state' -C 1)
echo " State: $STATE"
if echo "$STATE" | grep -q '"mode": "heat"' && echo "$STATE" | grep -q '"target": 22.5'; then
echo " ✓ Mode ist HEAT, Target ist 22.5"
else
echo " ✗ Mode oder Target nicht korrekt!"
fi
# 5.2 Wait for temperature drift
echo " 5.2 Warte 6 Sekunden auf Temperature Drift..."
sleep 6
STATE=$(timeout 2 mosquitto_sub -h 172.16.2.16 -t 'vendor/test_thermo_1/state' -C 1)
echo " State: $STATE"
CURRENT=$(echo "$STATE" | grep -o '"current": [0-9.]*' | grep -o '[0-9.]*$')
echo " Current Temperature: ${CURRENT}°C"
if [ -n "$CURRENT" ]; then
echo " ✓ Temperature drift funktioniert"
else
echo " ✗ Temperature drift nicht sichtbar!"
fi
# 5.3 Set mode OFF
echo " 5.3 Setze Mode OFF..."
RESPONSE=$(timeout 3 curl -s -X POST http://localhost:8001/devices/test_thermo_1/set \
-H "Content-Type: application/json" \
-d '{"type":"thermostat","payload":{"mode":"off","target":22.5}}')
echo " Response: $RESPONSE"
sleep 1
STATE=$(timeout 2 mosquitto_sub -h 172.16.2.16 -t 'vendor/test_thermo_1/state' -C 1)
if echo "$STATE" | grep -q '"mode": "off"'; then
echo " ✓ Mode ist OFF"
else
echo " ✗ Mode nicht OFF!"
fi
echo ""
# 6. Check simulator log
echo "6. Simulator Log (letzte 20 Zeilen)..."
tail -20 /tmp/simulator.log
echo ""
# 7. Summary
echo "=== Test Summary ==="
echo "✓ Alle Tests abgeschlossen"
echo ""
echo "Laufende Prozesse:"
echo " Abstraction: PID $ABSTRACTION_PID"
echo " API: PID $API_PID"
echo " Simulator: PID $SIM_PID"
echo ""
echo "Logs verfügbar in:"
echo " /tmp/abstraction.log"
echo " /tmp/api.log"
echo " /tmp/simulator.log"