From af7164094b4ea732d39409a1f62f1827e78defb1 Mon Sep 17 00:00:00 2001 From: grch101 Date: Tue, 12 Jan 2021 20:03:51 +0100 Subject: [PATCH] add first draft of the bluetooth feature. swtiching between wifi and bt is done via wifiEnabled flag and reboot --- platformio.ini | 14 +- src/main.cpp | 395 ++++++++++++------------------------------------- 2 files changed, 101 insertions(+), 308 deletions(-) diff --git a/platformio.ini b/platformio.ini index e1c458199..c159122f1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -21,7 +21,7 @@ lib_deps_external = ESP Async WebServer https://github.com/me-no-dev/AsyncTCP https://github.com/bblanchon/ArduinoJson.git -; https://github.com/pschatzmann/ESP32-A2DP.git + https://github.com/pschatzmann/ESP32-A2DP.git [env:common] platform = espressif32 @@ -36,7 +36,7 @@ platform = espressif32 board = esp-wrover-kit framework = arduino monitor_speed = 115200 -board_build.partitions = no_ota.csv +board_build.partitions = huge_app.csv build_flags = -DHAL=2 -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue @@ -52,7 +52,7 @@ platform = espressif32 board = lolin32 framework = arduino monitor_speed = 115200 -board_build.partitions = no_ota.csv +board_build.partitions = huge_app.csv lib_deps = ${common.lib_deps_builtin} ${common.lib_deps_external} @@ -68,7 +68,7 @@ platform = espressif32 board = lolin_d32 framework = arduino monitor_speed = 115200 -board_build.partitions = no_ota.csv +board_build.partitions = huge_app.csv lib_deps = ${common.lib_deps_builtin} ${common.lib_deps_external} @@ -84,7 +84,7 @@ platform = espressif32 board = lolin_d32_pro framework = arduino monitor_speed = 115200 -board_build.partitions = no_ota.csv +board_build.partitions = huge_app.csv lib_deps = ${common.lib_deps_builtin} ${common.lib_deps_external} @@ -102,7 +102,7 @@ platform = espressif32 board = nodemcu-32s framework = arduino monitor_speed = 115200 -board_build.partitions = no_ota.csv +board_build.partitions = huge_app.csv lib_deps = ${common.lib_deps_builtin} ${common.lib_deps_external} @@ -118,7 +118,7 @@ platform = espressif32 board = az-delivery-devkit-v4 framework = arduino monitor_speed = 115200 -board_build.partitions = no_ota.csv +board_build.partitions = huge_app.csv lib_deps = ${common.lib_deps_builtin} ${common.lib_deps_external} diff --git a/src/main.cpp b/src/main.cpp index 8dbd8245c..2dde1b30b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,6 +23,7 @@ #include "ESP32FtpServer.h" #endif #ifdef BLUETOOTH_ENABLE + #include "esp_bt.h" #include "BluetoothA2DPSink.h" #endif #include "Audio.h" @@ -84,7 +85,7 @@ char *logBuf = (char*) calloc(serialLoglength, sizeof(char)); // Buffer for all #endif #ifdef BLUETOOTH_ENABLE - BluetoothA2DPSink a2dp_sink; + BluetoothA2DPSink *a2dp_sink; #endif #ifdef MEASURE_BATTERY_VOLTAGE @@ -277,6 +278,8 @@ AsyncEventSource events("/events"); TaskHandle_t mp3Play; TaskHandle_t rfid; +TaskHandle_t wifiTaskHandle; +TaskHandle_t bluetoothTaskHandle; #ifdef NEOPIXEL_ENABLE TaskHandle_t LED; @@ -358,13 +361,6 @@ void freeMultiCharArray(char **arr, const uint32_t cnt); uint8_t getRepeatMode(void); bool getWifiEnableStatusFromNVS(void); void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); -void convertUtf8ToAscii(String utf8String, char *asciiString); -void explorerHandleFileUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); -void explorerHandleListRequest(AsyncWebServerRequest *request); -void explorerHandleDeleteRequest(AsyncWebServerRequest *request); -void explorerHandleCreateRequest(AsyncWebServerRequest *request); -void explorerHandleRenameRequest(AsyncWebServerRequest *request); -void explorerHandleAudioRequest(AsyncWebServerRequest *request); void headphoneVolumeManager(void); bool isNumber(const char *str); void loggerNl(const char *str, const uint8_t logLevel); @@ -1453,10 +1449,7 @@ void playAudio(void *parameter) { // If we're in audiobook-mode and apply a modification-card, we don't // want to save lastPlayPosition for the mod-card but for the card that holds the playlist - if(currentRfidTagId != NULL){ - strncpy(playProperties.playRfidTag, currentRfidTagId, sizeof(playProperties.playRfidTag) / sizeof(playProperties.playRfidTag[0])); - } - + strncpy(playProperties.playRfidTag, currentRfidTagId, sizeof(playProperties.playRfidTag) / sizeof(playProperties.playRfidTag[0])); } if (playProperties.trackFinished) { playProperties.trackFinished = false; @@ -3596,7 +3589,7 @@ void webserverStart(void) { wServer.addHandler(&events); wServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { - request->send(FSystem, "/management.html", String(), false, templateProcessor); + request->send_P(200, "text/html", management_HTML, templateProcessor); }); wServer.on("/upload", HTTP_POST, [](AsyncWebServerRequest *request){ @@ -3613,20 +3606,6 @@ void webserverStart(void) { request->send(FSystem, DIRECTORY_INDEX_FILE, "application/json"); }); - wServer.on("/explorer", HTTP_GET, explorerHandleListRequest); - - wServer.on("/explorer", HTTP_POST, [](AsyncWebServerRequest *request) { - request->send(200); - }, explorerHandleFileUpload); - - wServer.on("/explorer", HTTP_DELETE, explorerHandleDeleteRequest); - - wServer.on("/explorer", HTTP_PUT, explorerHandleCreateRequest); - - wServer.on("/explorer", HTTP_PATCH, explorerHandleRenameRequest); - - wServer.on("/exploreraudio", HTTP_POST, explorerHandleAudioRequest); - wServer.onNotFound(notFound); // allow cors for local debug @@ -3707,221 +3686,6 @@ void webserverStart(void) { return true; } -// Conversion routine for http UTF-8 strings -void convertUtf8ToAscii(String utf8String, char *asciiString) { - uint16_t i = 0, j=0; - bool f_C3_seen = false; - - while(utf8String[i] != 0) { // convert UTF8 to ASCII - if(utf8String[i] == 195){ // C3 - i++; - f_C3_seen = true; - continue; - } - asciiString[j] = utf8String[i]; - if(asciiString[j] > 128 && asciiString[j] < 189 && f_C3_seen == true) { - asciiString[j] = asciiString[j] + 64; // found a related ASCII sign - f_C3_seen = false; - } - i++; j++; - } - asciiString[j] = 0; - -} - -// Handles file upload request from the explorer -// requires a GET parameter path, as directory path to the file -void explorerHandleFileUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { - if (!index) { - if (request->hasParam("path")) { - AsyncWebParameter *param = request->getParam("path"); - String utf8FilePath = param->value() + "/" + filename; - char asciiFilePath[256]; - convertUtf8ToAscii(utf8FilePath, asciiFilePath); - request->_tempFile = FSystem.open( asciiFilePath, "w"); - } else { - request->_tempFile = FSystem.open("/" + filename, "w"); - } - Serial.println("write file"); - // open the file on first call and store the file handle in the request object - } - - if (len) { - // stream the incoming chunk to the opened file - request->_tempFile.write(data, len); - } - - if (final) { - // close the file handle as the upload is now done - request->_tempFile.close(); - } -} - -// Sends a list of the content of a directory as JSON file -// requires a GET parameter path for the directory -void explorerHandleListRequest(AsyncWebServerRequest *request) { - DynamicJsonDocument jsonBuffer(8192); - //StaticJsonDocument<4096> jsonBuffer; - String serializedJsonString; - AsyncWebParameter *param; - char asciiFilePath[256]; - JsonArray obj = jsonBuffer.createNestedArray(); - File root; - if(request->hasParam("path")){ - param = request->getParam("path"); - convertUtf8ToAscii(param->value(), asciiFilePath); - root = FSystem.open(asciiFilePath); - } else { - root = FSystem.open("/"); - } - - if (!root) { - snprintf(logBuf, serialLoglength, (char *) FPSTR(failedToOpenDirectory)); - loggerNl(logBuf, LOGLEVEL_DEBUG); - return; - } - - if (!root.isDirectory()) { - snprintf(logBuf, serialLoglength, (char *) FPSTR(notADirectory)); - loggerNl(logBuf, LOGLEVEL_DEBUG); - return; - } - - File file = root.openNextFile(); - - while(file) { - JsonObject entry = obj.createNestedObject(); - std::string path = file.name(); - std::string fileName = path.substr(path.find_last_of("/") + 1); - - entry["name"] = fileName; - entry["dir"].set(file.isDirectory()); - - file = root.openNextFile(); - - } - - serializeJson(obj, serializedJsonString); - request->send(200, "application/json; charset=iso-8859-1", serializedJsonString); -} - -// Handles delete request of a file or directory -// requires a GET parameter path to the file or directory -void explorerHandleDeleteRequest(AsyncWebServerRequest *request) { - File file; - bool isDir; - AsyncWebParameter *param; - char asciiFilePath[256]; - if(request->hasParam("path")){ - param = request->getParam("path"); - convertUtf8ToAscii(param->value(), asciiFilePath); - if(FSystem.exists(asciiFilePath)) { - file = FSystem.open(asciiFilePath); - isDir = file.isDirectory(); - file.close(); - if(isDir) { - if(FSystem.rmdir(asciiFilePath)) { - snprintf(logBuf, serialLoglength, "DELETE: %s deleted", asciiFilePath); - loggerNl(logBuf, LOGLEVEL_INFO); - } else { - snprintf(logBuf, serialLoglength, "DELETE: Cannot delete %s", asciiFilePath); - loggerNl(logBuf, LOGLEVEL_ERROR); - } - } else { - if(FSystem.remove(asciiFilePath)) { - snprintf(logBuf, serialLoglength, "DELETE: %s deleted", asciiFilePath); - loggerNl(logBuf, LOGLEVEL_INFO); - } else { - snprintf(logBuf, serialLoglength, "DELETE: Cannot delete %s", asciiFilePath); - loggerNl(logBuf, LOGLEVEL_ERROR); - } - } - } else { - snprintf(logBuf, serialLoglength, "DELETE: Path %s does not exist", asciiFilePath); - loggerNl(logBuf, LOGLEVEL_ERROR); - } - } else { - loggerNl("DELETE: No path variable set", LOGLEVEL_ERROR); - } - request->send(200); -} - -// Handles create request of a directory -// requires a GET parameter path to the new directory -void explorerHandleCreateRequest(AsyncWebServerRequest *request) { - AsyncWebParameter *param; - char asciiFilePath[256]; - if(request->hasParam("path")){ - param = request->getParam("path"); - convertUtf8ToAscii(param->value(), asciiFilePath); - if(FSystem.mkdir(asciiFilePath)) { - snprintf(logBuf, serialLoglength, "CREATE: %s created", asciiFilePath); - loggerNl(logBuf, LOGLEVEL_INFO); - } else { - snprintf(logBuf, serialLoglength, "CREATE: Cannot create %s", asciiFilePath); - loggerNl(logBuf, LOGLEVEL_ERROR); - } - } else { - loggerNl("CREATE: No path variable set", LOGLEVEL_ERROR); - } - request->send(200); -} - -// Handles rename request of a file or directory -// requires a GET parameter srcpath to the old file or directory name -// requires a GET parameter dstpath to the new file or directory name -void explorerHandleRenameRequest(AsyncWebServerRequest *request) { - AsyncWebParameter *srcPath; - AsyncWebParameter *dstPath; - char srcFullFilePath[256]; - char dstFullFilePath[256]; - if(request->hasParam("srcpath") && request->hasParam("dstpath")) { - srcPath = request->getParam("srcpath"); - dstPath = request->getParam("dstpath"); - convertUtf8ToAscii(srcPath->value(), srcFullFilePath); - convertUtf8ToAscii(dstPath->value(), dstFullFilePath); - if(FSystem.exists(srcFullFilePath)) { - if(FSystem.rename(srcFullFilePath, dstFullFilePath)) { - snprintf(logBuf, serialLoglength, "RENAME: %s renamed to %s", srcFullFilePath, dstFullFilePath); - loggerNl(logBuf, LOGLEVEL_INFO); - } else { - snprintf(logBuf, serialLoglength, "RENAME: Cannot rename %s", srcFullFilePath); - loggerNl(logBuf, LOGLEVEL_ERROR); - } - } else { - snprintf(logBuf, serialLoglength, "RENAME: Path %s does not exitst", srcFullFilePath); - loggerNl(logBuf, LOGLEVEL_ERROR); - } - } else { - loggerNl("RENAME: No path variable set", LOGLEVEL_ERROR); - } - - request->send(200); -} - -// Handles audio play requests -// requires a GET parameter path to the audio file or directory -// requires a GET parameter playmode -void explorerHandleAudioRequest(AsyncWebServerRequest *request) { - AsyncWebParameter *param; - String playModeString; - uint32_t playMode; - char asciiFilePath[256]; - if(request->hasParam("path") && request->hasParam("playmode")) { - param = request->getParam("path"); - convertUtf8ToAscii(param->value(), asciiFilePath); - param = request->getParam("playmode"); - playModeString = param->value(); - - playMode = atoi(playModeString.c_str()); - trackQueueDispatcher(asciiFilePath,0,playMode,0); - } else { - loggerNl("AUDIO: No path variable set", LOGLEVEL_ERROR); - } - - request->send(200); -} - // Handles uploaded backup-file and writes valid entries into NVS void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { @@ -3965,6 +3729,42 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, #endif } +void wifiTask(void *parameter) { + + wifiManager(); + + for(;;) { + webserverStart(); + #ifdef FTP_ENABLE + ftpManager(); + #endif + if (wifiManager() == WL_CONNECTED) { + #ifdef MQTT_ENABLE + if (enableMqtt) { + reconnect(); + MQTTclient.loop(); + postHeartbeatViaMqtt(); + } + #endif + #ifdef FTP_ENABLE + if (ftpEnableLastStatus && ftpEnableCurrentStatus) { + ftpSrv->handleFTP(); + } + #endif + } + #ifdef FTP_ENABLE + if (ftpEnableLastStatus && ftpEnableCurrentStatus) { + if (ftpSrv->isConnected()) { + lastTimeActiveTimestamp = millis(); // Re-adjust timer while client is connected to avoid ESP falling asleep + } + } + #endif + ws.cleanupClients(); + vTaskDelay(portTICK_PERIOD_MS/2); + esp_task_wdt_reset(); // Don't forget to feed the dog! + } + vTaskDelete(NULL); +} void setup() { Serial.begin(115200); @@ -4111,17 +3911,6 @@ void setup() { headphoneLastDetectionState = digitalRead(HP_DETECT); #endif - #ifdef BLUETOOTH_ENABLE - i2s_pin_config_t pin_config = { - .bck_io_num = I2S_BCLK, - .ws_io_num = I2S_LRC, - .data_out_num = I2S_DOUT, - .data_in_num = I2S_PIN_NO_CHANGE - }; - a2dp_sink.set_pin_config(pin_config); - a2dp_sink.start("Tonuino"); - #endif - // Create queues volumeQueue = xQueueCreate(1, sizeof(int)); if (volumeQueue == NULL) { @@ -4339,27 +4128,16 @@ void setup() { timerAlarmWrite(timer, 1000, true); // 1000 Hz timerAlarmEnable(timer); - // Create tasks - xTaskCreatePinnedToCore( - rfidScanner, /* Function to implement the task */ - "rfidhandling", /* Name of the task */ - 2000, /* Stack size in words */ - NULL, /* Task input parameter */ - 1, /* Priority of the task */ - &rfid, /* Task handle. */ - 0 /* Core where the task should run */ - ); - - xTaskCreatePinnedToCore( - playAudio, /* Function to implement the task */ - "mp3play", /* Name of the task */ - 11000, /* Stack size in words */ - NULL, /* Task input parameter */ - 2 | portPRIVILEGE_BIT, /* Priority of the task */ - &mp3Play, /* Task handle. */ - 1 /* Core where the task should run */ - ); - + // Create tasks + xTaskCreatePinnedToCore( + rfidScanner, /* Function to implement the task */ + "rfidhandling", /* Name of the task */ + 2000, /* Stack size in words */ + NULL, /* Task input parameter */ + 1, /* Priority of the task */ + &rfid, /* Task handle. */ + 0 /* Core where the task should run */ + ); // Activate internal pullups for all buttons pinMode(DREHENCODER_BUTTON, INPUT_PULLUP); @@ -4381,7 +4159,44 @@ void setup() { #endif wifiEnabled = getWifiEnableStatusFromNVS(); - wifiManager(); + #ifdef BLUETOOTH_ENABLE + if( wifiEnabled ) { + + esp_bt_mem_release(ESP_BT_MODE_BTDM); + #endif + xTaskCreatePinnedToCore( + playAudio, /* Function to implement the task */ + "mp3play", /* Name of the task */ + 10000, /* Stack size in words */ + NULL, /* Task input parameter */ + 2 | portPRIVILEGE_BIT, /* Priority of the task */ + &mp3Play, /* Task handle. */ + 1 /* Core where the task should run */ + ); + + xTaskCreate( + wifiTask, /* Function to implement the task */ + "wifiTask", /* Name of the task */ + 8000, /* Stack size in words */ + NULL, /* Task input parameter */ + 1 | portPRIVILEGE_BIT, /* Priority of the task */ + &wifiTaskHandle /* Task handle. */ + ); + + #ifdef BLUETOOTH_ENABLE + + } else { + a2dp_sink = new BluetoothA2DPSink(); + i2s_pin_config_t pin_config = { + .bck_io_num = I2S_BCLK, + .ws_io_num = I2S_LRC, + .data_out_num = I2S_DOUT, + .data_in_num = I2S_PIN_NO_CHANGE + }; + a2dp_sink->set_pin_config(pin_config); + a2dp_sink->start("Tonuino"); + } + #endif lastTimeActiveTimestamp = millis(); // initial set after boot @@ -4399,10 +4214,7 @@ void setup() { void loop() { - webserverStart(); - #ifdef FTP_ENABLE - ftpManager(); - #endif + #ifdef HEADPHONE_ADJUST_ENABLE headphoneVolumeManager(); #endif @@ -4415,31 +4227,12 @@ void loop() { sleepHandler(); deepSleepManager(); rfidPreferenceLookupHandler(); - if (wifiManager() == WL_CONNECTED) { - #ifdef MQTT_ENABLE - if (enableMqtt) { - reconnect(); - MQTTclient.loop(); - postHeartbeatViaMqtt(); - } - #endif - #ifdef FTP_ENABLE - if (ftpEnableLastStatus && ftpEnableCurrentStatus) { - ftpSrv->handleFTP(); - } - #endif - } - #ifdef FTP_ENABLE - if (ftpEnableLastStatus && ftpEnableCurrentStatus) { - if (ftpSrv->isConnected()) { - lastTimeActiveTimestamp = millis(); // Re-adjust timer while client is connected to avoid ESP falling asleep - } - } - #endif + #ifdef PLAY_LAST_RFID_AFTER_REBOOT recoverLastRfidPlayed(); #endif - ws.cleanupClients(); + vTaskDelay(portTICK_PERIOD_MS*10); + }