Moyenne de mesures fausse à chaque cycle de mesure

Bonjour,
J’ai fait un montage pour controler le niveau de l’eau dans un bassin de jardin
en utilisant un capteur à ultrasons et déclencher l’ouverture d’une electrovanne 9V à impulsion quand le niveau baisse pour remplir.

Mon code est censé faire une moyenne de 30 mesures pour éviter de se déclencher au moindre remous à la surface mais j’ai essayé différentes méthodes de calcul de cette moyenne des mesures et j’arrive toujours à faire la même erreur.
Quand le cycle de mesures repart la valeur de la moyenne n'est plus dans la continuité du reste, la première mesure de la série de 30 le calcul de la moyenne est faussée car elle se base sur la mesure n1/1 et donc ça déclenche l’ouverture de la vanne.

La moyenne au lieu d'etre "glissante" progressivement sur les 30 dernières valeurs se fait par bloc de 30 mesures puis repart sur un nouvel ensemble de 30 mesures.

Quelque chose de surement très basique m’échappe… la programmation n’est pas mon fort. :-[

Voici mon code.
Celui ci contient en plus de la partie gestion du niveau, une partie qui me permet de calibrer le niveau à respecter en appuyant sur un bouton et stocker la valeur dans l’eeprom, une partie pour un capteur de température car je compte ajouter un capteur de température de l'eau
et la partie série me sert à faire à remonter avec un ESP12 avec espeasy les infos à ma solution domotique.

Je vous remercie.

// ---------------------------------------------------------------------------
// Capteur Niveau Ultrasons
// ---------------------------------------------------------------------------

#include <NewPing.h>
#include <EEPROM.h>
#include <SoftwareSerial.h>
#include <OneWire.h> //Librairie du bus OneWire
#include <DallasTemperature.h> //Librairie du capteur

OneWire oneWire(11); //Bus One Wire sur la pin 2 de l'arduino
DallasTemperature sensors(&oneWire); //Utilistion du bus Onewire pour les capteurs
DeviceAddress sensorDeviceAddress; //Vérifie la compatibilité des capteurs avec la librairie

SoftwareSerial ESPserial(4, 3); // RX | TX
int valvePin_a = 6;
int valvePin_b = 7;
#define TRIGGER_PIN  9  // Arduino pin tied to trigger pin on the ultrasonic sensor.
#define ECHO_PIN     10  // Arduino pin tied to echo pin on the ultrasonic sensor.
#define valvePin  13// Arduino pin tied to valve pin.
#define setdistPin     12  // Arduino pin tied to calibration pin.

#define MAX_DISTANCE 250 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.
int addr = 0; // EEPROM Address
int measuredDist = 0; // Sensor Distance
int buttonState = 0;         // variable for reading the pushbutton status
int valve_state=0;

int calibrationDist = 0;
int lastButtonState = 0;     // previous state of the button
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance

unsigned long previousMillis = 0;        // last time measure was updated
unsigned long cal_previousMillis = 0;        // last time calibration measure was updated
unsigned long fill_previousMillis = 0;        // last time refill measure was updated
long interval = 2000;           // interval at which to measure distance
long interval_measurement = 1000;           // interval at which to measure distance
long interval_refill = 500;           // interval at which to measure distance
const long cal_interval = 500;           // debounce calibration
int MeasurementsToAverage = 30;
int average = 0;                // the average
int lastaverage = 0;     // previous state of the button


void setup() {
  Serial.begin(115200); // Open serial monitor at 115200 baud to see ping results.
   // initialize the LED pin as an output:
   while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  ESPserial.begin(57600); 
  pinMode(valvePin, OUTPUT);
  pinMode(valvePin_a, OUTPUT);
  pinMode(valvePin_b, OUTPUT);
  // initialize the pushbutton pin as an input:
  pinMode(setdistPin, INPUT);
  calibrationDist = EEPROM.read(addr);
  Serial.println("-----------------------");
  Serial.println("Pond Refill");
  Serial.println("-----------------------");
 digitalWrite(valvePin_a, LOW);
 digitalWrite(valvePin_b, LOW);
}

void loop() {


  if ( ESPserial.available() )   {  Serial.write( ESPserial.read() );  }
 
    // listen for user input and send it to the ESP8266
    if ( Serial.available() )       {  ESPserial.write( Serial.read() );  }
    
 buttonState = digitalRead(setdistPin);

 if (buttonState == HIGH) {
set_distance();
  } 
  
  else {

measure_timer();
refill();

  }



}


// Solenoid Open-Close functions
void open_valve()
{
   digitalWrite(valvePin_a, HIGH);
   digitalWrite(valvePin_b, LOW);
  delay (100);
 digitalWrite(valvePin_a, LOW);
 digitalWrite(valvePin_b, LOW);
 valve_state=1;
 Serial.println("Valve");
Serial.print(valve_state);
}

void close_valve()
{
 digitalWrite(valvePin_a, LOW);
 digitalWrite(valvePin_b, HIGH);
 delay (100);
 digitalWrite(valvePin_a, LOW);
 digitalWrite(valvePin_b, LOW);
 valve_state=0;
 Serial.println("Valve");
Serial.print(valve_state);
}


//mesure du niveau

int measure_distance(){
  measuredDist = sonar.ping_cm();
  for(int i = 0; i < MeasurementsToAverage; ++i)
  {
    average += measuredDist;
    
  }
  average /= MeasurementsToAverage;
Serial.println(average);
 if (average != lastaverage) {
 String StrTsk1 = "TaskValueSet,1,1,";
 String StrAverage = StrTsk1 + average ;
  ESPserial.println(StrAverage);
  delay(50);
 }


lastaverage = average;
  }

int measure_timer(){
   unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;
    measure_distance();
  }
}


//calibration
int set_distance(){

 buttonState = digitalRead(setdistPin);

  // check if the pushbutton is pressed. If it is, the buttonState is HIGH:
  if (buttonState == HIGH) {

unsigned long currentMillis = millis();
  if (currentMillis - cal_previousMillis >  cal_interval) {
    // save the last time you checked water level
    cal_previousMillis = currentMillis;

    // turn LED on:
    digitalWrite(valvePin, HIGH);
  EEPROM.write(addr, measuredDist);
  calibrationDist = EEPROM.read(addr);
  digitalWrite(valvePin, LOW);
  Serial.println("Calibration");
  Serial.println(calibrationDist);
  } 
  } 
  else {
  }
}


//remplissage

void refill(){
// if the distance is higher than average start refill
   if (average > calibrationDist) {
    interval = interval_refill;
   if (valve_state < 1){
    Serial.println(valve_state);
    open_valve();
   }
    

    
unsigned long currentMillis = millis();
  if (currentMillis - fill_previousMillis >= cal_interval) {
    // save the last time you checked water level
    fill_previousMillis = currentMillis;
 
  
  } 
     } 
     
     else {
    if (valve_state > 0){
      Serial.println(valve_state);
    close_valve();
    interval = interval_measurement;
       }
    
 
    }
}



  //lecture température

void temperature_read(){
   sensors.requestTemperatures(); //Demande la température aux capteurs
 Serial.print("La température est: ");
 Serial.print(sensors.getTempCByIndex(0)); //Récupération de la température en celsius du capteur n°0
 Serial.println(" C°");
}

jr2fl:

  measuredDist = sonar.ping_cm();

for(int i = 0; i < MeasurementsToAverage; ++i)
  {
    average += measuredDist;
   
  }
  average /= MeasurementsToAverage;
}

C'est quoi ça ?!?
tu prend une mesure, une seule,
tu l'ajoutes N fois à average, puis tu divises par N
Si average vaut zéro au départ, à la fin il vaut exactement la seule mesure que tu as faite.
Mais en plus, comme tu as oublié de le remettre à zéro, le résultat est ... n'importe quoi

vu l'effort de description et l'usage des balises de code pour un premier post, faut encourager :slight_smile:

Cependant comme l'a dit biggil, vous n'effectuez qu'une seule mesure puisque le sonar.ping_cm(); n'est pas dans votre boucle.

Si vous voulez faire une moyenne de 30 valeurs non glissantes sur 30 secondes, c'est simple, vous basculez la mesure dans la boucle et là c'est bon (à condition que sonar.ping_cm ne plante pas).

average = 0;
for(int i = 0; i < MeasurementsToAverage; ++i) {
  average += sonar.ping_cm();
  delay(1); // un ping par seconde
} // on est bloqué 30 secondes. Amélioration: utiliser la loop et millis()
average /= MeasurementsToAverage;

si vous voulez que ce soit glissant, il faut un tableau de 30 cases que vous remplissez, idéalement en mode circulaire. mais le bassin va-t-il vraiment baisser de niveau en 30 secondes ? ça me semble un peu 'over-engineered'

Merci pour les explications, en effet mon erreur est évidente une fois soulignée, comme la plupart du temps je me perds dans ce que je fais.

Concernant l’ajout du delais, je vois que je fais encore une erreur car je pensais que mon code faisait déja le délais de 1 seconde entre chaque mesure.

Dans ma logique:

int measure_timer() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;
    measure_distance();
  }
}

lançait chaque seconde une mesure de la distance:

 int measure_distance() {
  average = 0;
    for (int i = 0; i < MeasurementsToAverage; ++i)
    {
      average += sonar.ping_cm();
    }

  average /= MeasurementsToAverage;
  }

qui faisait un ping et incrémentait le compteur et qui au bout de 30 lancements repartait à zéro donc je pensais arriver à 30 secondes en déclenchant une mesure chaque seconde.
Hors la je comprends que je lui indique de faire chaque seconde une salve de 30 mesures.

Je pensais donc que la fréquence à laquelle se faisait la moyenne était "interval" * X mesures hors en mettant le délais de 1 seconde je vois que une seule mesure s'effectue donc il semble que je fasse: fréquence de la moyenne ="interval" et que les X mesures se fassent toutes dans la durée de "interval"
... j'ai encore tout compris à l'envers visiblement. :-[

:wink:

C’est en forgeant qu’on devient forgeron

J'ai modifié mon code, il semblerait que cela fonctionne...
mais j'ai l'impression d'avoir pris des chemins détournés au lieu de faire au plus simple.
pouvez-vous me donner votre avis sur les modifications que j'ai apporté.
merci

// ---------------------------------------------------------------------------
// Capteur Niveau Ultrasons
// ---------------------------------------------------------------------------

#include <NewPing.h>
#include <EEPROM.h>
#include <SoftwareSerial.h>
#include <OneWire.h> //Librairie du bus OneWire
#include <DallasTemperature.h> //Librairie du capteur

OneWire oneWire(11); //Bus One Wire sur pin 11
DallasTemperature sensors(&oneWire); //bus Onewire pour les capteurs
DeviceAddress sensorDeviceAddress; //Vérifie la compatibilité des capteurs avec la librairie

SoftwareSerial ESPserial(4, 3); // RX | TX ESP
int valvePin_a = 6; //Pin A inverseur vanne
int valvePin_b = 7; //Pin B inverseur vanne
#define TRIGGER_PIN  9  // Arduino pin tied to trigger pin on the ultrasonic sensor.
#define ECHO_PIN     10  // Arduino pin tied to echo pin on the ultrasonic sensor.
#define setdistPin     12  // Arduino pin tied to calibration pin.
#define valvePin  13// Arduino pin tied to valve pin.


#define MAX_DISTANCE 250 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.
int addr = 0; // EEPROM Address
int measuredDist = 0; // Sensor Distance
int buttonState = 0;         // variable for reading the pushbutton status
int valve_state = 0;

int calibrationDist = 0;
int lastButtonState = 0;     // previous state of the button
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance


unsigned long cal_previousMillis = 0;        // last time calibration measure was updated
unsigned long fill_previousMillis = 0;        // last time refill measure was updated
unsigned long read_previousMillis = 0;        // last time measure was done
unsigned long average_previousMillis = 0;     // last time average was done

long interval_measurement = 2000;           // interval at which to measure distance
long interval_refill = 1000;           // interval at which to measure distance when refilling
long interval = 0;           // interval at which to measure distance
const int MeasurementsToAverage = 30; // nombre de mesures à effectuer

const long cal_interval = 500;           // debounce calibration button
int average = 0;                // the average
int lastaverage = 0;     // previous average

int average_interval= 0;

int readings = 0;

void setup() {
  Serial.begin(115200); // Open serial monitor at 115200 baud to see ping results.
  // initialize the LED pin as an output:
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  interval = interval_measurement;
  ESPserial.begin(57600);
  pinMode(valvePin, OUTPUT);
  pinMode(valvePin_a, OUTPUT);
  pinMode(valvePin_b, OUTPUT);
  // initialize the pushbutton pin as an input:
  pinMode(setdistPin, INPUT);
  calibrationDist = EEPROM.read(addr);
  Serial.println("-----------------------");
  Serial.println("Pond Refill");
  Serial.println("-----------------------");
  digitalWrite(valvePin_a, LOW);
  digitalWrite(valvePin_b, LOW);
}

void loop() {


  if ( ESPserial.available() )   {
    Serial.write( ESPserial.read() );
  }

  // listen for user input and send it to the ESP8266
  if ( Serial.available() )       {
    ESPserial.write( Serial.read() );
  }

  buttonState = digitalRead(setdistPin);

  if (buttonState == HIGH) {
    set_distance();
  }

  else {

    measure_counter();
    refill();


  }



}


// Solenoid Open-Close functions
void open_valve()
{
  digitalWrite(valvePin_a, HIGH);
  digitalWrite(valvePin_b, LOW);
  delay (100);
  digitalWrite(valvePin_a, LOW);
  digitalWrite(valvePin_b, LOW);
  valve_state = 1;
  Serial.println("Valve");
  Serial.print(valve_state);
}

void close_valve()
{
  digitalWrite(valvePin_a, LOW);
  digitalWrite(valvePin_b, HIGH);
  delay (100);
  digitalWrite(valvePin_a, LOW);
  digitalWrite(valvePin_b, LOW);
  valve_state = 0;
  Serial.println("Valve");
  Serial.print(valve_state);
}



//Mesure de la distance

int measure_distance(){

  unsigned long currentMillis = millis(); 
  if (currentMillis - read_previousMillis >= interval) {
  read_previousMillis = currentMillis;
  measuredDist = sonar.ping_cm();
  readings += measuredDist;

  Serial.print(measuredDist);
  Serial.print("cm - somme: ");
  Serial.print(readings);
  Serial.println(" cm ");


}
}


int measure_counter() {

    for (int i = 0; i < MeasurementsToAverage; ++i)
    {  
      measure_distance();
      
    }
    int average_interval= MeasurementsToAverage*interval;
unsigned long currentMillis = millis(); 

if (currentMillis - average_previousMillis >= average_interval) {
average_previousMillis = currentMillis;
 average = readings/MeasurementsToAverage;
Serial.println(average);

 if (average != lastaverage) {
 String StrTsk1 = "TaskValueSet,1,1,";
 String StrAverage = StrTsk1 + average ;
  ESPserial.println(StrAverage);
  delay(50);
 }
lastaverage = average;
readings = 0;
}



}

//Calibration
int set_distance() {

  buttonState = digitalRead(setdistPin);

  // check if the pushbutton is pressed. If it is, the buttonState is HIGH:
  if (buttonState == HIGH) {

    unsigned long currentMillis = millis();
    if (currentMillis - cal_previousMillis >  cal_interval) {
      // save the last time you checked water level
      cal_previousMillis = currentMillis;

      // turn LED on:
      digitalWrite(valvePin, HIGH);
      EEPROM.write(addr, measuredDist);
      calibrationDist = EEPROM.read(addr);
      digitalWrite(valvePin, LOW);
      Serial.println("Calibration");
      Serial.println(calibrationDist);
    }
  }
  else {
  }
}



//Remplissage
void refill() {
  // if the distance is higher than average start refill
  if (average > calibrationDist) {
    interval = interval_refill;
    if (valve_state < 1) {
      Serial.println(valve_state);
      open_valve();
    }

    unsigned long currentMillis = millis();
    if (currentMillis - fill_previousMillis >= cal_interval) {
      // save the last time you checked water level
      fill_previousMillis = currentMillis;
    }
  }

  else {
    if (valve_state > 0) {
      Serial.println(valve_state);
      close_valve();
      interval = interval_measurement;
    }

  }
}


//Lecture de la température
void temperature_read() {
  sensors.requestTemperatures(); //Demande la température aux capteurs
  Serial.print("La température est: ");
  Serial.print(sensors.getTempCByIndex(0)); //Récupération de la température en celsius du capteur n°0
  Serial.println(" C°");
}

lastButtonState ne sert à rien

pas la peine de prendre de la mémoire pour buttonState, faites directement   if (digitalRead(setdistPin) == HIGH) {...}

Préférez une variable const typée à un #define. ça aide le compilateur à gérer cette valeur.

const byte valvePin_a =   6; //Pin A inverseur vanne
const byte valvePin_b =   7; //Pin B inverseur vanne

const byte TRIGGER_PIN =  9; // Arduino pin tied to trigger pin on the ultrasonic sensor.
const byte ECHO_PIN    = 10; // Arduino pin tied to echo pin on the ultrasonic sensor.
const byte setdistPin  = 12; // Arduino pin tied to calibration pin.
const byte valvePin    = 13; // Arduino pin tied to valve pin.

tout ce qui est constant doit être déclaré comme tel, par exemple

int addr = 0; // EEPROM Address

doit êtreconst uint16_t addr = 0; // EEPROM Addresset autant lui donner un joli nom et commentaireconst uint16_t adresseSauvegardeEEPROM = 0; // where to save in EEPROR

Les fonctions int measure_counter() {ou encore

int measure_distance() {

sont supposées renvoyer un int mais ne revoie rien... Vous mentez au compilateur, ce n'est pas bien :slight_smile: --> passez les en type de retour 'void'

Je ne suis pas sûr de bien saisir ce que vous faites ici

void measure_distance()
{
  unsigned long currentMillis = millis();
  if (currentMillis - read_previousMillis >= interval) {
    read_previousMillis = currentMillis;
    measuredDist = sonar.ping_cm();
    readings += measuredDist;

    Serial.print(measuredDist);
    Serial.print("cm - somme: ");
    Serial.print(readings);
    Serial.println(" cm ");
  }
}


void measure_counter() {

  for (int i = 0; i < MeasurementsToAverage; ++i) measure_distance();

  int average_interval = MeasurementsToAverage * interval;
  unsigned long currentMillis = millis();

  if (currentMillis - average_previousMillis >= average_interval) {
    average_previousMillis = currentMillis;
    average = readings / MeasurementsToAverage;
    Serial.println(average);

    if (average != lastaverage) {
      String StrTsk1 = "TaskValueSet,1,1,";
      String StrAverage = StrTsk1 + average ;
      ESPserial.println(StrAverage);
      delay(50);
    }
    lastaverage = average;
    readings = 0;
  }
}

vous nous expliquez ce qu'il va se passer pendant l'exécution de la première boucle for de measure_counter() par exemple ? et que fait la suite ?

quand un else est vide, ce n'est pas la peine de le mettre, ça alourdit la lecture pour rien.

  // check if the pushbutton is pressed. If it is, the buttonState is HIGH:
  if (digitalRead(setdistPin) == HIGH) {
    unsigned long currentMillis = millis();
    if (currentMillis - cal_previousMillis >  cal_interval) {
..
   }
  }
  else { // <<=== inutile
  }

Pourquoi faites vous cela:

    unsigned long currentMillis = millis();
    if (currentMillis - fill_previousMillis >= cal_interval) {
      // save the last time you checked water level
      fill_previousMillis = currentMillis;
    }

généralement ce n'est pas juste la mise à jour de la variable temporelle qui doit être dans le test, mais aussi l'exécution de la commande, sinon vous l'exécutez tout le temps...

--> bref du nettoyage encore à effectuer.

C'est un programme qui se prête bien à la programmation par machine à états (cf mon tuto éventuellement)

Merci pour toutes ces corrections, j'ai fait les modifications indiquées
et merci pour le tutoriel que je vais essayer de suivre, il semble en plus à première vue être utilisable pour 2 autres projets que j'ai autour du bassin de jardin.

Concernant les éléments que vous avez soulevé:

void measure_distance()
{
  unsigned long currentMillis = millis();
  if (currentMillis - read_previousMillis >= interval) {
    read_previousMillis = currentMillis;
    measuredDist = sonar.ping_cm();
    readings += measuredDist;

    Serial.print(measuredDist);
    Serial.print("cm - somme: ");
    Serial.print(readings);
    Serial.println(" cm ");
  }
}

void measure_counter() {

  for (int i = 0; i < MeasurementsToAverage; ++i) measure_distance();

  int average_interval = MeasurementsToAverage * interval;
  unsigned long currentMillis = millis();

  if (currentMillis - average_previousMillis >= average_interval) {
    average_previousMillis = currentMillis;
    average = readings / MeasurementsToAverage;
    Serial.println(average);

    if (average != lastaverage) {
      String StrTsk1 = "TaskValueSet,1,1,";
      String StrAverage = StrTsk1 + average ;
      ESPserial.println(StrAverage);
      delay(50);
    }
    lastaverage = average;
    readings = 0;
  }
}

C’est justement cette partie qui me semble prendre beaucoup de détours pour faire une chose simple.
-mesure 1, calcul du délais à attendre avant de faire la moyenne
-mesure 2 , re calcul du délais…..absolument inutile mais à ce moment là je voyais pas comment faire autrement

Finalement j’ai modifié pour faire plus simplement sans être sur que ce soit vraiment la manière la plus simple d'avoir ajouté un compteur:
-mesure 1 avec incrementation d'un compteur
-mesure 2 , incrementation du compteur
.....
-mesure 30, nombre de mesures atteint > calcul de la moyenne et RAZ du compteur

//Mesure de la distance

void measure_distance() {

  unsigned long currentMillis = millis();
  if (currentMillis - read_previousMillis >= interval) {
    read_previousMillis = currentMillis;
    measuredDist = sonar.ping_cm();
    readings += measuredDist;
    compteur = ++compteur; //comptage du nombre de lectures
  }
}


void measure_counter() {

  for (int i = 0; i < MeasurementsToAverage; ++i)
  {
    measure_distance();

  }
 
  if (compteur == MeasurementsToAverage ) {
    average = readings / MeasurementsToAverage;
    Serial.println(average);
    compteur = 0; //remise à zéro du compteur de lectures

           if (average != lastaverage) {
                String StrTsk1 = "TaskValueSet,1,1,";
                String StrAverage = StrTsk1 + average ;
                ESPserial.println(StrAverage); // mise a jour de la valeur sur l'ESP si changement
                delay(50);
                }
   
    lastaverage = average;
    readings = 0; //remise à zero du total des mesures après moyenne
  }



}

Concernant cette partie:

    unsigned long currentMillis = millis();
    if (currentMillis - fill_previousMillis >= cal_interval) {
      // save the last time you checked water level
      fill_previousMillis = currentMillis;
    }

Elle me semble être un oubli, je devais avoir une idée de quelque chose que j'ai oublié en cours.

    compteur = ++compteur; //comptage du nombre de lectures

on fait juste compteur++;pour faire une incrémentation.

Sans la loop() difficile de dire ce qu'il se passe.

A mon avis vous devriez vous simplifier la vie. Faites une fonction measure_distance() qui retourne la distance mesurée et qui cache le calcul de la moyenne.

En premier appel vous n'avez pas assez d'éléments donc il faudra retourner la mesure brute mais ensuite attendez d'avoir accumulé le bon nombre d'échantillons pour renvoyer une nouvelle valeur.

ça pourrait ressembler à cela (tapé ici donc peut-être des fautes de frappe)

unsigned int measure_distance(bool forceUpdate = false)
{
  // les paramètres de mesure
  const unsigned long distancePeriod = 1000; // 1 fois par seconde
  const byte nombreEchantillons = 30;

  // les éléments qui sont mémorisés entre appels de fonction (static)
  static unsigned long chrono = 0;
  static unsigned int niveauEnCours = 0;
  static unsigned long cumulMoyenne = 0;
  static byte compteurMoyenne = 0;

  // les variables locales temporaires
  unsigned int distanceCM = 0;
  unsigned long maintenant = millis();

  // si on nous demande une valeur tout de suite ou qu'il est temps de prendre un nouvelle mesure
  if (forceUpdate || (maintenant - chrono >= distancePeriod)) {
    distanceCM = sonar.ping_cm(); // on effectue la mesure
    cumulMoyenne += distanceCM;   // on met à jour le cumul
    chrono = maintenant;          // on se souvient du moment de la dernière mesure
    compteurMoyenne++;            // on met à jour comptage du nombre de lectures
    if (compteurMoyenne >= nombreEchantillons) { // si on a le bon nommbre d'échantillons
      distanceCM = cumulMoyenne / nombreEchantillons; // on met à jour la distance
      niveauEnCours = distanceCM;
      compteurMoyenne = 0;
      cumulMoyenne = 0;
    }
    if (forceUpdate) niveauEnCours = distanceCM;
    return niveauEnCours;
  }
}

unsigned int niveauEauBassin = 0;

void setup()
{
  ...
  niveauEauBassin = measure_distance(true); // on force une première lecture
  ...
}

void loop()
{
  niveauEauBassin = measure_distance(); // on met à jour le niveau au fil du temps
  ...
}

Notez que dans ce cas on n'a pas une moyenne glissante puisqu'on ne mémorise pas les échantillons. à mon avis ce n'est pas grave car sur 30 secondes le niveau de votre bassin ne devrait pas varier significativement.

Une autre approche consisterait à toujours retourner une nouvelle valeur mais en faisant un filtre qui va lisser un peu les variations : la valeur que vous retournez (à part au premier appel) serait par exemple 90% de l'ancienne valeur plus 10% de la nouvelle.

vous pouvez bien sûr faire varier le rapport 90/10 en fonction de combien d'importance vous souhaitez donner au 'poids de l'historique' ==> Si vous prenez un rapport 95/5 vous dites que la nouvelle valeur ne compte que pour 5% de la valeur mesurée, si vous prenez 30/70 ce sera 70% par exemple.

Dans votre cas vous avez un phénomène à variation assez lente (évaporation, arrosage je suppose) donc un 90/10 serait sans doute pas mal.