Media "complessa" con arduino

Salve a tutti,
con un BH1750 vorrei leggere la luce ambientale in lux per circa 8 ore tutti i giorni cosi’ da percepire se la giornata è soleggiata, nuvolosa ecc…
Definisco quindi se la giornata è in pieno sole o meno grazie alla media dei dati raccolti con il sensore.

Eseguendo questo sketch:

#include <Wire.h>
#include <BH1750.h>
BH1750 lightMeter(0x23);
long t1 = 0;
int pausa = 120, conteggioMedia;
float lux, mediaLux;
void setup()
{
  Wire.begin();
  t1 = millis();
}

void loop()
{
  long differenza = millis() - t1;
  if (differenza > pausa)
  {
    lux += lightMeter.readLightLevel();
    conteggioMedia++;
    t1 = millis();
  }
  else if (conteggioMedia == 1000)
  {
    mediaLux = lux / 1000;
    conteggioMedia = 0;
  }
}

ho il problema che ogni 2 minuti (120ms*1000 che è il conteggioMedia) si sovrascrive la media. Ma io non voglio sovrascriverla, vorrei che ogni 2 minuti per circa 8 ore si aggiungono dati per ottenere una media sempre più dettagliata. Penso e ci ripenso, ma non arrivo al dunque :confused:

Prima sarebbe utile sapere cosa usi come scheda arduino.
Secondo, i dati vengono visualizzati in quale maniera?

Ciao savoriano,
Utilizzo un Arduino uno e sono visualizzati in seriale via bluetooth. Lo sketch citato è un piccolo blocco dell'intero programma, ma superficiale e che svia nella comprensione del problema, per questo non ho citato tutto :slight_smile:

seriale via bluetooth

e chi c'è dietro il bluetooth?
Cerca di fare un riassunto di come funziona il tuo progetto per evitare messaggi come questo. :wink:

Lo sketch, una volta giunti alla risoluzione sarà inglobato in un programma che gestisce un impianto di irrigazione giá operativo.
Arduino è in grado di rilevare l'alba e il tramonto tramite una libreria, questo è importante per poter definire da quando puó iniziare a gestire l'irrigazione.
La programmazione dei dati critici è affidata all'HC-05 e tramite l'avr/eeproom si salvano i dati salienti. Quindi Arduino ha anche un RTC che è un DS1307, ha dei relè per comandare più elettrovalvole e non manca il sensore di pioggia il quale è in grado, grazie a dei dischetti igroscopici al suo interno, di continuare a staccare il circuito finché non sarà ultimata l'evaporazione all'interno del sensore.
In questo momento il programma non è in grado di capire se il cielo è nuvoloso o meno, e se imposto un irrigazione di 2 minuti al giorno, la fa indipendentemente se è nuvoloso o meno il che non va bene.
Volevo implementare ulteriormente "l'intelligenza" sulla gestione idrica pensando al BH1750, perché ho pensato che facendo la media della luminosità giornaliera, che inizierà il campiomamento due ore dopo l'alba e finirà due ore prima del tramonto (già questo è in grado di farlo, il problema è come campionare), sia quindi in grado di rilevare se una giornata è nuvolosa, in base poi alla taratura si definirá quanta percentuale diminuire al tempo di accensione.
Esempio:
Ho impostato un tempo di accensione pari a 2 minuti, se è molto nuvoloso, riduci del 70%. Tutto questo non perché soffro di chissà quale mania, ma perché in estate ho mancanza di acqua e dovrei gestirla al meglio :slight_smile:

Tu irrighi la sera dopo il tramonto?
Quindi ti serve sapere la media di luminosità della giornata intera e non il dato di luminosità di ogni singola lettura.
Non è sufficiente sommare le letture per poi dividerle per il numero di letture?
Avrai cosi una media di luminosità giornaliera.
Con la pratica vedrai il valore medio in una giornata nuvolosa, di una giornata completamente soleggiata et da questi dati puoi gestire la tua irrigazione tenendo conto anche dei dati provenienti dagli altri sensori.
E' un bel progetto.

mi spiace (ci spiace, in effetti) dover dire all'autore che non è sulla strada corretta

lui sta misurando la luminosità ogni 120 millisecondi (circa 8 volte al secondo, quindi)
che fanno 480 volte al minuto
che fanno grosso modo trentamila letture l'ora
per circa 8 ore fanno mezzo milione di letture intere

siccome per fare la media serve di conservare le letture E il numero delle stesse ben si vede che la memoria di arduino non basta

noi (per l'esattezza Fabio) obbietteremmo che basta conservare la SOMMA delle letture e il loro numero,
ma la questione non cambia, dato che la somma di mezzomilione di interi per essere conservata richiede un tipo di dato che arduino non possiede (sì, forse la possiede: LONG LONG INT, ma tanto non è stampabile)

a questo punto si potrebbe obbiettare (e qui parlo io) che basterebbe eseguire subito la divisione per il numero di letture
anche qui la questione non cambia, mi spiace dirlo ma è così
fare subito la divisione e conservare la somma dei valori già ponderati, richiede di conoscere fin dal principio il numero dei campioni

siccome le letture vengono eseguite tra due ore dopo l'alba e due ore prima del tramonto il numero di letture non è conosciuto fino alla fine della giornata (e delle letture stesse)

quindi di nuovo questa strada non è praticabile,
senza considerare che dividere fin da subito la lettura per il numero di campiono riduce drasticamente la risoluzione della lettura stessa, nutriamo forti dubbi che i conti poi abbiano un qualche senso

quindi di nuovo:
questa strada è impraticabile

serve ripensare bene lo "spazio" delle misure: condizioni, durata, limiti

savoriano:
Tu irrighi la sera dopo il tramonto?

no, irrigo immediatamente all'aba e poco prima del tramonto

savoriano:
Non è sufficiente sommare le letture per poi dividerle per il numero di letture?
Avrai cosi una media di luminosità giornaliera.

cosi lo sto già facendo,
con

lux += lightMeter.readLightLevel();

interrogo il sensore ogni 120ms con millis e il dato lo sommo alla varabile lux,poi dopo che ci sono state 1000 somme (e quindi sono passatti 120ms * 1000 = 2min) lo divido per 1000 cosi da avere la media. Il problema è che cosi facendo ogni due minuti arduino si "dimentica" la copertura del cielo di due o più minuti fa...

@Salvorhardin
Grazie anche a te per avermi risposto, io ho impostato che ogni 120ms interroga il sensore e somma alla variabile lux. Ho scelto 120ms perchè è il tempo MINIMO seguendo il datasheet del BH1750 tempo di lettura affidabile, in effetti è anche esagerato leggere 8 volte al secondo, basterebbe tranquillamente 1 volta ogni 2 secondi, il che vorrà dire:
30 letture al minuto
1800 letture l'ora
14400 letture al giorno (con durata campionamento di 8 ore).

cosi riduciamo drasticamente la capienza.
quel sensore può misurare massimo 65535lux e quindi, nella "peggiore" delle ipotesi, se 14400 letture darebbero una lettura di massima intensità (cosa impossibile per 8 ore continui, ovviamente) avremmo un dato di 14400*65535 = 943,7 milioni
un long cosi va benissimo..

corretto il ragionamento?

millis() richiede "unsigned long" e non "long" (che è signed)

Ok, ho capito il problema
Una cosa cosi’?

#include <Wire.h>
#include <BH1750.h>
BH1750 lightMeter(0x23);
uint32_t t1 = 0;
uint16_t conteMedie = 1, mediaGenerale = 0;
int pausa = 120;
float lux, mediaLux;
void setup()
{
  Wire.begin();
  t1 = millis();
}

void loop()
{
  long differenza = millis() - t1;
  if (differenza > pausa)
  {
    lux += lightMeter.readLightLevel();
    conteMedie++;
    t1 = millis();
  }
  else if (conteMedie == 1000)
  {
    mediaLux = lux / 1000;
    if (!mediaGenerale){ //forse si puo fare differamente!
      mediaGenerale = mediaLux;
    } else {
      conteMedie++;
      mediaGenerale = mediaLux + (mediaGenerale * (conteMedie - 1)) / conteMedie;
    }
  }
}

come dice nid69ita tutte le variabili associate a millis() devono essere "unsigned long" e non "long".

su consiglio di nid e ORSO ho modificato la variabile in unsigned long.
copio quindi lo sketch di savoriano risolvendo piccolissimi problemi di compilazione, ma non ho capito il programma e purtroppo non posso in questo momento testarlo non avendo un sensore BH1750 sotto mano…

se dovesse essere corretto lo sketch, si dovrebbe azzerare la variabile conteggioMedia

#include <Wire.h>
#include <BH1750.h>
BH1750 lightMeter(0x23);
unsigned long t1 = 0;
int conteMedie = 1, mediaGenerale = 0;
int pausa = 600, conteggioMedia;
float lux, mediaLux;
void setup()
{
  Wire.begin();
  t1 = millis();
}

void loop()
{
  unsigned long differenza = millis() - t1;
  if (differenza > pausa)
  {
    lux += lightMeter.readLightLevel();
    conteggioMedia++;
    t1 = millis();
  }

  else if (conteggioMedia == 1000)
  {
    mediaLux = lux / 1000;
    if (!mediaGenerale)
    { //forse si puo fare differamente!
      mediaGenerale = mediaLux;
    }

    else
    {
      conteMedie++;
      mediaGenerale = (mediaGenerale + (mediaLux * (conteMedie - 1)) / conteMedie);
    }
  }
}

Ho modificato la formula perché era sbagliata (vedi il codice messaggio#9)
Ho corretto anche gli errori di compilazione (credo tutti)!

if (!mediaGenerale){
      mediaGenerale = medialux;

Questo perché avevo un problema la prima volta che fa la media
Quindi per il primo passaggio se medialux = 1000 mediaGenerale = 1000 (l'alba)

2 passassioggio medialux= 1200 conteMedia = 2 (il sole si alza)
mediagenerale = (1200 + (1000 * 1)) / 2 = 1100

3 passaggio medialux = 1300 conteeMedia = 3 (il sole si alza ancora)
mediagenerale = (1300 + (1100 * 2)) / 3 = 1166

4 passaggio medialux = 1100 conteeMedia = 4 (un nuvolone è passato)
mediagenerale = (1100 + (1166 * 3)) / 4 = 1149

etc. etc.

Ho avuto modo di caricare il programma aggiungendo soltanto due cose:

  • la ricezione in seriale di un comando che mi simulasse il periodo per poter campionare e non
  • l’azzeramento della variabile conteMedie

il programma è quindi il seguente:

#include <Wire.h>
#include <BH1750.h>
BH1750 lightMeter(0x23);
uint32_t t1 = 0;
uint16_t conteMedie = 1, mediaGenerale = 0;
int pausa = 600, ciclo;
float lux, mediaLux;
int semaforo;
String comando;
void setup()
{
  Serial.begin(9600);
  Wire.begin();
  if (lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE_2)) {
    Serial.println(F("BH1750 Advanced begin"));
  }
  else {
    Serial.println(F("Error initialising BH1750"));
  }
  t1 = millis();
}
void loop()
{
  unsigned long differenza = millis() - t1;
  //FARE MEDIA BH1750 DALLE 2ore DOPO ALBA FINO A 2ore PRIMA TRAMONTO
  if (semaforo == 1) //siamo dentro tale periodo
  {
    unsigned long differenza = millis() - t1;
    if (differenza > pausa)
    {
      lux += lightMeter.readLightLevel();
      conteMedie++;
      //  Serial.print("conteMedie: ");
      //   Serial.println(conteMedie);
      t1 = millis();
    }
    else if (conteMedie == 30)
    {
      mediaLux = lux / 30;
      Serial.print("mediaLux: ");
      Serial.println(mediaLux);
      if (!mediaGenerale) //con questo blocco, si esegue l'if solo una volta
      { //forse si puo fare differamente!
        mediaGenerale = mediaLux;
      } else
      {
        conteMedie++;
        ciclo++;
        Serial.print("Ciclo n* ");
        Serial.print(ciclo);
        Serial.println("");
        Serial.println("DATI PRIMA DELLA FORMULA: ");
        Serial.print("conteMedie: ");
        Serial.println(conteMedie);
        Serial.print("mediaGenerale: ");
        Serial.println(mediaGenerale);
        Serial.println("");
        mediaGenerale = mediaLux + (mediaGenerale * (conteMedie - 1)) / conteMedie;
        Serial.println("DOPO LA FORMULA: ");
        Serial.print("mediaGenerale: ");
        Serial.println(mediaGenerale);
        Serial.println("-----------------");
        conteMedie = 0;
      }
    }
  }
  else //non è il periodo di calcolare la media
  {
    Serial.println("FUORI ALBA");
    Serial.println("");
    conteMedie = 1;
    mediaGenerale = 0;
  }
  if (Serial.available() > 0)
  {
    comando = Serial.readString();
    comando.trim();
  }
  if (comando.indexOf("semaforo") > -1)
  {
    comando.remove(0, 8);
    comando.trim();
    unsigned long b = comando.toInt();
    semaforo = b;
    Serial.print(F("Semaforo: "));
    Serial.print(semaforo);
    Serial.println("");
  }
}

e nella seriale mi viene stampato:

Semaforo: 1
mediaLux: 10.33
mediaLux: 10.33
Ciclo n* 1
DATI PRIMA DELLA FORMULA: 
conteMedie: 31
mediaGenerale: 10

DOPO LA FORMULA: 
mediaGenerale: 19
-----------------
mediaLux: 21.03
Ciclo n* 2
DATI PRIMA DELLA FORMULA: 
conteMedie: 31
mediaGenerale: 19

DOPO LA FORMULA: 
mediaGenerale: 39
-----------------
mediaLux: 31.68
Ciclo n* 3
DATI PRIMA DELLA FORMULA: 
conteMedie: 31
mediaGenerale: 39

DOPO LA FORMULA: 
mediaGenerale: 68
-----------------
mediaLux: 42.33
Ciclo n* 4
DATI PRIMA DELLA FORMULA: 
conteMedie: 31
mediaGenerale: 68

DOPO LA FORMULA: 
mediaGenerale: 107
------------------

etc...

la variabile mediaGenerale non fa altro che aumentare e il BH1750 è stato fermo senza alcuna interferenza di luce.
c’è un problema nella formula o non so…più ci sbatto e più non capisco >:(

EDIT:
modificando la formula in:

mediaGenerale = (mediaLux + (mediaGenerale * (ciclo -1))) / ciclo;

e modificando

      if (!mediaGenerale) //con questo blocco, si esegue l'if solo una volta
      { //forse si puo fare differamente!
        mediaGenerale = mediaLux;
        ciclo++;
      }

sembra funzionare la formula anche se non capisco perchè ad un mediaLux iniziale di 10.07 poi nel secondo loop diventa 21.03, nel terzo loop 31.68 etc…
sfalsando quindi anche la variabile mediaGenerale. Credo che risolvendo questo di conseguenza possa funzionare anche la formula

Ma se leggi 2 volte al minuto pensi di perdere qualcosa? :slight_smile: Considera che, anche se ci sono nuvole che corrono, le tue letture non sono sincronizzate con il loro passaggio, quindi alla fine otterrai un valore medio.

Datman:
Ma se leggi 2 volte al minuto pensi di perdere qualcosa? :slight_smile: Considera che, anche se ci sono nuvole che corrono, le tue letture non sono sincronizzate con il loro passaggio, quindi alla fine otterrai un valore medio.

no, nemmeno :smiley:
però il punto adesso non è quanto essere preciso…non capisco perchè ad ogni loop aumenta del doppio iniziale la variabile mediaLux

doppio iniziale la variabile mediaLux

dove azzeri la variabile lux? non vedo

Azzerando lux sembra funzionare tutto per il meglio, vedremo domani fuori. Anche se è previsto temporale tutto il giorno, occasione per tarare alla peggio condizione :smiley:

Buongiorno ragazzi,
sto pensando che il BH170, che è collegato tramite bus i2c ad arduino, lo dovrò mettere ad una distanza di circa 4-5 metri. Ho quindi cercato un extender i2c ma trovo solo il chip singolo P82B715 e il post di Etemenanki per la realizzazione della scheda che vorrei utilizzare come ultima spiaggia solo per il momento, vorrei prenderne una bella e fatta. Trovo solo QUESTI ma hannno un tempo di spedizione di due mesi...

Usa un altro Arduino per convertire da I2C a RS232...