Per trasferire file

Dicevo: per trasferire file e non intasare la discussione madre

https://forum.arduino.cc/t/la-pappa-e-pronta/598251/57

Ho aperto questa
E @gpb01 copiare qui i giusti messaggi

E per stasera ho finito, che Angelo Poretti mi ha stroncato

Ammettiamo ora di voler copiare un file, da un arduino ad un'altro
usando un qualsiasi canale di comunicazione

per il momento mi sono dedicato al lavoro facile:
ovvero copiare un file in lettura inteso come stream, su uno stream inteso invece come file in scrittura

ecco quello che ho fatto, anche se il goal non è stato ancora ottenuto ma arriverà

per prima cosa ho copiato uno stream in un'altro

a questo livello si parla di stream, non di file
i corrispondenti file devono essere già aperti e pronti

size_t streamcopy(Stream * sorgente, Stream * canale, byte modo = 0, byte led = 0)
{
   // puntatore a stream sorgente, puntatore a stream destinazione, modo di trasmissione, led che deve lampeggiare
   // legge de uno stream preventivamente aperto
   // copiandolo in uno stream di comunicazione
   // anch'esso preventivamente aperto
   // restituisce il numero di byte trasferiti
   // l'argomento opzionale modo permette di usare in futuro canali cifrati o con correzione d'errore
   size_t trasmessi = 0;

   if (led)
   {
      pinMode(led, OUTPUT);
   }

   if (modo == 0)
   {
      // banale ma sempre efficace
      while (sorgente->available())
      {
         while (canale->availableForWrite())
         {
            int c = sorgente->read();

            if (c == -1)
            {
               // no data, break
               break;
            }

            trasmessi = trasmessi +  canale->write(c);
            //
         }

         // canale di uscita pieno, lo svuoto
         canale->flush();

         if (led)
         {
            digitalWrite(led, !digitalRead(led));
         }
      }

      // finita copia
      //flusso il canale
      canale->flush();

      // se serve spengo il led
      if (led)
      {
         digitalWrite(led, 0);
      }
   }

   return trasmessi;
}

come vedete è completamente "agnostico": non presume la quantità di dati da trasferire, non sa di che tipo siano i flussi sui quali sta lavorando, prevede anche che per errore il flusso abbia available() alto, ma in realtà sia vuoto (succede con alcuni File System mal realizzati)
restituisce un size_t coi byte realmente trasferiti
Si potrebbe fare meglio, se si potessero fare assunzioni sulla dimensione del buffer o sulle velocità di svuotamento o simili, ma non sarebbe più agnostico

per proseguire apro due file, uno in lettura e uno in scrittura e ne passo gli indirizzi degli stream alla funzione di copia


void filecopy(char * file1, char * file2)
{
   // apro il file da trasmettere
   File sorgente = SD.open(file1, FILE_READ);

   if (!sorgente)
   {
      Serial.print("Gestione errore in apertura ");
      Serial.println(file1);

      while (1);
   }

   // apro il file dove ricevere
   // preventivamente vuotato
   SD.remove(file2);
   File destino = SD.open(file2, FILE_WRITE);

   if (!destino)
   {
      Serial.print("Gestione errore in apertura ");
      Serial.println(file2);

      while (1);
   }

   // 'operazione fisica di trasferimento
   Serial << "trasferiti: " << streamcopy(&sorgente, &destino) << '\n';
   Serial << "valore nominale: " << sorgente.size() << '\n';
   // chiudo i file
   destino.close();
   sorgente.close();
   Serial << "operazione terminata, copiato da: " << file1 << " su: " << file2 << '\n';
}

Naturalmente per poterla usare su una UNO serve di collegare la scheda SD, inizializzarla ed avere un file da copiare...

ecco quindi il preambolo e la setup()

// di Nelson-StandardOil
// IDE 1.8.10
// Progetto:
// Scopo: traferire un file da un arduino ad un'altro
// Data inizio lavori: 17/02/2025

#include "nelson.h"
#include <SD.h>

// nome file sorgente
#define SORGENTE "DATALOG.TXT"
// nome file destinazione
#define DESTINO "COPIA.TXT"
void setup(void)
{
   // Apro la seriale
   Serial.begin(9600);
   Serial.print("Inizializzo la scheda");

   /* parametri di default per una UNO:
   ** MOSI - pin 11
   ** MISO - pin 12
   ** CLK - pin 13
   ** CS - pin 4
   */

   if (!SD.begin(10))
   {
      Serial.println("Inizializzazione scheda fallita");

      while (1);
   }

   Serial.println("Inizializzazione scheda riuscita");
   filecopy(SORGENTE, DESTINO);
}

void loop(void)
{
}

ora, quale sarà il prossimo passo?

naturalmente duplicare il programma su un secondo arduino e usare "in mezzo" la seriale come stream, nel trasmettitore come stream di uscita, nel ricevitore come stream di ingresso

e poi una maniera per scambiarsi i nomi di file da trasferire
e un controllo degli errori
e un protocollo di correzione degli errori
e un protocollo di cifratura

tutte cose che avevo pensato di implementare come "filtri" da interporre sugli stream in uso
come dire un oggetto stream che in ingresso è non cifrato, ma in uscita lo è

che ne pensate?
chi mi vuole aiutare?

PS
fin qui è tutto provato funzionante, non venitemi a dire che non va...

a me potrebbe interessare...

Grazie
Scalda il cuore sapere di essere letto

Penso di aver scritto, con l'ultimo programma, una mezza porcheria
È bloccante e non può andare in full duplex
Quindi non accetterebbe ne ACK ne ritrasmissione e nemmeno un minimo di handshaking per evitare overload dei buffer di ricezione all'altro capo

Tu hai idee?

Ah, una domanda per la moderazione:
È forse meglio se di queste cose discutiamo in un altro trhead e qui mettiamo solo i risultati?

1 Like

Mah ... visto che si discute di una cosa qui messa, forse, per non disperdere troppo, meglio continuare qui ... :thinking:

Guglielmo

pensavo di averlo perso... :grin:

@Standardoil : meglio definire prima le caratteristiche che deve avere...tipo:

  • non bloccante?...immagino di si
  • master_slave o per priorità (il primo che invia il comando decide se invia o richiede)?
  • per definire l'ordine di "richiedente" e "ascoltatore" seriale o fisico (pin) ?
  • ...di conseguenza come rilasci la risorsa?
  • il controllo della qualità di quanto inviato lo fai "periodicamente" (ogni tot byte) o alla fine?
  • se il file inviato risulta "corrotto" lo invii di nuovo ? ...se fai controllo periodoco puoi limitarti ad inviarrre il solo pezzo "corrotto"...magari è na fesseria.
    -...varie ed eventuali

Rispondo in ordine sparso

Non è per nulla una fesseria

Direi che numeriamo i blocchi e si ritrasmettere solo il blocco fallato

Avevo affrontato qualcosa di simile in passato, anche se l'ambito era diverso e in quel caso si trattava di trasmettere immagini con un client WiFi.
Ad ogni modo, si tratta comunque di uno stream e quindi restano validi gli stessi concetti.

Secondo le prove che feci a suo tempo, l'approccio byte a byte per la trasmissione non è vantaggioso sia in termini di velocità che di scalabilità del protocollo.

Io ottenni i risultati migliori dividendo il file in pacchetti di dimensione fissa e poi inviandoli con un algoritmo tipo questo (data e size, sono gli argomenti della funzione).

        uint16_t index = 0;
        uint16_t numBlock = trunc(size / BLOCK_SIZE);
        uint16_t lastBytes = size - (numBlock * BLOCK_SIZE);

        for (index = 0; index < numBlock ; index++)
        {
            client->write((const uint8_t *)data + index * BLOCK_SIZE, BLOCK_SIZE);
        }
        client->write((const uint8_t *)data + index * BLOCK_SIZE, lastBytes);

Dividendo lo stream in pacchetti inoltre è più semplice implementare l'handshaking, la gestione degli errori e risolvere anche il problema del nome del file in quanto puoi differenziare il tipo di "telegramma" inviato e aggiungere anche il numero del pacchetto anche se io ritrasmetterei subito il blocco in caso di errore invece di ricostruire tutto alla fine: con pochi dati un semplice e veloce CRC8 può essere sufficiente o al massimo si può usare un CRC16 con tabella di lookup che è comunque veloce.

<START> <TYPE> <COUNT> <LEN0> <LEN1> <........ PAYLOAD ........ > <CRC> <STOP>

Interessante.
Ho scritto per avere le notifiche se ci fossero sviluppi.

Basta usare il "Tracking" qui sotto...

avevo pensato proprio ad una struttura del tipo da te indicato...vedo che il mio cervellino funziona...sono contento :grin:

Ci saranno sviluppi contaci

E adesso rispondo a tutti:

A dire la verità sono un poco spaventato
Sono anni che faccio tutto da solo e adesso pensare di lavorare in team mi mette ansia

Ma lo ho proposto e cercato io, e quindi devo superarla

Comunque pensavo a trasmettere per blocchi
Non c'è il rischio di rendere non reattivo il sistema ?
Intendo rispetto ad eventuali intasamenti del buffer lato ricevitore

Io del tuo cervello non ho mai dubitato

Direi che in linea di massima dipende dal sistema.
Una MCU "piccola" senza dubbio non avrebbe tempo e risorse per fare altro, ma con quelle più moderne, si potrebbe far girare un task manager (come l'arci-noto FreeRTOS) e dividere l'invio dei dati in blocchi lascerebbe più spazio al task manager per gestire gli slot temporali assegnati a ciascun task.

Fatto, ed ho ripulito dai messaggi che ora non servono più.
Buona continuazione :slight_smile:

Guglielmo

Potresti strutturare l'invio del file con una trama semplice ad esempio:
Arduino TX

[1byte][0x01<numero byte 1>]
[2byte][0x02<numero byte 2>]
...
[CRC32]
fine

alla fine verifichi il CRC32 di TX con quello che ti sei calcolato su ARDUINO RX

e questo sarebbe il metodo più semplice...e sicuramente funziona...però, penso, l'intenzione e/o l'idea iniziale sia quella di rendere, per l'utilizzatore, la questione più semplice e generica possibile lasciando al sottobosco (funzione nel loop) gestire invio, ricezione e decodifica messaggio.
sto lavorando ad una Classe dove i metodi pubblici sono una cosa tipo:

clearForNewQuery(void);
setFileName(char* _fileName)
setQueryType(QUERY _type)
sendQuery(void)

da chiamare all'esigenza; mentre nel loop girerebbe una:

event(void)

che gestisce "ascolto" ed "invio" del messaggio.
sempre a livello pubblico avrei i:

    Trasferitore(Stream& _source, Stream& _flow); // costruttore con sorgente ed invio

    STATE getEventState(void); // ritorna lo stato del flusso
    ERR getEventError(void); // ritorna eventuale errore

come dicevo nel sottobosco gestire, come logica, l'invio della prima richesta con tipo richiesta e nome "file"...quindi so se riceverò qualche cosa o devo inviare qualche cosa...risposta di ritorno con "ok" o "pacchetto" o "errore"...contro risposta di "ok" o "fail" (il rispondente invia di nuovo stesso pacchetto) etc...fino a chiusura flusso.
per non rendere "bloccante" l'invio del payload pensavo di inviare un byte per ogni ciclo del programma...comunque con dimensione massima impostabile a seconda della scheda utilizzata.
penso di essere circa a metà strada, forse un po' di più, da quello che ho in mente come risultato finale...appena riesco posto qualche cosa che cicla...

ovviamente sono aperto a confronto e consigli

Siamo pienamente d'accordo

Fa piacere
Finora con le mie idee mi sentivo sempre isolato...

Questo significherebbe che la trasmissione sia ferma dopo ogni pacchetto per attendere la conferma

Però così la trasmissione è la ricezione sarebbero stateless e si risparmierebbe memoria

Grazie

1 Like

Una risposta più generale

Sto combattendo la mia ansia

Sembrerà strano ma ho ansia di non riuscire
E questo mi blocca