From cfa3f6112e73b9a6acf2dc97d499e46b1a4d416f Mon Sep 17 00:00:00 2001 From: rxf Date: Wed, 24 Sep 2025 11:35:07 +0200 Subject: [PATCH] =?UTF-8?q?L=C3=A4uft=20mal=20mit=20kontinuierlicher=20Kup?= =?UTF-8?q?pelposition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 18 +++ JS/package-lock.json | 256 ++++++++++++++++++++++++++++- JS/package.json | 3 +- JS/public/dome.js | 142 ++++++++++++++++ JS/public/index.html | 53 +++++- JS/public/style.css | 126 +++++++++++++++ JS/public/telescope.js | 118 ++++++++++++++ JS/public/visualization.js | 322 +++++++++++++++++++++++++++++++++++++ JS/server.js | 41 ++++- 9 files changed, 1070 insertions(+), 9 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 JS/public/dome.js create mode 100644 JS/public/style.css create mode 100644 JS/public/telescope.js create mode 100644 JS/public/visualization.js diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..82d5d5f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/JS/server.js" + } + ] +} \ No newline at end of file diff --git a/JS/package-lock.json b/JS/package-lock.json index 16be04e..6275596 100644 --- a/JS/package-lock.json +++ b/JS/package-lock.json @@ -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 + } + } } } } diff --git a/JS/package.json b/JS/package.json index 100408b..76bcfba 100644 --- a/JS/package.json +++ b/JS/package.json @@ -6,6 +6,7 @@ "start": "node server.js" }, "dependencies": { - "express": "^4.18.2" + "express": "^4.18.2", + "socket.io": "^4.8.1" } } diff --git a/JS/public/dome.js b/JS/public/dome.js new file mode 100644 index 0000000..e71817c --- /dev/null +++ b/JS/public/dome.js @@ -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; +} \ No newline at end of file diff --git a/JS/public/index.html b/JS/public/index.html index cf9b464..f477059 100644 --- a/JS/public/index.html +++ b/JS/public/index.html @@ -2,13 +2,56 @@ - Kuppel-Simulation (JS) + Observatory Simulation - Multi-Task System -

Kuppel-Simulation mit NFC-Tags (JavaScript)

- -

-  
+  

Observatory Simulation - Distributed Tasks

+ +
+
+ +
+ +
+
+

🔭 Teleskop-Steuerung

+
+ + + + +
+
+ Position: 0.0° | Speed: 1 U/min +
+
+ +
+

🏠 Kuppel-Tracking

+
+ Position: 0.0° | Tracking: Standby +
+
+ +
+

📊 System Status

+
+ WebSocket: Disconnected +
+
+
+
+ +
+

Debug Information

+

+  
+ + + + + + diff --git a/JS/public/style.css b/JS/public/style.css new file mode 100644 index 0000000..4be0e4e --- /dev/null +++ b/JS/public/style.css @@ -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; + } +} \ No newline at end of file diff --git a/JS/public/telescope.js b/JS/public/telescope.js new file mode 100644 index 0000000..4180187 --- /dev/null +++ b/JS/public/telescope.js @@ -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; +} \ No newline at end of file diff --git a/JS/public/visualization.js b/JS/public/visualization.js new file mode 100644 index 0000000..a948c9d --- /dev/null +++ b/JS/public/visualization.js @@ -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); +}); \ No newline at end of file diff --git a/JS/server.js b/JS/server.js index 66171f9..32faab1 100644 --- a/JS/server.js +++ b/JS/server.js @@ -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"); });