265 lines
8.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# app/routes/api.py
import os
import time
import threading
import logging
import pygame
from flask import Blueprint, jsonify, request, current_app
import app.state as state
from app.bluetooth.manager import MouldKing, advertiser, tracer
from app.utils.helpers import load_default_sounds
logger = logging.getLogger(__name__)
api_bp = Blueprint('api', __name__)
@api_bp.route('/connect', methods=['POST'])
def api_connect():
if state.current_config is None:
return jsonify({"success": False, "message": "Keine Konfiguration geladen"}), 400
try:
hub_id = int(state.current_config.get('hub_id', 0))
hub_type = state.current_config.get('hub_type', '6channel') # '4channel' oder '6channel'
# Alte Verbindung sauber trennen
if state.current_device is not None:
try:
state.current_device.Disconnect()
except:
pass
if state.current_module is not None:
# ggf. Stop vor Disconnect
try:
state.current_device.Stop()
except:
pass
# MouldKing neu initialisieren
mk = MouldKing()
mk.SetAdvertiser(advertiser)
try:
mk.SetTracer(tracer)
except:
pass
# Modul holen
if hub_type.lower() in ['6channel', '6']:
state.current_module = mk.Module6_0()
else:
state.current_module = mk.Module4_0()
# Device auswählen (hub_id = 0,1,2 → Device0,1,2)
device_attr = f'Device{hub_id}'
if not hasattr(state.current_module, device_attr):
raise ValueError(f"Hub-ID {hub_id} nicht unterstützt (max 0-2)")
state.current_device = getattr(state.current_module, device_attr)
# Verbinden
state.current_device.Connect()
state.current_hub = mk # falls später noch gebraucht
logger.info(f"Verbunden: {hub_type} Hub-ID {hub_id}{device_attr}")
return jsonify({"success": True, "message": f"Hub {hub_id} ({hub_type}) verbunden"})
except Exception as e:
logger.exception("Connect-Fehler")
return jsonify({"success": False, "message": f"Verbindungsfehler: {str(e)}"}), 500
pass
@api_bp.route('/reconnect', methods=['POST'])
def api_reconnect():
if state.current_config is None:
return jsonify({"success": False, "message": "Keine Konfiguration geladen"}), 400
try:
# Alte Verbindung sauber beenden, falls nötig
if state.current_device is not None:
try:
state.current_device.Disconnect()
except:
pass
# Neu verbinden (kopierter Code aus api_connect)
hub_id = int(state.current_config.get('hub_id', 0))
hub_type = state.current_config.get('hub_type', '6channel')
mk = MouldKing()
mk.SetAdvertiser(advertiser)
try:
mk.SetTracer(tracer)
except:
pass
if hub_type.lower() in ['6channel', '6']:
state.current_module = mk.Module6_0()
else:
state.current_module = mk.Module4_0()
device_attr = f'Device{hub_id}'
if not hasattr(state.current_module, device_attr):
raise ValueError(f"Hub-ID {hub_id} nicht unterstützt")
state.current_device = getattr(state.current_module, device_attr)
state.current_device.Connect()
state.current_hub = mk
logger.info(f"Re-Connect erfolgreich: Hub {hub_id}")
return jsonify({"success": True, "message": "Verbindung wiederhergestellt"})
except Exception as e:
logger.exception("Re-Connect-Fehler")
return jsonify({"success": False, "message": str(e)}), 500
pass
@api_bp.route('/control', methods=['POST'])
def api_control():
if state.current_device is None:
return jsonify({"success": False, "message": "Nicht verbunden"}), 400
try:
data = request.get_json()
port = data['port'] # 'A', 'B', ...
value = float(data['value'])
# Port → channelId mappen (A=0, B=1, ...)
channel_map = {'A':0, 'B':1, 'C':2, 'D':3, 'E':4, 'F':5}
if isinstance(port, str):
port_upper = port.upper()
if port_upper in channel_map:
channel_id = channel_map[port_upper]
else:
raise ValueError(f"Ungültiger Port: {port}")
else:
channel_id = int(port)
# Config-spezifische Anpassungen (invert, negative_only, on/off-Werte)
ch_cfg = next((c for c in state.current_config['channels'] if c['port'].upper() == port.upper()), None)
if ch_cfg:
if ch_cfg.get('invert', False):
value = -value
if ch_cfg.get('negative_only', False) and value >= 0:
value = -abs(value)
if ch_cfg['type'] != 'motor':
value = ch_cfg.get('on_value', 1.0) if value != 0 else ch_cfg.get('off_value', 0.0)
# Echter Aufruf!
state.current_device.SetChannel(channel_id, value)
logger.info(f"SetChannel({channel_id}, {value:.2f}) → Port {port}")
return jsonify({"success": True})
except Exception as e:
logger.exception("Control-Fehler")
return jsonify({"success": False, "message": str(e)}), 500
pass
@api_bp.route('/stop_all', methods=['POST'])
def api_stop_all():
if state.current_device is None:
return jsonify({"success": False, "message": "Nicht verbunden"}), 400
try:
# Nur stoppen KEIN Disconnect!
state.current_device.Stop()
logger.info("Alle Kanäle gestoppt (Verbindung bleibt bestehen)")
return jsonify({"success": True, "message": "Alle Kanäle gestoppt"})
except Exception as e:
logger.exception("Stop-Fehler")
return jsonify({"success": False, "message": str(e)}), 500
pass
@api_bp.route('/status', methods=['GET'])
def api_status():
if state.current_device is not None:
return jsonify({
"connected": True,
"message": "Verbunden"
})
else:
return jsonify({
"connected": False,
"message": "Keine aktive Verbindung"
}), 200
pass
@api_bp.route('/play_sound', methods=['POST'])
def api_play_sound():
try:
data = request.get_json()
sound_id = data.get('sound_id')
if not sound_id:
return jsonify({"success": False, "message": "Kein sound_id angegeben"}), 400
# Sounds zusammenstellen: lokalspezifisch + globale Default-Sounds
config_sounds = state.current_config.get('sounds', []) if state.current_config else []
global_sounds = load_default_sounds(current_app.config['CONFIG_DIR'])
all_sounds = list(config_sounds) + list(global_sounds)
if not all_sounds:
return jsonify({"success": False, "message": "Keine Sounds konfiguriert"}), 400
sound_entry = next((s for s in all_sounds if s.get('id') == sound_id), None)
if not sound_entry:
return jsonify({"success": False, "message": f"Sound mit ID '{sound_id}' nicht gefunden"}), 404
sounds_dir = current_app.config['SOUNDS_DIR']
file_path = os.path.join(sounds_dir, sound_entry['file'])
if not os.path.exists(file_path):
logger.error(f"Sound-Datei nicht gefunden: {file_path}")
return jsonify({"success": False, "message": "Sound-Datei nicht gefunden"}), 404
# Abspielen (non-blocking, kein eigener Thread nötig)
try:
pygame.mixer.music.load(file_path)
pygame.mixer.music.play()
except Exception as e:
logger.exception(f"Fehler beim Abspielen von {file_path}")
return jsonify({"success": False, "message": str(e)}), 500
logger.info(f"Spiele Sound: {sound_entry['name']} ({sound_id})")
return jsonify({"success": True, "message": f"Spiele: {sound_entry['name']}"})
except Exception as e:
logger.exception("Play-Sound-Fehler")
return jsonify({"success": False, "message": str(e)}), 500
pass
@api_bp.route('/stop_sound', methods=['POST'])
def api_stop_sound():
try:
pygame.mixer.music.stop()
logger.info("Aktueller Sound gestoppt")
return jsonify({"success": True})
except Exception as e:
logger.error(f"Stop-Sound-Fehler: {e}")
return jsonify({"success": False, "message": str(e)}), 500
pass
@api_bp.route('/set_volume', methods=['POST'])
def api_set_volume():
try:
data = request.get_json()
vol = float(data.get('volume', 0.8))
vol = max(0.0, min(1.0, vol))
pygame.mixer.music.set_volume(vol)
logger.info(f"Volume auf {vol} gesetzt")
return jsonify({"success": True})
except Exception as e:
logger.error(f"Volume-Fehler: {e}")
return jsonify({"success": False, "message": str(e)}), 500
pass