websocket server example

This commit is contained in:
foamyguy 2025-05-28 10:12:42 -05:00
parent 40bd717d32
commit 341a4de061
6 changed files with 560 additions and 0 deletions

View file

@ -0,0 +1,3 @@
I, the copyright holder of this work, release this work into the public domain. This applies worldwide.
In some countries this may not be legally possible; if so:
I grant anyone the right to use this work for any purpose, without any conditions, unless such conditions are required by law.

View file

@ -0,0 +1,79 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 Tim C for Adafruit Industries
# SPDX-License-Identifier: MIT
from asyncio import create_task, gather, run
from asyncio import sleep as async_sleep
import board
import socketpool
import wifi
from adafruit_httpserver import GET, FileResponse, Request, Response, Server, Websocket
from adafruit_opt4048 import OPT4048, ConversionTime, Mode, Range
pool = socketpool.SocketPool(wifi.radio)
server = Server(pool, debug=True, root_path="opt4048_ws_static")
websocket: Websocket = None
READ_INTERVAL = 0.1 # seconds
i2c = board.I2C() # uses board.SCL and board.SDA
# i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller
sensor = OPT4048(i2c)
sensor.range = Range.AUTO
sensor.conversion_time = ConversionTime.TIME_100MS
sensor.mode = Mode.CONTINUOUS
@server.route("/connect-websocket", GET)
def connect_client(request: Request):
global websocket # noqa: PLW0603, global use
if websocket is not None:
websocket.close() # Close any existing connection
websocket = Websocket(request)
return websocket
server.start(str(wifi.radio.ipv4_address))
async def handle_http_requests():
while True:
server.poll()
await async_sleep(0)
async def send_color_data_ws():
while True:
if websocket is not None:
try:
x, y, lux = sensor.cie
out_msg = "---CIE Data---\n"
out_msg += f"CIE x: {x}\n"
out_msg += f"CIE y: {y}\n"
out_msg += f"Lux: {lux}\n"
out_msg += f"Color Temperature: {sensor.calculate_color_temperature(x, y)} K\n"
out_msg += "-------------\n"
websocket.send_message(out_msg, fail_silently=True)
except RuntimeError:
# error reading sensor
pass
await async_sleep(READ_INTERVAL)
async def main():
await gather(
create_task(handle_http_requests()),
create_task(send_color_data_ws()),
)
run(main())

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -0,0 +1,2 @@
// SPDX-FileCopyrightText: Copyright (c) 2009 BenRG
// SPDX-License-Identifier: LicenseRef-Wikipedia-Public-Domain

View file

@ -0,0 +1,202 @@
<!--
SPDX-FileCopyrightText: Copyright (c) 2025 Tim C for Adafruit Industries
SPDX-License-Identifier: MIT
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OPT4048 CIE Color Plotter</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 100%;
margin: 0 auto;
padding: 8px;
height: 100vh;
display: flex;
flex-direction: column;
}
.container {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
flex: 1;
}
.controls {
flex: 1;
min-width: 230px;
max-width: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
.visualization {
flex: 2;
min-width: 280px;
max-width: 100%;
display: flex;
flex-direction: column;
}
#cie-diagram {
position: relative;
width: 100%;
max-width: 450px;
margin: 0 auto 10px auto;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
#cie-diagram img {
width: 100%;
max-height: 100%;
object-fit: contain;
display: block;
}
#data-point {
position: absolute;
width: 16px;
height: 16px;
background-color: white;
border-radius: 50%;
border: 3px solid black;
transform: translate(-50%, -50%);
pointer-events: none;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.9);
z-index: 10;
}
#color-sample {
width: 30px;
height: 30px;
border: 1px solid #ccc;
margin: 0 auto;
border-radius: 50%;
}
#serial-log {
flex: 1;
min-height: 80px;
max-height: 150px;
overflow-y: auto;
background-color: #f5f5f5;
padding: 8px;
border: 1px solid #ddd;
font-family: monospace;
font-size: 12px;
margin-bottom: 5px;
}
button {
padding: 8px 12px;
margin: 4px 4px 4px 0;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
button:hover {
background-color: #45a049;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.data-display {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
margin-top: 10px;
}
.data-box {
flex: 1;
min-width: 100px;
margin: 0;
padding: 10px;
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 4px;
text-align: center;
}
.data-box h3 {
margin: 0 0 5px 0;
font-size: 14px;
}
.data-value {
font-size: 18px;
font-weight: bold;
}
/* Responsive adjustments for very small screens */
@media (max-height: 600px) {
h1 { font-size: 20px !important; margin: 5px 0 !important; }
h2 { font-size: 16px; margin: 8px 0 5px 0; }
p { font-size: 12px !important; margin: 5px 0 !important; }
.data-box { padding: 5px; }
.data-box h3 { font-size: 12px; margin: 0 0 3px 0; }
.data-value { font-size: 14px; }
#serial-log { min-height: 60px; max-height: 100px; }
button { padding: 6px 10px; font-size: 12px; }
}
</style>
</head>
<body>
<div class="container">
<div class="controls">
<h2 style="margin: 10px 0; font-size: 24px;">OPT4048 CIE Color Plotter</h2>
<p style="margin: 8px 0; font-size: 14px;">Connect your Arduino with OPT4048 sensor to visualize color measurements on a CIE diagram.</p>
<button id="clear-button">Clear Log</button>
<button id="test-plot-button" style="display: none;">Test Plot Point</button>
<h2>Connection Status</h2>
<p id="status">Not connected</p>
<h2 style="margin-bottom: 5px;">Serial Monitor</h2>
<div id="serial-log"></div>
</div>
<div class="visualization">
<h2 style="margin: 10px 0; font-size: 24px;">CIE 1931 Chromaticity Diagram</h2>
<div id="cie-diagram">
<img src="cie1931_diagram.svg" alt="CIE 1931 Chromaticity Diagram">
<div id="data-point" style="display: none;"></div>
<!-- Debug info: will show exact location of measured color -->
<div id="debug-coordinates" style="position: absolute; bottom: 3px; right: 3px; font-size: 10px; background: rgba(255,255,255,0.7); padding: 2px; border: 1px solid #ccc; display: block;"></div>
</div>
<div class="data-display">
<div class="data-box">
<h3>CIE x</h3>
<div id="cie-x" class="data-value">-</div>
</div>
<div class="data-box">
<h3>CIE y</h3>
<div id="cie-y" class="data-value">-</div>
</div>
<div class="data-box">
<h3>Lux</h3>
<div id="lux" class="data-value">-</div>
</div>
<div class="data-box">
<h3>CCT (K)</h3>
<div id="cct" class="data-value">-</div>
</div>
</div>
<div class="data-box">
<h3>Color Approximation</h3>
<div id="color-sample"></div>
<small>(Note: This is a rough approximation)</small>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>

View file

@ -0,0 +1,271 @@
// SPDX-FileCopyrightText: Copyright (c) 2025 Tim C for Adafruit Industries
// SPDX-License-Identifier: MIT
// Global variables
let port;
let reader;
let writer;
let readTimeout;
let keepReading = false;
let decoder = new TextDecoder();
let lineBuffer = '';
// DOM Elements
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');
const debugCoordinates = document.getElementById('debug-coordinates');
// Add a test plotting function
function testPlotPoint() {
// Test with fixed values at 25%, 50%, and 75% across the CIE diagram
const testPoints = [
{ x: 0.2, y: 0.3, label: "Test point 1 (0.2, 0.3)" },
{ x: 0.4, y: 0.45, label: "Test point 2 (0.4, 0.45)" },
{ x: 0.6, y: 0.6, label: "Test point 3 (0.6, 0.6)" }
];
// Get next test point (rotate through them)
const currentTest = parseInt(localStorage.getItem('currentTestPoint') || '0');
const nextTest = (currentTest + 1) % testPoints.length;
localStorage.setItem('currentTestPoint', nextTest);
const testPoint = testPoints[nextTest];
// Update the data displays
cieXDisplay.textContent = testPoint.x.toFixed(6);
cieYDisplay.textContent = testPoint.y.toFixed(6);
// Call the plot function
updateCIEPlot(testPoint.x, testPoint.y);
// Show test information
addToLog(`Testing point: ${testPoint.label}`, 'status');
debugCoordinates.textContent = `TEST MODE: ${testPoint.label}`;
}
clearButton.addEventListener('click', clearLog);
let ws = new WebSocket('ws://' + location.host + '/connect-websocket');
ws.onopen = () => {
console.log('WebSocket connection opened');
statusDisplay.innerText = "Connected";
}
ws.onclose = () => {
console.log('WebSocket connection closed');
statusDisplay.innerText = "Not Connected";
hideDataPoint();
}
ws.onmessage = ws_onmessage;
ws.onerror = error => console.log(error);
function ws_onmessage(event){
processSerialData(event.data)
}
// Process data received from MCU
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 MCU
function parseDataFromLine(line) {
// Log the raw line
console.log("Data received:", 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);
console.log("Found CIE x:", cieX);
// If we have a y value already stored in the display
const cieYStr = cieYDisplay.textContent;
if (cieYStr !== '-') {
const cieY = parseFloat(cieYStr);
console.log("Using existing CIE y:", cieY);
if (!isNaN(cieY)) {
// Update the plot with the current x,y pair
updateCIEPlot(cieX, cieY);
}
}
}
// Look for CIE y value
const cieYMatch = line.match(/CIE y: ([\d.]+)/);
if (cieYMatch) {
const cieY = parseFloat(cieYMatch[1]);
cieYDisplay.textContent = cieY.toFixed(6);
console.log("Found CIE y:", cieY);
// If we have an x value already stored in the display
const cieXStr = cieXDisplay.textContent;
if (cieXStr !== '-' && cieXMatch === null) { // Only use stored x if not found on this line
const cieX = parseFloat(cieXStr);
console.log("Using existing CIE x:", cieX);
if (!isNaN(cieX)) {
// Update the plot with the current x,y pair
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);
console.log("Found Lux:", lux);
}
// Look for Color Temperature value
const cctMatch = line.match(/Color Temperature: ([\d.]+)/);
if (cctMatch) {
const cct = parseFloat(cctMatch[1]);
cctDisplay.textContent = cct.toFixed(0);
console.log("Found CCT:", cct);
// If we have both x and y values by now, let's try to update the plot again
const cieXStr = cieXDisplay.textContent;
const cieYStr = cieYDisplay.textContent;
if (cieXStr !== '-' && cieYStr !== '-') {
const cieX = parseFloat(cieXStr);
const cieY = parseFloat(cieYStr);
if (!isNaN(cieX) && !isNaN(cieY)) {
// Final attempt to update plot
updateCIEPlot(cieX, cieY);
console.log("Updating plot after CCT with:", cieX, cieY);
}
}
}
}
// Initialize debug coordinates
document.addEventListener('DOMContentLoaded', function() {
debugCoordinates.textContent = 'Waiting for color data...';
});
// Update the CIE plot with new data point
function updateCIEPlot(x, y) {
console.log(`Plotting CIE coordinates: x=${x}, y=${y}`); // Debug log
// Get the dimensions of the CIE diagram container
const cieDiagram = document.getElementById('cie-diagram');
// Ensure we're only working with valid x,y coordinates
if (isNaN(x) || isNaN(y) || x < 0 || y < 0 || x > 1 || y > 1) {
console.warn(`Invalid CIE coordinates: x=${x}, y=${y}`);
debugCoordinates.textContent = `Invalid coordinates: x=${x}, y=${y}`;
return;
}
// Adjust coordinates to fit the visible area of the CIE diagram
// CIE diagram typically has coordinates: x [0-0.8], y [0-0.9]
const xMax = 0.8;
const yMax = 0.9;
// Get actual dimensions of the CIE diagram image
const cieImage = document.querySelector('#cie-diagram img');
const imgWidth = cieImage.clientWidth;
const imgHeight = cieImage.clientHeight;
// Calculate percentage positions within the SVG viewBox
const xPercent = (x / xMax) * 100; // Scale to percentage of max x (0.8)
const yPercent = (1 - (y / yMax)) * 100; // Invert y-axis and scale to percentage of max y (0.9)
console.log(`Plotting at: left=${xPercent}%, top=${yPercent}%`); // Debug log
// Set the data point position
dataPoint.style.left = `${xPercent}%`;
dataPoint.style.top = `${yPercent}%`;
dataPoint.style.display = 'block';
// Show debug coordinates for troubleshooting
debugCoordinates.textContent = `CIE: (${x.toFixed(4)}, ${y.toFixed(4)}) → Position: (${Math.round(xPercent)}%, ${Math.round(yPercent)}%)`;
// 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';
debugCoordinates.textContent = 'Waiting for color data...';
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 = '';
}