From 651a4e85106dd2f34836a3a69f0b8888a9f5312e Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Mon, 12 Aug 2019 20:15:00 +0200 Subject: [PATCH] Added sleep mode for VS1053, HTTP server, tar upload, JSON status, RFID card removal debouncing, ... --- include/config.h | 12 ++++ include/controller.h | 2 + include/http_server.h | 28 ++++++++ include/player.h | 16 ++++- src/controller.cpp | 14 +++- src/http_server.cpp | 154 ++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 11 +++ src/player.cpp | 62 ++++++++++++++--- 8 files changed, 286 insertions(+), 13 deletions(-) create mode 100644 include/http_server.h create mode 100644 src/http_server.cpp diff --git a/include/config.h b/include/config.h index ed5ea67..f3f7e4d 100644 --- a/include/config.h +++ b/include/config.h @@ -21,6 +21,12 @@ #define TX 1 #define SHOW_DEBUG +//#define SHOW_TRACE + +#define WIFI_SSID "Schlenz" +#define WIFI_PASS "1410WischlingenPanda" + +#define VS1053_SLEEP_DELAY 5000 #define PIN_SD_CS D4 @@ -56,3 +62,9 @@ #else #define DEBUG(x, ...) while(0) {} #endif + +#ifdef SHOW_TRACE + #define TRACE(x, ...) Serial.printf(x, ##__VA_ARGS__) +#else + #define TRACE(x, ...) while(0) {} +#endif diff --git a/include/controller.h b/include/controller.h index 4b8ae2b..31edc72 100644 --- a/include/controller.h +++ b/include/controller.h @@ -16,6 +16,7 @@ private: void _check_buttons(); uint32_t _get_rfid_card_uid(); uint32_t _last_rfid_card_uid = 0; + uint8_t _no_rfid_card_count = 0; Player* _player; unsigned long _last_rfid_scan_at = 0; String _serial_buffer = String(); @@ -26,5 +27,6 @@ private: bool _check_button(uint8_t btn); public: Controller(Player* p, MCP* m); + String rfid_uid() { return String(_last_rfid_card_uid, HEX); } void loop(); }; diff --git a/include/http_server.h b/include/http_server.h new file mode 100644 index 0000000..18945e3 --- /dev/null +++ b/include/http_server.h @@ -0,0 +1,28 @@ +#pragma once +#include +#include "spi_master.h" +#include "player.h" +#include "controller.h" +#include +#include +#include +#include + +class HTTPServer { +private: + ESP8266WebServer* _http_server; + void _handle_upload(); + uint16_t _chunk_length; + uint8_t* _chunk; + uint32_t _file_size; + uint32_t _file_size_done; + bool _need_header; + uint32_t _upload_position; + void _handle_index(); + void _handle_status(); + Player* _player; + Controller* _controller; +public: + HTTPServer(Player* p, Controller* c); + void loop(); +}; diff --git a/include/player.h b/include/player.h index 2d7a612..d8a7df0 100644 --- a/include/player.h +++ b/include/player.h @@ -10,6 +10,7 @@ #define SCI_STATUS 0x01 #define SCI_CLOCKF 0x03 #define SCI_DECODE_TIME 0x04 +#define SCI_AUDATA 0x05 #define SCI_VOL 0x0B #define SCI_WRAMADDR 0x07 #define SCI_WRAM 0x06 @@ -30,7 +31,8 @@ class Player { private: enum state { uninitialized, idle, playing, stopping, - system_sound_while_playing, system_sound_while_stopped }; + system_sound_while_playing, system_sound_while_stopped, + sleeping }; struct album_state { uint8_t index; uint32_t position; @@ -51,12 +53,15 @@ private: void _flush(uint count, int8_t fill_byte); void _set_last_track(const char* album, uint8_t track, uint32_t position); std::map _last_tracks; + String _random_album(); void _play_file(String filename, uint32_t offset); uint32_t _id3_tag_offset(File f); void _finish_playing(); void _finish_stopping(); void _mute(); void _unmute(); + void _sleep(); + void _wakeup(); SPISettings _spi_settings_slow = SPISettings(250000, MSBFIRST, SPI_MODE0); SPISettings _spi_settings_fast = SPISettings(4000000, MSBFIRST, SPI_MODE0); @@ -75,18 +80,25 @@ private: uint16_t _stop_delay; uint32_t _skip_to; MCP* _mcp; + unsigned long _stopped_at; public: Player(MCP* m); void vol_up(); void vol_down(); void track_next(); void track_prev(); + bool is_playing(); bool play_album(String album); + void play_random_album(); bool play_song(String album, uint8_t song_index, uint32_t offset=0); void play_system_sound(String filename); void stop(); bool loop(); void set_volume(uint8_t vol, bool save = true); - std::list ls(String path); + std::list ls(String path, bool withFiles=true, bool withDirs=true, bool withHidden=false); + String album() { return _playing_album; } + uint8_t track() { return _playing_index; } + uint32_t position() { return _current_play_position; } + uint8_t volume() { return _volume; } }; diff --git a/src/controller.cpp b/src/controller.cpp index 05a5fd6..1f44c61 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -53,12 +53,18 @@ void Controller::_check_rfid() { uint32_t uid = _get_rfid_card_uid(); if (uid != _last_rfid_card_uid) { if (uid > 0) { + _no_rfid_card_count = 0; INFO("New RFID card uid: %08x\n", uid); String s_uid = String(uid, HEX); _player->play_album(s_uid); } else { - INFO("No more RFID card."); - _player->stop(); + if (_no_rfid_card_count >= 1) { + INFO("No more RFID card."); + _player->stop(); + } else { + _no_rfid_card_count++; + return; + } } _last_rfid_card_uid = uid; } @@ -80,12 +86,14 @@ void Controller::_check_serial() { } void Controller::_execute_serial_command(String cmd) { - DEBUG("Executing command: %s", cmd.c_str()); + DEBUG("Executing command: %s\n", cmd.c_str()); if (cmd.equals("ls")) { _execute_command_ls("/"); } else if (cmd.startsWith("ls ")) { _execute_command_ls(cmd.substring(3)); + } else if (cmd.equals("play")) { + _player->play_random_album(); } else if (cmd.startsWith("play ")) { _player->play_album(cmd.substring(5)); } else if (cmd.startsWith("sys ")) { diff --git a/src/http_server.cpp b/src/http_server.cpp new file mode 100644 index 0000000..7103d4e --- /dev/null +++ b/src/http_server.cpp @@ -0,0 +1,154 @@ +#include "http_server.h" + +HTTPServer::HTTPServer(Player* p, Controller* c) { + _player = p; + _controller = c; + _http_server = new ESP8266WebServer(80); + DEBUG("Connecting to wifi \"%s\"...\n", WIFI_SSID); + WiFi.mode(WIFI_AP_STA); + WiFi.begin(WIFI_SSID, WIFI_PASS); + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + ERROR("Could not connect to Wifi. Rebooting."); + delay(1000); + ESP.restart(); + } + INFO("WiFi connected.\n"); + MDNS.begin("esmp3"); + //_http_server->onFileUpload([&]() { _handle_upload(); yield();}); + _http_server->on("/upload", HTTP_POST, [&]() { + _http_server->sendHeader("Connection", "close"); + _http_server->send(200, "text/plain", "OK"); + }, [&]() { + _handle_upload(); + yield(); + }); + _http_server->on("/", HTTP_GET, [&](){ _handle_index(); }); + _http_server->on("/status", HTTP_GET, [&](){ _handle_status(); }); + _http_server->begin(); + MDNS.addService("http", "tcp", 80); +} + +void HTTPServer::_handle_upload() { + // https://www.gnu.org/software/tar/manual/html_node/Standard.html + // https://www.mkssoftware.com/docs/man4/tar.4.asp + HTTPUpload* upload = &_http_server->upload(); + DEBUG("_handle_upload Status: %d, length: %d\n", upload->status, upload->currentSize); + + if (upload->status == UPLOAD_FILE_START) { + _chunk = new uint8_t[512]; + _chunk_length = 0; + _upload_position = 0; + _file_size = 0; + _file_size_done = 0; + _need_header = true; + } + + if (upload->status == UPLOAD_FILE_END || upload->status == UPLOAD_FILE_ABORTED) { + // Close the file + delete _chunk; + return; + } + + uint32_t upload_offset = 0; + while (upload_offset < upload->currentSize) { + // Load a chunk + if (_chunk_length < 512 && upload->currentSize > upload_offset) { + uint16_t needed = 512 - _chunk_length; + if (needed > upload->currentSize - upload_offset) needed = upload->currentSize - upload_offset; + memcpy(_chunk + _chunk_length, upload->buf + upload_offset, needed); + _chunk_length += needed; + upload_offset += needed; + _upload_position += needed; + + if (_chunk_length == 512) { + // Process chunk + DEBUG("Chunk.\n"); + if (_need_header) { + if (_chunk[257]=='u'&&_chunk[258]=='s'&&_chunk[259]=='t'&&_chunk[260]=='a'&&_chunk[261]=='r') { + DEBUG("It is a valid header, starting at 0x%X!\n", _upload_position-512); + char filename[200]; + strncpy(filename, (char*)_chunk, 100); + DEBUG("filename: %s\n", filename); + _file_size = 0; + _file_size_done = 0; + for (int i=0; i<11; i++) { + //Serial.print(_header_buffer[124 + i]); + _file_size = (_file_size<<3) + (_chunk[124 + i] - '0'); + } + DEBUG("filesize: %d\n", _file_size); + uint8_t type = _chunk[156] - '0'; + if (type==0) { + DEBUG("Is a file.\n"); + } else if (type==5) { + DEBUG("Is a directory.\n"); + } else { + ERROR("Unknown file type %d\n", type); + } + _need_header = (type==5 || _file_size==0); // No chunks needed for directories. + } else { + bool byte_found = false; + for (int i=0; i<512; i++) byte_found = byte_found || _chunk[i]>0; + if (!byte_found) { + DEBUG("Empty chunk while looking for header -> ignoring.\n"); + } else { + ERROR("Invalid tar header: %c %c %c %c %c. Looking at header start offset 0x%X.\n", _chunk[257], _chunk[258], _chunk[259], _chunk[260], _chunk[261], _upload_position-512); + } + } + } else { + uint16_t bytes_to_write = _file_size - _file_size_done; + if (bytes_to_write > 512) bytes_to_write=512; + // Write bytes... + _file_size_done += bytes_to_write; + if (_file_size_done >= _file_size) _need_header = true; + } + + _chunk_length = 0; + } + } + + } +} + +void HTTPServer::_handle_index() { + String response = String("ESMP3"); + response.concat("Albums on SD card:"); + std::list files = _player->ls("/", false, true, false); + for(std::list::iterator it=files.begin(); it!=files.end(); it++) { + response.concat("\n"); + } + response.concat("
"); + response.concat(*it); + response.concat("Play
"); + _http_server->send(200, "text/html", response); +} + +void HTTPServer::_handle_status() { + String response = String("{"); + response.concat("\"state\": \""); + response.concat(_player->is_playing() ? "playing" : "idle"); + response.concat("\", "); + if (_player->is_playing()) { + response.concat("\"album\": \""); + response.concat(_player->album()); + response.concat("\", \"track\": "); + response.concat(_player->track()); + response.concat(", \"position\": "); + response.concat(_player->position()); + response.concat(", "); + } + response.concat("\"volume\": "); + response.concat(_player->volume()); + response.concat(", \"volume_max\": "); + response.concat(VOLUME_MAX); + response.concat(", \"volume_min\": "); + response.concat(VOLUME_MIN); + response.concat(", \"rfid_uid\": "); + response.concat(_controller->rfid_uid()); + response.concat("}"); + _http_server->send(200, "application/json", response); +} + +void HTTPServer::loop() { + _http_server->handleClient(); + MDNS.update(); +} diff --git a/src/main.cpp b/src/main.cpp index 8ca0511..f48a8f2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,16 +6,20 @@ #include "controller.h" #include "player.h" #include "spi_master.h" +#include "http_server.h" Controller* controller; Player* player; MCP* mcp; +HTTPServer* http_server; void setup() { delay(500); Serial.begin(74880); INFO("Starting.\n"); INFO("Initializing...\n"); + + DEBUG("Setting up SPI...\n"); SPI.begin(); @@ -35,8 +39,14 @@ void setup() { ERROR("Could not initialize SD card. Halting.\n"); while(1); } + + DEBUG("Initializing Player and Controller...\n"); player = new Player(mcp); controller = new Controller(player, mcp); + INFO("Player and controller initialized.\n"); + + DEBUG("Setting up WiFi and web server...\n"); + http_server = new HTTPServer(player, controller); INFO("Initialization completed.\n"); } @@ -46,4 +56,5 @@ void loop() { if (more_data_needed) return; controller->loop(); + http_server->loop(); } diff --git a/src/player.cpp b/src/player.cpp index 107539d..994cc9f 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -72,6 +72,27 @@ void Player::_init() { _state = idle; } +void Player::_sleep() { + DEBUG("VS1053 going to sleep.\n"); + _write_control_register(SCI_CLOCKF, 0x0000); + _spi_settings = &_spi_settings_slow; + _write_control_register(SCI_AUDATA, 0x0010); + set_volume(0, false); + _state = sleeping; +} + +void Player::_wakeup() { + if (_state != sleeping) return; + _stopped_at = millis(); + DEBUG("Waking VS1053...\n"); + set_volume(_volume, false); + _write_control_register(SCI_AUDATA, 0x0000); + _write_control_register(SCI_CLOCKF, 0x6000); + delay(10); + _spi_settings = &_spi_settings_fast; + _state = idle; +} + void Player::_check_system_sound(String filename) { String path = String("/system/") + filename; if (!SD.exists(path)) { @@ -159,7 +180,6 @@ void Player::set_volume(uint8_t vol, bool save) { } INFO("Setting volume to %d\n", vol); vol = 0xFF - vol; - if (vol==0xFF) vol=0xFE; uint16_t value = (vol<<8)|vol; DEBUG("Setting volume register to 0x%04X\n", value); _write_control_register(SCI_VOL, value); @@ -178,12 +198,12 @@ void Player::vol_down() { } void Player::_mute() { - INFO("Muting."); - set_volume(0, false); + INFO("Muting.\n"); + set_volume(1, false); } void Player::_unmute() { - INFO("Unmuting."); + INFO("Unmuting.\n"); set_volume(_volume, false); } @@ -212,21 +232,30 @@ void Player::track_prev() { } } -std::list Player::ls(String path) { +bool Player::is_playing() { + return _state == playing; +} + +std::list Player::ls(String path, bool withFiles, bool withDirs, bool withHidden) { SPIMaster::enable(PIN_SD_CS); std::list result; if (!SD.exists(path)) return result; File dir = SD.open(path); File entry; while (entry = dir.openNextFile()) { + if (!withDirs && entry.isDirectory()) continue; + if (!withFiles && !entry.isDirectory()) continue; String filename = entry.name(); + if (!withHidden && filename.startsWith(".")) continue; if (entry.isDirectory()) filename.concat("/"); result.push_back(filename); } + result.sort(); return result; } String Player::_find_album_dir(String id) { + if (id.endsWith("/")) id = id.substring(0, id.length() - 1); String id_with_divider = id + " - "; File root = SD.open("/"); File entry; @@ -259,10 +288,10 @@ std::list Player::_files_in_dir(String path) { filename.endsWith(".wma") || filename.endsWith(".mp4") || filename.endsWith(".mpa"))) { - DEBUG(" Adding entry %s\n", filename.c_str()); + TRACE(" Adding entry %s\n", filename.c_str()); result.push_back(path + filename); } else { - DEBUG(" Ignoring entry %s\n", filename.c_str()); + TRACE(" Ignoring entry %s\n", filename.c_str()); } entry.close(); } @@ -272,14 +301,26 @@ std::list Player::_files_in_dir(String path) { return result; } +String Player::_random_album() { + std::list albums = ls("/", false, true, false); + uint8_t rnd = random(albums.size()); + std::list::iterator it = albums.begin(); + for (int i=0; i