Array di oggetti che devono essere eliminati dopo tot tempo

Salve a tutti, chiedo il vostro aiuto in quanto mi sono impantanato nella ragionata :grin:
Sto cercando di creare un sistema in cui ho un'array di oggetti che attivano un'uscita e dopo tot tempo la disattivano.
Le richieste arrivano tramite delle GET HTTP (numero uscita + tempo di attivazione), di conseguenza gli oggetti dovrebbero essere accodati in ordine in base alle richieste e quando il tempo "scade" dovrebbero portare l'uscita ad OFF e rimuoversi dalla "coda" in modo da lasciare inalterato l'ordine delle richieste già presenti.

Provo a spiegarmi meglio, l'oggetto "OperationOut" dovrebbe essere fatto circa cosi:

int OutIndex  // indice che indica qual'è l'uscita coinvolta
unsigned long OutTimeOn // millis() di quando è stata eseguito l'ON dell'uscita
int Timeout //  tempo in millisecondi che mi indica dopo quanto impostare ad OFF l'uscita
bool OutState // true se ON, false se OFF
funzione Switch(ON/OFF, time)  //  attiva o disattiva l'uscita OutIndex e aggiorna OutState, OutTimeOn, Timeout

Una volta ricevuta la richiesta HTTP, dovrei instanziare l'oggetto, chiamare la funzione Switch che attiva l'uscita corrispondente e aggiungerlo all'array.

All'inizio del loop dovrei poi ciclare tutti gli oggetti presenti nell'array e fare lo Switch(off) per quelli il cui tempo è scaduto, e rimuoverli dall'array.

Ora, il mio problema principale è che non so come creare un'array dinamico di oggetti che possono essere rimossi "nel mezzo" quando uno di questi finisce il tempo.

Ho provato a cercare nel forum e su google ma non ho trovato una soluzione valida, alcuni consigliano di creare array fissi di oggetti, altri dei puntatori ma anche qui non ho capito come posso rimuovere gli elementi a metà array.
Se devo per forza avere un'array fisso, in teoria con una coda di 100 richieste dovrei averne abbastanza, ma vorrei evitare che ad ogni loop vengano ciclati 100 elementi perchè questo mi sballerebbe tutti i tempi.

Avete qualche dritta?

Grazie mille! :smiley:

Se devo per forza avere un'array fisso, in teoria con una coda di 100 richieste dovrei averne abbastanza, ma vorrei evitare che ad ogni loop vengano ciclati 100 elementi perchè questo mi sballerebbe tutti i tempi.

Vedo la cosa fattibile solo se cicla su tutti gli elementi dell'array, non vedo altra soluzione con arduino.
La cosa sarebbe fattibile con le liste collegate (linked list) che nel modo classico alloca memoria sullo heap e la dealloca per rimuovere l'oggetto dalla lista, però con arduino c'è il rischio di ritrovarsi senza memoria ram allocabile.

Il problema potrebbe verificarsi quando per il nuovo oggetto allochiamo dinamicamente (a run-time) la memoria, malloc (o altro allocatore) potrebbe non essere in grado di trovare uno spazio di memoria head sufficientemente grande per contenere l'oggetto.

Se allocassi/deallocassi dinamicamente oggetti di un solo tipo (con dimensione fissa) tutto funzionerebbe, ma se oltre a questi oggetti ne usi altri come ad esempio String primo o poi il programma va in crash.

Una soluzione sicuramente c'è, ma vanno valutate attentamente tutti i risvolti e prese le precauzioni necessarie per evitare che il codice smetta di funzionare dopo X tempo (a causa della continua allocazione/deallocazione).
Al momento non posso prendermi tempo per trovare una possibile soluzione, tu intanto continua a ragionarci su, magari trovi un modo diverso di svolgere il compito.

Ciao.

@leen15
Ti invitiamo a presentarti qui: Re: Presentazioni nuovi iscritti, fatevi conoscere da tutti! - Generale - Arduino Forum
e a leggere il regolamento: [REGOLAMENTO] Come usare questa sezione del forum - Italiano - Arduino Forum

Grazie mille per la risposta MauroTec, utilizzando un array come dici, è comunque possibile eliminare un elemento al suo interno e far shiftare i rimanenti in modo da dover ciclare solo fino a quando ho elementi nell'array (senza dovermeli passare tutti e 100)?

Per linkedList intendi questa? GitHub - ivanseidel/LinkedList: 🔗 A fully implemented LinkedList made to work with general Microcontrollers and Arduino projects
Utilizzando un'oggetto semplice come ho scritto sopra con 2 int, un long e un bool (magari senza la funzione se da problemi di memoria) raggiungerei il mio scopo, giusto?
Magari c'è un modo di controllare quanta memoria sta per alloccare per evitare di crashare? sulla libreria parlano di alloccare anche 2000 elementi senza problemi, a me ne bastano al massimo 100 :smiley:

@nid69ita: Vedo di presentarmi subito sulla discussione linkata, il regolamento l'ho già letto e non ho capito cosa centrano le schede che hai linkato.
Grazie

Sono schede generiche, non legate alla domanda del thread. Siccome chi è nuovo non sempre conosce quei thread utili tra i MegaTopic. :wink:

nid69ita:
Sono schede generiche, non legate alla domanda del thread. Siccome chi è nuovo non sempre conosce quei thread utili tra i MegaTopic. :wink:

Ah ok, non è il mio caso ma grazie comunque :wink:

Magari c'è un modo di controllare quanta memoria sta per alloccare per evitare di crashare? sulla libreria parlano di alloccare anche 2000 elementi senza problemi, a me ne bastano al massimo 100 smiley-grin

Rileggi e ragionaci su ancora un poco. Il problema non sta nel numero degli oggetti, ma nel fatto che si alloca dinamicamente la memoria, su un PC c'è la memoria virtuale, c'è il compattamento della ram ecc, che evitano l'esaurimento della memoria. La memoria potrebbe non essere tutta occupata, ma ci potrebbero essere 100 locazioni (100 byte) non contigue, per cui malloc non trovando una spazio di memoria contigua di 100byte restituirebbe 0 e non allocherebbe alcuno spazio.

Gli oggetti di tipo struct (o class) (es typeA) li puoi dichiarare staticamente, in questo modo non usi lo heap (quindi niente malloc o new), mentre la linked list potresti decidere di farla dinamica.

Ogni elemento della lista lincata sarà una struct contenente due campi puntatore. Uno a tipo typeA e uno a se stesso. Allocare dinamicamente un elemento grande soltanto 32 bit migliora la situazione, in quanto la ram non serve che sia contigua e quindi ci sono molte più probabilità che in ram si trovi 32 bit liberi contigui che 60-80-100byte contigui (es un array in heap deve essere contiguo, altrimenti non può crescere o diminuire di dimensioni).

Cerca scheduler con google, leggi un po (magari su wikipedia) e dimmi se quello che serve a te è simile allo scheduler.

Ciao.

MauroTec:

Magari c’è un modo di controllare quanta memoria sta per alloccare per evitare di crashare? sulla libreria parlano di alloccare anche 2000 elementi senza problemi, a me ne bastano al massimo 100 smiley-grin

Rileggi e ragionaci su ancora un poco. Il problema non sta nel numero degli oggetti, ma nel fatto che si alloca dinamicamente la memoria, su un PC c’è la memoria virtuale, c’è il compattamento della ram ecc, che evitano l’esaurimento della memoria. La memoria potrebbe non essere tutta occupata, ma ci potrebbero essere 100 locazioni (100 byte) non contigue, per cui malloc non trovando una spazio di memoria contigua di 100byte restituirebbe 0 e non allocherebbe alcuno spazio.

Gli oggetti di tipo struct (o class) (es typeA) li puoi dichiarare staticamente, in questo modo non usi lo heap (quindi niente malloc o new), mentre la linked list potresti decidere di farla dinamica.

Ogni elemento della lista lincata sarà una struct contenente due campi puntatore. Uno a tipo typeA e uno a se stesso. Allocare dinamicamente un elemento grande soltanto 32 bit migliora la situazione, in quanto la ram non serve che sia contigua e quindi ci sono molte più probabilità che in ram si trovi 32 bit liberi contigui che 60-80-100byte contigui (es un array in heap deve essere contiguo, altrimenti non può crescere o diminuire di dimensioni).

Cerca scheduler con google, leggi un po (magari su wikipedia) e dimmi se quello che serve a te è simile allo scheduler.

Ciao.

Mi sa che sei andato troppo sul tecnico :stuck_out_tongue_closed_eyes:

Ho letto un po riguardo allo scheduler ma non riesco ad applicarlo al mio utilizzo, gli elementi nel mio caso hanno tutti piu o meno la stessa importanza (in quanto quando vengono creati, l’uscita viene impostata a true e non ho un’ordine di “esecuzione” ne risorse “inutilizzate”), ma ovviamente se hanno tutti la stessa durata devono spegnersi in sequenza cosi come si sono accesi (e quindi devo mantenere un’ordine almeno per lo spegnimento).
Se ho ben capito quindi, usando una LinkedList dinamica tipo cosi:

LinkedList<MyClass> *myLinkedList = new LinkedList<MyClass>();

Dovrei riuscire a svolgere quello che serve senza avere grossi problemi di allocazione?
Tengo occupato solo gli oggetti che mi servono (senza alloccare un array intero) e mano a mano che “scadono” li tolgo dalla lista.

Quindi mano a mano che mi arrivano la richiesta faccio una Add alla lista, e all’inizio del loop ciclo gli elementi che ho nella lista per Size e se è stato superato il tempo per ognuno li rimuovo con il Remove.

Per spiegarmi meglio:

Mi arrivano 3 richieste:
#uscita, #tempo di esecuzione
Out1, 250ms
Out2, 100ms
Out3, 150ms

mentre le processo le aggiungo alla lista e attivo l’uscita (salvandomi il millis di avvio).

Ad ogni loop verifico se le uscite hanno raggiunto il tempo necessario e di conseguenza le rimuovo dalla lista.
Nel mio caso quindi verrà rimosso prima il secondo elemento, poi il terzo e alla fine il primo, mentre nel frattempo verranno accodate altre richieste in fondo alla lista.

Se riesco a lavorare cosi, senza dovermi ciclare un array intero di 100 elementi ma solo quelli presenti in lista e se non ho problemi di allocazione per me sarebbe il top.

Che dici, è fattibile? :slight_smile:

Mi sa che sei andato troppo sul tecnico smiley-yell

Ok, pensa che mi sono limitato fortemente e il motivo è che l’argomento non è banale.
Il problema inoltre non è solo funzionale, ma anche informatico, nel senso che la funzionalità e la logica di ciò che vuoi ottenere rappresenta la parte funzionale, mentre il problema informatico è scrivere la parte funzionale in modo che non possa rompersi sul più bello, aggirando al contempo le limitazioni dell’hardware.

La libreria LinkedList di cui sopra io non la conosco nel dettaglio, gli ho dato una occhiata veloce ed è più o meno il massimo che si possa ottenere in C++, dove la allocazione/deallocazione continua alla lunga può portare al malfunzionamento.

Oltre alla lista lincata di cui sopra ti serve un contenitore di dati con dentro:

int OutIndex // indice che indica qual’è l’uscita coinvolta
unsigned long OutTimeOn // millis() di quando è stata eseguito l’ON dell’uscita
int Timeout // tempo in millisecondi che mi indica dopo quanto impostare ad OFF l’uscita
bool OutState // true se ON, false se OFF

Un esempio di contenitore dati è la struct (o class)

struct SoftTimer {
    int OutIndex;
    unsigned long OutTimeOn; 
    int Timeout;
    // ecc
};

La linked list la si crea con qualcosa di simile a:
LinkedList myLinkedList = LinkedList();
e si usano i metodi
add, remove ecc.

Tuttavia non avendo studiato nel dettaglio la libreria in questione ne conoscendo nel dettaglio il problema
non mi sento di suggerirti altro.

PS: La libreria in questione usa i template C++.

Ciao.

Ok, cercherò di fare qualche prova con questa liberia :slight_smile:

Grazie ancora per le dritte! :wink:

anche usando solo int (+ int puntatore prossimo elemento) dubito che riuscirai mai ad avere 100 elementi.

IMHO la suluzione migliore è salvare questa “lista” su una SD, con nome file == tempo di esecuzione (+ un indice incrementale per eviutare duplicati), però appesantisci assai la CPU.

lesto:
anche usando solo int (+ int puntatore prossimo elemento) dubito che riuscirai mai ad avere 100 elementi.

IMHO la suluzione migliore è salvare questa "lista" su una SD, con nome file == tempo di esecuzione (+ un indice incrementale per eviutare duplicati), però appesantisci assai la CPU.

Mi sembra molto grezza come cosa no?
Inoltre il tempo di lettura e scrittura su sd non è molto piu elevato per un'operazione che fa dei millisecondi la chiave di tutto?

grezza? hai 2kB di ram, senza contare i 128 byte per la seriale, da condividere con stack e variabili globali della lib arduino, che essendo C++ non è che ci vada piano.. diciamo che 180byte partodo di base.

L'esempio webServer occupa di sola memoria sttaica 540byte (quando compili il progetto, l'ide 1.5.X ti dice la ram disponibile), ovvero il 26%, considerando che lasciare 20/25% librero per stack e globali è necessario, ti riamngono 1000byte circa, 10 byte ad elemento, senza considerare frammentazione della ram.

Se non vuoi litigare con la ram piena e frammentazione, l'unica è allocare staticamenta la ram o meglio ancora "svuotare" il possibile su disco. Sinceramente io avevo programmi che si impallavano dichiarando arrau unsigned long di 100 elementi, (400 byte contigui)

millisecondi per SPI sono niente, essendo accelerata in HW fa parecchi kB/s, le chiavi puoi tenerle in memoria, e solo i dati più "tozzi" sulla SD

Annammo bene. :cold_sweat:

Vedrò di fare qualche prova e in caso terrò in considerazione il discorso sd.
Sono anche su arduino yun quindi probabilmente la ram disponibile è ancora minore calcolando il bridge.. ma forse riesco ad inventarmi qualcosa lato linux..

ahhhh la yun usa un 8u32 che appunto mi pare avere 32kB, ma come hai detto usa più per il bridge. La comunicazione seriale con linux, anche se pompata a 1.000.000 baud (100.000Byte/s) è più lenta di una SPI, ma in compenso la RAM lato linux è molto capiente e quindi non avresti tempo perso per gestione filesystem etc...

In oltre, SE il bridge usa meno (e ne sono quasi sicuro) meno della librerie Ethernet (lo smazzo ethernet/wifi avviene lato linux) allora forse riesci a stare in RAM.

o meglio ancora, fai TUTTO lato linux, e usi arduino solo come peendice per piccole operazioni e/o input/output con i pin.

lesto:
ahhhh la yun usa un 8u32 che appunto mi pare avere 32kB, ma come hai detto usa più per il bridge. La comunicazione seriale con linux, anche se pompata a 1.000.000 baud (100.000Byte/s) è più lenta di una SPI, ma in compenso la RAM lato linux è molto capiente e quindi non avresti tempo perso per gestione filesystem etc...

In oltre, SE il bridge usa meno (e ne sono quasi sicuro) meno della librerie Ethernet (lo smazzo ethernet/wifi avviene lato linux) allora forse riesci a stare in RAM.

o meglio ancora, fai TUTTO lato linux, e usi arduino solo come peendice per piccole operazioni e/o input/output con i pin.

Si il problema è che in piu ho anche una libreria ulteriore per gestire 2 espansioni da 64 I/O.
Volevo cercare di evitare la parte linux in quanto volevo fare un sistema il piu "versatile" possibile nel caso si usasse un'altro arduino invece dello yun (che non so ancora se si comporterà molto bene, dati i noti problemi di lentezza del bridge).
Per ora ho bypassato tutta la parte di rest api accedendo direttamente ad /arduino/ e sembra essere abbastanza veloce.
Una volta messo tutto insieme sarà un bel malloppo, quindi devo già prepararmi a gestire eventuali problemi di memoria.
C'è pero anche da dire che i 100 elementi era il caso piu estremo, devo controllare luci e tapparelle di un'impianto domotico per sostituire quello che ho già, e piu di 64 uscite non conto di controllarne, e anche che siano tutte attivate in contemporanea la vedo molto difficile, vuol dire che cmq impiego meno di 250ms per effettuare 64 request dal server rack, conto tutto sulla lentezza :grin:

mm niente da fare mi sa con le linkedList, funziona bene le prime volte, poi dopo un po comincia a perdere colpi.
Sembra che l'int dell'output che viene salvato sulla likendList quando crea l'oggetto poi quando viene riletto per distruggerlo abbia un valore che non centra niente (o zero o altri valori alti) e quindi non spegne piu le uscite perchè ovviamente l'index dell'output non centra niente.
Ad occhio sembra un problema di memoria, anche se per ora sto lavorando con 10 uscite che non mi sembra granchè!
Dopo un centinaio di chiamate poi lo sketch non mi risponde piu (mi risponde solo la parte linux).

Ho provato il discorso del filesystem, utilizzando la shell (runShellCommand) mi faccio dare l'elenco di file creati uno per ogni uscita e cancello il file nel caso in cui ha superato il tempo di timeout.
Purtroppo la funzione è molto lenta anche solo a fare il comando "ls" (tra i 150 e i 500ms, mentre da ssh è immediata), e lanciando le get http in sequenza con la stessa durata per ogni comando (250ms) praticamente vedo accendersi e spegnersi ogni led uno alla volta, mentre dovrebbero accendersi e spegnersi insieme quasi simultaneamente.

Quindi praticamente.. con le linkedList mi si intorta la scheda, con il filesystem è troppo lento.

Altre idee? :astonished:

Non mi è chiara la funzionalità che vuoi implementare. Per adesso io ho intuito che da connessione HTTP vuoi fornire dei dati e registrarli in un contenitore per poi processarli. I dati saranno interpretati e visti come un soft timer attaccato ad una azione, ci sarà una azione di ingresso diversa per ogni timer ecc, ma sono solo cose che ho intuito di cui non ho certezza.

Se potessi scrivere uno state case lo userei per scrivere codice di test, se ti va, io ti faccio le domande e tu rispondi e lo state case provo a scriverlo io, senza impegno.

Si parte semplificando:
Rimuovi tutto ciò che è dinamico e scrivi un codice di test che elabora 3 elementi timer a cui ad ogni timer c'è connessa una o più azioni. Minimizza le possibilità di insuccesso, rendi il codice funzionante staticamente e annota delle osservazioni.

State START: {
che deve fare il codice?

  1. Attende dati da HTTP. (1) Salta la fase dinamica, crea staticamente i dati in un contenitore
  2. Problema: come colleziono i dati? Cerca soluzione migliore es struct o class. (ring buffer, linked list ecc)
    Scrivi e testa una sola struct (o class) timer.
    }

Punto 2:
Serve una struttura dati, delle funzioni, per implementare delle azioni temporizzate basate su millis()
Descrivi nel dettaglio tutti i dati i tipo ecc, fino a creare un timer.

Sono cosciente che non sai come affrontare la cosa, e che ti sembrerà di non arrivare mai alla soluzione.

In un mio programma uso queste funzioni e macro, per temporizzare le azioni:

/* crea una variabile static (static_timer) per ospitare il valore di millis, se questa vale 0 (zero) 
     inizializza il timer con il valore di restituito da millis()
*/
#define new_timer(static_timer)\
    static uint32_t static_timer = 0;\
    if(!static_timer) static_timer = millis();

/*
    Restituisce true se il tempo è scaduto, viceversa false
*/
bool timer_has_expired(uint32_t *const static_timer, const uint32_t time);

bool timer_has_expired(uint32_t *const static_timer, const uint32_t time)
{
    if ( (millis() - *static_timer) >= time) {
        *static_timer = 0;    // azzera il timer, così il timer viene reinizializzato
        return TRUE;
    }
    return FALSE;

}

void loop() 
{
    new_timer(myTimer);
    if (timer_has_expired(&myTimer, 100) {
          // ... your code         
    } 
    
    new_timer(myTimerA);
    if (timer_has_expired(&myTimerA, 250) {
         // ... your code
    }  
}

Ciao.

Oddio non so come altro spiegarmi. :grin:

Ti posto il codice che ho già scritto (la versione linkedList e la versione con shell linux) cosi forse capisci meglio. :slight_smile:
(guarda la versione linkedList per rimanere su quanto avevamo detto ieri).

Io invio N richieste ad arduino da uno script php:

// Uscite in sequenza
$url = "http://192.168.220.94/arduino/out/10/4";
$command = get_url($url);

$url = "http://192.168.220.94/arduino/out/11/3";
$command = get_url($url);

$url = "http://192.168.220.94/arduino/out/12/2";
$command = get_url($url);

$url = "http://192.168.220.94/arduino/out/13/1";
$command = get_url($url);

$url = "http://192.168.220.94/arduino/out/15";
$command = get_url($url);


$url = "http://192.168.220.94/arduino/out/2/4";
$command = get_url($url);

$url = "http://192.168.220.94/arduino/out/3/3";
$command = get_url($url);

$url = "http://192.168.220.94/arduino/out/4/2";
$command = get_url($url);

$url = "http://192.168.220.94/arduino/out/5/1";
$command = get_url($url);

$url = "http://192.168.220.94/arduino/out/7";
$command = get_url($url);

L’arduino accetta una stringa del tipo:
/arduino/out/NUM_USCITA/TEMPO_IN_SEC

Se il tempo in secondi non c’è, usa 250ms.

Ciò vuol dire che le uscite devono venire attivate in sequenza, ed in base al timeout passato devono essere disattivate, in questo caso vedrò apparire l’uscita 10, 11, 12, 13, la 15 farà un lampeggio e poi si spegneranno in sequenza 13,12,11,10 mentre si attivano le successive nello stesso modo.

Il codice gira, le uscite si attivano ma sembra che quando fa il ciclo for per azzerarsi dopo un po le uscite salvate sulla LinkedList assumano valori che non centrano niente.

Risultato da console quando gira bene:
NB: Nella console gli output sono quelli passati via http-1 perchè ovviamente nelle request la prima uscita è la numero 1, mentre all’interno del programma è la posizione 0.

9: HIGH
1
10: HIGH
2
11: HIGH
3
12: HIGH
4
14: HIGH
5
1: HIGH
6
14: LOW
5
12: LOW
4
2: HIGH
5
11: LOW
4
3: HIGH
5
10: LOW
4
4: HIGH
5
9: LOW
4
6: HIGH
5
6: LOW
4
4: LOW
3
3: LOW
2
2: LOW
1
1: LOW
0

(il valore intermedio indica quanti elementi contiene la linkedList dopo ogni operazione HIGH/LOW)

Risultato da console quando si intorta:

9: HIGH
1
10: HIGH
2
11: HIGH
3
12: HIGH
4
14: HIGH
5
14: LOW
4
1: HIGH
5
12: LOW
4
223: LOW
3
2: HIGH
4
11: LOW
3
127: LOW
2
3: HIGH
3
127: LOW
2
10: LOW
1
4: HIGH
2
30: LOW
1
9: LOW
0
6: HIGH
1
6: LOW
0

Come vedi qui alcuni output presi dalla linkedList assumono valori che non centrano niente, l’uscita non si disattiva, la lista si svuota e rimangono erroneamente delle uscite attive.

yun_centipede_webserver_linkedList.ino (3.41 KB)

yun_centipede_webserver.ino (4.25 KB)

ehmm il problema con le linked pare un errore o di puntatori o di ram in overflow, per; senza codice [ difficile da dire.

Secondo me, l'uso della memoria dinamico ti crea problemi, usa un array, tanto per cosa altro vuoi usare quella ram che libereresti?