diff --git a/app.py b/app.py index 935cca4..a389720 100644 --- a/app.py +++ b/app.py @@ -47,35 +47,69 @@ def index(): return render_template('index.html', configs=configs) +current_config = None +current_filename = None + @app.route('/load_config/') def load_config(filename): - # Später: Config in Session speichern oder global (für Entwicklung erstmal redirect) - # Hier nur redirect zur Steuerseite – echte Logik kommt später + global current_config, current_filename path = os.path.join(app.config['CONFIG_DIR'], filename) if not os.path.exists(path): return "Konfigurationsdatei nicht gefunden", 404 - # Für den Moment nur redirect – später speichern wir current_config - return redirect(url_for('control_page', filename=filename)) + 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}") + 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 -@app.route('/control/') -def control_page(filename): - # Später: Config laden und übergeben - # Aktuell nur Dummy-Template - return render_template('control.html', filename=filename) +@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('/configs/') -def serve_config_file(filename): - """Bilder und andere Dateien aus configs/ ausliefern""" - return send_from_directory(app.config['CONFIG_DIR'], filename) +# Neue API-Route (Platzhalter – später echte Bluetooth-Steuerung) +@app.route('/api/connect', methods=['POST']) +def api_connect(): + # Hier kommt später der echte Code mit MouldKing + # Aktuell nur simuliert + 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') + }) +@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)"}) + + +@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"}) + @app.route('/admin') def admin(): return render_template('admin.html') if __name__ == '__main__': - app.run(host='0.0.0.0', port=5050, debug=True) + app.run(host='0.0.0.0', port=5055, debug=True) diff --git a/static/js/app.js b/static/js/app.js index 5e68713..c69c93b 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -3,5 +3,139 @@ console.log("MK Control JS geladen"); document.addEventListener('DOMContentLoaded', () => { console.log('MK Control Frontend geladen'); - // Hier kommen später alle Fetch-Logiken, Slider-Handler etc. rein + + const connectBtn = document.getElementById('connect-btn'); + const connectSection = document.getElementById('connect-section'); + const controlSection = document.getElementById('control-section'); + const channelsContainer = document.getElementById('channels-container'); + const stopAllBtn = document.getElementById('stop-all-btn'); + + if (!connectBtn) return; // Sicherstellen, dass wir auf der richtigen Seite sind + + connectBtn.addEventListener('click', async () => { + connectBtn.disabled = true; + connectBtn.innerHTML = ' Verbinde...'; + + try { + const response = await fetch('/api/connect', { + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }); + + const result = await response.json(); + + if (result.success) { + connectSection.style.display = 'none'; + controlSection.style.display = 'block'; + renderChannels(); + alert('Verbunden! (Simulation)'); + } else { + alert('Verbindung fehlgeschlagen: ' + (result.message || 'Unbekannter Fehler')); + } + } catch (err) { + console.error(err); + alert('Fehler bei der Verbindung: ' + err.message); + } finally { + connectBtn.disabled = false; + connectBtn.innerHTML = ' Mit Hub verbinden'; + } + }); + + stopAllBtn.addEventListener('click', async () => { + if (!confirm('Wirklich alle Kanäle stoppen?')) return; + + try { + const res = await fetch('/api/stop_all', { method: 'POST' }); + const data = await res.json(); + if (data.success) { + alert('Alle Kanäle gestoppt'); + } + } catch (err) { + alert('Fehler beim Stoppen'); + } + }); + + function renderChannels() { + channelsContainer.innerHTML = ''; + + if (!config.channels || config.channels.length === 0) { + channelsContainer.innerHTML = '

Keine Kanäle in der Konfiguration definiert.

'; + return; + } + + config.channels.forEach(channel => { + const col = document.createElement('div'); + col.className = 'col-md-6 mb-4'; + + let controlHTML = ''; + + if (channel.type === 'motor') { + controlHTML = ` + + +
+ 0 % +
+ `; + } else { + // Licht, Sound, Fogger → Toggle-Button + controlHTML = ` + + + `; + } + + col.innerHTML = ` +
+
+ ${controlHTML} +
+
+ `; + + channelsContainer.appendChild(col); + }); + + // Event-Listener für Slider + document.querySelectorAll('.motor-slider').forEach(slider => { + const display = slider.parentElement.querySelector('.value-display'); + slider.addEventListener('input', async () => { + const value = parseInt(slider.value) / 100; + display.textContent = `${slider.value} %`; + await sendControl(slider.dataset.port, value); + }); + }); + + // Event-Listener für Toggle-Buttons + document.querySelectorAll('.toggle-btn').forEach(btn => { + btn.addEventListener('click', async () => { + const current = btn.dataset.state; + const newState = current === 'off' ? 'on' : 'off'; + btn.dataset.state = newState; + btn.textContent = newState === 'on' ? 'EIN' : 'AUS'; + btn.classList.toggle('btn-success', newState === 'on'); + btn.classList.toggle('btn-outline-secondary', newState === 'off'); + + await sendControl(btn.dataset.port, newState === 'on' ? 1 : 0); + }); + }); + } + + async function sendControl(port, value) { + try { + const res = await fetch('/api/control', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ port, value }) + }); + // Kann später Feedback geben + } catch (err) { + console.error('Steuerbefehl fehlgeschlagen:', err); + } + } }); \ No newline at end of file diff --git a/templates/control.html b/templates/control.html index e69de29..c23abb5 100644 --- a/templates/control.html +++ b/templates/control.html @@ -0,0 +1,67 @@ +{% extends "base.html" %} + +{% block title %}{{ config.name }} – Steuerung{% endblock %} + +{% block content %} + +
+ +
+
+

{{ config.name }}

+

+ Hub-ID: {{ config.hub_id | default('unbekannt') }} • + Typ: {{ config.hub_type | default('unbekannt') }} • + Datei: {{ config.filename }} +

+
+
+ {% if config.image %} + {{ config.name }} + {% else %} +
+ +
+ {% endif %} +
+
+ + +
+ +

Stelle sicher, dass der Hub eingeschaltet ist und im Bluetooth-Modus ausgewählt wurde.

+
+ + + + +
+ +{% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file