Commit 5d3a515a authored by Nitek's avatar Nitek

* make it work on NodeMCU* improve webinterface

parent 09ec9b56
/**
* Put this file https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WebServer/examples/SDWebServer/SdRoot/edit/index.htm
* in the root folder of the SD card for webinterface
Put this file https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WebServer/examples/SDWebServer/SdRoot/edit/index.htm
in the root folder of the SD card for webinterface
*/
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>
#include <SPI.h>
#include <MFRC522.h>
#include <Adafruit_VS1053.h>
#include <SD.h>
#include <SdFat.h>
#define RC522_CS_PIN D0
#define RC522_CS_PIN D3
#define SD_CS_PIN D2
#define BREAKOUT_RESET D4 // VS1053 reset pin (output)
#define BREAKOUT_CS D8 // VS1053 chip select pin (output)
#define BREAKOUT_DCS D3 // VS1053 Data/command select pin (output)
#define BREAKOUT_DCS D0 // VS1053 Data/command select pin (output)
#define DREQ D1 // VS1053 Data request, ideally an Interrupt pin
Adafruit_VS1053_FilePlayer musicPlayer =
......@@ -32,18 +31,29 @@ MFRC522 mfrc522(RC522_CS_PIN, UINT8_MAX); // Create MFRC522 instance.
MFRC522::MIFARE_Key key;
// Init array that will store new NUID
byte nuidPICC[4];
boolean playing = false;
File currentFolder;
File uploadFile;
SdFat SD;
SdFile uploadFile;
void setup() {
Serial.begin(115200);
Serial.println();
// Init SPI SS pins
pinMode(RC522_CS_PIN, OUTPUT);
digitalWrite(RC522_CS_PIN, HIGH);
pinMode(SD_CS_PIN, OUTPUT);
digitalWrite(SD_CS_PIN, HIGH);
pinMode(BREAKOUT_CS, OUTPUT);
digitalWrite(BREAKOUT_CS, HIGH);
pinMode(BREAKOUT_DCS, OUTPUT);
digitalWrite(BREAKOUT_DCS, HIGH);
SPI.begin(); // Init SPI bus
mfrc522.PCD_Init(); // Init MFRC522 card
......@@ -53,33 +63,27 @@ void setup() {
}
Serial.print(F("Using rfid key (for A and B):"));
dump_byte_array(key.keyByte, MFRC522::MF_KEY_SIZE);
print_byte_array(key.keyByte, MFRC522::MF_KEY_SIZE);
Serial.println();
//Initialize the SdCard.
if (!SD.begin(SD_CS_PIN)) Serial.println(F("SD.begin failed"));
if (!(currentFolder = SD.open("/"))) Serial.println(F("sd.chdir"));
while (File file = currentFolder.openNextFile())
{
if(file.isDirectory()){
switchFolder(file.name());
file.close();
}
}
if (!SD.begin(SD_CS_PIN)) SD.initErrorHalt();
// initialise the music player
if (! musicPlayer.begin()) { // initialise the music player
Serial.println(F("Couldn't find VS1053, do you have the right pins defined?"));
while(1) delay(500);
while (1) delay(500);
}
Serial.println(F("VS1053 found"));
// Fix for the design fuckup of the cheap LC Technology MP3 shield
// see http://www.bajdi.com/lcsoft-vs1053-mp3-module/#comment-33773
/* Fix for the design fuckup of the cheap LC Technology MP3 shield
see http://www.bajdi.com/lcsoft-vs1053-mp3-module/#comment-33773
Doesn't hurt for other shields
*/
musicPlayer.sciWrite(VS1053_REG_WRAMADDR, VS1053_GPIO_DDR);
musicPlayer.sciWrite(VS1053_REG_WRAM, 0x0003);
musicPlayer.GPIO_digitalWrite(0x0000);
musicPlayer.softReset();
Serial.print(F("SampleRate "));
......@@ -87,17 +91,14 @@ void setup() {
musicPlayer.setVolume(40, 40);
wifiManager.autoConnect("MP3-SHELF", "ikealack");
wifiManager.autoConnect("MP3-SHELF", "lacklack");
Serial.print("Connected! IP address: ");
Serial.println(WiFi.localIP());
server.on("/playMp3", HTTP_GET, handlePlayMp3);
server.on("/stopMp3", HTTP_GET, handleStopMp3);
server.on("/list", HTTP_GET, printDirectory);
server.on("/edit", HTTP_DELETE, handleDelete);
server.on("/edit", HTTP_PUT, handleCreate);
server.on("/edit", HTTP_POST, [](){ returnOK(); }, handleFileUpload);
server.on("/", HTTP_POST, handleNotFound, handleFileUpload);
server.onNotFound(handleNotFound);
server.begin();
......@@ -106,18 +107,19 @@ void setup() {
}
void loop() {
if (musicPlayer.playingMusic) {
if (playing && musicPlayer.playingMusic) {
musicPlayer.feedBuffer();
} else if(playing) {
} else if (playing) {
playMp3();
}
handleRfid();
server.handleClient();
}
void handleRfid() {
// Look for new cards
// Look for new cards
if ( ! mfrc522.PICC_IsNewCardPresent()) {
return;
}
......@@ -138,7 +140,7 @@ void handleRfid() {
// Show some details of the PICC (that is: the tag/card)
Serial.print(F("Card UID:"));
dump_byte_array(mfrc522.uid.uidByte, mfrc522.uid.size);
print_byte_array(mfrc522.uid.uidByte, mfrc522.uid.size);
Serial.println();
Serial.print(F("PICC type: "));
MFRC522::PICC_Type piccType = mfrc522.PICC_GetType(mfrc522.uid.sak);
......@@ -197,7 +199,7 @@ boolean readRfidBlock(uint8_t sector, uint8_t relativeBlock, char *outputBuffer)
return false;
}
Serial.print(F("Data in block ")); Serial.print(absoluteBlock); Serial.println(F(":"));
dump_byte_array(buffer, 16);
print_byte_array(buffer, 16);
Serial.println();
Serial.println();
......@@ -215,10 +217,8 @@ boolean readRfidBlock(uint8_t sector, uint8_t relativeBlock, char *outputBuffer)
return true;
}
/**
Helper routine to dump a byte array as hex values to Serial.
*/
void dump_byte_array(byte *buffer, byte bufferSize) {
void print_byte_array(byte *buffer, byte bufferSize) {
for (byte i = 0; i < bufferSize; i++) {
Serial.print(buffer[i] < 0x10 ? " 0" : " ");
Serial.print(buffer[i], HEX);
......@@ -226,35 +226,44 @@ void dump_byte_array(byte *buffer, byte bufferSize) {
}
void switchFolder(const char *folder) {
// TODO do SD.exists(folder) check here, but it keeps crashing the ESP
Serial.print(F("Switching folder to "));
Serial.println(folder);
currentFolder.close();
currentFolder = SD.open(folder);
if (!SD.exists(folder)) {
Serial.println(F("Folder does not exist"));
return;
}
SD.chdir(folder);
SD.vwd()->rewind();
}
void stopMp3() {
playing = false;
musicPlayer.stopPlaying();
playing = false;
musicPlayer.stopPlaying();
}
void playMp3() {
while (File file = currentFolder.openNextFile())
SdFile file;
while (file.openNext(SD.vwd(), O_READ))
{
char filenameChar[100];
String filename = "/";
SD.vwd()->getName(filenameChar, 100);
filename += filenameChar;
filename += "/";
file.getName(filenameChar, 100);
filename += filenameChar;
char filename[30] = "";
strcat(filename, "/");
strcat(filename, currentFolder.name());
strcat(filename, "/");
strcat(filename, file.name());
file.close();
if(!file.isDirectory() && isMP3File(filename)){
if (!file.isDir() && isMP3File(filename)) {
Serial.print(F("Playing "));
Serial.println(filename);
playing = true;
musicPlayer.startPlayingFile(filename);
musicPlayer.startPlayingFile((char *)filename.c_str());
return;
} else {
Serial.print(F("Ignoring "));
......@@ -262,61 +271,102 @@ void playMp3() {
}
}
// Start again
currentFolder.rewindDirectory();
SD.vwd()->rewind();
}
bool isMP3File(const char* filename) {
int numChars;
char dotMP3[] = ".MP3";
if (filename) {
numChars = strlen(filename);
if (numChars > 4) {
byte index = 0;
for (byte i = numChars - 4; i < numChars; i++) {
if (!(toupper(filename[i]) == dotMP3[index])) {
return false;
}
index++;
}
return true;
}
}
return false;
bool isMP3File(String filename) {
return filename.endsWith(".mp3");
}
void returnOK() {
server.send(200, "text/plain", "");
}
void returnFail(String msg) {
server.send(500, "text/plain", msg + "\r\n");
void returnServerError(String msg) {
server.send(500, "text/plain", msg);
}
bool loadFromSdCard(String path){
String dataType = "text/plain";
if(path.endsWith("/")) path += "index.htm";
void renderDirectory(String path) {
SdFile dir;
dir.open(path.c_str(), O_READ);
if(path.endsWith(".htm")) dataType = "text/html";
else if(path.endsWith(".css")) dataType = "text/css";
else if(path.endsWith(".js")) dataType = "application/javascript";
dir.rewind();
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
server.send(200, "text/html", "");
WiFiClient client = server.client();
File dataFile = SD.open(path.c_str());
if(dataFile.isDirectory()){
path += "/index.htm";
dataType = "text/html";
dataFile = SD.open(path.c_str());
server.sendContent(
F("<html><head><script>"
"function deleteUrl(url){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { location.reload(); }}; xhr.open('DELETE', url); xhr.send();}"
"function upload(folder){ var fileInput = document.getElementById('fileInput'); if(fileInput.files.length === 0){ alert('Choose a file first'); return; } xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { location.reload(); }};"
"var formData = new FormData(); for(var i = 0; i < fileInput.files.length; i++) { formData.append('data', fileInput.files[i], folder.concat(fileInput.files[i].name)); }; xhr.open('POST', '/'); xhr.send(formData); }"
"function mkdir() { var folder = document.getElementById('folder'); if(folder != ''){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { location.reload(); }}; var formData = new FormData(); formData.append('folder', folder.value); xhr.open('POST', '/'); xhr.send(formData);}}"
"</script></head><body>"));
String output;
if (path == "/") {
output = F("<form><input type=\"text\" name=\"folder\" id=\"folder\"><input type=\"button\" value=\"mkdir\" onclick=\"mkdir()\"></form>");
output.replace("{folder}", path);
server.sendContent(output);
} else {
output = F("<form><input type=\"file\" multiple=\"true\" name=\"data\" id=\"fileInput\"><input type=\"button\" value=\"upload\" onclick=\"upload('{folder}')\"></form>");
output.replace("{folder}", path);
server.sendContent(output);
server.sendContent(F("<div><a href=\"..\">..</a></div>"));
}
if (!dataFile)
return false;
SdFile entry;
while (entry.openNext(&dir, O_READ)) {
char filenameChar[100];
entry.getName(filenameChar, 100);
// TODO encode special characters
String filename = String(filenameChar);
String output = F("<div id=\"{name}\">{icon}<a href=\"{path}\">{name}</a> <a href=\"#\" onclick=\"deleteUrl('{name}'); return false;\">&#x2718;</a>");
if (entry.isDir()) {
//output += F("<a href=\"#\" onclick=\"\">&#x1f4be;</a>");
output.replace("{icon}", F("&#x1f4c2; "));
output.replace("{path}", filename + "/");
} else {
if (isMP3File(filename)) {
output.replace("{icon}", F("&#x266b; "));
} else {
output.replace("{icon}", F(""));
}
output.replace("{path}", filename);
}
output += F("</div>");
output.replace("{name}", filename);
server.sendContent(output);
entry.close();
}
server.sendContent(F("</body></html>"));
server.client().stop();
}
bool loadFromSdCard(String path) {
String dataType = "text/plain";
if (server.hasArg("download")) dataType = "application/octet-stream";
if (path.endsWith(".HTM")) dataType = "text/html";
else if (path.endsWith(".CSS")) dataType = "text/css";
else if (path.endsWith(".JS")) dataType = "application/javascript";
else dataType = "application/octet-stream";
if (server.streamFile(dataFile, dataType) != dataFile.size()) {
Serial.println("Sent less data than expected!");
File dataFile = SD.open(path.c_str());
if (!dataFile) {
Serial.println("File not open");
return false;
}
if (dataFile.isDir()) {
// dataFile.name() will always be "/" for directorys, so we cannot know if we are in the root directory without handing it over
renderDirectory(path);
} else {
if (server.streamFile(dataFile, dataType) != dataFile.size()) {
Serial.println("Sent less data than expected!");
}
}
dataFile.close();
return true;
}
......@@ -330,131 +380,85 @@ void handleStopMp3() {
stopMp3();
returnOK();
}
void handleFileUpload(){
if(server.uri() != "/edit") return;
HTTPUpload& upload = server.upload();
if(upload.status == UPLOAD_FILE_START){
if(SD.exists((char *)upload.filename.c_str())) SD.remove((char *)upload.filename.c_str());
uploadFile = SD.open(upload.filename.c_str(), FILE_WRITE);
Serial.print("Upload: START, filename: "); Serial.println(upload.filename);
} else if(upload.status == UPLOAD_FILE_WRITE){
if(uploadFile) uploadFile.write(upload.buf, upload.currentSize);
Serial.print("Upload: WRITE, Bytes: "); Serial.println(upload.currentSize);
} else if(upload.status == UPLOAD_FILE_END){
if(uploadFile) uploadFile.close();
Serial.print("Upload: END, Size: "); Serial.println(upload.totalSize);
}
}
void deleteRecursive(String path){
File file = SD.open((char *)path.c_str());
if(!file.isDirectory()){
file.close();
SD.remove((char *)path.c_str());
void handleFileUpload() {
// Upload always happens on /
if (server.uri() != "/") {
Serial.println(F("Invalid upload URI"));
return;
}
file.rewindDirectory();
while(true) {
File entry = file.openNextFile();
if (!entry) break;
String entryPath = path + "/" +entry.name();
if(entry.isDirectory()){
entry.close();
deleteRecursive(entryPath);
} else {
entry.close();
SD.remove((char *)entryPath.c_str());
}
yield();
}
HTTPUpload& upload = server.upload();
SD.rmdir((char *)path.c_str());
file.close();
}
if (upload.status == UPLOAD_FILE_START) {
String filename = upload.filename;
void handleDelete(){
if(server.args() == 0) return returnFail("BAD ARGS");
String path = server.arg(0);
if(path == "/" || !SD.exists((char *)path.c_str())) {
returnFail("BAD PATH");
return;
}
deleteRecursive(path);
returnOK();
}
if (!isMP3File(filename)) {
Serial.print(F("Not a MP3: "));
Serial.println(filename);
return;
}
void handleCreate(){
if(server.args() == 0) return returnFail("BAD ARGS");
String path = server.arg(0);
if(path == "/" || SD.exists((char *)path.c_str())) {
returnFail("BAD PATH");
return;
}
if (!filename.startsWith("/")) {
Serial.println(F("Invalid upload target"));
return;
}
if(path.indexOf('.') > 0){
File file = SD.open((char *)path.c_str(), FILE_WRITE);
if(file){
file.write((const char *)0);
file.close();
if (SD.exists((char *)filename.c_str())) {
Serial.println("File " + filename + " already exists. Skipping");
return;
}
} else {
SD.mkdir((char *)path.c_str());
}
returnOK();
}
void printDirectory() {
if(!server.hasArg("dir")) return returnFail("BAD ARGS");
String path = server.arg("dir");
if(path != "/" && !SD.exists((char *)path.c_str())) return returnFail("BAD PATH");
File dir = SD.open((char *)path.c_str());
path = String();
if(!dir.isDirectory()){
dir.close();
return returnFail("NOT DIR");
uploadFile.open((char *)filename.c_str(), FILE_WRITE);
Serial.print(F("UPLOAD_FILE_START: "));
Serial.println(filename);
} else if (upload.status == UPLOAD_FILE_WRITE) {
if (uploadFile.isOpen()) {
uploadFile.write(upload.buf, upload.currentSize);
Serial.print(F("UPLOAD_FILE_WRITE: "));
Serial.println(upload.currentSize);
}
} else if (upload.status == UPLOAD_FILE_END) {
if (uploadFile.isOpen()) {
uploadFile.close();
Serial.print(F("UPLOAD_FILE_END: "));
Serial.println(upload.totalSize);
}
}
dir.rewindDirectory();
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
server.send(200, "text/json", "");
WiFiClient client = server.client();
server.sendContent("[");
for (int cnt = 0; true; ++cnt) {
File entry = dir.openNextFile();
if (!entry)
break;
String output;
if (cnt > 0)
output = ',';
output += "{\"type\":\"";
output += (entry.isDirectory()) ? "dir" : "file";
output += "\",\"name\":\"";
output += entry.name();
output += "\"";
output += "}";
server.sendContent(output);
entry.close();
}
server.sendContent("]");
dir.close();
}
void handleNotFound(){
if(loadFromSdCard(server.uri())) return;
String message = "SDCARD Not Detected\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET)?"GET":"POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i=0; i<server.args(); i++){
message += " NAME:"+server.argName(i) + "\n VALUE:" + server.arg(i) + "\n";
void handleNotFound() {
String path = server.urlDecode(server.uri());
if (server.method() == HTTP_GET) {
if (loadFromSdCard(path)) return;
} else if (server.method() == HTTP_DELETE) {
if (server.uri() == "/" || !SD.exists((char *)path.c_str())) {
returnServerError("BAD PATH: " + server.uri());
return;
}
SdFile file;
file.open(path.c_str());
if (file.isDir()) {
file.rmRfStar();
} else {
file.remove();
}
returnOK();
return;
} else if (server.method() == HTTP_POST) {
if (server.hasArg("folder")) {
Serial.println(F("Creating folder"));
SD.mkdir((char *)server.arg("folder").c_str());
returnOK();
return;
} else if ((server.uri() == "/") || SD.exists((char *)path.c_str())) {
Serial.println(F("Probably got an upload request"));
returnOK();
return;
}
// 404 otherwise
}
server.send(404, "text/plain", message);
Serial.print(message);
server.send(404, "text/plain", F("Not found"));
Serial.println("404: " + path);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment