Skip to main content
Glama
direct-control.js61.2 kB
/** * GeoFS Direct Control Script * * This script directly controls the GeoFS aircraft using the Boeing 787 flight data * without requiring a WebSocket connection. * * Version: 1.1.0 - Enhanced with more flight data and controls */ (function() { console.log('Starting GeoFS Direct Control...'); // Check if GeoFS is loaded if (typeof geofs === 'undefined') { console.error('GeoFS not detected! Please run this script on the GeoFS website.'); return; } // Load the Boeing 787 flight data - expanded with more detailed takeoff sequence const flightData = [ // Initial position on runway { position: { latitude: 49.6233, longitude: 6.2044, altitude: 376 }, attitude: { heading: 270, pitch: 0, roll: 0 }, speed: { kias: 0, groundSpeed: 0, verticalSpeed: 0 }, controls: { throttle: 0, flaps: 0.5, gear: 1 } }, // Begin taxi { position: { latitude: 49.6233, longitude: 6.2044, altitude: 376 }, attitude: { heading: 270, pitch: 0, roll: 0 }, speed: { kias: 10, groundSpeed: 10, verticalSpeed: 0 }, controls: { throttle: 0.2, flaps: 0.5, gear: 1 } }, // Accelerating on runway { position: { latitude: 49.6232, longitude: 6.203, altitude: 376 }, attitude: { heading: 270, pitch: 0, roll: 0 }, speed: { kias: 40, groundSpeed: 40, verticalSpeed: 0 }, controls: { throttle: 0.7, flaps: 0.5, gear: 1 } }, // Continue acceleration { position: { latitude: 49.6232, longitude: 6.201, altitude: 376 }, attitude: { heading: 270, pitch: 0, roll: 0 }, speed: { kias: 80, groundSpeed: 80, verticalSpeed: 0 }, controls: { throttle: 0.9, flaps: 0.5, gear: 1 } }, // Approaching rotation speed { position: { latitude: 49.6231, longitude: 6.199, altitude: 376 }, attitude: { heading: 270, pitch: 0, roll: 0 }, speed: { kias: 120, groundSpeed: 120, verticalSpeed: 0 }, controls: { throttle: 1.0, flaps: 0.5, gear: 1 } }, // Rotation { position: { latitude: 49.623, longitude: 6.197, altitude: 376 }, attitude: { heading: 270, pitch: 5, roll: 0 }, speed: { kias: 140, groundSpeed: 140, verticalSpeed: 300 }, controls: { throttle: 1.0, flaps: 0.5, gear: 1 } }, // Liftoff { position: { latitude: 49.6229, longitude: 6.195, altitude: 380 }, attitude: { heading: 270, pitch: 10, roll: 0 }, speed: { kias: 160, groundSpeed: 160, verticalSpeed: 1000 }, controls: { throttle: 1.0, flaps: 0.5, gear: 1 } }, // Initial climb { position: { latitude: 49.6228, longitude: 6.193, altitude: 400 }, attitude: { heading: 270, pitch: 15, roll: 0 }, speed: { kias: 170, groundSpeed: 170, verticalSpeed: 1500 }, controls: { throttle: 1.0, flaps: 0.5, gear: 0.5 } // Gear retracting }, // Continuing climb { position: { latitude: 49.6227, longitude: 6.191, altitude: 450 }, attitude: { heading: 270, pitch: 15, roll: 0 }, speed: { kias: 180, groundSpeed: 180, verticalSpeed: 1800 }, controls: { throttle: 1.0, flaps: 0.3, gear: 0 } // Gear up, reducing flaps }, // Climbing and accelerating { position: { latitude: 49.6226, longitude: 6.189, altitude: 500 }, attitude: { heading: 270, pitch: 12, roll: 0 }, speed: { kias: 200, groundSpeed: 200, verticalSpeed: 1500 }, controls: { throttle: 0.9, flaps: 0.1, gear: 0 } // Reducing flaps further }, // Leveling at 1000ft { position: { latitude: 49.6225, longitude: 6.187, altitude: 680 }, // ~1000ft attitude: { heading: 270, pitch: 5, roll: 0 }, speed: { kias: 220, groundSpeed: 220, verticalSpeed: 500 }, controls: { throttle: 0.8, flaps: 0, gear: 0 } // Flaps up }, // Cruising { position: { latitude: 49.6224, longitude: 6.185, altitude: 700 }, attitude: { heading: 270, pitch: 2, roll: 0 }, speed: { kias: 250, groundSpeed: 250, verticalSpeed: 0 }, controls: { throttle: 0.7, flaps: 0, gear: 0 } } ]; // Additional flight patterns const flightPatterns = { takeoff: flightData, rightTurn: [ // Starting from level flight { position: { latitude: 49.6224, longitude: 6.185, altitude: 700 }, attitude: { heading: 270, pitch: 2, roll: 0 }, speed: { kias: 250, groundSpeed: 250, verticalSpeed: 0 }, controls: { throttle: 0.7, flaps: 0, gear: 0 } }, // Begin right turn { position: { latitude: 49.6224, longitude: 6.183, altitude: 700 }, attitude: { heading: 290, pitch: 2, roll: 15 }, speed: { kias: 250, groundSpeed: 250, verticalSpeed: 0 }, controls: { throttle: 0.7, flaps: 0, gear: 0 } }, // Continuing turn { position: { latitude: 49.6226, longitude: 6.181, altitude: 700 }, attitude: { heading: 310, pitch: 2, roll: 20 }, speed: { kias: 245, groundSpeed: 245, verticalSpeed: 0 }, controls: { throttle: 0.7, flaps: 0, gear: 0 } }, // Halfway through turn { position: { latitude: 49.6228, longitude: 6.180, altitude: 700 }, attitude: { heading: 330, pitch: 2, roll: 20 }, speed: { kias: 245, groundSpeed: 245, verticalSpeed: 0 }, controls: { throttle: 0.7, flaps: 0, gear: 0 } }, // Continuing turn { position: { latitude: 49.6230, longitude: 6.181, altitude: 700 }, attitude: { heading: 350, pitch: 2, roll: 15 }, speed: { kias: 245, groundSpeed: 245, verticalSpeed: 0 }, controls: { throttle: 0.7, flaps: 0, gear: 0 } }, // Completing turn { position: { latitude: 49.6232, longitude: 6.183, altitude: 700 }, attitude: { heading: 10, pitch: 2, roll: 5 }, speed: { kias: 250, groundSpeed: 250, verticalSpeed: 0 }, controls: { throttle: 0.7, flaps: 0, gear: 0 } }, // Leveled out on new heading { position: { latitude: 49.6234, longitude: 6.184, altitude: 700 }, attitude: { heading: 30, pitch: 2, roll: 0 }, speed: { kias: 250, groundSpeed: 250, verticalSpeed: 0 }, controls: { throttle: 0.7, flaps: 0, gear: 0 } } ], descent: [ // Starting from cruise { position: { latitude: 49.6234, longitude: 6.184, altitude: 700 }, attitude: { heading: 30, pitch: 2, roll: 0 }, speed: { kias: 250, groundSpeed: 250, verticalSpeed: 0 }, controls: { throttle: 0.7, flaps: 0, gear: 0 } }, // Begin descent { position: { latitude: 49.6236, longitude: 6.186, altitude: 690 }, attitude: { heading: 30, pitch: -2, roll: 0 }, speed: { kias: 260, groundSpeed: 260, verticalSpeed: -500 }, controls: { throttle: 0.5, flaps: 0, gear: 0 } }, // Continuing descent { position: { latitude: 49.6238, longitude: 6.188, altitude: 670 }, attitude: { heading: 30, pitch: -3, roll: 0 }, speed: { kias: 270, groundSpeed: 270, verticalSpeed: -800 }, controls: { throttle: 0.4, flaps: 0, gear: 0 } }, // Approaching pattern altitude { position: { latitude: 49.6240, longitude: 6.190, altitude: 600 }, attitude: { heading: 30, pitch: -2, roll: 0 }, speed: { kias: 250, groundSpeed: 250, verticalSpeed: -500 }, controls: { throttle: 0.4, flaps: 0.1, gear: 0 } }, // Leveling at pattern altitude { position: { latitude: 49.6242, longitude: 6.192, altitude: 550 }, attitude: { heading: 30, pitch: 0, roll: 0 }, speed: { kias: 230, groundSpeed: 230, verticalSpeed: 0 }, controls: { throttle: 0.5, flaps: 0.2, gear: 0 } } ], approach: [ // Starting approach from pattern altitude { position: { latitude: 49.6242, longitude: 6.192, altitude: 550 }, attitude: { heading: 30, pitch: 0, roll: 0 }, speed: { kias: 230, groundSpeed: 230, verticalSpeed: 0 }, controls: { throttle: 0.5, flaps: 0.2, gear: 0 } }, // Begin turn to final approach { position: { latitude: 49.6244, longitude: 6.194, altitude: 550 }, attitude: { heading: 60, pitch: 0, roll: 15 }, speed: { kias: 220, groundSpeed: 220, verticalSpeed: 0 }, controls: { throttle: 0.4, flaps: 0.3, gear: 0 } }, // Continuing turn { position: { latitude: 49.6246, longitude: 6.196, altitude: 550 }, attitude: { heading: 90, pitch: 0, roll: 20 }, speed: { kias: 210, groundSpeed: 210, verticalSpeed: 0 }, controls: { throttle: 0.4, flaps: 0.3, gear: 0 } }, // Halfway through turn { position: { latitude: 49.6248, longitude: 6.198, altitude: 550 }, attitude: { heading: 120, pitch: 0, roll: 20 }, speed: { kias: 200, groundSpeed: 200, verticalSpeed: 0 }, controls: { throttle: 0.4, flaps: 0.4, gear: 0 } }, // Continuing turn { position: { latitude: 49.6250, longitude: 6.200, altitude: 550 }, attitude: { heading: 150, pitch: 0, roll: 15 }, speed: { kias: 190, groundSpeed: 190, verticalSpeed: 0 }, controls: { throttle: 0.4, flaps: 0.4, gear: 0 } }, // Completing turn to final { position: { latitude: 49.6252, longitude: 6.202, altitude: 550 }, attitude: { heading: 180, pitch: 0, roll: 5 }, speed: { kias: 180, groundSpeed: 180, verticalSpeed: 0 }, controls: { throttle: 0.4, flaps: 0.5, gear: 0 } }, // Established on final approach { position: { latitude: 49.6254, longitude: 6.202, altitude: 550 }, attitude: { heading: 180, pitch: -1, roll: 0 }, speed: { kias: 170, groundSpeed: 170, verticalSpeed: -300 }, controls: { throttle: 0.4, flaps: 0.5, gear: 0.5 } // Starting gear extension }, // Begin glideslope { position: { latitude: 49.6256, longitude: 6.202, altitude: 530 }, attitude: { heading: 180, pitch: -3, roll: 0 }, speed: { kias: 160, groundSpeed: 160, verticalSpeed: -600 }, controls: { throttle: 0.35, flaps: 0.7, gear: 1 } // Gear fully extended }, // Continuing approach { position: { latitude: 49.6258, longitude: 6.202, altitude: 500 }, attitude: { heading: 180, pitch: -3, roll: 0 }, speed: { kias: 150, groundSpeed: 150, verticalSpeed: -600 }, controls: { throttle: 0.35, flaps: 0.8, gear: 1 } }, // Final approach { position: { latitude: 49.6260, longitude: 6.202, altitude: 470 }, attitude: { heading: 180, pitch: -3, roll: 0 }, speed: { kias: 145, groundSpeed: 145, verticalSpeed: -600 }, controls: { throttle: 0.35, flaps: 1.0, gear: 1 } // Full flaps } ], landing: [ // Short final { position: { latitude: 49.6260, longitude: 6.202, altitude: 470 }, attitude: { heading: 180, pitch: -3, roll: 0 }, speed: { kias: 145, groundSpeed: 145, verticalSpeed: -600 }, controls: { throttle: 0.35, flaps: 1.0, gear: 1 } }, // Over threshold { position: { latitude: 49.6262, longitude: 6.202, altitude: 400 }, attitude: { heading: 180, pitch: -2, roll: 0 }, speed: { kias: 140, groundSpeed: 140, verticalSpeed: -300 }, controls: { throttle: 0.3, flaps: 1.0, gear: 1 } }, // Flare { position: { latitude: 49.6264, longitude: 6.202, altitude: 380 }, attitude: { heading: 180, pitch: 2, roll: 0 }, speed: { kias: 135, groundSpeed: 135, verticalSpeed: -100 }, controls: { throttle: 0.1, flaps: 1.0, gear: 1 } }, // Touchdown { position: { latitude: 49.6266, longitude: 6.202, altitude: 376 }, attitude: { heading: 180, pitch: 0, roll: 0 }, speed: { kias: 130, groundSpeed: 130, verticalSpeed: 0 }, controls: { throttle: 0, flaps: 1.0, gear: 1 } }, // Deceleration { position: { latitude: 49.6268, longitude: 6.202, altitude: 376 }, attitude: { heading: 180, pitch: 0, roll: 0 }, speed: { kias: 100, groundSpeed: 100, verticalSpeed: 0 }, controls: { throttle: 0, flaps: 1.0, gear: 1 } }, // Continued deceleration { position: { latitude: 49.6270, longitude: 6.202, altitude: 376 }, attitude: { heading: 180, pitch: 0, roll: 0 }, speed: { kias: 60, groundSpeed: 60, verticalSpeed: 0 }, controls: { throttle: 0, flaps: 1.0, gear: 1 } }, // Taxi speed { position: { latitude: 49.6272, longitude: 6.202, altitude: 376 }, attitude: { heading: 180, pitch: 0, roll: 0 }, speed: { kias: 20, groundSpeed: 20, verticalSpeed: 0 }, controls: { throttle: 0.1, flaps: 1.0, gear: 1 } } ], holdingPattern: [ // Entry point { position: { latitude: 49.6234, longitude: 6.184, altitude: 700 }, attitude: { heading: 30, pitch: 2, roll: 0 }, speed: { kias: 250, groundSpeed: 250, verticalSpeed: 0 }, controls: { throttle: 0.7, flaps: 0, gear: 0 } }, // Begin turn { position: { latitude: 49.6236, longitude: 6.186, altitude: 700 }, attitude: { heading: 60, pitch: 2, roll: 15 }, speed: { kias: 240, groundSpeed: 240, verticalSpeed: 0 }, controls: { throttle: 0.65, flaps: 0, gear: 0 } }, // Continuing turn { position: { latitude: 49.6238, longitude: 6.188, altitude: 700 }, attitude: { heading: 90, pitch: 2, roll: 20 }, speed: { kias: 230, groundSpeed: 230, verticalSpeed: 0 }, controls: { throttle: 0.6, flaps: 0, gear: 0 } }, // Halfway through turn { position: { latitude: 49.6240, longitude: 6.190, altitude: 700 }, attitude: { heading: 120, pitch: 2, roll: 20 }, speed: { kias: 230, groundSpeed: 230, verticalSpeed: 0 }, controls: { throttle: 0.6, flaps: 0, gear: 0 } }, // Continuing turn { position: { latitude: 49.6242, longitude: 6.192, altitude: 700 }, attitude: { heading: 150, pitch: 2, roll: 15 }, speed: { kias: 230, groundSpeed: 230, verticalSpeed: 0 }, controls: { throttle: 0.6, flaps: 0, gear: 0 } }, // Completing turn { position: { latitude: 49.6244, longitude: 6.194, altitude: 700 }, attitude: { heading: 180, pitch: 2, roll: 5 }, speed: { kias: 230, groundSpeed: 230, verticalSpeed: 0 }, controls: { throttle: 0.6, flaps: 0, gear: 0 } }, // Outbound leg { position: { latitude: 49.6246, longitude: 6.194, altitude: 700 }, attitude: { heading: 210, pitch: 2, roll: 0 }, speed: { kias: 230, groundSpeed: 230, verticalSpeed: 0 }, controls: { throttle: 0.6, flaps: 0, gear: 0 } }, // Begin second turn { position: { latitude: 49.6248, longitude: 6.192, altitude: 700 }, attitude: { heading: 240, pitch: 2, roll: 15 }, speed: { kias: 230, groundSpeed: 230, verticalSpeed: 0 }, controls: { throttle: 0.6, flaps: 0, gear: 0 } }, // Continuing second turn { position: { latitude: 49.6246, longitude: 6.190, altitude: 700 }, attitude: { heading: 270, pitch: 2, roll: 20 }, speed: { kias: 230, groundSpeed: 230, verticalSpeed: 0 }, controls: { throttle: 0.6, flaps: 0, gear: 0 } }, // Halfway through second turn { position: { latitude: 49.6244, longitude: 6.188, altitude: 700 }, attitude: { heading: 300, pitch: 2, roll: 20 }, speed: { kias: 230, groundSpeed: 230, verticalSpeed: 0 }, controls: { throttle: 0.6, flaps: 0, gear: 0 } }, // Continuing second turn { position: { latitude: 49.6242, longitude: 6.186, altitude: 700 }, attitude: { heading: 330, pitch: 2, roll: 15 }, speed: { kias: 230, groundSpeed: 230, verticalSpeed: 0 }, controls: { throttle: 0.6, flaps: 0, gear: 0 } }, // Completing second turn { position: { latitude: 49.6240, longitude: 6.184, altitude: 700 }, attitude: { heading: 0, pitch: 2, roll: 5 }, speed: { kias: 230, groundSpeed: 230, verticalSpeed: 0 }, controls: { throttle: 0.6, flaps: 0, gear: 0 } }, // Inbound leg, completing the pattern { position: { latitude: 49.6238, longitude: 6.184, altitude: 700 }, attitude: { heading: 30, pitch: 2, roll: 0 }, speed: { kias: 240, groundSpeed: 240, verticalSpeed: 0 }, controls: { throttle: 0.65, flaps: 0, gear: 0 } } ] }; let currentIndex = 0; let updateInterval = null; let currentPattern = 'takeoff'; let isPaused = false; // Function to update aircraft state function updateAircraftState(data) { try { console.log(`Updating aircraft state: Index ${currentIndex} in pattern ${currentPattern}`); // Update position if (data.position) { console.log(`Setting position: lat=${data.position.latitude}, lon=${data.position.longitude}, alt=${data.position.altitude}`); // Direct property setting if (geofs.aircraft.instance.llaLocation) { geofs.aircraft.instance.llaLocation[0] = data.position.latitude; geofs.aircraft.instance.llaLocation[1] = data.position.longitude; geofs.aircraft.instance.llaLocation[2] = data.position.altitude; console.log('Position set by directly updating llaLocation'); } } // Update attitude if (data.attitude) { console.log(`Setting attitude: heading=${data.attitude.heading}, pitch=${data.attitude.pitch}, roll=${data.attitude.roll}`); // Direct property setting if (geofs.aircraft.instance.htr) { geofs.aircraft.instance.htr[0] = data.attitude.heading; geofs.aircraft.instance.htr[1] = data.attitude.pitch; geofs.aircraft.instance.htr[2] = data.attitude.roll; console.log('Attitude set by directly updating htr'); } } // Update speed if (data.speed) { console.log(`Setting speed: kias=${data.speed.kias}, groundSpeed=${data.speed.groundSpeed}, verticalSpeed=${data.speed.verticalSpeed}`); // Set airspeed if (geofs.aircraft.instance.animationValue && geofs.aircraft.instance.animationValue.kias) { geofs.aircraft.instance.animationValue.kias = data.speed.kias; console.log('Airspeed set using animationValue.kias'); } // Set ground speed if (typeof geofs.aircraft.instance.groundSpeed !== 'undefined') { geofs.aircraft.instance.groundSpeed = data.speed.groundSpeed; console.log('Ground speed set'); } // Set vertical speed if (typeof geofs.aircraft.instance.verticalSpeed !== 'undefined') { geofs.aircraft.instance.verticalSpeed = data.speed.verticalSpeed; console.log('Vertical speed set'); } } // Update controls if (data.controls) { console.log(`Setting controls: throttle=${data.controls.throttle}, flaps=${data.controls.flaps}, gear=${data.controls.gear}`); // Set throttle if (geofs.aircraft.instance.engines && geofs.aircraft.instance.engines.length > 0) { geofs.aircraft.instance.engines.forEach(engine => { engine.throttle = data.controls.throttle; }); console.log('Throttle set'); } // Set flaps if (geofs.aircraft.instance.controls && typeof geofs.aircraft.instance.controls.flaps !== 'undefined') { geofs.aircraft.instance.controls.flaps = data.controls.flaps; console.log('Flaps set'); } // Set landing gear if (typeof geofs.aircraft.instance.gearPosition !== 'undefined') { geofs.aircraft.instance.gearPosition = data.controls.gear; console.log('Landing gear set'); } } // Force update of aircraft physics if (typeof geofs.aircraft.instance.update === 'function') { geofs.aircraft.instance.update(); console.log('Called aircraft update method'); } } catch (error) { console.error('Error updating aircraft state:', error); } } // Start the replay function startReplay(patternName = 'takeoff') { console.log(`Starting flight pattern: ${patternName}`); // First, make sure we're using the Boeing 787 if (typeof geofs.aircraft.instance.change === 'function') { geofs.aircraft.instance.change(18); // 18 is the ID for Boeing 787 console.log('Changed aircraft to Boeing 787'); } // Get the selected pattern const pattern = flightPatterns[patternName] || flightData; currentPattern = patternName; // Set initial state updateAircraftState(pattern[0]); currentIndex = 0; isPaused = false; // Update UI updateControlButtons(); // Clear any existing interval if (updateInterval) { clearInterval(updateInterval); } // Update at specified intervals updateInterval = setInterval(function() { if (isPaused) return; currentIndex++; if (currentIndex < pattern.length) { updateAircraftState(pattern[currentIndex]); } else { // End of data, stop the replay clearInterval(updateInterval); console.log('Flight pattern replay complete'); // Reset UI const startButton = document.getElementById('direct-control-start'); if (startButton) { startButton.textContent = 'Start Boeing 787 Takeoff'; startButton.disabled = false; } } }, 2000); // 2-second intervals for easier visualization } // Pause or resume the replay function togglePause() { isPaused = !isPaused; console.log(isPaused ? 'Replay paused' : 'Replay resumed'); updateControlButtons(); } // Stop the replay function stopReplay() { if (updateInterval) { clearInterval(updateInterval); updateInterval = null; } isPaused = false; console.log('Replay stopped'); updateControlButtons(); } // Update control buttons state function updateControlButtons() { const startButton = document.getElementById('direct-control-start'); const pauseButton = document.getElementById('direct-control-pause'); const stopButton = document.getElementById('direct-control-stop'); const patternSelect = document.getElementById('direct-control-pattern'); if (startButton) { startButton.disabled = updateInterval !== null; } if (pauseButton) { pauseButton.textContent = isPaused ? 'Resume' : 'Pause'; pauseButton.disabled = updateInterval === null; } if (stopButton) { stopButton.disabled = updateInterval === null; } if (patternSelect) { patternSelect.disabled = updateInterval !== null; } } // Add UI controls function addControlPanel() { // Create control panel container const panel = document.createElement('div'); panel.id = 'direct-control-panel'; panel.style.position = 'absolute'; panel.style.top = '50px'; panel.style.right = '10px'; panel.style.padding = '15px'; panel.style.backgroundColor = 'rgba(0, 0, 0, 0.8)'; panel.style.borderRadius = '8px'; panel.style.color = '#fff'; panel.style.zIndex = '1000'; panel.style.width = '280px'; panel.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.5)'; panel.style.fontFamily = 'Arial, sans-serif'; // Add title const title = document.createElement('div'); title.textContent = 'Boeing 787 MCP Control'; title.style.fontWeight = 'bold'; title.style.marginBottom = '15px'; title.style.textAlign = 'center'; title.style.fontSize = '16px'; title.style.borderBottom = '1px solid #444'; title.style.paddingBottom = '8px'; panel.appendChild(title); // Add tabs const tabContainer = document.createElement('div'); tabContainer.style.display = 'flex'; tabContainer.style.marginBottom = '15px'; tabContainer.style.borderBottom = '1px solid #444'; const tabs = [ { id: 'flight-patterns-tab', text: 'Flight Patterns' }, { id: 'flight-data-tab', text: 'Flight Data' }, { id: 'connection-tab', text: 'Connection' }, { id: 'gpt-tab', text: 'GPT Control' } ]; const tabContents = {}; tabs.forEach((tab, index) => { const tabElement = document.createElement('div'); tabElement.id = tab.id; tabElement.textContent = tab.text; tabElement.style.padding = '8px 12px'; tabElement.style.cursor = 'pointer'; tabElement.style.flex = '1'; tabElement.style.textAlign = 'center'; tabElement.style.borderBottom = index === 0 ? '2px solid #28a745' : 'none'; tabElement.style.color = index === 0 ? '#28a745' : '#ccc'; // Create content div for this tab const contentDiv = document.createElement('div'); contentDiv.id = `${tab.id}-content`; contentDiv.style.display = index === 0 ? 'block' : 'none'; tabContents[tab.id] = contentDiv; tabElement.addEventListener('click', function() { // Update tab styles tabs.forEach(t => { const tabEl = document.getElementById(t.id); if (tabEl) { tabEl.style.borderBottom = 'none'; tabEl.style.color = '#ccc'; } const contentEl = document.getElementById(`${t.id}-content`); if (contentEl) { contentEl.style.display = 'none'; } }); // Highlight active tab tabElement.style.borderBottom = '2px solid #28a745'; tabElement.style.color = '#28a745'; // Show active content const contentEl = document.getElementById(`${tab.id}-content`); if (contentEl) { contentEl.style.display = 'block'; } }); tabContainer.appendChild(tabElement); }); panel.appendChild(tabContainer); // Flight Patterns Tab Content const flightPatternsContent = tabContents['flight-patterns-tab']; // Add pattern selection const patternLabel = document.createElement('div'); patternLabel.textContent = 'Select Flight Pattern:'; patternLabel.style.marginBottom = '8px'; patternLabel.style.fontSize = '14px'; flightPatternsContent.appendChild(patternLabel); const patternSelect = document.createElement('select'); patternSelect.id = 'direct-control-pattern'; patternSelect.style.width = '100%'; patternSelect.style.marginBottom = '15px'; patternSelect.style.padding = '6px'; patternSelect.style.borderRadius = '4px'; patternSelect.style.backgroundColor = '#333'; patternSelect.style.color = '#fff'; patternSelect.style.border = '1px solid #555'; // Add pattern options with descriptions const patternDescriptions = { 'takeoff': 'Standard takeoff from runway', 'rightTurn': 'Right turn at cruise altitude', 'descent': 'Descent from cruise altitude', 'approach': 'Approach to landing', 'landing': 'Final approach and landing', 'holdingPattern': 'Standard holding pattern' }; // Add pattern options const patterns = Object.keys(flightPatterns); patterns.forEach(pattern => { const option = document.createElement('option'); option.value = pattern; option.textContent = pattern.charAt(0).toUpperCase() + pattern.slice(1); patternSelect.appendChild(option); }); flightPatternsContent.appendChild(patternSelect); // Add pattern description const patternDescription = document.createElement('div'); patternDescription.id = 'pattern-description'; patternDescription.style.fontSize = '12px'; patternDescription.style.marginBottom = '15px'; patternDescription.style.padding = '8px'; patternDescription.style.backgroundColor = 'rgba(255, 255, 255, 0.1)'; patternDescription.style.borderRadius = '4px'; patternDescription.textContent = patternDescriptions['takeoff'] || ''; flightPatternsContent.appendChild(patternDescription); // Update description when pattern changes patternSelect.addEventListener('change', function() { const descriptionElement = document.getElementById('pattern-description'); if (descriptionElement) { descriptionElement.textContent = patternDescriptions[this.value] || ''; } }); // Add buttons container const buttonsContainer = document.createElement('div'); buttonsContainer.style.display = 'flex'; buttonsContainer.style.justifyContent = 'space-between'; buttonsContainer.style.marginBottom = '15px'; // Start button const startButton = document.createElement('button'); startButton.id = 'direct-control-start'; startButton.style.padding = '8px 12px'; startButton.style.borderRadius = '4px'; startButton.style.backgroundColor = '#28a745'; startButton.style.color = '#fff'; startButton.style.border = 'none'; startButton.style.cursor = 'pointer'; startButton.style.flex = '1'; startButton.style.marginRight = '8px'; startButton.style.fontWeight = 'bold'; startButton.textContent = 'Start'; startButton.addEventListener('click', function() { const patternSelect = document.getElementById('direct-control-pattern'); const selectedPattern = patternSelect ? patternSelect.value : 'takeoff'; startReplay(selectedPattern); }); buttonsContainer.appendChild(startButton); // Pause button const pauseButton = document.createElement('button'); pauseButton.id = 'direct-control-pause'; pauseButton.style.padding = '8px 12px'; pauseButton.style.borderRadius = '4px'; pauseButton.style.backgroundColor = '#ffc107'; pauseButton.style.color = '#000'; pauseButton.style.border = 'none'; pauseButton.style.cursor = 'pointer'; pauseButton.style.flex = '1'; pauseButton.style.marginRight = '8px'; pauseButton.style.fontWeight = 'bold'; pauseButton.textContent = 'Pause'; pauseButton.disabled = true; pauseButton.addEventListener('click', togglePause); buttonsContainer.appendChild(pauseButton); // Stop button const stopButton = document.createElement('button'); stopButton.id = 'direct-control-stop'; stopButton.style.padding = '8px 12px'; stopButton.style.borderRadius = '4px'; stopButton.style.backgroundColor = '#dc3545'; stopButton.style.color = '#fff'; stopButton.style.border = 'none'; stopButton.style.cursor = 'pointer'; stopButton.style.flex = '1'; stopButton.style.fontWeight = 'bold'; stopButton.textContent = 'Stop'; stopButton.disabled = true; stopButton.addEventListener('click', stopReplay); buttonsContainer.appendChild(stopButton); flightPatternsContent.appendChild(buttonsContainer); // Add progress bar const progressContainer = document.createElement('div'); progressContainer.style.width = '100%'; progressContainer.style.backgroundColor = '#333'; progressContainer.style.borderRadius = '4px'; progressContainer.style.overflow = 'hidden'; progressContainer.style.height = '10px'; progressContainer.style.marginBottom = '10px'; const progressBar = document.createElement('div'); progressBar.id = 'direct-control-progress'; progressBar.style.width = '0%'; progressBar.style.height = '100%'; progressBar.style.backgroundColor = '#28a745'; progressBar.style.transition = 'width 0.5s ease'; progressContainer.appendChild(progressBar); flightPatternsContent.appendChild(progressContainer); // Add status display const statusDisplay = document.createElement('div'); statusDisplay.id = 'direct-control-status'; statusDisplay.style.marginTop = '10px'; statusDisplay.style.fontSize = '12px'; statusDisplay.style.textAlign = 'center'; statusDisplay.style.padding = '5px'; statusDisplay.style.backgroundColor = 'rgba(0, 0, 0, 0.3)'; statusDisplay.style.borderRadius = '4px'; statusDisplay.textContent = 'Ready'; flightPatternsContent.appendChild(statusDisplay); // Flight Data Tab Content const flightDataContent = tabContents['flight-data-tab']; // Create flight data display const createDataRow = (label, id) => { const row = document.createElement('div'); row.style.display = 'flex'; row.style.justifyContent = 'space-between'; row.style.marginBottom = '8px'; row.style.fontSize = '13px'; const labelElement = document.createElement('div'); labelElement.textContent = label; labelElement.style.flex = '1'; const valueElement = document.createElement('div'); valueElement.id = id; valueElement.textContent = '—'; valueElement.style.flex = '1'; valueElement.style.textAlign = 'right'; valueElement.style.fontFamily = 'monospace'; row.appendChild(labelElement); row.appendChild(valueElement); return row; }; const flightDataSection = document.createElement('div'); flightDataSection.style.marginBottom = '15px'; const positionTitle = document.createElement('div'); positionTitle.textContent = 'Position'; positionTitle.style.fontWeight = 'bold'; positionTitle.style.marginBottom = '5px'; positionTitle.style.borderBottom = '1px solid #444'; flightDataSection.appendChild(positionTitle); flightDataSection.appendChild(createDataRow('Latitude:', 'data-latitude')); flightDataSection.appendChild(createDataRow('Longitude:', 'data-longitude')); flightDataSection.appendChild(createDataRow('Altitude (m):', 'data-altitude')); const attitudeTitle = document.createElement('div'); attitudeTitle.textContent = 'Attitude'; attitudeTitle.style.fontWeight = 'bold'; attitudeTitle.style.marginTop = '10px'; attitudeTitle.style.marginBottom = '5px'; attitudeTitle.style.borderBottom = '1px solid #444'; flightDataSection.appendChild(attitudeTitle); flightDataSection.appendChild(createDataRow('Heading (°):', 'data-heading')); flightDataSection.appendChild(createDataRow('Pitch (°):', 'data-pitch')); flightDataSection.appendChild(createDataRow('Roll (°):', 'data-roll')); const speedTitle = document.createElement('div'); speedTitle.textContent = 'Speed'; speedTitle.style.fontWeight = 'bold'; speedTitle.style.marginTop = '10px'; speedTitle.style.marginBottom = '5px'; speedTitle.style.borderBottom = '1px solid #444'; flightDataSection.appendChild(speedTitle); flightDataSection.appendChild(createDataRow('Airspeed (KIAS):', 'data-kias')); flightDataSection.appendChild(createDataRow('Ground Speed (kt):', 'data-gs')); flightDataSection.appendChild(createDataRow('Vertical Speed (ft/min):', 'data-vs')); const controlsTitle = document.createElement('div'); controlsTitle.textContent = 'Controls'; controlsTitle.style.fontWeight = 'bold'; controlsTitle.style.marginTop = '10px'; controlsTitle.style.marginBottom = '5px'; controlsTitle.style.borderBottom = '1px solid #444'; flightDataSection.appendChild(controlsTitle); flightDataSection.appendChild(createDataRow('Throttle:', 'data-throttle')); flightDataSection.appendChild(createDataRow('Flaps:', 'data-flaps')); flightDataSection.appendChild(createDataRow('Gear:', 'data-gear')); flightDataContent.appendChild(flightDataSection); // Connection Tab Content const connectionContent = tabContents['connection-tab']; // Connection status const connectionStatus = document.createElement('div'); connectionStatus.id = 'connection-status'; connectionStatus.style.padding = '10px'; connectionStatus.style.marginBottom = '15px'; connectionStatus.style.backgroundColor = '#333'; connectionStatus.style.borderRadius = '4px'; connectionStatus.style.textAlign = 'center'; connectionStatus.innerHTML = '<span style="color: #dc3545;">●</span> Disconnected'; connectionContent.appendChild(connectionStatus); // Connect to MCP Server button const connectButton = document.createElement('button'); connectButton.id = 'connect-mcp-button'; connectButton.style.width = '100%'; connectButton.style.padding = '8px 12px'; connectButton.style.borderRadius = '4px'; connectButton.style.backgroundColor = '#007bff'; connectButton.style.color = '#fff'; connectButton.style.border = 'none'; connectButton.style.cursor = 'pointer'; connectButton.style.marginBottom = '15px'; connectButton.style.fontWeight = 'bold'; connectButton.textContent = 'Connect to MCP Server'; connectButton.addEventListener('click', function() { sendToMCPServer(); // Update connection status const connectionStatus = document.getElementById('connection-status'); if (connectionStatus) { connectionStatus.innerHTML = '<span style="color: #ffc107;">●</span> Connecting to MCP Server...'; } // Disable connect button this.disabled = true; }); connectionContent.appendChild(connectButton); // Server info const serverInfo = document.createElement('div'); serverInfo.style.fontSize = '12px'; serverInfo.style.lineHeight = '1.5'; serverInfo.innerHTML = ` <div style="font-weight: bold; margin-bottom: 5px;">Server Information:</div> <div>URL: ws://localhost:3002</div> <div>Protocol: WebSocket</div> <div style="margin-top: 10px; font-style: italic;"> The MCP server must be running for the connection to succeed. </div> `; connectionContent.appendChild(serverInfo); // GPT Control Tab Content const gptControlContent = tabContents['gpt-tab']; // Create input area const inputContainer = document.createElement('div'); inputContainer.className = 'gpt-input-container'; const textInput = document.createElement('textarea'); textInput.id = 'gpt-command-input'; textInput.placeholder = 'Enter a natural language command for the aircraft (e.g., "Take off from runway 3 at Luxembourg airport")'; textInput.style.width = '100%'; textInput.style.height = '80px'; textInput.style.padding = '8px'; textInput.style.marginBottom = '10px'; textInput.style.borderRadius = '4px'; textInput.style.border = '1px solid #ccc'; const submitButton = document.createElement('button'); submitButton.textContent = 'Send to GPT'; submitButton.className = 'control-button'; submitButton.style.backgroundColor = '#4CAF50'; submitButton.style.color = 'white'; submitButton.style.padding = '10px 15px'; submitButton.style.border = 'none'; submitButton.style.borderRadius = '4px'; submitButton.style.cursor = 'pointer'; submitButton.onclick = async () => { const command = textInput.value; if (!command.trim()) return; // Show loading state const responseArea = document.getElementById('gpt-response-area'); responseArea.innerHTML = '<div class="loading">Processing your request...</div>'; // Send command to server try { const response = await fetch('/gpt-command', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ command }), }); if (!response.ok) { throw new Error(`Server responded with ${response.status}`); } const data = await response.json(); // Display the response responseArea.innerHTML = ` <div class="gpt-response"> <h4>GPT Response:</h4> <div class="response-content">${formatGptResponse(data.response)}</div> </div> `; } catch (error) { responseArea.innerHTML = ` <div class="gpt-error"> <h4>Error:</h4> <div>${error.message}</div> </div> `; } }; inputContainer.appendChild(textInput); inputContainer.appendChild(submitButton); // Create response area const responseArea = document.createElement('div'); responseArea.id = 'gpt-response-area'; responseArea.className = 'gpt-response-area'; responseArea.style.marginTop = '20px'; responseArea.style.padding = '10px'; responseArea.style.backgroundColor = '#f8f9fa'; responseArea.style.borderRadius = '4px'; responseArea.style.minHeight = '100px'; // Add API key input const apiKeyContainer = document.createElement('div'); apiKeyContainer.className = 'api-key-container'; apiKeyContainer.style.marginTop = '20px'; const apiKeyLabel = document.createElement('label'); apiKeyLabel.htmlFor = 'openai-api-key'; apiKeyLabel.textContent = 'OpenAI API Key (optional):'; const apiKeyInput = document.createElement('input'); apiKeyInput.type = 'password'; apiKeyInput.id = 'openai-api-key'; apiKeyInput.placeholder = 'Enter your OpenAI API key'; apiKeyInput.style.width = '100%'; apiKeyInput.style.padding = '8px'; apiKeyInput.style.marginTop = '5px'; apiKeyInput.style.borderRadius = '4px'; apiKeyInput.style.border = '1px solid #ccc'; const saveKeyButton = document.createElement('button'); saveKeyButton.textContent = 'Save API Key'; saveKeyButton.className = 'control-button'; saveKeyButton.style.marginTop = '5px'; saveKeyButton.onclick = () => { const apiKey = apiKeyInput.value.trim(); if (apiKey) { // Save API key to localStorage localStorage.setItem('openai-api-key', apiKey); alert('API key saved!'); // Send API key to server fetch('/set-api-key', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ apiKey }), }).catch(error => console.error('Error saving API key:', error)); } }; apiKeyContainer.appendChild(apiKeyLabel); apiKeyContainer.appendChild(apiKeyInput); apiKeyContainer.appendChild(saveKeyButton); // Add note about API key const apiKeyNote = document.createElement('div'); apiKeyNote.className = 'api-key-note'; apiKeyNote.style.marginTop = '10px'; apiKeyNote.style.fontSize = '12px'; apiKeyNote.style.color = '#666'; apiKeyNote.innerHTML = 'Note: Your API key is stored locally in your browser. If no API key is provided, the server will use simulated responses.'; apiKeyContainer.appendChild(apiKeyNote); // Add everything to the tab content gptControlContent.appendChild(inputContainer); gptControlContent.appendChild(responseArea); gptControlContent.appendChild(apiKeyContainer); // Add all tab contents to panel Object.values(tabContents).forEach(content => { panel.appendChild(content); }); document.body.appendChild(panel); console.log('Added enhanced control panel to UI'); // Update flight data display periodically setInterval(function() { if (typeof geofs !== 'undefined' && geofs.aircraft && geofs.aircraft.instance) { const aircraft = geofs.aircraft.instance; // Update position data if (aircraft.llaLocation) { const latElement = document.getElementById('data-latitude'); if (latElement) latElement.textContent = aircraft.llaLocation[0].toFixed(6); const lonElement = document.getElementById('data-longitude'); if (lonElement) lonElement.textContent = aircraft.llaLocation[1].toFixed(6); const altElement = document.getElementById('data-altitude'); if (altElement) altElement.textContent = aircraft.llaLocation[2].toFixed(1); } // Update attitude data if (aircraft.htr) { const headingElement = document.getElementById('data-heading'); if (headingElement) headingElement.textContent = aircraft.htr[0].toFixed(1); const pitchElement = document.getElementById('data-pitch'); if (pitchElement) pitchElement.textContent = aircraft.htr[1].toFixed(1); const rollElement = document.getElementById('data-roll'); if (rollElement) rollElement.textContent = aircraft.htr[2].toFixed(1); } // Update speed data if (aircraft.animationValue && aircraft.animationValue.kias) { const kiasElement = document.getElementById('data-kias'); if (kiasElement) kiasElement.textContent = aircraft.animationValue.kias.toFixed(1); } if (typeof aircraft.groundSpeed !== 'undefined') { const gsElement = document.getElementById('data-gs'); if (gsElement) gsElement.textContent = aircraft.groundSpeed.toFixed(1); } if (typeof aircraft.verticalSpeed !== 'undefined') { const vsElement = document.getElementById('data-vs'); if (vsElement) vsElement.textContent = (aircraft.verticalSpeed * 196.85).toFixed(0); // Convert to ft/min } // Update control data if (aircraft.engines && aircraft.engines.length > 0) { const throttleElement = document.getElementById('data-throttle'); if (throttleElement) throttleElement.textContent = aircraft.engines[0].throttle.toFixed(2); } if (aircraft.controls && typeof aircraft.controls.flaps !== 'undefined') { const flapsElement = document.getElementById('data-flaps'); if (flapsElement) flapsElement.textContent = aircraft.controls.flaps.toFixed(2); } if (typeof aircraft.gearPosition !== 'undefined') { const gearElement = document.getElementById('data-gear'); if (gearElement) gearElement.textContent = aircraft.gearPosition.toFixed(2); } } // Update status display and progress bar const statusDisplay = document.getElementById('direct-control-status'); const progressBar = document.getElementById('direct-control-progress'); if (statusDisplay) { if (updateInterval === null) { statusDisplay.textContent = 'Ready'; if (progressBar) progressBar.style.width = '0%'; } else if (isPaused) { statusDisplay.textContent = `Paused at step ${currentIndex + 1}/${flightPatterns[currentPattern].length}`; if (progressBar) { const progress = ((currentIndex + 1) / flightPatterns[currentPattern].length) * 100; progressBar.style.width = `${progress}%`; } } else { statusDisplay.textContent = `Running: ${currentIndex + 1}/${flightPatterns[currentPattern].length}`; if (progressBar) { const progress = ((currentIndex + 1) / flightPatterns[currentPattern].length) * 100; progressBar.style.width = `${progress}%`; } } } }, 500); } // Send flight data to MCP server if WebSocket is available function sendToMCPServer() { try { // Check if WebSocket is defined in window if (typeof WebSocket !== 'undefined') { // Update connection status UI const connectionStatus = document.getElementById('connection-status'); if (connectionStatus) { connectionStatus.innerHTML = '<span style="color: #ffc107;">●</span> Connecting to MCP Server...'; } // Create WebSocket connection const ws = new WebSocket('ws://localhost:3002'); let connectionActive = false; let reconnectAttempts = 0; let reconnectInterval = null; ws.onopen = function() { console.log('Connected to MCP server'); connectionActive = true; reconnectAttempts = 0; // Update connection status in UI if (connectionStatus) { connectionStatus.innerHTML = '<span style="color: #28a745;">●</span> Connected to MCP Server'; } // Disable connect button const connectButton = document.getElementById('connect-mcp-button'); if (connectButton) { connectButton.disabled = true; } // Send initial aircraft state sendCurrentAircraftState(ws); // Set up interval to send data periodically const dataInterval = setInterval(function() { if (ws.readyState === WebSocket.OPEN) { sendCurrentAircraftState(ws); } else { clearInterval(dataInterval); } }, 1000); // Send data every second }; ws.onmessage = function(event) { try { const message = JSON.parse(event.data); console.log('Received message from MCP server:', message); // Handle different message types if (message.type === 'command') { console.log(`Executing command: ${message.command}`); executeCommand(message.command, message.params); } else if (message.type === 'ping') { // Respond to ping with pong ws.send(JSON.stringify({ type: 'pong', timestamp: Date.now() })); } } catch (error) { console.error('Error processing message from server:', error); } }; ws.onerror = function(error) { console.error('WebSocket error:', error); // Update connection status in UI if (connectionStatus) { connectionStatus.innerHTML = '<span style="color: #dc3545;">●</span> Connection Error'; } }; ws.onclose = function(event) { console.log(`Disconnected from MCP server: ${event.code} ${event.reason}`); connectionActive = false; // Update connection status in UI if (connectionStatus) { connectionStatus.innerHTML = '<span style="color: #dc3545;">●</span> Disconnected'; } // Enable connect button const connectButton = document.getElementById('connect-mcp-button'); if (connectButton) { connectButton.disabled = false; } // Attempt to reconnect if (reconnectAttempts < 5) { const reconnectDelay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000); console.log(`Attempting to reconnect in ${reconnectDelay/1000} seconds...`); if (connectionStatus) { connectionStatus.innerHTML = `<span style="color: #ffc107;">●</span> Reconnecting in ${reconnectDelay/1000}s (${reconnectAttempts + 1}/5)`; } reconnectAttempts++; if (reconnectInterval) { clearTimeout(reconnectInterval); } reconnectInterval = setTimeout(function() { if (!connectionActive) { sendToMCPServer(); } }, reconnectDelay); } else { console.log('Maximum reconnection attempts reached'); if (connectionStatus) { connectionStatus.innerHTML = '<span style="color: #dc3545;">●</span> Failed to connect after 5 attempts'; } } }; // Function to send current aircraft state function sendCurrentAircraftState(ws) { try { if (typeof geofs !== 'undefined' && geofs.aircraft && geofs.aircraft.instance) { const aircraft = geofs.aircraft.instance; // Create comprehensive flight data object const flightData = { position: { latitude: aircraft.llaLocation ? aircraft.llaLocation[0] : 0, longitude: aircraft.llaLocation ? aircraft.llaLocation[1] : 0, altitude: aircraft.llaLocation ? aircraft.llaLocation[2] : 0 }, attitude: { heading: aircraft.htr ? aircraft.htr[0] : 0, pitch: aircraft.htr ? aircraft.htr[1] : 0, roll: aircraft.htr ? aircraft.htr[2] : 0 }, speed: { kias: aircraft.animationValue && aircraft.animationValue.kias ? aircraft.animationValue.kias : 0, groundSpeed: typeof aircraft.groundSpeed !== 'undefined' ? aircraft.groundSpeed : 0, verticalSpeed: typeof aircraft.verticalSpeed !== 'undefined' ? aircraft.verticalSpeed : 0 }, controls: { throttle: aircraft.engines && aircraft.engines.length > 0 ? aircraft.engines[0].throttle : 0, flaps: aircraft.controls && typeof aircraft.controls.flaps !== 'undefined' ? aircraft.controls.flaps : 0, gear: typeof aircraft.gearPosition !== 'undefined' ? aircraft.gearPosition : 0 }, aircraft: { type: 'Boeing 787', id: geofs.aircraft.instance.id || 3575, name: 'Boeing 787-9 Dreamliner' }, timestamp: Date.now() }; // Send data to server ws.send(JSON.stringify({ type: 'flightData', data: flightData })); // Update the flight data log in the UI updateFlightDataLog(flightData); } } catch (error) { console.error('Error sending aircraft state:', error); } } // Function to execute commands received from the server function executeCommand(command, params) { try { console.log(`Executing command: ${command} with params:`, params); switch (command) { case 'setThrottle': if (params && typeof params.value !== 'undefined' && geofs.aircraft.instance.engines && geofs.aircraft.instance.engines.length > 0) { geofs.aircraft.instance.engines.forEach(engine => { engine.throttle = Math.max(0, Math.min(1, params.value)); }); console.log(`Throttle set to ${params.value}`); } break; case 'setFlaps': if (params && typeof params.value !== 'undefined' && geofs.aircraft.instance.controls && typeof geofs.aircraft.instance.controls.flaps !== 'undefined') { geofs.aircraft.instance.controls.flaps = Math.max(0, Math.min(1, params.value)); console.log(`Flaps set to ${params.value}`); } break; case 'setGear': if (params && typeof params.value !== 'undefined' && typeof geofs.aircraft.instance.gearPosition !== 'undefined') { geofs.aircraft.instance.gearPosition = Math.max(0, Math.min(1, params.value)); console.log(`Gear set to ${params.value}`); } break; case 'startPattern': if (params && typeof params.pattern !== 'undefined' && flightPatterns[params.pattern]) { startReplay(params.pattern); console.log(`Started flight pattern: ${params.pattern}`); } break; case 'pauseReplay': togglePause(); console.log('Toggled pause state'); break; case 'stopReplay': stopReplay(); console.log('Stopped replay'); break; default: console.warn(`Unknown command: ${command}`); } } catch (error) { console.error(`Error executing command ${command}:`, error); } } // Function to update flight data log in the UI function updateFlightDataLog(flightData) { const logContainer = document.getElementById('flight-data-log'); if (logContainer) { // Create log entry const logEntry = document.createElement('div'); logEntry.style.fontSize = '11px'; logEntry.style.marginBottom = '5px'; logEntry.style.borderBottom = '1px dotted #444'; logEntry.style.paddingBottom = '5px'; const timestamp = new Date().toLocaleTimeString(); logEntry.innerHTML = ` <div><strong>${timestamp}</strong></div> <div>Pos: ${flightData.position.latitude.toFixed(4)}, ${flightData.position.longitude.toFixed(4)}, ${flightData.position.altitude.toFixed(1)}m</div> <div>Spd: ${flightData.speed.kias.toFixed(1)} KIAS, VS: ${(flightData.speed.verticalSpeed * 196.85).toFixed(0)} ft/min</div> `; // Add to the top of the log if (logContainer.firstChild) { logContainer.insertBefore(logEntry, logContainer.firstChild); } else { logContainer.appendChild(logEntry); } // Limit the number of log entries while (logContainer.childNodes.length > 10) { logContainer.removeChild(logContainer.lastChild); } } } // Add a flight data log to the Connection tab const connectionTab = document.getElementById('connection-tab-content'); if (connectionTab) { const logTitle = document.createElement('div'); logTitle.textContent = 'Flight Data Log'; logTitle.style.fontWeight = 'bold'; logTitle.style.marginTop = '20px'; logTitle.style.marginBottom = '10px'; logTitle.style.borderBottom = '1px solid #444'; connectionTab.appendChild(logTitle); const logContainer = document.createElement('div'); logContainer.id = 'flight-data-log'; logContainer.style.maxHeight = '200px'; logContainer.style.overflowY = 'auto'; logContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.2)'; logContainer.style.padding = '8px'; logContainer.style.borderRadius = '4px'; connectionTab.appendChild(logContainer); } } else { console.warn('WebSocket not available in this browser'); // Update connection status const connectionStatus = document.getElementById('connection-status'); if (connectionStatus) { connectionStatus.innerHTML = '<span style="color: #dc3545;">●</span> WebSocket not supported in this browser'; } } } catch (error) { console.error('Error connecting to MCP server:', error); // Update connection status const connectionStatus = document.getElementById('connection-status'); if (connectionStatus) { connectionStatus.innerHTML = `<span style="color: #dc3545;">●</span> Connection Error: ${error.message}`; } } } // Format GPT response with markdown-like syntax function formatGptResponse(text) { // Escape HTML const escaped = text.replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;'); // Add some basic formatting return escaped .replace(/\n\n/g, '<br><br>') .replace(/\n/g, '<br>') .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>') .replace(/\*(.*?)\*/g, '<em>$1</em>') .replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>'); } // Initialize addControlPanel(); console.log('GeoFS Direct Control initialized. Use the control panel to manage flight patterns.'); // Expose API to window for debugging window.directControl = { startReplay, togglePause, stopReplay, updateAircraftState, sendToMCPServer, flightPatterns }; })();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/lobstercare/geofs-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server