Download index.html from nkellil/drone1: direct link, hf CLI and curl.
- Browser
- Download file 40.3 kB
-
https://huggingface.co/spaces/nkellil/drone1/resolve/main/index.html
- Command line
-
hf download hf://spaces/nkellil/drone1/index.html
-
curl -L -o index.html https://huggingface.co/spaces/nkellil/drone1/resolve/main/index.html
40.3 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>ThermoDrone - Photovoltaic Plant Monitoring</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCGNXE_QanVIz3XUFL0EDgYn2ZQ1QQhG1U&libraries=visualization,geometry&callback=initMap" async defer></script> | |
| <style> | |
| #map { | |
| height: 100vh; | |
| width: 100%; | |
| } | |
| .thermal-gradient { | |
| background: linear-gradient(to right, #000000, #4000ff, #00ffff, #00ff00, #ffff00, #ff0000); | |
| height: 20px; | |
| border-radius: 4px; | |
| } | |
| .telemetry-value { | |
| font-family: 'Courier New', monospace; | |
| font-weight: bold; | |
| } | |
| .sidebar { | |
| transition: all 0.3s ease; | |
| } | |
| .sidebar.collapsed { | |
| width: 60px; | |
| } | |
| .sidebar.collapsed .sidebar-content { | |
| opacity: 0; | |
| pointer-events: none; | |
| } | |
| .defect-marker { | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { transform: scale(1); opacity: 0.7; } | |
| 50% { transform: scale(1.2); opacity: 1; } | |
| 100% { transform: scale(1); opacity: 0.7; } | |
| } | |
| #image-modal { | |
| transition: opacity 0.3s ease; | |
| } | |
| .modal-content { | |
| max-height: 90vh; | |
| } | |
| .active { | |
| background-color: #3b82f6; | |
| color: white; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 h-screen flex overflow-hidden"> | |
| <!-- Sidebar --> | |
| <div id="sidebar" class="sidebar bg-gray-800 text-white w-64 flex flex-col"> | |
| <div class="p-4 flex items-center justify-between border-b border-gray-700"> | |
| <div class="flex items-center space-x-2"> | |
| <i class="fas fa-drone-alt text-blue-400 text-2xl"></i> | |
| <h1 class="text-xl font-bold">ThermoDrone</h1> | |
| </div> | |
| <button id="toggle-sidebar" class="text-gray-400 hover:text-white"> | |
| <i class="fas fa-chevron-left"></i> | |
| </button> | |
| </div> | |
| <div class="sidebar-content flex-1 overflow-y-auto p-4 space-y-6"> | |
| <!-- Connection Panel --> | |
| <div class="bg-gray-700 rounded-lg p-4"> | |
| <h2 class="text-lg font-semibold mb-3 flex items-center"> | |
| <i class="fas fa-plug mr-2 text-blue-400"></i> | |
| Connection | |
| </h2> | |
| <div class="space-y-3"> | |
| <div> | |
| <label class="block text-sm font-medium mb-1">Serial Port</label> | |
| <select id="com-port" class="w-full bg-gray-600 border border-gray-500 rounded px-3 py-2 text-sm"> | |
| <option value="">Select COM Port</option> | |
| <!-- Ports will be populated by JavaScript --> | |
| </select> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium mb-1">Baud Rate</label> | |
| <select id="baud-rate" class="w-full bg-gray-600 border border-gray-500 rounded px-3 py-2 text-sm"> | |
| <option value="57600">57600</option> | |
| <option value="115200" selected>115200</option> | |
| <option value="921600">921600</option> | |
| </select> | |
| </div> | |
| <button id="connect-btn" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 rounded flex items-center justify-center"> | |
| <i class="fas fa-link mr-2"></i> Connect | |
| </button> | |
| <button id="disconnect-btn" class="w-full bg-gray-600 hover:bg-gray-700 text-white py-2 rounded flex items-center justify-center hidden"> | |
| <i class="fas fa-unlink mr-2"></i> Disconnect | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Mission Planning --> | |
| <div class="bg-gray-700 rounded-lg p-4"> | |
| <h2 class="text-lg font-semibold mb-3 flex items-center"> | |
| <i class="fas fa-map-marked-alt mr-2 text-green-400"></i> | |
| Mission Planning | |
| </h2> | |
| <div class="space-y-3"> | |
| <div class="flex space-x-2"> | |
| <button id="add-waypoint-btn" class="flex-1 bg-green-600 hover:bg-green-700 text-white py-2 rounded flex items-center justify-center text-sm"> | |
| <i class="fas fa-plus mr-1"></i> Add Waypoint | |
| </button> | |
| <button id="clear-waypoints-btn" class="flex-1 bg-red-600 hover:bg-red-700 text-white py-2 rounded flex items-center justify-center text-sm"> | |
| <i class="fas fa-trash mr-1"></i> Clear All | |
| </button> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium mb-1">Altitude (m)</label> | |
| <input type="number" id="waypoint-altitude" value="50" min="10" max="200" class="w-full bg-gray-600 border border-gray-500 rounded px-3 py-2 text-sm"> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium mb-1">Speed (m/s)</label> | |
| <input type="number" id="waypoint-speed" value="5" min="1" max="15" class="w-full bg-gray-600 border border-gray-500 rounded px-3 py-2 text-sm"> | |
| </div> | |
| <button id="upload-mission-btn" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 rounded flex items-center justify-center"> | |
| <i class="fas fa-upload mr-2"></i> Upload Mission | |
| </button> | |
| <button id="start-mission-btn" class="w-full bg-green-600 hover:bg-green-700 text-white py-2 rounded flex items-center justify-center hidden"> | |
| <i class="fas fa-play mr-2"></i> Start Mission | |
| </button> | |
| <button id="pause-mission-btn" class="w-full bg-yellow-600 hover:bg-yellow-700 text-white py-2 rounded flex items-center justify-center hidden"> | |
| <i class="fas fa-pause mr-2"></i> Pause Mission | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Thermal Analysis --> | |
| <div class="bg-gray-700 rounded-lg p-4"> | |
| <h2 class="text-lg font-semibold mb-3 flex items-center"> | |
| <i class="fas fa-thermometer-half mr-2 text-red-400"></i> | |
| Thermal Analysis | |
| </h2> | |
| <div class="space-y-3"> | |
| <div class="thermal-gradient rounded-full mb-2"></div> | |
| <div class="flex justify-between text-xs"> | |
| <span>20°C</span> | |
| <span>40°C</span> | |
| <span>60°C</span> | |
| <span>80°C</span> | |
| <span>100°C</span> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium mb-1">Threshold (°C)</label> | |
| <input type="number" id="thermal-threshold" value="70" min="30" max="120" class="w-full bg-gray-600 border border-gray-500 rounded px-3 py-2 text-sm"> | |
| </div> | |
| <div class="flex items-center"> | |
| <input type="checkbox" id="enable-yolo" class="mr-2" checked> | |
| <label for="enable-yolo" class="text-sm">Enable Defect Detection</label> | |
| </div> | |
| <button id="analyze-btn" class="w-full bg-purple-600 hover:bg-purple-700 text-white py-2 rounded flex items-center justify-center"> | |
| <i class="fas fa-search mr-2"></i> Analyze Images | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="p-4 border-t border-gray-700"> | |
| <div class="text-xs text-gray-400"> | |
| <p>MAVLink Connected: <span id="mavlink-status" class="text-red-500">No</span></p> | |
| <p>Drone Status: <span id="drone-status" class="text-gray-300">Disconnected</span></p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Main Content --> | |
| <div class="flex-1 flex flex-col overflow-hidden"> | |
| <!-- Top Bar --> | |
| <div class="bg-gray-800 text-white p-3 flex items-center justify-between"> | |
| <div class="flex items-center space-x-4"> | |
| <button id="mobile-menu-btn" class="md:hidden text-gray-300 hover:text-white"> | |
| <i class="fas fa-bars"></i> | |
| </button> | |
| <h2 class="text-lg font-semibold">Photovoltaic Plant Monitoring</h2> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <div class="flex items-center space-x-2"> | |
| <i class="fas fa-battery-three-quarters text-green-400"></i> | |
| <span id="battery-level" class="text-sm">--%</span> | |
| </div> | |
| <div class="flex items-center space-x-2"> | |
| <i class="fas fa-satellite-dish text-blue-400"></i> | |
| <span id="satellites" class="text-sm">--</span> | |
| </div> | |
| <div class="flex items-center space-x-2"> | |
| <i class="fas fa-map-marker-alt text-yellow-400"></i> | |
| <span id="gps-coords" class="text-sm">--, --</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Map and Telemetry Area --> | |
| <div class="flex-1 flex flex-col md:flex-row overflow-hidden"> | |
| <!-- Map Container --> | |
| <div class="flex-1 relative"> | |
| <div id="map" class="absolute inset-0"></div> | |
| <!-- Map Controls --> | |
| <div class="absolute top-4 right-4 space-y-2 z-10"> | |
| <button id="satellite-btn" class="bg-white p-2 rounded-full shadow hover:bg-gray-100" title="Satellite View"> | |
| <i class="fas fa-satellite text-gray-800"></i> | |
| </button> | |
| <button id="terrain-btn" class="bg-white p-2 rounded-full shadow hover:bg-gray-100" title="Terrain View"> | |
| <i class="fas fa-mountain text-gray-800"></i> | |
| </button> | |
| <button id="thermal-overlay-btn" class="bg-white p-2 rounded-full shadow hover:bg-gray-100" title="Thermal Overlay"> | |
| <i class="fas fa-fire text-gray-800"></i> | |
| </button> | |
| </div> | |
| <!-- Mission Progress --> | |
| <div class="absolute bottom-4 left-4 bg-white bg-opacity-90 p-3 rounded shadow z-10"> | |
| <h3 class="font-semibold text-sm mb-1">Mission Progress</h3> | |
| <div class="w-full bg-gray-200 rounded-full h-2.5"> | |
| <div id="mission-progress" class="bg-blue-600 h-2.5 rounded-full" style="width: 0%"></div> | |
| </div> | |
| <div class="flex justify-between text-xs mt-1"> | |
| <span>0%</span> | |
| <span id="mission-percentage">0%</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Telemetry Panel --> | |
| <div class="w-full md:w-64 bg-gray-700 text-white overflow-y-auto border-t md:border-t-0 md:border-l border-gray-600"> | |
| <div class="p-4 space-y-6"> | |
| <div> | |
| <h3 class="font-semibold mb-2 flex items-center"> | |
| <i class="fas fa-heartbeat mr-2 text-red-400"></i> | |
| Drone Telemetry | |
| </h3> | |
| <div class="space-y-2"> | |
| <div class="flex justify-between"> | |
| <span class="text-sm text-gray-300">Mode:</span> | |
| <span id="flight-mode" class="telemetry-value">--</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-sm text-gray-300">Altitude:</span> | |
| <span id="altitude" class="telemetry-value">-- m</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-sm text-gray-300">Speed:</span> | |
| <span id="speed" class="telemetry-value">-- m/s</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-sm text-gray-300">Heading:</span> | |
| <span id="heading" class="telemetry-value">--°</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-sm text-gray-300">Distance:</span> | |
| <span id="distance" class="telemetry-value">-- m</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div> | |
| <h3 class="font-semibold mb-2 flex items-center"> | |
| <i class="fas fa-bolt mr-2 text-yellow-400"></i> | |
| Power System | |
| </h3> | |
| <div class="space-y-2"> | |
| <div class="flex justify-between"> | |
| <span class="text-sm text-gray-300">Voltage:</span> | |
| <span id="voltage" class="telemetry-value">-- V</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-sm text-gray-300">Current:</span> | |
| <span id="current" class="telemetry-value">-- A</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-sm text-gray-300">Remaining:</span> | |
| <span id="battery-remaining" class="telemetry-value">--%</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-sm text-gray-300">Flight Time:</span> | |
| <span id="flight-time" class="telemetry-value">--:--</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div> | |
| <h3 class="font-semibold mb-2 flex items-center"> | |
| <i class="fas fa-search mr-2 text-purple-400"></i> | |
| Thermal Defects | |
| </h3> | |
| <div class="space-y-2"> | |
| <div class="flex justify-between"> | |
| <span class="text-sm text-gray-300">Detected:</span> | |
| <span id="defects-count" class="telemetry-value">0</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-sm text-gray-300">Max Temp:</span> | |
| <span id="max-temp" class="telemetry-value">--°C</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-sm text-gray-300">Avg Temp:</span> | |
| <span id="avg-temp" class="telemetry-value">--°C</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-gray-600 rounded p-3"> | |
| <h3 class="font-semibold mb-2 text-sm">Quick Actions</h3> | |
| <div class="grid grid-cols-2 gap-2"> | |
| <button id="rtl-btn" class="bg-yellow-600 hover:bg-yellow-700 text-white py-1 px-2 rounded text-xs"> | |
| <i class="fas fa-home mr-1"></i> RTL | |
| </button> | |
| <button id="loiter-btn" class="bg-blue-600 hover:bg-blue-700 text-white py-1 px-2 rounded text-xs"> | |
| <i class="fas fa-circle-notch mr-1"></i> Loiter | |
| </button> | |
| <button id="land-btn" class="bg-red-600 hover:bg-red-700 text-white py-1 px-2 rounded text-xs"> | |
| <i class="fas fa-arrow-down mr-1"></i> Land | |
| </button> | |
| <button id="takeoff-btn" class="bg-green-600 hover:bg-green-700 text-white py-1 px-2 rounded text-xs"> | |
| <i class="fas fa-arrow-up mr-1"></i> Takeoff | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Image Preview Modal --> | |
| <div id="image-modal" class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 hidden"> | |
| <div class="bg-gray-800 rounded-lg max-w-4xl w-full modal-content overflow-hidden"> | |
| <div class="flex justify-between items-center p-4 border-b border-gray-700"> | |
| <h3 class="text-lg font-semibold">Thermal Image Analysis</h3> | |
| <button id="close-modal" class="text-gray-400 hover:text-white"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="p-4 flex flex-col md:flex-row overflow-auto"> | |
| <div class="w-full md:w-2/3 mb-4 md:mb-0 md:pr-4"> | |
| <img id="modal-image" src="" alt="Thermal Image" class="w-full h-auto rounded max-h-[60vh] object-contain"> | |
| </div> | |
| <div class="w-full md:w-1/3"> | |
| <h4 class="font-semibold mb-2">Defect Details</h4> | |
| <div class="space-y-3"> | |
| <div> | |
| <label class="block text-sm text-gray-300">Module ID:</label> | |
| <p id="module-id" class="font-mono">--</p> | |
| </div> | |
| <div> | |
| <label class="block text-sm text-gray-300">Temperature:</label> | |
| <p id="module-temp" class="font-mono">--°C</p> | |
| </div> | |
| <div> | |
| <label class="block text-sm text-gray-300">Location:</label> | |
| <p id="module-location" class="font-mono">--</p> | |
| </div> | |
| <div> | |
| <label class="block text-sm text-gray-300">Defect Type:</label> | |
| <p id="defect-type" class="font-mono">--</p> | |
| </div> | |
| <div> | |
| <label class="block text-sm text-gray-300">Confidence:</label> | |
| <p id="defect-confidence" class="font-mono">--%</p> | |
| </div> | |
| </div> | |
| <div class="mt-4"> | |
| <button id="export-report-btn" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 rounded"> | |
| <i class="fas fa-file-export mr-2"></i> Export Report | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Initialize Google Maps | |
| let map; | |
| let droneMarker; | |
| let waypoints = []; | |
| let defectMarkers = []; | |
| let flightPath = null; | |
| let telemetrySocket = null; | |
| function initMap() { | |
| try { | |
| // Default to a solar farm location (example: Topaz Solar Farm, California) | |
| const solarFarm = { lat: 36.6625, lng: 2.71986 }; | |
| map = new google.maps.Map(document.getElementById('map'), { | |
| center: solarFarm, | |
| zoom: 17, | |
| mapTypeId: 'satellite', | |
| disableDefaultUI: true, | |
| mapTypeControlOptions: { | |
| mapTypeIds: ['roadmap', 'satellite', 'hybrid', 'terrain'] | |
| } | |
| }); | |
| // Add drone marker | |
| droneMarker = new google.maps.Marker({ | |
| position: solarFarm, | |
| map: map, | |
| icon: { | |
| path: google.maps.SymbolPath.CIRCLE, | |
| fillColor: '#ef4444', | |
| fillOpacity: 1, | |
| strokeColor: 'white', | |
| strokeWeight: 2, | |
| scale: 10 | |
| }, | |
| title: 'Drone', | |
| zIndex: 999 | |
| }); | |
| // Add click listener for waypoints | |
| map.addListener('click', (event) => { | |
| const addWaypointBtn = document.getElementById('add-waypoint-btn'); | |
| if (addWaypointBtn.classList.contains('active')) { | |
| addWaypoint(event.latLng); | |
| } | |
| }); | |
| console.log("Map initialized successfully"); | |
| } catch (error) { | |
| console.error("Error loading map:", error); | |
| alert("Unable to load map. Please check your internet connection and API key."); | |
| } | |
| } | |
| function addWaypoint(location) { | |
| const waypointNumber = waypoints.length + 1; | |
| const altitude = document.getElementById('waypoint-altitude').value; | |
| const marker = new google.maps.Marker({ | |
| position: location, | |
| map: map, | |
| icon: { | |
| path: google.maps.SymbolPath.CIRCLE, | |
| fillColor: '#3b82f6', | |
| fillOpacity: 1, | |
| strokeColor: 'white', | |
| strokeWeight: 2, | |
| scale: 8 | |
| }, | |
| label: { | |
| text: waypointNumber.toString(), | |
| color: 'white', | |
| fontSize: '10px' | |
| }, | |
| title: `Waypoint ${waypointNumber} (${altitude}m)` | |
| }); | |
| // Store additional waypoint data | |
| marker.altitude = altitude; | |
| marker.speed = document.getElementById('waypoint-speed').value; | |
| waypoints.push(marker); | |
| // Draw flight path | |
| drawFlightPath(); | |
| } | |
| function drawFlightPath() { | |
| // Clear previous path if exists | |
| if (flightPath) { | |
| flightPath.setMap(null); | |
| } | |
| if (waypoints.length > 1) { | |
| flightPath = new google.maps.Polyline({ | |
| path: waypoints.map(wp => wp.getPosition()), | |
| geodesic: true, | |
| strokeColor: '#3b82f6', | |
| strokeOpacity: 1.0, | |
| strokeWeight: 3, | |
| zIndex: 1 | |
| }); | |
| flightPath.setMap(map); | |
| } | |
| } | |
| function clearWaypoints() { | |
| waypoints.forEach(marker => marker.setMap(null)); | |
| waypoints = []; | |
| if (flightPath) { | |
| flightPath.setMap(null); | |
| flightPath = null; | |
| } | |
| document.getElementById('mission-percentage').textContent = '0%'; | |
| document.getElementById('mission-progress').style.width = '0%'; | |
| } | |
| function connectToTelemetry() { | |
| const wsUrl = 'ws://localhost:8080/telemetry'; // Replace with your WebSocket endpoint | |
| telemetrySocket = new WebSocket(wsUrl); | |
| telemetrySocket.onopen = () => { | |
| console.log('Connected to telemetry server'); | |
| document.getElementById('mavlink-status').textContent = 'Yes'; | |
| document.getElementById('mavlink-status').classList.remove('text-red-500'); | |
| document.getElementById('mavlink-status').classList.add('text-green-500'); | |
| document.getElementById('drone-status').textContent = 'Connected'; | |
| document.getElementById('drone-status').classList.remove('text-gray-300'); | |
| document.getElementById('drone-status').classList.add('text-green-400'); | |
| document.getElementById('start-mission-btn').classList.remove('hidden'); | |
| }; | |
| telemetrySocket.onmessage = (event) => { | |
| try { | |
| const data = JSON.parse(event.data); | |
| updateTelemetry(data); | |
| } catch (error) { | |
| console.error('Error parsing telemetry data:', error); | |
| } | |
| }; | |
| telemetrySocket.onerror = (error) => { | |
| console.error('WebSocket error:', error); | |
| disconnectFromTelemetry(); | |
| }; | |
| telemetrySocket.onclose = () => { | |
| console.log('Disconnected from telemetry server'); | |
| disconnectFromTelemetry(); | |
| }; | |
| } | |
| function disconnectFromTelemetry() { | |
| if (telemetrySocket) { | |
| telemetrySocket.close(); | |
| telemetrySocket = null; | |
| } | |
| document.getElementById('mavlink-status').textContent = 'No'; | |
| document.getElementById('mavlink-status').classList.remove('text-green-500'); | |
| document.getElementById('mavlink-status').classList.add('text-red-500'); | |
| document.getElementById('drone-status').textContent = 'Disconnected'; | |
| document.getElementById('drone-status').classList.remove('text-green-400'); | |
| document.getElementById('drone-status').classList.add('text-gray-300'); | |
| document.getElementById('start-mission-btn').classList.add('hidden'); | |
| document.getElementById('pause-mission-btn').classList.add('hidden'); | |
| // Reset telemetry values | |
| document.getElementById('gps-coords').textContent = '--, --'; | |
| document.getElementById('altitude').textContent = '-- m'; | |
| document.getElementById('battery-level').textContent = '--%'; | |
| document.getElementById('satellites').textContent = '--'; | |
| document.getElementById('flight-mode').textContent = '--'; | |
| document.getElementById('speed').textContent = '-- m/s'; | |
| document.getElementById('heading').textContent = '--°'; | |
| document.getElementById('distance').textContent = '-- m'; | |
| document.getElementById('voltage').textContent = '-- V'; | |
| document.getElementById('current').textContent = '-- A'; | |
| document.getElementById('battery-remaining').textContent = '--%'; | |
| document.getElementById('flight-time').textContent = '--:--'; | |
| } | |
| function updateTelemetry(data) { | |
| // Update drone position | |
| if (data.latitude && data.longitude) { | |
| const dronePos = new google.maps.LatLng(data.latitude, data.longitude); | |
| droneMarker.setPosition(dronePos); | |
| // Center map on drone if it's moving | |
| if (data.ground_speed > 1) { | |
| map.panTo(dronePos); | |
| } | |
| } | |
| // Update telemetry values | |
| if (data.latitude && data.longitude) { | |
| document.getElementById('gps-coords').textContent = `${data.latitude.toFixed(6)}, ${data.longitude.toFixed(6)}`; | |
| } | |
| if (data.altitude) { | |
| document.getElementById('altitude').textContent = `${data.altitude.toFixed(1)} m`; | |
| } | |
| if (data.battery_remaining !== undefined) { | |
| const batteryPercent = Math.round(data.battery_remaining * 100); | |
| document.getElementById('battery-level').textContent = `${batteryPercent}%`; | |
| document.getElementById('battery-remaining').textContent = `${batteryPercent}%`; | |
| } | |
| if (data.satellites_visible) { | |
| document.getElementById('satellites').textContent = data.satellites_visible; | |
| } | |
| if (data.custom_mode) { | |
| document.getElementById('flight-mode').textContent = getFlightModeName(data.custom_mode); | |
| } | |
| if (data.ground_speed) { | |
| document.getElementById('speed').textContent = `${data.ground_speed.toFixed(1)} m/s`; | |
| } | |
| if (data.heading) { | |
| document.getElementById('heading').textContent = `${Math.round(data.heading)}°`; | |
| } | |
| if (data.voltage_battery) { | |
| document.getElementById('voltage').textContent = `${data.voltage_battery.toFixed(1)} V`; | |
| } | |
| if (data.current_battery) { | |
| document.getElementById('current').textContent = `${data.current_battery.toFixed(1)} A`; | |
| } | |
| if (data.flight_time) { | |
| const minutes = Math.floor(data.flight_time / 60); | |
| const seconds = data.flight_time % 60; | |
| document.getElementById('flight-time').textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`; | |
| } | |
| } | |
| function getFlightModeName(modeNumber) { | |
| // Map MAVLink flight modes to human-readable names | |
| const flightModes = { | |
| 0: 'Stabilize', | |
| 1: 'Acro', | |
| 2: 'Alt Hold', | |
| 3: 'Auto', | |
| 4: 'Guided', | |
| 5: 'Loiter', | |
| 6: 'RTL', | |
| 7: 'Circle', | |
| 9: 'Land', | |
| 11: 'Drift', | |
| 13: 'Sport', | |
| 14: 'Flip', | |
| 15: 'Auto Tune', | |
| 16: 'Pos Hold', | |
| 17: 'Brake', | |
| 18: 'Throw', | |
| 19: 'Avoid ADSB', | |
| 20: 'Guided NoGPS', | |
| 21: 'Smart RTL' | |
| }; | |
| return flightModes[modeNumber] || 'Unknown'; | |
| } | |
| function showDefectDetails(location, temperature, defectType) { | |
| document.getElementById('modal-image').src = ''; // Will be set by actual image data | |
| document.getElementById('module-temp').textContent = `${temperature}°C`; | |
| document.getElementById('defect-type').textContent = defectType; | |
| document.getElementById('defect-confidence').textContent = '--%'; | |
| document.getElementById('module-id').textContent = '--'; | |
| document.getElementById('module-location').textContent = '--'; | |
| document.getElementById('image-modal').classList.remove('hidden'); | |
| } | |
| // DOM Event Listeners | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // Initialize map when window loads | |
| window.initMap = initMap; | |
| // Toggle sidebar | |
| document.getElementById('toggle-sidebar').addEventListener('click', () => { | |
| document.getElementById('sidebar').classList.toggle('collapsed'); | |
| const icon = document.querySelector('#toggle-sidebar i'); | |
| if (document.getElementById('sidebar').classList.contains('collapsed')) { | |
| icon.classList.remove('fa-chevron-left'); | |
| icon.classList.add('fa-chevron-right'); | |
| } else { | |
| icon.classList.remove('fa-chevron-right'); | |
| icon.classList.add('fa-chevron-left'); | |
| } | |
| }); | |
| // Mobile menu toggle | |
| document.getElementById('mobile-menu-btn').addEventListener('click', () => { | |
| document.getElementById('sidebar').classList.toggle('hidden'); | |
| }); | |
| // Connect button | |
| document.getElementById('connect-btn').addEventListener('click', () => { | |
| const comPort = document.getElementById('com-port').value; | |
| const baudRate = document.getElementById('baud-rate').value; | |
| if (!comPort) { | |
| alert('Please select a COM port'); | |
| return; | |
| } | |
| // Connect to telemetry | |
| connectToTelemetry(); | |
| document.getElementById('connect-btn').classList.add('hidden'); | |
| document.getElementById('disconnect-btn').classList.remove('hidden'); | |
| }); | |
| // Disconnect button | |
| document.getElementById('disconnect-btn').addEventListener('click', disconnectFromTelemetry); | |
| // Add waypoint button | |
| document.getElementById('add-waypoint-btn').addEventListener('click', function() { | |
| this.classList.toggle('active'); | |
| if (this.classList.contains('active')) { | |
| this.innerHTML = '<i class="fas fa-times mr-1"></i> Cancel'; | |
| document.getElementById('map').style.cursor = 'crosshair'; | |
| } else { | |
| this.innerHTML = '<i class="fas fa-plus mr-1"></i> Add Waypoint'; | |
| document.getElementById('map').style.cursor = ''; | |
| } | |
| }); | |
| // Clear waypoints button | |
| document.getElementById('clear-waypoints-btn').addEventListener('click', clearWaypoints); | |
| // Start mission button | |
| document.getElementById('start-mission-btn').addEventListener('click', () => { | |
| if (waypoints.length === 0) { | |
| alert('Please add waypoints first'); | |
| return; | |
| } | |
| document.getElementById('start-mission-btn').classList.add('hidden'); | |
| document.getElementById('pause-mission-btn').classList.remove('hidden'); | |
| // Send mission to drone via WebSocket | |
| if (telemetrySocket && telemetrySocket.readyState === WebSocket.OPEN) { | |
| const mission = { | |
| type: 'mission', | |
| waypoints: waypoints.map(wp => ({ | |
| lat: wp.getPosition().lat(), | |
| lng: wp.getPosition().lng(), | |
| alt: parseFloat(wp.altitude), | |
| speed: parseFloat(wp.speed) | |
| })) | |
| }; | |
| telemetrySocket.send(JSON.stringify(mission)); | |
| } | |
| }); | |
| // Pause mission button | |
| document.getElementById('pause-mission-btn').addEventListener('click', () => { | |
| document.getElementById('start-mission-btn').classList.remove('hidden'); | |
| document.getElementById('pause-mission-btn').classList.add('hidden'); | |
| // Send pause command to drone | |
| if (telemetrySocket && telemetrySocket.readyState === WebSocket.OPEN) { | |
| telemetrySocket.send(JSON.stringify({ type: 'pause' })); | |
| } | |
| }); | |
| // Upload mission button | |
| document.getElementById('upload-mission-btn').addEventListener('click', () => { | |
| if (waypoints.length === 0) { | |
| alert('Please add waypoints first'); | |
| return; | |
| } | |
| // Send mission to drone | |
| if (telemetrySocket && telemetrySocket.readyState === WebSocket.OPEN) { | |
| const mission = { | |
| type: 'mission', | |
| waypoints: waypoints.map(wp => ({ | |
| lat: wp.getPosition().lat(), | |
| lng: wp.getPosition().lng(), | |
| alt: parseFloat(wp.altitude), | |
| speed: parseFloat(wp.speed) | |
| })) | |
| }; | |
| telemetrySocket.send(JSON.stringify(mission)); | |
| alert(`Mission with ${waypoints.length} waypoints uploaded to drone`); | |
| } else { | |
| alert('Not connected to drone'); | |
| } | |
| }); | |
| // Analyze images button | |
| document.getElementById('analyze-btn').addEventListener('click', () => { | |
| const threshold = document.getElementById('thermal-threshold').value; | |
| const useYolo = document.getElementById('enable-yolo').checked; | |
| // Send analysis request to server | |
| if (telemetrySocket && telemetrySocket.readyState === WebSocket.OPEN) { | |
| telemetrySocket.send(JSON.stringify({ | |
| type: 'analyze', | |
| threshold: parseFloat(threshold), | |
| useYolo: useYolo | |
| })); | |
| } else { | |
| alert('Not connected to drone'); | |
| } | |
| }); | |
| // Map type buttons | |
| document.getElementById('satellite-btn').addEventListener('click', () => { | |
| map.setMapTypeId('satellite'); | |
| }); | |
| document.getElementById('terrain-btn').addEventListener('click', () => { | |
| map.setMapTypeId('terrain'); | |
| }); | |
| document.getElementById('thermal-overlay-btn').addEventListener('click', () => { | |
| // This would toggle a thermal overlay if implemented | |
| alert('Thermal overlay would be displayed here if implemented'); | |
| }); | |
| // Quick action buttons | |
| document.getElementById('rtl-btn').addEventListener('click', () => { | |
| if (telemetrySocket && telemetrySocket.readyState === WebSocket.OPEN) { | |
| telemetrySocket.send(JSON.stringify({ type: 'command', command: 'RTL' })); | |
| } else { | |
| alert('Not connected to drone'); | |
| } | |
| }); | |
| document.getElementById('loiter-btn').addEventListener('click', () => { | |
| if (telemetrySocket && telemetrySocket.readyState === WebSocket.OPEN) { | |
| telemetrySocket.send(JSON.stringify({ type: 'command', command: 'LOITER' })); | |
| } else { | |
| alert('Not connected to drone'); | |
| } | |
| }); | |
| document.getElementById('land-btn').addEventListener('click', () => { | |
| if (telemetrySocket && telemetrySocket.readyState === WebSocket.OPEN) { | |
| telemetrySocket.send(JSON.stringify({ type: 'command', command: 'LAND' })); | |
| } else { | |
| alert('Not connected to drone'); | |
| } | |
| }); | |
| document.getElementById('takeoff-btn').addEventListener | |
| ('click', () => { | |
| if (telemetrySocket && telemetrySocket.readyState === WebSocket.OPEN) { | |
| telemetrySocket.send(JSON.stringify({ type: 'command', command: 'TAKEOFF' })); | |
| } else { | |
| alert('Not connected to drone'); | |
| } | |
| }); | |