Search code examples
esp32

ESPAsyncWebServer request->send_P problem


I am doing a simple example using ESPAsyncWebServer on ESP32. In this context I wrote a html file (there are a slider and a button) and tested it on a browser until the content look well. Then I integrate it in the C++ source code for ESP32. It works and look as expected until I don't add a callback to read the value of the slider. This is the complete source code:

#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Replace with your network credentials
const char ssid[]    = "Vodafone-A40881218";
const char pswd[]    = "rJbFMktHCcqN67Ye";

const int output = 2;

String sliderValue = "0";

// setting PWM properties
const int freq = 5000;
const int ledChannel = 0;
const int resolution = 8;

const char* PARAM_INPUT = "value";

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

#if 0
const char old_index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>ESP Web Server</title>
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    h2 {font-size: 2.3rem;}
    p {font-size: 1.9rem;}
    body {max-width: 400px; margin:0px auto; padding-bottom: 25px;}
    .slider { -webkit-appearance: none; margin: 14px; width: 360px; height: 25px; background: #FFD65C; outline: none; -webkit-transition: .2s; transition: opacity .2s;}
    .slider::-webkit-slider-thumb {-webkit-appearance: none; appearance: none; width: 35px; height: 35px; background: #003249; cursor: pointer;}
    .slider::-moz-range-thumb { width: 35px; height: 35px; background: #003249; cursor: pointer; } 

    button { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:20%;color:white;font-size:130%; }
      .buttons { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:15%;color:white;font-size:80%; }
      .buttonsm { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:9%; color:white;font-size:70%; }
      .buttonm { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:15%;color:white;font-size:70%; }
      .buttonw { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:40%;color:white;font-size:70%; }
      .buttong { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:40%;color:white;font-size:130%; }

  </style>
</head>
<body>
  <h2>ESP Web Server</h2>
  <p><span id="textSliderValue">%SLIDERVALUE%</span></p>
  <p><input type="range" onchange="updateSliderPWM(this)" id="pwmSlider" min="0" max="21" value="%SLIDERVALUE%" step="1" class="slider"></p>
  <a href='/setup'><button class='button'>SETUP</button></a>
<script>
function updateSliderPWM(element) {
  var sliderValue = document.getElementById("pwmSlider").value;
  document.getElementById("textSliderValue").innerHTML = sliderValue;
  console.log(sliderValue);
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "/slider?value="+sliderValue, true);
  xhr.send();
}
</script>
</body>
</html>
)rawliteral";
#endif

const char index_html[] = R"rawliteral(
<!DOCTYPE HTML>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>ESP Web Server</title>
    <style>
      html {font-family: Arial; display: inline-block; text-align: center;}
      h2 {font-size: 2.3rem;}
      p {font-size: 1.9rem;}
      body {max-width: 400px; margin:0px auto; padding-bottom: 25px;}
      .slider { width: 360px; }  
      .slider::-webkit-slider-thumb { width: 50px; height: 50px; }
      .slider::-moz-range-thumb { width: 50px; height: 50px; } 
      button { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:40%;color:white;font-size:130%; }
      .buttons { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:15%;color:white;font-size:80%; }
      .buttonx { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:9%; color:white;font-size:70%; }
      .buttonm { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:15%;color:white;font-size:70%; }
      .buttonw { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:40%;color:white;font-size:70%; }
      .buttong { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:40%;color:white;font-size:130%; }
    </style>
  </head>
  <body>
    <h2>ESP Web Server</h2>
    <p><span id="textSliderValue">%SLIDERVALUE%</span></p>
    <p><input type="range" onchange="updateSliderPWM(this)" id="pwmSlider" min="0" max="21" value="%SLIDERVALUE%" step="1" class="slider"></p>
    <a href='/setup'><button class='button'>SETUP</button></a>
    <script>
      function updateSliderPWM(element) {
        var sliderValue = document.getElementById("pwmSlider").value;
        document.getElementById("textSliderValue").innerHTML = sliderValue;
        console.log(sliderValue);
        var xhr = new XMLHttpRequest();
        xhr.open("GET", "/slider?value="+sliderValue, true);
        xhr.send();
      }
    </script>
  </body>
</html>
)rawliteral";
// Replaces placeholder with button section in your web page

String processor(const String& var)
{
  //Serial.println(var);
  if (var == "SLIDERVALUE"){
    return sliderValue;
  }
  return String();
}

#include <Preferences.h>
Preferences Pref;
int32_t g_iVolume = 0;

void setup()
{
  // Serial port for debugging purposes
  Serial.begin(115200);
  
  Pref.begin("datasetup", false);
  g_iVolume = Pref.getInt("volume", 5);
  Serial.print("volume="); Serial.println(g_iVolume);
  sliderValue = String(g_iVolume);
  Pref.end();

  // Connect to Wi-Fi
  WiFi.begin(ssid, pswd);
  Serial.println("Connecting ...");
  while (WiFi.status() != WL_CONNECTED) 
  { // Wait for the Wi-Fi to connect: scan for Wi-Fi networks, and connect to the strongest of the networks above
    delay(250); 
    Serial.print('.');
  }

  // Print ESP Local IP Address
  Serial.println(WiFi.localIP());

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
  });

  // Send a GET request to <ESP_IP>/slider?value=<inputMessage>
  server.on("/slider", HTTP_GET, [] (AsyncWebServerRequest *request) {
    String inputMessage;
    // GET input1 value on <ESP_IP>/slider?value=<inputMessage>
    if (request->hasParam(PARAM_INPUT)) {
      inputMessage = request->getParam(PARAM_INPUT)->value();
      sliderValue = inputMessage;
      int ival = sliderValue.toInt();
      Serial.print("ival="); Serial.println(ival);
      Pref.begin("datasetup", false);
      size_t st = Pref.putInt("volume", ival);
      Pref.end();
      Serial.print("st="); Serial.println(st);

      Pref.begin("datasetup", false);
      int vol = Pref.getInt("volume", -1);
      Serial.print("volume="); Serial.println(vol);
      Pref.end();
    }
    else {
      inputMessage = "No message sent";
    }
    Serial.println(inputMessage);
    request->send(200, "text/plain", "OK");
  });
  
  // Start server
  server.begin();
}
//------------------------------------------------------------------------


void loop() 
{
  // put your main code here, to run repeatedly:
}
//------------------------------------------------------------------------

the 1st image is with nullptr instead of processor and the 2nd with processor callback.

then this is the image with nullptr instead of processor:

and then this is the image with processor callback


Solution

  • I found the problem. The author of ESPAsyncWebServer decided to use the % character as the delimiter for placeholders for the template processor. Unfortunately, the % is quite common in CSS and JavaScript so, writing CSS and JavaScript in HTML file(s) or Strings does not work as expected because the wrong interpretation of % as delimiters of chuncks of text that are not placeholders but CSS or JavaScript code. At the moment there is not a workaround, just do not use % in CSS and JavaScript.