diff --git a/src/config.cpp b/src/config.cpp new file mode 100644 index 0000000..3919c91 --- /dev/null +++ b/src/config.cpp @@ -0,0 +1,11 @@ +#include +#include +#include +#include + +#include "config.h" + +Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); +AsyncWebServer server(80); +Audio audio; +Preferences preferences; diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..3a90d85 --- /dev/null +++ b/src/config.h @@ -0,0 +1,56 @@ +#ifndef __CONFIG_H__ +#define __CONFIG_H__ + +// Uncomment to switch to AP mode. +// Leave commented for wifi station mode. +// #define B_WIFI_AP + +// DAC +#define I2S_DOUT 32 +#define I2S_BCLK 25 +#define I2S_LRC 27 + +// SD CARD +#define SPI_MISO 18 +#define SPI_MOSI 19 +#define SPI_SCK 23 +#define SD_CS 5 + +// GPIO +#define LED 2 +#define BUTTON 33 + +// Screen +#define SSD1306_NO_SPLASH +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 +#define OLED_RESET -1 +#define SCREEN_ADDRESS 0x3C + +#define SCREEN_MSG_X 0 +#define SCREEN_MSG_Y 24 + +#define LINE_LENGTH 21 + +// Volume +#define VOLUME_MIN 0 +#define VOLUME_MAX 21 + +// Preference keys +#define SELECTED_FILE "selectedFile" +#define CURRENT_VOLUME "currentVolume" +#define WIFI_IP "wifiIP" +#define WIFI_SSID "wifiSSID" +#define WIFI_PASSWORD "wifiPassword" + +#include +#include +#include +#include + +extern Adafruit_SSD1306 display; +extern AsyncWebServer server; +extern Audio audio; +extern Preferences preferences; + +#endif \ No newline at end of file diff --git a/src/creds.dist.h b/src/creds.dist.h index 62fb27c..5e0110d 100644 --- a/src/creds.dist.h +++ b/src/creds.dist.h @@ -1,3 +1,6 @@ // Copy to creds.h or creds_ap.h and edit +#ifndef __CREDS_H__ +#define __CREDS_H__ const char *ssid = "buzzer"; const char *password = "123456789"; +#endif diff --git a/src/main.cpp b/src/main.cpp index 428f897..0490053 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,361 +1,43 @@ #include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include #include -#include -// Toggle on to switch to AP mode. -// Leave commented for wifi station mode. -// #define B_WIFI_AP - -#ifdef B_WIFI_AP -#include "creds_ap.h" -#else -#include "creds.h" -#endif - -// DAC -#define I2S_DOUT 32 -#define I2S_BCLK 25 -#define I2S_LRC 27 - -// SD CARD -#define SPI_MISO 18 -#define SPI_MOSI 19 -#define SPI_SCK 23 -#define SD_CS 5 - -#define LED 2 -#define BUTTON 33 - -#define SSD1306_NO_SPLASH -#define SCREEN_WIDTH 128 -#define SCREEN_HEIGHT 64 -#define OLED_RESET -1 -#define SCREEN_ADDRESS 0x3C - -#define SCREEN_MSG_X 0 -#define SCREEN_MSG_Y 24 - -#define VOLUME_MIN 0 -#define VOLUME_MAX 21 - -Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); - -String selectedFile = ""; - -AsyncWebServer server(80); -Audio audio; -Preferences preferences; +#include "setup.h" +#include "utils.h" +#include "config.h" byte buttonLastState = HIGH; -byte currentVolume = 12; - -bool fileIsValid(String fileName) -{ - return !fileName.startsWith(".") && (fileName.endsWith(".mp3") || fileName.endsWith(".wav")); -} - -void clearMessageArea() -{ - display.fillRect(SCREEN_MSG_X, SCREEN_MSG_Y, SCREEN_WIDTH - SCREEN_MSG_X, SCREEN_HEIGHT - SCREEN_MSG_Y, BLACK); - display.setCursor(SCREEN_MSG_X, SCREEN_MSG_Y); -} - -void displayText(String text) -{ - clearMessageArea(); - display.println(text); - display.display(); -} - -void play() -{ - String path = "/" + selectedFile; - Serial.println("Playing file: " + path); - audio.stopSong(); - audio.connecttoFS(SD, path.c_str()); -} - -void onStop(AsyncWebServerRequest *request) -{ - Serial.println("Stop playing"); - audio.stopSong(); - request->send(200); -} - -void onPlay(AsyncWebServerRequest *request) -{ - play(); - request->send(200); -} - -void onStatus(AsyncWebServerRequest *request) -{ - Serial.println("Status"); - AsyncResponseStream *response = request->beginResponseStream("application/json"); - - StaticJsonDocument<96> root; - root["files"]["selected"] = selectedFile.c_str(); - - JsonObject volume = root.createNestedObject("volume"); - volume["current"] = currentVolume; - volume["canDecrease"] = currentVolume > 0; - volume["canIncrease"] = currentVolume < 21; - - serializeJson(root, *response); - request->send(response); -} - -void onListFiles(AsyncWebServerRequest *request) -{ - Serial.print("List files cursor="); - int cursor = 0; - if (request->hasParam("cursor")) { - String s_cursor = request->getParam("cursor")->value(); - cursor = s_cursor.toInt(); - } - Serial.println(cursor); - - AsyncResponseStream *response = request->beginResponseStream("application/json"); - - StaticJsonDocument<512> root; - root["next"] = -1; - JsonArray files = root.createNestedArray("files"); - File music = SD.open("/"); - File file = music.openNextFile(); - int index = 0; - while (file) - { - String fileName = file.name(); - if (fileIsValid(fileName)) - { - index++; - if (index >= cursor) - files.add(fileName); - } - file.close(); - - if (root.overflowed()) - { - root["next"] = index; - break; - } - file = music.openNextFile(); - } - if (root["next"] == -1) { - root.remove("next"); - } - - serializeJson(root, *response); - request->send(response); -} - -void onSelectFile(AsyncWebServerRequest *request) -{ - Serial.print("Select file: "); - if (request->hasParam("fileName", true)) - { - selectedFile = request->getParam("fileName", true)->value(); - preferences.putString("selectedFile", selectedFile); - Serial.print(selectedFile); - displayText("Selectionne : " + selectedFile); - } - Serial.println(); - onStatus(request); -} - -void onChangeVolume(AsyncWebServerRequest *request) -{ - Serial.print("Volume: "); - if (request->hasParam("modifier", true)) - { - String s_modifier = request->getParam("modifier", true)->value(); - int modifier = s_modifier.toInt(); - currentVolume += modifier; - if (currentVolume > VOLUME_MAX) - currentVolume = VOLUME_MAX; - else if (currentVolume < VOLUME_MIN) - currentVolume = VOLUME_MIN; - preferences.putUChar("currentVolume", currentVolume); - audio.setVolume(currentVolume); - Serial.print(currentVolume); - clearMessageArea(); - display.print("Volume : "); - display.println(currentVolume); - display.display(); - } - Serial.println(); - onStatus(request); -} - -void onUpload(AsyncWebServerRequest *request) -{ - Serial.println("onUpload"); - request->send(200); -} - -void onUploadFile(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) -{ - if (!index) - { - Serial.printf("UploadStart: %s\n", filename.c_str()); - String filePath = "/" + filename; - request->_tempFile = SD.open(filePath, FILE_WRITE); - } - if (!request->_tempFile) - { - Serial.println("Couldn't open file."); - request->redirect("/"); - return; - } - if (len) - { - request->_tempFile.write(data, len); - } - - if (final) - { - Serial.printf("UploadEnd: %s, %u B\n", filename.c_str(), index + len); - request->_tempFile.close(); - request->redirect("/"); - } -} - -void onNotFound(AsyncWebServerRequest *request) -{ - Serial.println("not found"); - request->send(400); -} void setup() { - // Setup serial - Serial.begin(115200); + // Setup GPIO pinMode(LED, OUTPUT); digitalWrite(LED, LOW); - - preferences.begin("buzzer", false); - - // Screen - if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) - { - Serial.println("Display init failed"); - while (true); - } - display.clearDisplay(); - display.setTextSize(1); - display.setTextColor(WHITE, BLACK); - display.setCursor(0, 0); - display.println("Chargement..."); - display.display(); - pinMode(BUTTON, INPUT_PULLUP); buttonLastState = digitalRead(BUTTON); - // Setup SPIFFS - if (!SPIFFS.begin()) - { - Serial.println("SPIFFS error. Exiting."); - display.println("Impossible d'acceder aux fichiers..."); - display.display(); - while (true); - } + Serial.begin(115200); + Serial.println("Serial... OK"); - pinMode(SD_CS, OUTPUT); - digitalWrite(SD_CS, HIGH); - SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI); + preferences.begin("buzzer", false); + Serial.println("Preferences... OK"); - if (!SD.begin(SD_CS)) - { - Serial.println("Error talking to SD card!"); - display.println("Impossible d'acceder a la carte SD..."); - display.display(); - while (true); - } + setupScreen(); + setupSPIFFS(); + setupSDCard(); + setupAudio(); + selectDefaultFile(); + setupWifi(); + setupWebServer(); + diagnosticPrintln("Configuration OK!"); - selectedFile = preferences.getString("selectedFile", ""); - if (selectedFile == "" || !fileIsValid(selectedFile)) - { - File root = SD.open("/"); - File file = root.openNextFile(); - while (file) - { - String fileName = file.name(); - if (fileIsValid(fileName)) - { - selectedFile = fileName; - Serial.println("Selected " + fileName); - display.println("Selectionne : " + fileName); - display.display(); - break; - } - file.close(); - file = root.openNextFile(); - } - root.close(); - } - - display.clearDisplay(); - display.setCursor(0, 0); - // Wifi -#ifdef B_WIFI_AP - Serial.println("Setting up AP..."); - WiFi.softAP(ssid, password); - String wifiIP = WiFi.softAPIP().toString(); - String wifiMode = "AP"; - display.print("Wifi: "); - display.println(ssid); - display.print("Pass: "); - display.println(password); -#else - Serial.print("Connecting to wifi..."); - WiFi.begin(ssid, password); - while (WiFi.status() != WL_CONNECTED) - { - Serial.print("."); - delay(500); - } - Serial.println(); - String wifiIP = WiFi.localIP().toString(); - String wifiMode = "client"; -#endif - String wifiMessage = wifiMode + " IP: " + wifiIP; - Serial.println(wifiMessage); - display.print("IP: "); - display.println(wifiIP); - display.display(); - - // Server - server.on("/play", HTTP_GET, onPlay); - server.on("/stop", HTTP_GET, onStop); - server.on("/status", HTTP_GET, onStatus); - server.on("/list-files", HTTP_GET, onListFiles); - server.on("/select-file", HTTP_POST, onSelectFile); - server.on("/change-volume", HTTP_POST, onChangeVolume); - server.on("/upload", HTTP_POST, onUpload, onUploadFile); - server.onNotFound(onNotFound); - server.serveStatic("/", SPIFFS, "/www/").setDefaultFile("index.html"); - AsyncElegantOTA.begin(&server); - server.begin(); - - Serial.println("Server ready!"); - clearMessageArea(); - display.println("Pret !"); - display.display(); - - // Audio - audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT); - currentVolume = preferences.getUChar("currentVolume", 12); - audio.setVolume(currentVolume); + displayWifiCreds(); + displaySelectedFile(); // Setup is done, light up the LED + Serial.println("All setup & ready to go!"); digitalWrite(LED, HIGH); } diff --git a/src/setup.cpp b/src/setup.cpp new file mode 100644 index 0000000..684af36 --- /dev/null +++ b/src/setup.cpp @@ -0,0 +1,140 @@ +#include +#include // Required by display.begin +#include +#include + +#ifdef B_WIFI_AP +#include "creds_ap.h" +#else +#include "creds.h" +#endif + +#include "config.h" +#include "utils.h" +#include "webHandlers.h" + +void setupScreen() +{ + if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) + { + Serial.println("Display init failed"); + while (true); + } + display.clearDisplay(); + display.setTextSize(1); + display.setTextColor(WHITE, BLACK); + display.setCursor(0, 0); + display.println("Ecran... OK"); + display.display(); + Serial.println("Ecran... OK"); +} + +void setupSPIFFS() +{ + diagnosticPrint("Fichiers... "); + if (!SPIFFS.begin()) + { + Serial.println("SPIFFS error. Exiting."); + display.println("KO"); + display.display(); + while (true); + } + diagnosticPrintln("OK"); +} + +void setupSDCard() +{ + diagnosticPrint("Carte SD... "); + pinMode(SD_CS, OUTPUT); + digitalWrite(SD_CS, HIGH); + SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI); + + if (!SD.begin(SD_CS)) + { + Serial.println("Error talking to SD card!"); + display.println("KO"); + display.display(); + while (true); + } + diagnosticPrintln("OK"); +} + +void setupAudio() +{ + diagnosticPrint("Audio... "); + audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT); + byte volume = preferences.getUChar(CURRENT_VOLUME, 12); + audio.setVolume(volume); + diagnosticPrintln("OK"); +} + +void selectDefaultFile() +{ + String linePrefix = "Son: "; + diagnosticPrint(linePrefix); + String selectedFile = preferences.getString(SELECTED_FILE, ""); + if (selectedFile == "" || !fileIsValid(selectedFile) || !fileExists(selectedFile)) + { + File root = SD.open("/"); + File file = root.openNextFile(); + while (file) + { + String fileName = file.name(); + if (fileIsValid(fileName)) + { + Serial.println("Selected " + fileName); + selectedFile = fileName; + preferences.putString(SELECTED_FILE, fileName); + break; + } + file.close(); + file = root.openNextFile(); + } + root.close(); + } + diagnosticPrintln(selectedFile.substring(0, LINE_LENGTH - linePrefix.length())); +} + +void setupWifi() +{ + diagnosticPrint("Wifi..."); + preferences.putString(WIFI_SSID, ssid); + preferences.putString(WIFI_PASSWORD, password); +#ifdef B_WIFI_AP + WiFi.softAP(ssid, password); + String wifiIP = WiFi.softAPIP().toString(); +#else + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) + { + Serial.print("."); + delay(500); + } + String wifiIP = WiFi.localIP().toString(); +#endif + diagnosticPrintln(" OK"); + Serial.print("IP: "); + Serial.println(wifiIP); + preferences.putString(WIFI_IP, wifiIP); +} + +void setupWebServer() +{ + Serial.print("Server... "); + display.print("Serveur... "); + display.display(); + server.on("/play", HTTP_GET, onPlay); + server.on("/stop", HTTP_GET, onStop); + server.on("/status", HTTP_GET, onStatus); + server.on("/list-files", HTTP_GET, onListFiles); + server.on("/select-file", HTTP_POST, onSelectFile); + server.on("/change-volume", HTTP_POST, onChangeVolume); + server.on("/upload", HTTP_POST, onUpload, onUploadFile); + server.onNotFound(onNotFound); + server.serveStatic("/", SPIFFS, "/www/").setDefaultFile("index.html"); + AsyncElegantOTA.begin(&server); + server.begin(); + Serial.println("OK"); + display.println("OK"); + display.display(); +} \ No newline at end of file diff --git a/src/setup.h b/src/setup.h new file mode 100644 index 0000000..7ca9c24 --- /dev/null +++ b/src/setup.h @@ -0,0 +1,17 @@ +#ifndef __SETUP_H__ +#define __SETUP_H__ + +#include +#include +#include +#include + +void setupScreen(); +void setupSPIFFS(); +void setupSDCard(); +void setupAudio(); +void selectDefaultFile(); +void setupWifi(); +void setupWebServer(); + +#endif diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..4488e12 --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include + +#include "config.h" + +bool fileIsValid(String fileName) +{ + return !fileName.startsWith(".") && (fileName.endsWith(".mp3") || fileName.endsWith(".wav")); +} + +bool fileExists(String fileName) +{ + return SD.exists("/" + fileName); +} + +void clearMessageArea() +{ + display.fillRect(SCREEN_MSG_X, SCREEN_MSG_Y, SCREEN_WIDTH - SCREEN_MSG_X, SCREEN_HEIGHT - SCREEN_MSG_Y, BLACK); + display.setCursor(SCREEN_MSG_X, SCREEN_MSG_Y); +} + +void displayText(String text) +{ + clearMessageArea(); + display.println(text); + display.display(); +} + +void play() +{ + String selectedFile = preferences.getString(SELECTED_FILE); + String path = "/" + selectedFile; + Serial.println("Playing file: " + path); + audio.stopSong(); + audio.connecttoFS(SD, path.c_str()); +} + + +void diagnosticPrint(String text) +{ + Serial.print(text); + display.print(text); + display.display(); +} +void diagnosticPrintln(String text) +{ + Serial.println(text); + display.println(text); + display.display(); +} + +void displayWifiCreds() +{ + display.clearDisplay(); + display.setCursor(0, 0); + + String ssid = preferences.getString(WIFI_SSID, ""); + display.print("Wifi: "); + display.println(ssid); + + display.print("Pass: "); +#ifdef B_WIFI_AP + String password = preferences.getString(WIFI_PASSWORD, ""); + display.println(password); +#else + display.println("****"); +#endif + + display.print("IP: "); + String ip = preferences.getString(WIFI_IP, ""); + display.println(ip); + display.display(); +} + +void displaySelectedFile() +{ + String prefix = "Son: "; + displayText(prefix + preferences.getString(SELECTED_FILE, "").substring(0, LINE_LENGTH - prefix.length())); +} + +void displayVolume() { + String volume = String(preferences.getUChar(CURRENT_VOLUME)); + displayText("Volume: " + volume); +} \ No newline at end of file diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..29d4abf --- /dev/null +++ b/src/utils.h @@ -0,0 +1,20 @@ +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#include +#include +#include + +bool fileIsValid(String fileName); +bool fileExists(String fileName); +void clearMessageArea(); +void displayText(String text); +void play(); + +void displayWifiCreds(); +void displaySelectedFile(); + +void diagnosticPrint(String text); +void diagnosticPrintln(String text); + +#endif diff --git a/src/webHandlers.cpp b/src/webHandlers.cpp new file mode 100644 index 0000000..ca6a6eb --- /dev/null +++ b/src/webHandlers.cpp @@ -0,0 +1,161 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "utils.h" + +void onStop(AsyncWebServerRequest *request) +{ + Serial.println("Stop playing"); + audio.stopSong(); + request->send(200); +} + +void onPlay(AsyncWebServerRequest *request) +{ + play(); + request->send(200); +} + +void onStatus(AsyncWebServerRequest *request) +{ + Serial.println("Status"); + AsyncResponseStream *response = request->beginResponseStream("application/json"); + + StaticJsonDocument<96> root; + root["files"]["selected"] = preferences.getString(SELECTED_FILE, "").c_str(); + + JsonObject volume = root.createNestedObject("volume"); + byte currentVolume = preferences.getUChar(CURRENT_VOLUME); + volume["current"] = currentVolume; + volume["canDecrease"] = currentVolume > 0; + volume["canIncrease"] = currentVolume < 21; + + serializeJson(root, *response); + request->send(response); +} + +void onListFiles(AsyncWebServerRequest *request) +{ + Serial.print("List files cursor="); + int cursor = 0; + if (request->hasParam("cursor")) { + String s_cursor = request->getParam("cursor")->value(); + cursor = s_cursor.toInt(); + } + Serial.println(cursor); + + AsyncResponseStream *response = request->beginResponseStream("application/json"); + + StaticJsonDocument<512> root; + root["next"] = -1; + JsonArray files = root.createNestedArray("files"); + File music = SD.open("/"); + File file = music.openNextFile(); + int index = 0; + while (file) + { + String fileName = file.name(); + if (fileIsValid(fileName)) + { + index++; + if (index >= cursor) + files.add(fileName); + } + file.close(); + + if (root.overflowed()) + { + root["next"] = index; + break; + } + file = music.openNextFile(); + } + if (root["next"] == -1) { + root.remove("next"); + } + + serializeJson(root, *response); + request->send(response); +} + +void onSelectFile(AsyncWebServerRequest *request) +{ + Serial.print("Select file: "); + if (request->hasParam("fileName", true)) + { + String selectedFile = request->getParam("fileName", true)->value(); + preferences.putString(SELECTED_FILE, selectedFile); + Serial.print(selectedFile); + displaySelectedFile(); + } + Serial.println(); + onStatus(request); +} + +void onChangeVolume(AsyncWebServerRequest *request) +{ + Serial.print("Volume: "); + if (request->hasParam("modifier", true)) + { + String s_modifier = request->getParam("modifier", true)->value(); + int modifier = s_modifier.toInt(); + byte currentVolume = preferences.getUChar(CURRENT_VOLUME); + currentVolume += modifier; + if (currentVolume > VOLUME_MAX) + currentVolume = VOLUME_MAX; + else if (currentVolume < VOLUME_MIN) + currentVolume = VOLUME_MIN; + preferences.putUChar(CURRENT_VOLUME, currentVolume); + audio.setVolume(currentVolume); + Serial.print(currentVolume); + String s_volume = String(currentVolume); + displayText("Volume : " + s_volume); + } + Serial.println(); + onStatus(request); +} + +void onUpload(AsyncWebServerRequest *request) +{ + Serial.println("onUpload"); + request->send(200); +} + +void onUploadFile(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) +{ + if (!index) + { + Serial.printf("UploadStart: %s\n", filename.c_str()); + String filePath = "/" + filename; + request->_tempFile = SD.open(filePath, FILE_WRITE); + } + if (!request->_tempFile) + { + Serial.println("Couldn't open file."); + request->redirect("/"); + return; + } + if (len) + { + request->_tempFile.write(data, len); + } + + if (final) + { + Serial.printf("UploadEnd: %s, %u B\n", filename.c_str(), index + len); + request->_tempFile.close(); + request->redirect("/"); + } +} + +void onNotFound(AsyncWebServerRequest *request) +{ + Serial.println("not found"); + request->send(400); +} diff --git a/src/webHandlers.h b/src/webHandlers.h new file mode 100644 index 0000000..cecc260 --- /dev/null +++ b/src/webHandlers.h @@ -0,0 +1,20 @@ +#ifndef __MAIN_H__ +#define __MAIN_H__ + +#include +#include +#include +#include + + +void onStop(AsyncWebServerRequest *request); +void onPlay(AsyncWebServerRequest *request); +void onStatus(AsyncWebServerRequest *request); +void onListFiles(AsyncWebServerRequest *request); +void onSelectFile(AsyncWebServerRequest *request); +void onChangeVolume(AsyncWebServerRequest *request); +void onUpload(AsyncWebServerRequest *request); +void onUploadFile(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); +void onNotFound(AsyncWebServerRequest *request); + +#endif \ No newline at end of file