Call void(function) on webpage button click

Hello,

I have read dozens of posts on this topic to no success so thank you in advance for any assistance.

I have a fully functioning script that executes successfully and now want to execute the function from a webpage button.

I have created the webpage and buttons, last step is to call a function in the script named void(rotate) when clicking the webpage button. I have tried dozens of ideas and believe that someting like this is close:

client.println("<input type=button value=Dump2 onmousedown=(rotate() >");

But obviously this is not working

I think there is an extra open parenthesis "("... try...

onmousedown = rotate()

Some clarification of terminology

  • You may have heard of JavaScript
  • What you run on an Arduino is a sketch, not a script. For one thing, it's written in C++, which is not a scripting language.
  • If you have a function in your sketch, it's void rotate(), not void(rotate). That sort of difference really matters with C++.
    • void in JavaScript means something else and is rarely used

If so far, you've got

  • a sketch with a web server
  • your browser makes an HTTP request to the web server
  • you've got some kind of handler function that uses println to send HTML in response; so that
  • the browser gets a complete web page
  • with a button that can do something; execute some JavaScript

Consider that the browser must initiate some communication to the server if you want to run your function in the sketch, on the Arduino. You've already used the mechanism for the web server to respond to a given request path to serve the page.

For a given handler in your sketch, you can call your function, and return an appropriate HTTP response. If the function is void, it returns no information, so the most appropriate thing is to return HTTP 204 No Content, which means, "it worked, and I don't have anything to add"

The final part is to have the button on the page make a request to the web server. Nowadays, you can just use fetch

<input type="button" value="Dump2" onclick="fetch('/rotate')">

which will request a given path and -- in this usage here -- ignore everything and anything that happens (or not) as a result. That should be enough to get started.

There is no automatic linkage between the request path /rotate and your function with the same name. You'll have to code that, as mentioned before.

Thank you for the information, yes I misspoke calling the sketch a script...(I am quite new to all of this). I think I can wrap my head around your direction...however in your last paragraph (thinking this is my missing piece)... could you send me to perhaps an example on how to code the request to the Arduino web server so it initiates rotate?

I'd say perhaps you should post your current complete code first.

OK, now post it in-line, with code tags, per the forum guidelines. BTW, you should already know this as you should have read the guidelines before you posted your question:

Apologies, no disrespect intended...

// Load librarys
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <NTPClient.h>

#define STEPPIN D7
#define DIRPIN D6
#define ENAPIN D5
#ifndef STASSID
#define STASSID "GibsonGuest"        //Comment out to use Cabin
#define STAPSK  "havesomefun"        //Comment out to use Cabin
//#define STASSID "KLSWNT"           //Uncomment to use Cabin
//#define STAPSK "0017759!"          //Uncomment to use Cabin
#endif

// Replace with your network credentials
const char* ssid = STASSID;
const char* password = STAPSK;
const int STEPTIME = 5;
// Set web server port number to 80
WiFiServer server(80);

// Define NTP Client to get time
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org");

// Variable to store the HTTP request
String header;

// Current time for use in client connect
unsigned long currentTime = millis();
// Previous time
unsigned long previousTime = 0; 
// Define timeout time in milliseconds (example: 2000ms = 2s)
const long timeoutTime = 2000;

/////////////////////////////////////////////////////////////////////////////////////////////////
void setup() {
  Serial.begin(115200);
  // Initialize the output variables as outputs
  pinMode(STEPPIN,OUTPUT);
  pinMode(DIRPIN,OUTPUT);
  pinMode(ENAPIN,OUTPUT);

  // Connect to Wi-Fi network with SSID and password
  WiFi.setHostname("Snow_Dumper");
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // Print local IP address and start web server
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin();

  // Initialize a NTPClient to get time
  timeClient.begin();
  timeClient.setTimeOffset(-25200);     //Denver is -6 during winter, multiply GMT offset by 3600  
}

////////////////////////////////////////////////////////////////////////////////////////////////
void loop(){
  WiFiClient client = server.available();   // Listen for incoming clients
  timeClient.update();

  time_t epochTime = timeClient.getEpochTime();
  Serial.print("Epoch Time: ");
  Serial.println(epochTime);
  
  String formattedTime = timeClient.getFormattedTime();
  Serial.print("Formatted Time: ");
  Serial.println(formattedTime);  

  int currentMinute = timeClient.getMinutes();
  Serial.print("Minute: ");
  Serial.println(currentMinute);

  int currentHour = timeClient.getHours();
  Serial.print("Hour: ");
  Serial.println(currentHour);

  Serial.println("");                   // Uncomment to see hours in serial monitor
  delay(15000);                          // Delay time to use during testing
  //delay(86400100);                    // Fetch time just over every 24 hours during use
    if((currentMinute==48)){            // Use for testing, allow trigger on a minute vs an hour
    //if((currentHour)==16){              // Change the number of the hour here during testing if needed
      Serial.println("rotate now");
      rotate(10000);                      // Set the number here to achieve your 180 degree rotation based upon your gearing etc
    }                                         

  if (client) {                             // If a new client connects,
    Serial.println("New Client.");          // print a message out in the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    currentTime = millis();
    previousTime = currentTime;
    while (client.connected() && currentTime - previousTime <= timeoutTime) { // loop while the client's connected
      currentTime = millis();         
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header += c;
        if (c == '\n') {                    // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();

            // Display the HTML web page
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            // CSS to style the on/off buttons 
            client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
            client.println(".button { background-color: #195B6A; border: none; color: white; padding: 16px 40px;");
            client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
            client.println(".button2 {background-color: #77878A;}</style></head>");
            
            // Web Page Heading
            client.println("<body><h1>Snow Dumper</h1>");
            client.println("<p>Current time on device " + formattedTime + "</p>");  
            client.print("<p>IP Address ""</p>");
            client.println(WiFi.localIP());

            // Web Page Button
            client.println("<p><a href=\"SendPage (rotate(10000))\"><button class=\"button buttonRed\">Press to Dump Snow now</button></a></p>");
            //client.println("<input type=button value=ON onmousedown=location.href='/?on4;'>");
            //client.println("<input type=button value=Dump1 onclick=(rotate(10000) >");
            client.println("<input type=button value=Dump2 onmousedown=rotate(10000) >");

            client.println();   // The HTTP response ends with another blank line
            break;             // Break out of the while loop
          } else {             // if you got a newline, then clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    // Clear the header variable
    header = "";
    // Close the connection
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");
  }
}

void rotate(int steps){
  Serial.println("rotate");
  int i;
  digitalWrite(ENAPIN,LOW);//ENABLE IS ACTIVE LOW
  digitalWrite(DIRPIN,HIGH);//SET DIRECTION 
  for(i=0;i<steps;i++){
    digitalWrite(STEPPIN,HIGH);
    delay(STEPTIME);
    digitalWrite(STEPPIN,LOW);
    delay(STEPTIME);
    } 
}

The onclick="fetch('/rotate')" is the entirety of the request.

Web servers are about coding responses to different requests, for example

  • the root path /
  • /favicon.ico
  • /someimage.png
  • /whatever_path_you_used_to_send_that_html
  • /rotate

You're adding that last one. Servers vary in their API, but it's usually something like

// set up response handlers
server.on("/", serveRoot);
server.on("/favicon.ico", serveFavIcon);

Those are separate functions above, but some servers/examples favor lambdas. Either way, it would look like

server.on("/rotate", HTTP_GET, [] (AsyncWebServerRequest *request) {
    rotate(10000);  // do the thing
    request->send(204);  // respond with something, so both
      // the server here knows you're done, and
      // the client browser is not left hanging
});

But you're not using a web server class; you're manually performing HTTP, as a server. If you're planning on doing more than just this one other thing, I recommend using a real server. But if you're going for the stone knives approach, in addition to the corrected HTML for the input, it's just a handful of additional statements, added to this stripped-down version that runs on the ESP32 I have handy

#include <WiFi.h>
#include "arduino_secrets.h"

WiFiServer server(80);

void setup() {
  Serial.begin(115200);

  WiFi.begin(SECRET_SSID, SECRET_PASS);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("\nWiFi connected.");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin();
}

////////////////////////////////////////////////////////////////////////////////////////////////

void loop(){
  WiFiClient client = server.available();   // Listen for incoming clients

  if (client) {                             // If a new client connects,
    Serial.println("New Client.");          // print a message out in the serial port
    String header;
    header.reserve(400);
    String currentLine;                     // make a String to hold incoming data from the client
    currentLine.reserve(100);
    auto currentTime = millis();
    auto previousTime = currentTime;
    while (client.connected() && currentTime - previousTime <= 2000) { // loop while the client's connected
      currentTime = millis();         
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header += c;
        if (c == '\n') {                    // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // Is it the request to rotate?
            if (header.startsWith("GET /rotate")) {
              rotate(10000);
              client.println("HTTP/1.1 204 No Content");
              client.println("Connection: close");
              client.println();
              break;
            }
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type: text/html");
            client.println("Connection: close");
            client.println();

            // Display the HTML web page
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            // CSS to style the on/off buttons 
            client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
            client.println(".button { background-color: #195B6A; border: none; color: white; padding: 16px 40px;");
            client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
            client.println(".button2 {background-color: #77878A;}</style></head>");
            
            // Web Page Heading
            client.println("<body><h1>Snow Dumper</h1>");
            // client.println("<p>Current time on device " + formattedTime + "</p>");  
            client.print("<p>IP Address ""</p>");
            client.println(WiFi.localIP());

            // Web Page Button
            client.println("<p><a href=\"SendPage (rotate(10000))\"><button class=\"button buttonRed\">Press to Dump Snow now</button></a></p>");
            //client.println("<input type=button value=ON onmousedown=location.href='/?on4;'>");
            //client.println("<input type=button value=Dump1 onclick=(rotate(10000) >");
            //client.println("<input type=button value=Dump2 onmousedown=rotate(10000) >");
            // use raw string to easily include double-quote in HTML
            client.println(R"~(<input type="button" value="Dump2" onclick="fetch('/rotate')">)~");

            client.println();   // The HTTP response ends with another blank line
            break;             // Break out of the while loop
          } else {             // if you got a newline, then clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    // Close the connection
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println();
  }
}

void rotate(int steps){
  Serial.print("rotate: ");
  Serial.println(steps);
}

This hard-codes the steps to 10000. If you want to be able to input that on the web page, the easiest thing would be to have a query parameter. Then the request-line would look like "GET /rotate?steps=9876". You'd parse out that number.

On the HTML side, you'd have "fetch('/rotate?steps=' + getTheSteps())", where getTheSteps reads whatever you're using to input the number. But that's an HTML/JavaScript question, not an Arduino one.

BTW, startsWith("GET /rotate") also matches /rotaterZZZ and anything else like that. If you look at the echoed request-line

GET /rotate HTTP/1.1

There's a space between the request-path and the HTTP/-thing that follows. But if you're doing a query parameter, you'd have to allow for ? as well. A web server would parse the request-line for you to handle these details.

And now with an exposed endpoint, with no additional checks, anyone that can ping your server can invoke it.

1 Like

According to your code...

... you intend to call an Arduino C++ function rotate() from the webpage button.

No, it can't be done that way.
In order for a function to be run from a web page, it must either be a javascript or a separate callback, as shown in @kenb4 message

Kenb4....Can't thank you enough for the solution AND the education! Truly appreciate you taking the time to help out a NOOB. Can I buy you a coffee?

Now I just need to figure out how to format the new button since it is using the raw string...