Je souhaiterais créer une page web permettant de contrôler deux caméras PTZ (thermique et jour), d’afficher le flux RTSP de chacune, d’intégrer un bouton de configuration et un bouton pour basculer entre les deux caméras, tout en envoyant les commandes(clique et release) et en récupérant les informations de position (PAN, TILT, ZOOM) via une application Java desktop utilisant un WebSocketServer.
Browse files- README.md +8 -5
- index.html +360 -18
README.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: PTZ Camera Control Hub 🎥
|
| 3 |
+
colorFrom: gray
|
| 4 |
+
colorTo: gray
|
| 5 |
+
emoji: 🐳
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite-v3
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Welcome to your new DeepSite project!
|
| 13 |
+
This project was created with [DeepSite](https://deepsite.hf.co).
|
index.html
CHANGED
|
@@ -1,19 +1,361 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</html>
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>PTZ Camera Control Hub</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 9 |
+
<style>
|
| 10 |
+
.camera-feed {
|
| 11 |
+
background-color: #1a202c;
|
| 12 |
+
aspect-ratio: 16/9;
|
| 13 |
+
}
|
| 14 |
+
.position-indicator {
|
| 15 |
+
background-color: rgba(0, 0, 0, 0.7);
|
| 16 |
+
}
|
| 17 |
+
.control-btn {
|
| 18 |
+
transition: all 0.2s;
|
| 19 |
+
}
|
| 20 |
+
.control-btn:active {
|
| 21 |
+
transform: scale(0.95);
|
| 22 |
+
}
|
| 23 |
+
</style>
|
| 24 |
+
</head>
|
| 25 |
+
<body class="bg-gray-900 text-white min-h-screen">
|
| 26 |
+
<!-- Header -->
|
| 27 |
+
<header class="bg-gray-800 py-4 px-6 shadow-lg">
|
| 28 |
+
<div class="container mx-auto flex justify-between items-center">
|
| 29 |
+
<h1 class="text-2xl font-bold flex items-center">
|
| 30 |
+
<i data-feather="video" class="mr-2"></i>
|
| 31 |
+
PTZ Camera Control Hub
|
| 32 |
+
</h1>
|
| 33 |
+
<button id="configBtn" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-lg flex items-center">
|
| 34 |
+
<i data-feather="settings" class="mr-2"></i>
|
| 35 |
+
Configuration
|
| 36 |
+
</button>
|
| 37 |
+
</div>
|
| 38 |
+
</header>
|
| 39 |
+
|
| 40 |
+
<!-- Main Content -->
|
| 41 |
+
<main class="container mx-auto py-6 px-4">
|
| 42 |
+
<!-- Camera Toggle -->
|
| 43 |
+
<div class="flex justify-center mb-6">
|
| 44 |
+
<div class="inline-flex rounded-md shadow-sm">
|
| 45 |
+
<button id="dayCamBtn" class="px-4 py-2 text-sm font-medium rounded-l-lg bg-gray-700 hover:bg-gray-600 focus:z-10 focus:ring-2 focus:ring-blue-500">
|
| 46 |
+
Day Camera
|
| 47 |
+
</button>
|
| 48 |
+
<button id="thermalCamBtn" class="px-4 py-2 text-sm font-medium rounded-r-md bg-gray-700 hover:bg-gray-600 focus:z-10 focus:ring-2 focus:ring-blue-500">
|
| 49 |
+
Thermal Camera
|
| 50 |
+
</button>
|
| 51 |
+
</div>
|
| 52 |
+
</div>
|
| 53 |
+
|
| 54 |
+
<!-- Camera Feeds -->
|
| 55 |
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
| 56 |
+
<!-- Day Camera Feed -->
|
| 57 |
+
<div class="camera-container" id="dayCameraContainer">
|
| 58 |
+
<div class="camera-feed rounded-lg overflow-hidden relative">
|
| 59 |
+
<video id="dayCamFeed" class="w-full h-full" autoplay muted></video>
|
| 60 |
+
<div class="position-indicator absolute bottom-0 left-0 right-0 p-2 text-sm">
|
| 61 |
+
<div class="flex justify-between">
|
| 62 |
+
<span>PAN: <span id="dayPan">0°</span></span>
|
| 63 |
+
<span>TILT: <span id="dayTilt">0°</span></span>
|
| 64 |
+
<span>ZOOM: <span id="dayZoom">1x</span></span>
|
| 65 |
+
</div>
|
| 66 |
+
</div>
|
| 67 |
+
</div>
|
| 68 |
+
<div class="controls grid grid-cols-3 gap-2 mt-2">
|
| 69 |
+
<button class="control-btn bg-gray-700 hover:bg-gray-600 p-2 rounded" data-command="pan-left">
|
| 70 |
+
<i data-feather="arrow-left"></i>
|
| 71 |
+
</button>
|
| 72 |
+
<button class="control-btn bg-gray-700 hover:bg-gray-600 p-2 rounded" data-command="tilt-up">
|
| 73 |
+
<i data-feather="arrow-up"></i>
|
| 74 |
+
</button>
|
| 75 |
+
<button class="control-btn bg-gray-700 hover:bg-gray-600 p-2 rounded" data-command="zoom-in">
|
| 76 |
+
<i data-feather="zoom-in"></i>
|
| 77 |
+
</button>
|
| 78 |
+
<button class="control-btn bg-gray-700 hover:bg-gray-600 p-2 rounded" data-command="pan-right">
|
| 79 |
+
<i data-feather="arrow-right"></i>
|
| 80 |
+
</button>
|
| 81 |
+
<button class="control-btn bg-gray-700 hover:bg-gray-600 p-2 rounded" data-command="tilt-down">
|
| 82 |
+
<i data-feather="arrow-down"></i>
|
| 83 |
+
</button>
|
| 84 |
+
<button class="control-btn bg-gray-700 hover:bg-gray-600 p-2 rounded" data-command="zoom-out">
|
| 85 |
+
<i data-feather="zoom-out"></i>
|
| 86 |
+
</button>
|
| 87 |
+
</div>
|
| 88 |
+
</div>
|
| 89 |
+
|
| 90 |
+
<!-- Thermal Camera Feed -->
|
| 91 |
+
<div class="camera-container hidden" id="thermalCameraContainer">
|
| 92 |
+
<div class="camera-feed rounded-lg overflow-hidden relative">
|
| 93 |
+
<video id="thermalCamFeed" class="w-full h-full" autoplay muted></video>
|
| 94 |
+
<div class="position-indicator absolute bottom-0 left-0 right-0 p-2 text-sm">
|
| 95 |
+
<div class="flex justify-between">
|
| 96 |
+
<span>PAN: <span id="thermalPan">0°</span></span>
|
| 97 |
+
<span>TILT: <span id="thermalTilt">0°</span></span>
|
| 98 |
+
<span>ZOOM: <span id="thermalZoom">1x</span></span>
|
| 99 |
+
</div>
|
| 100 |
+
</div>
|
| 101 |
+
</div>
|
| 102 |
+
<div class="controls grid grid-cols-3 gap-2 mt-2">
|
| 103 |
+
<button class="control-btn bg-gray-700 hover:bg-gray-600 p-2 rounded" data-command="pan-left">
|
| 104 |
+
<i data-feather="arrow-left"></i>
|
| 105 |
+
</button>
|
| 106 |
+
<button class="control-btn bg-gray-700 hover:bg-gray-600 p-2 rounded" data-command="tilt-up">
|
| 107 |
+
<i data-feather="arrow-up"></i>
|
| 108 |
+
</button>
|
| 109 |
+
<button class="control-btn bg-gray-700 hover:bg-gray-600 p-2 rounded" data-command="zoom-in">
|
| 110 |
+
<i data-feather="zoom-in"></i>
|
| 111 |
+
</button>
|
| 112 |
+
<button class="control-btn bg-gray-700 hover:bg-gray-600 p-2 rounded" data-command="pan-right">
|
| 113 |
+
<i data-feather="arrow-right"></i>
|
| 114 |
+
</button>
|
| 115 |
+
<button class="control-btn bg-gray-700 hover:bg-gray-600 p-2 rounded" data-command="tilt-down">
|
| 116 |
+
<i data-feather="arrow-down"></i>
|
| 117 |
+
</button>
|
| 118 |
+
<button class="control-btn bg-gray-700 hover:bg-gray-600 p-2 rounded" data-command="zoom-out">
|
| 119 |
+
<i data-feather="zoom-out"></i>
|
| 120 |
+
</button>
|
| 121 |
+
</div>
|
| 122 |
+
</div>
|
| 123 |
+
</div>
|
| 124 |
+
|
| 125 |
+
<!-- Status Bar -->
|
| 126 |
+
<div class="bg-gray-800 rounded-lg p-4 text-sm">
|
| 127 |
+
<div class="flex items-center">
|
| 128 |
+
<div id="connectionStatus" class="flex items-center mr-4">
|
| 129 |
+
<span class="w-3 h-3 rounded-full bg-red-500 mr-2"></span>
|
| 130 |
+
<span>Disconnected</span>
|
| 131 |
+
</div>
|
| 132 |
+
<div id="activeCamera" class="flex items-center">
|
| 133 |
+
<i data-feather="video" class="mr-2"></i>
|
| 134 |
+
<span>Day Camera</span>
|
| 135 |
+
</div>
|
| 136 |
+
</div>
|
| 137 |
+
</div>
|
| 138 |
+
</main>
|
| 139 |
+
|
| 140 |
+
<!-- Configuration Modal -->
|
| 141 |
+
<div id="configModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden">
|
| 142 |
+
<div class="bg-gray-800 rounded-lg p-6 w-full max-w-md">
|
| 143 |
+
<div class="flex justify-between items-center mb-4">
|
| 144 |
+
<h2 class="text-xl font-bold">Configuration</h2>
|
| 145 |
+
<button id="closeConfigBtn" class="text-gray-400 hover:text-white">
|
| 146 |
+
<i data-feather="x"></i>
|
| 147 |
+
</button>
|
| 148 |
+
</div>
|
| 149 |
+
|
| 150 |
+
<div class="space-y-4">
|
| 151 |
+
<div>
|
| 152 |
+
<label class="block text-sm font-medium mb-1">Day Camera RTSP URL</label>
|
| 153 |
+
<input type="text" id="dayCamUrl" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2">
|
| 154 |
+
</div>
|
| 155 |
+
<div>
|
| 156 |
+
<label class="block text-sm font-medium mb-1">Thermal Camera RTSP URL</label>
|
| 157 |
+
<input type="text" id="thermalCamUrl" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2">
|
| 158 |
+
</div>
|
| 159 |
+
<div>
|
| 160 |
+
<label class="block text-sm font-medium mb-1">WebSocket Server URL</label>
|
| 161 |
+
<input type="text" id="wsUrl" value="ws://localhost:8080" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2">
|
| 162 |
+
</div>
|
| 163 |
+
</div>
|
| 164 |
+
|
| 165 |
+
<div class="mt-6 flex justify-end space-x-3">
|
| 166 |
+
<button id="saveConfigBtn" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-lg">
|
| 167 |
+
Save
|
| 168 |
+
</button>
|
| 169 |
+
<button id="cancelConfigBtn" class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg">
|
| 170 |
+
Cancel
|
| 171 |
+
</button>
|
| 172 |
+
</div>
|
| 173 |
+
</div>
|
| 174 |
+
</div>
|
| 175 |
+
|
| 176 |
+
<script>
|
| 177 |
+
feather.replace();
|
| 178 |
+
|
| 179 |
+
// WebSocket connection
|
| 180 |
+
let socket;
|
| 181 |
+
|
| 182 |
+
// DOM Elements
|
| 183 |
+
const dayCamBtn = document.getElementById('dayCamBtn');
|
| 184 |
+
const thermalCamBtn = document.getElementById('thermalCamBtn');
|
| 185 |
+
const dayCameraContainer = document.getElementById('dayCameraContainer');
|
| 186 |
+
const thermalCameraContainer = document.getElementById('thermalCameraContainer');
|
| 187 |
+
const configBtn = document.getElementById('configBtn');
|
| 188 |
+
const configModal = document.getElementById('configModal');
|
| 189 |
+
const closeConfigBtn = document.getElementById('closeConfigBtn');
|
| 190 |
+
const saveConfigBtn = document.getElementById('saveConfigBtn');
|
| 191 |
+
const cancelConfigBtn = document.getElementById('cancelConfigBtn');
|
| 192 |
+
const connectionStatus = document.getElementById('connectionStatus');
|
| 193 |
+
const activeCamera = document.getElementById('activeCamera');
|
| 194 |
+
|
| 195 |
+
// Camera control buttons
|
| 196 |
+
const controlButtons = document.querySelectorAll('.control-btn');
|
| 197 |
+
|
| 198 |
+
// Initialize camera feeds
|
| 199 |
+
const dayCamFeed = document.getElementById('dayCamFeed');
|
| 200 |
+
const thermalCamFeed = document.getElementById('thermalCamFeed');
|
| 201 |
+
|
| 202 |
+
// Toggle between cameras
|
| 203 |
+
dayCamBtn.addEventListener('click', () => {
|
| 204 |
+
dayCameraContainer.classList.remove('hidden');
|
| 205 |
+
thermalCameraContainer.classList.add('hidden');
|
| 206 |
+
dayCamBtn.classList.add('bg-blue-600');
|
| 207 |
+
thermalCamBtn.classList.remove('bg-blue-600');
|
| 208 |
+
activeCamera.innerHTML = '<i data-feather="video" class="mr-2"></i><span>Day Camera</span>';
|
| 209 |
+
feather.replace();
|
| 210 |
+
});
|
| 211 |
+
|
| 212 |
+
thermalCamBtn.addEventListener('click', () => {
|
| 213 |
+
dayCameraContainer.classList.add('hidden');
|
| 214 |
+
thermalCameraContainer.classList.remove('hidden');
|
| 215 |
+
dayCamBtn.classList.remove('bg-blue-600');
|
| 216 |
+
thermalCamBtn.classList.add('bg-blue-600');
|
| 217 |
+
activeCamera.innerHTML = '<i data-feather="video" class="mr-2"></i><span>Thermal Camera</span>';
|
| 218 |
+
feather.replace();
|
| 219 |
+
});
|
| 220 |
+
|
| 221 |
+
// Configuration modal
|
| 222 |
+
configBtn.addEventListener('click', () => {
|
| 223 |
+
configModal.classList.remove('hidden');
|
| 224 |
+
});
|
| 225 |
+
|
| 226 |
+
closeConfigBtn.addEventListener('click', () => {
|
| 227 |
+
configModal.classList.add('hidden');
|
| 228 |
+
});
|
| 229 |
+
|
| 230 |
+
cancelConfigBtn.addEventListener('click', () => {
|
| 231 |
+
configModal.classList.add('hidden');
|
| 232 |
+
});
|
| 233 |
+
|
| 234 |
+
saveConfigBtn.addEventListener('click', () => {
|
| 235 |
+
const dayCamUrl = document.getElementById('dayCamUrl').value;
|
| 236 |
+
const thermalCamUrl = document.getElementById('thermalCamUrl').value;
|
| 237 |
+
const wsUrl = document.getElementById('wsUrl').value;
|
| 238 |
+
|
| 239 |
+
// Save to localStorage
|
| 240 |
+
localStorage.setItem('dayCamUrl', dayCamUrl);
|
| 241 |
+
localStorage.setItem('thermalCamUrl', thermalCamUrl);
|
| 242 |
+
localStorage.setItem('wsUrl', wsUrl);
|
| 243 |
+
|
| 244 |
+
// Connect to WebSocket
|
| 245 |
+
connectWebSocket(wsUrl);
|
| 246 |
+
|
| 247 |
+
// Load camera feeds
|
| 248 |
+
if (dayCamUrl) {
|
| 249 |
+
dayCamFeed.src = dayCamUrl;
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
if (thermalCamUrl) {
|
| 253 |
+
thermalCamFeed.src = thermalCamUrl;
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
configModal.classList.add('hidden');
|
| 257 |
+
});
|
| 258 |
+
|
| 259 |
+
// Control button events
|
| 260 |
+
controlButtons.forEach(button => {
|
| 261 |
+
button.addEventListener('mousedown', () => {
|
| 262 |
+
const command = button.getAttribute('data-command');
|
| 263 |
+
sendCommand(command, 'start');
|
| 264 |
+
});
|
| 265 |
+
|
| 266 |
+
button.addEventListener('mouseup', () => {
|
| 267 |
+
const command = button.getAttribute('data-command');
|
| 268 |
+
sendCommand(command, 'stop');
|
| 269 |
+
});
|
| 270 |
+
|
| 271 |
+
button.addEventListener('mouseleave', () => {
|
| 272 |
+
const command = button.getAttribute('data-command');
|
| 273 |
+
sendCommand(command, 'stop');
|
| 274 |
+
});
|
| 275 |
+
});
|
| 276 |
+
|
| 277 |
+
// WebSocket functions
|
| 278 |
+
function connectWebSocket(url) {
|
| 279 |
+
if (socket) {
|
| 280 |
+
socket.close();
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
socket = new WebSocket(url);
|
| 284 |
+
|
| 285 |
+
socket.onopen = () => {
|
| 286 |
+
connectionStatus.innerHTML = '<span class="w-3 h-3 rounded-full bg-green-500 mr-2"></span><span>Connected</span>';
|
| 287 |
+
console.log('WebSocket connected');
|
| 288 |
+
};
|
| 289 |
+
|
| 290 |
+
socket.onclose = () => {
|
| 291 |
+
connectionStatus.innerHTML = '<span class="w-3 h-3 rounded-full bg-red-500 mr-2"></span><span>Disconnected</span>';
|
| 292 |
+
console.log('WebSocket disconnected');
|
| 293 |
+
};
|
| 294 |
+
|
| 295 |
+
socket.onerror = (error) => {
|
| 296 |
+
console.error('WebSocket error:', error);
|
| 297 |
+
};
|
| 298 |
+
|
| 299 |
+
socket.onmessage = (event) => {
|
| 300 |
+
const data = JSON.parse(event.data);
|
| 301 |
+
console.log('Received:', data);
|
| 302 |
+
|
| 303 |
+
// Update position indicators
|
| 304 |
+
if (data.type === 'position') {
|
| 305 |
+
if (data.camera === 'day') {
|
| 306 |
+
document.getElementById('dayPan').textContent = `${data.pan}°`;
|
| 307 |
+
document.getElementById('dayTilt').textContent = `${data.tilt}°`;
|
| 308 |
+
document.getElementById('dayZoom').textContent = `${data.zoom}x`;
|
| 309 |
+
} else if (data.camera === 'thermal') {
|
| 310 |
+
document.getElementById('thermalPan').textContent = `${data.pan}°`;
|
| 311 |
+
document.getElementById('thermalTilt').textContent = `${data.tilt}°`;
|
| 312 |
+
document.getElementById('thermalZoom').textContent = `${data.zoom}x`;
|
| 313 |
+
}
|
| 314 |
+
}
|
| 315 |
+
};
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
function sendCommand(command, action) {
|
| 319 |
+
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
| 320 |
+
console.error('WebSocket is not connected');
|
| 321 |
+
return;
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
const activeCamera = dayCameraContainer.classList.contains('hidden') ? 'thermal' : 'day';
|
| 325 |
+
const message = {
|
| 326 |
+
type: 'control',
|
| 327 |
+
camera: activeCamera,
|
| 328 |
+
command: command,
|
| 329 |
+
action: action
|
| 330 |
+
};
|
| 331 |
+
|
| 332 |
+
socket.send(JSON.stringify(message));
|
| 333 |
+
console.log('Sent:', message);
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
// Load saved configuration
|
| 337 |
+
function loadConfig() {
|
| 338 |
+
const dayCamUrl = localStorage.getItem('dayCamUrl');
|
| 339 |
+
const thermalCamUrl = localStorage.getItem('thermalCamUrl');
|
| 340 |
+
const wsUrl = localStorage.getItem('wsUrl') || 'ws://localhost:8080';
|
| 341 |
+
|
| 342 |
+
if (dayCamUrl) {
|
| 343 |
+
document.getElementById('dayCamUrl').value = dayCamUrl;
|
| 344 |
+
dayCamFeed.src = dayCamUrl;
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
if (thermalCamUrl) {
|
| 348 |
+
document.getElementById('thermalCamUrl').value = thermalCamUrl;
|
| 349 |
+
thermalCamFeed.src = thermalCamUrl;
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
document.getElementById('wsUrl').value = wsUrl;
|
| 353 |
+
connectWebSocket(wsUrl);
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
// Initialize
|
| 357 |
+
loadConfig();
|
| 358 |
+
feather.replace();
|
| 359 |
+
</script>
|
| 360 |
+
</body>
|
| 361 |
</html>
|