Irrigazione automatica

Salve a tutti. E' da un po che copio codici e progetti online e mi scoraggia molto non riuscire a creare da me piccoli progetti come per esempio quest'ultimo che ho in mente, cioè un irrigazione automatica.
Mi rendo conto che ci sono un sacco di progetti online e vi assicuro che li ho visionati quasi tutti ed ho provato a capirci qualcosa ma ognuno di essi è specifico per un certo utilizzo e quando provo a modificarlo per le mie esigenze mi perdo nel codice e non ci capisco nulla. Vi prego dunque di aiutarmi a capire cosa non capisco... grazie.
Vi espongo il mio progetto dunque:

Materiali:

  • Arduino uno (o anche mega)
  • TinyRTC
  • Relay a 4 stadi
  • Elettrovalvola (non ancora comprata)
  • Pompa (non ancora comprata)
  • Sonda troppopieno
  • Driver Pololu per movimentare una pompa peristaltica (forse l'utilizzo di un motore stepper è esagerato)

Tralasciando il fatto che un domani vorrei aggiungere funzioni di rilevamento umidità, temperatura e pioggia, ma per ora preferisco non complicarmi la vita...

In pratica, con questo irrigatore vorrei evitare di annaffiare le piante con l'acqua di rete per evitare che il cloro danneggi i microorganismi presenti nella terra. Quindi ho previsto un bidone che verrà riempito alla fine di ogni annaffiatura e che contiene la pompa che porterà l'acqua alle piante.

In poche parole la logica sarebbe questa: dalle ore hh:mm alle ore hh:mm attivo la pompa (relay1) e innaffio dal bidone. Subito dopo aver innaffiato si apre l'elettrovalvola e il bidone viene riempito nuovamente. Un rilevatore di troppopieno arresterà l'elettrovalvola a bidone riempito.

Per complicarmi la vita vorrei inserire una pompa peristaltica che una volta al mese prima di innaffiare andrà ad aggiungere all'acqua un po concime liquido e lo mescolerà con un agitatore magnetico ricavato da una ventola. Tutto quì, semplice no? :smiley:

Ora, il mio handicap:

/**********************************************************
 * Prova irrigazione 
/**********************************************************/

#include <Wire.h>
#include <RTClib.h>
   
#define pinPompa 5
   
int Pompa[] = {13,29,13,30};
   
RTC_DS1307 RTC;
   
void setup() {
  Serial.begin(9600);
  Serial.println( "START" );
   
  Wire.begin();
  RTC.begin();
  
/**********************************************************/
// RTC.adjust(DateTime(2019, 05, 23, 12, 53, 00));
  
/* togliere il commento dalla riga sopra quando si vuole impostare l'ora
nel modulo rtc, inserire data e ora attuale, formato: (yyyy,mm,dd,hh,mm,ss).
Una volta impostata l'ora ricommentare la riga e ricaricare lo sketch.  */
/**********************************************************/
  
   
   pinMode( pinPompa,OUTPUT );

   
  if (! RTC.isrunning()) {
    Serial.println("RTC is NOT running!");
    RTC.adjust(DateTime(__DATE__, __TIME__));
  }
   
   digitalWrite( pinPompa,LOW );

 }
   
void loop() {
  if ( RTC.isrunning()) {
    DateTime now = RTC.now();
   
    Serial.print(now.year(), DEC);
    Serial.print('/');
    Serial.print(now.month(), DEC);
    Serial.print('/');
    Serial.print(now.day(), DEC);
    Serial.print(' ');
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.print(':');
    Serial.print(now.second(), DEC);
    Serial.println();
   
    int _hour   = now.hour();
    int _minute = now.minute();
 
    // Avvio Pompa
    if ( Pompa[0] <= _hour && _hour <= Pompa[2] && Pompa[1] <= _minute && _minute <= Pompa[3] ) {
      digitalWrite( pinPompa,HIGH );
    } else { digitalWrite( pinPompa,LOW ); }
    }
}

Ovviamente ho scopiazzato qua e la e tra l'altro non funziona neanche bene perchè funziona al contrario... in pratica dovrebbe accendersi alle 13,29 e spegnersi alle 13,30 mentre in questo lasso di tempo si spegne. Non a caso non sono riuscito a capire la variabile che si occupa dell'accensione:

  if ( Pompa[0] <= _hour && _hour <= Pompa[2] && Pompa[1] <= _minute && _minute <= Pompa[3]

Ne uscirò?

Per forza di cose visto che non la capivo ho dovuto sostituire l'ultima variabile con questo

/**********************************************************
 * Prova irrigazione 
/**********************************************************/

#include <Wire.h>
#include <RTClib.h>
   
#define pinPompa 5
   
   
RTC_DS1307 RTC;
   
void setup() {
  Serial.begin(9600);
  Serial.println( "START" );
   
  Wire.begin();
  RTC.begin();
  
/**********************************************************/
// RTC.adjust(DateTime(2019, 05, 23, 12, 53, 00));
  
/* togliere il commento dalla riga sopra quando si vuole impostare l'ora
nel modulo rtc, inserire data e ora attuale, formato: (yyyy,mm,dd,hh,mm,ss).
Una volta impostata l'ora ricommentare la riga e ricaricare lo sketch.  */
/**********************************************************/
   
   pinMode( pinPompa,OUTPUT );
   
  if (! RTC.isrunning()) {
    Serial.println("RTC is NOT running!");
    RTC.adjust(DateTime(__DATE__, __TIME__));
  }
   
   digitalWrite( pinPompa,HIGH );
   
 }
   
void loop() {
  if ( RTC.isrunning()) {
    DateTime now = RTC.now();
   
    Serial.print(now.year(), DEC);
    Serial.print('/');
    Serial.print(now.month(), DEC);
    Serial.print('/');
    Serial.print(now.day(), DEC);
    Serial.print(' ');
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.print(':');
    Serial.print(now.second(), DEC);
    Serial.println();

    if( now.hour()==15 && now.minute()==36) {
      digitalWrite( pinPompa,LOW );
    } else { digitalWrite( pinPompa,HIGH ); }
    }
}

Ed ho dovuto invertire i pin HIGH con LOW altrimenti funzionava al contrario (probabilmente perchè il relay che uso è normalmente chiuso?). Ora mi rimane di capire se posso impostare una durata in secondi dall'avvio del relay

karnhack:
Salve a tutti. E' da un po che copio codici e progetti online e mi scoraggia molto non riuscire a creare da me piccoli progetti come per esempio quest'ultimo che ho in mente, cioè un irrigazione automatica.

Bisogna vedere se è una questione di impostazione logica/pianificazione o di poca conoscenza della semantica e della sintassi del linguaggio di programmazione, o del mettere insieme le due cose.

quando provo a modificarlo per le mie esigenze mi perdo nel codice e non ci capisco nulla.

Il codice è la trascrizione per una macchina di un'idea o modello mentale. Modificare il codice senza aver capito l'idea di chi lo ha pensato raramente porta a qualcosa di funzionante.

Vi prego dunque di aiutarmi a capire cosa non capisco

Ecco una sfida interessante...

In pratica, con questo irrigatore vorrei...

Qui hai descritto il dominio del problema in senso astratto, bene, è chiaro.

In poche parole la logica sarebbe questa: dalle ore hh:mm alle ore hh:mm attivo la pompa (relay1) e innaffio dal bidone. Subito dopo aver innaffiato si apre l'elettrovalvola e il bidone viene riempito nuovamente. Un rilevatore di troppopieno arresterà l'elettrovalvola a bidone riempito.
...vorrei inserire una pompa peristaltica che una volta al mese prima di innaffiare andrà ad aggiungere all'acqua un po concime liquido e lo mescolerà con un agitatore magnetico ricavato da una ventola.

Qui hai descritto la logica in modo più schematico, benissimo, è già chiara, il modello mentale di cui parlavo parte da qui. Perché si tratta adesso di trovare un modo per "descriverla" usando gli strumenti messi a disposizione dalla macchina (ingressi/uscite hardware, istruzioni usabili ecc)... e non copiando o adattando cose scritte da altri che pensavano a logiche diverse (quello che hanno scritto gli altri va bene per vedere come hanno affrontato un singolo sottoproblema). In particolare è importante riuscire a ragionare per fasi di funzionamento, in tutto quello che hai descritto ne vedo almeno quattro o cinque ben precise... ci si può anche dare un nome, anzi, meglio farlo il prima possibile. Quando avrai identificato le fasi puoi chiederti: "in questa fase a quale evento devo prestare attenzione?", e successivamente: "quando avviene quell'evento quali azioni devo compiere? Ed eventualmente, se è il caso, in quale nuova fase di funzionamento mi dovrò trovare?". Prova a buttare giù uno schema su carta di fasi, eventi, azioni, prima in italiano, poi si vede come tradurlo in arduinese.

Ovviamente ho scopiazzato qua e la e tra l'altro non funziona neanche bene perchè funziona al contrario... in pratica dovrebbe accendersi alle 13,29 e spegnersi alle 13,30 mentre in questo lasso di tempo si spegne. Non a caso non sono riuscito a capire la variabile che si occupa dell'accensione:

  if ( Pompa[0] <= _hour && _hour <= Pompa[2] && Pompa[1] <= _minute && _minute <= Pompa[3]

Non funziona al contrario, porta alta l'uscita nell'intervallo previsto (purché non sia a cavallo tra due ore altrimenti può sbagliare), ma il modulo relè funziona a logica negativa, cioè il relé si attiva quando l'uscita di Arduino (ingresso del modulo) è bassa. Come hai già visto è bastato invertire HIGH con LOW.

Per il resto quale parte non è chiara nell'espressione logica che hai quotato?

Ne uscirò?

Mah :slight_smile:

Per quanto riguarda il controllo dei periodi orari, meglio lavorare con i minuti, in questo modo basta evitare un periodo a cavallo della mezzanotte:

int adesso = now.hour()*60 + now.minute();  // tempi da 0 a 1439 minuti
int inizio = Pompa[0]*60 + Pompa[1];
int fine   = Pompa[2]*60 + Pompa[3];

// Comando Pompa, no orari a cavallo della mezzanotte!!!
if ((adesso >= inizio) && (adesso < fine)) 
{
        digitalWrite(PINPOMPA, RELEACCESO);
} 
else 
{ 
        digitalWrite(PINPOMPA, RELESPENTO); 
}

Ottimo il suggerimento di @Claudio_FF che dice di scriverlo prima in italiano.
Aggiungo un suggerimento ulteriore, pensalo per essere interpretato da uno che ti rema contro e che alla minima frase ambigua sceglierà la strada opposta a quella che pensi tu.
Quindi cerca di essere il più preciso possibile e il più chiaro possibile.
A costo di essere super elementare. Ad es. se dici di scrivere una cosa assicurati prima che ci siano il foglio e la penna e che la penna scriva, non so se hai capito cosa intendo...
Se riesci a fare questo, poi si tratterà solo di tradurre il tutto in C++ e puoi farlo un pezzo per volta chiedendo e imparando.
Pensa semplice e vedrai che ne uscirai.

Ragazzi, innanzitutto grazie perché questo è proprio il tipo di aiuto che cercavo. Seguo con interesse i vostri consigli e stasera cercherò di fare uno schema. Per ora vi ringrazio per l'incoraggiamento.

Ho provato a fare uno schema di funzionamento... mentre ragionavo sulla fase 4 mi è venuta in mente la firma di @Claudio_FF "if non è un ciclo" infatti non so se è corretto come ho fatto io. Che ne dite può andare lo schema logico?

'if' non è un ciclo perché è una decisione/ramificazione. I cicli sono 'while' 'do while' e 'for'. È che spesso scrivono qualcosa come "non mi funziona il ciclo if" :slight_smile: Ma naturalmente non vuol dire che non si possa usare un 'if' in modo ciclico per valutare ripetutamente delle condizioni, anzi, ma la ripetizione è appunto creata con un' istruzione di ciclo come 'while' o 'for' e non con l' 'if' in se stesso.

Detto ciò, direi che la scomposizione in fasi è grosso modo la stessa che avevo pensato. Hai disegnato una via di mezzo tra un diagramma di flusso e un diagramma funzionale sequenziale. Questo è come lo avevo pensato (avevo scomposto la concimazione in due fasi diverse):

karnhackfasi.png

Nel tuo diagramma di differente c'è la fase 'input' che non mi è chiara, mentre io davo per scontato un funzionamento ciclico automatico, magari iniziante con la fase riempimento.

Le condizioni di passaggio da una fase all'altra (eventi) che ho scritto in rosso corrispondono ai rombi decisionali (if) del tuo schema. Vuol dire che l'intera sequenza logica si potrebbe gestire con soli sei 'if' principali, ma dipende dalla struttura/codifica che si vuole implementare.

1) Se il programma deve fare solo questo e non anche altre cose "in parallelo", allora può bastare dettagliare meglio ogni singolo blocco sotto forma di diagramma di flusso completo. Se il diagramma prevede solo blocchi di istruzioni sequenziali, ramificazioni 'if' e ripetizioni 'while'/'for' la traduzione in C è in pratica uno a uno, e restano solo i dettagli sintattici. Le varie durate possono essere realizzate con la funzione 'delay'.

2) Se invece si vuole scrivere un programma più "generale", che potrebbe nel frattempo fare anche altre cose, allora servono una struttura e ragionamento diversi, più affini al concetto di ciclo di scansione di un PLC (le operazioni vengono portate avanti in cicli/passaggi successivi e i ritardi sono realizzati con la permanenza in una fase per un certo numero di cicli fino al riconoscimento di un evento).

La prima via sembra più semplice da affrontare perché la sequenza delle istruzioni corrisponde esattamente al pensiero: faccio questo, poi questo e poi quest'altro. La seconda invece è un po' più tecnica e interessante, perché tante cose si riescono a fare solo con questa, ed è più facilmente modificabile per aggiungere o togliere funzionalità senza stravolgere o ripensare tutto.

Comunque direi che la logica c'è, adesso va dettagliata e tradotta in macchinese...

karnhackfasi.png

Claudio_FF:
Nel tuo diagramma di differente c'è la fase 'input' che non mi è chiara, mentre io davo per scontato un funzionamento ciclico automatico, magari iniziante con la fase riempimento.

E' che non sapevo da dove iniziare quindi ho pensato di partire dall'inserimento dei dati.
Pensando meglio al progetto mi sono accorto che sarebbe opportuno tenere conto dei mesi non solo per quanto riguarda la concimazione ma anche per avere diversi orari di innaffiatura in base al mese. Per esempio gestendo i "tempi" di innaffiatura mese per mese durante le 4 stagioni (climaticamente parlando), d'estate potrei annaffiare di sera dopo una giornata di sole cocente, mentre d'inverno potrei annaffiare la mattina per evitare gelate notturne.

Mi piace che sei partito dal riempimento, ci avevo pensato ma pensavo di complicarmi le cose, invece dal tuo schema sembra semplice e logico fare così.

karnhack:
E' che non sapevo da dove iniziare quindi ho pensato di partire dall'inserimento dei dati.

Che però è una macrofunzione decisamente complessa. Alla fine dovrebbe popolare una matrice di dati mese->ora, che per semplicità si può anche già scrivere a mano nel programma, poi a complicare c'è sempre tempo.

Piuttosto vedrei utili altre funzionalità manuali... che so, scarico del bidone senza riempimento successivo, ciclo di annaffiatura (e/o concimazione) manuale, interruzione dell'operazione in corso... e altro a fantasia. Basta aggiungere fasi ed eventi (e pulsanti :D). Si può anche aggiungere una segnalazione di allarme se il rabbocco va in timeout (guasto al sensore di pieno o mancanza di acqua) ecc.

Come idea, se si indica la fase attualmente attiva con un valore contenuto in una variabile (ad esempio di nome... uhmm... 'fase'), nel loop ad ogni giro si può semplicemente chiamare una funzioncina diversa per ogni fase. Successivamente aggiungere o togliere fasi è immediato e non bisogna stravolgere nessun lungo codice.

Claudio_FF:
Che però è una macrofunzione decisamente complessa. Alla fine dovrebbe popolare una matrice di dati mese->ora, che per semplicità si può anche già scrivere a mano nel programma, poi a complicare c'è sempre tempo.

Sono assolutamente d'accordo a semplificare la cosa, anche perchè altrimenti rischierei di avere solo gli schemi in mano e senza una riga di codice. Anche per quanto riguarda pulsanti, lcd ed altro per ora eviterei per lo stesso motivo che ho citato sopra.

Claudio_FF:
Si può anche aggiungere una segnalazione di allarme se il rabbocco va in timeout (guasto al sensore di pieno o mancanza di acqua) ecc.

Potrei anche utilizzare una valvola galleggiante (meccanica, tipo quella del wc) sopra al sensore, così nel caso di guasto del sensore comunque non strasborda.

Ora però da dove comincio? mi riferisco all'arduinese...

Ora però da dove comincio? mi riferisco all'arduinese...

Hai iniziato bene quando ti sei concentrato a fare accendere o spegnere la pompa, penso che debba continuare da questi dettagli, uno preso in modo isolato.

Ora se sai già che la pompa si dovrà accendere e spegnere in base ad una serie di condizioni potresti pensare di creare una funzione dal nome gestisciPompa (waterPumpManage). All'interno della funzione farai i test con le if (), dove sono variabili globali.

void pumpManage() {

    if( now.hour()==15 && now.minute()==36) {

      digitalWrite( pinPompa,LOW );

    } else {

       digitalWrite( pinPompa,HIGH ); 
    }
}

Così intanto riduci la quantità di righe di codice all'interno del loop e sai dove andare a guadare quando si presenta un problema con la pompa.

Ciao.

O ecco, ero giù a prendere pizze... e poi prova te a spiegare a un'amica totalmente non tecnica che non guardi la TV perché passi il tempo libero su questo forum "inutile" :grin:

E torniamo a noio... si, l'idea è di scomporre in piccole funzioni (che alla fine possono corrispondere alle fasi).

Tra l'altro partendo dallo schema delle fasi viene particolarmente naturale/facile procedere top-down lasciando i dettagli alla fine (e l'intero programma può essere scritto quasi tutto in italiano).

Ora... "da dove comincio" dipende anche da cosa si sa.

Come detto prima ci sono vari modi per procedere. Quello focalizzato sulle fasi secondo me è il più generale e adattabile a ogni tipo di progetto, sia single task che multi task.

Ma occorrono delle basi sintattiche...

Io comincerei con lo scrivere le due funzioni obbligatorie vuote. Definirei una variabile globale 'fase' per indicare la fase di funzionamento attuale, e la inizializzerei al valore della prima fase che voglio attiva all'accensione (il valore associato ad ogni fase è naturalmente una convenzione, e se a questi valori diamo dei nomi, ad esempio con define, diventa tutto molto più chiaro).

Poi nel loop scriverei un selettore (if/else if o switch) che in base al valore di questa variabile richiami una funzione o un'altra, e quindi scriverei tutte queste funzioni vuote.

Così c'è già l'ossatura per tutta la logica.

Adesso dentro le funzioni vanno scritte le operazioni da svolgere e i test (controllo eventi) per vedere se è ora di cambiare fase. Per cambiare fase basta semplicemente scrivere nella variabile 'fase' il valore della nuova fase attiva e terminare la funzione tornando al selettore.

Tutto questo è arabo e torniamo indietro, o continuiamo?

Diamo per buono questo schema fasi?

karnhackfasi2.png

karnhackfasi2.png

Adesso dentro le funzioni vanno scritte le operazioni da svolgere e i test (controllo eventi) per vedere se è ora di cambiare fase. Per cambiare fase basta semplicemente scrivere nella variabile 'fase' il valore della nuova fase attiva e terminare la funzione tornando al selettore.

Tutto questo è arabo e torniamo indietro, o continuiamo?

Tradotto in codice,

#define PH_START 0
#define PH_RUN  1
#define NEXT_PHASE( ph ) ph = ph+1 
byte currentPhase = PH_START;

void helloPrint() {
    serial.println("Hello Word!");
    NEXT_PHASE(currentPhase);  // passa alla prossima fase
    // currentPhase = PH_RUN;      // fase scelta in modo arbitrario 
}

void setup() {
    serial.begin(57600);
}

void loop() {
    switch (currentPhase) {
    case PH_START:
        helloPrint();
        break;
    case PH_RUN:
        // rimane in questa fase fino a che non modifichiamo la il contenuto della variabile
        // currentPhase.
        break;
    }   // end switch currentPhase
}   //end loop

Si tratta solo di un esempio per fare capire cosa intende @claudio con define.

Ciao.

Innanzitutto ringrazio tutti e due per il tempo dedicato. Rispondendo a @ Claudio_FF non è arabo ma è Bergamasco (senza offesa ovviamente) :sweat_smile:
Da quello che capisco dall'esempio di @Maurotec si definiscono prima le fasi, nell'esempio PH_START, PH_RUN e NEXT_PHASE e poi con il void loop si avvia il ciclo che passa sequenzialmente da una fase all'altra finchè non viene fermato dalla stringa break;.
Poi ci sono tante piccole cosette che mi sfuggono per esempio

#define PH_START 0
#define PH_RUN  1
#define NEXT_PHASE( ph ) ph = ph+1 
byte currentPhase = PH_START; // byte (valore da 0 a 255) trasforma currentPhase in PH_START che ha valore 0 ????

void helloPrint() {
    serial.println("Hello Word!");
    NEXT_PHASE(currentPhase);  // qui mi perdo... NEXT_PHASE sarebbe PH_START che è 0 + 1 e dunque stiamo avviando la PH_RUN??
    // currentPhase = PH_RUN;      //invece qui contrariamente alla riga sopra non stiamo usando una logica variabile (passatemi il termine) ma stiamo semplicemente dicendo che la fase attuale è 1
}

void setup() {
    serial.begin(57600); 
}

void loop() {
    switch (currentPhase) { // qui si inizia il flusso partendo da currentPhase che è 0
    case PH_START:  // mi pare di capire che qui si descrivono i vari flussi:
        helloPrint(); // flusso 1 stampa Hello Word
        break;        //  blocca il flusso
    case PH_RUN:  // questo è Bergamasco... 
        // rimane in questa fase fino a che non modifichiamo la il contenuto della variabile
        // currentPhase.
        break;
    }   // end switch currentPhase
}   //end loop

Ho commentato le stringhe in modo da rendere visibile le lacune o meglio ancora le lagune nella quale sono immerso. Ne usciro? :slightly_smiling_face:

Cerco di tradurtelo un pelino:

#define PH_START 0 // PH_START verrà sostituito con 0 prima della compilazione
#define PH_RUN  1 // PH_RUN verrà sostituito con 1 prima della compilazione
#define NEXT_PHASE( ph ) ph = ph+1 // NEXT_PHASE(ph) verrà sostituito con l'istruzione ph = ph + 1 prima della compilazione;
// Questo:
byte currentPhase = PH_START; // byte (valore da 0 a 255) trasforma currentPhase in PH_START che ha valore 0 ????
//diventa
byte currentPhase = 0; // Che è il valore di partenza all'avvio o al reset di Arduino.

void helloPrint() {
    serial.println("Hello Word!");
    // questo:
    NEXT_PHASE(currentPhase);  // qui mi perdo... NEXT_PHASE sarebbe PH_START che è 0 + 1 e dunque stiamo avviando la PH_RUN??
    // diventa;
    currentPhase = currentPhase + 1;
    // Questo invece è un esempio se si vuole impostare un valore fisso e diventerebbe: currentPhase = 1;
    // currentPhase = PH_RUN;      //invece qui contrariamente alla riga sopra non stiamo usando una logica variabile (passatemi il termine) ma stiamo semplicemente dicendo che la fase attuale è 1
}

void setup() {
    serial.begin(57600);
}

void loop() {
// Lo switch è un test che analizza il valore currentPhase ed esegue il case con lo stesso valore e tutti i successivi fino a quando non trova il break;
    switch (currentPhase) { // qui si inizia il flusso partendo da currentPhase che è 0
    case 0:
        helloPrint(); // flusso 1 stampa Hello Word e aumenta di 1 currentPhase, quindi lo fa diventare uguale a 1 e al prossimo giro di loop verrà eseguito il case 1
        break;        //  blocca il flusso, più precisamente fa uscire dallo switch saltando i case successivi. Se non lo metti esegue anche il case 1
    case 1:
        // rimane in questa fase fino a che non modifichiamo la il contenuto della variabile:
        // currentPhase. esatto!
        break;
    }   // end switch currentPhase
}   //end loop

Ho chiarito un po' di più o aumentato solo la confusione?

Ok, allora ci sono innanzitutto delle grosse lagune con le basi del C :slight_smile:

Se lo switch non è chiaro, meglio prima vedere questo confronto con un if del tutto equivalente, è solo una struttura decisionale scritta in altro modo. Entrambi i codici eseguono solo 'istruzioni_a' o solo 'istruzioni_b' a seconda del valore della variabile 'x'. Se il valore della variabile non corrisponde a nessuno dei test precedenti vengono eseguite 'istruzioni_c'.

switch (x)
{
    case 20:
        ...istruzioni_a...
    break;
    
    case 30:
        ...istruzioni_b...
    break;

    default:
        ...istruzioni_c...
    break;
}
if (x == 20) 
{
    ...istruzioni_a...
}
else if (x == 30) 
{
    ...istruzioni_b...
}
else
{
    ...istruzioni_c...
}

Detto ciò con le define crei dei nomi di comodo/etichette, quindi scrivere un valore o il nome creato con la define è la stessa cosa, perché alla compilazione nei punti dove hai scritto i nomi questi verranno sostituiti con i valori. Quindi si:

byte currentPhase = PH_START;

è esattamente come scrivere:

byte currentPhase = 0;

Ma la prima forma è più chiara perché non serve andare in cerca di (o ricordarsi) cosa voglia dire lo zero. E questo è un consiglio sempre valido: evitare di cospargere il codice di numerini che non è chiaro cosa rappresentano, ad esempio la riga seguente è una scrittura su un pin di uscita, ma non abbiamo alcuna idea sulla funzione del pin, e neppure se vuol dire che quello che vi è collegato lo stiamo accendendo o spegnendo (ad esempio se è un relé in logica negativa lo stiamo spegnendo, ma senza commenti e schemi tocca tirare a indovinare):

digitalWrite(3, HIGH);

Invece la riga seguente è chiarissima, sappiamo che stiamo accendendo e anche cosa. È sufficiente scrivere all'inizio del codice le define coerenti con i collegamenti hardware e con i livelli HIGH o LOW presenti sui pin di ingresso o voluti sui pin di uscita:

digitalWrite(POMPA, LIVELLOACCESO);

NEXTPHASE è una macro, concetto più avanzato che personalmente non avrei introdotto in questo esempio, di fatto opera sempre con una sostituzione ed è come se scrivessi:

currentPhase = currentPhase + 1;
currentPhase = PH_RUN;   //invece qui contrariamente alla riga sopra
non stiamo usando una logica variabile (passatemi il termine) ma stiamo
semplicemente dicendo che la fase attuale è 1

Esatto. Tra l'altro i valori sono del tutto convenzionali, una fase può essere la 50 e quella successiva la 25, per cui l'incremento di 1 della fase ha senso solo nei casi specifici di una sequenza lineare, che so... le fasi notte alba giorno tramonto di un presepio... ma vedi ben che dopo il tramonto non basta incrementarla, va riportata al valore di notte.

E per concludere grazie, se non mi fossi fermato a rispondere a questo post adesso sarei in bicicletta sotto la pioggia :smiley: :smiley: :smiley:

@Claudio_FF devo dire che hai il dono della chiarezza, senza nulla togliere allo sforzo che gli altri stanno facendo per farmi capire.
Purtroppo le mie lacune sono aggravate dal fatto che non ho studiato la materia in giovane età e che non comprendo bene l'inglese, lingua con cui sono scritte la maggior parte delle guide che si trovano in giro.
Detto questo, vorrei provare a buttare giù un po di righe guardando gli esempi finora esposti, tralasciando per ora l'RTC e concentrandomi sulle funzioni delle cosiddette fasi... sempre che questo modo di procedere non mi incasini ancora di più le cose.

karnhack:
tralasciando per ora l'RTC e concentrandomi sulle funzioni delle cosiddette fasi... sempre che questo modo di procedere non mi incasini ancora di più le cose.

In un normale diagramma di flusso le fasi di funzionamento ci sarebbero comunque, solo che sarebbero "embeddate" e implicite nella la sequenza delle istruzioni invece di essere esplicite. Anche se non sembra in realtà con le fasi esplicite le cose si semplificano, ci si accorge di questo appena si vuole modificare qualcosa o aggiungere qualche funzionalità (soprattutto fare qualcosa in multitasking).

Per l'RTC... se scrivi una funzioncina 'leggi_rtc' fittizia che restituisce un orario fittizio a te comodo, il fatto di non usare l'RTC reale è ininfluente per il resto del programma. Poi basta modificare solo quella funzione per leggere l'RTC vero.

Ogni sforzo fatto per comprendere la struttura di questo progetto diventa strada spianata per qualsiasi altro progetto futuro.

Ora quello che c'è da dettagliare meglio è a cosa prestare attenzione in ogni fase, e quali azioni svolgere in quel caso.

Anche se non ho fatto chissà che sono già contento di non aver scopiazzato qua e la. Intanto allego i progressi e rinnovo la mia gratitudine per gli aiuti

#include <Wire.h>
#include <RTClib.h>

#define POMPA 5 // definisce il pin della pompa
#define ELETTROVALVOLA 6
#define TROPPOPIENO A0 // ??? devo ancora capire come funziona il sensore del troppopieno (contatto aperto-chiuso) ???

int ACCESO = LOW; // sostituisce la logica del pin (LOW/HIGH) con il termine ACCESO/SPENTO per una migliore comprensione della fuzione
int SPENTO = HIGH;

RTC_DS1307 RTC;

void setup() {
  pinMode(POMPA, OUTPUT); // imposta il pin (POMPA) come output
  pinMode(ELETTROVALVOLA, OUTPUT);
  pinMode(TROPPOPIENO, INPUT);

  digitalWrite(POMPA, SPENTO); // all'avvio di arduino spegne la pompa
  digitalWrite(ELETTROVALVOLA, SPENTO);
  
/***************************RTC e WIRE**************************/
  Serial.begin(9600);
  Serial.println( "START" );
   
  Wire.begin();
  RTC.begin();

//  RTC.adjust(DateTime(2019, 05, 23, 12, 53, 00)); // decommentare per regolare ora e data (yyyy,mm,dd,hh,mm,ss)

  if (! RTC.isrunning()) {
    Serial.println("RTC non è attivo!");
    RTC.adjust(DateTime(__DATE__, __TIME__));
  }
}






void loop() {
  if ( RTC.isrunning()) {
    DateTime now = RTC.now();
    Serial.print(now.year(), DEC);
    Serial.print('/');
    Serial.print(now.month(), DEC);
    Serial.print('/');
    Serial.print(now.day(), DEC);
    Serial.print(' ');
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.print(':');
    Serial.print(now.second(), DEC);
    Serial.println();
    }
}

Claudio_FF:
Per quanto riguarda il controllo dei periodi orari, meglio lavorare con i minuti, in questo modo basta evitare un periodo a cavallo della mezzanotte:

int adesso = now.hour()*60 + now.minute();  // tempi da 0 a 1439 minuti

int inizio = Pompa[0]*60 + Pompa[1];
int fine  = Pompa[2]*60 + Pompa[3];

Mi dite gentilmente a che servono i numeri nelle parentesi quadre?

Prova.ino (2.36 KB)