Hi there,
First time poster and I'm really scratching my head over this one, but I'm attempting to setup a webserver that automatically populates a web browser with downloadable links to the files stored on an SD card. I can get the links listed on the browser, but then when I go to implement the actual client request and download part, I'm having issues. I've cobbled the code together from the basic AP webserver and SD info examples on the Arduino IDE. Ideally something like this would be nice;
This is what it looks like before I try to implement the client request and download. I am manually counting the number of files and retrieving their filenames using the following code stored in a header;
fileAid.h
uint32_t getFileCount(File &root)
{
int count = 0;
// Get number of files on SD card.
while (true)
{
File entry = root.openNextFile();
if (! entry)
{
root.rewindDirectory();
return count;
}
++count;
entry.close();
}
}
bool getFileNames(File &root, char **fileNames, uint32_t &fileCount)
{
// Get file names.
for (uint32_t i = 0; i < fileCount; ++i)
{
File entry = root.openNextFile();
char *entryName{entry.name()};
// Count the number of characters in the current filename.
uint8_t charCount{};
while (entryName[charCount++] != '\0');
fileNames[i] = new char[charCount];
for (uint8_t j = 0; j < charCount; ++j)
fileNames[i][j] = entryName[j];
entry.close();
}
root.rewindDirectory();
return true;
}
Then after the client has stopped sending data upon initially connecting to the webserver, I am sending the following back to the client;
if (c == '\n') { // if the byte is a newline character
// if the current line is blank, you got two newline characters in a row.
// that's the end of the client HTTP request, so send a response:
if (currentLine.length() == 0) {
// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
// and a content-type so the client knows what's coming, then a blank line:
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println();
// the content of the HTTP response follows the header:
client.print("Click <a href=\"/H\">here</a> turn the LED on<br>");
client.print("Click <a href=\"/L\">here</a> turn the LED off<br><br>");
client.print("<body>File List</body><br><br>");
File rt = SD.open('/');
uint32_t fileCount = getFileCount(rt); // Count the number of files present on the SD card
char **fileNames{new char*[fileCount]};
bool success = getFileNames(rt, fileNames, fileCount); // Get a list of available files from the SD card
// Print a list of files to the browser.
for (uint32_t i{}; i < fileCount; ++i)
{
String entryName{fileNames[i]};
client.print("<a href=\"/" + entryName + "\">" + entryName + "</a><br>");
}
client.print("<body>File List</body><br><br>");
// The HTTP response ends with another blank line:
client.println();
// break out of the while loop:
break;
}
else { // if you got a newline, then clear currentLine:
currentLine = "";
}
}
else if (c != '\r') { // if you got anything else but a carriage return character,
currentLine += c; // add it to the end of the currentLine
}
Of course, this results in the screenshot above. However now I need to move this block of code;
File rt = SD.open('/');
uint32_t fileCount = getFileCount(rt); // Count the number of files present on the SD card
char **fileNames{new char*[fileCount]};
bool success = getFileNames(rt, fileNames, fileCount); // Get a list of available files from the SD card
To outside of the above if statement so that I can loop through each filename and see if the client has sent a request for that particular file;
while (client.connected()) { // loop while the client's connected
File rt = SD.open('/');
uint32_t fileCount = getFileCount(rt); // Count the number of files present on the SD card
char **fileNames{new char*[fileCount]};
bool success = getFileNames(rt, fileNames, fileCount); // Get a list of available files from the SD card
if (client.available()) { // if there's bytes to read from the client,
char c = client.read(); // read a byte, then
Serial.write(c); // print it out the serial monitor
if (c == '\n') { // if the byte is a newline character
// if the current line is blank, you got two newline characters in a row.
// that's the end of the client HTTP request, so send a response:
if (currentLine.length() == 0) {
// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
// and a content-type so the client knows what's coming, then a blank line:
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println();
// the content of the HTTP response follows the header:
client.print("Click <a href=\"/H\">here</a> turn the LED on<br>");
client.print("Click <a href=\"/L\">here</a> turn the LED off<br><br>");
client.print("<body>File List</body><br><br>");
// Print a list of files to the browser.
for (uint32_t i{}; i < fileCount; ++i)
{
String entryName{fileNames[i]};
client.print("<a href=\"/" + entryName + "\">" + entryName + "</a><br>");
}
client.print("<body>File List</body><br><br>");
// The HTTP response ends with another blank line:
client.println();
// break out of the while loop:
break;
}
else { // if you got a newline, then clear currentLine:
currentLine = "";
}
}
else if (c != '\r') { // if you got anything else but a carriage return character,
currentLine += c; // add it to the end of the currentLine
}
// Check to see if the client request was "GET /H" or "GET /L":
if (currentLine.endsWith("GET /H")) {
digitalWrite(led, HIGH); // GET /H turns the LED on
}
if (currentLine.endsWith("GET /L")) {
digitalWrite(led, LOW); // GET /L turns the LED off
}
// Check to see if a download request has been made and if so, send the requested file to the client.
for (uint32_t i{}; i < fileCount; ++i)
{
String entryName = fileNames[i];
if (currentLine.endsWith("GET /" + entryName))
{
File download{SD.open(entryName)};
if (download) {
while (download.available())
{
client.write(download.read());
}
}
download.close();
}
}
}
}
However when I do this I get the following (see follow up reply, as I could only include one embedded image in the original post).
I'm really at a loss and kind of at my wits end with this, I really hope someone can help. Thanks in advance.
Here is the full code;
webServer.cpp
/*
Satellite AP Web Server
A simple web server that lets you blink an LED via the web.
This sketch will create a new access point (with no password).
It will then launch a new server and print out the IP address
to the Serial monitor. From there, you can open that address in a web browser
to turn on and off the LED on pin 13.
If the IP address of your board is yourAddress:
http://yourAddress/H turns the LED on
http://yourAddress/L turns it off
*/
#include <SD.h>
#include <SPI.h>
#include <WiFiNINA.h>
#include "arduino_secrets.h"
#include "fileAid.h"
// set up variables using the SD utility library functions:
Sd2Card card;
SdVolume volume;
SdFile root;
// change this to match your SD shield or module;
// Arduino Ethernet shield: pin 4
// Adafruit SD shields and modules: pin 10
// Sparkfun SD shield: pin 8
// MKRZero SD: SDCARD_SS_PIN
const int chipSelect = 4;
///////please enter your sensitive data in the Secret tab/arduino_secrets.h
char ssid[] = SECRET_SSID; // your network SSID (name)
char pass[] = SECRET_PASS; // your network password (use for WPA, or use as key for WEP)
int keyIndex = 0; // your network key Index number (needed only for WEP)
int led = LED_BUILTIN;
int status = WL_IDLE_STATUS;
WiFiServer server(80);
void setup() {
//Initialize serial and wait for port to open:
Serial.begin(250000);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
/////////////////////////////////////////////////////////////////////////////////////////
// Webserver AP Initialization
/////////////////////////////////////////////////////////////////////////////////////////
Serial.println("Access Point Web Server");
pinMode(led, OUTPUT); // set the LED pin mode
// check for the WiFi module:
if (WiFi.status() == WL_NO_MODULE) {
Serial.println("Communication with WiFi module failed!");
// don't continue
while (true);
}
String fv = WiFi.firmwareVersion();
if (fv < "1.0.0") {
Serial.println("Please upgrade the firmware");
}
// by default the local IP address of will be 192.168.4.1
// you can override it with the following:
// WiFi.config(IPAddress(10, 0, 0, 1));
// print the network name (SSID);
Serial.print("Creating access point named: ");
Serial.println(ssid);
// Create open network. Change this line if you want to create an WEP network:
status = WiFi.beginAP(ssid, pass);
if (status != WL_AP_LISTENING) {
Serial.println("Creating access point failed");
// don't continue
while (true);
}
// wait 10 seconds for connection:
delay(10000);
// start the web server on port 80
server.begin();
// you're connected now, so print out the status
printWiFiStatus();
/////////////////////////////////////////////////////////////////////////////////////////
// SD Card Initialization
/////////////////////////////////////////////////////////////////////////////////////////
Serial.print("\nInitializing SD card...");
// we'll use the initialization code from the utility libraries
// since we're just testing if the card is working!
if (!card.init(SPI_HALF_SPEED, chipSelect)) {
Serial.println("initialization failed. Things to check:");
Serial.println("* is a card inserted?");
Serial.println("* is your wiring correct?");
Serial.println("* did you change the chipSelect pin to match your shield or module?");
while (1);
} else {
Serial.println("Wiring is correct and a card is present.");
}
// print the type of card
Serial.println();
Serial.print("Card type: ");
switch (card.type()) {
case SD_CARD_TYPE_SD1:
Serial.println("SD1");
break;
case SD_CARD_TYPE_SD2:
Serial.println("SD2");
break;
case SD_CARD_TYPE_SDHC:
Serial.println("SDHC");
break;
default:
Serial.println("Unknown");
}
// Now we will try to open the 'volume'/'partition' - it should be FAT16 or FAT32
if (!volume.init(card)) {
Serial.println("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card");
while (1);
}
Serial.print("Clusters: ");
Serial.println(volume.clusterCount());
Serial.print("Blocks x Cluster: ");
Serial.println(volume.blocksPerCluster());
Serial.print("Total Blocks: ");
Serial.println(volume.blocksPerCluster() * volume.clusterCount());
Serial.println();
// print the type and size of the first FAT-type volume
uint32_t volumesize;
Serial.print("Volume type is: FAT");
Serial.println(volume.fatType(), DEC);
volumesize = volume.blocksPerCluster(); // clusters are collections of blocks
volumesize *= volume.clusterCount(); // we'll have a lot of clusters
volumesize /= 2; // SD card blocks are always 512 bytes (2 blocks are 1KB)
Serial.print("Volume size (Kb): ");
Serial.println(volumesize);
Serial.print("Volume size (Mb): ");
volumesize /= 1024;
Serial.println(volumesize);
Serial.print("Volume size (Gb): ");
Serial.println((float)volumesize / 1024.0);
Serial.println("\nInitializing root directory...");
if (SD.begin())
{
Serial.println("Root directory initialized.");
}
else
{
Serial.println("Could not initialize root directory.");
while (true);
}
Serial.println("\nFiles found on the card (name, date and size in bytes): ");
root.openRoot(volume);
// list all files in the card with date and size
root.ls(LS_R | LS_DATE | LS_SIZE);
}
void loop() {
// compare the previous status to the current status
if (status != WiFi.status()) {
// it has changed update the variable
status = WiFi.status();
if (status == WL_AP_CONNECTED) {
// a device has connected to the AP
Serial.println("Device connected to AP");
} else {
// a device has disconnected from the AP, and we are back in listening mode
Serial.println("Device disconnected from AP");
}
}
WiFiClient client = server.available(); // listen for incoming clients
if (client) { // if you get a client,
Serial.println("new client"); // print a message out the serial port
String currentLine = ""; // make a String to hold incoming data from the client
while (client.connected()) { // loop while the client's connected
File rt = SD.open('/');
uint32_t fileCount = getFileCount(rt); // Count the number of files present on the SD card
char **fileNames{new char*[fileCount]};
bool success = getFileNames(rt, fileNames, fileCount); // Get a list of available files from the SD card
if (client.available()) { // if there's bytes to read from the client,
char c = client.read(); // read a byte, then
Serial.write(c); // print it out the serial monitor
if (c == '\n') { // if the byte is a newline character
// if the current line is blank, you got two newline characters in a row.
// that's the end of the client HTTP request, so send a response:
if (currentLine.length() == 0) {
// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
// and a content-type so the client knows what's coming, then a blank line:
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println();
// the content of the HTTP response follows the header:
client.print("Click <a href=\"/H\">here</a> turn the LED on<br>");
client.print("Click <a href=\"/L\">here</a> turn the LED off<br><br>");
client.print("<body>File List</body><br><br>");
// Print a list of files to the browser.
for (uint32_t i{}; i < fileCount; ++i)
{
String entryName{fileNames[i]};
client.print("<a href=\"/" + entryName + "\">" + entryName + "</a><br>");
}
// while (true)
// {
// File entry = rt.openNextFile();
// if (!entry)
// {
// rt.rewindDirectory();
// break;
// }
//
// String entryName{entry.name()};
// Serial.println(entryName);
// client.print("<body>" + entryName + "</body>");
// client.print("<a href=\"/TEST.PNG\">TEST.PNG</a><br>");
// }
client.print("<body>File List</body><br><br>");
// The HTTP response ends with another blank line:
client.println();
// break out of the while loop:
break;
}
else { // if you got a newline, then clear currentLine:
currentLine = "";
}
}
else if (c != '\r') { // if you got anything else but a carriage return character,
currentLine += c; // add it to the end of the currentLine
}
// Check to see if the client request was "GET /H" or "GET /L":
if (currentLine.endsWith("GET /H")) {
digitalWrite(led, HIGH); // GET /H turns the LED on
}
if (currentLine.endsWith("GET /L")) {
digitalWrite(led, LOW); // GET /L turns the LED off
}
// Check to see if a download request has been made and if so, send the requested file to the client.
for (uint32_t i{}; i < fileCount; ++i)
{
String entryName = fileNames[i];
if (currentLine.endsWith("GET /" + entryName))
{
File download{SD.open(entryName)};
if (download) {
while (download.available())
{
client.write(download.read());
}
}
download.close();
}
}
}
// rt.close();
}
// close the connection:
client.stop();
Serial.println("client disconnected");
}
}
void printWiFiStatus() {
// print the SSID of the network you're attached to:
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
// print your WiFi shield's IP address:
IPAddress ip = WiFi.localIP();
Serial.print("IP Address: ");
Serial.println(ip);
// print where to go in a browser:
Serial.print("To see this page in action, open a browser to http://");
Serial.println(ip);
}
arduino_secrets.h
#define SECRET_SSID "mrwam"
#define SECRET_PASS "mrwam123"
James