Initiate a web page send from c++

I have a Platformio project written in C++ and using an asyncWebServer server that is managing several pages served from LittleFS at the client's request. The project is using websockets. All quite standard.
However, one of the pages has a button that initiates a series of events in the server. Again, all standard, but once the events are started, I need to send another page, also in LittleFS, that will display the graphical result of the just started events. So the c++ code needs to somehow initiate the page being sent to the current client(s).
I have spent hours on the web trying to find example code. The answer may be absolutely trivial, but even so, I would greatly appreciate some suggestions.
Many thanks, Ron

Your description is very abstract and therefore hard to analyse and it is very hard to imagine what your code looks like.

What exact type of microcontroller are you using?
is it ESP32 nodeMCU?
is it Arduino-ESP32 (which is something totally different)

Like always: post the complete sketch.

Additionally point to that part in your code that does react on clicking the button

How do the events come into play?

and point to that part of the code that you wrote that shall

Edit: in some cases I am experimenting with AI.
The results were very different. Some of the prompts worked. Most prompts needed 10 to 20 iterative refinements to come close to working code. To be able to write the more precise prompt required programming knowledge. So in the situation of knowing almost nothing is a bad starting point to use AI.

I took your initial post and added a microcontroller-type and asked deepseek with activated reasoning. The reasoning creates a long text which will give at least interesting informations you can learn from and/or is giving keywords to research on additionally.

the whole process
the prompt

I have a Platformio project written in C++ and using an asyncWebServer server that is managing several pages served from LittleFS at the client's request. The project is using websockets. All quite standard.
However, one of the pages has a button that initiates a series of events in the server. Again, all standard, but once the events are started, I need to send another page, also in LittleFS, that will display the graphical result of the just started events. So the c++ code needs to somehow initiate the page being sent to the current client(s).

I am using an ESP32 for this

Write the C++code acording to work on platformIO that does this

the reasoning answer

Okay, let's tackle this problem. The user has an ESP32 running a PlatformIO project with asyncWebServer and websockets. They need a button on one page that triggers server events, then automatically sends another page to the client to display the results.

First, I need to figure out how to serve a new page after the server events start. Since the server is asynchronous, traditional redirects might not work directly. The initial page has a button that sends a request, maybe via POST or GET. When that button is clicked, the server starts processing and then needs to send the new page.

The user mentioned using LittleFS, so the pages are stored there. Normally, clients request pages, but here the server needs to push a page without a client request. How to do that? Well, maybe after handling the button's request, the server can send the new page as a response. Or perhaps use websockets to notify the client to load the new page.

Wait, the button click could be an AJAX request. So when the server receives the POST from the button, it processes the events and then responds with a redirect instruction. But the client would handle that redirect. Alternatively, the server could send the HTML content directly in the response to the POST, but that might not be standard practice.

Another approach: after the button is pressed, the server sends a websocket message telling the client to navigate to the new page. The client would have JS listening for such messages and change the window location accordingly. That seems feasible. Let's outline that.

So the steps would be:

  1. Client clicks button on page A, sends a POST (or websocket message) to the server.

  2. Server starts the events.

  3. Server sends a websocket message to the client instructing to load page B.

  4. Client receives the message, navigates to page B, which is served from LittleFS when requested.

But the user wants the server to send the page automatically. Alternatively, when the client receives the websocket message, it could fetch page B via AJAX and replace the content, but that might complicate things.

Alternatively, in the POST handler for the button, after processing, the server could respond with a 302 redirect to the URL of page B. That would make the client automatically request page B. That's a standard HTTP redirect.

Yes, that's simpler. So when the button is clicked, it submits a form or sends a POST request. The server handles that request, starts the events, then sends a redirect response to the client, pointing to the URL of page B. The client then follows the redirect and requests page B, which the server serves from LittleFS.

So in code terms:

  • Define a POST handler for the button's action.

  • In that handler, initiate the server events.

  • Send a 302 redirect response with the URL of the new page.

But how to structure this in the AsyncWebServer code?

Looking up AsyncWebServer documentation, when handling a request, you can call redirect() on the response object. For example:

server.on("/start-events", HTTP_POST, [](AsyncWebServerRequest *request){

// Start events here

request->redirect("/page-b.html");

});

That would make the client redirect to page-b.html after submitting the form to /start-events.

So the button on page A would be part of a form that POSTs to /start-events. Then the server handles that, starts the events, and redirects to page B, which is served as a static file from LittleFS.

Alternatively, the button could trigger an AJAX POST, and the server responds with a JSON object indicating success, and the client then changes window.location. But the redirect approach is simpler and doesn't require client-side JS for navigation.

So the steps for the code:

  1. Set up the AsyncWebServer to serve static files from LittleFS.

  2. Define a POST route for handling the button action.

  3. In the POST handler, start the server events.

  4. Redirect the client to the new page.

Additionally, if the events are long-running, maybe the server should handle them asynchronously so the redirect happens immediately. But the user says the events are started, and then the page is sent, so the redirect should happen right after starting the events, not waiting for them to finish.

Also, if multiple clients are connected, the server needs to track which clients initiated the action. But perhaps the redirect is per-request, so each client that POSTs to /start-events gets redirected individually.

So code outline:

#include <Arduino.h>

#include <AsyncTCP.h>

#include <ESPAsyncWebServer.h>

#include <LittleFS.h>

AsyncWebServer server(80);

AsyncWebSocket ws("/ws");

void setup() {

LittleFS.begin();

// Setup websocket and server routes

server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){

request->send(LittleFS, "/page-a.html");

});

server.on("/start-events", HTTP_POST, [](AsyncWebServerRequest *request){

// Initiate events here

// Maybe send a message via websocket to all clients?

request->redirect("/page-b.html");

});

server.on("/page-b.html", HTTP_GET, [](AsyncWebServerRequest *request){

request->send(LittleFS, "/page-b.html");

});

// Setup websocket events

ws.onEvent(onWsEvent);

server.addHandler(&ws);

server.begin();

}

void loop() {

// Handle websocket cleanup

ws.cleanupClients();

}

// Websocket event handler

void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {

// Handle incoming websocket messages if needed

}

But wait, the user mentioned using websockets already, so maybe the events are communicated over websockets. So when the button is pressed, the client sends a websocket message, the server processes it, starts the events, and then sends a websocket message back telling the client to load the new page.

In that case, the button's JavaScript would send a websocket message instead of a POST request. Then the server's websocket handler would process that message, start the events, and send a message to the client to navigate to the new page.

For example, client-side JS:

function startEvents() {

websocket.send('startEvents');

}

Server-side, in the websocket event handler:

if (type == WS_EVT_DATA) {

// Parse the data

if (strcmp((char*)data, "startEvents") == 0) {

// Start the events

// Then send a message to the client to redirect

client->text("redirect:/page-b.html");

}

}

Then client JS would listen for messages:

websocket.onmessage = function(event) {

if (event.data.startsWith("redirect:")) {

window.location.href = event.data.split(':')[1];

}

};

This way, the server initiates the redirect via websocket after starting the events.

Which approach is better? Using POST with redirect or websocket messages. The user's existing setup uses websockets, so integrating with that might be more consistent.

So combining both methods:

If the button is part of a form using POST, redirect is straightforward. If the button uses websockets to communicate, then the server sends a websocket message to redirect.

The user's original question says that after the events are started, the server needs to send another page. So the server needs to initiate sending the page. With HTTP, the client must make a request, but the server can't push a page without a client request. Hence, using websockets to tell the client to request the new page is the way to go.

Therefore, the code would involve:

  • When the server starts the events (triggered by a websocket message), it sends a websocket message to the client to navigate to the new page.

So modifying the code to handle websocket messages:

In the websocket event handler:

void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {

if (type == WS_EVT_DATA) {

AwsFrameInfo info = (AwsFrameInfo)arg;

if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {

data[len] = 0;

String message = (char*)data;

if (message == "startEvents") {

// Start the events here

// Then send redirect command to this client

client->text("redirect:/page-b.html");

}

}

}

}

Then the client's JavaScript handles the redirect.

This approach allows the server to initiate sending the new page via websocket after starting the events.

So putting it all together, the code would set up the webserver to serve static files, set up websockets, and handle the client's message to start events, then send a redirect command.

The user also mentioned that the new page (page-b.html) is in LittleFS, so the server must serve it when requested. So the server should have a handler for "/page-b.html" that sends the file.

In summary, the code needs:

  • Serve static files from LittleFS.

  • Websocket setup to handle incoming messages.

  • When "startEvents" is received via websocket, start events and send redirect message.

  • Client-side JS to handle the redirect.

Testing this, the client would load page-a.html, click the button which sends a websocket message. The server processes it, starts events, and sends a redirect message. The client then navigates to page-b.html, which is served by the server.

This should meet the user's requirement of the server initiating the page change after starting events.

the c++-code
#include <Arduino.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>

AsyncWebServer server(80);
AsyncWebSocket ws("/ws");

// WebSocket event handler
void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, 
              AwsEventType type, void *arg, uint8_t *data, size_t len) {
  if (type == WS_EVT_DATA) {
    AwsFrameInfo *info = (AwsFrameInfo*)arg;
    
    if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
      data[len] = 0; // Null-terminate the data
      String message = (char*)data;

      if (message == "startEvents") {
        // Start your server-side events here
        Serial.println("Starting events...");
        
        // Send redirect command to this specific client
        client->text("redirect:/status.html");
      }
    }
  }
}

void setup() {
  Serial.begin(115200);
  
  // Initialize LittleFS
  if (!LittleFS.begin()) {
    Serial.println("LittleFS mount failed!");
    return;
  }

  // Set up WebSocket
  ws.onEvent(onWsEvent);
  server.addHandler(&ws);

  // Serve static files from LittleFS
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(LittleFS, "/index.html", "text/html");
  });

  server.on("/status.html", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(LittleFS, "/status.html", "text/html");
  });

  // Start server
  server.begin();
}

void loop() {
  // Cleanup disconnected WebSocket clients
  ws.cleanupClients();
}
the html-code
<script>
  window.addEventListener('DOMContentLoaded', (event) => {
    const websocket = new WebSocket('ws://' + window.location.hostname + '/ws');

    websocket.onmessage = function(event) {
      if (event.data.startsWith('redirect:')) {
        window.location.href = event.data.split(':')[1];
      }
    };

    // Button click handler
    document.getElementById('startButton').addEventListener('click', () => {
      websocket.send('startEvents');
    });
  });
</script>
additional comments

Key components:

  1. WebSocket Communication: The server uses WebSocket to receive commands and trigger redirects
  2. File Serving: Static files (HTML/CSS/JS) are served from LittleFS
  3. Event Handling:
  • Client sends "startEvents" via WebSocket when button is clicked
  • Server processes events and sends redirect command
  • Client handles redirect command to load new page
LittleFS-structure

/littlefs
|- index.html # Initial page with start button
|- status.html # Status display page
|- other assets...

to use this code:
  1. Create your HTML files and store them in LittleFS
  2. Add necessary event handling logic where commented
  3. Upload files to ESP32 using PlatformIO's LittleFS upload tool
  4. The client will automatically redirect to the status page after initiating events

This approach maintains async communication and allows real-time updates through WebSocket while providing seamless navigation between pages.

Not sure what the processor type has to do with it. In effect the web socket 'button' message is decoded, the code to create the plot is started, and at that point I want to send the page (in LittleFS) that will show the resulting plot. I will create a simple project that demonstrates what is required.

The reasoning that was created by deepseek shows that deepseek is "thinking" about different versions how your code might look like.
This clearly indicates that you should post more details about your code.

Which you said you will do

At some points the code looks different depending on the type of board.
The Arduino-ESP32-boards use different libraries than other ESP32-boards.
And there were breaking changes between ESP32-core 2.0.17 and ESP32-core 3.X

@ronkremborg

Hi

So won't a simple page request suffice?

server.on("/doStuff", HTTP_GET, [](AsyncWebServerRequest * request) {
  // doStuff init() code here......
    request->send(LittleFS, "/doneStuff.html", "text/html");
  });

I don't know. Because my personal knowledge about these things is very limited.

Again: Post your complete sketch so that details like:

  • are you using "POST"
    or
  • are you using "GET"
  • are you using "AJAX"
  • or
    • some other kind of javascript etc. etc. becomes clear.

NO human user will write down an extra tutorial for you that discusses and demonstrates the different techniques how to realise this.

But you can count on helping support if you post your best attempt how you tried to write the code for it.

@StefanL38

Hi

My post was a response to the OP.

That's just not how HTTP protocol works, sorry.

This protocol will allow the server to initiate data being sent from the server to the client.

So I think the way this is supposed to work:

  1. Client sends HTTP request to server
  2. Server responds with a page
  3. That page contains code (usually javascript) which, when run, opens a websocket with the server, and displays any data received from the server. As long as the page is displayed on the client and the script is running, data can be received from, or sent to, the server.
  4. When the page is closed, the socket is also closed. I'm not sure exactly how this happens.

As @PaulRB correctly pointed out, this isn't possible with plain HTTP.

However, since you're using WebSockets — which are bidirectional — instead of sending a new webpage to the client, you can simply send a "command" through the WebSocket. The client, at this point, knows that it can load the updated resource.

As far as I know, the code:

server.on("/doStuff", HTTP_GET, [](AsyncWebServerRequest * request) {
  // doStuff init() code here......
    request->send(LittleFS, "/doneStuff.html", "text/html");
  });

is responding to a request from the client.

Whereas in my case the client has used a websocket to ask for something to begin, and the server must make the decision about which page to send based on that something and then do the sending.

Yes, I will try using websockets to send a command back to the client that then sends a request back to the server for the correct page, but I would have thought this was not that an uncommon requirement.

Can't you include some javascript in the web page so the webpage can periodically make a request to the webserver to check if this graphical data is available and, if so, collect and display it say within a frame in the currently displayed page.
You can use the javascript function XMLHttpRequest for this. There is an example here of a Web page collecting and displaying additional information from the server during the session: ESP32 ESPAsyncWebServer sample demonstration application

Hi

I'm OK with the client websocket request., but...

The server does not make decisions, it is dumb!
You make the 'decision' by writing the code that is running on the server.
So write that 'decision' into the javascript on the page instead.

//Some decision making js function here...

// then in your ws.send() handler

var path = "/test";//Default which can be selected and changed in above function

function send_onclick() {
  if (ws != null) {
    var message = document.getElementById("message").value;

    if (message) {
      ws.send(message + "\n");
      update_text('<span style="color:navy">' + message + '</span>');
    }  
      // You can send the message to the server or process it as needed
    if (message == "><") { 
      ws.close();//Close socket
      console.log("Socket Closed");
      setTimeout(1000);//Wait a second
      window.location.href = path;//Request and load page
      
    }
    document.getElementById("message").value = "";
  } 
}


Then back to page handles on the server as in my first post..

server.on("/test", HTTP_GET, [](AsyncWebServerRequest * request) {
    //do other stuff here if required...
    request->send(LittleFS, "/testpage.html",  "text/html");
  });

This all works for me as I'm currently developing a similar idea.

@ronkremborg

Just to add, I've tried constructing page handlers fired from WS_EVT_DATA, but none worked for the simple fact that the browser will not display the page...
The browser/client are in total control of that.

HyperText Transport Protocol is a text-based request-response system for a user agent to acquire HTML for display. WebSocket uses the existing connection; the first and last response expected is

HTTP/1.1 101 Switching Protocols

and from that point it's a separate, parallel, bi-directional frame-based channel. The browser takes no extra notice of any text received that happens to be HTML.

You can't trigger a page handler if there are no client requests.
A simple solution would be to send the page you want the client to load directly through the WebSocket channel, for example using a simple protocol like loadurl:/webpage1.html.

This way, your JavaScript could look something like this:

// Create a new WebSocket connection
const socket = new WebSocket('ws://' + document.location.host + '/ws', ['arduino']);

// Event: connection opened
socket.addEventListener("open", () => {
  console.log("WebSocket connected");
});

// Event: message received
socket.addEventListener("message", async (event) => {
  console.log("Message received from WebSocket:", event.data);

  const match = event.data.match(/^loadurl:\s*(.+)$/);
  if (match) {
    // Extract the path from the message starting with "loadurl:"
    const path = match[1];

    // Call the function to fetch the page based on the received URL
    // This function only print the content in the console
    await fetchAndProcessPage(path);
 
    // You can also perform a complete reload using the new URL
    // window.location.href = fullUrl; 

  } else {
    console.warn("Message format not recognized");
  }  
});

// Function that fetches the content of a page
async function fetchAndProcessPage(url) {
  try {
    const response = await fetch(url);

    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }
    const text = await response.text();
    console.log("Fetched page content:", text);    
  } catch (error) {
    console.error("Error during fetch:", error);
  }
}