class Dome { constructor(socketUrl) { // Verhindere mehrfache Instanzen if (window.activeDomeInstance) { console.warn('Dome instance already exists! Stopping old one...'); window.activeDomeInstance.stop(); } this.angle = 0; // Grad (Mitte des Spalts) - immer auf NFC-Tag-Position! this.slotWidth = 20; // Spaltbreite in Grad this.domeSpeed = 0.5; // Grad pro Frame (für sehr smoothe animation) this.telescopeAngle = 0; // NFC-Tag System (wie in Original) this.tagCount = 36; // 36 Tags = alle 10° this.tagStep = 360 / this.tagCount; // 10° pro Tag this.currentTagIndex = 0; // Aktueller Tag (0-35) this.targetTagIndex = 0; // Ziel-Tag während Bewegung // Setze initiale Position auf Tag 0 this.angle = this.tagToAngle(this.currentTagIndex); // 5° (Mitte von Tag 0) this.targetDomeAngle = this.angle; // State Machine für diskrete Bewegung (wie Original) this.state = "IDLE"; // "IDLE" oder "MOVING" this.targetDomeAngle = this.angle; this.socket = io(socketUrl); // Registriere als aktive Instanz window.activeDomeInstance = this; this.setupSocketListeners(); this.startTracking(); } setupSocketListeners() { this.socket.on('connect', () => { console.log('Dome connected to server'); }); this.socket.on('telescope-position', (data) => { this.updateTelescopePosition(data.angle); }); } startTracking() { this.isTracking = true; // Tracking-Loop (30x pro Sekunde für smooth movement) this.trackingInterval = setInterval(() => { if (this.isTracking) { this.updateDomePosition(); } }, 1000 / 30); // 30 FPS für sehr smooth movement // Position broadcasting (10x pro Sekunde für responsive Visualisierung) this.broadcastInterval = setInterval(() => { this.broadcastPosition(); }, 100); // 10x pro Sekunde } // NFC-Tag Hilfsfunktionen (wie im Original script.js) getTagIndex(angle) { return Math.floor(((angle % 360) + 360) % 360 / this.tagStep); } tagToAngle(tagIndex) { return (tagIndex * this.tagStep + this.tagStep / 2) % 360; } findOptimalTag(telescopeAngle) { // Finde den Tag, der das Teleskop am besten zentriert const telTagIndex = this.getTagIndex(telescopeAngle); // Teste den Tag mit dem Teleskop und die Nachbar-Tags const candidates = [ (telTagIndex - 1 + this.tagCount) % this.tagCount, telTagIndex, (telTagIndex + 1) % this.tagCount ]; let bestTag = telTagIndex; let bestDiff = Infinity; for (let tagIndex of candidates) { const tagAngle = this.tagToAngle(tagIndex); const diff = this.angleDiff(telescopeAngle, tagAngle); const absDiff = Math.abs(diff); if (absDiff < bestDiff) { bestDiff = absDiff; bestTag = tagIndex; } } return bestTag; } updateTelescopePosition(telescopeAngle) { this.telescopeAngle = telescopeAngle; } updateDomePosition() { let diff = this.angleDiff(this.telescopeAngle, this.angle); let halfSlot = this.slotWidth / 2; let triggerPoint = halfSlot - 4.0; // Noch früher triggern! (6° statt 8°) let moveDir = "STOP"; if (this.state === "IDLE") { // Prüfen, ob Teleskop aus Schlitz läuft if (Math.abs(diff) > triggerPoint) { // Finde den optimalen Tag für das Teleskop let optimalTag = this.findOptimalTag(this.telescopeAngle); this.targetTagIndex = optimalTag; // Debug für große Sprünge (45° Bewegungen) const absDiff = Math.abs(diff); if (absDiff > 20) { console.log(`[Dome] LARGE JUMP DEBUG: Tel=${this.telescopeAngle.toFixed(1)}°, Dome=${this.angle.toFixed(1)}°, Diff=${diff.toFixed(1)}°`); console.log(`[Dome] Current Tag=${this.currentTagIndex}, Optimal Tag=${optimalTag}, Direction=${diff > 0 ? 'CW' : 'CCW'}`); // Zeige auch die findOptimalTag Ergebnisse console.log(`[Dome] Optimal Tag ${optimalTag} = ${this.tagToAngle(optimalTag).toFixed(1)}°`); } // Bei großen Sprüngen (>20°) erweiterten Puffer verwenden // WICHTIG: Puffer immer in CW-Richtung (Teleskop-Rotationsrichtung!) if (absDiff > 20) { // Bei großen Sprüngen: 1 Tag CW-Puffer (Teleskop dreht sich ja CW weiter!) this.targetTagIndex = (optimalTag + 1) % this.tagCount; console.log(`[Dome] Large jump: Adding CW buffer, Tag ${optimalTag} → ${this.targetTagIndex}`); } else if (absDiff > 8.0) { // Normale große Abweichungen if (diff > 0) { // Normale CW-Bewegung → 1 Tag weiter CW this.targetTagIndex = (optimalTag + 1) % this.tagCount; } else { // Normale CCW-Bewegung → kein Puffer (Teleskop kommt ja zurück) this.targetTagIndex = optimalTag; } if (diff > 0) { this.targetTagIndex = (optimalTag + 1) % this.tagCount; } else { this.targetTagIndex = (optimalTag - 1 + this.tagCount) % this.tagCount; } } const targetAngle = this.tagToAngle(this.targetTagIndex); if (this.targetTagIndex !== this.currentTagIndex) { this.targetDomeAngle = targetAngle; // Physisches Ziel this.state = "MOVING"; const bufferInfo = this.targetTagIndex !== optimalTag ? " [+buffer]" : ""; console.log(`[Dome] START MOVING: Tel out of slot (${diff.toFixed(1)}° > ${triggerPoint.toFixed(1)}°)`); console.log(`[Dome] Tag ${this.currentTagIndex} (${this.angle.toFixed(1)}°) → Tag ${this.targetTagIndex} (${targetAngle.toFixed(1)}°)${bufferInfo}`); } } } else if (this.state === "MOVING") { // Während der Bewegung: Höhere Trigger-Schwelle, damit keine neuen Bewegungen ausgelöst werden if (Math.abs(diff) > halfSlot + 2.0) { // 12° statt 6° während Bewegung console.log(`[Dome] WARNING: Large deviation during movement! Tel=${this.telescopeAngle.toFixed(1)}°, Dome=${this.angle.toFixed(1)}°, Diff=${diff.toFixed(1)}°`); console.log(`[Dome] Continuing current movement to ${this.targetDomeAngle.toFixed(1)}°`); } } if (this.state === "MOVING") { // Smoothe physische Bewegung zum Ziel-Tag let delta = this.angleDiff(this.targetDomeAngle, this.angle); if (Math.abs(delta) > this.domeSpeed) { this.angle = (this.angle + Math.sign(delta) * this.domeSpeed + 360) % 360; moveDir = Math.sign(delta) > 0 ? "CW" : "CCW"; console.log(`[Dome] MOVING: ${this.angle.toFixed(1)}° → ${this.targetDomeAngle.toFixed(1)}°, Dir=${moveDir}`); } else { // Ziel erreicht - Kuppel "erkennt" jetzt den neuen Tag this.angle = this.targetDomeAngle; this.currentTagIndex = this.targetTagIndex; // Tag-Position aktualisiert! this.state = "IDLE"; console.log(`[Dome] REACHED TARGET: Tag ${this.currentTagIndex} (${this.angle.toFixed(1)}°), back to IDLE`); } } } // Winkeldifferenz berechnen - kürzester Weg zwischen zwei Winkeln angleDiff(target, source) { // Standard-Formel für kürzesten Winkel let diff = target - source; // Normalisiere auf -180 bis +180 while (diff > 180) diff -= 360; while (diff < -180) diff += 360; return diff; } broadcastPosition() { const data = { angle: this.angle, slotWidth: this.slotWidth, telescopeAngle: this.telescopeAngle, diff: this.angleDiff(this.telescopeAngle, this.angle), timestamp: Date.now() }; this.socket.emit('dome-position', data); } stop() { this.isTracking = false; if (this.trackingInterval) clearInterval(this.trackingInterval); if (this.broadcastInterval) clearInterval(this.broadcastInterval); // Entferne Referenz als aktive Instanz if (window.activeDomeInstance === this) { window.activeDomeInstance = null; } } } // Wenn wir in einem Browser sind, NICHT automatisch initialisieren // Die Visualisierung wird die Kuppel kontrollieren if (typeof window !== 'undefined') { // Nur die Klasse verfügbar machen, aber noch nicht instanziieren window.Dome = Dome; } // Node.js Export für separate Prozesse if (typeof module !== 'undefined' && module.exports) { module.exports = Dome; }