updated control functions
This commit is contained in:
parent
55f85f8684
commit
af4355a83f
54
app.py
54
app.py
@ -267,13 +267,61 @@ def api_stop_all():
|
|||||||
return jsonify({"success": False, "message": "Nicht verbunden"}), 400
|
return jsonify({"success": False, "message": "Nicht verbunden"}), 400
|
||||||
|
|
||||||
try:
|
try:
|
||||||
current_device.Stop() # ← Stoppt alle Kanäle des Devices
|
# Nur stoppen – KEIN Disconnect!
|
||||||
logger.info("Alle Kanäle gestoppt")
|
current_device.Stop()
|
||||||
return jsonify({"success": True})
|
logger.info("Alle Kanäle gestoppt (Verbindung bleibt bestehen)")
|
||||||
|
return jsonify({"success": True, "message": "Alle Kanäle gestoppt"})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception("Stop-Fehler")
|
logger.exception("Stop-Fehler")
|
||||||
return jsonify({"success": False, "message": str(e)}), 500
|
return jsonify({"success": False, "message": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/reconnect', methods=['POST'])
|
||||||
|
def api_reconnect():
|
||||||
|
global current_device, current_module, current_hub
|
||||||
|
if current_config is None:
|
||||||
|
return jsonify({"success": False, "message": "Keine Konfiguration geladen"}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Alte Verbindung sauber beenden, falls nötig
|
||||||
|
if current_device is not None:
|
||||||
|
try:
|
||||||
|
current_device.Disconnect()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Neu verbinden (kopierter Code aus api_connect)
|
||||||
|
hub_id = int(current_config.get('hub_id', 0))
|
||||||
|
hub_type = current_config.get('hub_type', '6channel')
|
||||||
|
|
||||||
|
mk = MouldKing()
|
||||||
|
mk.SetAdvertiser(advertiser)
|
||||||
|
try:
|
||||||
|
mk.SetTracer(tracer)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if hub_type.lower() in ['6channel', '6']:
|
||||||
|
current_module = mk.Module6_0()
|
||||||
|
else:
|
||||||
|
current_module = mk.Module4_0()
|
||||||
|
|
||||||
|
device_attr = f'Device{hub_id}'
|
||||||
|
if not hasattr(current_module, device_attr):
|
||||||
|
raise ValueError(f"Hub-ID {hub_id} nicht unterstützt")
|
||||||
|
|
||||||
|
current_device = getattr(current_module, device_attr)
|
||||||
|
current_device.Connect()
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||||
BIN
static/.DS_Store
vendored
Normal file
BIN
static/.DS_Store
vendored
Normal file
Binary file not shown.
171
static/js/app.js
171
static/js/app.js
@ -1,17 +1,26 @@
|
|||||||
// app.js – gemeinsames JavaScript
|
// static/js/app.js – MK Control Frontend
|
||||||
console.log("MK Control JS geladen");
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
console.log('MK Control Frontend geladen');
|
console.log('MK Control Frontend geladen');
|
||||||
|
|
||||||
|
// ── Elemente ───────────────────────────────────────────────────────────────
|
||||||
const connectBtn = document.getElementById('connect-btn');
|
const connectBtn = document.getElementById('connect-btn');
|
||||||
const connectSection = document.getElementById('connect-section');
|
const connectSection = document.getElementById('connect-section');
|
||||||
const controlSection = document.getElementById('control-section');
|
const controlSection = document.getElementById('control-section');
|
||||||
const channelsContainer = document.getElementById('channels-container');
|
const channelsContainer = document.getElementById('channels-container');
|
||||||
const stopAllBtn = document.getElementById('stop-all-btn');
|
const stopAllBtn = document.getElementById('stop-all-btn');
|
||||||
|
const reconnectSection = document.getElementById('reconnect-section');
|
||||||
|
const reconnectBtn = document.getElementById('reconnect-btn');
|
||||||
|
|
||||||
if (!connectBtn) return; // Sicherstellen, dass wir auf der richtigen Seite sind
|
if (!connectBtn) {
|
||||||
|
console.warn('Nicht auf der Steuerseite – Connect-Button nicht gefunden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Config aus Template (von Flask eingebettet) ───────────────────────────
|
||||||
|
const config = window.config || {}; // Sicherstellen, dass config existiert
|
||||||
|
|
||||||
|
// ── Connect-Button ────────────────────────────────────────────────────────
|
||||||
connectBtn.addEventListener('click', async () => {
|
connectBtn.addEventListener('click', async () => {
|
||||||
connectBtn.disabled = true;
|
connectBtn.disabled = true;
|
||||||
connectBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span> Verbinde...';
|
connectBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span> Verbinde...';
|
||||||
@ -27,39 +36,89 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
connectSection.style.display = 'none';
|
connectSection.style.display = 'none';
|
||||||
controlSection.style.display = 'block';
|
controlSection.style.display = 'block';
|
||||||
|
reconnectSection.style.display = 'none';
|
||||||
renderChannels();
|
renderChannels();
|
||||||
alert('Verbunden! (Simulation)');
|
console.log('Verbindung erfolgreich');
|
||||||
} else {
|
} else {
|
||||||
alert('Verbindung fehlgeschlagen: ' + (result.message || 'Unbekannter Fehler'));
|
alert('Verbindung fehlgeschlagen:\n' + (result.message || 'Unbekannter Fehler'));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error('Connect-Fehler:', err);
|
||||||
alert('Fehler bei der Verbindung: ' + err.message);
|
alert('Netzwerk- oder Verbindungsfehler: ' + err.message);
|
||||||
} finally {
|
} finally {
|
||||||
connectBtn.disabled = false;
|
connectBtn.disabled = false;
|
||||||
connectBtn.innerHTML = '<i class="bi bi-bluetooth me-2"></i> Mit Hub verbinden';
|
connectBtn.innerHTML = '<i class="bi bi-bluetooth me-2"></i> Mit Hub verbinden';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Alle stoppen ──────────────────────────────────────────────────────────
|
||||||
|
if (stopAllBtn) {
|
||||||
stopAllBtn.addEventListener('click', async () => {
|
stopAllBtn.addEventListener('click', async () => {
|
||||||
if (!confirm('Wirklich alle Kanäle stoppen?')) return;
|
if (!confirm('Wirklich ALLE Kanäle stoppen?')) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/stop_all', { method: 'POST' });
|
const res = await fetch('/api/stop_all', { method: 'POST' });
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
alert('Alle Kanäle gestoppt');
|
// Alle Motor-Slider zurücksetzen
|
||||||
}
|
document.querySelectorAll('.motor-slider').forEach(slider => {
|
||||||
} catch (err) {
|
slider.value = 0;
|
||||||
alert('Fehler beim Stoppen');
|
const display = slider.parentElement.querySelector('.value-display');
|
||||||
}
|
if (display) display.textContent = '0 %';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('Alle Kanäle gestoppt');
|
||||||
|
alert('Alle Kanäle gestoppt');
|
||||||
|
} else {
|
||||||
|
alert('Fehler beim Stoppen:\n' + (data.message || 'Unbekannt'));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Stop-all Fehler:', err);
|
||||||
|
alert('Netzwerkfehler beim Stoppen aller Kanäle');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Erneut verbinden ──────────────────────────────────────────────────────
|
||||||
|
if (reconnectBtn) {
|
||||||
|
reconnectBtn.addEventListener('click', async () => {
|
||||||
|
reconnectBtn.disabled = true;
|
||||||
|
reconnectBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span> Verbinde...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/reconnect', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
reconnectSection.style.display = 'none';
|
||||||
|
controlSection.style.display = 'block';
|
||||||
|
alert('Verbindung wiederhergestellt!');
|
||||||
|
// Optional: Kanäle neu rendern oder Status aktualisieren
|
||||||
|
} else {
|
||||||
|
alert('Erneute Verbindung fehlgeschlagen:\n' + (result.message || 'Unbekannt'));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Reconnect-Fehler:', err);
|
||||||
|
alert('Netzwerkfehler beim erneuten Verbinden');
|
||||||
|
} finally {
|
||||||
|
reconnectBtn.disabled = false;
|
||||||
|
reconnectBtn.innerHTML = '<i class="bi bi-arrow-repeat me-2"></i> Erneut verbinden';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Kanäle dynamisch rendern ──────────────────────────────────────────────
|
||||||
function renderChannels() {
|
function renderChannels() {
|
||||||
|
if (!channelsContainer) return;
|
||||||
channelsContainer.innerHTML = '';
|
channelsContainer.innerHTML = '';
|
||||||
|
|
||||||
if (!config.channels || config.channels.length === 0) {
|
if (!config.channels || config.channels.length === 0) {
|
||||||
channelsContainer.innerHTML = '<p class="text-center text-muted py-4">Keine Kanäle in der Konfiguration definiert.</p>';
|
channelsContainer.innerHTML = '<p class="text-center text-muted py-5">Keine Kanäle in der Konfiguration definiert.</p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,18 +130,26 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
if (channel.type === 'motor') {
|
if (channel.type === 'motor') {
|
||||||
controlHTML = `
|
controlHTML = `
|
||||||
<label class="form-label">${channel.name} (${channel.port})</label>
|
<label class="form-label fw-bold">${channel.name} (${channel.port})</label>
|
||||||
<input type="range" class="form-range motor-slider"
|
<input type="range" class="form-range motor-slider"
|
||||||
min="-100" max="100" value="0" step="5"
|
min="-100" max="100" value="0" step="5"
|
||||||
data-port="${channel.port}">
|
data-port="${channel.port}">
|
||||||
<div class="text-center mt-1">
|
<div class="d-flex justify-content-between mt-1 small">
|
||||||
<span class="value-display">0 %</span>
|
<span>-100 %</span>
|
||||||
|
<span class="value-display fw-bold">0 %</span>
|
||||||
|
<span>+100 %</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-center mt-2">
|
||||||
|
<button class="btn btn-sm btn-outline-danger stop-channel-btn"
|
||||||
|
data-port="${channel.port}">
|
||||||
|
<i class="bi bi-stop-fill me-1"></i> Stop
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
// Licht, Sound, Fogger → Toggle-Button
|
// Licht, Sound, Fogger etc. → Toggle
|
||||||
controlHTML = `
|
controlHTML = `
|
||||||
<label class="form-label">${channel.name} (${channel.port})</label>
|
<label class="form-label fw-bold">${channel.name} (${channel.port})</label>
|
||||||
<button class="btn btn-outline-secondary w-100 toggle-btn"
|
<button class="btn btn-outline-secondary w-100 toggle-btn"
|
||||||
data-port="${channel.port}" data-state="off">
|
data-port="${channel.port}" data-state="off">
|
||||||
AUS
|
AUS
|
||||||
@ -91,7 +158,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
col.innerHTML = `
|
col.innerHTML = `
|
||||||
<div class="card h-100">
|
<div class="card h-100 shadow-sm">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
${controlHTML}
|
${controlHTML}
|
||||||
</div>
|
</div>
|
||||||
@ -101,17 +168,49 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
channelsContainer.appendChild(col);
|
channelsContainer.appendChild(col);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Event-Listener für Slider
|
// ── Event-Listener Slider (Motoren) ─────────────────────────────────────
|
||||||
document.querySelectorAll('.motor-slider').forEach(slider => {
|
document.querySelectorAll('.motor-slider').forEach(slider => {
|
||||||
const display = slider.parentElement.querySelector('.value-display');
|
const display = slider.parentElement.querySelector('.value-display');
|
||||||
slider.addEventListener('input', async () => {
|
slider.addEventListener('input', async () => {
|
||||||
const value = parseInt(slider.value) / 100;
|
const value = parseInt(slider.value) / 100;
|
||||||
display.textContent = `${slider.value} %`;
|
if (display) display.textContent = `${slider.value} %`;
|
||||||
|
|
||||||
|
try {
|
||||||
await sendControl(slider.dataset.port, value);
|
await sendControl(slider.dataset.port, value);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Slider-Steuerfehler:', err);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Event-Listener für Toggle-Buttons
|
// ── Event-Listener Einzel-Stop pro Kanal ────────────────────────────────
|
||||||
|
document.querySelectorAll('.stop-channel-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
const port = btn.dataset.port;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendControl(port, 0);
|
||||||
|
|
||||||
|
// Slider zurücksetzen
|
||||||
|
const slider = document.querySelector(`input[data-port="${port}"]`);
|
||||||
|
if (slider) {
|
||||||
|
slider.value = 0;
|
||||||
|
const display = slider.parentElement.querySelector('.value-display');
|
||||||
|
if (display) display.textContent = '0 %';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feedback
|
||||||
|
btn.classList.add('btn-danger');
|
||||||
|
setTimeout(() => btn.classList.remove('btn-danger'), 600);
|
||||||
|
|
||||||
|
console.log(`Einzelstop: ${port}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Einzelstop-Fehler:', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Event-Listener Toggle-Buttons (Licht/Sound/Fogger) ──────────────────
|
||||||
document.querySelectorAll('.toggle-btn').forEach(btn => {
|
document.querySelectorAll('.toggle-btn').forEach(btn => {
|
||||||
btn.addEventListener('click', async () => {
|
btn.addEventListener('click', async () => {
|
||||||
const current = btn.dataset.state;
|
const current = btn.dataset.state;
|
||||||
@ -121,11 +220,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
btn.classList.toggle('btn-success', newState === 'on');
|
btn.classList.toggle('btn-success', newState === 'on');
|
||||||
btn.classList.toggle('btn-outline-secondary', newState === 'off');
|
btn.classList.toggle('btn-outline-secondary', newState === 'off');
|
||||||
|
|
||||||
|
try {
|
||||||
await sendControl(btn.dataset.port, newState === 'on' ? 1 : 0);
|
await sendControl(btn.dataset.port, newState === 'on' ? 1 : 0);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Toggle-Fehler:', err);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Hilfsfunktion: Steuerbefehl senden ────────────────────────────────────
|
||||||
async function sendControl(port, value) {
|
async function sendControl(port, value) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/control', {
|
const res = await fetch('/api/control', {
|
||||||
@ -133,9 +237,26 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ port, value })
|
body: JSON.stringify({ port, value })
|
||||||
});
|
});
|
||||||
// Kann später Feedback geben
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const errData = await res.json();
|
||||||
|
throw new Error(errData.message || 'Steuerbefehl fehlgeschlagen');
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Steuerbefehl fehlgeschlagen:', err);
|
console.error('sendControl Fehler:', err);
|
||||||
|
// Optional: Hier könnte man showReconnect() aufrufen, wenn gewünscht
|
||||||
|
// showReconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Hilfsfunktion: Reconnect-Bereich anzeigen ─────────────────────────────
|
||||||
|
function showReconnect() {
|
||||||
|
if (reconnectSection && controlSection) {
|
||||||
|
controlSection.style.display = 'none';
|
||||||
|
reconnectSection.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: Bei Seitenlade-Fehler oder späterem Verbindungsverlust aufrufen
|
||||||
|
// showReconnect(); // ← zum Testen manuell einblenden
|
||||||
});
|
});
|
||||||
@ -54,6 +54,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Reconnect-Bereich – wird nur angezeigt, wenn Verbindung verloren -->
|
||||||
|
<div class="text-center mt-5" id="reconnect-section" style="display: none;">
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<strong>Verbindung unterbrochen</strong><br>
|
||||||
|
Der Hub reagiert nicht mehr. Bitte erneut verbinden.
|
||||||
|
</div>
|
||||||
|
<button id="reconnect-btn" class="btn btn-warning btn-lg px-5 py-3">
|
||||||
|
<i class="bi bi-arrow-repeat me-2"></i> Erneut verbinden
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user