This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

1import "./global.css"; 2import * as faceapi from "face-api.js"; 3 4let port: SerialPort; 5let videoElement: HTMLVideoElement; 6let lastError = 0; 7let integral = 0; 8 9// PID constants 10const Kp = 0.5; 11const Ki = 0.1; 12const Kd = 0.2; 13 14// Check if Serial API is supported 15if (!("serial" in navigator)) { 16 alert( 17 "Web Serial API is not supported in this browser. Please use Chrome or Edge.", 18 ); 19} 20 21const createTemplate = () => ` 22 <div style="display: flex; flex-direction: row; height: 100vh;"> 23 <div style="display: flex; flex-direction: column; align-items: center; justify-content: center; flex: 1; gap: 20px;"> 24 <button id="connect">Connect Serial Port</button> 25 <button id="startTracking">Start Face Tracking</button> 26 <video id="webcam" width="640" height="480" autoplay muted></video> 27 <canvas id="overlay" style="position: absolute;"></canvas> 28 29 <div> 30 <label>Left Motor (1) Rotations:</label> 31 <input type="range" id="motor1" min="-10" max="10" step="0.1" value="0"> 32 <span id="motor1Value">0</span> 33 <button id="send1">Send</button> 34 </div> 35 36 <div> 37 <label>Right Motor (2) Rotations:</label> 38 <input type="range" id="motor2" min="-10" max="10" step="0.1" value="0"> 39 <span id="motor2Value">0</span> 40 <button id="send2">Send</button> 41 </div> 42 </div> 43 <div style="width: 300px; padding: 20px; border-left: 1px solid #ccc; overflow-y: auto;"> 44 <h3>Serial Log</h3> 45 <pre id="serialLog" style="white-space: pre-wrap; margin: 0;"></pre> 46 </div> 47 </div> 48`; 49 50async function loadFaceDetectionModels() { 51 await faceapi.nets.tinyFaceDetector.loadFromUri("/models"); 52 await faceapi.nets.faceLandmark68Net.loadFromUri("/models"); 53} 54 55async function startWebcam() { 56 try { 57 const stream = await navigator.mediaDevices.getUserMedia({ video: true }); 58 videoElement.srcObject = stream; 59 } catch (err) { 60 console.error("Error accessing webcam:", err); 61 alert("Failed to access webcam"); 62 } 63} 64 65function calculatePID(error: number) { 66 integral += error; 67 const derivative = error - lastError; 68 lastError = error; 69 70 return Kp * error + Ki * integral + Kd * derivative; 71} 72 73async function trackFaces() { 74 const canvas = document.getElementById("overlay") as HTMLCanvasElement; 75 canvas.width = videoElement.width; 76 canvas.height = videoElement.height; 77 const displaySize = { 78 width: videoElement.width, 79 height: videoElement.height, 80 }; 81 82 setInterval(async () => { 83 const detections = await faceapi.detectAllFaces( 84 videoElement, 85 new faceapi.TinyFaceDetectorOptions(), 86 ); 87 88 if (detections.length > 0) { 89 const face = detections[0]; 90 const centerX = face.box.x + face.box.width / 2; 91 const targetX = videoElement.width / 2; 92 const error = (centerX - targetX) / videoElement.width; 93 94 const adjustment = calculatePID(error); 95 await sendMotorCommand(1, adjustment); 96 await sendMotorCommand(2, -adjustment); 97 98 // Draw face detection 99 const context = canvas.getContext("2d"); 100 if (context) { 101 context.clearRect(0, 0, canvas.width, canvas.height); 102 faceapi.draw.drawDetections(canvas, detections); 103 } 104 } 105 }, 100); 106} 107 108async function connectSerial() { 109 try { 110 port = await navigator.serial.requestPort(); 111 await port.open({ baudRate: 115200 }); 112 113 if (port.writable == null) { 114 throw new Error("Failed to open serial port - port is not writable"); 115 } 116 117 console.log("Connected to serial port"); 118 appendToLog("Connected to serial port"); 119 120 while (port.readable) { 121 const reader = port.readable.getReader(); 122 try { 123 while (true) { 124 const { value, done } = await reader.read(); 125 if (done) break; 126 const decoded = new TextDecoder().decode(value); 127 appendToLog(decoded); 128 } 129 } catch (error) { 130 console.error(error); 131 } finally { 132 reader.releaseLock(); 133 } 134 } 135 } catch (err) { 136 console.error("Serial port error:", err); 137 alert( 138 "Failed to open serial port. Please check your connection and permissions.", 139 ); 140 } 141} 142 143function appendToLog(message: string) { 144 const log = document.getElementById("serialLog"); 145 if (log) { 146 log.textContent += message + "\n"; 147 log.scrollTop = log.scrollHeight; 148 } 149} 150 151async function sendMotorCommand(motorNum: number, rotation: number) { 152 if (!port) { 153 alert("Please connect serial port first"); 154 return; 155 } 156 157 if (!port.writable) { 158 alert("Serial port is not writable"); 159 return; 160 } 161 162 const writer = port.writable.getWriter(); 163 const encoder = new TextEncoder(); 164 const data = `${motorNum} ${rotation}\r`; 165 166 try { 167 await writer.write(encoder.encode(data)); 168 appendToLog(`Sent to motor ${motorNum}: ${rotation} rotations`); 169 } catch (err) { 170 console.error("Write error:", err); 171 appendToLog(`Error sending to motor ${motorNum}: ${err}`); 172 } finally { 173 writer.releaseLock(); 174 } 175} 176 177function defaultPageRender() { 178 const app = document.querySelector<HTMLDivElement>("#app"); 179 if (!app) throw new Error("App element not found"); 180 app.innerHTML = createTemplate(); 181 182 videoElement = document.getElementById("webcam") as HTMLVideoElement; 183 184 document.getElementById("motor1")?.addEventListener("input", (e) => { 185 const value = (e.target as HTMLInputElement).value; 186 const display = document.getElementById("motor1Value"); 187 if (display) display.textContent = value; 188 }); 189 190 document.getElementById("motor2")?.addEventListener("input", (e) => { 191 const value = (e.target as HTMLInputElement).value; 192 const display = document.getElementById("motor2Value"); 193 if (display) display.textContent = value; 194 }); 195 196 document.getElementById("connect")?.addEventListener("click", connectSerial); 197 document 198 .getElementById("startTracking") 199 ?.addEventListener("click", async () => { 200 await loadFaceDetectionModels(); 201 await startWebcam(); 202 trackFaces(); 203 }); 204 document.getElementById("send1")?.addEventListener("click", () => { 205 const value = (document.getElementById("motor1") as HTMLInputElement).value; 206 sendMotorCommand(1, parseFloat(value)); 207 }); 208 document.getElementById("send2")?.addEventListener("click", () => { 209 const value = (document.getElementById("motor2") as HTMLInputElement).value; 210 sendMotorCommand(2, parseFloat(value)); 211 }); 212} 213 214function handleRoute() { 215 defaultPageRender(); 216} 217 218handleRoute();