| <!DOCTYPE html> |
| <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"> |
| |
| <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"> |
| |
| <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> |
| |
| </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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <div class="flex-1 flex flex-col overflow-hidden"> |
| |
| <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> |
|
|
| |
| <div class="flex-1 flex flex-col md:flex-row overflow-hidden"> |
| |
| <div class="flex-1 relative"> |
| <div id="map" class="absolute inset-0"></div> |
| |
| |
| <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> |
| |
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
| |
| let map; |
| let droneMarker; |
| let waypoints = []; |
| let defectMarkers = []; |
| let flightPath = null; |
| let telemetrySocket = null; |
| |
| function initMap() { |
| try { |
| |
| 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'] |
| } |
| }); |
| |
| |
| 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 |
| }); |
| |
| |
| 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)` |
| }); |
| |
| |
| marker.altitude = altitude; |
| marker.speed = document.getElementById('waypoint-speed').value; |
| |
| waypoints.push(marker); |
| |
| |
| drawFlightPath(); |
| } |
| |
| function drawFlightPath() { |
| |
| 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'; |
| 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'); |
| |
| |
| 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) { |
| |
| if (data.latitude && data.longitude) { |
| const dronePos = new google.maps.LatLng(data.latitude, data.longitude); |
| droneMarker.setPosition(dronePos); |
| |
| |
| if (data.ground_speed > 1) { |
| map.panTo(dronePos); |
| } |
| } |
| |
| |
| 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) { |
| |
| 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 = ''; |
| 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'); |
| } |
| |
| |
| document.addEventListener('DOMContentLoaded', () => { |
| |
| window.initMap = initMap; |
| |
| |
| 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'); |
| } |
| }); |
| |
| |
| document.getElementById('mobile-menu-btn').addEventListener('click', () => { |
| document.getElementById('sidebar').classList.toggle('hidden'); |
| }); |
| |
| |
| 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; |
| } |
| |
| |
| connectToTelemetry(); |
| |
| document.getElementById('connect-btn').classList.add('hidden'); |
| document.getElementById('disconnect-btn').classList.remove('hidden'); |
| }); |
| |
| |
| document.getElementById('disconnect-btn').addEventListener('click', disconnectFromTelemetry); |
| |
| |
| 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 = ''; |
| } |
| }); |
| |
| |
| document.getElementById('clear-waypoints-btn').addEventListener('click', clearWaypoints); |
| |
| |
| 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'); |
| |
| |
| 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)); |
| } |
| }); |
| |
| |
| document.getElementById('pause-mission-btn').addEventListener('click', () => { |
| document.getElementById('start-mission-btn').classList.remove('hidden'); |
| document.getElementById('pause-mission-btn').classList.add('hidden'); |
| |
| |
| if (telemetrySocket && telemetrySocket.readyState === WebSocket.OPEN) { |
| telemetrySocket.send(JSON.stringify({ type: 'pause' })); |
| } |
| }); |
| |
| |
| document.getElementById('upload-mission-btn').addEventListener('click', () => { |
| if (waypoints.length === 0) { |
| alert('Please add waypoints first'); |
| return; |
| } |
| |
| |
| 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'); |
| } |
| }); |
| |
| |
| document.getElementById('analyze-btn').addEventListener('click', () => { |
| const threshold = document.getElementById('thermal-threshold').value; |
| const useYolo = document.getElementById('enable-yolo').checked; |
| |
| |
| if (telemetrySocket && telemetrySocket.readyState === WebSocket.OPEN) { |
| telemetrySocket.send(JSON.stringify({ |
| type: 'analyze', |
| threshold: parseFloat(threshold), |
| useYolo: useYolo |
| })); |
| } else { |
| alert('Not connected to drone'); |
| } |
| }); |
| |
| |
| 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', () => { |
| |
| alert('Thermal overlay would be displayed here if implemented'); |
| }); |
| |
| |
| 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'); |
| } |
| }); |
| |