Hi,
From Google Gemini:
"I have refactored the code to be completely self-contained. All the styling that was previously handled by Tailwind CSS has been recreated using standard, inline CSS within a <style> block in the <head> of the document. This means the file has no external dependencies and will render correctly when served from your ESP32, even when it's completely offline.
The layout and functionality remain the same, but it's now a single, portable file."
The new code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP32 CAN Bus Monitor</title>
<!--
All styling is now included directly in this HTML file.
No external resources are needed, making this perfect for an offline ESP32.
-->
<style>
/* A simple CSS reset to ensure consistent styling across browsers */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Use common system fonts that are available on almost any device */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
background-color: #f1f5f9; /* A light gray background, similar to Tailwind's gray-100 */
color: #334155; /* A dark gray for text */
/* Flexbox properties to center the content on the page */
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 1rem; /* Add some padding for smaller screens */
}
/* The main container for all content */
.main-container {
width: 100%;
max-width: 600px; /* Limit the max width on larger screens */
}
/* Header styling */
header {
text-align: center;
margin-bottom: 2rem;
}
h1 {
font-size: 2.25rem; /* 36px */
font-weight: 700;
color: #1e293b; /* A darker text color for the title */
}
header p {
margin-top: 0.5rem;
color: #64748b; /* A lighter gray for the subtitle */
}
/* The white card that holds the data fields */
.data-card {
background-color: #ffffff;
border-radius: 0.75rem; /* Rounded corners */
padding: 1.5rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); /* A nice shadow effect */
}
/* Each row containing a label and an input field */
.data-row {
display: grid;
grid-template-columns: 1fr 1fr; /* Create a two-column layout */
gap: 1rem; /* Space between the columns */
align-items: center;
}
/* Add space between each row, but not after the last one */
.data-row:not(:last-child) {
margin-bottom: 1.5rem;
}
/* Styling for the text labels (e.g., "Error Counter") */
.data-label {
font-size: 1.125rem; /* 18px */
font-weight: 500;
}
/* Styling for the data input fields */
.data-input {
font-family: "Courier New", Courier, monospace; /* Use a common monospace font for data */
font-size: 1.5rem; /* 24px */
font-weight: 700;
text-align: right;
padding: 0.75rem;
background-color: #f1f5f9; /* Light gray background */
border: 1px solid #cbd5e1; /* A light gray border */
border-radius: 0.5rem; /* Rounded corners */
color: #1e293b;
width: 100%; /* Make the input fill its column */
}
/* The informational text at the bottom */
.info-text {
text-align: center;
margin-top: 2rem;
color: #64748b;
font-size: 0.875rem; /* 14px */
}
</style>
</head>
<body>
<div class="main-container">
<!-- Header -->
<header>
<h1>CAN Bus Monitor</h1>
<p>Live data from ESP32</p>
</header>
<!-- Data Display Card -->
<div class="data-card">
<!-- Row 1: Error Counter -->
<div class="data-row">
<label for="error-counter" class="data-label">Error Counter</label>
<input type="text" id="error-counter" value="Connecting..." readonly class="data-input">
</div>
<!-- Row 2: RX Counter -->
<div class="data-row">
<label for="rx-counter" class="data-label">RX Counter</label>
<input type="text" id="rx-counter" value="Connecting..." readonly class="data-input">
</div>
<!-- Row 3: Error Frame Counter -->
<div class="data-row">
<label for="frame-counter" class="data-label">Error Frame Counter</label>
<input type="text" id="frame-counter" value="Connecting..." readonly class="data-input">
</div>
</div>
<!-- Informational Text -->
<div class="info-text">
<p>A low error count is normal. The count should ideally remain below 128.</p>
</div>
</div>
<!-------------------------------------JavaScript--------------------------------------->
<script>
/**
* This is the main function that sets up the WebSocket connection.
* A WebSocket is a persistent, two-way communication channel between this web browser
* and the ESP32 server. It allows the ESP32 to push data to the webpage instantly.
*/
function InitWebSocket() {
// 'window.location.hostname' gets the IP address or hostname from the browser's address bar.
// When you connect to your ESP32 (e.g., at http://192.168.1.100), this will be "192.168.1.100".
const hostname = window.location.hostname;
// --- SAFETY CHECK ---
// Sometimes, the hostname might be empty (e.g., when opening the file directly on a computer).
// An empty hostname would create an invalid WebSocket URL and cause an error.
// This 'if' statement checks if the hostname is valid before proceeding.
if (!hostname) {
// Log an error to the browser's developer console (press F12 to see it).
console.error("Connection failed: Hostname is not available.");
// Update the user interface to show that the connection failed.
const errorMsg = "Conn. Failed";
document.getElementById('error-counter').value = errorMsg;
document.getElementById('rx-counter').value = errorMsg;
document.getElementById('frame-counter').value = errorMsg;
// 'return' stops the function from running any further.
return;
}
// Construct the full WebSocket URL.
// 'ws://' is the protocol for WebSockets, similar to 'http://'.
// The ESP32 WebSocket server must be listening on port 81 for this to work.
const wsUrl = `ws://${hostname}:81/`;
console.log(`Attempting to connect to: ${wsUrl}`);
// This line creates a new WebSocket object and tells it to connect to the ESP32 server.
const websock = new WebSocket(wsUrl);
/**
* --- EVENT LISTENERS ---
* These functions define what happens when different WebSocket events occur.
*/
/**
* 'onmessage' is triggered every time the ESP32 server sends a message to this webpage.
* This is where we receive and display the CAN bus data.
* 'evt' is an event object that contains the data sent by the server.
*/
websock.onmessage = function(evt) {
// The data from the server arrives as a string in JSON format (e.g., '{"ER":5,"RX":100,"EER":2}').
// 'JSON.parse(evt.data)' converts this string into a JavaScript object, which is easier to work with.
const JSONobj = JSON.parse(evt.data);
// Now, we update the values of the input fields on the webpage.
// 'document.getElementById()' finds an HTML element by its unique 'id'.
// We update the '.value' property because we are working with <input> elements.
// Find the input with id="error-counter" and set its value.
const errorCounterElement = document.getElementById('error-counter');
if (errorCounterElement) {
errorCounterElement.value = JSONobj.ER; // Get the value for the key "ER" from the JSON object.
}
// Find the input with id="rx-counter" and set its value.
const rxCounterElement = document.getElementById('rx-counter');
if (rxCounterElement) {
rxCounterElement.value = JSONobj.RX; // Get the value for the key "RX".
}
// Find the input with id="frame-counter" and set its value.
const frameCounterElement = document.getElementById('frame-counter');
if (frameCounterElement) {
frameCounterElement.value = JSONobj.EER; // Get the value for the key "EER".
}
};
/**
* 'onopen' is triggered once the WebSocket connection is successfully established.
* It's useful for debugging to confirm the connection worked.
*/
websock.onopen = function(evt) {
console.log('Connection opened');
};
/**
* 'onclose' is triggered if the connection is closed for any reason
* (e.g., the ESP32 restarts, or the network is lost).
* We can try to reconnect after a short delay.
*/
websock.onclose = function(evt) {
console.log('Connection closed. Retrying in 3 seconds...');
// Update the UI to let the user know they are no longer receiving live data.
const disconnectedMsg = "Disconnected";
document.getElementById('error-counter').value = disconnectedMsg;
document.getElementById('rx-counter').value = disconnectedMsg;
document.getElementById('frame-counter').value = disconnectedMsg;
// Attempt to reconnect after 3 seconds
setTimeout(InitWebSocket, 3000);
};
/**
* 'onerror' is triggered if there is a communication error.
*/
websock.onerror = function(evt) {
console.error('WebSocket error:', evt);
// Update the UI to show that an error occurred.
const errorMsg = "WS Error";
document.getElementById('error-counter').value = errorMsg;
document.getElementById('rx-counter').value = errorMsg;
document.getElementById('frame-counter').value = errorMsg;
};
}
/**
* This is a crucial line. It tells the browser to wait until the entire HTML
* page has been loaded and is ready before it tries to run our InitWebSocket function.
* If we tried to run it immediately, 'document.getElementById(...)' might fail because
* the HTML elements wouldn't exist yet.
*/
document.addEventListener('DOMContentLoaded', InitWebSocket);
</script>
</body>
</html>
And the result looks like this:
