diff --git a/CIE1931xy_CIERGB.svg b/CIE1931xy_CIERGB.svg
new file mode 100644
index 0000000..f05f9d5
--- /dev/null
+++ b/CIE1931xy_CIERGB.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..70efe76
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,48 @@
+# OPT4048 CIE Color Plotter
+
+This web interface allows you to visualize color measurements from an Adafruit OPT4048 color sensor in real-time using the Web Serial API.
+
+## How to Use
+
+1. **Upload the Arduino sketch**: First, upload the `opt4048_webserial.ino` sketch from the examples folder to your Arduino board.
+
+2. **Connect to this web page**: You can either:
+ - Host the page locally
+ - Use GitHub Pages (if this repository is published there)
+
+3. **Connect to your Arduino**: Click the "Connect to Arduino" button and select your Arduino from the popup menu.
+
+4. **View measurements**: The sensor readings will appear on the CIE chromaticity diagram, showing you exactly where the measured color falls in the CIE color space.
+
+## Features
+
+- Displays CIE x,y coordinates in real-time
+- Plots the color point on a standard CIE 1931 chromaticity diagram
+- Shows lux (brightness) and color temperature (CCT) values
+- Provides approximate RGB color visualization
+- Monitors serial output for debugging
+
+## Browser Compatibility
+
+This interface uses the Web Serial API, which is currently supported in:
+- Google Chrome (version 89+)
+- Microsoft Edge (version 89+)
+- Opera (version 75+)
+
+It is **not** supported in Firefox or Safari due to their Web Serial API implementation status.
+
+## About the OPT4048 Sensor
+
+The OPT4048 is a high-precision tristimulus XYZ color sensor by Texas Instruments. The Adafruit breakout board makes it easy to interface with this sensor using I2C.
+
+This sensor provides accurate color measurements in XYZ color space, which can be converted to standard CIE 1931 xy chromaticity coordinates.
+
+## File Structure
+
+- `index.html` - The main webpage
+- `script.js` - JavaScript code for communication and visualization
+- `cie1931_diagram.svg` - SVG image of the CIE 1931 chromaticity diagram
+
+## License
+
+MIT license, all text here must be included in any redistribution
\ No newline at end of file
diff --git a/docs/cie1931_diagram.svg b/docs/cie1931_diagram.svg
new file mode 100644
index 0000000..f05f9d5
--- /dev/null
+++ b/docs/cie1931_diagram.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..ebeb066
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,156 @@
+
+
+
+
+
+ OPT4048 CIE Color Plotter
+
+
+
+
OPT4048 CIE Color Plotter
+
Connect your Arduino with OPT4048 sensor to visualize color measurements on a CIE diagram.
+
+
+
+
+
+
+
+
Connection Status
+
Not connected
+
+
Serial Monitor
+
+
+
+
+
CIE 1931 Chromaticity Diagram
+
+
+
+
+
+
+
+
CIE x
+
-
+
+
+
CIE y
+
-
+
+
+
Lux
+
-
+
+
+
CCT (K)
+
-
+
+
+
+
+
Color Approximation
+
+ (Note: This is a rough approximation)
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/script.js b/docs/script.js
new file mode 100644
index 0000000..9b81e8a
--- /dev/null
+++ b/docs/script.js
@@ -0,0 +1,283 @@
+// 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');
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/opt4048_webserial/opt4048_webserial.ino b/examples/opt4048_webserial/opt4048_webserial.ino
new file mode 100644
index 0000000..9c938fd
--- /dev/null
+++ b/examples/opt4048_webserial/opt4048_webserial.ino
@@ -0,0 +1,79 @@
+/*!
+ * @file opt4048_webserial.ino
+ *
+ * This example reads color data from the OPT4048 sensor and outputs it
+ * in a format suitable for displaying on a web page using Web Serial API.
+ *
+ * It continuously measures CIE x,y coordinates, lux, and color temperature.
+ */
+
+#include
+#include "Adafruit_OPT4048.h"
+
+// Create sensor object
+Adafruit_OPT4048 sensor;
+
+// Set how often to read data (in milliseconds)
+const unsigned long READ_INTERVAL = 100;
+unsigned long lastReadTime = 0;
+
+void setup() {
+ // Initialize serial communication at 115200 baud
+ Serial.begin(115200);
+
+ // Wait briefly for serial to connect (not needed for all boards)
+ delay(100);
+
+ Serial.println(F("Adafruit OPT4048 WebSerial Example"));
+ Serial.println(F("This sketch works with the OPT4048 CIE Color Plotter web page"));
+
+ // Initialize the sensor
+ if (!sensor.begin()) {
+ Serial.println(F("Failed to find OPT4048 chip"));
+ while (1) {
+ delay(10);
+ }
+ }
+
+ Serial.println(F("OPT4048 sensor found!"));
+
+ // Set sensor configuration
+ sensor.setRange(OPT4048_RANGE_AUTO); // Auto-range for best results across lighting conditions
+ sensor.setConversionTime(OPT4048_CONVERSION_TIME_100MS); // 100ms conversion time
+ sensor.setMode(OPT4048_MODE_CONTINUOUS); // Continuous mode
+}
+
+void loop() {
+ // Only read at the specified interval
+ unsigned long currentTime = millis();
+ if (currentTime - lastReadTime >= READ_INTERVAL) {
+ lastReadTime = currentTime;
+
+ // Calculate and display CIE chromaticity coordinates and lux
+ double CIEx, CIEy, lux;
+ if (sensor.getCIE(&CIEx, &CIEy, &lux)) {
+ // Print the values in a format that can be easily parsed by the web page
+ Serial.println(F("---CIE Data---"));
+ Serial.print(F("CIE x: ")); Serial.println(CIEx, 8);
+ Serial.print(F("CIE y: ")); Serial.println(CIEy, 8);
+ Serial.print(F("Lux: ")); Serial.println(lux, 4);
+
+ // Calculate and display color temperature
+ double colorTemp = sensor.calculateColorTemperature(CIEx, CIEy);
+ Serial.print(F("Color Temperature: "));
+ Serial.print(colorTemp, 2);
+ Serial.println(F(" K"));
+ Serial.println(F("-------------"));
+ } else {
+ Serial.println(F("Error reading sensor data"));
+ }
+ }
+
+ // Check for any incoming serial commands
+ if (Serial.available() > 0) {
+ String command = Serial.readStringUntil('\n');
+ command.trim();
+
+ // Process any commands here if needed
+ }
+}
\ No newline at end of file