Send sensor data through WiFi-ESP32

Hey guys,

I am designing my own smartwatch, it has a PPG sensor and an accelerometer that both have a sample rate of 128Hz.

I want to plot in real time every point on a web server with no data loss. I am using an ESP32 s3.

I am already capable of ploting data from my accelerometer, but there is a non negligeable delay between when the data has been sent by the controler and when it has been ploted. (About 2 to 3 seconds)

First question: How can I improve the delay and plot data as fast as I'm getting it ?

Also, in order to plot data 128 times a second, at the moment I force an intervalle between two point that corresponds to 128Hz. It does not feel like the correct way, I should to send data and plot it whenever the SENSOR is giving me data, not whenever the software wants it.

Second question: what is the correct way to get data in real time using wifi? Is there some sort of ".notify()" like for bluetooth?

here are my codes: (Don't give too much attention on the variables named temperature, pressure etc. The code mostly come from a tutorial unrelated to my accelerometer, that's why)

#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#include "accelerometer.h"

uint16_t ax, ay, az;
uint16_t fall, move = 1;  //Interrupt active low

const char *ssid = "***";
const char *password = "***!";

AsyncWebServer server(80);

//don't worry, this will be transformed to only one function. And yes these are //unsigned int I know.
String acc_X(uint16_t ax, uint16_t ay, uint16_t az) {
  readAccelData(ax, ay, az);
  return String(ax);
}

String acc_Y(uint16_t ax, uint16_t ay, uint16_t az) {
  readAccelData(ax, ay, az);
  return String(ay);
}

String acc_Z(uint16_t ax, uint16_t ay, uint16_t az) {
  readAccelData(ax, ay, az);
  return String(az);
}



void setup() {

  Serial.begin(115200);
  accel_innit();

  if (!LittleFS.begin()) {
    Serial.println("An Error has occurred while mounting LittleFS");
    return;
  }

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }
  Serial.println(WiFi.localIP()); //

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(LittleFS, "/index.html");
  });
  server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send_P(200, "text/plain", acc_X(ax, ay, az).c_str());
  });
  server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send_P(200, "text/plain", acc_Y(ax, ay, az).c_str());
  });
  server.on("/pressure", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send_P(200, "text/plain", acc_Z(ax, ay, az).c_str());
  });

  // Start server
  server.begin();
}

void loop() {
}

<!DOCTYPE HTML><html>

<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <script src="https://code.highcharts.com/highcharts.js"></script>
  <style>
    body {
      min-width: 310px;
    	max-width: 800px;
    	height: 400px;
      margin: 0 auto;
    }
    h2 {
      font-family: Arial;
      font-size: 2.5rem;
      text-align: center;
    }
  </style>
</head>
<body>
  <h2>Plot</h2>
  <div id="Chart_X_axis" class="container"></div>
  <div id="Chart_Y_axis" class="container"></div>
  <div id="Chart_Z_axis" class="container"></div>
</body>
<script>



var chartT = new Highcharts.Chart({
  chart:{ renderTo : 'Chart_X_axis' },
  title: { text: 'Accelerometer data' },
  series: [{
    showInLegend: false,
    data: []
  }],
  plotOptions: {
    line: { animation: false,
      dataLabels: { enabled: false }
    },
    series: { color: '#059e8a' }
  },
  xAxis: { type: 'datetime',
    dateTimeLabelFormats: { second: '%H:%M:%S' }
  },
  yAxis: {
    title: { text: 'Acceleration X axis (m/s^2)' }
  },
  credits: { enabled: false }
});
setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var x = (new Date()).getTime(),
          y = parseFloat(this.responseText);
      if(chartT.series[0].data.length > 40) {
        chartT.series[0].addPoint([x, y], true, true, true);
      } else {
        chartT.series[0].addPoint([x, y], true, false, true);
      }
    }
  };
  xhttp.open("GET", "/temperature", true);
  xhttp.send();
}, 300 ) ;







var chartH = new Highcharts.Chart({
  chart:{ renderTo:'Chart_Y_axis' },
  title: { text: 'Accelerometer Y axis' },
  series: [{
    showInLegend: false,
    data: []
  }],
  plotOptions: {
    line: { animation: false,
      dataLabels: { enabled: false }
    }
  },
  xAxis: {
    type: 'datetime',
    dateTimeLabelFormats: { second: '%H:%M:%S' }
  },
  yAxis: {
    title: { text: 'Acceleration Y axis (m/s^2)' }
  },
  credits: { enabled: false }
});
setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var x = (new Date()).getTime(),
          y = parseFloat(this.responseText);
      if(chartH.series[0].data.length > 40) {
        chartH.series[0].addPoint([x, y], true, true, true);
      } else {
        chartH.series[0].addPoint([x, y], true, false, true);
      }
    }
  };
  xhttp.open("GET", "/humidity", true);
  xhttp.send();
}, 300 ) ;




var chartP = new Highcharts.Chart({
  chart:{ renderTo:'Chart_Z_axis' },
  title: { text: 'Acceleration Z axis (m/s^2)' },
  series: [{
    showInLegend: false,
    data: []
  }],
  plotOptions: {
    line: { animation: false,
      dataLabels: { enabled: false }
    },
    series: { color: '#18009c' }
  },
  xAxis: {
    type: 'datetime',
    dateTimeLabelFormats: { second: '%H:%M:%S' }
  },
  yAxis: {
    title: { text: 'Acceleration (m/^2)' }
  },
  credits: { enabled: false }
});
setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var x = (new Date()).getTime(),
          y = parseFloat(this.responseText);
      if(chartP.series[0].data.length > 40) {
        chartP.series[0].addPoint([x, y], true, true, true);
      } else {
        chartP.series[0].addPoint([x, y], true, false, true);
      }
    }
  };
  xhttp.open("GET", "/pressure", true);
  xhttp.send();
}, 300 ) ;
</script>
</html>

No there will be several delays from the time it takes your sensor to process it, then Arduino to get the data, processes it and transmit it. There will be delay getting on the network. There will be delay connecting to the target. There will be a delay while the target receives the data. There will be a delay while the target processes the data. There will be a delay while it is being written to the display,

Your software controls the sensors. You can purchase other sensors that will do what you want. The software will process the data as soon as your code gets to it if the sensor is ready.

What is the purpose of the "300" that appears in your Javascript setInterval function?

Don't poll. Have the Javascript be a listener and push data from the Arduino.
Minimize the packet overhead by building messages that contain multiple readings.

If things are still too slow then send raw data without string conversion, use a protocol with less overhead than HTTP. If that still doesn't give the performance then switch to UDP and accept some data loss.

For practical purposes you can assume that the browser has many times the processing power of the Arduino therefore you should minimize the amount of data manipulation done on the Arduino end and let the Javascript deal with the inconvenience of converting the data into a format it can easily digest.

1 Like

Have a look at AsyncWebSocket

you can pack up the 3 variables in an array and push it to the JS with this routine here

 ws_esp_32.binary(const char * array, size_t len);

Are you sure highcharts isn't a bottleneck here ? I would add

console.log(Date.now());

just before and after those lines seeing how long does it take to execute

What is the purpose of the "300" that appears in your Javascript setInterval function?

This sets the interval between to points. Read this as 300 ms between two data points being ploted. Not the correct approach most certainly. I'll change it later for something that plots data every time my sensor has acquired new values. But for the moment, this is not my issue (to my understanding at least)

Don't poll. Have the Javascript be a listener and push data from the Arduino.
Minimize the packet overhead by building messages that contain multiple readings.

Won't that just increase the delay on the Arduino side?
Let's say I want to send 100 samples at a time. I'll then have to wait a full second making my buffer before sending anything, is that right? I may have misunderstood something

If things are still too slow then send raw data without string conversion, use a protocol with less overhead than HTTP. If that still doesn't give the performance then switch to UDP and accept some data loss.

Good point, I'll implement sending raw data, no point in sending a string tbh.
Not sure if I can accept data loss at the moment. Or at least, I'm not sure how much data loss is acceptable. Would TCP be a better option than HTML/UDP?

You know, that is a good point. I'll try measuring the time to plot the variables vs the time to acquire the values to see if theres any problem comming from the charts.

But even if it was indeed a bottleneck, what option would I have to improve latency? Do you think this is not a standard approach?

Turn the calculation around.
First you need to decide what minimum screen update rate is acceptable. 10Hz is probably a reasonable number to start with. You then use that number to calculate the number of samples you need to bundle together in a single packet.

As suggested by KASSIMSAMJI try Websockets. That uses TCPIP as a reliable transport layer.

Javascript running in the browser is restricted in the sense that you cannot just create a listening TCPIP/UDP socket server like you would on an Arduino. Instead you have to use the APIs provided by the Javascript engine. Websockets and WebRTC are two examples.

1 Like

I would go for graphics lib more lightweight, like this