secutorpro commited on
Commit
61e6960
·
verified ·
1 Parent(s): 98f1d13

🐳 27/05 - 18:47 - cree un api pour le connecté avec des apk radio poc

Browse files
Files changed (2) hide show
  1. components/navbar.js +8 -0
  2. radio-poc.html +642 -0
components/navbar.js CHANGED
@@ -171,6 +171,14 @@ class CustomNavbar extends HTMLElement {
171
  <i data-feather="shield"></i>
172
  <span>Security</span>
173
  </a>
 
 
 
 
 
 
 
 
174
  </div>
175
  </div>
176
  </nav>
 
171
  <i data-feather="shield"></i>
172
  <span>Security</span>
173
  </a>
174
+ <a href="crm.html" class="nav-link">
175
+ <i data-feather="users"></i>
176
+ <span>CRM</span>
177
+ </a>
178
+ <a href="radio-poc.html" class="nav-link">
179
+ <i data-feather="radio"></i>
180
+ <span>Radio POC API</span>
181
+ </a>
182
  </div>
183
  </div>
184
  </nav>
radio-poc.html ADDED
@@ -0,0 +1,642 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Radio POC API - ComSync Pro</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/feather-icons"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.net.min.js"></script>
11
+ <link rel="stylesheet" href="style.css">
12
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
13
+ <style>
14
+ .ptt-button {
15
+ transition: all 0.1s ease;
16
+ box-shadow: 0 10px 30px rgba(239, 68, 68, 0.3);
17
+ user-select: none;
18
+ -webkit-user-select: none;
19
+ touch-action: none;
20
+ }
21
+ .ptt-button:active, .ptt-button.active {
22
+ transform: scale(0.95);
23
+ box-shadow: 0 5px 15px rgba(239, 68, 68, 0.5), inset 0 0 20px rgba(0,0,0,0.2);
24
+ background: linear-gradient(135deg, #dc2626, #b91c1c);
25
+ }
26
+ .api-log-line {
27
+ font-family: 'Courier New', Courier, monospace;
28
+ font-size: 0.75rem;
29
+ border-left: 3px solid transparent;
30
+ }
31
+ .api-log-line.info { border-left-color: #3b82f6; }
32
+ .api-log-line.success { border-left-color: #10b981; }
33
+ .api-log-line.warn { border-left-color: #f59e0b; }
34
+ .api-log-line.error { border-left-color: #ef4444; }
35
+ .api-log-line.ptt { border-left-color: #ef4444; }
36
+ .channel-pill {
37
+ transition: all 0.2s ease;
38
+ }
39
+ .channel-pill.active {
40
+ background: rgba(59, 130, 246, 0.3);
41
+ border-color: #3b82f6;
42
+ color: #60a5fa;
43
+ }
44
+ .glass-card {
45
+ background: rgba(31, 41, 55, 0.7);
46
+ backdrop-filter: blur(20px);
47
+ border: 1px solid rgba(75, 85, 99, 0.4);
48
+ }
49
+ .metric-card::before {
50
+ content: '';
51
+ position: absolute;
52
+ top: 0;
53
+ left: 0;
54
+ right: 0;
55
+ height: 4px;
56
+ background: linear-gradient(90deg, #ef4444, #f59e0b);
57
+ border-radius: 0.75rem 0.75rem 0 0;
58
+ }
59
+ .metric-card {
60
+ position: relative;
61
+ overflow: hidden;
62
+ }
63
+ </style>
64
+ </head>
65
+ <body class="bg-gray-900 text-gray-100 font-inter overflow-x-hidden">
66
+ <div id="vanta-bg" class="fixed inset-0 z-0"></div>
67
+ <script src="components/navbar.js"></script>
68
+ <custom-navbar></custom-navbar>
69
+
70
+ <main class="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
71
+ <!-- Header -->
72
+ <div class="mb-8 slide-in-bottom">
73
+ <div class="flex flex-col md:flex-row md:items-center justify-between gap-4">
74
+ <div>
75
+ <h2 class="text-3xl font-bold text-white mb-2">Radio POC API Connector</h2>
76
+ <p class="text-gray-400">Connecteur SDK pour APK Android Radio PTT — Bridge Push-to-Talk & Fréquences FM</p>
77
+ </div>
78
+ <div class="flex items-center gap-3">
79
+ <div class="px-3 py-1 bg-blue-900/30 rounded-full text-blue-400 text-sm border border-blue-500/30 font-mono" id="api-version">v1.0.0-poc</div>
80
+ <div class="px-3 py-1 bg-green-900/30 rounded-full text-green-400 text-sm border border-green-500/30 flex items-center gap-2">
81
+ <span class="w-2 h-2 bg-green-500 rounded-full animate-pulse"></span>
82
+ Endpoint Actif
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </div>
87
+
88
+ <!-- Stats -->
89
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
90
+ <div class="metric-card glass-card rounded-xl p-6">
91
+ <div class="flex items-center justify-between mb-4">
92
+ <div class="p-3 bg-blue-900/30 rounded-xl"><i data-feather="smartphone" class="w-6 h-6 text-blue-400"></i></div>
93
+ <span class="text-green-400 text-sm">+2 aujourd'hui</span>
94
+ </div>
95
+ <p class="text-3xl font-bold text-white" id="stat-devices">0</p>
96
+ <p class="text-gray-400">Devices Connectés</p>
97
+ </div>
98
+ <div class="metric-card glass-card rounded-xl p-6">
99
+ <div class="flex items-center justify-between mb-4">
100
+ <div class="p-3 bg-red-900/30 rounded-xl"><i data-feather="mic" class="w-6 h-6 text-red-400"></i></div>
101
+ <span class="text-gray-400 text-sm">Session</span>
102
+ </div>
103
+ <p class="text-3xl font-bold text-white" id="stat-ptt">0</p>
104
+ <p class="text-gray-400">Sessions PTT</p>
105
+ </div>
106
+ <div class="metric-card glass-card rounded-xl p-6">
107
+ <div class="flex items-center justify-between mb-4">
108
+ <div class="p-3 bg-purple-900/30 rounded-xl"><i data-feather="radio" class="w-6 h-6 text-purple-400"></i></div>
109
+ <span class="text-yellow-400 text-sm font-mono" id="current-channel-display">CH 1</span>
110
+ </div>
111
+ <p class="text-3xl font-bold text-white" id="current-freq-display">462.550</p>
112
+ <p class="text-gray-400">MHz Active</p>
113
+ </div>
114
+ <div class="metric-card glass-card rounded-xl p-6">
115
+ <div class="flex items-center justify-between mb-4">
116
+ <div class="p-3 bg-green-900/30 rounded-xl"><i data-feather="activity" class="w-6 h-6 text-green-400"></i></div>
117
+ <span class="text-green-400 text-sm font-mono" id="api-uptime">00:00</span>
118
+ </div>
119
+ <p class="text-3xl font-bold text-white" id="stat-latency">12</p>
120
+ <p class="text-gray-400">ms Latence</p>
121
+ </div>
122
+ </div>
123
+
124
+ <!-- Main Grid -->
125
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
126
+ <!-- Left: APK Device Bridge -->
127
+ <div class="space-y-6">
128
+ <div class="glass-card rounded-xl p-6">
129
+ <h3 class="text-xl font-semibold text-white mb-4 flex items-center">
130
+ <i data-feather="smartphone" class="w-5 h-5 mr-3 text-blue-400"></i>
131
+ APK Device Bridge
132
+ </h3>
133
+ <div id="device-form" class="space-y-4">
134
+ <div>
135
+ <label class="block text-sm font-medium mb-2">Package Name (APK)</label>
136
+ <input type="text" id="apk-package" class="w-full bg-gray-700/50 rounded-lg px-3 py-2 border border-gray-600 text-white text-sm focus:outline-none focus:border-blue-500" placeholder="com.secuvox.ptt" value="com.secuvox.ptt">
137
+ </div>
138
+ <div class="grid grid-cols-2 gap-3">
139
+ <div>
140
+ <label class="block text-sm font-medium mb-2">Marque</label>
141
+ <select id="apk-brand" class="w-full bg-gray-700/50 rounded-lg px-3 py-2 border border-gray-600 text-white text-sm focus:outline-none focus:border-blue-500">
142
+ <option>Motorola</option>
143
+ <option>Kenwood</option>
144
+ <option>Hytera</option>
145
+ <option>Crosscall</option>
146
+ <option>CAT</option>
147
+ <option>Generic</option>
148
+ </select>
149
+ </div>
150
+ <div>
151
+ <label class="block text-sm font-medium mb-2">Modèle</label>
152
+ <input type="text" id="apk-model" class="w-full bg-gray-700/50 rounded-lg px-3 py-2 border border-gray-600 text-white text-sm focus:outline-none focus:border-blue-500" placeholder="EVO 4G">
153
+ </div>
154
+ </div>
155
+ <div>
156
+ <label class="block text-sm font-medium mb-2">IMEI / Device ID</label>
157
+ <input type="text" id="apk-imei" class="w-full bg-gray-700/50 rounded-lg px-3 py-2 border border-gray-600 text-white text-sm focus:outline-none focus:border-blue-500" placeholder="354601080768XXX">
158
+ </div>
159
+ <div>
160
+ <label class="block text-sm font-medium mb-2">Bluetooth MAC</label>
161
+ <input type="text" id="apk-mac" class="w-full bg-gray-700/50 rounded-lg px-3 py-2 border border-gray-600 text-white text-sm focus:outline-none focus:border-blue-500" placeholder="AA:BB:CC:DD:EE:FF">
162
+ </div>
163
+ <div class="pt-2">
164
+ <button onclick="apiRegisterDevice()" class="w-full btn-primary flex items-center justify-center gap-2 py-3">
165
+ <i data-feather="link" class="w-4 h-4"></i> Enregistrer & Connecter
166
+ </button>
167
+ </div>
168
+ </div>
169
+
170
+ <div id="device-connected-panel" class="hidden space-y-4">
171
+ <div class="bg-green-900/20 border border-green-700/50 rounded-lg p-4 text-center">
172
+ <div class="w-12 h-12 bg-green-600 rounded-full flex items-center justify-center mx-auto mb-3">
173
+ <i data-feather="check" class="w-6 h-6 text-white"></i>
174
+ </div>
175
+ <h4 class="text-white font-semibold mb-1">Appareil Connecté</h4>
176
+ <p class="text-sm text-gray-400" id="connected-device-name">-</p>
177
+ <p class="text-xs text-green-400 mt-1 font-mono" id="connected-device-id">-</p>
178
+ </div>
179
+ <div class="grid grid-cols-2 gap-3">
180
+ <button onclick="apiDisconnect()" class="btn-secondary w-full text-sm py-2">Déconnecter</button>
181
+ <button onclick="apiForceSync()" class="btn-primary w-full text-sm flex items-center justify-center gap-1 py-2">
182
+ <i data-feather="refresh-cw" class="w-3 h-3"></i> Sync
183
+ </button>
184
+ </div>
185
+ </div>
186
+ </div>
187
+
188
+ <!-- Channel Manager -->
189
+ <div class="glass-card rounded-xl p-6">
190
+ <h3 class="text-lg font-semibold text-white mb-4 flex items-center">
191
+ <i data-feather="list" class="w-5 h-5 mr-2 text-purple-400"></i>
192
+ Canaux Prédéfinis
193
+ </h3>
194
+ <div class="grid grid-cols-3 gap-2" id="channel-grid">
195
+ <!-- JS populated -->
196
+ </div>
197
+ <div class="mt-4 pt-4 border-t border-gray-700">
198
+ <label class="block text-sm font-medium mb-2">Fréquence Manuelle (MHz)</label>
199
+ <div class="flex gap-2">
200
+ <input type="number" id="custom-freq" class="flex-1 bg-gray-700/50 rounded-lg px-3 py-2 border border-gray-600 text-white text-sm focus:outline-none focus:border-blue-500" step="0.001" value="462.550">
201
+ <button onclick="apiSetFrequency(parseFloat(document.getElementById('custom-freq').value))" class="px-3 py-2 bg-blue-600 rounded-lg hover:bg-blue-700 text-sm transition-colors">
202
+ <i data-feather="check" class="w-4 h-4"></i>
203
+ </button>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ </div>
208
+
209
+ <!-- Center: PTT Control -->
210
+ <div class="space-y-6">
211
+ <div class="glass-card rounded-xl p-6 h-full flex flex-col">
212
+ <h3 class="text-xl font-semibold text-white mb-4 flex items-center">
213
+ <i data-feather="mic" class="w-5 h-5 mr-3 text-red-400"></i>
214
+ PTT Control Center
215
+ </h3>
216
+
217
+ <div class="flex-1 flex flex-col items-center justify-center py-8">
218
+ <div class="relative mb-6">
219
+ <div class="absolute inset-0 bg-red-500/20 rounded-full blur-xl" id="ptt-glow" style="opacity:0.2"></div>
220
+ <button
221
+ id="ptt-btn"
222
+ class="ptt-button relative w-40 h-40 rounded-full bg-gradient-to-br from-red-500 to-red-700 border-4 border-red-400/50 flex items-center justify-center text-white font-bold text-lg outline-none"
223
+ onmousedown="apiPttPress()"
224
+ onmouseup="apiPttRelease()"
225
+ onmouseleave="apiPttRelease()"
226
+ ontouchstart="apiPttPress()"
227
+ ontouchend="apiPttRelease()"
228
+ >
229
+ <div class="flex flex-col items-center gap-1 pointer-events-none">
230
+ <i data-feather="mic" class="w-10 h-10"></i>
231
+ <span id="ptt-label">PUSH</span>
232
+ </div>
233
+ </button>
234
+ </div>
235
+
236
+ <div class="w-full max-w-xs space-y-4">
237
+ <div>
238
+ <div class="flex justify-between text-xs text-gray-400 mb-1">
239
+ <span>Signal TX</span>
240
+ <span id="tx-level">0%</span>
241
+ </div>
242
+ <div class="h-2 bg-gray-700 rounded-full overflow-hidden">
243
+ <div id="tx-bar" class="h-full bg-red-500 rounded-full transition-all duration-100" style="width: 0%"></div>
244
+ </div>
245
+ </div>
246
+ <div>
247
+ <div class="flex justify-between text-xs text-gray-400 mb-1">
248
+ <span>Signal RX</span>
249
+ <span id="rx-level">0%</span>
250
+ </div>
251
+ <div class="h-2 bg-gray-700 rounded-full overflow-hidden">
252
+ <div id="rx-bar" class="h-full bg-blue-500 rounded-full transition-all duration-300" style="width: 0%"></div>
253
+ </div>
254
+ </div>
255
+ </div>
256
+ </div>
257
+
258
+ <div class="grid grid-cols-2 gap-3 mt-4">
259
+ <button onclick="apiSendMessage('RADIO CHECK')" class="p-3 bg-gray-700/50 rounded-lg hover:bg-gray-700 text-sm font-medium transition-colors border border-gray-600">
260
+ 📡 Radio Check
261
+ </button>
262
+ <button onclick="apiSendMessage('EMERGENCY')" class="p-3 bg-red-900/30 border border-red-700/50 rounded-lg hover:bg-red-900/50 text-red-400 text-sm font-medium transition-colors">
263
+ 🚨 Emergency
264
+ </button>
265
+ </div>
266
+ </div>
267
+ </div>
268
+
269
+ <!-- Right: API Terminal -->
270
+ <div class="space-y-6">
271
+ <div class="glass-card rounded-xl p-6 flex flex-col h-full max-h-[800px]">
272
+ <div class="flex items-center justify-between mb-4">
273
+ <h3 class="text-lg font-semibold text-white flex items-center">
274
+ <i data-feather="terminal" class="w-5 h-5 mr-2 text-green-400"></i>
275
+ API Live Log
276
+ </h3>
277
+ <div class="flex gap-2">
278
+ <button onclick="clearLog()" class="p-2 bg-gray-700 rounded-lg hover:bg-gray-600 transition-colors" title="Effacer">
279
+ <i data-feather="trash-2" class="w-4 h-4"></i>
280
+ </button>
281
+ <button onclick="exportLog()" class="p-2 bg-gray-700 rounded-lg hover:bg-gray-600 transition-colors" title="Exporter">
282
+ <i data-feather="download" class="w-4 h-4"></i>
283
+ </button>
284
+ </div>
285
+ </div>
286
+ <div class="flex-1 bg-gray-900/80 rounded-lg border border-gray-700 p-3 overflow-y-auto space-y-1" id="api-log">
287
+ <div class="text-gray-500 italic text-xs font-mono">Waiting for API calls...</div>
288
+ </div>
289
+ <div class="mt-3 flex gap-2">
290
+ <input type="text" id="inject-command" class="flex-1 bg-gray-700/50 rounded-lg px-3 py-2 border border-gray-600 text-white text-xs font-mono focus:outline-none focus:border-purple-500" placeholder="Inject: ComSyncRadioAPI.getStatus()">
291
+ <button onclick="injectCommand()" class="px-3 py-2 bg-purple-600 rounded-lg hover:bg-purple-700 transition-colors text-white">
292
+ <i data-feather="play" class="w-4 h-4"></i>
293
+ </button>
294
+ </div>
295
+ </div>
296
+ </div>
297
+ </div>
298
+
299
+ <!-- API Reference -->
300
+ <div class="mt-8">
301
+ <h3 class="text-xl font-semibold text-white mb-4 flex items-center">
302
+ <i data-feather="code" class="w-5 h-5 mr-3 text-yellow-400"></i>
303
+ API Reference — SDK Android / WebView
304
+ </h3>
305
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" id="api-reference">
306
+ <!-- Filled by JS -->
307
+ </div>
308
+ </div>
309
+ </main>
310
+
311
+ <script>
312
+ // ========== ComSync Radio POC API v1.0 ==========
313
+ window.ComSyncRadioAPI = {
314
+ version: '1.0.0-poc',
315
+ device: null,
316
+ connected: false,
317
+ channel: 1,
318
+ frequency: 462.550,
319
+ pttActive: false,
320
+ listeners: {},
321
+ pttCount: 0,
322
+
323
+ registerDevice(info) {
324
+ const id = 'APK-' + Date.now().toString(36).toUpperCase();
325
+ this.device = { id, ...info, registeredAt: new Date().toISOString(), status: 'registered' };
326
+ storeLog('info', `Device registered [${id}] — ${info.brand} ${info.model}`);
327
+ this.emit('deviceRegistered', this.device);
328
+ return this.device;
329
+ },
330
+
331
+ connect(deviceId) {
332
+ if (!this.device) throw new Error('No device registered');
333
+ this.device.status = 'online';
334
+ this.connected = true;
335
+ storeLog('success', `CONNECT — Device ${deviceId || this.device.id} linked to ComSync network`);
336
+ this.emit('connected', { device: this.device, channel: this.channel, frequency: this.frequency });
337
+ updateUI();
338
+ return { success: true, latency: 12 };
339
+ },
340
+
341
+ disconnect() {
342
+ this.connected = false;
343
+ if (this.device) this.device.status = 'offline';
344
+ this.pttActive = false;
345
+ storeLog('warn', 'DISCONNECT — Device unlinked from network');
346
+ this.emit('disconnected', {});
347
+ updateUI();
348
+ return { success: true };
349
+ },
350
+
351
+ pttPress() {
352
+ if (!this.connected) { storeLog('error', 'PTT denied — device offline'); return { error: 'Device offline' }; }
353
+ if (this.pttActive) return { success: true };
354
+ this.pttActive = true;
355
+ this.pttCount++;
356
+ document.getElementById('stat-ptt').textContent = this.pttCount;
357
+ storeLog('ptt', 'PTT PRESS — TX started on ' + this.frequency + ' MHz');
358
+ this.emit('pttStart', { frequency: this.frequency, channel: this.channel });
359
+ simulateTx();
360
+ return { success: true, tx: true };
361
+ },
362
+
363
+ pttRelease() {
364
+ if (!this.pttActive) return { success: true, tx: false };
365
+ this.pttActive = false;
366
+ storeLog('ptt', 'PTT RELEASE — TX stopped');
367
+ this.emit('pttStop', {});
368
+ stopTx();
369
+ return { success: true, tx: false };
370
+ },
371
+
372
+ sendMessage(text) {
373
+ if (!this.connected) return { error: 'Device offline' };
374
+ storeLog('success', `MESSAGE — "${text}" sent on CH${this.channel}`);
375
+ this.emit('messageSent', { text, channel: this.channel, frequency: this.frequency });
376
+ return { success: true, timestamp: Date.now() };
377
+ },
378
+
379
+ setFrequency(freq) {
380
+ this.frequency = parseFloat(freq);
381
+ storeLog('info', `FREQ CHANGE — ${this.frequency.toFixed(3)} MHz`);
382
+ this.emit('frequencyChanged', { frequency: this.frequency });
383
+ updateUI();
384
+ return { success: true, frequency: this.frequency };
385
+ },
386
+
387
+ setChannel(ch) {
388
+ this.channel = parseInt(ch);
389
+ const map = {1:462.550,2:467.550,3:151.940,4:154.570,5:446.006,6:446.031,7:446.056,8:446.081,9:446.106,10:446.131};
390
+ if (map[this.channel]) this.frequency = map[this.channel];
391
+ storeLog('info', `CHANNEL — CH${this.channel} (${this.frequency.toFixed(3)} MHz)`);
392
+ this.emit('channelChanged', { channel: this.channel, frequency: this.frequency });
393
+ updateUI();
394
+ return { success: true, channel: this.channel, frequency: this.frequency };
395
+ },
396
+
397
+ getStatus() {
398
+ return {
399
+ version: this.version,
400
+ connected: this.connected,
401
+ device: this.device,
402
+ channel: this.channel,
403
+ frequency: this.frequency,
404
+ ptt: this.pttActive,
405
+ pttCount: this.pttCount,
406
+ timestamp: Date.now()
407
+ };
408
+ },
409
+
410
+ on(event, cb) {
411
+ if (!this.listeners[event]) this.listeners[event] = [];
412
+ this.listeners[event].push(cb);
413
+ },
414
+
415
+ emit(event, data) {
416
+ if (this.listeners[event]) this.listeners[event].forEach(cb => {
417
+ try { cb(data); } catch(e) {}
418
+ });
419
+ }
420
+ };
421
+
422
+ // Logger
423
+ function storeLog(type, msg) {
424
+ const log = document.getElementById('api-log');
425
+ const time = new Date().toLocaleTimeString('fr-FR', {hour12:false}) + '.' + Date.now().toString().slice(-3);
426
+ const line = document.createElement('div');
427
+ line.className = `api-log-line ${type} py-1 px-2 rounded hover:bg-gray-800`;
428
+ const colors = {info:'text-blue-400', success:'text-green-400', warn:'text-yellow-400', error:'text-red-400', ptt:'text-red-300'};
429
+ line.innerHTML = `<span class="text-gray-600 mr-2">${time}</span><span class="${colors[type]||'text-gray-300'}">${msg}</span>`;
430
+ log.appendChild(line);
431
+ log.scrollTop = log.scrollHeight;
432
+ }
433
+
434
+ function clearLog() {
435
+ document.getElementById('api-log').innerHTML = '<div class="text-gray-500 italic text-xs font-mono">Log cleared.</div>';
436
+ }
437
+ function exportLog() {
438
+ const lines = Array.from(document.querySelectorAll('.api-log-line')).map(l => l.innerText).join('\n');
439
+ const blob = new Blob([lines], {type:'text/plain'});
440
+ const a = document.createElement('a');
441
+ a.href = URL.createObjectURL(blob);
442
+ a.download = `comsync-radio-api-${Date.now()}.log`;
443
+ a.click();
444
+ }
445
+ function injectCommand() {
446
+ const cmd = document.getElementById('inject-command').value;
447
+ try {
448
+ const res = eval(cmd);
449
+ storeLog('info', `INJECT — ${cmd} => ${JSON.stringify(res).slice(0,120)}`);
450
+ } catch(e) {
451
+ storeLog('error', `INJECT ERROR — ${e.message}`);
452
+ }
453
+ }
454
+
455
+ // TX Simulation
456
+ let txInterval;
457
+ function simulateTx() {
458
+ const bar = document.getElementById('tx-bar');
459
+ const label = document.getElementById('tx-level');
460
+ const glow = document.getElementById('ptt-glow');
461
+ let v = 0;
462
+ txInterval = setInterval(() => {
463
+ v = 60 + Math.random()*35;
464
+ bar.style.width = v+'%';
465
+ label.textContent = Math.round(v)+'%';
466
+ glow.style.opacity = (v/100).toFixed(2);
467
+ }, 100);
468
+ }
469
+ function stopTx() {
470
+ clearInterval(txInterval);
471
+ document.getElementById('tx-bar').style.width = '0%';
472
+ document.getElementById('tx-level').textContent = '0%';
473
+ document.getElementById('ptt-glow').style.opacity = '0.2';
474
+ }
475
+
476
+ // UI Actions
477
+ function apiRegisterDevice() {
478
+ const brand = document.getElementById('apk-brand').value;
479
+ const model = document.getElementById('apk-model').value || 'Unknown';
480
+ const imei = document.getElementById('apk-imei').value || '000000000000000';
481
+ const mac = document.getElementById('apk-mac').value || 'AA:BB:CC:DD:EE:FF';
482
+ const pkg = document.getElementById('apk-package').value || 'com.unknown.ptt';
483
+ const dev = window.ComSyncRadioAPI.registerDevice({ brand, model, imei, mac, package: pkg });
484
+ window.ComSyncRadioAPI.connect(dev.id);
485
+
486
+ document.getElementById('device-form').classList.add('hidden');
487
+ document.getElementById('device-connected-panel').classList.remove('hidden');
488
+ document.getElementById('connected-device-name').textContent = `${brand} ${model}`;
489
+ document.getElementById('connected-device-id').textContent = dev.id;
490
+
491
+ let devices = JSON.parse(localStorage.getItem('comsync_devices')||'[]');
492
+ devices.push({
493
+ id: dev.id,
494
+ name: `${brand} ${model}`,
495
+ type: 'android',
496
+ brand, model,
497
+ status:'online',
498
+ protocol:'Radio POC API v1',
499
+ lastSync: new Date().toLocaleString(),
500
+ imei,
501
+ macAddress: mac
502
+ });
503
+ localStorage.setItem('comsync_devices', JSON.stringify(devices));
504
+ }
505
+
506
+ function apiDisconnect() {
507
+ window.ComSyncRadioAPI.disconnect();
508
+ document.getElementById('device-form').classList.remove('hidden');
509
+ document.getElementById('device-connected-panel').classList.add('hidden');
510
+ }
511
+ function apiForceSync() {
512
+ storeLog('info', 'SYNC — Broadcasting state sync to all network nodes');
513
+ window.ComSyncRadioAPI.emit('sync', { timestamp: Date.now(), uptime: document.getElementById('api-uptime').textContent });
514
+ }
515
+ function apiPttPress() {
516
+ window.ComSyncRadioAPI.pttPress();
517
+ document.getElementById('ptt-btn').classList.add('active');
518
+ document.getElementById('ptt-label').textContent = 'TX';
519
+ }
520
+ function apiPttRelease() {
521
+ if (!window.ComSyncRadioAPI.pttActive) return;
522
+ window.ComSyncRadioAPI.pttRelease();
523
+ document.getElementById('ptt-btn').classList.remove('active');
524
+ document.getElementById('ptt-label').textContent = 'PUSH';
525
+ }
526
+ function apiSendMessage(text) {
527
+ window.ComSyncRadioAPI.sendMessage(text);
528
+ }
529
+ function apiSetFrequency(freq) {
530
+ window.ComSyncRadioAPI.setFrequency(freq);
531
+ }
532
+
533
+ // Channels
534
+ const channels = [
535
+ {ch:1, freq:462.550, name:'FRS 1'},
536
+ {ch:2, freq:467.550, name:'FRS 2'},
537
+ {ch:3, freq:151.940, name:'MURS 1'},
538
+ {ch:4, freq:154.570, name:'MURS 2'},
539
+ {ch:5, freq:446.006, name:'PMR 1'},
540
+ {ch:6, freq:446.031, name:'PMR 2'},
541
+ {ch:7, freq:446.056, name:'PMR 3'},
542
+ {ch:8, freq:446.081, name:'PMR 4'},
543
+ {ch:9, freq:446.106, name:'PMR 5'},
544
+ {ch:10, freq:446.131, name:'PMR 6'},
545
+ ];
546
+ function renderChannels() {
547
+ const grid = document.getElementById('channel-grid');
548
+ grid.innerHTML = channels.map(c => `
549
+ <button onclick="window.ComSyncRadioAPI.setChannel(${c.ch})" class="channel-pill p-2 rounded-lg border border-gray-600 bg-gray-700/30 text-xs text-gray-300 text-center hover:bg-gray-700" id="ch-${c.ch}">
550
+ <div class="font-bold">${c.ch}</div>
551
+ <div class="text-gray-500 text-[10px]">${c.freq}</div>
552
+ </button>
553
+ `).join('');
554
+ }
555
+
556
+ // API Reference
557
+ const endpoints = [
558
+ {method:'registerDevice(info)', desc:'Enregistre un APK Android (brand, model, imei, mac, package). Retourne le device object avec ID unique.', color:'blue'},
559
+ {method:'connect(deviceId)', desc:'Connecte l\'appareil au réseau ComSync. Nécessite un device pré-enregistré via registerDevice.', color:'green'},
560
+ {method:'disconnect()', desc:'Déconnecte l\'appareil du réseau et libère le canal radio alloué.', color:'red'},
561
+ {method:'pttPress()', desc:'Active le Push-to-Talk. Démarre l\'émission TX sur la fréquence active. Bloquant si offline.', color:'red'},
562
+ {method:'pttRelease()', desc:'Relâche le PTT. Arrête l\'émission TX et repasse en mode réception RX.', color:'orange'},
563
+ {method:'sendMessage(text)', desc:'Envoi un message texte/data sur le canal actif (mode digital/packet).', color:'purple'},
564
+ {method:'setFrequency(mhz)', desc:'Règle la fréquence radio manuellement (ex: 462.550). Écrase le canal courant.', color:'blue'},
565
+ {method:'setChannel(n)', desc:'Change de canal prédéfini (1-10) avec mapping automatique FRS/PMR/MURS.', color:'blue'},
566
+ {method:'getStatus()', desc:'Retourne l\'état complet: device, connected, channel, frequency, ptt, timestamp.', color:'gray'},
567
+ {method:'on(event, callback)', desc:'S\'abonne aux événements: connected, pttStart, pttStop, messageSent, frequencyChanged, sync.', color:'yellow'},
568
+ ];
569
+ function renderReference() {
570
+ document.getElementById('api-reference').innerHTML = endpoints.map(e => `
571
+ <div class="glass-card rounded-xl p-4 border-l-4 border-${e.color}-500 hover:border-${e.color}-400 transition-colors">
572
+ <h4 class="font-mono text-sm font-bold text-${e.color}-400 mb-2">ComSyncRadioAPI.${e.method}</h4>
573
+ <p class="text-xs text-gray-400 leading-relaxed">${e.desc}</p>
574
+ </div>
575
+ `).join('');
576
+ }
577
+
578
+ function updateUI() {
579
+ const st = window.ComSyncRadioAPI.getStatus();
580
+ document.getElementById('current-freq-display').textContent = st.frequency.toFixed(3);
581
+ document.getElementById('current-channel-display').textContent = 'CH ' + st.channel;
582
+ document.getElementById('stat-devices').textContent = st.connected ? '1' : '0';
583
+ document.querySelectorAll('.channel-pill').forEach(b => b.classList.remove('active'));
584
+ const chBtn = document.getElementById('ch-'+st.channel);
585
+ if (chBtn) chBtn.classList.add('active');
586
+ }
587
+
588
+ // RX Simulation background
589
+ setInterval(() => {
590
+ if (Math.random() > 0.7 && window.ComSyncRadioAPI.connected) {
591
+ const rx = Math.random()*40 + 10;
592
+ document.getElementById('rx-bar').style.width = rx+'%';
593
+ document.getElementById('rx-level').textContent = Math.round(rx)+'%';
594
+ if (Math.random() > 0.85) {
595
+ storeLog('info', `RX — Signal detected on CH${window.ComSyncRadioAPI.channel} (${rx.toFixed(0)}% strength)`);
596
+ }
597
+ } else {
598
+ document.getElementById('rx-bar').style.width = '0%';
599
+ document.getElementById('rx-level').textContent = '0%';
600
+ }
601
+ }, 800);
602
+
603
+ // Latency simulation
604
+ setInterval(() => {
605
+ document.getElementById('stat-latency').textContent = Math.floor(8 + Math.random()*12);
606
+ }, 2000);
607
+
608
+ // Uptime
609
+ let sec = 0;
610
+ setInterval(() => {
611
+ sec++;
612
+ const m = Math.floor(sec/60).toString().padStart(2,'0');
613
+ const s = (sec%60).toString().padStart(2,'0');
614
+ document.getElementById('api-uptime').textContent = `${m}:${s}`;
615
+ }, 1000);
616
+
617
+ // Init
618
+ renderChannels();
619
+ renderReference();
620
+ updateUI();
621
+
622
+ if (typeof VANTA !== 'undefined') {
623
+ VANTA.NET({
624
+ el: "#vanta-bg",
625
+ mouseControls: true,
626
+ touchControls: true,
627
+ gyroControls: false,
628
+ minHeight: 200,
629
+ minWidth: 200,
630
+ scale: 1,
631
+ scaleMobile: 1,
632
+ color: 0xef4444,
633
+ backgroundColor: 0x111827,
634
+ points: 10,
635
+ maxDistance: 22,
636
+ spacing: 18
637
+ });
638
+ }
639
+ feather.replace();
640
+ </script>
641
+ </body>
642
+ </html>