class Visualization { constructor() { this.canvas = document.getElementById('simCanvas'); this.ctx = this.canvas.getContext('2d'); this.width = 700; this.height = 700; this.center = { x: this.width / 2, y: this.height / 2 }; this.radius = 260; this.tagCount = 36; // Aktuelle Positionen this.telescopeAngle = 0; this.domeAngle = 0; this.slotWidth = 20; this.telescopeData = null; this.domeData = null; // Lokale Teleskop-Interpolation für flüssige Bewegung this.localTelescopeAngle = 0; this.telescopeSpeed = 6; // 6°/sec = 1 U/min this.lastUpdateTime = Date.now(); // Lokale Kuppel-Interpolation für flüssige Bewegung this.localDomeAngle = 0; this.domeSpeed = 720; // 720°/sec = 2 U/sec wie in der Kuppel-Logik this.lastDomeUpdateTime = Date.now(); this.socket = io('http://localhost:3005'); this.setupSocketListeners(); this.startAnimation(); this.updateStatus(); } setupSocketListeners() { this.socket.on('connect', () => { console.log('Visualization connected to server'); this.updateSystemStatus('WebSocket: Connected'); }); this.socket.on('disconnect', () => { this.updateSystemStatus('WebSocket: Disconnected'); }); this.socket.on('telescope-position', (data) => { console.log(`[Visualization] Received telescope position: ${data.angle.toFixed(1)}°`); this.telescopeData = data; this.telescopeAngle = data.angle; // Weiche Synchronisation: nur korrigieren wenn Differenz groß ist const diff = Math.abs(data.angle - this.localTelescopeAngle); const adjustedDiff = Math.min(diff, 360 - diff); // Berücksichtige Überlauf bei 360° if (adjustedDiff > 3) { // Große Abweichung (z.B. durch Button-Klick) -> sofort korrigieren console.log(`[Visualization] Large sync correction: ${adjustedDiff.toFixed(1)}°`); this.localTelescopeAngle = data.angle; } else { // Kleine Abweichung -> langsam angleichen um Blitzen zu vermeiden const correctionFactor = 0.1; // 10% Korrektur pro Update let targetAngle = data.angle; // Handle 360° wrap-around if (Math.abs(targetAngle - this.localTelescopeAngle) > 180) { if (targetAngle > this.localTelescopeAngle) { targetAngle -= 360; } else { targetAngle += 360; } } this.localTelescopeAngle += (targetAngle - this.localTelescopeAngle) * correctionFactor; this.localTelescopeAngle = (this.localTelescopeAngle + 360) % 360; } this.lastUpdateTime = Date.now(); this.updateTelescopeStatus(); }); this.socket.on('dome-position', (data) => { this.domeData = data; this.domeAngle = data.angle; this.slotWidth = data.slotWidth; console.log(`[Visualization] Received dome position: ${data.angle.toFixed(1)}°`); // SOFORTIGE Synchronisation für die Kuppel (keine weiche Angleichung) // Die Kuppel soll genau das anzeigen, was sie wirklich ist this.localDomeAngle = data.angle; this.lastDomeUpdateTime = Date.now(); this.updateDomeStatus(); }); } startAnimation() { // 60 FPS Animation setInterval(() => { this.draw(); }, 1000 / 60); } draw() { // Lokale Teleskop-Interpolation aktualisieren (für flüssige Bewegung) const now = Date.now(); const deltaTime = (now - this.lastUpdateTime) / 1000; // in Sekunden this.localTelescopeAngle = (this.localTelescopeAngle + this.telescopeSpeed * deltaTime) % 360; this.lastUpdateTime = now; // KUPPEL: Keine lokale Interpolation! Zeige nur echte Position // Die Kuppel bewegt sich nur diskret, wenn das Teleskop den Spalt verlässt // Canvas löschen this.ctx.fillStyle = '#2a2a2a'; this.ctx.fillRect(0, 0, this.width, this.height); // NFC-Tags zeichnen this.drawTags(); // Kuppel-Spalt zeichnen (verwendet echte Position, keine Interpolation) this.drawDomeSlot(); // Teleskop zeichnen (verwendet lokale Interpolation) this.drawTelescope(); // Zentrum this.drawCenter(); // Debug-Info aktualisieren this.updateDebugInfo(); } drawTags() { this.ctx.fillStyle = '#666'; for (let i = 0; i < this.tagCount; i++) { const angle = 2 * Math.PI * i / this.tagCount; // Positive für Uhrzeigersinn const x = this.center.x + Math.cos(angle) * this.radius; const y = this.center.y + Math.sin(angle) * this.radius; this.ctx.beginPath(); this.ctx.arc(x, y, 4, 0, 2 * Math.PI); this.ctx.fill(); } } drawDomeSlot() { const midAngle = this.localDomeAngle * Math.PI / 180; // Verwendet sofortigen Sync ohne Interpolation const halfSlot = this.slotWidth * Math.PI / 360; this.ctx.strokeStyle = '#4a9eff'; this.ctx.lineWidth = 12; this.ctx.beginPath(); this.ctx.arc( this.center.x, this.center.y, this.radius, midAngle - halfSlot, midAngle + halfSlot ); this.ctx.stroke(); } drawTelescope() { const angle = this.localTelescopeAngle * Math.PI / 180; // Verwende lokale Interpolation const length = this.radius - 60; const endX = this.center.x + Math.cos(angle) * length; const endY = this.center.y + Math.sin(angle) * length; // Linie this.ctx.strokeStyle = '#ff4444'; this.ctx.lineWidth = 6; this.ctx.beginPath(); this.ctx.moveTo(this.center.x, this.center.y); this.ctx.lineTo(endX, endY); this.ctx.stroke(); // Endpunkt this.ctx.fillStyle = '#ff4444'; this.ctx.beginPath(); this.ctx.arc(endX, endY, 8, 0, 2 * Math.PI); this.ctx.fill(); } drawCenter() { this.ctx.fillStyle = '#333'; this.ctx.beginPath(); this.ctx.arc(this.center.x, this.center.y, 6, 0, 2 * Math.PI); this.ctx.fill(); } updateTelescopeStatus() { if (this.telescopeData) { const statusEl = document.getElementById('telescope-status'); statusEl.textContent = `Position: ${this.telescopeAngle.toFixed(1)}° | Speed: 1 U/min | Running`; } } updateDomeStatus() { if (this.domeData) { const statusEl = document.getElementById('dome-status'); const diff = this.domeData.diff || 0; const isTracking = Math.abs(diff) > this.slotWidth / 2; statusEl.textContent = `Position: ${this.domeAngle.toFixed(1)}° | ${isTracking ? 'Tracking' : 'Standby'} | Diff: ${diff.toFixed(1)}°`; } } updateSystemStatus(message) { const statusEl = document.getElementById('system-status'); statusEl.textContent = message; } updateDebugInfo() { const debugEl = document.getElementById('debug'); const now = new Date().toLocaleTimeString(); let info = `=== Observatory Debug Info [${now}] ===\n`; if (this.telescopeData) { info += `🔭 TELESCOPE:\n`; info += ` Position: ${this.telescopeAngle.toFixed(1)}°\n`; info += ` Last Update: ${new Date(this.telescopeData.timestamp).toLocaleTimeString()}\n\n`; } if (this.domeData) { info += `🏠 DOME:\n`; info += ` Position: ${this.domeAngle.toFixed(1)}°\n`; info += ` Slot Width: ${this.slotWidth}°\n`; info += ` Difference: ${(this.domeData.diff || 0).toFixed(1)}°\n`; info += ` Status: ${Math.abs(this.domeData.diff || 0) > this.slotWidth / 2 ? 'TRACKING' : 'STANDBY'}\n`; info += ` Last Update: ${new Date(this.domeData.timestamp).toLocaleTimeString()}\n\n`; } info += `📊 SYSTEM:\n`; info += ` FPS: 60\n`; info += ` Tags: ${this.tagCount}\n`; info += ` WebSocket: ${this.socket.connected ? 'Connected' : 'Disconnected'}`; debugEl.textContent = info; } updateStatus() { // Status alle 100ms aktualisieren setInterval(() => { this.updateDebugInfo(); }, 100); } } // Telescope Control Funktionen für Buttons function adjustTelescope(degrees) { if (window.telescope && window.telescope.socket) { window.telescope.socket.emit('telescope-control', { command: 'adjust', degrees: degrees }); } } // Keyboard Controls document.addEventListener('keydown', (event) => { switch(event.code) { case 'ArrowLeft': adjustTelescope(-15); break; case 'ArrowRight': adjustTelescope(15); break; case 'KeyQ': adjustTelescope(-45); break; case 'KeyE': adjustTelescope(45); break; } }); // Global flag to prevent multiple initializations window.observatoryInitialized = window.observatoryInitialized || false; // Initialize visualization when page loads document.addEventListener('DOMContentLoaded', () => { // Prevent multiple initializations if (window.observatoryInitialized) { console.log('=== Observatory System Already Running - Skipping ==='); return; } console.log('=== Observatory System Starting ==='); // Stoppe und entferne eventuelle bereits laufende Instanzen if (window.telescope) { console.log('Stopping existing telescope...'); window.telescope.stop(); delete window.telescope; } if (window.dome) { console.log('Stopping existing dome...'); window.dome.stop(); delete window.dome; } if (window.visualization) { console.log('Stopping existing visualization...'); delete window.visualization; } // Warte kurz, dann erstelle neue Instanzen setTimeout(() => { console.log('Creating telescope...'); window.telescope = new Telescope('http://localhost:3005'); console.log('Creating dome...'); window.dome = new Dome('http://localhost:3005'); console.log('Creating visualization...'); window.visualization = new Visualization(); // Markiere als initialisiert window.observatoryInitialized = true; console.log('=== Observatory System Ready ==='); }, 100); });