privdrone / index.html
privateuserh's picture
Add 2 files
cc30b94 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PicoDrone - BLE Drone Controller</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">
<style>
.joystick {
width: 120px;
height: 120px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
position: relative;
touch-action: none;
}
.joystick-knob {
width: 50px;
height: 50px;
background: rgba(255, 255, 255, 0.8);
border-radius: 50%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.battery-level {
height: 20px;
border-radius: 10px;
transition: width 0.3s ease;
}
.connection-pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.gauge {
width: 100%;
height: 10px;
border-radius: 5px;
overflow: hidden;
}
.gauge-fill {
height: 100%;
transition: width 0.3s ease;
}
</style>
</head>
<body class="bg-gray-900 text-white min-h-screen">
<div class="container mx-auto px-4 py-8 max-w-md">
<!-- Header -->
<header class="flex justify-between items-center mb-8">
<div>
<h1 class="text-2xl font-bold">PicoDrone</h1>
<p class="text-gray-400 text-sm">BLE Drone Controller</p>
</div>
<div class="flex items-center space-x-2">
<div id="connectionStatus" class="w-3 h-3 rounded-full bg-red-500"></div>
<span id="connectionText" class="text-sm">Disconnected</span>
</div>
</header>
<!-- Connection Panel -->
<div class="bg-gray-800 rounded-xl p-6 mb-6 shadow-lg">
<h2 class="text-lg font-semibold mb-4 flex items-center">
<i class="fas fa-bluetooth-b mr-2"></i> BLE Connection
</h2>
<div class="mb-4">
<label class="block text-sm font-medium mb-2">Select Device</label>
<div class="flex">
<select id="deviceList" class="bg-gray-700 text-white rounded-l-lg p-2 flex-grow focus:outline-none">
<option value="">No devices found</option>
</select>
<button id="refreshBtn" class="bg-blue-600 hover:bg-blue-700 px-4 rounded-r-lg">
<i class="fas fa-sync-alt"></i>
</button>
</div>
</div>
<button id="connectBtn" class="w-full py-3 bg-green-600 hover:bg-green-700 rounded-lg font-medium flex items-center justify-center">
<i class="fas fa-plug mr-2"></i> Connect to Drone
</button>
</div>
<!-- Status Panel -->
<div class="bg-gray-800 rounded-xl p-6 mb-6 shadow-lg">
<h2 class="text-lg font-semibold mb-4 flex items-center">
<i class="fas fa-info-circle mr-2"></i> Drone Status
</h2>
<div class="grid grid-cols-2 gap-4 mb-4">
<div>
<p class="text-sm text-gray-400">Battery</p>
<div class="flex items-center">
<div class="gauge bg-gray-700 mr-2">
<div id="batteryLevel" class="gauge-fill bg-green-500" style="width: 75%"></div>
</div>
<span id="batteryPercent">75%</span>
</div>
</div>
<div>
<p class="text-sm text-gray-400">Signal Strength</p>
<div class="flex items-center">
<div class="gauge bg-gray-700 mr-2">
<div id="signalLevel" class="gauge-fill bg-blue-500" style="width: 90%"></div>
</div>
<span id="signalPercent">-</span>
</div>
</div>
</div>
<div class="grid grid-cols-3 gap-2 text-center">
<div class="bg-gray-700 p-2 rounded-lg">
<p class="text-sm text-gray-400">Altitude</p>
<p id="altitudeValue" class="font-medium">0.0 m</p>
</div>
<div class="bg-gray-700 p-2 rounded-lg">
<p class="text-sm text-gray-400">Speed</p>
<p id="speedValue" class="font-medium">0.0 m/s</p>
</div>
<div class="bg-gray-700 p-2 rounded-lg">
<p class="text-sm text-gray-400">Flight Time</p>
<p id="flightTime" class="font-medium">00:00</p>
</div>
</div>
</div>
<!-- Flight Controls -->
<div class="bg-gray-800 rounded-xl p-6 shadow-lg">
<h2 class="text-lg font-semibold mb-6 flex items-center">
<i class="fas fa-gamepad mr-2"></i> Flight Controls
</h2>
<div class="flex justify-between items-center mb-8">
<!-- Left Joystick (Throttle/Yaw) -->
<div class="text-center">
<p class="text-sm mb-2">Throttle/Yaw</p>
<div id="leftJoystick" class="joystick mx-auto">
<div class="joystick-knob"></div>
</div>
</div>
<!-- Right Joystick (Pitch/Roll) -->
<div class="text-center">
<p class="text-sm mb-2">Pitch/Roll</p>
<div id="rightJoystick" class="joystick mx-auto">
<div class="joystick-knob"></div>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="grid grid-cols-3 gap-3 mb-4">
<button id="takeoffBtn" class="bg-green-600 hover:bg-green-700 py-3 rounded-lg font-medium disabled:opacity-50" disabled>
<i class="fas fa-rocket"></i> Takeoff
</button>
<button id="landBtn" class="bg-yellow-600 hover:bg-yellow-700 py-3 rounded-lg font-medium disabled:opacity-50" disabled>
<i class="fas fa-landmark"></i> Land
</button>
<button id="emergencyBtn" class="bg-red-600 hover:bg-red-700 py-3 rounded-lg font-medium disabled:opacity-50" disabled>
<i class="fas fa-exclamation-triangle"></i> Stop
</button>
</div>
<!-- Trim Controls -->
<div class="bg-gray-700 rounded-lg p-3 mb-4">
<p class="text-sm mb-2 text-center">Trim Adjustments</p>
<div class="grid grid-cols-4 gap-2">
<button class="trim-btn bg-gray-600 hover:bg-gray-500 p-2 rounded" data-axis="pitch" data-direction="up">
<i class="fas fa-arrow-up"></i> Pitch
</button>
<button class="trim-btn bg-gray-600 hover:bg-gray-500 p-2 rounded" data-axis="roll" data-direction="left">
<i class="fas fa-arrow-left"></i> Roll
</button>
<button class="trim-btn bg-gray-600 hover:bg-gray-500 p-2 rounded" data-axis="roll" data-direction="right">
<i class="fas fa-arrow-right"></i> Roll
</button>
<button class="trim-btn bg-gray-600 hover:bg-gray-500 p-2 rounded" data-axis="pitch" data-direction="down">
<i class="fas fa-arrow-down"></i> Pitch
</button>
</div>
</div>
<!-- Flight Mode Selector -->
<div class="mb-4">
<label class="block text-sm font-medium mb-2">Flight Mode</label>
<select id="flightMode" class="w-full bg-gray-700 text-white rounded-lg p-2 focus:outline-none">
<option value="beginner">Beginner (Limited)</option>
<option value="normal" selected>Normal</option>
<option value="acrobatic">Acrobatic</option>
</select>
</div>
</div>
<!-- Console Log -->
<div class="bg-gray-800 rounded-xl p-4 mt-6 shadow-lg">
<div class="flex justify-between items-center mb-2">
<h3 class="text-sm font-semibold">
<i class="fas fa-terminal mr-1"></i> Console
</h3>
<button id="clearConsole" class="text-xs text-gray-400 hover:text-white">
<i class="fas fa-trash-alt mr-1"></i> Clear
</button>
</div>
<div id="consoleOutput" class="bg-black text-green-400 font-mono text-xs p-2 rounded h-24 overflow-y-auto">
> Welcome to PicoDrone controller<br>
> Ready to connect...
</div>
</div>
</div>
<script>
// Simulated BLE functionality for demonstration
document.addEventListener('DOMContentLoaded', function() {
// UI Elements
const connectBtn = document.getElementById('connectBtn');
const refreshBtn = document.getElementById('refreshBtn');
const deviceList = document.getElementById('deviceList');
const connectionStatus = document.getElementById('connectionStatus');
const connectionText = document.getElementById('connectionText');
const takeoffBtn = document.getElementById('takeoffBtn');
const landBtn = document.getElementById('landBtn');
const emergencyBtn = document.getElementById('emergencyBtn');
const consoleOutput = document.getElementById('consoleOutput');
const clearConsole = document.getElementById('clearConsole');
// Status elements
const batteryLevel = document.getElementById('batteryLevel');
const batteryPercent = document.getElementById('batteryPercent');
const signalLevel = document.getElementById('signalLevel');
const signalPercent = document.getElementById('signalPercent');
const altitudeValue = document.getElementById('altitudeValue');
const speedValue = document.getElementById('speedValue');
const flightTime = document.getElementById('flightTime');
// Joystick elements
const leftJoystick = document.getElementById('leftJoystick');
const rightJoystick = document.getElementById('rightJoystick');
const leftKnob = leftJoystick.querySelector('.joystick-knob');
const rightKnob = rightJoystick.querySelector('.joystick-knob');
// Flight mode selector
const flightMode = document.getElementById('flightMode');
// Simulated devices
const devices = [
{ name: 'PicoDrone-1', id: 'pico-1234' },
{ name: 'PicoDrone-2', id: 'pico-5678' },
{ name: 'PicoDrone-Test', id: 'pico-9ABC' }
];
// State variables
let isConnected = false;
let isFlying = false;
let flightStartTime = null;
let flightTimer = null;
let batterySimulator = null;
// Initialize the app
function init() {
populateDeviceList();
setupEventListeners();
setupJoysticks();
}
// Populate device list with simulated devices
function populateDeviceList() {
deviceList.innerHTML = '';
if (devices.length === 0) {
deviceList.innerHTML = '<option value="">No devices found</option>';
return;
}
devices.forEach(device => {
const option = document.createElement('option');
option.value = device.id;
option.textContent = device.name;
deviceList.appendChild(option);
});
}
// Set up event listeners
function setupEventListeners() {
// Connect button
connectBtn.addEventListener('click', function() {
if (isConnected) {
disconnectFromDrone();
} else {
const selectedDeviceId = deviceList.value;
if (selectedDeviceId) {
connectToDrone(selectedDeviceId);
} else {
logToConsole('Please select a device first');
}
}
});
// Refresh button
refreshBtn.addEventListener('click', function() {
logToConsole('Scanning for BLE devices...');
// Simulate scanning delay
setTimeout(() => {
populateDeviceList();
logToConsole(`Found ${devices.length} device(s)`);
}, 1000);
});
// Action buttons
takeoffBtn.addEventListener('click', function() {
if (isConnected) {
takeoff();
}
});
landBtn.addEventListener('click', function() {
if (isConnected && isFlying) {
land();
}
});
emergencyBtn.addEventListener('click', function() {
if (isConnected) {
emergencyStop();
}
});
// Trim buttons
document.querySelectorAll('.trim-btn').forEach(btn => {
btn.addEventListener('click', function() {
const axis = this.dataset.axis;
const direction = this.dataset.direction;
adjustTrim(axis, direction);
});
});
// Flight mode selector
flightMode.addEventListener('change', function() {
if (isConnected) {
const mode = this.value;
setFlightMode(mode);
logToConsole(`Flight mode changed to: ${mode}`);
}
});
// Clear console
clearConsole.addEventListener('click', function() {
consoleOutput.innerHTML = '> Console cleared<br>';
});
}
// Set up joystick controls
function setupJoysticks() {
setupJoystick(leftJoystick, leftKnob, (x, y) => {
// Throttle (Y) and Yaw (X) controls
if (isConnected) {
const throttle = ((y * -1 + 1) / 2 * 100).toFixed(0);
const yaw = (x * 100).toFixed(0);
// In a real app, send these values to the drone via BLE
logToConsole(`Throttle: ${throttle}%, Yaw: ${yaw}%`, true);
}
});
setupJoystick(rightJoystick, rightKnob, (x, y) => {
// Pitch (Y) and Roll (X) controls
if (isConnected) {
const pitch = (y * 100).toFixed(0);
const roll = (x * 100).toFixed(0);
// In a real app, send these values to the drone via BLE
logToConsole(`Pitch: ${pitch}%, Roll: ${roll}%`, true);
}
});
}
// Helper function to set up a joystick
function setupJoystick(joystick, knob, callback) {
let isDragging = false;
// Touch events
joystick.addEventListener('touchstart', handleStart);
joystick.addEventListener('touchmove', handleMove);
joystick.addEventListener('touchend', handleEnd);
// Mouse events
joystick.addEventListener('mousedown', handleStart);
document.addEventListener('mousemove', handleMove);
document.addEventListener('mouseup', handleEnd);
function handleStart(e) {
e.preventDefault();
isDragging = true;
updatePosition(e);
}
function handleMove(e) {
if (!isDragging) return;
e.preventDefault();
updatePosition(e);
}
function handleEnd(e) {
isDragging = false;
// Return knob to center
knob.style.transform = 'translate(-50%, -50%)';
callback(0, 0);
}
function updatePosition(e) {
const rect = joystick.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
let clientX, clientY;
if (e.type.includes('touch')) {
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
} else {
clientX = e.clientX;
clientY = e.clientY;
}
const x = clientX - centerX;
const y = clientY - centerY;
// Calculate distance from center
const distance = Math.sqrt(x * x + y * y);
const maxDistance = rect.width / 2 - 25; // 25 is half knob width
// Normalized values (-1 to 1)
let normX = x / maxDistance;
let normY = y / maxDistance;
// Limit to joystick bounds
if (distance > maxDistance) {
normX = (x / distance) * 1;
normY = (y / distance) * 1;
}
// Update knob position
knob.style.transform = `translate(${normX * 35}px, ${normY * 35}px) translate(-50%, -50%)`;
// Call callback with normalized values
callback(normX, normY);
}
}
// Connect to drone (simulated)
function connectToDrone(deviceId) {
logToConsole(`Connecting to device ${deviceId}...`);
// Simulate connection delay
setTimeout(() => {
isConnected = true;
updateConnectionStatus(true);
enableControls();
// Start battery simulation
startBatterySimulation();
logToConsole('Connected successfully!');
logToConsole('PicoDrone ready for commands');
// Update device name in UI
const device = devices.find(d => d.id === deviceId);
if (device) {
connectionText.textContent = device.name;
}
}, 1500);
}
// Disconnect from drone (simulated)
function disconnectFromDrone() {
logToConsole('Disconnecting from drone...');
// Stop any ongoing processes
if (isFlying) {
emergencyStop();
}
if (batterySimulator) {
clearInterval(batterySimulator);
batterySimulator = null;
}
if (flightTimer) {
clearInterval(flightTimer);
flightTimer = null;
}
// Simulate disconnection delay
setTimeout(() => {
isConnected = false;
isFlying = false;
updateConnectionStatus(false);
disableControls();
// Reset status displays
batteryLevel.style.width = '0%';
batteryPercent.textContent = '0%';
signalLevel.style.width = '0%';
signalPercent.textContent = '-';
altitudeValue.textContent = '0.0 m';
speedValue.textContent = '0.0 m/s';
flightTime.textContent = '00:00';
logToConsole('Disconnected successfully');
connectionText.textContent = 'Disconnected';
}, 1000);
}
// Update connection status UI
function updateConnectionStatus(connected) {
if (connected) {
connectionStatus.classList.remove('bg-red-500');
connectionStatus.classList.add('bg-green-500', 'connection-pulse');
connectionText.textContent = 'Connected';
connectBtn.innerHTML = '<i class="fas fa-unplug mr-2"></i> Disconnect';
connectBtn.classList.remove('bg-green-600', 'hover:bg-green-700');
connectBtn.classList.add('bg-red-600', 'hover:bg-red-700');
} else {
connectionStatus.classList.remove('bg-green-500', 'connection-pulse');
connectionStatus.classList.add('bg-red-500');
connectionText.textContent = 'Disconnected';
connectBtn.innerHTML = '<i class="fas fa-plug mr-2"></i> Connect to Drone';
connectBtn.classList.remove('bg-red-600', 'hover:bg-red-700');
connectBtn.classList.add('bg-green-600', 'hover:bg-green-700');
}
}
// Enable flight controls
function enableControls() {
takeoffBtn.disabled = false;
landBtn.disabled = !isFlying;
emergencyBtn.disabled = false;
}
// Disable flight controls
function disableControls() {
takeoffBtn.disabled = true;
landBtn.disabled = true;
emergencyBtn.disabled = true;
}
// Takeoff command
function takeoff() {
if (!isFlying) {
logToConsole('Initiating takeoff sequence...');
// Simulate takeoff delay
setTimeout(() => {
isFlying = true;
flightStartTime = new Date();
startFlightTimer();
landBtn.disabled = false;
logToConsole('Drone is airborne!');
// Simulate altitude increase
let altitude = 0;
const altitudeInterval = setInterval(() => {
if (!isFlying) {
clearInterval(altitudeInterval);
return;
}
altitude += 0.1;
if (altitude > 10) altitude = 10; // Max altitude for demo
altitudeValue.textContent = altitude.toFixed(1) + ' m';
}, 200);
}, 2000);
}
}
// Land command
function land() {
if (isFlying) {
logToConsole('Initiating landing sequence...');
// Simulate landing delay
setTimeout(() => {
isFlying = false;
if (flightTimer) {
clearInterval(flightTimer);
flightTimer = null;
}
landBtn.disabled = true;
logToConsole('Drone has landed safely');
// Simulate altitude decrease
let altitude = parseFloat(altitudeValue.textContent);
const altitudeInterval = setInterval(() => {
altitude -= 0.2;
if (altitude < 0) {
altitude = 0;
clearInterval(altitudeInterval);
}
altitudeValue.textContent = altitude.toFixed(1) + ' m';
}, 200);
}, 2000);
}
}
// Emergency stop command
function emergencyStop() {
logToConsole('EMERGENCY STOP ACTIVATED!', false, 'text-red-500');
// Immediate stop
isFlying = false;
if (flightTimer) {
clearInterval(flightTimer);
flightTimer = null;
}
landBtn.disabled = true;
// Simulate rapid descent
let altitude = parseFloat(altitudeValue.textContent);
const crashInterval = setInterval(() => {
altitude -= 0.5;
if (altitude <= 0) {
altitude = 0;
clearInterval(crashInterval);
logToConsole('Drone has crashed!', false, 'text-red-500');
}
altitudeValue.textContent = altitude.toFixed(1) + ' m';
}, 100);
}
// Adjust trim
function adjustTrim(axis, direction) {
if (isConnected) {
logToConsole(`Adjusting ${axis} trim: ${direction}`);
// In a real app, send trim commands to the drone
}
}
// Set flight mode
function setFlightMode(mode) {
// In a real app, send flight mode command to the drone
}
// Start flight timer
function startFlightTimer() {
flightTimer = setInterval(() => {
const now = new Date();
const diff = now - flightStartTime;
const minutes = Math.floor(diff / 60000);
const seconds = Math.floor((diff % 60000) / 1000);
flightTime.textContent =
`${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
// Simulate speed changes
const speed = (0.5 + Math.random() * 2).toFixed(1);
speedValue.textContent = `${speed} m/s`;
// Simulate signal strength fluctuations
const signal = 70 + Math.floor(Math.random() * 30);
signalLevel.style.width = `${signal}%`;
signalPercent.textContent = `${signal}%`;
}, 1000);
}
// Simulate battery drain
function startBatterySimulation() {
let battery = 75; // Start at 75%
batteryLevel.style.width = `${battery}%`;
batteryPercent.textContent = `${battery}%`;
batterySimulator = setInterval(() => {
if (!isConnected) return;
// Drain faster when flying
const drainRate = isFlying ? 0.5 : 0.1;
battery -= drainRate;
if (battery < 0) battery = 0;
batteryLevel.style.width = `${battery}%`;
batteryPercent.textContent = `${Math.floor(battery)}%`;
// Change color based on level
if (battery < 20) {
batteryLevel.classList.remove('bg-green-500', 'bg-yellow-500');
batteryLevel.classList.add('bg-red-500');
} else if (battery < 50) {
batteryLevel.classList.remove('bg-green-500', 'bg-red-500');
batteryLevel.classList.add('bg-yellow-500');
} else {
batteryLevel.classList.remove('bg-yellow-500', 'bg-red-500');
batteryLevel.classList.add('bg-green-500');
}
// Critical battery warning
if (battery < 10 && isFlying) {
logToConsole('WARNING: Critical battery level! Land immediately!', false, 'text-red-500');
} else if (battery < 20) {
logToConsole('Warning: Low battery', false, 'text-yellow-500');
}
// Auto-land at 5% if flying
if (battery <= 5 && isFlying) {
logToConsole('AUTO-LANDING: Battery critically low', false, 'text-red-500');
land();
}
}, 1000);
}
// Log messages to console
function logToConsole(message, isCommand = false, colorClass = '') {
const now = new Date();
const timestamp = now.toLocaleTimeString();
const messageElement = document.createElement('div');
if (colorClass) {
messageElement.className = colorClass;
}
messageElement.innerHTML = `&gt; [${timestamp}] ${message}`;
consoleOutput.appendChild(messageElement);
consoleOutput.scrollTop = consoleOutput.scrollHeight;
}
// Initialize the app
init();
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=privateuserh/privdrone" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>