Mi proyecto es crear un servidor Web con el “Arduino UNO” (RS 715-4081 [1]) y el “ETHernet shield with SD card” (RS 715-4072 [2]).
En una primera búsqueda encontré el tutorial:
http://www.ladyada.net/wiki/tutorials/learn/arduino/ethfiles.html
donde propone el código ofrecido aquí:
https://github.com/adafruit/SDWebBrowse/blob/master/SDWebBrowse.ino
a partir del cual empecé a realizar modificaciones.
En este código se emplean la librería SdFat.h. Esta librería no aparece en el sistema de desarrollo en su versión para Windows 32bits. La obtengo desde:
http://sdfatlib.googlecode.com/files/sdfatlib20111205.zip
siendo el listado de las librerías:
http://code.google.com/p/sdfatlib/downloads/list
Ese código permite listar el directorio raiz de la tarjeta y abrir ficheros listados en ese directorio.
La idea es ser capaz de mostrar el árbol completo de la tarjeta con sus enlaces bien construidos en HTML para el acceso a cualquier fichero independientemente de la profundidad.
Una vez hecho esto, si el fichero es un fichero subdirectorio, se muestra el directorio con los enlaces adecuados, y si se trata de un fichero, se envia como respuesta HTML al cliente que lo solicita.
Realmente un servidor web estático (sin peticiones que tenga que evaluar) lo unico que hace es enviar un fichero. El navegador que recibe el fichero es el que averigua si necesita más piezas y las pide de una en una.
Las mejoras de mi código suponen lo siguiente:
A partir de aquí se puede mejorar la rutina de lectura de la linea de petición, para detectar no solo el nombre del fichero solicitado sino también argumentos.
Fichero ino:
sketch_mar21c004WebServerSD_015t.ino
Fichero ino comprimido:
sketch_mar21c004WebServerSD_015t.zip
Fichero SdBaseFile.cpp modificado:
SdBaseFile.cpp (Path: arduino-1.0/libraries/SdFat)
Fichero SdBaseFile.cpp modificado comprimido:
SdBaseFile.zip (Path: arduino-1.0/libraries/SdFat)
En fichero DOC: Proyecto Servidor web 15m.doc
Versiones anteriores:
Fichero ino:
sketch_mar21c004WebServerSD_015m.ino
Fichero ino comprimido:
sketch_mar21c004WebServerSD_015m.zip
Fuente:
Alberto Seva WebServerSD.015t
*--------------------------------------- Cortar por aquí ---------------------------------------*
/*
* Alberto Seva WebServerSD.015t
*
* http://dfists.ua.es/~alberto/arduino/arduino.htm
*
*
* Mi proyecto es crear un servidor Web con el “Arduino UNO” (RS 715-4081[1]) y
* el "ETHernet shield with SD card” (RS 715-4072[2]).
*
* [1] http://es.rs-online.com/web/p/products/715-4081/
* [2] http://es.rs-online.com/web/p/microcontrolador-procesador/7154072/
*
* A partir del sketch 'SDWebBrowse.ino', las mejoras de mi código suponen
* lo siguiente:
*
* * Los nombres de fichero 8.3 que el sistema SdFat es capaz de encontrar,
* se guardan en el formato correspondiente a la página de códigos de
* MSDOS cp850. Se debe especificar esa página de códigos para que los
* enlaces se puedan reconstruir.
* * Los nombres 8.3 normalmente no deberían tener caracteres por encima
* del 0x7F, pero en algunas de mis tarjetas he descubierto que no es así.
* Surge el problema de que los enlaces con caracteres por encima del 0x7F
* enviados en los enlaces con formato cp850, regresan con el formto UTF-8
* expresado como %C2 ó %C3 y a continuación otro código con el formato
* %XX. Esos códigos hay que convertirlos primero a un carácter
* equivalente, y luego mediante una tabla totalmente arbitraria, al
* carácter correcto en cp850 que sirva en el proceso de búsqueda del
* nombre del fichero.
* * En el listado de directorio, la rutina es capaz de detectar las entradas
* de nombre largo y escribirlas como información aunque sin ninguna opción
* de búsqueda real.
* * En la petición de ficheros, se intenta mandar información del
* content-type de manera correcta. El HTML supone programas muchas cadenas
* constantes que hay que almacenar en la zona de programa para que el
* programa no se quede sin memoria.
* * La transmisión de ficheros usando un buffer, mejora muchísimo el
* rendimiento del servidor web.
*
* Se ha probado con el Arduino UNO y con el MEGA 2560 (RS 715-4084 [3].
* Como intento que funcione en el primero, hay unos trucos de ahorro de
* memoria compartiendo el buffer de entrada.
*
*
* [3] http://es.rs-online.com/web/p/microcontrolador-procesador/7154084/
*/
/*
* Some code is from Bill Greiman's SdFatLib examples, some is from the
* Arduino Ethernet WebServer example and the rest is from Limor Fried
* (Adafruit) so its probably under GPL
*
* Tutorial is at http://www.ladyada.net/learn/arduino/ethfiles.html
* Pull requests should go to http://github.com/adafruit/SDWebBrowse
*/
/*
// Para que funcione y busque los caracteres acentuados, debe modificarse una librería.
// SdBaseFile.cpp, linea 418 pone:
// if (i > n || c < 0X21 || c > 0X7E)goto fail;
// debe cambiarse a:
// if (i > n || c < 0X21)goto fail;
*/
#include <SdFat.h>
#include <SdFatUtil.h>
#include <Ethernet.h>
#include <SPI.h>
// Las cadenas constantes se ponen en memoria de programa
#define P_P(x,y) print_P(x,PSTR(y));
#define Pln_P(x,y) println_P(x,PSTR(y));
// Descomentar para no mostrar la versión
#define VERSION "Alberto Seva WebServerSD.015t"
// Desconectar para no mostrar los atributos en el listado de directorios
// Se pueden añadir condiciones para no ver determinados tipos de ficheros
#define ATRIBUTOS
// Descomentar para ver los caracteres leidos en HEX para cada byte de la entrada
// de directorio.
//#define DEBUG_FileOpenClose
//#define DEBUG_VerFiledirHex
//#define DEBUG_VerDatosTarjeta
//#define DEBUG_VerTreeTarjeta
//#define DEBUG_VerConversionUTF_8
//#define DEBUG_VerCambiosPath
//#define DEBUG_NombreLargo
//#define DEBUG_CabeceraHTML
// http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1284593828
/************ ETHERNET STUFF ************/
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 1, 14 };
//byte ip[] = { 172, 17, 34, 50 };
//byte gateway[] = { 192, 168, 1, 1 };
//byte subnet[] = { 255, 255, 255, 0 };
EthernetServer server(80);
/************ SDCARD STUFF ************/
Sd2Card card;
SdVolume volume;
SdFile root;
SdFile dir;
SdFile file;
// Buffer multiusos:
// - Para Transferencia de ficheros en bloques
// - Para Path en enlaces a ficheros al mostrar el directorio
// - Buffer de recepción de la orden
// Atención, una vez obtenido el nombre del fichero, se emplea para transferencia del propio fichero
// o para gestion del nombre si es directorio
#define SIZEBUFFER 128 // 256 38s,
char MiBuffer[SIZEBUFFER];
#define BNL_FILAS 6
// Nombre largo
// Como aparecen las entradas desde atrás hacia alante, se rellena desde atras
// y luego se lee como unicode
// unsigned int 2 bytes para unicode
int BnlFila_i=0; // -1 si nombre largo mal
boolean BnlSi=false; // Hay nombre largo
char Bnl[BNL_FILAS+1][26];
// Para nombre 8.3 el vista de subdirectorios
char * FN83=&(Bnl[BNL_FILAS+1][2]); // Reserva del 2 al 14 (13 bytes; 8+'.'+3+'\0')
// store error strings in flash to save RAM
#define error(s) error_P(PSTR(s))
void error_P(const char* str) {
PgmPrint("error: ");
SerialPrintln_P(str);
if (card.errorCode()) {
PgmPrint("SD error: ");
Serial.print(card.errorCode(), HEX);
Serial.print(',');
Serial.println(card.errorData(), HEX);
}
while(1);
}
void setup() {
// Ultimo caracter de buffer de nombre largo a 0
Bnl[BNL_FILAS+1][0]='\0';
Bnl[BNL_FILAS+1][1]='\0';
// Debug por Serial
Serial.begin(115200);
#ifdef VERSION
PgmPrint("\n");PgmPrintln(VERSION);
PgmPrint("Free RAM: ");
Serial.println(FreeRam());
#endif
// initialize the SD card at SPI_HALF_SPEED to avoid bus errors with
// breadboards. use SPI_FULL_SPEED for better performance.
pinMode(10, OUTPUT); // set the SS pin as an output (necessary!)
digitalWrite(10, HIGH); // but turn off the W5100 chip!
// initilize card
if (!card.init(SPI_HALF_SPEED, 4)) error("card.init failed!");
#ifdef DEBUG_VerDatosTarjeta
// print the type of card
PgmPrint("\nCard type: ");
switch(card.type()) {
case SD_CARD_TYPE_SD1:
PgmPrintln("SD1");
break;
case SD_CARD_TYPE_SD2:
PgmPrintln("SD2");
break;
case SD_CARD_TYPE_SDHC:
PgmPrintln("SDHC");
break;
default:
PgmPrintln("Unknown");
}
#endif
// initialize a FAT volume
if (!volume.init(&card)) error("vol.init failed!");
#ifdef DEBUG_VerDatosTarjeta
PgmPrint("Volume is FAT: "); Serial.println(volume.fatType(),DEC);
PgmPrint(" Bloques por cluster: "); Serial.println(volume.blocksPerCluster(),DEC);
PgmPrint(" Bloques por FAT: "); Serial.println(volume.blocksPerFat(),DEC);
PgmPrint(" Numero de clusters: "); Serial.println(volume.clusterCount(),DEC);
uint32_t volumesize;
volumesize = volume.blocksPerCluster(); // clusters are collections of blocks
volumesize *= volume.clusterCount(); // we'll have a lot of clusters
volumesize *= 512; // SD card blocks are always 512 bytes
volumesize /= 1024; // Kib
volumesize /= 1024; // Mib
PgmPrint(" Volume size (Mbytes): "); Serial.println(volumesize,DEC);
#endif
if (!root.openRoot(&volume)) error("openRoot failed");
#ifdef DEBUG_VerTreeTarjeta
// Recursive list of all directories
PgmPrintln("Files found in all dirs:");
root.ls(&Serial,LS_R | LS_DATE | LS_SIZE,1);
Serial.println();
#endif
PgmPrintln("Done");
// Debugging complete, we start the server!
Ethernet.begin(mac, ip);
server.begin();
}
// Escribir cadenas de content type.
// Las cadenas se almacenan en zona de programa y se escribe 2 veces
// Cabecera HTML (no se muestra en codigo fuente)
#define TXT 1
#define RAR 2
#define MP3 3
#define JPG 4
#define XLS 5
#define GIF 6
#define HTM 7
#define ZIP 8
#define PDF 9
#define TAR 10
void htmlLin(EthernetClient client,const prog_char * p) {
print_P(&client,p);
#ifdef DEBUG_CabeceraHTML
print_P(&Serial,p);
#endif
}
void htmlLinln(EthernetClient client,const prog_char * p) {
println_P(&client,p);
#ifdef DEBUG_CabeceraHTML
println_P(&Serial,p);
#endif
}
#define HTMLIN(x) htmlLin(client,PSTR(x))
#define HTMLINln(x) htmlLinln(client,PSTR(x))
void htmlCab(EthernetClient client,int tipo) {
HTMLINln("HTTP/1.1 200 OK");
HTMLIN("Content-Type: ");
switch (tipo) {
case TXT: HTMLINln("text/plain");
break;
case RAR: HTMLINln("application/x-rar");
break;
case MP3: HTMLINln("audio/mpeg");
break;
case JPG: HTMLINln("image/jpeg");
break;
case XLS: HTMLINln("application/xls");
break;
case GIF: HTMLINln("image/gif");
break;
case HTM: HTMLINln("text/html");
break;
case ZIP: HTMLINln("application/x-zip");
break;
case PDF: HTMLINln("application/pdf");
break;
case TAR: HTMLINln("application/x-tar");
break;
default: HTMLINln("application");
}
client.println();
}
#undef HTMLIN
#undef HTMLINln
// Para mostrar bien las cosas
void htmlStart(EthernetClient client) {
Pln_P(&client,"<html>");
Pln_P(&client,"<head>");
// Los nombres cortos 8.3 están guardados en cp850.
// Pln_P(&client,"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=windows-1252\">");
Pln_P(&client,"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=cp850\">");
Pln_P(&client,"</head>");
Pln_P(&client,"<body>");
}
// Para mostrar bien las cosas
void htmlEnd(EthernetClient client) {
Pln_P(&client,"</body>\n</html>");
}
void HTNLNotFound(EthernetClient client) {
Pln_P(&client,"HTTP/1.1 404 Not Found");
Pln_P(&client,"Content-Type: text/html");
client.println();
htmlStart(client);
Pln_P(&client,"<h2>File Not Found!</h2>");
htmlEnd(client);
}
// Entre
boolean Entre(char c,char cle, char cge) {
return ((cle<=c) && (c<=cge));
}
// How big our line buffer should be. 100 is plenty!
//#define BUFSIZ 100
#define BUFSIZ SIZEBUFFER
void loop()
{
// char clientline[BUFSIZ];
char * clientline = MiBuffer;
int index = 0;
EthernetClient client = server.available();
if (client) {
// an http request ends with a blank line
boolean current_line_is_blank = true;
// reset the input buffer
index = 0;
while (client.connected()) {
if (client.available()) {
char c = client.read();
// If it isn't a new line, add the character to the buffer
if (c != '\n' && c != '\r') {
clientline[index] = c;
index++;
// are we too big for the buffer? start tossing out data
if (index >= BUFSIZ)
index = BUFSIZ -1;
// continue to read more data!
continue;
}
// got a \n or \r new line, which means the string is done
clientline[index] = 0;
// Print it out for debugging
Serial.println(clientline);
// Look for substring such as a request to get the root file
if (strstr(clientline,"GET / ") != 0) {
// send a standard http response header
htmlCab(client,HTM);
// print all the files
htmlStart(client);
Pln_P(&client,"<h2>Files:</h2>");
Pln_P(&client,"<pre>");
PonRoot(); // Path para enlaces
#ifdef DEBUG_VerCambiosPath
PgmPrint("++Path: ");Serial.println(MiBuffer);
#endif
// ls(&root,&client,LS_R | LS_DATE | LS_SIZE,1);
ls(&root,&client,LS_DATE | LS_SIZE,1);
P_P(&client,"</pre>");
htmlEnd(client);
break;
}
// Caracteres %xx
int i=0,j=i,estado=0,bien=true;
if (strstr(clientline,"%") !=0 ) {
#ifdef DEBUG_VerConversionUTF_8
PgmPrint("++Caracteres especiales: ");Serial.println(clientline);
#endif
while (bien) {
if (estado==0) {
while ( (clientline[j]=clientline[i++]) != '\0' ) {
if (clientline[j]=='%') {estado=1;break;}
else j++;
}
if (clientline[j]=='\0') break;
}
// Como está en estado 1 mira si es uno o dos bytes
if (estado==1) {
// si es dos bites mira y guarda si es 2 ó 3
// si es 2 bytes empieza por C
if (clientline[i]=='C') {
i++;
// Debe seguir 2 ó 3
if (clientline[i]=='2') estado=2;
else if (clientline[i]='3') estado=3;
else {bien=false; break;}
i++;
// y finalizar con la cebecera del siguiente byte
if (clientline[i]!='%') {bien=false; break;}
else i++;
} else {
if (Entre(clientline[i],'0','7') && Entre(clientline[i+1],'0','F')) {
clientline[j]=(char)( ( (clientline[i++] & 0xF) * 0x10) + (clientline[i++] & 0xF) );
j++;
estado=0;
} else {
bien=false; break;
}
}
}
// Estado es 2 ó 3
if ( Entre(clientline[i],'8','F') && Entre(clientline[i+1],'0','F') ) {
// clientline[j]=( ( (clientline[i++] & 0xF) * 0x10) + (clientline[i++] & 0xF) );
clientline[j]=clientline[i++];
clientline[j]&=0xF;
clientline[j]*=0x10;
clientline[j]+=(clientline[i++] & 0xF);
if (estado==3) clientline[j]|=0x40;
UTF_8acp850(&(clientline[j]));
j++; estado=0;
} else {
bien=false; break;
}
} // while
if (!bien) {
PgmPrint("++Caracteres especiales: Ha fallado");
HTNLNotFound(client);
}
#ifdef DEBUG_VerConversionUTF_8
PgmPrint("++Caracteres especiales: ");Serial.println(clientline);
#endif
}
// La cadena clientline está para extraer nombres de fichero
if (strstr(clientline,"GET /") != 0) {
// this time no space after the /, so a sub-file!
char *filename;
filename = clientline + 4; // look after the "GET /" (4 chars)
// a little trick, look for the " HTTP/1.1" string and
// turn the first character of the substring into a 0 to clear it out.
(strstr(clientline," HTTP"))[0] = 0;
// print the file we want
Serial.println(filename);
#ifdef DEBUG_VerConversionUTF_8
// debug de nombre
for (int j=0;filename[j]!='\0';j++) {
Serial.print((char)filename[j],HEX);Serial.print(' ');
}
Serial.println();
#endif
if (! file.open(&root, filename, O_READ)) {
#ifdef DEBUG_VerConversionUTF_8
PgmPrint("++Caracteres especiales: No encontrado");
#endif
HTNLNotFound(client);
break;
}
#ifdef DEBUG_FileOpenClose
PgmPrintln("Opened!");
#endif
// Si es directorio, muestra el árbol descendente.
// Si es fichero lo traslada (write)
if (file.isDir()) {
htmlStart(client);
P_P(&client,"<h2>Files: ");
client.print(filename);
Pln_P(&client,"</h2>");
Pln_P(&client,"<pre>");
// Atención: aqui hay un truco. Se copia el filename al principio del mismo buffer.
// El puntero filename no debe volver a usarse.
PonPath(filename); // Path para enlaces
#ifdef DEBUG_VerCambiosPath
PgmPrint("++Path: ");Serial.println(MiBuffer);
#endif
// ls(&file,&client,LS_R | LS_DATE | LS_SIZE,1);
ls(&file,&client,LS_DATE | LS_SIZE,1);
Pln_P(&client,"</pre>");
htmlEnd(client);
file.close();
break;
}
// Es fichero a descargar
#ifdef DEBUG_FileOpenClose
PgmPrintln("A por la extension.");
#endif
char * extension;
int i=0;
while (filename[i++]!='\0');
#ifdef DEBUG_FileOpenClose
PgmPrint("LongFilename: ");Serial.println(i);
#endif
while (i>0 && filename[i]!='.') i--;
if (i>0) {
extension=&(filename[++i]);
#ifdef DEBUG_FileOpenClose
PgmPrint("Extension: ");Serial.println(extension);
#endif
#define Ve3Z(x,y,z,t) Serial.print(x[0]);Serial.print(x[1]);Serial.print(x[2]);Serial.write(' ');Serial.write(y);Serial.write(z);Serial.write(t);Serial.println()
Ve3Z(extension,'H','T','M');
#define Ve3(x,y,z,t) (((x[0]&0xDF)==y) && ((x[1]&0xDF)==z) && ((x[2]&0xDF)==t))
if Ve3(extension,'T','X','T') htmlCab(client,TXT);
else if Ve3(extension,'R','A','R') htmlCab(client,RAR);
else if Ve3(extension,'M','P','3') htmlCab(client,MP3);
else if Ve3(extension,'J','P','G') htmlCab(client,JPG);
else if Ve3(extension,'X','L','S') htmlCab(client,XLS);
else if Ve3(extension,'G','I','F') htmlCab(client,GIF);
else if Ve3(extension,'H','T','M') htmlCab(client,HTM);
else if Ve3(extension,'Z','I','P') htmlCab(client,ZIP);
else if Ve3(extension,'P','D','F') htmlCab(client,PDF);
else if Ve3(extension,'T','A','R') htmlCab(client,TAR);
else htmlCab(client,99);
} else htmlCab(client,HTM);
#undef Ve3
// Aqui hay un truco: el buffer empleado es el mimsmo que tiene filename y extensión.
// Los punteros no deben volver a usarse
// Leo en cantidades de BUFFESIZE
uint32_t Size=file.fileSize();
while ((Size>=SIZEBUFFER) && (file.read(MiBuffer,SIZEBUFFER) > -1)) {
client.write((uint8_t*)MiBuffer,SIZEBUFFER);
Size-=SIZEBUFFER;
}
if ((Size>0) && (Size<SIZEBUFFER) && (file.read(MiBuffer,Size) > -1)) client.write((uint8_t*)MiBuffer,Size);
file.close();
#ifdef DEBUG_FileOpenClose
PgmPrintln("Closed!");
#endif
} else {
// everything else is a 404
HTNLNotFound(client);
}
break;
}
}
// give the web browser time to receive the data
delay(1);
client.stop();
}
}
//
// Escribe una cadena de unsigne int en HTML, caracteres y unicode cuando haga falta
//void PrintUnicode(Print* pr,unsigned int *p) {
void PrintUnicode(Print* pr) {
unsigned int *p=(unsigned int *)&Bnl;
while (*p != 0) {
if ((0x1F < *p) && (*p < 0x80)) pr->write((char)*p);
else {
pr->print(F("&#x"));pr->print(*p,HEX);pr->write(';');
}
p++;
}
}
// Nombre del último fichero encontrado. Si es directorio debe pasarse al Path para
// tener la referencia del siguiente nivel
// char FN83[13];
// Listado de subestructura de directorios
void ls(SdBaseFile* This,Print* pr, uint8_t flags, uint8_t indent) {
This->rewind();
int8_t status;
while ((status = lsPrintNext(This, pr, flags, indent))) {
if (status > 1 && (flags & LS_R)) {
uint16_t index = This->curPosition()/32 - 1;
SdBaseFile s;
PonDir(FN83);
#ifdef DEBUG_VerCambiosPath
PgmPrint("--Fichero: ");Serial.println(FN83);
PgmPrint("++Path: ");Serial.println(MiBuffer);
#endif
if (s.open(This, index, O_READ)) ls(&s,pr, flags, indent + 2);
QuitaDir();
#ifdef DEBUG_VerCambiosPath
PgmPrint("--Path: ");Serial.println(MiBuffer);
#endif
This->seekSet(32 * (index + 1));
}
}
}
//------------------------------------------------------------------------------
// saves 32 bytes on stack for ls recursion
// return 0 - EOF, 1 - normal file, or 2 - directory
int8_t lsPrintNext(SdBaseFile *This,Print* pr, uint8_t flags, uint8_t indent) {
dir_t dir;
uint8_t w = 0;
// char FN83[13];
// No sale hasta que encuentra un fichero a mostrar
// Las lineas de nombres largos rellenan el buffer de nombre largo
while (1) {
// No lee lo suficiente
if (This->read(&dir, sizeof(dir)) != sizeof(dir)) return 0; // No hay mas ficheros
#ifdef DEBUG_VerFiledirHex
// debug d caracteres leídos
for (int j=0;j<sizeof(dir);j++) {
pr->print(dir.name[j],HEX);pr->print(' ');
// Serial.print(dir.name[j],HEX);Serial.print(' ');
}
pr->println();
// Serial.println;
#endif
// entrada libre en fin de entradas de fichero
if (dir.name[0] == DIR_NAME_FREE) return 0; // no hay mas ficheros
// es una entrada borrada
if (dir.name[0] == DIR_NAME_DELETED) continue; // buscar siguiente
// salta el directorio . (el .. me interesa para subir)
if ((dir.name[0] == '.') && (dir.name[1] != '.')) continue; // buscar siguiente
// Condicion de nombre largo negativa para salis
if (DIR_IS_FILE_OR_SUBDIR(&dir)) break; // Ficheros y directorios
if ((dir.attributes & DIR_ATT_LONG_NAME_MASK) != DIR_ATT_LONG_NAME) break; // Sale a impimir linea
// Nombre largo, se añade al buffer
if (BnlFila_i == -1) continue; // Error en nombre largo. La rutina de escritura lo debe poner a cero
// Si es final 0x4X (aparece primero) y no hay nombre largo inicializo nombre largo
if ((dir.name[0] & 0xF0) == 0x40) {
if (BnlSi) {BnlFila_i=-1; continue;} // Algo raro en la secuencia
else BnlSi=true;
// Debería ser un else pero no hace falta
if ((BnlFila_i=(dir.name[0] & 0x3F)) > BNL_FILAS) {BnlFila_i=-1; continue;} // Fila inicial No cabe
if (BnlFila_i<BNL_FILAS) Bnl[BnlFila_i--][0]=0; // Asegurar de poner un 0 por si las mosca
else BnlFila_i--;
} else {
if (!(BnlFila_i == (dir.name[0] & 0x0F))) {BnlFila_i=-1; continue;} // Algo raro en la secuencia
BnlFila_i--;
}
// Ahora a rellenar la linea
int k=0;
for (int i=1;i<32;i++) {
if (i==0 || i==11 || i==12 || i==13 || i==26 || i==27) continue;
Bnl[BnlFila_i][k++]=dir.name[i];
}
#ifdef DEBUG_NombreLargo
// Si entrada de nombre largo, mostrarlo en hexa
if ((dir.attributes & DIR_ATT_LONG_NAME_MASK) == DIR_ATT_LONG_NAME) {
for (int j=0;j<sizeof(dir);j++) {
pr->print(dir.name[j],HEX);pr->print(' ');
Serial.print(dir.name[j],HEX);Serial.print(' ');
}
pr->println();
Serial.println();
}
#endif
} // while
// indent for dir level
for (uint8_t i = 0; i < indent; i++) pr->write(' ');
// print name
int j=0;
for (uint8_t i = 0; i < 11; i++) {
if (dir.name[i] == ' ')continue;
if (i == 8) {
FN83[j++]='.';
w++;
}
FN83[j++]=dir.name[i];
w++;
}
// Termina en 0
FN83[j]='\0';
// Escribe referencia de fichero //
if (!(dir.attributes & DIR_ATT_VOLUME_ID))
{ // No es etiqueta de volumen
pr->print(F("<a href=\""));
pr->print(MiBuffer); // Path termina en /
pr->print(FN83);
pr->print(F("\">"));
} // endif No es etiqueta de volumen
pr->print(FN83);
//Serial.print(FN83);
// Esto solo a la vista, no en el enlace
if (DIR_IS_SUBDIR(&dir)) {
pr->write('/');
w++;
}
if (!(dir.attributes & DIR_ATT_VOLUME_ID))
{ // No es etiqueta de volumen
pr->print(F("</a>"));
} // endif No es etiqueta de volumen
if (flags & (LS_DATE | LS_SIZE)) {
while (w++ < 14) pr->write(' ');
}
#ifdef ATRIBUTOS
// Mostrar atributos
pr->write(' ');
pr->write((dir.attributes & DIR_ATT_READ_ONLY) ? 'R' : ' '); //1
pr->write((dir.attributes & DIR_ATT_HIDDEN ) ? 'H' : ' '); //2
pr->write((dir.attributes & DIR_ATT_SYSTEM ) ? 'S' : ' '); //4
pr->write((dir.attributes & DIR_ATT_VOLUME_ID) ? 'V' : ' '); //8
pr->write((dir.attributes & DIR_ATT_DIRECTORY) ? 'D' : ' '); //16 0x10
pr->write((dir.attributes & DIR_ATT_ARCHIVE ) ? 'A' : ' '); //32 0x20
pr->write((dir.attributes & 0x40 ) ? '6' : ' '); //64 0x40
pr->write((dir.attributes & 0x80 ) ? '7' : ' '); //128 0x80
#endif
// print modify date/time if requested
if (flags & LS_DATE) {
pr->write(' ');
This->printFatDate(pr, dir.lastWriteDate);
pr->write(' ');
This->printFatTime(pr, dir.lastWriteTime);
}
// print size if requested
if (!DIR_IS_SUBDIR(&dir) && !(dir.attributes & DIR_ATT_VOLUME_ID) && (flags & LS_SIZE)) {
pr->write(' ');
pr->print(dir.fileSize);
}
// nombre largo
if (BnlSi && (BnlFila_i!=-1)) {
pr->write(' ');
PrintUnicode(pr);
// PrintUnicode(&Serial);
}
BnlSi=false;BnlFila_i=0;
//
pr->println();
//Serial.println();
int8_t retorno;
if (!DIR_IS_SUBDIR(&dir)) retorno=1;
else retorno=(dir.name[0] == '.') ? 1: 2;
return retorno;
}
// Pon directorio raiz
void PonRoot() {
MiBuffer[0]='/';
MiBuffer[1]='\0'; // inicializo la cadena
#ifdef DEBUG_VerCambiosPath
PgmPrint("PonRoot: ");Serial.println(MiBuffer);
#endif
}
// Pone el path completo
void PonPath(char *c) {
// PgmPrint("-- Rutina PonPath: ");Serial.println(c);
int i=0;
//int j=i;Serial.print(j);PgmPrint(" ");Serial.print((char)c[j]);
while ((MiBuffer[i]=c[i])!='\0') i++; // {PgmPrint(" ");Serial.println(MiBuffer[j],HEX);j=i;}
// Si no termina en / la añado
if (MiBuffer[(i-1)]!='/') {
MiBuffer[i++]='/';
MiBuffer[i]='\0';
#ifdef DEBUG_VerCambiosPath
PgmPrint("PonPath: ");Serial.println(MiBuffer);
#endif
}
}
// Añado nombre al path y pongo barra si falta
void PonDir(char* c) {
int i=0,j=0;
for (i=0;i<SIZEBUFFER;i++) { // Busco el 0
if (MiBuffer[i]=='\0') break;
}
// Si no termina en '/' lo añado
if ((i==0) || (MiBuffer[i-1]!='/')) {
MiBuffer[i++]='/';
MiBuffer[i]='/0';
}
while ((MiBuffer[i++]=c[j++])!='\0');
if (MiBuffer[i-2]!='/') {
MiBuffer[i-1]='/';
MiBuffer[i]='\0';
}
}
// Termina en barra, por lo que tengo que buscar la segunda barra
void QuitaDir() {
int i;
for (i=0;i<SIZEBUFFER;i++) { // Busco el 0
if (MiBuffer[i]=='\0') break;
}
if ((i>0) && (MiBuffer[--i]=='/')) i--;
while ((i>0) && (MiBuffer[i]!='/')) i--; // Busco la barra
if (i==0) {MiBuffer[i]='/';}; // Directorio raiz
MiBuffer[++i]='\0'; // Nuevo cero
}
// Para que funcione y busque los caracteres acentuados, debe modificarse una librería.
// SdBaseFile.cpp, linea 418 pone:
// if (i > n || c < 0X21 || c > 0X7E)goto fail;
// debe cambiarse a:
// if (i > n || c < 0X21)goto fail;
// Cambiar caracteres a cp850
void UTF_8acp850(char * c) {
if ( (unsigned char)*c < 0xA0 ) return;
switch (*c) {
case 0xA0: {*c=0xA0;break;} // NO-BREAK SPACE
case 0xA1: {*c=0xAD;break;} // ¡ INVERTED EXCLAMATION MARK
case 0xA2: {*c=0xBD;break;} // ¢ CENT SIGN
case 0xA3: {*c=0x9C;break;} // £ POUND SIGN
case 0xA4: {*c=0xCF;break;} // ¤ CURRENCY SIGN
case 0xA5: {*c=0xBE;break;} // ¥ YEN SIGN
case 0xA6: {*c=0xDD;break;} // ¦ BROKEN BAR
case 0xA7: {*c=0xF5;break;} // § SECTION SIGN
case 0xA8: {*c=0xF9;break;} // ¨ DIAERESIS
case 0xA9: {*c=0xB8;break;} // © COPYRIGHT SIGN
case 0xAA: {*c=0xA6;break;} // ª FEMININE ORDINAL INDICATOR
case 0xAB: {*c=0xAE;break;} // « LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
case 0xAC: {*c=0xAA;break;} // ¬ NOT SIGN
case 0xAD: {*c=0xF0;break;} // SOFT HYPHEN
case 0xAE: {*c=0xA9;break;} // ® REGISTERED SIGN
case 0xAF: {*c=0xEE;break;} // ¯ MACRON
case 0xB0: {*c=0xF8;break;} // ° DEGREE SIGN
case 0xB1: {*c=0xF1;break;} // ± PLUS-MINUS SIGN
case 0xB2: {*c=0xFD;break;} // ² SUPERSCRIPT TWO
case 0xB3: {*c=0xFC;break;} // ³ SUPERSCRIPT THREE
case 0xB4: {*c=0xEF;break;} // ´ ACUTE ACCENT
case 0xB5: {*c=0xE6;break;} // µ MICRO SIGN
case 0xB6: {*c=0xF4;break;} // ¶ PILCROW SIGN
case 0xB7: {*c=0xFA;break;} // · MIDDLE DOT
case 0xB8: {*c=0xF7;break;} // ¸ CEDILLA
case 0xB9: {*c=0xD5;break;} // ¹ SUPERSCRIPT ONE
case 0xBA: {*c=0xA7;break;} // º MASCULINE ORDINAL INDICATOR
case 0xBB: {*c=0xAF;break;} // » RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
case 0xBC: {*c=0xAC;break;} // ¼ VULGAR FRACTION ONE QUARTER
case 0xBD: {*c=0xAB;break;} // ½ VULGAR FRACTION ONE HALF
case 0xBE: {*c=0xF3;break;} // ¾ VULGAR FRACTION THREE QUARTERS
case 0xBF: {*c=0xA8;break;} // ¿ INVERTED QUESTION MARK
case 0xC0: {*c=0xB7;break;} // À LATIN CAPITAL LETTER A WITH GRAVE
case 0xC1: {*c=0xB5;break;} // Á LATIN CAPITAL LETTER A WITH ACUTE
case 0xC2: {*c=0xB6;break;} // Â LATIN CAPITAL LETTER A WITH CIRCUMFLEX
case 0xC3: {*c=0xC7;break;} // Ã LATIN CAPITAL LETTER A WITH TILDE
case 0xC4: {*c=0x8E;break;} // Ä LATIN CAPITAL LETTER A WITH DIAERESIS
case 0xC5: {*c=0x8F;break;} // Å LATIN CAPITAL LETTER A WITH RING ABOVE
case 0xC6: {*c=0x92;break;} // Æ LATIN CAPITAL LETTER AE
case 0xC7: {*c=0x80;break;} // Ç LATIN CAPITAL LETTER C WITH CEDILLA
case 0xC8: {*c=0xD4;break;} // È LATIN CAPITAL LETTER E WITH GRAVE
case 0xC9: {*c=0x90;break;} // É LATIN CAPITAL LETTER E WITH ACUTE
case 0xCA: {*c=0xD2;break;} // Ê LATIN CAPITAL LETTER E WITH CIRCUMFLEX
case 0xCB: {*c=0xD3;break;} // Ë LATIN CAPITAL LETTER E WITH DIAERESIS
case 0xCC: {*c=0xDE;break;} // Ì LATIN CAPITAL LETTER I WITH GRAVE
case 0xCD: {*c=0xD6;break;} // Í LATIN CAPITAL LETTER I WITH ACUTE
case 0xCE: {*c=0xD7;break;} // Î LATIN CAPITAL LETTER I WITH CIRCUMFLEX
case 0xCF: {*c=0xD8;break;} // Ï LATIN CAPITAL LETTER I WITH DIAERESIS
case 0xD0: {*c=0xD1;break;} // Ð LATIN CAPITAL LETTER ETH
case 0xD1: {*c=0xA5;break;} // Ñ LATIN CAPITAL LETTER N WITH TILDE
case 0xD2: {*c=0xE3;break;} // Ò LATIN CAPITAL LETTER O WITH GRAVE
case 0xD3: {*c=0xE0;break;} // Ó LATIN CAPITAL LETTER O WITH ACUTE
case 0xD4: {*c=0xE2;break;} // Ô LATIN CAPITAL LETTER O WITH CIRCUMFLEX
case 0xD5: {*c=0xE5;break;} // Õ LATIN CAPITAL LETTER O WITH TILDE
case 0xD6: {*c=0x99;break;} // Ö LATIN CAPITAL LETTER O WITH DIAERESIS
case 0xD7: {*c=0x9E;break;} // × MULTIPLICATION SIGN
case 0xD8: {*c=0x9D;break;} // Ø LATIN CAPITAL LETTER O WITH STROKE
case 0xD9: {*c=0xEB;break;} // Ù LATIN CAPITAL LETTER U WITH GRAVE
case 0xDA: {*c=0xE9;break;} // Ú LATIN CAPITAL LETTER U WITH ACUTE
case 0xDB: {*c=0xEA;break;} // Û LATIN CAPITAL LETTER U WITH CIRCUMFLEX
case 0xDC: {*c=0x9A;break;} // Ü LATIN CAPITAL LETTER U WITH DIAERESIS
case 0xDD: {*c=0xED;break;} // Ý LATIN CAPITAL LETTER Y WITH ACUTE
case 0xDE: {*c=0xE7;break;} // Þ LATIN CAPITAL LETTER THORN
case 0xDF: {*c=0xE1;break;} // ß LATIN SMALL LETTER SHARP S
case 0xE0: {*c=0x85;break;} // à LATIN SMALL LETTER A WITH GRAVE
case 0xE1: {*c=0xA0;break;} // á LATIN SMALL LETTER A WITH ACUTE
case 0xE2: {*c=0x83;break;} // â LATIN SMALL LETTER A WITH CIRCUMFLEX
case 0xE3: {*c=0xC6;break;} // ã LATIN SMALL LETTER A WITH TILDE
case 0xE4: {*c=0x84;break;} // ä LATIN SMALL LETTER A WITH DIAERESIS
case 0xE5: {*c=0x86;break;} // å LATIN SMALL LETTER A WITH RING ABOVE
case 0xE6: {*c=0x91;break;} // æ LATIN SMALL LETTER AE
case 0xE7: {*c=0x87;break;} // ç LATIN SMALL LETTER C WITH CEDILLA
case 0xE8: {*c=0x8A;break;} // è LATIN SMALL LETTER E WITH GRAVE
case 0xE9: {*c=0x82;break;} // é LATIN SMALL LETTER E WITH ACUTE
case 0xEA: {*c=0x88;break;} // ê LATIN SMALL LETTER E WITH CIRCUMFLEX
case 0xEB: {*c=0x89;break;} // ë LATIN SMALL LETTER E WITH DIAERESIS
case 0xEC: {*c=0x8D;break;} // ì LATIN SMALL LETTER I WITH GRAVE
case 0xED: {*c=0xA1;break;} // í LATIN SMALL LETTER I WITH ACUTE
case 0xEE: {*c=0x8C;break;} // î LATIN SMALL LETTER I WITH CIRCUMFLEX
case 0xEF: {*c=0x8B;break;} // ï LATIN SMALL LETTER I WITH DIAERESIS
case 0xF0: {*c=0xD1;break;} // ð LATIN SMALL LETTER ETH
case 0xF1: {*c=0xA4;break;} // ñ LATIN SMALL LETTER N WITH TILDE
case 0xF2: {*c=0x95;break;} // ò LATIN SMALL LETTER O WITH GRAVE
case 0xF3: {*c=0xA2;break;} // ó LATIN SMALL LETTER O WITH ACUTE
case 0xF4: {*c=0x93;break;} // ô LATIN SMALL LETTER O WITH CIRCUMFLEX
case 0xF5: {*c=0xE4;break;} // õ LATIN SMALL LETTER O WITH TILDE
case 0xF6: {*c=0x94;break;} // ö LATIN SMALL LETTER O WITH DIAERESIS
case 0xF7: {*c=0xF6;break;} // ÷ DIVISION SIGN
case 0xF8: {*c=0x9B;break;} // ø LATIN SMALL LETTER O WITH STROKE
case 0xF9: {*c=0x97;break;} // ù LATIN SMALL LETTER U WITH GRAVE
case 0xFA: {*c=0xA3;break;} // ú LATIN SMALL LETTER U WITH ACUTE
case 0xFB: {*c=0x96;break;} // û LATIN SMALL LETTER U WITH CIRCUMFLEX
case 0xFC: {*c=0x81;break;} // ü LATIN SMALL LETTER U WITH DIAERESIS
case 0xFD: {*c=0xEC;break;} // ý LATIN SMALL LETTER Y WITH ACUTE
case 0xFE: {*c=0xE8;break;} // þ LATIN SMALL LETTER THORN
case 0xFF: {*c=0x98;break;} // ÿ LATIN SMALL LETTER Y WITH DIAERESIS
}
}
*--------------------------------------- Cortar por aquí ---------------------------------------*