Hi,
I've added your question to Google Gemini 2.5 PRO AI and here is what it came up with:
<!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>
<!-- Using Tailwind CSS for modern, responsive styling -->
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=Roboto+Mono:wght@400;700&display=swap" rel="stylesheet">
<style>
/* Using the Inter font for general text and Roboto Mono for data values */
body {
font-family: 'Inter', sans-serif;
}
.data-value {
font-family: 'Roboto Mono', monospace;
}
</style>
</head>
<body class="bg-gray-100 flex items-center justify-center min-h-screen">
<div class="w-full max-w-2xl mx-auto p-4 md:p-6">
<!-- Header -->
<header class="text-center mb-8">
<h1 class="text-3xl md:text-4xl font-bold text-gray-800">CAN Bus Monitor</h1>
<p class="text-gray-600 mt-2">Live data from ESP32</p>
</header>
<!-- Data Display Grid -->
<div class="bg-white rounded-xl shadow-lg p-6 space-y-6">
<!-- Row 1: Error Counter -->
<div class="grid grid-cols-2 items-center gap-4">
<label for="error-counter" class="text-lg font-medium text-gray-700">Error Counter</label>
<input
type="text"
id="error-counter"
value="Connecting..."
readonly
class="data-value text-xl md:text-2xl font-bold text-right bg-gray-100 border-2 border-gray-300 rounded-lg p-3 focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
>
</div>
<!-- Row 2: RX Counter -->
<div class="grid grid-cols-2 items-center gap-4">
<label for="rx-counter" class="text-lg font-medium text-gray-700">RX Counter</label>
<input
type="text"
id="rx-counter"
value="Connecting..."
readonly
class="data-value text-xl md:text-2xl font-bold text-right bg-gray-100 border-2 border-gray-300 rounded-lg p-3 focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
>
</div>
<!-- Row 3: Error Frame Counter -->
<div class="grid grid-cols-2 items-center gap-4">
<label for="frame-counter" class="text-lg font-medium text-gray-700">Error Frame Counter</label>
<input
type="text"
id="frame-counter"
value="Connecting..."
readonly
class="data-value text-xl md:text-2xl font-bold text-right bg-gray-100 border-2 border-gray-300 rounded-lg p-3 focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
>
</div>
</div>
<!-- Informational Text -->
<div class="text-center mt-8 text-gray-600">
<p>A low error count is normal.</p>
<p>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).
*/
websock.onclose = function(evt) {
console.log('Connection closed');
// 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;
};
/**
* '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>
The result looks like this:

And the explanation of what it did to fix your code:
The issue in your "new layout" is that the JavaScript is trying to update elements by an id that doesn't exist or is on the wrong element. You're trying to update the text inside a table cell (<td>) when you should be updating the value of the text box (<input>).
I've created a clean, modernized version of your CAN bus monitor page. This new code combines the layout you wanted with the working WebSocket logic from your original file. It's built to be responsive, easy to read, and simple to modify.
I've made the following key changes to fix your layout and make it more robust:
- Correct JavaScript Targeting: The main issue is fixed. The JavaScript now correctly targets each <input> field by its unique id (error-counter, rx-counter, frame-counter) and updates its value.
- Modern HTML & CSS: The code uses Tailwind CSS for a clean, responsive design that looks great on both mobile phones and desktop browsers. The layout is managed with a CSS Grid, which is more flexible than tables for this purpose.
- Readability: I've cleaned up the structure, removed redundant tags, and added comments to both the HTML and JavaScript to explain what each part does.
- User Experience: The input fields are set to readonly since they are for display only. I've also added a "Connecting..." message so you know the page is waiting for data.
The HTML and JavaScript code in the canvas is designed specifically to work with an ESP32.
Here’s a simple breakdown of how it all fits together:
1. Your ESP32's Role (The Server): You will program your ESP32 using the Arduino IDE or a similar tool. Your C++/Arduino code needs to do three things:
- Act as a Web Server: It will store this HTML file in its memory (usually in SPIFFS or LittleFS) and send it to any web browser that connects to the ESP32's IP address.
- Create a WebSocket Server: This is the critical part. The ESP32 must run a WebSocket server on port 81, just as the JavaScript expects (ws://${hostname}:81/).
- Send Data: The ESP32 will read your CAN bus data, format it into a JSON string like {"ER":5, "RX":1234, "EER":1}, and send this string over the WebSocket connection at regular intervals.
2. This Code's Role (The Client): The HTML file you see in the canvas is the "client-side" code. It runs entirely in the web browser on your phone or computer after being loaded from the ESP32. It listens for the JSON messages from the ESP32's WebSocket server and uses them to update the values in the text boxes.
Your next step is to write the ESP32 code that serves this file and provides the data.