Refactor & split into multiple files

This commit is contained in:
Gabriel Augendre 2023-01-06 15:22:32 +01:00
parent 30e46ebfab
commit a9ca2150d4
10 changed files with 536 additions and 339 deletions

11
src/config.cpp Normal file
View file

@ -0,0 +1,11 @@
#include <Adafruit_SSD1306.h>
#include <ESPAsyncWebServer.h>
#include <Audio.h>
#include <Preferences.h>
#include "config.h"
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
AsyncWebServer server(80);
Audio audio;
Preferences preferences;

56
src/config.h Normal file
View file

@ -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 <Adafruit_SSD1306.h>
#include <ESPAsyncWebServer.h>
#include <Audio.h>
#include <Preferences.h>
extern Adafruit_SSD1306 display;
extern AsyncWebServer server;
extern Audio audio;
extern Preferences preferences;
#endif

View file

@ -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

View file

@ -1,361 +1,43 @@
#include <Arduino.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>
#include <AsyncJson.h>
#include <Audio.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ESPAsyncWebServer.h>
#include <Audio.h>
#include <Preferences.h>
#include <AsyncElegantOTA.h>
// 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);
}

140
src/setup.cpp Normal file
View file

@ -0,0 +1,140 @@
#include <SPIFFS.h>
#include <Wire.h> // Required by display.begin
#include <AsyncElegantOTA.h>
#include <Audio.h>
#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();
}

17
src/setup.h Normal file
View file

@ -0,0 +1,17 @@
#ifndef __SETUP_H__
#define __SETUP_H__
#include <ESPAsyncWebServer.h>
#include <Adafruit_SSD1306.h>
#include <Preferences.h>
#include <Audio.h>
void setupScreen();
void setupSPIFFS();
void setupSDCard();
void setupAudio();
void selectDefaultFile();
void setupWifi();
void setupWebServer();
#endif

87
src/utils.cpp Normal file
View file

@ -0,0 +1,87 @@
#include <Arduino.h>
#include <Audio.h>
#include <Preferences.h>
#include <Adafruit_SSD1306.h>
#include <SD.h>
#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);
}

20
src/utils.h Normal file
View file

@ -0,0 +1,20 @@
#ifndef __UTILS_H__
#define __UTILS_H__
#include <Arduino.h>
#include <Adafruit_SSD1306.h>
#include <Preferences.h>
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

161
src/webHandlers.cpp Normal file
View file

@ -0,0 +1,161 @@
#include <Arduino.h>
#include <ESPAsyncWebServer.h>
#include <AsyncJson.h>
#include <ArduinoJson.h>
#include <Adafruit_SSD1306.h>
#include <Preferences.h>
#include <SD.h>
#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);
}

20
src/webHandlers.h Normal file
View file

@ -0,0 +1,20 @@
#ifndef __MAIN_H__
#define __MAIN_H__
#include <Arduino.h>
#include <ESPAsyncWebServer.h>
#include <Adafruit_SSD1306.h>
#include <Preferences.h>
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