# app.py import os import json import logging from flask import Flask, render_template, redirect, url_for, send_from_directory, request, jsonify # ── Bluetooth ──────────────────────────────────────────────────────────────── from mkconnect.mouldking.MouldKing import MouldKing from mkconnect.tracer.TracerConsole import TracerConsole from mkconnect.advertiser.AdvertiserBTSocket import AdvertiserBTSocket app = Flask(__name__) app.config['CONFIG_DIR'] = 'configs' os.makedirs(app.config['CONFIG_DIR'], exist_ok=True) # Logging logging.basicConfig( filename='app.log', level=logging.INFO, format='%(asctime)s | %(levelname)-8s | %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) logger = logging.getLogger(__name__) # Bluetooth-Komponenten (einmalig initialisieren) tracer = TracerConsole() advertiser = AdvertiserBTSocket() # ← ohne tracer, wie korrigiert # Globale Zustände (für Einzelbenutzer / Entwicklung – später Session/DB) current_config = None current_hub: MouldKing | None = None def load_configs(): configs = [] for filename in os.listdir(app.config['CONFIG_DIR']): if filename.lower().endswith('.json'): path = os.path.join(app.config['CONFIG_DIR'], filename) try: with open(path, 'r', encoding='utf-8') as f: data = json.load(f) data['filename'] = filename data.setdefault('name', filename.replace('.json', '').replace('_', ' ').title()) configs.append(data) except Exception as e: logger.error(f"Fehler beim Laden {filename}: {e}") return sorted(configs, key=lambda x: x.get('name', '')) @app.route('/') def index(): return render_template('index.html', configs=load_configs()) @app.route('/load_config/') def load_config(filename): global current_config path = os.path.join(app.config['CONFIG_DIR'], filename) if not os.path.exists(path): return "Datei nicht gefunden", 404 try: with open(path, 'r', encoding='utf-8') as f: current_config = json.load(f) current_config['filename'] = filename logger.info(f"Config geladen: {filename}") return redirect(url_for('control_page')) except Exception as e: logger.error(f"Config-Ladefehler {filename}: {e}") return "Fehler beim Laden", 500 @app.route('/control') def control_page(): if current_config is None: return redirect(url_for('index')) return render_template('control.html', config=current_config) @app.route('/admin') def admin(): """Admin-Übersicht: Configs anzeigen, bearbeiten, Logs etc.""" configs = load_configs() return render_template('admin.html', configs=configs) @app.route('/configs/') def serve_config_file(filename): return send_from_directory(app.config['CONFIG_DIR'], filename) # ── API ────────────────────────────────────────────────────────────────────── @app.route('/api/connect', methods=['POST']) def api_connect(): global current_hub if current_config is None: return jsonify({"success": False, "message": "Keine Konfiguration geladen"}), 400 try: hub_id = int(current_config.get('hub_id', 0)) # Falls schon verbunden → erstmal trennen (sauberer Neustart) if current_hub is not None: try: current_hub.disconnect() # oder .close() – je nach Methode except: pass current_hub = MouldKing( hub_id=hub_id, advertiser=advertiser, tracer=tracer # ← falls das doch geht; sonst entfernen ) current_hub.connect() # ← das ist der entscheidende Aufruf logger.info(f"Erfolgreich verbunden mit Hub-ID {hub_id}") return jsonify({"success": True, "message": f"Hub {hub_id} verbunden"}) except Exception as e: logger.exception("Connect-Fehler") return jsonify({ "success": False, "message": f"Verbindungsfehler: {str(e)}" }), 500 @app.route('/api/control', methods=['POST']) def api_control(): global current_hub if current_hub is None: return jsonify({"success": False, "message": "Nicht verbunden"}), 400 try: data = request.get_json() port = data['port'] value = float(data['value']) # Config-spezifische Anpassungen channel_config = next((c for c in current_config['channels'] if c['port'] == port), None) if channel_config: if channel_config.get('invert', False): value = -value if channel_config.get('negative_only', False) and value >= 0: value = -abs(value) if channel_config['type'] != 'motor': value = channel_config.get('on_value', 1.0) if value != 0 else channel_config.get('off_value', 0.0) # Hier kommt der echte Aufruf current_hub.set_motor(channel=port, power=value) # ← ← ← Kernaufruf logger.info(f"Steuerung: {port} → {value:.2f}") return jsonify({"success": True}) except Exception as e: logger.exception("Control-Fehler") return jsonify({"success": False, "message": str(e)}), 500 @app.route('/api/stop_all', methods=['POST']) def api_stop_all(): global current_hub if current_hub is None: return jsonify({"success": False, "message": "Nicht verbunden"}), 400 try: current_hub.stop_all() # ← oder alle Kanäle manuell auf 0 setzen logger.info("Alle Kanäle gestoppt") return jsonify({"success": True}) except Exception as e: logger.exception("Stop-Fehler") return jsonify({"success": False, "message": str(e)}), 500 if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True)