322 lines
9.8 KiB
JavaScript
322 lines
9.8 KiB
JavaScript
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);
|
|
}); |