-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwrapper.html
More file actions
188 lines (161 loc) · 8.58 KB
/
wrapper.html
File metadata and controls
188 lines (161 loc) · 8.58 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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SCE | BLETrack Central</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
body { font-family: 'Inter', sans-serif; }
</style>
</head>
<body class="bg-slate-950 text-slate-100 min-h-screen p-6">
<nav class="max-w-6xl mx-auto mb-10 flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
<div>
<h1 class="text-3xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-indigo-500">
BLETrack <span class="text-slate-600 font-light text-xl">Lab Presence</span>
</h1>
<div id="conn-badge" class="mt-1 text-xs font-mono text-red-500 flex items-center gap-2">
<span class="w-2 h-2 bg-red-500 rounded-full animate-pulse"></span> OFFLINE
</div>
</div>
<button onclick="triggerActiveEnroll()"
class="bg-indigo-600 hover:bg-indigo-500 text-white px-6 py-2.5 rounded-xl font-semibold transition-all shadow-lg shadow-indigo-900/20 active:scale-95">
Start Discovery Mode
</button>
</nav>
<div class="max-w-6xl mx-auto grid grid-cols-1 lg:grid-cols-3 gap-8">
<div class="lg:col-span-2">
<h3 class="text-slate-400 font-bold text-sm uppercase tracking-widest mb-4">Active in SCE</h3>
<div id="user-grid" class="grid grid-cols-1 md:grid-cols-2 gap-4">
</div>
</div>
<div class="bg-slate-900/50 rounded-3xl p-6 border border-slate-800 h-fit">
<h3 class="text-slate-400 font-bold text-sm uppercase tracking-widest mb-4">Nearby (Unregistered)</h3>
<div id="discovery-list" class="space-y-3">
<p id="discovery-empty" class="text-slate-600 text-sm italic">No new devices detected within 1.5m.</p>
</div>
</div>
</div>
<div id="modal" class="hidden fixed inset-0 bg-slate-950/90 backdrop-blur-md flex items-center justify-center p-4 z-50">
<div class="bg-slate-900 p-8 rounded-3xl w-full max-w-md border border-slate-800 shadow-2xl">
<h2 class="text-2xl font-bold mb-1 text-white">Enroll Member</h2>
<p id="target-id" class="text-[10px] font-mono text-indigo-400 mb-6 bg-black/40 p-3 rounded-lg break-all"></p>
<label class="block text-sm font-medium text-slate-400 mb-2">Display Name</label>
<input type="text" id="member-name" placeholder="e.g. andrew_dover"
class="w-full bg-slate-950 border border-slate-800 rounded-xl p-4 mb-6 focus:ring-2 focus:ring-indigo-500 outline-none transition-all placeholder:text-slate-700">
<div class="flex gap-4">
<button onclick="closeModal()" class="flex-1 px-4 py-3 text-slate-500 hover:text-white transition-colors">Cancel</button>
<button onclick="submitEnrollment()" class="flex-1 bg-indigo-600 hover:bg-indigo-500 py-3 rounded-xl font-bold shadow-lg shadow-indigo-900/40">Register</button>
</div>
</div>
</div>
<script>
// CONFIG - UPDATE THESE
const MQTT_CONFIG = {
host: "10.250.102.89", // Your laptop/Docker IP
port: 9001, // Mosquitto Websocket Port
room: "sce" // Your ESPresense Room Name
};
const client = new Paho.MQTT.Client(MQTT_CONFIG.host, MQTT_CONFIG.port, "sce_ui_" + Math.random().toString(16).substr(2, 5));
let selectedId = null;
// --- MQTT Core Logic ---
client.onMessageArrived = (message) => {
const topic = message.destinationName;
const data = JSON.parse(message.payloadString);
const idParts = topic.split('/');
const rawId = idParts[2];
// If topic is phone:name, it goes to User Grid
if (rawId.startsWith('phone:')) {
const name = rawId.split(':')[1];
updateUserCard(name, data.distance);
}
// If topic is raw HEX and very close, it goes to Discovery
else if (data.distance < 1.5) {
updateDiscoveryList(rawId, data.distance);
}
};
function triggerActiveEnroll() {
try {
const topic = `espresense/rooms/${MQTT_CONFIG.room}/enroll/set`;
const message = new Paho.MQTT.Message("enroll_mode|SCE_Discovery");
message.destinationName = topic;
client.send(message);
alert("Discovery Mode Active for 2 mins! Look for 'SCE_Discovery' on your phone.");
} catch (e) {
console.error("Discovery trigger failed:", e);
}
}
// --- UI Updates ---
function updateUserCard(name, distance) {
let card = document.getElementById(`card-${name}`);
const isPresent = distance < 2.5; // Hysteresis threshold
if (!card) {
card = document.createElement('div');
card.id = `card-${name}`;
document.getElementById('user-grid').appendChild(card);
}
card.className = `p-6 rounded-3xl border transition-all duration-700 ${isPresent ? 'bg-slate-900 border-indigo-500/30' : 'bg-slate-950/40 border-slate-800 opacity-50'}`;
card.innerHTML = `
<div class="flex justify-between items-start mb-4">
<h4 class="font-bold text-xl capitalize text-slate-200">${name}</h4>
<span class="text-[10px] font-bold px-2 py-0.5 rounded ${isPresent ? 'bg-indigo-500/20 text-indigo-400' : 'bg-slate-800 text-slate-600'} uppercase tracking-tighter">
${isPresent ? 'Present' : 'Away'}
</span>
</div>
<div class="flex items-baseline gap-1">
<span class="text-2xl font-mono font-bold">${distance.toFixed(2)}</span>
<span class="text-slate-500 text-sm">meters</span>
</div>
`;
}
function updateDiscoveryList(id, distance) {
document.getElementById('discovery-empty').classList.add('hidden');
let item = document.getElementById(`disc-${id}`);
if (!item) {
item = document.createElement('div');
item.id = `disc-${id}`;
item.className = "p-4 bg-slate-900 rounded-2xl border border-slate-800 hover:border-indigo-500 cursor-pointer transition-all";
item.onclick = () => openModal(id);
document.getElementById('discovery-list').prepend(item);
}
item.innerHTML = `
<div class="flex justify-between items-center text-sm font-mono">
<span class="text-slate-400 text-xs">${id.substr(0,12)}...</span>
<span class="text-indigo-400 font-bold">${distance.toFixed(2)}m</span>
</div>
`;
}
// --- Modal Logic ---
function openModal(id) {
selectedId = id;
document.getElementById('target-id').innerText = id;
document.getElementById('modal').classList.remove('hidden');
}
function closeModal() { document.getElementById('modal').classList.add('hidden'); }
function submitEnrollment() {
const name = document.getElementById('member-name').value.trim().toLowerCase().replace(/\s+/g, '_');
if (!name) return;
const topic = `espresense/settings/fingerprints/phone:${name}`;
const msg = new Paho.MQTT.Message(selectedId);
msg.destinationName = topic;
msg.retained = true;
client.send(msg);
alert(`Enrolled ${name}! Card will appear shortly.`);
closeModal();
location.reload();
}
// --- Init ---
client.connect({
onSuccess: () => {
document.getElementById('conn-badge').innerHTML = `<span class="w-2 h-2 bg-green-500 rounded-full"></span> BROKER CONNECTED`;
document.getElementById('conn-badge').className = "mt-1 text-xs font-mono text-green-500 flex items-center gap-2";
client.subscribe("espresense/devices/+/sce");
},
onFailure: (e) => { console.error(e); },
useSSL: false
});
</script>
</body>
</html>