Irrigazione automatica

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 :)

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 :D :D :D

@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 
#include 

#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)

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

Siamo messi male. :) Si chiamano array (o vettori) link

int ACCESO = LOW;

Anche qui serve studiare i tipi di variabile. più corretto è,

const byte acceso = LOW;

Mentre per le macro (preprocessore C) corretto è,

#define ACCESO LOW

Ciao.

Maurotec: Siamo messi male. :)

Quoto in pieno! grazie per il link!

Ecco ora mi sono arenato… se switch è l’ora attuale, nel mio caso ADESSO, però ADESSO viene ricavato da

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

come faccio a prelevare anche il mese senza superare i 1439 minuti? c’è un operatore che non sia + e che mi permette di farlo? Allego il codice

Prova.ino (2.79 KB)

Per gli array la sintesi è questa:

-Una variabile è una scatoletta che può contenere un valore. -Un array è una fila di scatolette ciascuna identificata da un indice (che parte da 0 per la prima)

Ho scritto Pompa[n] perché nel codice del primo post gli orari di inizio e fine sono contenuti in un array di quattro posizioni:

int Pompa[] = {13, 29, 13, 30};

e per ottenere i valori bisogna indicare non solo la variabile array, ma anche quale delle sue scatolette:

       .----.----.----.----.
Pompa  | 13 | 29 | 13 | 30 |
       '----'----'----'----'
Indici   0    1    2    3

Per il resto il fatto che tu stia in qualche modo parlando di switch legato alla variabile 'adesso' e di sommare il mese mi fa temere qualcosa di brutto brutto, ma magari non è così :) Prima di pensare a come codificarla, non mi è chiaro come vorresti usare l'informazione del mese, perché il mese ce l'hai già in chiaro con now.month()

Te l’ho già detto che hai il dono della chiarezza? :slight_smile:

Ho capito anche io dopo aver fatto la domanda che stavo facendo una cavolata… poi aggiungendo il MESE col metodo a scatoletta (vettore, array) mi sono reso conto che posso verificare quella condizione in un altro modo.

int MESE[] = {3,5,7,9,11};  // (GEN 1, FEB 2, MAR 3, APR 4, MAG 5, GIU 6, LUG 7, AGO 8, SET 9, OTT 10, NOV 11, DIC 12)

Però ora mi sono arenato di nuovo con sto benedetto switch, in quanto arduino mi restituisce un errore incomprensibile tipo Prova:102:1: error: stray '\302' in program.

In pratica sto provando a fare così ma mi da errore:

switch (ADESSO)
    {
      case RIEMPIMENTO:
      // istruzioni a
      break;

      case RIPOSO:
      // istruzioni a
      break;

      case CONCIMAZIONE:
      // istruzioni a
      break;

      default:
      if ((ADESSO >= INIZIO) && (ADESSO < FINE))
      {
       digitalWrite(PinPOMPA, ACCESO);
      } 
      else 
      { 
       digitalWrite(PinPOMPA, SPENTO); 
      }
      break;
    }

Allego il codice nel caso vogliate dare un occhiata al casino che sto facendo :smiley:

Prova.ino (3.06 KB)

No scusa, nessun errore, avevo copiato ed incollato qualche carattere invisibile dal tuo esempio! FUNZIONA!!! :D :D

No no, l'errore c'è, ed è quello che temevo :)

Lo switch deve funzionare in base al valore della variabile di fase, non del valore orario... Di fatto con lo switch hai scritto l'equivalente esatto di questo if... un sacco di condizioni che non risulteranno mai vere, o meglio, risulteranno vere solo a mezzanotte, a mezzanotte e un minuto, a mezzanotte e due minuti ecc, mentre per tutto il resto del tempo viene eseguito solo l'else:

if (ADESSO == RIEMPIMENTO)
{
    // istruzioni a
}
else if (ADESSO == RIPOSO)
{
    // istruzioni a
}
else if (ADESSO == CONCIMAZIONE)
{
    // istruzioni a
}
else
{
    if ((ADESSO >= INIZIO) && (ADESSO < FINE))
    {
        digitalWrite(PinPOMPA, ACCESO);
    }
    else
    {
        digitalWrite(PinPOMPA, SPENTO);
    }
}

Di solito nella parte default dello switch non c'è bisogno di scrivere nulla. Avviene tutto nei case (o nelle funzioni richiamate dai case). Ed è all'interno dei case che si controlla il valore dell'orario decidendo cosa fare.

      case RIPOSO:  // attendo l'orario, e in quel momento passo a fase irrigazione
          if (in_orario())
          {
              accendi_pompa();
              fase = IRRIGAZIONE;
          }
      break;

      case IRRIGAZIONE:  // attendo la fine orario, e in quel momento passo a riempimento
          if (!in_orario())
          {
              spegni_pompa();
              apri_valvola();
              fase = RIEMPIMENTO;
          }
      break;

Se scrivi tante piccole funzioncine (come accendi_pompa, in_orario ecc) hai l'intera logica scritta in italiano.

In maiuscolo andrebbero scritte solo le costanti, in modo da distinguerle dalle variabili minuscole.

Quanta fretta ma dove corri… https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=2ahUKEwju47j4ibfiAhVQyKQKHRtABHUQyCkwAHoECAoQBQ&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DYoBuv7HR-Fw&usg=AOvVaw34Sz9Sb-XmZdDvDzkl9Xsd

Freno a mano tirato su.
Esistono delle convenzioni che se rispettate permettono al programmatore di ricavare informazioni in un solo colpo di occhio. Ad esempio se per convenzione scriviamo le macro tutte in maiuscolo e le variabili in minuscolo abbiamo il seguente vantaggio, cioè osservando una piccola porzione di codice di un programma che conta 10000 righe sappiamo che PH_START è un macro, mentre phStatus è una variabile. Ovviamente possiamo modificare il valore di una variabile ma non di una macro.

Stessa potenza c’è nell’uso del tipo di variabili, come dire se uso il tipo int vuole dire che mi serve contenga anche valori negativi. Quando so a priori che la variabile non conterrà mai valori negativi il tipo corretto è byte, uint8_t, uint16_t, uint32_t, boolean ecc, in sostanza tutti tipi senza segno.

No no, l’errore c’è, ed è quello che temevo :slight_smile:

Che vuoi fare l’ebbrezza di avere scritto qualcosa che funziona senza copiare da alla testa. :slight_smile:

PS: lo so, la domanda è; da dove spuntano fuori uint8_t, uint16_t ecc?

Ciao.

Maurotec:
Che vuoi fare l’ebbrezza di avere scritto qualcosa che funziona senza copiare da alla testa. :slight_smile:

E’ stato breve ma intenso, il momento di gloria intendo…
Già non funziona più, appena ho tirato fuori il codice da default:

Non capisco una cosa però, a parte quello che mi avete detto. Lo switch richiede che il case sia dichiarato… si ma in che modo? facendo delle prove avevo messo dei numeri a caso e funzionava lo stesso:

#define RIEMPIMENTO 0 // perchè 0?
#define RIPOSO 1 // perchè 1?
#define CONCIMAZIONE 2 // perchè 2?
#define IRRIGAZIONE 3

Prova.ino (3.15 KB)

karnhack:
facendo delle prove avevo messo dei numeri a caso e funzionava lo stesso:

Dal post #11: «il valore associato ad ogni fase è naturalmente una convenzione»
Dal post #15: «Tra l’altro i valori sono del tutto convenzionali»

Al punto che dandogli dei nomi con ‘define’ o con ‘const byte’ ci possiamo dimenticare dei valori… purché siano tutti diversi l’uno dall’altro.

Claudio_FF: Dal post #11: «il valore associato ad ogni fase è naturalmente una convenzione» Dal post #15: «Tra l'altro i valori sono del tutto convenzionali»

Mi fai paura, ma sei umano o un BOT? :o Che memoria! :open_mouth:

Claudio_FF: Lo switch deve funzionare in base al valore della variabile di fase, non del valore orario... Di fatto con lo switch hai scritto l'equivalente esatto di questo if... un sacco di condizioni che non risulteranno mai vere, o meglio, risulteranno vere solo a mezzanotte, a mezzanotte e un minuto, a mezzanotte e due minuti ecc, mentre per tutto il resto del tempo viene eseguito solo l'else [/code]

Ok quindi se ho capito bene la tua indicazione il problema sta in switch (ADESSO) Quindi dovrei lavorare a cascata? cioè partire dalla fase1, verificarla e poi passare alla fase 2 ecc.

Dunque in poche parole dovrei partire dalla fase RIEMPIMENTO e interrogare la valvola se aperta o chiusa? è aperta, bene allora riempi e passa alla fase 2 RIPOSO. Nella fase RIPOSO vado a interrogare l'RTC in attesa che la data o l'ora coincidano oppure solo l'orario per le operazioni quotidiane, etc. etc. fino alle fasi successive.

Ma quindi la X di "switch (x)" non deve essere un fattore comune a tutte le fasi? è questo che non capisco... La X può essere legata solo alla lettura del sensore troppopieno della fase RIEMPIMENTO? Non so se mi sono spiegato.

karnhack: Quindi dovrei lavorare a cascata? cioè partire dalla fase1, verificarla e poi passare alla fase 2 ecc.

Si, in ogni situazione esegui solo un pezzo del programma, quello relativo alla situazione corrente identificata dalla variabile fase. Quando ci sono le condizioni, ad esempio quando nella fase riempimento trovi che il sensore pieno si è chiuso, modifichi le uscite e passi alla situazione/fase seguente (seguente in senso logico, non necessariamente di numero). Al prossimo giro del programma verrà eseguito il nuovo pezzo di codice relativo alla nuova fase e così via.

Nella fase RIPOSO vado a interrogare l'RTC in attesa che la data o l'ora coincidano oppure solo l'orario per le operazioni quotidiane, etc. etc. fino alle fasi successive.

Esatto.

La variabile 'x' degli esempi del post #15 contiene il numero della fase corrente, e serve per eseguire solo il pezzo di codice di quella fase. Che poi lo fai con switch o con if poco importa.