Water Quality Monitoring using TensorFlowLite_ESP32

Hello,
I am creating a project on "ANN-based Water Quality Monitoring" with Arduino UNO and ESP32. As a part of this project, I am using the TensorFlowLite_ESP32 library to access my ANN model "water_quality_monitoring.h". The following code is to be uploaded into my ESP32 devboard.

#include <TensorFlowLite_ESP32.h>
#include "water_quality_model.h"  // Model header file

// Micro TensorFlow Lite (TFLM) Dependencies
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/micro/micro_error_reporter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/micro/all_ops_resolver.h"

#define NUM_FEATURES 4  // Temp, pH, TDS, Turbidity
#define RX2 16
#define TX2 17

// TensorFlow Lite Model Setup
constexpr int tensor_arena_size = 8 * 1024;  // Memory allocation for TFLM
uint8_t tensor_arena[tensor_arena_size];

tflite::MicroErrorReporter error_reporter;
const tflite::Model* tflite_model;
tflite::MicroInterpreter* interpreter;
TfLiteTensor* input;
TfLiteTensor* output;

// Water quality parameters
float temp, ph, tds, turbidity;

void setup() {
  Serial.begin(115200);
  Serial2.begin(9600, SERIAL_8N1, RX2, TX2);  // ESP32 receives data from Arduino Uno via Serial2

  // Load TensorFlow Lite Model
  tflite_model = tflite::GetModel(water_quality_model);
  if (tflite_model->version() != TFLITE_SCHEMA_VERSION) {
    Serial.println("Model version mismatch!");
    return;
  }

  // Set up TFLite interpreter with Micro TensorFlow Lite
  static tflite::AllOpsResolver resolver;
  static tflite::MicroInterpreter static_interpreter(tflite_model, resolver, tensor_arena, tensor_arena_size, &error_reporter);

  interpreter = &static_interpreter;
  interpreter->AllocateTensors();
  input = interpreter->input(0);
  output = interpreter->output(0);

  Serial.println("Micro TensorFlow Lite Model Initialized.");
}

void loop() {
  // Read water parameters from Arduino Uno (Assuming comma-separated values)
  if (Serial2.available()) {
    String data = Serial2.readStringUntil('\n');
    sscanf(data.c_str(), "%f,%f,%f,%f", &temp, &ph, &tds, &turbidity);

    Serial.print("Received Data: ");
    Serial.println(data);

    // Set input tensor values
    input->data.f[0] = temp;
    input->data.f[1] = ph;
    input->data.f[2] = tds;
    input->data.f[3] = turbidity;

    // Run inference
    interpreter->Invoke();

    // Retrieve classification results
    int usage = static_cast<int>(output->data.f[0]);
    int composition = static_cast<int>(output->data.f[1]);
    int source = static_cast<int>(output->data.f[2]);

    // Print results to Serial Monitor
    Serial.println("Classification Results:");
    Serial.print("Usage: ");
    switch (usage) {
      case 0: Serial.println("DRINKING WATER"); break;
      case 1: Serial.println("INDUSTRIAL WATER"); break;
      case 2: Serial.println("AGRICULTURAL WATER"); break;
      case 3: Serial.println("WASTEWATER"); break;
      default: Serial.println("UNKNOWN"); break;
    }

    Serial.print("Composition: ");
    switch (composition) {
      case 0: Serial.println("FRESHWATER"); break;
      case 1: Serial.println("BRACKISH WATER"); break;
      case 2: Serial.println("SALINE WATER"); break;
      case 3: Serial.println("HARD WATER"); break;
      case 4: Serial.println("SOFT WATER"); break;
      default: Serial.println("UNKNOWN"); break;
    }

    Serial.print("Source: ");
    switch (source) {
      case 0: Serial.println("SURFACE WATER"); break;
      case 1: Serial.println("GROUNDWATER"); break;
      case 2: Serial.println("RAINWATER"); break;
      case 3: Serial.println("SEAWATER"); break;
      default: Serial.println("UNKNOWN"); break;
    }

    Serial.println("-----------------------------------------");
  }

  delay(1000);  // Wait before processing the next reading
}

After compiling, I get the following error(s):

In file included from e:\Arduino 2\PROGRAM FILES\libraries\TensorFlowLite_ESP32\src/third_party/flatbuffers/array.h:21,
                 from e:\Arduino 2\PROGRAM FILES\libraries\TensorFlowLite_ESP32\src/third_party/flatbuffers/flatbuffers.h:22,
                 from e:\Arduino 2\PROGRAM FILES\libraries\TensorFlowLite_ESP32\src/tensorflow/lite/micro/micro_interpreter.h:21,
                 from E:\Arduino 2\PROGRAM FILES\ESP32_WATER_QUALITY_WIFI2\ESP32_WATER_QUALITY_WIFI2.ino:5:
e:\Arduino 2\PROGRAM FILES\libraries\TensorFlowLite_ESP32\src/third_party/flatbuffers/stl_emulation.h: In member function 'constexpr flatbuffers::span<T, Extent>& flatbuffers::span<T, Extent>::operator=(const flatbuffers::span<T, Extent>&)':
e:\Arduino 2\PROGRAM FILES\libraries\TensorFlowLite_ESP32\src/third_party/flatbuffers/stl_emulation.h:386:12: error: assignment of read-only member 'flatbuffers::span<T, Extent>::count_'
  386 |     count_ = other.count_;
      |     ~~~~~~~^~~~~~~~~~~~~~

exit status 1

Compilation error: exit status 1

Please let me know what causes the error and how it can be solved.
Thank you.

Best regards,
Sai Mukul

Hi @saimukul24

welcome to the arduino-forum.

Well done posting your code and error-messages as a code-section in your first post.

e:\Arduino 2\PROGRAM FILES\libraries\TensorFlowLite_ESP32\src/third_party/flatbuffers/stl_emulation.h: 
In member function 'constexpr flatbuffers::span<T, Extent>& flatbuffers::span<T, Extent>::operator=(const flatbuffers::span<T, Extent>&)':
e:\Arduino 2\PROGRAM FILES\libraries\TensorFlowLite_ESP32\src/third_party/flatbuffers/stl_emulation.h:386:12: 
error: assignment of read-only member 'flatbuffers::span<T, Extent>::count_'

Inside the error-log there is a line that starts with ": error:"
Which is that line that tells you in which source-codefile at which line and columm the error occurred.

: error: assignment of read-only member 

says that the code is trying to assign a new value to a variable that is read-only.
Which means the code tries to violate the read-only restriction.

The file is a library-file which I assume you did not modify at all.

So with this rather small information it seems to be a problem of the library-file.
Next step googling or using an AI like

to search for the error-messages tyring to find a solution.

Using an AI to create really bugfree working code is really rare, because you need to know a lot of about programming to even write down a sufficient prompt that is describing the functionality precise enough to get (almost) working code

But using an AI to collect human-based solutions for problems,
in my personal experience has a good chance to work. At least to guide you to forums or information that can be used as a starting point.

So I asked perplexity.ai with this prompt

prompt submitted to perplexity.ai

perplexity I am using the arduino-IDE V2 with this code

#include <TensorFlowLite_ESP32.h>
#include "water_quality_model.h"  // Model header file

// Micro TensorFlow Lite (TFLM) Dependencies
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/micro/micro_error_reporter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/micro/all_ops_resolver.h"

#define NUM_FEATURES 4  // Temp, pH, TDS, Turbidity
#define RX2 16
#define TX2 17

// TensorFlow Lite Model Setup
constexpr int tensor_arena_size = 8 * 1024;  // Memory allocation for TFLM
uint8_t tensor_arena[tensor_arena_size];

tflite::MicroErrorReporter error_reporter;
const tflite::Model* tflite_model;
tflite::MicroInterpreter* interpreter;
TfLiteTensor* input;
TfLiteTensor* output;

// Water quality parameters
float temp, ph, tds, turbidity;

void setup() {
  Serial.begin(115200);
  Serial2.begin(9600, SERIAL_8N1, RX2, TX2);  // ESP32 receives data from Arduino Uno via Serial2

  // Load TensorFlow Lite Model
  tflite_model = tflite::GetModel(water_quality_model);
  if (tflite_model->version() != TFLITE_SCHEMA_VERSION) {
    Serial.println("Model version mismatch!");
    return;
  }

  // Set up TFLite interpreter with Micro TensorFlow Lite
  static tflite::AllOpsResolver resolver;
  static tflite::MicroInterpreter static_interpreter(tflite_model, resolver, tensor_arena, tensor_arena_size, &error_reporter);

  interpreter = &static_interpreter;
  interpreter->AllocateTensors();
  input = interpreter->input(0);
  output = interpreter->output(0);

  Serial.println("Micro TensorFlow Lite Model Initialized.");
}

void loop() {
  // Read water parameters from Arduino Uno (Assuming comma-separated values)
  if (Serial2.available()) {
    String data = Serial2.readStringUntil('\n');
    sscanf(data.c_str(), "%f,%f,%f,%f", &temp, &ph, &tds, &turbidity);

    Serial.print("Received Data: ");
    Serial.println(data);

    // Set input tensor values
    input->data.f[0] = temp;
    input->data.f[1] = ph;
    input->data.f[2] = tds;
    input->data.f[3] = turbidity;

    // Run inference
    interpreter->Invoke();

    // Retrieve classification results
    int usage = static_cast<int>(output->data.f[0]);
    int composition = static_cast<int>(output->data.f[1]);
    int source = static_cast<int>(output->data.f[2]);

    // Print results to Serial Monitor
    Serial.println("Classification Results:");
    Serial.print("Usage: ");
    switch (usage) {
      case 0: Serial.println("DRINKING WATER"); break;
      case 1: Serial.println("INDUSTRIAL WATER"); break;
      case 2: Serial.println("AGRICULTURAL WATER"); break;
      case 3: Serial.println("WASTEWATER"); break;
      default: Serial.println("UNKNOWN"); break;
    }

    Serial.print("Composition: ");
    switch (composition) {
      case 0: Serial.println("FRESHWATER"); break;
      case 1: Serial.println("BRACKISH WATER"); break;
      case 2: Serial.println("SALINE WATER"); break;
      case 3: Serial.println("HARD WATER"); break;
      case 4: Serial.println("SOFT WATER"); break;
      default: Serial.println("UNKNOWN"); break;
    }

    Serial.print("Source: ");
    switch (source) {
      case 0: Serial.println("SURFACE WATER"); break;
      case 1: Serial.println("GROUNDWATER"); break;
      case 2: Serial.println("RAINWATER"); break;
      case 3: Serial.println("SEAWATER"); break;
      default: Serial.println("UNKNOWN"); break;
    }

    Serial.println("-----------------------------------------");
  }

  delay(1000);  // Wait before processing the next reading
}

I get this error-message

In file included from e:\Arduino 2\PROGRAM FILES\libraries\TensorFlowLite_ESP32\src/third_party/flatbuffers/array.h:21,
                 from e:\Arduino 2\PROGRAM FILES\libraries\TensorFlowLite_ESP32\src/third_party/flatbuffers/flatbuffers.h:22,
                 from e:\Arduino 2\PROGRAM FILES\libraries\TensorFlowLite_ESP32\src/tensorflow/lite/micro/micro_interpreter.h:21,
                 from E:\Arduino 2\PROGRAM FILES\ESP32_WATER_QUALITY_WIFI2\ESP32_WATER_QUALITY_WIFI2.ino:5:
e:\Arduino 2\PROGRAM FILES\libraries\TensorFlowLite_ESP32\src/third_party/flatbuffers/stl_emulation.h: In member function 'constexpr flatbuffers::span<T, Extent>& flatbuffers::span<T, Extent>::operator=(const flatbuffers::span<T, Extent>&)':
e:\Arduino 2\PROGRAM FILES\libraries\TensorFlowLite_ESP32\src/third_party/flatbuffers/stl_emulation.h:386:12: error: assignment of read-only member 'flatbuffers::span<T, Extent>::count_'
  386 |     count_ = other.count_;
      |     ~~~~~~~^~~~~~~~~~~~~~

exit status 1

Compilation error: exit status 1

find me possible GitHub-issues and user-forum suggestions how to solve this compiler-error

perplexity responded with this

The compilation error you're encountering stems from a conflict in the FlatBuffers library's stl_emulation.h file, specifically related to the count_ member being marked as read-only. Here's a breakdown of the issue and solutions based on GitHub discussions and forum posts:


Root Cause

The error arises because the count_ member variable in flatbuffers::span is declared as const in older versions of FlatBuffers, preventing assignment. This conflicts with code expecting mutable spans.


Solutions from Community Reports

1. Enable FLATBUFFERS_USE_STD_SPAN Compiler Flag

  • Add this flag to force FlatBuffers to use std::span instead of its emulated version:

bash

-DFLATBUFFERS_USE_STD_SPAN
  • In Arduino IDE, add this under "Sketch → Include Library → Add Compiler Flag" or modify platform.local.txt 15.

2. Update FlatBuffers Library

  • The issue was fixed in FlatBuffers v2.0.6+ by removing the const qualifier from count_ 25. Update the library:
    • Navigate to TensorFlowLite_ESP32/src/third_party/flatbuffers and replace it with the latest version.

3. Manual Patch (Temporary Fix)

  • Modify stl_emulation.h in your local installation:

cpp

// Line 386 (remove const from count_ declaration)
size_t count_;  // Originally: const size_t count_;
  • Location: Arduino/libraries/TensorFlowLite_ESP32/src/third_party/flatbuffers/stl_emulation.h 25.

4. Check TensorFlow Lite Version

  • Ensure you’re using a TensorFlow Lite Micro version that includes this fix. Older versions may bundle incompatible FlatBuffers code57.

Relevant GitHub Issues


Implementation Steps

  1. Try the compiler flag first (easiest).
  2. If unresolved, update FlatBuffers manually or via the library manager.
  3. For a quick test, patch stl_emulation.h as described.

This error is a known compatibility issue and is resolvable with updates or configuration tweaks1257.

As I already write "good chance to find helping information. Not giving a complete solution and still needs to be checked if the presented information does exactly fit to

  • your exact type of microcontroller
  • your exact type and version of the libraries that you use

The Espressif Corporation recently changed many board definitions and library files, which broke a lot of existing code.

Although that particular type of error should not be caused or related to those changes, you may need to upload old board definitions or libraries to get the original code to compile.

Hello @StefanL38 ,

Thanks for showing your interest,

I have tried the methods mentioned and only after updating the FlatBuffers library I got the below error:

e:\Arduino 2\PROGRAM FILES\libraries\TensorFlowLite_ESP32\src\third_party\flatbuffers-master\android\app\src\main\cpp\animals.cpp:17:10: fatal error: jni.h: No such file or directory
   17 | #include <jni.h>
      |          ^~~~~~~
compilation terminated.
exit status 1

Compilation error: exit status 1

The other methods didn't work out though.

e:\Arduino 2\PROGRAM FILES\libraries\TensorFlowLite_ESP32\src\third_party\flatbuffers-master\android\app\src\main\cpp\animals.cpp:17:10: fatal error: jni.h: No such file or directory
   17 | #include <jni.h>
      |          ^~~~~~~
compilation terminated.
exit status 1

Compilation error: exit status 1

This is the only error I got.

There is a real important reason why this tutorial is written.
If you follow the tutorial how to adjust the arduino-IDE to verbose output.
The compiling-process will produce hundreds of lines in this output-window.

You should post the complete compile-log content ! Not only the error-message. The complete compile-log.

This compile-log has additional informations like

  • library-versions,
  • microcontroller core-version

and the simple way for you to provide this information is to post the complete log as a code-section.

The hard way is to learn all the things that are important to analyse your compiler-error and then picking the relevant lines out of the compile-log.

So now: which way do you prefer ?

Okay, I have followed the tutorial, as well as I removed the android folder present in the flatbuffers folder associated with the jni.h error, and here is what I got now:

In file included from e:\Arduino 2\PROGRAM FILES\libraries\TensorFlowLite_ESP32\src\third_party\flatbuffers\src\bfbs_namer.h:5,
                 from e:\Arduino 2\PROGRAM FILES\libraries\TensorFlowLite_ESP32\src\third_party\flatbuffers\src\bfbs_gen_lua.cpp:28:
e:\Arduino 2\PROGRAM FILES\libraries\TensorFlowLite_ESP32\src\third_party\flatbuffers\src\namer.h:4:10: fatal error: codegen/namer.h: No such file or directory
    4 | #include "codegen/namer.h"
      |          ^~~~~~~~~~~~~~~~~
compilation terminated.
Alternatives for codegen/namer.h: []
ResolveLibrary(codegen/namer.h)
  -> candidates: []
exit status 1

Compilation error: exit status 1

I have doubts that you reallly adjusted the compiler log to maximum verbose output.
There should be more lines.

It is very unlikely that the compiler stops compiling just after these few lines.

Which Arduino IDE are you using? Version 1.8.19 or Version 2.X?

Post a screenshot of the Arduino-IDE showing the preferences window

Actually there are more lines, just it didn't me post here due to some 120000 characters limit. Let me post the screenshot. I am using arduino 2.3.4

Here's the screenshot of preferences:

Ah ok the ESP32 really big compiler-log exceeding the 120000 character-limit.

Copy the log into clipboard. Paste into a texteditor. Safe as *.txt-file

Attach the *.txt-file to a posting.

Here it is:
error.txt (348.1 KB)

OK you are using Arduino IDE 2.3.4
You use the ESP32 core 3.2.0
You are using TensorFlowLite_ESP32 version 1.0.0]

The error says this file is missing
codegen/namer.h

codegen seems to be a subdirectory

Inside the file

e:\Arduino 2\PROGRAM FILES\libraries\TensorFlowLite_ESP32\src\third_party\flatbuffers\src\bfbs_gen_lua.cpp

at line 28 there is the include for the file codegen/namer.h

I looked it up on github

The files listed on github do not have file bfbs_namer.h

At this point I do not know how to go on.
It might be that the code that you are using is completely different from the Github link I posted.

You will have to provide the complete things you did.

Did you install it from the arduino-IDE library manager
did you install the library as a ZIP-library??
If yes from where did you download the TensorFlowLite_ESP32-library
post the exact link

I did a search for this filename and only came up with this

which seems not arduino related

Well, I have installed the library zip file from the below link:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.