-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
399 lines (358 loc) · 23.5 KB
/
Copy pathindex.html
File metadata and controls
399 lines (358 loc) · 23.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="theme-color" content="#111827">
<title>Ballistix V3 Command</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
::-webkit-scrollbar { display: none; }
body.night-mode { background-color: #000000 !important; color: #aa0000 !important; }
body.night-mode .bg-gray-900 { background-color: #000000 !important; }
body.night-mode .bg-gray-800 { background-color: #1a0000 !important; }
body.night-mode .text-white { color: #ff4444 !important; }
body.night-mode .text-cyan-400 { color: #ff0000 !important; }
body.night-mode .text-yellow-400 { color: #ff6600 !important; }
body.night-mode .border-gray-800, body.night-mode .border-gray-700 { border-color: #330000 !important; }
body.night-mode .bg-green-600 { background-color: #660000 !important; color: #ffaaaa !important; }
body.night-mode .bg-cyan-600 { background-color: #440000 !important; color: #ffaaaa !important; }
body.night-mode input { background-color: #000000 !important; color: #ff0000 !important; border-color: #440000 !important;}
body.night-mode .accent-cyan-500 { accent-color: #ff0000 !important; }
</style>
</head>
<body class="bg-gray-900 text-white font-sans antialiased pb-20 transition-colors duration-300">
<div class="fixed top-0 w-full bg-gray-900 border-b border-gray-800 z-50 px-4 py-3 flex justify-between items-center">
<h1 class="text-xl font-bold text-cyan-400">BLV3 UPLINK</h1>
<div class="flex items-center space-x-4">
<span id="batIndicator" class="text-sm font-mono text-green-400 font-bold tracking-widest hidden">BAT: --%</span>
<button id="connectBtn" class="bg-gray-700 hover:bg-gray-600 px-3 py-1 rounded text-sm font-bold transition-colors">
Connect
</button>
</div>
</div>
<div class="pt-16 px-4 max-w-md mx-auto">
<!-- SCREEN 1: MAIN (STAGE CONTROL & VIRTUAL D-PAD) -->
<div id="screen-main" class="app-screen block">
<h2 class="text-gray-400 text-sm font-bold mb-4 tracking-wider">STAGE CONTROL</h2>
<div class="bg-gray-800 p-4 rounded-lg mb-4">
<label class="block text-xs text-gray-400 mb-2">STAGE TIME (MM:SS)</label>
<div class="flex items-center justify-center space-x-2 mb-3">
<input type="number" id="stageTimeMin" value="2" min="0" class="w-full bg-gray-900 text-3xl text-yellow-400 font-bold border border-gray-700 rounded p-3 text-center">
<span class="text-3xl font-bold text-gray-500">:</span>
<input type="number" id="stageTimeSec" value="00" min="0" max="59" class="w-full bg-gray-900 text-3xl text-yellow-400 font-bold border border-gray-700 rounded p-3 text-center">
</div>
<button onclick="sendStageTime()" class="w-full bg-cyan-600 hover:bg-cyan-500 text-white font-bold py-2 rounded transition-colors text-sm">
UPDATE STAGE TIME
</button>
</div>
<h2 class="text-gray-400 text-sm font-bold mb-4 tracking-wider">VIRTUAL D-PAD</h2>
<div class="bg-gray-800 p-6 rounded-lg flex justify-center items-center mb-4">
<div class="grid grid-cols-3 gap-2 w-48">
<div></div>
<button onclick="sendBtnCmd('up')" class="bg-gray-700 active:bg-cyan-600 h-14 rounded-t-lg flex justify-center items-center font-bold">UP</button>
<div></div>
<button onclick="sendBtnCmd('left')" class="bg-gray-700 active:bg-cyan-600 h-14 rounded-l-lg flex justify-center items-center font-bold">L</button>
<button onclick="sendBtnCmd('select')" class="bg-gray-600 active:bg-green-500 h-14 rounded flex justify-center items-center font-bold">SEL</button>
<button onclick="sendBtnCmd('right')" class="bg-gray-700 active:bg-cyan-600 h-14 rounded-r-lg flex justify-center items-center font-bold">R</button>
<div></div>
<button onclick="sendBtnCmd('down')" class="bg-gray-700 active:bg-cyan-600 h-14 rounded-b-lg flex justify-center items-center font-bold">DWN</button>
<div></div>
</div>
</div>
<p id="statusText" class="text-center text-sm text-gray-500 mt-2">Not Connected</p>
</div>
<!-- SCREEN 2: STAGE BUILDER (NRL HUNTER 4-TARGETS) -->
<div id="screen-stage" class="app-screen hidden">
<!-- Target HTML Omitted for brevity (same as your original) -->
<h2 class="text-gray-400 text-sm font-bold mb-4 tracking-wider">NRL HUNTER STAGE TARGETS</h2>
<div class="space-y-3 mb-4">
<!-- Target 1 -->
<div class="bg-gray-800 p-3 rounded-lg border border-gray-700">
<span class="text-xs font-bold text-cyan-400 block mb-2">TARGET 1</span>
<div class="grid grid-cols-3 gap-2">
<div><label class="text-[10px] text-gray-400 block">YARDS</label><input type="number" id="t1y" value="400" class="w-full bg-gray-900 text-white font-bold p-2 text-center border border-gray-700 rounded"></div>
<div><label class="text-[10px] text-gray-400 block">ELEV (M)</label><input type="number" step="0.1" id="t1e" value="3.5" class="w-full bg-gray-900 text-yellow-400 font-bold p-2 text-center border border-gray-700 rounded"></div>
<div><label class="text-[10px] text-gray-400 block">WIND (M)</label><input type="number" step="0.1" id="t1w" value="0.5" class="w-full bg-gray-900 text-white font-bold p-2 text-center border border-gray-700 rounded"></div>
</div>
</div>
<!-- Target 2 -->
<div class="bg-gray-800 p-3 rounded-lg border border-gray-700">
<span class="text-xs font-bold text-cyan-400 block mb-2">TARGET 2</span>
<div class="grid grid-cols-3 gap-2">
<div><label class="text-[10px] text-gray-400 block">YARDS</label><input type="number" id="t2y" value="550" class="w-full bg-gray-900 text-white font-bold p-2 text-center border border-gray-700 rounded"></div>
<div><label class="text-[10px] text-gray-400 block">ELEV (M)</label><input type="number" step="0.1" id="t2e" value="6.2" class="w-full bg-gray-900 text-yellow-400 font-bold p-2 text-center border border-gray-700 rounded"></div>
<div><label class="text-[10px] text-gray-400 block">WIND (M)</label><input type="number" step="0.1" id="t2w" value="0.8" class="w-full bg-gray-900 text-white font-bold p-2 text-center border border-gray-700 rounded"></div>
</div>
</div>
<!-- Target 3 -->
<div class="bg-gray-800 p-3 rounded-lg border border-gray-700">
<span class="text-xs font-bold text-cyan-400 block mb-2">TARGET 3</span>
<div class="grid grid-cols-3 gap-2">
<div><label class="text-[10px] text-gray-400 block">YARDS</label><input type="number" id="t3y" value="700" class="w-full bg-gray-900 text-white font-bold p-2 text-center border border-gray-700 rounded"></div>
<div><label class="text-[10px] text-gray-400 block">ELEV (M)</label><input type="number" step="0.1" id="t3e" value="9.4" class="w-full bg-gray-900 text-yellow-400 font-bold p-2 text-center border border-gray-700 rounded"></div>
<div><label class="text-[10px] text-gray-400 block">WIND (M)</label><input type="number" step="0.1" id="t3w" value="1.2" class="w-full bg-gray-900 text-white font-bold p-2 text-center border border-gray-700 rounded"></div>
</div>
</div>
<!-- Target 4 -->
<div class="bg-gray-800 p-3 rounded-lg border border-gray-700">
<span class="text-xs font-bold text-cyan-400 block mb-2">TARGET 4</span>
<div class="grid grid-cols-3 gap-2">
<div><label class="text-[10px] text-gray-400 block">YARDS</label><input type="number" id="t4y" value="850" class="w-full bg-gray-900 text-white font-bold p-2 text-center border border-gray-700 rounded"></div>
<div><label class="text-[10px] text-gray-400 block">ELEV (M)</label><input type="number" step="0.1" id="t4e" value="13.1" class="w-full bg-gray-900 text-yellow-400 font-bold p-2 text-center border border-gray-700 rounded"></div>
<div><label class="text-[10px] text-gray-400 block">WIND (M)</label><input type="number" step="0.1" id="t4w" value="1.6" class="w-full bg-gray-900 text-white font-bold p-2 text-center border border-gray-700 rounded"></div>
</div>
</div>
</div>
<button onclick="sendStageSync()" class="w-full bg-cyan-600 hover:bg-cyan-500 text-white font-bold py-3 rounded-lg transition-colors text-sm shadow-lg mb-6">
SYNC STAGE TO BLV3
</button>
</div>
<!-- SCREEN 3: DOPE -->
<div id="screen-dope" class="app-screen hidden">
<h2 class="text-gray-400 text-sm font-bold mb-4 tracking-wider">BALLISTICS ENGINE (DEFAULT ELD MATCH)</h2>
<div id="dopeContainer" class="bg-gray-800 p-4 rounded-lg mb-4 font-mono text-sm">
<div class="flex justify-between text-cyan-400 border-b border-gray-700 pb-2 mb-2 font-bold">
<span>YARDS</span>
<span>ELEV (MRAD)</span>
</div>
<div id="dopeList" class="space-y-2 text-gray-300">
<p class="text-center text-gray-500 italic py-6">Default 140gr ELD Match Baseline Table Loaded on BLV3</p>
</div>
</div>
</div>
<!-- SCREEN 4: ATMOSPHERICS -->
<div id="screen-weather" class="app-screen hidden">
<h2 class="text-gray-400 text-sm font-bold mb-4 tracking-wider">LIVE SENSOR TELEMETRY</h2>
<div class="bg-gray-800 p-4 rounded-lg mb-4 space-y-4">
<div class="flex justify-between items-center border-b border-gray-700 pb-2">
<span class="text-gray-400 text-xs">DENSITY ALTITUDE</span>
<span id="telemetryDA" class="text-yellow-400 font-mono font-bold text-lg">--- ft</span>
</div>
<div class="flex justify-between items-center border-b border-gray-700 pb-2">
<span class="text-gray-400 text-xs">TEMPERATURE</span>
<span id="telemetryTemp" class="text-white font-mono font-bold text-lg">--- °F</span>
</div>
<div class="flex justify-between items-center border-b border-gray-700 pb-2">
<span class="text-gray-400 text-xs">PRESSURE</span>
<span id="telemetryPres" class="text-white font-mono font-bold text-lg">--- hPa</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-400 text-xs">BAROMETRIC ALT</span>
<span id="telemetryAlt" class="text-white font-mono font-bold text-lg">--- ft</span>
</div>
</div>
<button onclick="requestWeatherData()" class="w-full bg-cyan-600 hover:bg-cyan-500 text-white font-bold py-2 rounded transition-colors text-sm">
REFRESH SENSORS
</button>
</div>
<!-- SCREEN 5: SETTINGS -->
<div id="screen-settings" class="app-screen hidden">
<h2 class="text-gray-400 text-sm font-bold mb-4 tracking-wider">DEVICE SETTINGS</h2>
<div class="bg-gray-800 p-4 rounded-lg mb-4 flex justify-between items-center">
<span class="font-bold">NIGHT VISION MODE</span>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="nightModeToggle" class="sr-only peer">
<div class="w-11 h-6 bg-gray-600 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-red-600"></div>
</label>
</div>
<div class="bg-gray-800 p-4 rounded-lg mb-4">
<div class="flex justify-between items-center mb-2">
<label class="text-xs text-gray-400">AUDIO VOLUME</label>
<span id="volVal" class="text-xs font-mono text-cyan-400 font-bold">75%</span>
</div>
<input type="range" id="volSlider" min="0" max="100" value="75" class="w-full accent-cyan-500" oninput="updateVolLabel(this.value)" onchange="sendSettings()">
</div>
<button id="calBtn" class="w-full bg-orange-600 text-white font-bold text-lg py-4 rounded-lg shadow-lg mb-4">
CALIBRATE ZERO LEVEL
</button>
<!-- NEW FORGET REMOTE BUTTON -->
<button id="resetRemoteBtn" class="w-full bg-gray-800 border border-red-900 text-red-500 font-bold text-sm py-3 rounded-lg shadow-lg mb-4 hover:bg-red-900 hover:text-white transition-colors">
FORGET PAIRED REMOTE
</button>
</div>
</div>
<!-- BOTTOM NAVIGATION TABS (5 TABS) -->
<div class="fixed bottom-0 w-full bg-gray-900 border-t border-gray-800 pb-safe">
<div class="flex justify-around items-center p-1">
<button onclick="switchTab('main')" id="tab-main" class="nav-btn text-cyan-400 flex flex-col items-center p-2 w-1/5">
<span class="text-lg mb-0.5">⏱️</span>
<span class="text-[9px] font-bold">MAIN</span>
</button>
<button onclick="switchTab('stage')" id="tab-stage" class="nav-btn text-gray-500 flex flex-col items-center p-2 w-1/5">
<span class="text-lg mb-0.5">🎯</span>
<span class="text-[9px] font-bold">STAGE</span>
</button>
<button onclick="switchTab('dope')" id="tab-dope" class="nav-btn text-gray-500 flex flex-col items-center p-2 w-1/5">
<span class="text-lg mb-0.5">📈</span>
<span class="text-[9px] font-bold">DOPE</span>
</button>
<button onclick="switchTab('weather')" id="tab-weather" class="nav-btn text-gray-500 flex flex-col items-center p-2 w-1/5">
<span class="text-lg mb-0.5">☁️</span>
<span class="text-[9px] font-bold">ATMOS</span>
</button>
<button onclick="switchTab('settings')" id="tab-settings" class="nav-btn text-gray-500 flex flex-col items-center p-2 w-1/5">
<span class="text-lg mb-0.5">⚙️</span>
<span class="text-[9px] font-bold">SETUPS</span>
</button>
</div>
</div>
<!-- JAVASCRIPT LOGIC -->
<script>
const SERVICE_UUID = "4fafc201-1fb5-459e-8fcc-c5c9c331914b";
const RX_CHAR_UUID = "beb5483e-36e1-4688-b7f5-ea07361b26a8";
const TX_CHAR_UUID = "12345678-1234-5678-1234-56789abcdef0";
let bleDevice, rxCharacteristic, txCharacteristic;
function updateVolLabel(val) { document.getElementById('volVal').innerText = `${val}%`; }
function switchTab(tabName) {
document.querySelectorAll('.app-screen').forEach(el => { el.classList.add('hidden'); el.classList.remove('block'); });
document.getElementById('screen-' + tabName).classList.remove('hidden', 'block');
document.getElementById('screen-' + tabName).classList.add('block');
document.querySelectorAll('.nav-btn').forEach(el => { el.classList.remove('text-cyan-400'); el.classList.add('text-gray-500'); });
const activeTab = document.getElementById('tab-' + tabName);
activeTab.classList.remove('text-gray-500');
activeTab.classList.add('text-cyan-400');
if(tabName === 'weather' && rxCharacteristic) {
requestWeatherData();
}
}
document.getElementById('nightModeToggle').addEventListener('change', (e) => {
if (e.target.checked) {
document.body.classList.add('night-mode');
} else {
document.body.classList.remove('night-mode');
}
});
document.getElementById("connectBtn").addEventListener("click", async () => {
try {
document.getElementById("statusText").innerText = "Scanning...";
bleDevice = await navigator.bluetooth.requestDevice({
acceptAllDevices: true,
optionalServices: [SERVICE_UUID]
});
const server = await bleDevice.gatt.connect();
const service = await server.getPrimaryService(SERVICE_UUID);
rxCharacteristic = await service.getCharacteristic(RX_CHAR_UUID);
txCharacteristic = await service.getCharacteristic(TX_CHAR_UUID);
await txCharacteristic.startNotifications();
txCharacteristic.addEventListener('characteristicvaluechanged', handleDeviceTelemetry);
document.getElementById("connectBtn").classList.replace("bg-gray-700", "bg-green-600");
document.getElementById("connectBtn").innerText = "Connected";
document.getElementById("statusText").innerText = "Linked via BLE";
document.getElementById("batIndicator").classList.remove("hidden");
} catch (error) {
document.getElementById("statusText").innerText = "Error: " + error;
}
});
function handleDeviceTelemetry(event) {
let value = new TextDecoder().decode(event.target.value);
try {
let data = JSON.parse(value);
if(data.bat !== undefined) {
const batText = document.getElementById("batIndicator");
batText.innerText = `BAT: ${data.bat}%`;
batText.className = data.bat < 20 ? "text-sm font-mono text-red-500 font-bold tracking-widest" : "text-sm font-mono text-green-400 font-bold tracking-widest";
}
if(data.da !== undefined) {
document.getElementById("telemetryDA").innerText = `${data.da > 0 ? '+' : ''}${Math.round(data.da)} ft`;
document.getElementById("telemetryTemp").innerText = `${data.temp.toFixed(1)} °F`;
document.getElementById("telemetryPres").innerText = `${data.pres.toFixed(1)} hPa`;
document.getElementById("telemetryAlt").innerText = `${Math.round(data.alt)} ft`;
}
} catch(e) {
let batteryPct = event.target.value.getUint8(0);
const batText = document.getElementById("batIndicator");
batText.innerText = `BAT: ${batteryPct}%`;
}
}
async function sendBtnCmd(action) {
if (!rxCharacteristic) return;
const payload = JSON.stringify({ cmd: "btn", action: action });
try {
await rxCharacteristic.writeValue(new TextEncoder().encode(payload));
} catch (err) {
console.error("Button command write failed:", err);
}
}
async function sendStageTime() {
if (!rxCharacteristic) return;
const mins = parseInt(document.getElementById('stageTimeMin').value) || 0;
const secs = parseInt(document.getElementById('stageTimeSec').value) || 0;
const totalSecs = (mins * 60) + secs;
const payload = JSON.stringify({ cmd: "stageTime", val: totalSecs });
try {
await rxCharacteristic.writeValue(new TextEncoder().encode(payload));
alert("Stage time updated on device!");
} catch (err) {
console.error("Stage time write failed:", err);
}
}
async function sendStageSync() {
if (!rxCharacteristic) return;
let targets = [];
for (let i = 1; i <= 4; i++) {
targets.push({
y: parseFloat(document.getElementById(`t${i}y`).value) || 0,
e: parseFloat(document.getElementById(`t${i}e`).value) || 0,
w: parseFloat(document.getElementById(`t${i}w`).value) || 0
});
}
const payload = JSON.stringify({ cmd: "stageSync", targets: targets });
try {
await rxCharacteristic.writeValue(new TextEncoder().encode(payload));
alert("NRL Hunter targets synced successfully!");
} catch (err) {
console.error("Stage target sync failed:", err);
}
}
async function sendSettings() {
if (!rxCharacteristic) return;
const vol = parseInt(document.getElementById('volSlider').value) / 100.0;
const payload = JSON.stringify({ cmd: "set", vol: vol });
try {
await rxCharacteristic.writeValue(new TextEncoder().encode(payload));
} catch (err) {
console.error("Settings write failed:", err);
}
}
async function requestWeatherData() {
if (!rxCharacteristic) return;
const payload = JSON.stringify({ cmd: "getEnv" });
try {
await rxCharacteristic.writeValue(new TextEncoder().encode(payload));
} catch (err) {
console.error("Weather request failed:", err);
}
}
document.getElementById("calBtn").addEventListener("click", async () => {
if (!rxCharacteristic) return;
if (window.confirm("Set current position as absolute level?")) {
const payload = JSON.stringify({ cmd: "cal" });
try {
await rxCharacteristic.writeValue(new TextEncoder().encode(payload));
} catch (err) {
console.error("Calibration write failed:", err);
}
}
});
// NEW LOGIC FOR THE FORGET REMOTE BUTTON
document.getElementById("resetRemoteBtn").addEventListener("click", async () => {
if (!rxCharacteristic) {
alert("Please connect to the BLV3 first!");
return;
}
if (window.confirm("Forget the currently paired remote? \n\nThe BLV3 will start scanning immediately. You will need to click a button on the new remote to pair it.")) {
const payload = JSON.stringify({ cmd: "resetRemote" });
try {
await rxCharacteristic.writeValue(new TextEncoder().encode(payload));
alert("Remote memory wiped! The BLV3 is now scanning for a new remote.");
} catch (err) {
console.error("Reset remote write failed:", err);
}
}
});
</script>
</body>
</html>