From d3ba2c270d3d1b8ab262fa68481a933fa0bc6976 Mon Sep 17 00:00:00 2001 From: oberon Date: Tue, 10 Feb 2026 21:39:54 +0100 Subject: [PATCH] added bluetooth connect and test-hub script --- app.py | 143 ++++++++++++++++++++++++++++++---------------- other/test-hub.py | 9 +++ 2 files changed, 103 insertions(+), 49 deletions(-) create mode 100644 other/test-hub.py diff --git a/app.py b/app.py index c1d1692..f14fa9b 100644 --- a/app.py +++ b/app.py @@ -4,9 +4,13 @@ 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__) -# Konfiguration app.config['CONFIG_DIR'] = 'configs' os.makedirs(app.config['CONFIG_DIR'], exist_ok=True) @@ -17,15 +21,18 @@ logging.basicConfig( 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(): - """Liest alle .json Dateien aus dem configs-Ordner""" configs = [] - if not os.path.exists(app.config['CONFIG_DIR']): - return configs - for filename in os.listdir(app.config['CONFIG_DIR']): if filename.lower().endswith('.json'): path = os.path.join(app.config['CONFIG_DIR'], filename) @@ -33,91 +40,129 @@ def load_configs(): with open(path, 'r', encoding='utf-8') as f: data = json.load(f) data['filename'] = filename - # Optional: name fallback, falls nicht vorhanden data.setdefault('name', filename.replace('.json', '').replace('_', ' ').title()) configs.append(data) except Exception as e: - logger.error(f"Fehler beim Laden von {filename}: {e}") + logger.error(f"Fehler beim Laden {filename}: {e}") return sorted(configs, key=lambda x: x.get('name', '')) @app.route('/') def index(): - configs = load_configs() - return render_template('index.html', configs=configs) + return render_template('index.html', configs=load_configs()) -@app.route('/configs/') -def serve_config_file(filename): - """Liefert Bilder und Dateien aus dem configs-Ordner aus""" - try: - return send_from_directory(app.config['CONFIG_DIR'], filename) - except Exception as e: - logger.error(f"Fehler beim Ausliefern von {filename}: {e}") - return "Datei nicht gefunden", 404 - -current_config = None -current_filename = None @app.route('/load_config/') def load_config(filename): - global current_config, current_filename + global current_config path = os.path.join(app.config['CONFIG_DIR'], filename) if not os.path.exists(path): - return "Konfigurationsdatei nicht gefunden", 404 - + return "Datei nicht gefunden", 404 try: with open(path, 'r', encoding='utf-8') as f: current_config = json.load(f) current_config['filename'] = filename - current_filename = filename - logger.info(f"Konfiguration geladen: {filename}") + logger.info(f"Config geladen: {filename}") return redirect(url_for('control_page')) except Exception as e: - logger.error(f"Fehler beim Laden der Config {filename}: {e}") - return "Fehler beim Laden der Konfiguration", 500 + 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) -# Neue API-Route (Platzhalter – später echte Bluetooth-Steuerung) +@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(): - # Hier kommt später der echte Code mit MouldKing - # Aktuell nur simuliert + global current_hub if current_config is None: return jsonify({"success": False, "message": "Keine Konfiguration geladen"}), 400 - - logger.info(f"Simulierter Connect zu Hub {current_config.get('hub_id')}") - return jsonify({ - "success": True, - "message": "Verbunden (Simulation)", - "hub_id": current_config.get('hub_id') - }) + + 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(): - # Platzhalter für spätere echte Steuerung - data = request.get_json() - logger.info(f"Steuerbefehl empfangen: {data}") - return jsonify({"success": True, "message": "Befehl gesendet (Simulation)"}) + 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(): - logger.info("Alle stoppen (Simulation)") - return jsonify({"success": True, "message": "Alle Kanäle gestoppt"}) + global current_hub + if current_hub is None: + return jsonify({"success": False, "message": "Nicht verbunden"}), 400 -@app.route('/admin') -def admin(): - return render_template('admin.html') + 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=5055, debug=True) + app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file diff --git a/other/test-hub.py b/other/test-hub.py new file mode 100644 index 0000000..304d774 --- /dev/null +++ b/other/test-hub.py @@ -0,0 +1,9 @@ +# test_hub.py +from mkconnect.mouldking.MouldKing import MouldKing +from mkconnect.tracer.TracerConsole import TracerConsole +from mkconnect.advertiser.AdvertiserBTSocket import AdvertiserBTSocket + +tracer = TracerConsole() +adv = AdvertiserBTSocket() +hub = MouldKing(hub_id=0, advertiser=adv, tracer=tracer) +print(dir(hub)) # ← zeigt alle Methoden \ No newline at end of file