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

256
JS/package-lock.json generated
View File

@@ -8,7 +8,32 @@
"name": "observatory-sim",
"version": "1.0.0",
"dependencies": {
"express": "^4.18.2"
"express": "^4.18.2",
"socket.io": "^4.8.1"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
"license": "MIT"
},
"node_modules/@types/cors": {
"version": "2.8.19",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
"integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "24.5.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz",
"integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.12.0"
}
},
"node_modules/accepts": {
@@ -30,6 +55,15 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
"node_modules/base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
"license": "MIT",
"engines": {
"node": "^4.5.0 || >= 5.9"
}
},
"node_modules/body-parser": {
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
@@ -128,6 +162,19 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"license": "MIT"
},
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -185,6 +232,67 @@
"node": ">= 0.8"
}
},
"node_modules/engine.io": {
"version": "6.6.4",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz",
"integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
"license": "MIT",
"dependencies": {
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.7.2",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1"
},
"engines": {
"node": ">=10.2.0"
}
},
"node_modules/engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/engine.io/node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/engine.io/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/engine.io/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -521,6 +629,15 @@
"node": ">= 0.6"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -770,6 +887,116 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/socket.io": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
"integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"cors": "~2.8.5",
"debug": "~4.3.2",
"engine.io": "~6.6.0",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.2.0"
}
},
"node_modules/socket.io-adapter": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
"license": "MIT",
"dependencies": {
"debug": "~4.3.4",
"ws": "~8.17.1"
}
},
"node_modules/socket.io-adapter/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/socket.io-adapter/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/socket.io-parser/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/socket.io/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/socket.io/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -801,6 +1028,12 @@
"node": ">= 0.6"
}
},
"node_modules/undici-types": {
"version": "7.12.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz",
"integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==",
"license": "MIT"
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -827,6 +1060,27 @@
"engines": {
"node": ">= 0.8"
}
},
"node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}

View File

@@ -6,6 +6,7 @@
"start": "node server.js"
},
"dependencies": {
"express": "^4.18.2"
"express": "^4.18.2",
"socket.io": "^4.8.1"
}
}

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);
});

View File

@@ -1,9 +1,46 @@
const express = require("express");
const http = require("http");
const socketIo = require("socket.io");
const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: "*",
methods: ["GET", "POST"]
}
});
const port = 3005;
app.use(express.static("public"));
app.listen(port, () => {
console.log(`Observatory simulation running at http://localhost:${port}`);
// WebSocket-Verbindungen verwalten
io.on("connection", (socket) => {
console.log("Client connected:", socket.id);
// Teleskop-Position broadcaasting
socket.on("telescope-position", (data) => {
// An alle anderen Clients weiterleiten (außer Sender)
socket.broadcast.emit("telescope-position", data);
});
// Kuppel-Position broadcasting
socket.on("dome-position", (data) => {
socket.broadcast.emit("dome-position", data);
});
// Teleskop-Steuerung
socket.on("telescope-control", (data) => {
io.emit("telescope-control", data);
});
socket.on("disconnect", () => {
console.log("Client disconnected:", socket.id);
});
});
server.listen(port, () => {
console.log(`Observatory simulation running at http://localhost:${port}`);
console.log("WebSocket server is ready for telescope and dome communication");
});