Relocated the Web Serial interface to avoid conflicts with Doxygen documentation. This change allows both the Doxygen site and the Web Serial interface to coexist. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
283 lines
No EOL
8.3 KiB
JavaScript
283 lines
No EOL
8.3 KiB
JavaScript
// Global variables
|
|
let port;
|
|
let reader;
|
|
let writer;
|
|
let readTimeout;
|
|
let keepReading = false;
|
|
let decoder = new TextDecoder();
|
|
let lineBuffer = '';
|
|
|
|
// DOM Elements
|
|
const connectButton = document.getElementById('connect-button');
|
|
const disconnectButton = document.getElementById('disconnect-button');
|
|
const clearButton = document.getElementById('clear-button');
|
|
const statusDisplay = document.getElementById('status');
|
|
const serialLog = document.getElementById('serial-log');
|
|
const dataPoint = document.getElementById('data-point');
|
|
const cieXDisplay = document.getElementById('cie-x');
|
|
const cieYDisplay = document.getElementById('cie-y');
|
|
const luxDisplay = document.getElementById('lux');
|
|
const cctDisplay = document.getElementById('cct');
|
|
const colorSample = document.getElementById('color-sample');
|
|
|
|
// Check if Web Serial API is supported
|
|
if ('serial' in navigator) {
|
|
connectButton.addEventListener('click', connectToArduino);
|
|
disconnectButton.addEventListener('click', disconnectFromArduino);
|
|
clearButton.addEventListener('click', clearLog);
|
|
} else {
|
|
statusDisplay.textContent = 'Web Serial API not supported in this browser. Try Chrome or Edge.';
|
|
connectButton.disabled = true;
|
|
}
|
|
|
|
// Connect to Arduino via Web Serial
|
|
async function connectToArduino() {
|
|
try {
|
|
// Request a port and open a connection
|
|
port = await navigator.serial.requestPort();
|
|
await port.open({ baudRate: 115200 });
|
|
|
|
// Set up the reader and writer
|
|
reader = port.readable.getReader();
|
|
writer = port.writable.getWriter();
|
|
|
|
// Enable/disable buttons
|
|
connectButton.disabled = true;
|
|
disconnectButton.disabled = false;
|
|
statusDisplay.textContent = 'Connected to Arduino';
|
|
addToLog('Connected to Arduino', 'status');
|
|
|
|
// Start reading data
|
|
keepReading = true;
|
|
readSerialData();
|
|
} catch (error) {
|
|
console.error('Error connecting to Arduino:', error);
|
|
addToLog(`Error connecting: ${error.message}`, 'error');
|
|
statusDisplay.textContent = 'Connection failed';
|
|
}
|
|
}
|
|
|
|
// Disconnect from Arduino
|
|
async function disconnectFromArduino() {
|
|
if (reader) {
|
|
keepReading = false;
|
|
clearTimeout(readTimeout);
|
|
|
|
try {
|
|
await reader.cancel();
|
|
await reader.releaseLock();
|
|
reader = null;
|
|
} catch (error) {
|
|
console.error('Error releasing reader:', error);
|
|
}
|
|
}
|
|
|
|
if (writer) {
|
|
try {
|
|
await writer.close();
|
|
writer = null;
|
|
} catch (error) {
|
|
console.error('Error releasing writer:', error);
|
|
}
|
|
}
|
|
|
|
if (port) {
|
|
try {
|
|
await port.close();
|
|
port = null;
|
|
} catch (error) {
|
|
console.error('Error closing port:', error);
|
|
}
|
|
}
|
|
|
|
// Update UI
|
|
connectButton.disabled = false;
|
|
disconnectButton.disabled = true;
|
|
statusDisplay.textContent = 'Disconnected';
|
|
addToLog('Disconnected from Arduino', 'status');
|
|
hideDataPoint();
|
|
}
|
|
|
|
// Read data from the serial port
|
|
async function readSerialData() {
|
|
while (port && keepReading) {
|
|
try {
|
|
const { value, done } = await reader.read();
|
|
|
|
if (done) {
|
|
// Reader has been canceled
|
|
break;
|
|
}
|
|
|
|
// Process the received data
|
|
processSerialData(decoder.decode(value));
|
|
} catch (error) {
|
|
console.error('Error reading data:', error);
|
|
addToLog(`Error reading data: ${error.message}`, 'error');
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we exited the loop without being explicitly disconnected
|
|
if (keepReading) {
|
|
disconnectFromArduino();
|
|
}
|
|
}
|
|
|
|
// Process data received from Arduino
|
|
function processSerialData(data) {
|
|
// Add received data to the buffer
|
|
lineBuffer += data;
|
|
|
|
// Process complete lines
|
|
let lineEnd;
|
|
while ((lineEnd = lineBuffer.indexOf('\n')) !== -1) {
|
|
const line = lineBuffer.substring(0, lineEnd).trim();
|
|
lineBuffer = lineBuffer.substring(lineEnd + 1);
|
|
|
|
if (line) {
|
|
addToLog(line);
|
|
parseDataFromLine(line);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse data from a line received from Arduino
|
|
function parseDataFromLine(line) {
|
|
// Look for CIE x value
|
|
const cieXMatch = line.match(/CIE x: ([\d.]+)/);
|
|
if (cieXMatch) {
|
|
const cieX = parseFloat(cieXMatch[1]);
|
|
cieXDisplay.textContent = cieX.toFixed(6);
|
|
}
|
|
|
|
// Look for CIE y value
|
|
const cieYMatch = line.match(/CIE y: ([\d.]+)/);
|
|
if (cieYMatch) {
|
|
const cieY = parseFloat(cieYMatch[1]);
|
|
cieYDisplay.textContent = cieY.toFixed(6);
|
|
|
|
// If we have both x and y, update the plot
|
|
if (cieXMatch) {
|
|
const cieX = parseFloat(cieXMatch[1]);
|
|
updateCIEPlot(cieX, cieY);
|
|
}
|
|
}
|
|
|
|
// Look for Lux value
|
|
const luxMatch = line.match(/Lux: ([\d.]+)/);
|
|
if (luxMatch) {
|
|
const lux = parseFloat(luxMatch[1]);
|
|
luxDisplay.textContent = lux.toFixed(2);
|
|
}
|
|
|
|
// Look for Color Temperature value
|
|
const cctMatch = line.match(/Color Temperature: ([\d.]+)/);
|
|
if (cctMatch) {
|
|
const cct = parseFloat(cctMatch[1]);
|
|
cctDisplay.textContent = cct.toFixed(0);
|
|
}
|
|
}
|
|
|
|
// Update the CIE plot with new data point
|
|
function updateCIEPlot(x, y) {
|
|
// Get the dimensions of the CIE diagram image
|
|
const cieImage = document.querySelector('#cie-diagram img');
|
|
const imageRect = cieImage.getBoundingClientRect();
|
|
|
|
// The SVG has a coordinate system where:
|
|
// - X axis is marked from 0.0 to 0.8 (matched to pixel positions 60 to 470)
|
|
// - Y axis is marked from 0.0 to 0.9 (matched to pixel positions 476 to 15)
|
|
|
|
// Define the SVG's coordinate mapping
|
|
const svgXMin = 60, svgXMax = 470; // Left and right edge pixel positions in SVG
|
|
const svgYMin = 476, svgYMax = 15; // Bottom and top edge pixel positions in SVG
|
|
const cieXMin = 0.0, cieXMax = 0.8; // Min and max x chromaticity values
|
|
const cieYMin = 0.0, cieYMax = 0.9; // Min and max y chromaticity values
|
|
|
|
// Map the CIE coordinates to percentages within the SVG viewport
|
|
const svgWidth = svgXMax - svgXMin;
|
|
const svgHeight = svgYMin - svgYMax;
|
|
|
|
// Calculate the percentage position (normalize to SVG viewBox)
|
|
const xPercent = ((x - cieXMin) / (cieXMax - cieXMin)) * 100;
|
|
const yPercent = (1 - ((y - cieYMin) / (cieYMax - cieYMin))) * 100; // Invert y-axis
|
|
|
|
// Set the data point position
|
|
dataPoint.style.left = `${xPercent}%`;
|
|
dataPoint.style.top = `${yPercent}%`;
|
|
dataPoint.style.display = 'block';
|
|
|
|
// Update the color sample with an approximate RGB color
|
|
updateColorSample(x, y);
|
|
}
|
|
|
|
// Convert CIE XYZ to RGB for color approximation
|
|
function updateColorSample(x, y) {
|
|
// Calculate XYZ from xyY (assuming Y=1 for relative luminance)
|
|
const Y = 1.0;
|
|
const X = (x * Y) / y;
|
|
const Z = ((1 - x - y) * Y) / y;
|
|
|
|
// XYZ to RGB conversion (sRGB)
|
|
// Using the standard D65 transformation matrix
|
|
let r = X * 3.2406 - Y * 1.5372 - Z * 0.4986;
|
|
let g = -X * 0.9689 + Y * 1.8758 + Z * 0.0415;
|
|
let b = X * 0.0557 - Y * 0.2040 + Z * 1.0570;
|
|
|
|
// Apply gamma correction
|
|
r = r <= 0.0031308 ? 12.92 * r : 1.055 * Math.pow(r, 1/2.4) - 0.055;
|
|
g = g <= 0.0031308 ? 12.92 * g : 1.055 * Math.pow(g, 1/2.4) - 0.055;
|
|
b = b <= 0.0031308 ? 12.92 * b : 1.055 * Math.pow(b, 1/2.4) - 0.055;
|
|
|
|
// Clamp RGB values between 0 and 1
|
|
r = Math.min(Math.max(0, r), 1);
|
|
g = Math.min(Math.max(0, g), 1);
|
|
b = Math.min(Math.max(0, b), 1);
|
|
|
|
// Convert to 8-bit color values
|
|
const ri = Math.round(r * 255);
|
|
const gi = Math.round(g * 255);
|
|
const bi = Math.round(b * 255);
|
|
|
|
// Set the background color of the sample
|
|
colorSample.style.backgroundColor = `rgb(${ri}, ${gi}, ${bi})`;
|
|
}
|
|
|
|
// Hide the data point and reset all displays
|
|
function hideDataPoint() {
|
|
dataPoint.style.display = 'none';
|
|
cieXDisplay.textContent = '-';
|
|
cieYDisplay.textContent = '-';
|
|
luxDisplay.textContent = '-';
|
|
cctDisplay.textContent = '-';
|
|
colorSample.style.backgroundColor = 'transparent';
|
|
}
|
|
|
|
// Add a message to the serial log
|
|
function addToLog(message, type = 'data') {
|
|
const entry = document.createElement('div');
|
|
entry.textContent = message;
|
|
entry.className = `log-entry ${type}`;
|
|
serialLog.appendChild(entry);
|
|
serialLog.scrollTop = serialLog.scrollHeight;
|
|
}
|
|
|
|
// Clear the serial log
|
|
function clearLog() {
|
|
serialLog.innerHTML = '';
|
|
}
|
|
|
|
// Send a command to the Arduino
|
|
async function sendCommand(command) {
|
|
if (writer) {
|
|
try {
|
|
const encoder = new TextEncoder();
|
|
await writer.write(encoder.encode(command + '\n'));
|
|
addToLog(`Sent: ${command}`, 'command');
|
|
} catch (error) {
|
|
console.error('Error sending command:', error);
|
|
addToLog(`Error sending command: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
} |