Läuft mal mit kontinuierlicher Kuppelposition

This commit is contained in:
rxf
2025-09-24 11:35:07 +02:00
parent d02588b554
commit cfa3f6112e
9 changed files with 1070 additions and 9 deletions

142
JS/public/dome.js Normal file
View File

@@ -0,0 +1,142 @@
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)
this.slotWidth = 20; // Spaltbreite in Grad
this.domeSpeed = 3.0; // Grad pro Frame (für smoothe animation)
this.telescopeAngle = 0;
// 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
}
updateTelescopePosition(telescopeAngle) {
this.telescopeAngle = telescopeAngle;
}
updateDomePosition() {
let diff = this.angleDiff(this.telescopeAngle, this.angle);
let halfSlot = this.slotWidth / 2;
let triggerPoint = halfSlot - 2.0; // Starte früher! (8° statt 10°)
let moveDir = "STOP";
if (this.state === "IDLE") {
// Prüfen, ob Teleskop aus Schlitz läuft - aber früher triggern!
if (diff > triggerPoint) {
// CW-Bewegung → Kuppel zentriert sich um das Teleskop
this.targetDomeAngle = (this.telescopeAngle + 360) % 360;
this.state = "MOVING";
console.log(`[Dome] START MOVING: Tel out of slot CW (${diff.toFixed(1)}° > ${triggerPoint.toFixed(1)}°), target: ${this.targetDomeAngle.toFixed(1)}° (centered on telescope)`);
} else if (diff < -triggerPoint) {
// CCW-Bewegung → Kuppel zentriert sich um das Teleskop
this.targetDomeAngle = (this.telescopeAngle + 360) % 360;
this.state = "MOVING";
console.log(`[Dome] START MOVING: Tel out of slot CCW (${diff.toFixed(1)}° < ${-triggerPoint.toFixed(1)}°), target: ${this.targetDomeAngle.toFixed(1)}° (centered on telescope)`);
}
}
if (this.state === "MOVING") {
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";
} else {
// Ziel erreicht
this.angle = this.targetDomeAngle;
this.state = "IDLE";
console.log(`[Dome] REACHED TARGET: ${this.angle.toFixed(1)}°, back to IDLE`);
}
}
// Debug nur bei Aktivität
if (moveDir !== "STOP") {
console.log(`[Dome] ${this.state}: Tel=${this.telescopeAngle.toFixed(1)}°, Dome=${this.angle.toFixed(1)}° → ${this.targetDomeAngle.toFixed(1)}°, Dir=${moveDir}`);
}
}
// 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;
}

View File

@@ -2,13 +2,56 @@
<html lang="de">
<head>
<meta charset="UTF-8" />
<title>Kuppel-Simulation (JS)</title>
<title>Observatory Simulation - Multi-Task System</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h2>Kuppel-Simulation mit NFC-Tags (JavaScript)</h2>
<canvas id="simCanvas" width="700" height="700"></canvas>
<pre id="debug"></pre>
<script src="script.js"></script>
<h2>Observatory Simulation - Distributed Tasks</h2>
<div class="container">
<div class="visualization">
<canvas id="simCanvas" width="700" height="700"></canvas>
</div>
<div class="controls">
<div class="task-section">
<h3>🔭 Teleskop-Steuerung</h3>
<div class="button-group">
<button onclick="adjustTelescope(-45)">↺ 45° CCW</button>
<button onclick="adjustTelescope(-15)">↺ 15° CCW</button>
<button onclick="adjustTelescope(15)">↻ 15° CW</button>
<button onclick="adjustTelescope(45)">↻ 45° CW</button>
</div>
<div class="status" id="telescope-status">
Position: 0.0° | Speed: 1 U/min
</div>
</div>
<div class="task-section">
<h3>🏠 Kuppel-Tracking</h3>
<div class="status" id="dome-status">
Position: 0.0° | Tracking: Standby
</div>
</div>
<div class="task-section">
<h3>📊 System Status</h3>
<div class="status" id="system-status">
WebSocket: Disconnected
</div>
</div>
</div>
</div>
<div class="debug-info">
<h4>Debug Information</h4>
<pre id="debug"></pre>
</div>
<!-- WebSocket & Task Scripts -->
<script src="/socket.io/socket.io.js"></script>
<script src="telescope.js"></script>
<script src="dome.js"></script>
<script src="visualization.js"></script>
</body>
</html>

126
JS/public/style.css Normal file
View File

@@ -0,0 +1,126 @@
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #1a1a1a;
color: #e0e0e0;
margin: 0;
padding: 20px;
}
h2, h3 {
color: #4a9eff;
margin-top: 0;
}
.container {
display: flex;
gap: 30px;
max-width: 1200px;
margin: 0 auto;
}
.visualization {
flex-shrink: 0;
}
#simCanvas {
border: 2px solid #444;
border-radius: 8px;
background-color: #2a2a2a;
}
.controls {
flex: 1;
min-width: 300px;
}
.task-section {
background-color: #2a2a2a;
border: 1px solid #444;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
.button-group {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 15px;
}
button {
background-color: #4a9eff;
color: white;
border: none;
padding: 10px 15px;
border-radius: 5px;
cursor: pointer;
font-weight: 500;
transition: background-color 0.2s;
}
button:hover {
background-color: #357abd;
}
button:active {
transform: translateY(1px);
}
.status {
font-family: 'Monaco', 'Menlo', monospace;
font-size: 12px;
color: #a0a0a0;
background-color: #1a1a1a;
padding: 8px 12px;
border-radius: 4px;
border-left: 3px solid #4a9eff;
}
.debug-info {
margin-top: 30px;
background-color: #2a2a2a;
border: 1px solid #444;
border-radius: 8px;
padding: 20px;
}
#debug {
font-family: 'Monaco', 'Menlo', monospace;
font-size: 11px;
line-height: 1.4;
color: #c0c0c0;
background-color: #1a1a1a;
padding: 15px;
border-radius: 4px;
overflow: auto;
max-height: 200px;
}
/* Responsive Design */
@media (max-width: 1024px) {
.container {
flex-direction: column;
align-items: center;
}
.controls {
max-width: 700px;
}
}
@media (max-width: 768px) {
body {
padding: 10px;
}
#simCanvas {
width: 100%;
height: auto;
max-width: 500px;
}
.button-group {
justify-content: center;
}
}

118
JS/public/telescope.js Normal file
View File

@@ -0,0 +1,118 @@
class Telescope {
constructor(socketUrl) {
// Verhindere mehrfache Instanzen
if (window.activeTelescopeInstance) {
console.warn('Telescope instance already exists! Stopping old one...');
window.activeTelescopeInstance.stop();
}
this.angle = 0; // Grad
this.rotationSpeed = 6; // Grad pro Sekunde (1 Umdrehung = 360° pro Minute = 6°/sec)
this.socket = io(socketUrl);
this.isRunning = false;
// Registriere als aktive Instanz
window.activeTelescopeInstance = this;
this.setupSocketListeners();
this.start();
}
setupSocketListeners() {
this.socket.on('connect', () => {
console.log('Telescope connected to server');
});
this.socket.on('telescope-control', (data) => {
this.handleControl(data);
});
}
start() {
this.isRunning = true;
// Kontinuierliche Rotation (1 U/min = 6°/sec)
// Vernünftige Frequenz für gleichmäßige aber nicht chaotische Bewegung
this.rotationInterval = setInterval(() => {
if (this.isRunning) {
this.angle = (this.angle + this.rotationSpeed / 60) % 360; // Update 60x pro Sekunde
}
}, 1000 / 60); // 60 FPS - das ist völlig ausreichend
// Position senden (1x pro Sekunde - Visualisierung interpoliert lokal)
this.broadcastInterval = setInterval(() => {
if (this.isRunning) {
this.broadcastPosition();
}
}, 1000); // Zurück zu 1 Sekunde
}
stop() {
this.isRunning = false;
if (this.rotationInterval) clearInterval(this.rotationInterval);
if (this.broadcastInterval) clearInterval(this.broadcastInterval);
// Entferne Referenz als aktive Instanz
if (window.activeTelescopeInstance === this) {
window.activeTelescopeInstance = null;
}
}
broadcastPosition() {
const data = {
angle: this.angle,
timestamp: Date.now()
};
this.socket.emit('telescope-position', data);
console.log(`[Telescope] Broadcasting position: ${this.angle.toFixed(1)}°`);
}
handleControl(data) {
switch (data.command) {
case 'adjust':
this.adjustPosition(data.degrees);
break;
case 'stop':
this.stop();
break;
case 'start':
this.start();
break;
}
}
adjustPosition(degrees) {
this.angle = (this.angle + degrees + 360) % 360;
console.log(`Telescope adjusted by ${degrees}°, new position: ${this.angle.toFixed(1)}°`);
this.broadcastPosition(); // Sofort neue Position senden
}
// Manuelle Steuerung für Testing
adjustBy15CW() {
this.adjustPosition(15);
}
adjustBy15CCW() {
this.adjustPosition(-15);
}
adjustBy45CW() {
this.adjustPosition(45);
}
adjustBy45CCW() {
this.adjustPosition(-45);
}
}
// Wenn wir in einem Browser sind, NICHT automatisch initialisieren
// Die Visualisierung wird das Teleskop kontrollieren
if (typeof window !== 'undefined') {
// Nur die Klasse verfügbar machen, aber noch nicht instanziieren
window.Telescope = Telescope;
}
// Node.js Export für separate Prozesse
if (typeof module !== 'undefined' && module.exports) {
module.exports = Telescope;
}

322
JS/public/visualization.js Normal file
View File

@@ -0,0 +1,322 @@
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);
});