Adafruit_OPT4048/webserial/script.js
ladyada 74d01a92f0 Move web interface from /docs to /webserial
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>
2025-05-14 10:07:23 -04:00

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');
}
}
}