Gestione idrica: come fare la "coda" nei settori da irrigare

Buonasera a tutti,
Ogni qualvolta ho riscontrato un problema al quale da solo non riuscivo ad uscirne, ho risolto chiedendo aiuto qui in forum riuscendo sempre a cavarmela bene con i vostri ottimi consigli.
Ennesima chicca, ennesimo post.

Breve descrizione minimizzando i componenti:
Ho un Arduino Mega, che comanda 3 elettrovalvole. Una a monte, di comando, altre due a valle, una per ogni settore ed un flussometro.
Quindi:

  • elettrovalvola di comando
  • elettrovalvola settore 1
  • elettrovalvola settore 2
  • flussometro

Ogni tot tempo mi irriga il settore 1, quindi apre l'elettrovalvola di comando e l'elettrovalvola settore 1. Con il flussometro conto quanta acqua sto dando, e quando arriva ad una certa soglia di litro/mq stacco l'irrigazione quindi chiude l'elettrovalvola di comando e l'elettrovalvola settore 1.
Fin qui tutto bene. Funziona.

Vorrei che al finire del settore 1, mi chiude l'elettrovalvalvola del settore 1 e mi apre quella del settore 2. Rimanendo quindi aperta l'elettrovalvola di comando per consentire il flusso d'acqua.
I tempi di quando irrigare sono gli stessi, il volume no. Ma questo non è un problema..

Esiste un metodo abbastanza semplice per creare "code" dato che avrò più settori nel tempo da gestire?

P.S: scarto l'idea dell'acquisto di una pompa più grossa che abbia più portata, preferisco risolvere elettronicamente.

Non posto attualmente il codice, 1700 righe dove c'è molto altro di inutile alla risoluzione del problema. Se necessario, posto il minimo indispensabile per capirne la logica.

Spero vi facciate avanti e....grazie sempre per l'aiuto!

Vorrei che al finire del settore 1, mi chiude l'elettrovalvalvola del settore 1 e mi apre quella del settore 2. Rimanendo quindi aperta l'elettrovalvola di comando per consentire il flusso d'acqua.
I tempi di quando irrigare sono gli stessi, il volume no. Ma questo non è un problema..

Si trattasse solo di questo, sarebbe facile, ma mi sembra evidente che il problema è più complesso di come appare.

Esiste un metodo abbastanza semplice per creare "code" dato che avrò più settori nel tempo da gestire?

Infatti è complesso. Intanto per me informatico le code si chiamano queue, vedi se è di questo che hai bisogno, diversamente c'è fraintendimento.

P.S: scarto l'idea dell'acquisto di una pompa più grossa che abbia più portata, preferisco risolvere elettronicamente.

Quindi se avessi una pompa più potente ad esempio potresti aprire la 1 la 2 e la 4 assieme, mentre con la pompa che hai
sei costretto ad aprire le stesse ma in tempi differenti. É corretto questo ragionamento?
Se si, potrebbe bastare un array, mi sa che è troppo semplice per essere possibile.

Non posto attualmente il codice, 1700 righe dove c'è molto altro di inutile alla risoluzione del problema. Se necessario, posto il minimo indispensabile

No no postalo tutto in allegato a pizzichi e bocconi non si capisce nulla.

Ciao.

Maurotec:
vedi se è di questo che hai bisogno, diversamente c’è fraintendimento.

Si, ho bisogno di un queue…una fila che se termina di irrigare il settore1 inizia con il settore2 ecc…

Maurotec:
Quindi se avessi una pompa più potente ad esempio potresti aprire la 1 la 2 e la 4 assieme, mentre con la pompa che hai
sei costretto ad aprire le stesse ma in tempi differenti. É corretto questo ragionamento?

si il problema è la portata. La mia pompa riesce a spingere massimo 60 l/min e io ho bisogno di circa 100l/min e se lascio stare cosi il sistema non andrà mai in pressione…

p.S: nel codice, c’è anche la logica di accensione/spegnimento per l’irrigazione del pollaio e del semenzaio…ma che non c’entrano nulla al contesto

EDIT:
Faccio un esempio, ogni 10 ore voglio che mi irriga l’orto di 5 settori ed ho un flussometro che mi misura quanta acqua sto dando, ho impostato la soglia di 5l/mq. Sono passate 10 ore, quindi si inizia dal settore 1. Si supera questa soglia, quindi si chiude il settore 1 e si apre il settore 2 e così via fino al settore 5.

Vorrei implementare solo il queue perché tutto il resto è già in funzionante

MEGA-ORTO.ino (82.9 KB)

Visto il programma e devo dire che si fatica molto a capire.
Già il codice è gonfio, ho provato a cercare una implementazione di "programma irrigazione" dove di questo ne puoi creare un certo numero. Una volta selezionato il programma questo rimarrà salvato e verrà riproposto ad ogni accensione della centralina. Un "programma di irrigazione" è composto da oggetti Settore, le proprietà di questo sono:

class Settore(id, name, schedule)

nome settore = char str
id Elettrovalvola = 1 (da 1 fino a 255) 1 byte
schedule = oggetto schedulatore - esso sa "il quando" e per "quanto" tempo.

Un lista di settore potrebbe benissimo essere un array di nome programmaCorrente.

(A) Esempio di array programmaCorrente

programmaCorrente[0] = Settore(3)
programmaCorrente[1] = Settore(1)
programmaCorrente[2] = Settore(5)

Anche ripensando il programma in modo da suddividerlo in oggetti non vedo la necessità della coda.

Comunque, credo sia il momento di fermarsi e fare un pesante rework, per snellire il numero di linee di codice
perché è davvero gonfio. Tutte quelle dichiarazioni messe una di seguito all'altra occupano tanto spazio e non sono
facili da seguire e inoltre ti impediscono di aggiungere commenti ad ognuna.

Dal codice individuo una porzione che solitamente viene abbreviata con "interfaccia comandi su console" ed è quella
dove ci sono quelle interminabili if (comando[n]) ... Anche questa parte si deve tentare di condensare e ci possono essere tanti modi ognuno con pregi e difetti.

Per il problema della EV0 che deve essere aperta quando da un settore è stato terminato di irrigare e si deve passare
al successivo presente in lista. EV0 va chiusa al termine del programma cioè quando non ci sono più settori da irrigare.

Altra cosa che noto è il limitato uso di comandi C, cioè struct è potente, class è ancora più potente, le classi template sono potenza elevata a potenza, in sostanza non stai sfruttando la potenza del linguaggio e per fare un paragone con un lavoro
pratico sembra che per fare un buco anziché usare il tramano ci vado di cacciavite. Attualmente se continui così aggiungendo funzionalità il codice da grasso diventa obeso. Io ti consiglio di fare un poco di esperienza con la parola chiave struct per iniziare a snellire il codice, poi passerei a eliminare tutte quelle if (comando[n]) o comunque se proprio li vuoi mantenere le si sposta in un altro file, ma non sarà facile e non è come interverrei io (ma io sarei troppo estremo).

EDIT:
Faccio un esempio, ogni 10 ore voglio che mi irriga l'orto di 5 settori ed ho un flussometro che mi misura quanta acqua sto dando, ho impostato la soglia di 5l/mq. Sono passate 10 ore, quindi si inizia dal settore 1. Si supera questa soglia, quindi si chiude il settore 1 e si apre il settore 2 e così via fino al settore 5.

Esatto, ma ti serve un contenitore sequenziale dove inserire i settori che io ho chiamato "programmaCorrente".
Da un menu i interfaccia console selezioni un programma da un lista di programmi contenuti ad esempio in una sdcard
e quello selezionato sarà il programma corrente. Quindi programmaCorrente è un tipo di dato contenitore e di contenitori informatici ne esistono tanti, il più semplice è l'array.

Con riferimento all'esempio (A) quando il contatore vale 0 esegui
programmaCorrente[0] a cui è stato assegnato il settore(3) terminato questo si incrementa il contatore esi passa
al settore(1) se ci sono altri settori si incrementa nuovamente il contatore fino al momento che l'ultimo settore vale
0 (zero) e questo ci indica il programma è terminato e quindi possiamo chiudere la EV0.

Spero che tu possa trarne uno o più spunti di riflessione.

Ciao.

Serial1 con che cosa comunica? Mi sembra un grosso spreco di risorse dover identificare lunghe sequenze di caratteri. Sarebbe molto più vantaggioso usare un solo valore o carattere di controllo per ciascun comando.

Grazie per le risposte.
Sicuramente devo snellire il programma, troppo laborioso e mal gestito ma funziona, e non ho nemmeno tempo per snellirlo..però prendo tesoro dei consigli di @maurotec.

@datman la serial1 riceve comandi da un cellulare via bluetooth. Dati i tanti comandi, dove alcuni modificano variabili e altre sono solo di controllo, è meglio avere un nome più calzante al comando e non solo un carattere che sicuramente snellisce tutto ma poi vattelo a ricordare quel comando ...

Ora vi dico come farei io a risolvere..ovviamente non è la migliore soluzione ma la più sbrigativa.

Dato che la turnazione irrigua sarà consequenziale, avevo pensato a questo:

Se elettrovalvola settore1 va da HIGH a LOW, vuol dire che ha appena smesso di irrigare, quindi elettrovavola settore2 va ad HIGH, il flussometro poi misurerà quanta acqua dare. Stessa cosa per altri settori.. che ne pensate?

Sul cellulare puoi far apparire il nome che vuoi, ma poi non sei costretto a inviare gli stessi caratteri. Nel menu può apparire un nome lungo, ma poi può inviare solo tre caratteri. In questo modo eviti quella lunghissima e inquietante catena di if, che occupa memoria e rallenta l'esecuzione del programma.

Gianky00:
Se elettrovalvola settore1 va da HIGH a LOW, vuol dire che ha appena smesso di irrigare, quindi elettrovavola settore2 va ad HIGH, il flussometro poi misurerà quanta acqua dare. Stessa cosa per altri settori

  1. Non c'entra strettamente con la coda ma solo con un dettaglio implementativo della logica di avanzamento.
  2. Se c'è già il flussometro a dire quando un settore ha avuto abbastanza acqua, a che serve testare un altro evento (la chiusura di una valvola) che è già condizionato al verificarsi del primo?
  3. La coda mi sembra sia banalmente realizzabile con un indice di settore, un array con i pin delle valvole di settore da comandare, e un array con le quantità di impulsi da leggere per ogni settore.
  4. Visto che ogni cosa si può rompere aggiungerei anche una fase di allarme se dopo aver aperto le valvole il flussometro non invia impulsi (o li invia molto lentamente), oppure se dopo averle chiuse si continuano a rilevare impulsi. Al limite ai dati dei settori si può aggiungere un array di tempi di timeout, per interrompere l'irrigazione anche se il flussometro non ha ancora raggiunto gli impulsi previsti.

Il codice seguente è solo un suggerimento su come usare gli array per snelline lo sketch.

// I pin a cui sono collegate le elettrovalvole 
#define EV_SETTORE1 5
#define EV_SETTORE2 8
#define EV_SETTORE3 11
#define EV_SETTORE4 15

// array di pin elettrovalvole (tutte quelle che hai)
uint8_t evPinArray[] = {EV_SETTORE1, EV_SETTORE2, EV_SETTORE3, EV_SETTORE4};

void setup () {
    
    // Imposta tutti i pin elencati nell'array come OUTPUT 
    for (uint8_t i = 0; i<sizeof(evPinArray); i++) {
        pinMode(evPinArray[i], OUTPUT);
    }

}

Sempre un suggerimento; un altro array (o più di uno) può essere salvato dentro un array.
Esempio:

uint8_t programmaIrrigazione1[5] = {0}; // array di 5 elementi inizializzati a zero 

// nel loop la configurazione del programma di irrigazione da eseguire, cosa che puoi fare anche con l'interfaccia comandi
// che hai già implementato. Ad esempio vuoi irrigare solo due settori a scelta esempio Settore2 e 4
programmaIrrigazione1[0] = evPinArray[1];
programmaIrrigazione1[1] = evPinArray[3];

Quando devi eseguire il programma

uint8_t indiceProgramma = 0;
uint8_t settore = programmaIrrigazione1[indiceProgramma]; 
if (settore != 0) {
    digtalWrite(settore, HIGH);
} else  {
    // programma terminato
}
// per passare al settore seguente devi incrementare la variabile indiceProgramma
// portandolo a 1 esegui il settore 4
// portandolo a 2 finisce per eseguire "else" e termina il programma di irrigazione

La cosa si semplifica enormemente se hai solo 4 elettrovalvole (1 per settore) ed esegui sempre in ciclo tutti i settori,
uno per volta, in questo non serve programmaIrrigazione1 e puoi usare direttamente evPinArray.

Ciao.

Ciao a tutti,
ho letto tutto il post ed ho pensato di dare anche il mio parere :wink: sulla questione.

Faccio un esempio, ogni 10 ore voglio che mi irriga l’orto di 5 settori ed ho un flussometro che mi misura quanta acqua sto dando, ho impostato la soglia di 5l/mq.
Sono passate 10 ore, quindi si inizia dal settore 1.
Si supera questa soglia, quindi si chiude il settore 1 e si apre il settore 2 e così via fino al settore 5.

Pensavo che il problema si potrebbe affrontare con l’utilizzo di una Finite State Machine (macchina a stati finiti).
Questa soluzione potrebbe semplificare il codice ed è una soluzione scalabile nel senso che se in un futuro dovresti avere non 5 settori ma 6, 7 o più la modifica sarebbe veramente semplice.
Comunque…
La macchina avrebbe 6 stati (nel tuo caso):
S0: Stato di avvio (Attesa delle 10h)
S1: Stato di apertura EV1 (Elettrovalvola settore 1)
S2: Stato di apertura EV2 (Elettrovalvola settore 2)
S3: Stato di apertura EV3 (Elettrovalvola settore 3)
S4: Stato di apertura EV4 (Elettrovalvola settore 4)
S5: Stato di apertura EV5 (Elettrovalvola settore 5)
I passaggi da uno stato al successivo avverrebbero al verificarsi della condizione portata>= soglia.
Attraversati tutti e5 stati si ritorna allo stato S0.
Questa un pò la logica di funzionamento molto generale, in quanto all’implementazione la cosa si traduce nell’utilizzo del costrutto: Switch case.
Se pensate possa interessare come proposta, datemi conferma che posto uno schizzo della logica con la macchina a stati nonché una bozza di codice.
Buona serata.

P.S.: In allegato trovate il diagramma della macchina a stati finiti.
In cui:
t: è il tempo
p: è la portata [l/mq]
Thr1…5: sono le 5 differenti soglie di portata (Threshold)

MacchinaStatiFiniti.png

Ringrazio enormemente per le risposte…
@DATman
quello che dici è in parte realizzabile almeno per come ho capito. Quando eseguo il comando di “controllo” allora si, va benissimo come mi hai detto e infatti lo snellirò anche cosi…

ma quando ho il comando “tempo accensione orto 36000” , che vuol dire che dall’alba si inizia a contare 10 ore e poi dà l’inizio della turnata irrigua, non ha molto senso. Sono comandi personalizzati al momento, un po come se volessi correggere l’RTC con “rtc 2035” per cambiare l’ora alle 20:35, devo comunque scrivere l’intero comando nel terminale del cellulare…

certo, potrei comunque scrivere come comando “a 36000” e “b 2035” … ma ogni qualvolta dovrò modificare qualcosa non ricorderò mai la lettera di inizio comando e quindi la dovrò leggere magari in un foglio stampato…

E’ vero che queste variabili non si modificano spesso anzi, ma è anche vero che uso un arduino MEGA e non ho problemi di spazio, il codice risulta anche più lento ma alla fine solo quando legge qualcosa in seriale viene eseguito tutto quel gran blocco che comunque sia, la risposta è molto veloce (<1sec) per me.

@Claudio_FF
2) difatti non ha senso quello che ho detto, semplicemente perchè non ho pensato alla condizione a monte del flussometro :smiley:
4) ci avevo già pensato :wink:

@Maurotec
è arrivato il momento di utilizzare gli array nel programma:
quindi con questo codice:

#define EV_SETTORE1 5
#define EV_SETTORE2 8
uint8_t evPinArray[] = {EV_SETTORE1, EV_SETTORE2};

void setup () {
  for (uint8_t i = 0; i < sizeof(evPinArray); i++) {
    pinMode(evPinArray[i], OUTPUT);
  }
}

void loop() {

  uint8_t indiceProgramma = 2;
  uint8_t settore = evPinArray[indiceProgramma];
  if (settore != 0) {
    digitalWrite(settore, HIGH);
  } else  {
    digitalWrite(settore, LOW);
  }
  // per passare al settore seguente devi incrementare la variabile indiceProgramma
  // a 0 esegui il settore 1
  // a 1 esegui il settore 2
  // a 2 finisce per eseguire "else" e termina l'irrigazione
}

leggendo nei commenti del codice, ha tutto un senso? E’ corretto cosi?

@BigFrancis per adesso provo a risolvere con array, grazie comunque del consiglio :wink:

Intanto ho letto gli altri interventi che condivido in pieno, ad esempio la FSM (Finite State Machine) suggerita da @BigFrancis. Tuttavia attualmente la tua implementazione è molto distante da FSM e implementarla stravolgerebbe tutto il codice, quindi documentati su FSM e prova ad immaginare la suddivisione in STATI. Visto che una FSM può essere implementata in tanti modi differenti prova ad implementare su un'altro sketch un FSM basata su switch case; aiutati con Serial.print per il degub.

In ogni caso un programma andrebbe sempre gestito tramite FSM, ma bisogna saperlo e volerlo fare.

Riguardo agli array nel programma già li usi, ad esempio comando è un array.

Come usarli però lo devi trovare tu e al momento sei fuori strada perché per funzionare
quella if (settore != 0) l'ultimo elemento di evPinArray deve essere 0 (zero).

#define EV_MAIN  10
#define EV_SETTORE1 5
#define EV_SETTORE2 8

uint8_t indiceProgramma = 0; // parte da EV_MAIN
// sempre un suggerimento, quindi puoi anche eliminare EV_MAIN da evPinArray
uint8_t evPinArray[] = { EV_MAIN,  EV_SETTORE1, EV_SETTORE2, 0 };

void loop() {
  
  uint8_t settore = evPinArray[indiceProgramma]; // quando indiceProgramma vale 0 settore vale EV_MAIN
  if (settore != 0) {
    digitalWrite(settore, HIGH);
    // qui dovrai monitorare il sensore di flusso per stabilire quando cessare l'irrigazione
    // quando è il momento di cessare l'irrigazione chiudi l'elettrovalvola 
    e incrementi indiceProgramma con indiceProgramma++;
  } else  {
    
    // adesso settore vale 0 per cui è terminato il programma di irrigazione e quindi chiudiamo 
    // EV_MAIN con digitalWrite(EV_MAIN, LOW);
  }
  // per passare al settore seguente devi incrementare la variabile indiceProgramma
  // a 0 esegui il settore 1
  // a 1 esegui il settore 2
  // a 2 finisce per eseguire "else" e termina l'irrigazione
}

Comunque sembra che tu abbia sempre 2 settori più una ev principale e non ti serve
introdurre dinamicità e genericità fornite dalle centraline che trovi in commercio, per cui
anche uno switch case potrebbe fare al caso tuo.

// dichiarare settoreCorrente globale come segue
// uint8_t settoreCorrente = 0; // globale significa fuori da ogni funzione
switch (settoreCorrente) {
    case 1: // è il settore 1
        // ricorda che devi anche aprire la ev principale e accendere la pompa
        // qui il tuo codice per gestire il settore 1, quando termina l'irrigazione
        // chiudi ev1 e incrementi settoreCorrente es.
        // settoreCorrente++;
        break;  // ci va sempre
    case 2:
        // qui il tuo codice per gestire il settore 2. Quando termina l'irrigazione
        // chiudi ev2, spegni pompa e chiudi ev principale.
        // settoreCorrente++;
        break;
}

Dopo che l'irrigazione di settore 2 è terminata la variabile settoreCorrente vale 3 e nessuno
dei case sarà eseguito per cui quando è il momento di ripetere l'irrigazione devi
assegnare a settoreCorrente il valore 1.

Si tratta sempre di spunti e suggerimenti che devi fare tuoi prima di procedere in un verso o nell'altro.

Ciao.

Buonasera,
Ho risolto perfettamente con uno switch case snellendo parecchio anche il codice inserendo un FSM sia per l'implementazione della turnazione irrigua (se finisce un settore passa all'altro) sia per la modalità fertirrigazione, una banalità senza precedenti che con le if ed else non sapevo dove sbattere la testa..

C'è molto da lavorarci per snellire ancora di più il codice. Vorrei ad esempio eliminare quelle interminabili if, con il suggerimento di Datman potrei farlo, ma solo in piccola parte.

E poi, per quanto riguarda di come ho dichiarato le variabili globali, questa sintassi l'ho letta sul forum di Nick Gammon. Se non specifica nessun valore di default la variabile esiste lo stesso, ma il valore contenuto nella sua cella di memoria è imprevedibile, in quanto è l'ultimo valore che ha occupato quella cella, potrebbe esservi stato lasciato da qualsiasi programma eseguito in percedenza. Quindi, va bene snellire anche lì oppure è meglio definire tutte le variabili per evitare numeri strani?

C’è molto da lavorarci per snellire ancora di più il codice.

Puoi iniziare dalla configurazione del timer1 che usi per qualcosa che non mi appare chiaro.
Ad esempio con una fava più piccioni:

  TIMSK1 &= ~(1 << TOIE1);
  TCCR1A &= ~((1 << WGM11) | (1 << WGM10));
  TCCR1B &= ~((1 << WGM12) | (1 << WGM13));
  TIMSK1 &= ~(1 << OCIE1A);
  TCCR1B |= (1 << CS12)  | (1 << CS10);
  TCCR1B &= ~(1 << CS11);
  TCNT1H = 0xC2;
  TCNT1L = 0xF7;
  TIMSK1 |= (1 << TOIE1);

Questa porzione di codice ha un grado di difficoltà maggiore rispetto al resto del programma.
Qui manipoli i registri del hardware del timer 1 della MCU. Genericamente si dice che questa porzione
di codice opera a basso livello. Mentre il resto del programma opera ad alto livello, per cui dobbiamo fare scomparire questo codice da dentro il setup. Crei un altro sketch e lo chiami timer1 e ci metti una funzione
di nome timer1Config().

//  qui ci scrivi il commento a questa funzione, cioè come configuri il timer, per cosa lo usi ecc
/*
   commento multi riga

*/                    
void timer1Config() {
  TIMSK1 &= ~(1 << TOIE1);
  TCCR1A &= ~((1 << WGM11) | (1 << WGM10));
  TCCR1B &= ~((1 << WGM12) | (1 << WGM13));
  TIMSK1 &= ~(1 << OCIE1A);
  TCCR1B |= (1 << CS12)  | (1 << CS10);
  TCCR1B &= ~(1 << CS11);
  TCNT1H = 0xC2;
  TCNT1L = 0xF7;
  TIMSK1 |= (1 << TOIE1);
}

Il setup si trasforma così:

void setup()
{
  //  commento la chiamata a timer1Config anche se il nome della funziona è autoesplicativo
  timer1Config();
  Wire.begin();

Potresti anche spostare nel file timer1 questa ISR

ISR(TIMER1_OVF_vect)
{
  TCNT1H = 0xC2;
  TCNT1L = 0xF7;
  TCNT1H = 0xC2;
  TCNT1L = 0xF7;
  stato_timer1_ISR = true;
}

La potresti commentare scrivendoci che con F_CPU = 16MHz la ISR viene chiamato ogni
tot

E questa pare semplice, rispettare le convenzioni è difficile da spiegare e da rispettare.

Ci provo prendendo in prestito un riga a caso:

statoelettrovalvolaCOMANDO = digitalRead(elettrovalvolaCOMANDO);

Stabilisco che tutto ciò che ha a che fare con le elettrovalvole si chiama EV seguito dal numero
e da qualcosa altro se è necessario. Tuttavia i pin li chiamo pinEV0, pinEV1 ecc.
Lo sta di un pin lo chiamo statoEV0 statoEV1 ecc.

statoEV0 = digitalRead(pinEV0);

Le convenzioni che stabilisci devono essere coerenti e coerentemente devono essere usate su tutto
il codice.

Altra riga:

int const fsize = 50;

Si tratta di una costante che occupa 1 byte (255 massimo valore decimale) ma io voglio sprecare un
byte è uso il tipo di dato int che è grande 2 byte ed è con segno anche se a me il segno non serve.

Tradotto:

const byte fsize = 50;

Questa forma è la più corretta (const prima del tipo), ci sono altre sintassi con due const per variabile
e quella tua può confondere.

Mi fermo qui, ma tu apporta le modifiche ad una ad una, osserva il codice dopo la modifica e chiediti
se puoi fare ancora qualcosa, se hai il dubbio fermati li e posta il codice.

Ciao.