From 978b25c34d2bfe3a8163b95e481d95379dbe9697 Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Fri, 19 Aug 2022 10:37:03 +0200 Subject: [PATCH 01/20] Started rewrite for using an I2S amplifier with less unnecessary features. --- include/config.sample.h | 84 ---- include/controller.h | 62 +-- include/data_sources.h | 54 --- include/esmp3.h | 11 + include/http_client_wrapper.h | 37 -- include/http_server.h | 29 -- include/main.h | 9 - include/player.h | 104 ----- include/playlist.h | 64 +-- include/playlist_manager.h | 31 +- include/spi_master.h | 73 +--- include/updater.h | 10 - platformio.ini | 22 +- src/controller.cpp | 470 +-------------------- src/data_sources.cpp | 57 --- src/esmp3.cpp | 68 +++ src/http_client_wrapper.cpp | 213 ---------- src/http_server.cpp | 167 -------- src/index.html | 359 ---------------- src/main.cpp | 149 ------- src/player.cpp | 753 ---------------------------------- src/playlist.cpp | 402 +----------------- src/playlist_manager.cpp | 247 +++-------- src/spi_master.cpp | 26 ++ src/updater.cpp | 96 ----- 25 files changed, 218 insertions(+), 3379 deletions(-) delete mode 100644 include/config.sample.h delete mode 100644 include/data_sources.h create mode 100644 include/esmp3.h delete mode 100644 include/http_client_wrapper.h delete mode 100644 include/http_server.h delete mode 100644 include/main.h delete mode 100644 include/player.h delete mode 100644 include/updater.h delete mode 100644 src/data_sources.cpp create mode 100644 src/esmp3.cpp delete mode 100644 src/http_client_wrapper.cpp delete mode 100644 src/http_server.cpp delete mode 100644 src/index.html delete mode 100644 src/main.cpp delete mode 100644 src/player.cpp create mode 100644 src/spi_master.cpp delete mode 100644 src/updater.cpp diff --git a/include/config.sample.h b/include/config.sample.h deleted file mode 100644 index 05fe964..0000000 --- a/include/config.sample.h +++ /dev/null @@ -1,84 +0,0 @@ -#pragma once -#include - -// This is a simple number indicating the version for the HTTP Updater. -#define OTA_VERSION 1 -// Comment out to prevent automatic updates. -#define OTA_UPDATE_URL "https://files.schle.nz/esmp3/update.manifest" -#define OTA_CHECK_INTERVAL 12*60*60*1000 // 12 hours - -#define SHOW_DEBUG -//#define SHOW_TRACE -#define FTP_DEBUG -#define DELAY_AFTER_DEBUG_AND_TRACE 0 - -// Here you can define WiFi data to use. But actually, the better way to do -// this is by using /_wifi.txt on the sd card. -//#define WIFI_SSID "---CHANGEME---" -//#define WIFI_PASS "---CHANGEME---" - -#define VS1053_SLEEP_DELAY 5000 -#define POSITION_SEND_INTERVAL 5000 - -#define DEBOUNCE_MILLIS 200 -#define VOLUME_DEFAULT 230 -#define VOLUME_MIN 190 -#define VOLUME_MAX 255 -#define VOLUME_STEP 0x08 - -#define RFID_SCAN_INTERVAL 100 - -#define NUM_BUTTONS 4 - -#define PIN_SD_CS(x) (digitalWrite(16, x)) -#define PIN_SD_CS_SETUP() (pinMode(16, OUTPUT)) - -#define PIN_VS1053_XCS(x) (digitalWrite(4, x)) -#define PIN_VS1053_XCS_SETUP() (pinMode(4, OUTPUT)) - -#define PIN_VS1053_XRESET(x) (digitalWrite(0, x)) -#define PIN_VS1053_XRESET_SETUP() (pinMode(0, OUTPUT)) - -#define PIN_VS1053_XDCS(x) (digitalWrite(2, x)) -#define PIN_VS1053_XDCS_SETUP() (pinMode(2, OUTPUT)) - -#define PIN_VS1053_DREQ() (digitalRead(15)) -#define PIN_VS1053_DREQ_SETUP() (pinMode(15, INPUT)) - -#define PIN_RC522_CS(x) (digitalWrite(17, x)) -#define PIN_RC522_CS_SETUP() (pinMode(17, OUTPUT)) - -#define PIN_SPEAKER_L(x) (digitalWrite(27, x)) -#define PIN_SPEAKER_L_SETUP() (pinMode(27, OUTPUT)) - -#define PIN_SPEAKER_R(x) (digitalWrite(26, x)) -#define PIN_SPEAKER_R_SETUP() (pinMode(26, OUTPUT)) - -#define BTN_PREV() ( ! digitalRead(22)) -#define BTN_PREV_SETUP() (pinMode(22, INPUT_PULLUP)) - -#define BTN_VOL_UP() ( ! digitalRead(21)) -#define BTN_VOL_UP_SETUP() (pinMode(21, INPUT_PULLUP)) - -#define BTN_VOL_DOWN() ( ! digitalRead(32)) -#define BTN_VOL_DOWN_SETUP() (pinMode(32, INPUT_PULLUP)) - -#define BTN_NEXT() ( ! digitalRead(33)) -#define BTN_NEXT_SETUP() (pinMode(33, INPUT_PULLUP)) - - -// Other definitions -#define INFO(x, ...) Serial.printf(x, ##__VA_ARGS__) -#define ERROR(x, ...) Serial.printf(x, ##__VA_ARGS__) - -#ifdef SHOW_DEBUG - #define DEBUG(x, ...) {Serial.printf(x, ##__VA_ARGS__); delay(DELAY_AFTER_DEBUG_AND_TRACE);} -#else - #define DEBUG(x, ...) while(0) {} -#endif - -#ifdef SHOW_TRACE - #define TRACE(x, ...) {Serial.printf(x, ##__VA_ARGS__); delay(DELAY_AFTER_DEBUG_AND_TRACE);} -#else - #define TRACE(x, ...) while(0) {} -#endif diff --git a/include/controller.h b/include/controller.h index 3989caf..7b56efe 100644 --- a/include/controller.h +++ b/include/controller.h @@ -1,61 +1,13 @@ #pragma once #include -#include -#include "config.h" - -class Controller; - -#include "player.h" -#include "playlist.h" -#include "playlist_manager.h" -#include "http_server.h" - -#undef DEPRECATED -#include - -enum ControllerState { NORMAL, LOCKING, LOCKED }; class Controller { -private: - MFRC522* _rfid; - HTTPServer* _http_server; - ControllerState _state = NORMAL; - bool _rfid_enabled = true; - void _check_rfid(); - void _check_serial(); - void _check_buttons(); - bool _debounce_button(uint8_t index); - uint32_t _get_rfid_card_uid(); - String _read_rfid_data(); - bool _rfid_present = false; - String _last_rfid_uid = ""; - String _last_rfid_data = ""; + private: + void handle_buttons(); + void handle_rfid(); + + public: + void handle(); - unsigned long _last_rfid_scan_at = 0; - unsigned long _last_position_info_at = 0; - unsigned long _last_update_check_at = 0; - unsigned long _last_wifi_try_at = 0; - String _serial_buffer = String(); - String _cmd_queue = ""; - void _execute_command_ls(String path); - void _execute_command_ids(); - void _execute_command_help(); - unsigned long _button_last_pressed_at[NUM_BUTTONS]; - bool _check_button(uint8_t btn); -public: - Controller(Player* p, PlaylistManager* pm); - PlaylistManager* pm; - Player* player; - void register_http_server(HTTPServer* h); - void loop(); - void send_controller_status(); - void send_player_status(); - void send_playlist_manager_status(); - void send_position(); - void inform_new_client(AsyncWebSocketClient* client); - String json(); - bool process_message(String m); - void queue_command(String cmd); - void update_playlist_manager(); -}; +}; \ No newline at end of file diff --git a/include/data_sources.h b/include/data_sources.h deleted file mode 100644 index d65e591..0000000 --- a/include/data_sources.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include -#include -#include "config.h" -#include "http_client_wrapper.h" - -class DataSource { -private: -public: - DataSource() {}; - virtual ~DataSource() {}; - virtual size_t read(uint8_t* buf, size_t len) = 0; - virtual int read() = 0; - virtual size_t position() = 0; - virtual void seek(size_t position) = 0; - virtual size_t size() = 0; - virtual void close() = 0; - virtual void skip_id3_tag() {}; - virtual bool usable() = 0; -}; - -class SDDataSource : public DataSource { -private: - File _file; -public: - SDDataSource(String file); - ~SDDataSource(); - size_t read(uint8_t* buf, size_t len); - int read(); - size_t position(); - void seek(size_t position); - size_t size(); - void close(); - void skip_id3_tag(); - bool usable(); -}; - -class HTTPSDataSource : public DataSource { -private: - WiFiClient* _stream = NULL; - HTTPClientWrapper* _http = NULL; - uint32_t _position; -public: - HTTPSDataSource(String url, uint32_t offset=0); - ~HTTPSDataSource(); - size_t read(uint8_t* buf, size_t len); - int read(); - size_t position(); - void seek(size_t position); - size_t size(); - void close(); - bool usable(); -}; diff --git a/include/esmp3.h b/include/esmp3.h new file mode 100644 index 0000000..d17af5d --- /dev/null +++ b/include/esmp3.h @@ -0,0 +1,11 @@ +#pragma once +#include "controller.h" + +#define PIN_CS_SD 16 +#define PIN_CS_RFID 17 + +#define I2S_DOUT 25 +#define I2S_BCLK 27 +#define I2S_LRC 26 + +extern Controller controller; \ No newline at end of file diff --git a/include/http_client_wrapper.h b/include/http_client_wrapper.h deleted file mode 100644 index a80e15f..0000000 --- a/include/http_client_wrapper.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include "config.h" - -class HTTPClientWrapper { -private: - HTTPClient* _http; - uint8_t* _buffer; - uint16_t _buffer_size; - uint16_t _buffer_length; - uint16_t _buffer_position; - uint32_t _chunk_length; - - bool _connected = false; - String _content_type; - uint32_t _length; - bool _request(String method, String url, uint32_t offset=0, uint8_t redirection_count=0); - WiFiClient* _stream; - bool _is_chunked; - void _read_next_chunk_header(bool first); - uint16_t _fill_buffer(); - -public: - HTTPClientWrapper(); - ~HTTPClientWrapper(); - bool get(String url, uint32_t offset=0, uint8_t redirection_count=0); - bool head(String url, uint32_t offset=0, uint8_t redirection_count=0); - String getContentType(); - String getString(); - int read(); - uint32_t read(uint8_t* dst, uint32_t len); - void close(); - uint32_t getSize(); - String readUntil(String sep); - String readLine(); -}; diff --git a/include/http_server.h b/include/http_server.h deleted file mode 100644 index b190e1c..0000000 --- a/include/http_server.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -class HTTPServer; - -#include "player.h" -#include "controller.h" -#include -#include - -class HTTPServer { -private: - AsyncWebServer* _server; - - Player* _player; - Controller* _controller; - void _handle_upload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final); - uint16_t _chunk_length; - uint8_t* _chunk; - File _upload_file; - uint32_t _file_size; - uint32_t _file_size_done; - bool _need_header; - uint32_t _upload_position; - void _onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len); - void _handle_index(AsyncWebServerRequest* req); -public: - HTTPServer(Player* p, Controller* c); - AsyncWebSocket* ws; -}; diff --git a/include/main.h b/include/main.h deleted file mode 100644 index be0496e..0000000 --- a/include/main.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once -#include - -void wifi_connect(); - -extern const uint8_t file_index_html_start[] asm("_binary_src_index_html_start"); -extern bool debug_enabled; -extern bool trace_enabled; -extern Preferences prefs; \ No newline at end of file diff --git a/include/player.h b/include/player.h deleted file mode 100644 index 522ee20..0000000 --- a/include/player.h +++ /dev/null @@ -1,104 +0,0 @@ -#pragma once -#include "config.h" -#include -#include -#include "spi_master.h" -#include "playlist.h" -#include "data_sources.h" - -class Player; - -#include "controller.h" - -#define SCI_MODE 0x00 -#define SCI_STATUS 0x01 -#define SCI_BASS 0x02 -#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 -#define SCI_HDAT0 0x08 -#define SCI_HDAT1 0x09 -#define SCI_AIADDR 0x0A -#define SCI_AICTRL0 0x0C -#define SCI_AICTRL1 0x0D -#define SCI_AICTRL2 0x0E -#define SCI_AICTRL3 0x0F - -#define CMD_WRITE 0x02 -#define CMD_READ 0x03 - -#define ADDR_ENDBYTE 0x1E06 - -#define SM_LAYER12 0x0001 -#define SM_RESET 0x0004 -#define SM_CANCEL 0x0008 -#define SM_SDINEW 0x0800 -#define SM_ADPCM 0x1000 -#define SS_DO_NOT_JUMP 0x8000 - -class Player { -private: - enum state { uninitialized, idle, playing, stopping, - sleeping, recording }; - void _reset(); - void _wait(); - uint16_t _read_control_register(uint8_t address, bool do_wait=true); - void _write_control_register(uint8_t address, uint16_t value, bool do_wait=true); - void _write_direct(uint8_t address, uint16_t value); - void _write_data(uint8_t* data); - uint16_t _read_wram(uint16_t address); - state _state = state::uninitialized; - void _refill(); - bool _refill_needed(); - void _flush_and_cancel(); - int8_t _get_endbyte(); - void _flush(uint count, int8_t fill_byte); - void _play_file(String filename, uint32_t offset); - void _finish_playing(); - void _finish_stopping(bool turn_speaker_off); - void _mute(); - void _unmute(); - void _sleep(); - void _wakeup(); - void _record(); - void _patch_adpcm(); - void _speaker_off(); - void _speaker_on(); - - SPISettings _spi_settings_slow = SPISettings(250000, MSBFIRST, SPI_MODE0); - SPISettings _spi_settings_fast = SPISettings(4000000, MSBFIRST, SPI_MODE0); - SPISettings* _spi_settings = &_spi_settings_slow; - - DataSource* _file; - uint32_t _file_size = 0; - uint8_t _buffer[32]; - uint32_t _current_play_position = 0; - Playlist* _current_playlist = NULL; - uint _refills; - uint8_t _volume; - uint16_t _stop_delay; - uint32_t _skip_to; - SPIMaster* _spi; - Controller* _controller; - unsigned long _stopped_at; -public: - Player(SPIMaster* s); - void init(); - void register_controller(Controller* c); - void vol_up(); - void vol_down(); - void track_next(); - void track_prev(); - void set_track(uint8_t track); - bool is_playing(); - bool play(); - bool play(Playlist* p); - void stop(bool turn_speaker_off=true); - bool loop(); - void set_volume(uint8_t vol, bool save = true); - String position_json(); - String json(); -}; diff --git a/include/playlist.h b/include/playlist.h index 38e1f97..73691e3 100644 --- a/include/playlist.h +++ b/include/playlist.h @@ -1,59 +1,15 @@ #pragma once -#include + #include -#include -#include "http_client_wrapper.h" - -enum PlaylistPersistence { - PERSIST_NONE, - PERSIST_TEMPORARY, - PERSIST_PERMANENTLY -}; - -struct PlaylistEntry { - String filename; - String title; - String id; - - bool operator<(PlaylistEntry p) { return title < p.title; } -}; +#include class Playlist { -private: - uint32_t _position = 0; - uint32_t _current_track = 0; - bool _started = false; - bool _shuffled = false; - std::vector _files; - String _title = ""; - String _path; - void _add_path(String path); - void _examine_http_url(String url); - void _parse_rss(HTTPClientWrapper* http); - void _parse_m3u(HTTPClientWrapper* http); - void _parse_pls(HTTPClientWrapper* http); -public: - PlaylistPersistence persistence = PERSIST_TEMPORARY; - Playlist(String path); - void start(); - uint16_t get_file_count(); - bool has_track_next(); - bool has_track_prev(); - bool track_next(); - bool track_prev(); - void track_restart(); - bool set_track(uint8_t track); - void set_track_by_id(String id); - void reset(); - String path(); - bool is_empty(); - bool get_current_file(String* dst); - String get_current_track_id(); - uint32_t get_position(); - void set_position(uint32_t p); - void shuffle(uint8_t random_offset=0); - void advent_shuffle(uint8_t day); - bool is_fresh(); - void dump(); - void json(JsonObject json); + private: + std::vector files; + uint8_t current_file = 0; + uint16_t current_time = 0; + + public: + void add_file(String filename); + void sort(); }; diff --git a/include/playlist_manager.h b/include/playlist_manager.h index 42c88a0..551c40a 100644 --- a/include/playlist_manager.h +++ b/include/playlist_manager.h @@ -1,24 +1,21 @@ #pragma once -#include #include +#include +#include #include "playlist.h" class PlaylistManager { -private: - std::map _map; - std::map _playlists; - std::vector _unmapped_folders; - void _check_for_special_chars(String s); - void _save_mapping(); -public: + private: + Playlist get_playlist_for_tag_id(String id); + String current_rfid_tag_id; + uint32_t audio_current_time = 0; + + public: PlaylistManager(); - Playlist* get_playlist_for_id(String id); - Playlist* get_playlist_for_folder(String folder); - void dump_ids(); - void scan_files(); - String json(); - bool add_mapping(String id, String folder); - String create_mapping_txt(); - void persist(Playlist* p); -}; + std::vector dirs; + std::map map; + Playlist get_playlist(String rfid_id); + Playlist current_playlist; + void set_audio_current_time(uint32_t time); +}; \ No newline at end of file diff --git a/include/spi_master.h b/include/spi_master.h index ee5724c..a32914d 100644 --- a/include/spi_master.h +++ b/include/spi_master.h @@ -1,70 +1,9 @@ #pragma once -#include -#include -#include "config.h" - class SPIMaster { -public: - static uint8_t state; - - static void init() { - PIN_SD_CS_SETUP(); - PIN_VS1053_XCS_SETUP(); - PIN_VS1053_XDCS_SETUP(); - PIN_RC522_CS_SETUP(); - disable(); - } - - static void select_sd(bool enabled=true) { - PIN_SD_CS(enabled ? LOW : HIGH); - if (enabled) { - state |= 1; - } else { - state &= ~1; - } - } - - static void select_vs1053_xcs(bool enabled=true) { - PIN_VS1053_XCS(enabled ? LOW : HIGH); - if (enabled) { - state |= 2; - } else { - state &= ~2; - } - } - - static void select_vs1053_xdcs(bool enabled=true) { - PIN_VS1053_XDCS(enabled ? LOW : HIGH); - if (enabled) { - state |= 4; - } else { - state &= ~4; - } - } - - static void select_rc522(bool enabled=true) { - PIN_RC522_CS(enabled ? LOW : HIGH); - if (enabled) { - state |= 8; - } else { - state &= ~8; - } - } - - static void set_state(uint8_t s) { - disable(); - if (s & 1) select_sd(); - if (s & 2) select_vs1053_xcs(); - if (s & 4) select_vs1053_xdcs(); - if (s & 8) select_rc522(); - } - - static void disable() { - PIN_SD_CS(HIGH); - PIN_VS1053_XCS(HIGH); - PIN_VS1053_XDCS(HIGH); - PIN_RC522_CS(HIGH); - state = 0; - } -}; \ No newline at end of file + public: + static void enable_sd(); + static void enable_rfid(); + static void disable_all(); + static void initialize(); +}; diff --git a/include/updater.h b/include/updater.h deleted file mode 100644 index 9d5a3a8..0000000 --- a/include/updater.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "http_client_wrapper.h" - -class Updater { -public: - static void run(); - static bool do_update(int cmd, String url, String expected_md5); - static bool read_line(String* dst, HTTPClientWrapper* http, String expected_key); -}; diff --git a/platformio.ini b/platformio.ini index c93214b..64fddb3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,29 +12,27 @@ default_envs = esp32 [extra] -lib_deps = - 63 @ 1.4.5 ; MFRC522 - https://github.com/me-no-dev/ESPAsyncWebServer.git - ArduinoJSON - 6691 ; TinyXML +lib_deps = [env:esp32] platform = espressif32 board = esp-wrover-kit framework = arduino upload_speed = 115200 -build_flags=!./build_version.sh -lib_deps = ${extra.lib_deps} -;upload_port = 10.10.2.108 ; /dev/cu.SLAB_USBtoUART +build_flags = !./build_version.sh +lib_deps = + ${extra.lib_deps} + esphome/ESP32-audioI2S@^2.1.0 +upload_port = 10.10.2.108 monitor_speed = 115200 -board_build.embed_txtfiles = src/index.html -;board_build.partitions = partitions.csv -;monitor_port = /dev/cu.wchusbserial1420 +monitor_port = /dev/cu.usbserial-0001 [env:deploy] platform = espressif32 board = esp-wrover-kit framework = arduino -lib_deps = ${extra.lib_deps} +lib_deps = + ${extra.lib_deps} + esphome/ESP32-audioI2S@^2.1.0 board_build.embed_txtfiles = src/index.html board_build.partitions = partitions.csv diff --git a/src/controller.cpp b/src/controller.cpp index f5cc26a..f2e476d 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -1,463 +1,13 @@ #include "controller.h" -#include "main.h" -#include "spi_master.h" -#include "config.h" -#include "playlist.h" -#include "http_server.h" -#include "updater.h" -#include -Controller::Controller(Player* p, PlaylistManager* playlist_manager) { - player = p; - pm = playlist_manager; - _rfid = new MFRC522(17, MFRC522::UNUSED_PIN); +void Controller::handle() { + +} + +void Controller::handle_buttons() { + +} + +void Controller::handle_rfid() { - player->register_controller(this); - - BTN_NEXT_SETUP(); - BTN_PREV_SETUP(); - BTN_VOL_UP_SETUP(); - BTN_VOL_DOWN_SETUP(); - - SPIMaster::select_rc522(); - DEBUG("Initializing RC522...\n"); - _rfid->PCD_Init(); - #ifdef SHOW_DEBUG - _rfid->PCD_DumpVersionToSerial(); - #endif - SPIMaster::select_rc522(false); - INFO("RC522 initialized.\n"); - - for (uint8_t i=0; i 0) { - process_message(_cmd_queue); - _cmd_queue = ""; - } - - #ifdef OTA_UPDATE_URL - if (!player->is_playing() && _last_update_check_at < now && _last_update_check_at + OTA_CHECK_INTERVAL < now) { - Updater::run(); - } else { - _last_update_check_at = now; - } - #endif - - if (!player->is_playing() && !WiFi.isConnected() && _last_wifi_try_at < now && _last_wifi_try_at + 5*60*1000 < now) { - wifi_connect(); - } else { - _last_wifi_try_at = now; - } -} - -uint32_t Controller::_get_rfid_card_uid() { - SPIMaster::select_rc522(); - if (!_rfid->PICC_ReadCardSerial()) { - if (!_rfid->PICC_IsNewCardPresent()) { - return 0; - } - if (!_rfid->PICC_ReadCardSerial()) { - return 0; - } - } - SPIMaster::select_rc522(false); - uint32_t uid = _rfid->uid.uidByte[0]<<24 | _rfid->uid.uidByte[1]<<16 | _rfid->uid.uidByte[2]<<8 | _rfid->uid.uidByte[3]; - return uid; -} - -void Controller::_check_rfid() { - //TRACE("check_rfid running...\n"); - MFRC522::StatusCode status; - if (_rfid_present) { - byte buffer[2]; - byte buffer_size = 2; - SPIMaster::select_rc522(); - status = _rfid->PICC_WakeupA(buffer, &buffer_size); - if (status == MFRC522::STATUS_OK) { - // Card is still present. - _rfid->PICC_HaltA(); - SPIMaster::select_rc522(false); - return; - } - SPIMaster::select_rc522(false); - // Card is now gone - _rfid_present = false; - INFO("No more RFID card.\n"); - if (_state != LOCKED) { - player->stop(); - } - send_controller_status(); - } else { - uint32_t uid = _get_rfid_card_uid(); - if (uid > 0) { - String temp = String(uid, HEX); - String s_uid = ""; - for (int i=0; i<(8-temp.length()); i++) { - s_uid.concat("0"); - } - s_uid.concat(temp); - INFO("New RFID card uid: %s\n", s_uid.c_str()); - _last_rfid_uid = s_uid; - _rfid_present = true; - - String data = _read_rfid_data(); - _last_rfid_data = data; - - Playlist* pl = pm->get_playlist_for_id(s_uid); - if (data.indexOf("[lock]") != -1) { - if (_state == LOCKED) { - _state = NORMAL; - DEBUG("ControllerState is now UNLOCKED\n"); - } else { - DEBUG("ControllerState is now LOCKING\n"); - _state = LOCKING; - } - } - if (pl==NULL) { - INFO("Could not find album for id '%s'.\n", s_uid.c_str()); - send_controller_status(); - return; - } - int index; - if (data.indexOf("[advent]") != -1 && pl->is_fresh()) { - struct tm time; - getLocalTime(&time); - if (time.tm_mon == 11) { // tm_mon is "months since january", so 11 means december. - pl->advent_shuffle(time.tm_mday); - } else { - DEBUG("Album is in advent mode, but it isn't december (yet). Not playing.\n"); - return; - } - } else if (data.indexOf("[random]") != -1 && pl->is_fresh()) { - pl->shuffle(); - } else if ((index=data.indexOf("[random:")) != -1 && pl->is_fresh()) { - String temp = data.substring(index + 8); - index = temp.indexOf("]"); - TRACE("temp: %s, temp.substring(0, %d): %s\n", temp.c_str(), index, temp.substring(0, index).c_str()); - if (index>0) { - uint8_t random_offset = temp.substring(0, index).toInt(); - pl->shuffle(random_offset); - } - } - - if (_state == LOCKED) { - DEBUG("ControllerState is LOCKED, ignoring card.\n"); - return; - } - - if (_state == LOCKING) { - _state = LOCKED; - DEBUG("ControllerState is now LOCKED.\n"); - } - - player->play(pl); - //send_playlist_manager_status(); - send_controller_status(); - } - } -} - -String Controller::_read_rfid_data() { - TRACE("_read_rfid_data() running...\n"); - static MFRC522::MIFARE_Key keys[8] = { - {{0xd3, 0xf7, 0xd3, 0xf7, 0xd3, 0xf7}}, // D3 F7 D3 F7 D3 F7 - {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, // FF FF FF FF FF FF = factory default - {{0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5}}, // A0 A1 A2 A3 A4 A5 - {{0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5}}, // B0 B1 B2 B3 B4 B5 - {{0x4d, 0x3a, 0x99, 0xc3, 0x51, 0xdd}}, // 4D 3A 99 C3 51 DD - {{0x1a, 0x98, 0x2c, 0x7e, 0x45, 0x9a}}, // 1A 98 2C 7E 45 9A - {{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}}, // AA BB CC DD EE FF - {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}} // 00 00 00 00 00 00 - }; - SPIMaster::select_rc522(); - DEBUG("Trying to read RFID data...\n"); - - String data = ""; - MFRC522::PICC_Type type = _rfid->PICC_GetType(_rfid->uid.sak); - - uint8_t sectors = 0; - switch(type) { - case MFRC522::PICC_TYPE_MIFARE_MINI: sectors = 5; break; - case MFRC522::PICC_TYPE_MIFARE_1K: sectors = 16; break; - case MFRC522::PICC_TYPE_MIFARE_4K: sectors = 40; break; - default: INFO("Unknown PICC type %s\n", String(MFRC522::PICC_GetTypeName(type)).c_str()); - } - sectors = 2; // Pretend we have only two sectors, so we read only sector #1. - int good_key_index = -1; - for (uint8_t sector=1; sectorkeyByte[0], k->keyByte[1], k->keyByte[2], k->keyByte[3], k->keyByte[4], k->keyByte[5]); - status = _rfid->PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, block_offset, k, &_rfid->uid); - if (status == MFRC522::STATUS_OK) { - TRACE("Authentication succeeded with key #%d\n", i); - good_key_index = i; - break; - } - } - if (good_key_index == -1) { - TRACE("Could not find a valid MIFARE key.\n"); - } else { - for (uint8_t block=0; blockMIFARE_Read(block_offset + block, buffer, &byte_count); - if (status != MFRC522::STATUS_OK) { - DEBUG("MIFARE_Read() failed: %s\n", String(_rfid->GetStatusCodeName(status)).c_str()); - continue; - } - for (int i=0; i<16; i++) { - if (buffer[i]>=0x20 && buffer[i]<0x7F) data.concat((char)buffer[i]); - } - } - } - } - - _rfid->PICC_HaltA(); - _rfid->PCD_StopCrypto1(); - DEBUG("Data from RFID: %s\n", data.c_str()); - SPIMaster::select_rc522(false); - return data; -} - -void Controller::_check_serial() { - if (Serial.available() > 0) { - char c = Serial.read(); - Serial.printf("%c", c); - if (c==10 || c==13) { - if (_serial_buffer.length()>0) { - process_message(_serial_buffer); - _serial_buffer = String(); - } - } else { - _serial_buffer.concat(c); - } - } -} - -bool Controller::process_message(String cmd) { - DEBUG("Executing command: %s\n", cmd.c_str()); - - if (cmd.startsWith("play ")) { - Playlist* p = pm->get_playlist_for_folder(cmd.substring(5)); - player->play(p); - } else if (cmd.equals("play")) { - player->play(); - } else if (cmd.equals("stop")) { - player->stop(); - } else if (cmd.equals("help")) { - _execute_command_help(); - } else if (cmd.equals("-")) { - player->vol_down(); - } else if (cmd.equals("+")) { - player->vol_up(); - } else if (cmd.startsWith("volume=")) { - uint8_t vol = cmd.substring(7).toInt(); - player->set_volume(vol); - } else if (cmd.equals("track_prev")) { - player->track_prev(); - } else if (cmd.equals("track_next")) { - player->track_next(); - } else if (cmd.startsWith("track=")) { - uint8_t track = cmd.substring(6).toInt(); - player->set_track(track); - } else if (cmd.equals("ids")) { - pm->dump_ids(); - } else if (cmd.equals("reset_vs1053")) { - player->stop(); - player->init(); - } else if (cmd.equals("reboot")) { - ESP.restart(); - } else if (cmd.startsWith("add_mapping=")) { - String rest = cmd.substring(12); - uint8_t idx = rest.indexOf('='); - String id = rest.substring(0, idx); - String folder = rest.substring(idx + 1); - pm->add_mapping(id, folder); - send_playlist_manager_status(); - #ifdef OTA_UPDATE_URL - } else if (cmd.equals("update")) { - Updater::run(); - #endif - } else if (cmd.startsWith("trace=")) { - int val = cmd.substring(6).toInt(); - if (val==0) { - trace_enabled = false; - prefs.putBool("trace_enabled", false); - } else if (val==1) { - trace_enabled = true; - prefs.putBool("trace_enabled", true); - } - } else if (cmd.startsWith("debug=")) { - int val = cmd.substring(6).toInt(); - if (val==0) { - debug_enabled = false; - prefs.putBool("debug_enabled", false); - } else if (val==1) { - debug_enabled = true; - prefs.putBool("debug_enabled", true); - } - } else { - ERROR("Unknown command: %s\n", cmd.c_str()); - return false; - } - return true; -} - -void Controller::_execute_command_ls(String path) { - INFO("Listing contents of %s:\n", path.c_str()); - // TODO - //std::list files = player->ls(path); - //for(std::list::iterator it=files.begin(); it!=files.end(); ++it) { - // INFO(" %s\n", (*it).c_str()); - //} -} - -void Controller::_execute_command_help() { - INFO("Valid commands are:"); - INFO(" help - Displays this help\n"); - //INFO(" ls [dir] - Lists the contents of [dir] or, if not given, of /\n"); - INFO(" ids - Lists all known ID-to-folder mappings\n"); - INFO(" play [id] - Plays the album with the given id\n"); - INFO(" stop - Stops playback\n"); - INFO(" - / + - Decrease or increase the volume\n"); - INFO(" p / n - Previous or next track\n"); -} - -void Controller::_check_buttons() { - if (BTN_PREV() && _debounce_button(0)) { - if (_state == NORMAL) { - player->track_prev(); - } else { - DEBUG("Ignoring btn_prev because state is LOCKED.\n"); - } - } else if (BTN_VOL_UP() && _debounce_button(1)) { - player->vol_up(); - } else if (BTN_VOL_DOWN() && _debounce_button(2)) { - player->vol_down(); - } else if (BTN_NEXT() && _debounce_button(3)) { - if (_state == NORMAL) { - player->track_next(); - } else { - DEBUG("Ignoring btn_next because state is LOCKED.\n"); - } - } -} - -bool Controller::_debounce_button(uint8_t index) { - bool ret = false; - if (_button_last_pressed_at[index] + DEBOUNCE_MILLIS < millis()) { - DEBUG("Button %d pressed.\n", index); - ret = true; - } - _button_last_pressed_at[index] = millis(); - return ret; -} - -String Controller::json() { - DynamicJsonDocument json(1024); - json["_type"] = "controller"; - switch(_state) { - case LOCKED: json["state"] = "locked"; break; - case LOCKING: json["state"] = "locking"; break; - case NORMAL: json["state"] = "normal"; break; - } - json["is_rfid_present"] = _rfid_present; - JsonObject rfid = json.createNestedObject("last_rfid"); - rfid["uid"] = _last_rfid_uid; - rfid["data"] = _last_rfid_data; - json["uptime"] = millis() / 1000; - json["free_heap"] = ESP.getFreeHeap(); - JsonObject versions = json.createNestedObject("versions"); - versions["ota"] = OTA_VERSION; - #ifdef VERSION - versions["release"] = VERSION; - #else - versions["release"] = "unknown"; - #endif - - JsonObject wifi = json.createNestedObject("wifi"); - if (WiFi.isConnected()) { - wifi["connected"] = true; - wifi["ssid"] = WiFi.SSID(); - wifi["rssi"] = WiFi.RSSI(); - } else { - wifi["connected"] = false; - } - return json.as(); -} - -void Controller::send_player_status() { - TRACE("In send_player_status()...\n"); - - if (_http_server->ws->count() > 0) { - _http_server->ws->textAll(player->json()); - _http_server->ws->textAll(player->position_json()); - } -} - -void Controller::send_playlist_manager_status() { - TRACE("In send_playlist_manager_status()...\n"); - if (_http_server->ws->count() > 0) { - _http_server->ws->textAll(pm->json()); - } -} - -void Controller::send_position() { - TRACE("In send_position()...\n"); - if (_http_server->ws->count() > 0) { - _http_server->ws->textAll(player->position_json()); - } - _last_position_info_at = millis(); -} - -void Controller::send_controller_status() { - TRACE("In send_controller_status()...\n"); - if (_http_server->ws->count() > 0) { - _http_server->ws->textAll(json()); - } -} - -void Controller::inform_new_client(AsyncWebSocketClient* client) { - String s; - s += pm->json(); - s += '\n'; - s += player->json(); - s += '\n'; - s += player->position_json(); - s += '\n'; - s += json(); - client->text(s); -} - -void Controller::queue_command(String s) { - DEBUG("Enqeueing command '%s'.\n", s.c_str()); - _cmd_queue = s; -} - -void Controller::update_playlist_manager() { - pm->scan_files(); - send_playlist_manager_status(); -} +} \ No newline at end of file diff --git a/src/data_sources.cpp b/src/data_sources.cpp deleted file mode 100644 index 0fb4337..0000000 --- a/src/data_sources.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "data_sources.h" - -////////////// SDDataSource ////////////// -SDDataSource::SDDataSource(String file) { _file = SD.open(file, "r"); } -SDDataSource::~SDDataSource() { if (_file) _file.close(); } -size_t SDDataSource::read(uint8_t* buf, size_t len) { return _file.read(buf, len); } -int SDDataSource::read() { return _file.read(); } -size_t SDDataSource::position() { return _file.position(); } -void SDDataSource::seek(size_t position) { _file.seek(position); } -size_t SDDataSource::size() { return _file.size(); } -void SDDataSource::close() { _file.close(); } -bool SDDataSource::usable() { return _file; } - -void SDDataSource::skip_id3_tag() { - uint32_t original_position = _file.position(); - uint32_t offset = 0; - if (_file.read()=='I' && _file.read()=='D' && _file.read()=='3') { - DEBUG("ID3 tag found\n"); - // Skip ID3 tag version - _file.read(); _file.read(); - byte tags = _file.read(); - bool footer_present = tags & 0x10; - DEBUG("ID3 footer found: %d\n", footer_present); - for (byte i=0; i<4; i++) { - offset <<= 7; - offset |= (0x7F & _file.read()); - } - offset += 10; - if (footer_present) offset += 10; - DEBUG("ID3 tag length is %d bytes.\n", offset); - _file.seek(offset); - } else { - DEBUG("No ID3 tag found\n"); - _file.seek(original_position); - } -} - - - -////////////// HTTPSDataSource ////////////// -HTTPSDataSource::HTTPSDataSource(String url, uint32_t offset) { - _http = new HTTPClientWrapper(); - if (!_http->get(url, offset)) return; - _position = 0; -} - -HTTPSDataSource::~HTTPSDataSource() { - _http->close(); - delete _http; -} -bool HTTPSDataSource::usable() { return _http; } -size_t HTTPSDataSource::read(uint8_t* buf, size_t len) { size_t result = _http->read(buf, len); _position += result; return result; } -int HTTPSDataSource::read() { int b = _http->read(); if (b>=0) _position++; return b; } -size_t HTTPSDataSource::position() { return _position; } -void HTTPSDataSource::seek(size_t position) { return; /* TODO */ } -size_t HTTPSDataSource::size() { return _http->getSize(); } -void HTTPSDataSource::close() { _http->close(); } diff --git a/src/esmp3.cpp b/src/esmp3.cpp new file mode 100644 index 0000000..1e3b830 --- /dev/null +++ b/src/esmp3.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include "spi_master.h" +#include "playlist_manager.h" +#include "controller.h" +#include +#include "esmp3.h" +#include + +Controller controller; +Audio audio; +Ticker ticker_save_audio_current_time; +PlaylistManager* pm; + +void setup() { + Serial.begin(115200); + WiFi.begin("Schlenz", "1410WischlingenPanda"); + Serial.print("Connecting to WiFi..."); + while(WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.println(); + Serial.print("Connected to WiFi. IP address: "); + Serial.println(WiFi.localIP()); + + ArduinoOTA.begin(); + Serial.println("Initializing SPI..."); + SPI.begin(); + SPI.setHwCs(false); + SPIMaster::initialize(); + + Serial.print("Initializing SD card..."); + SPIMaster::enable_sd(); + while(!SD.begin(14, SPI, 25000000)) { + for(int i=0; i<10; i++) { + if(SPI.transfer(0xFF)==0xFF) break; + delay(10); + } + Serial.print("."); + delay(100); + } + Serial.println(); + + Serial.println("Initializing PlaylistManager..."); + pm = new PlaylistManager(); + + Serial.println("Setting up audio..."); + audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT); + audio.setVolume(15); + audio.forceMono(true); + + Serial.println("Setting up tickers..."); + ticker_save_audio_current_time.attach(5, save_audio_current_time); + + Serial.println("Setup finished."); +} + +void loop() { + ArduinoOTA.handle(); + controller.handle(); + audio.loop(); +} + +void save_audio_current_time() { + pm->set_audio_current_time(audio.getAudioCurrentTime()); +} \ No newline at end of file diff --git a/src/http_client_wrapper.cpp b/src/http_client_wrapper.cpp deleted file mode 100644 index bb1a90a..0000000 --- a/src/http_client_wrapper.cpp +++ /dev/null @@ -1,213 +0,0 @@ -#include "http_client_wrapper.h" -#include - -HTTPClientWrapper::HTTPClientWrapper() { - _buffer = new uint8_t[512]; - _buffer_size = 512; -} - -HTTPClientWrapper::~HTTPClientWrapper() { - if (_http) { - _http->end(); - delete _http; - } - delete _buffer; -} - -void HTTPClientWrapper::close() { - _http->end(); - _connected = false; -} - -bool HTTPClientWrapper::get(String url, uint32_t offset, uint8_t redirection_count) { return _request("GET", url, offset, redirection_count); } -bool HTTPClientWrapper::head(String url, uint32_t offset, uint8_t redirection_count) { return _request("HEAD", url, offset, redirection_count); } - -bool HTTPClientWrapper::_request(String method, String url, uint32_t offset, uint8_t redirection_count) { - if (redirection_count>=5) return false; - - //if (_http) delete _http; - - DEBUG("%s %s\n", method.c_str(), url.c_str()); - _http = new HTTPClient(); - - - _http->setUserAgent("PodBox/0.1"); - if (offset) { - String s = "bytes="; - s += offset; - s += "-"; - _http->addHeader("Range: ", s); - } - - const char* headers[] = {"Location", "Content-Type", "Transfer-Encoding"}; - _http->collectHeaders(headers, 3); - bool result; - /*if (url.startsWith("https:")) { - BearSSL::WiFiClientSecure* client = new BearSSL::WiFiClientSecure(); - client->setInsecure(); - result = _http->begin(*client, url); - } else { - result = _http->begin(url); - }*/ - result = _http->begin(url); - TRACE("HTTP->begin result: %d\n", result); - if (!result) return false; - - int status = _http->sendRequest(method.c_str()); - TRACE("HTTP Status code: %d\n", status); - if (status == HTTP_CODE_FOUND || status==HTTP_CODE_MOVED_PERMANENTLY || status==HTTP_CODE_TEMPORARY_REDIRECT) { - if (_http->hasHeader("Location")) { - url = _http->header("Location"); - _http->end(); - delete _http; - _http = NULL; - return _request(method, url, offset, redirection_count+1); - } else { - ERROR("Got redirection HTTP code, but no Location header.\n"); - delete _http; - _http = NULL; - return false; - } - } else if (status != HTTP_CODE_OK) { - DEBUG("Unexpected HTTP return code %d. Cancelling.\n", status); - return false; - } - - _buffer_position = 0; - _buffer_length = 0; - - _connected = true; - _length = _http->getSize() + offset; - if (_http->hasHeader("Content-Type")) { - _content_type = _http->header("Content-Type"); - } else { - _content_type = ""; - } - _is_chunked = (_http->hasHeader("Transfer-Encoding")) && (_http->header("Transfer-Encoding").indexOf("chunked")!=-1); - _stream = _http->getStreamPtr(); - if (_is_chunked) { - _read_next_chunk_header(true); - } - return true; -} - -void HTTPClientWrapper::_read_next_chunk_header(bool first) { - if (!_connected) { - _chunk_length = 0; - return; - } - - if (!first) { - // read() returns an error if no bytes is available right at this moment. - // So we wait until 2 bytes are available or the connection times out. - while (_stream->connected() && !_stream->available()) { delay(1); } - int c1 = _stream->read(); - while (_stream->connected() && !_stream->available()) { delay(1); } - int c2 = _stream->read(); - if (c1==-1 || c2==-1) { - ERROR("Connection timeout.\n"); - DEBUG("_stream.connected() returns %d\n", _stream->connected()); - _chunk_length = 0; - _connected = false; - return; - } else if (c1!='\r' || c2!='\n') { - ERROR("Invalid chunk border found. Found: 0x%02X 0x%02X\n", c1, c2); - _chunk_length = 0; - _connected = false; - return; - } - } - String chunk_header = _stream->readStringUntil('\n'); - chunk_header.trim(); - _chunk_length = strtol(chunk_header.c_str(), NULL, 16); - if (_chunk_length == 0) { - _connected = false; - TRACE("Empty chunk found -> EOF reached.\n"); - } else { - TRACE("Chunk found. Length: %d\n", _chunk_length); - } -} - -uint16_t HTTPClientWrapper::_fill_buffer() { - if (!_connected) { - _buffer_position = 0; - _buffer_length = 0; - return 0; - } - - uint16_t bytes_to_fill = _buffer_size; - uint16_t bytes_filled = 0; - while (bytes_to_fill > 0) { - uint16_t bytes_to_request = bytes_to_fill; - if (_is_chunked && _chunk_length < bytes_to_fill) bytes_to_request = _chunk_length; - TRACE("fill_buffer loop. _is_chunked: %d, _chunk_length: %d, _buffer_size: %d, bytes_filled: %d, bytes_to_fill: %d, bytes_to_request: %d", _is_chunked, _chunk_length, _buffer_size, bytes_filled, bytes_to_fill, bytes_to_request); - uint16_t result = _stream->readBytes(_buffer + bytes_filled, bytes_to_request); - TRACE(", result: %d\n", result); - bytes_filled += result; - bytes_to_fill -= result; - if (_is_chunked) { - _chunk_length -= result; - if (_chunk_length == 0) _read_next_chunk_header(false); - } - if (result == 0) { - _connected = false; - break; - } - } - _buffer_position = 0; - _buffer_length = bytes_filled; - TRACE("Buffer filled. _buffer_length: %d\n", _buffer_length); - return bytes_filled; -} - -String HTTPClientWrapper::getContentType() { - return _content_type; -} - -int HTTPClientWrapper::read() { - if (_buffer_position >= _buffer_length) _fill_buffer(); - if (_buffer_position >= _buffer_length) return -1; - return _buffer[_buffer_position++]; -} - -uint32_t HTTPClientWrapper::read(uint8_t* dst, uint32_t len) { - TRACE("Reading %d bytes...\n", len); - uint32_t bytes_filled = 0; - while (1) { - if (_buffer_position >= _buffer_length) _fill_buffer(); - if (_buffer_position >= _buffer_length) break; - - uint32_t bytes_to_fill = len; - if (bytes_to_fill > _buffer_length - _buffer_position) bytes_to_fill = _buffer_length - _buffer_position; - - TRACE("read_loop: _buffer_length=%d, _buffer_position=%d, len=%d, bytes_to_fill=%d\n", _buffer_length, _buffer_position, len, bytes_to_fill); - memcpy(dst + bytes_filled, _buffer + _buffer_position, bytes_to_fill); - _buffer_position += bytes_to_fill; - bytes_filled += bytes_to_fill; - len -= bytes_to_fill; - if (bytes_to_fill==0 || len==0) break; - } - return bytes_filled; -} - -uint32_t HTTPClientWrapper::getSize() {return _length; } - -String HTTPClientWrapper::readUntil(String sep) { - String result = ""; - while(true) { - int i = read(); - if (i==-1) break; - char c = i; - if (sep.indexOf(c)!=-1) { - // separator - if (result.length()>0) break; - } else { - result.concat(c); - } - } - return result; -} - -String HTTPClientWrapper::readLine() { - return readUntil("\n\r"); -} diff --git a/src/http_server.cpp b/src/http_server.cpp deleted file mode 100644 index 087d9f7..0000000 --- a/src/http_server.cpp +++ /dev/null @@ -1,167 +0,0 @@ -#include "http_server.h" -#include "main.h" -#include "spi_master.h" -#include - -HTTPServer::HTTPServer(Player* p, Controller* c) { - _player = p; - _controller = c; - _server = new AsyncWebServer(80); - ws = new AsyncWebSocket("/ws"); - _server->addHandler(ws); - ws->onEvent([&](AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){this->_onEvent(server, client, type, arg, data, len);}); - - _server->on("/", HTTP_GET, [&](AsyncWebServerRequest* req) { - req->send(200, "text/html", (const char*)file_index_html_start); - }); - _server->on("/upload", HTTP_POST, [](AsyncWebServerRequest* req) { - req->send(200); - }, [&](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { - this->_handle_upload(request, filename, index, data, len, final); - }); - _server->on("/_mapping.txt", HTTP_GET, [&](AsyncWebServerRequest* req) { - req->send(200, "text/plain", _controller->pm->create_mapping_txt()); - }); - _server->on("/player.json", HTTP_GET, [&](AsyncWebServerRequest* req) { - req->send(200, "application/json", _controller->player->json()); - }); - _server->on("/playlist_manager.json", HTTP_GET, [&](AsyncWebServerRequest* req) { - req->send(200, "application/json", _controller->pm->json()); - }); - _server->on("/controller.json", HTTP_GET, [&](AsyncWebServerRequest* req) { - req->send(200, "application/json", _controller->json()); - }); - _server->on("/position.json", HTTP_GET, [&](AsyncWebServerRequest* req) { - req->send(200, "application/json", _controller->player->position_json()); - }); - _server->on("/cmd", HTTP_POST, [&](AsyncWebServerRequest *req) { - if (req->hasParam("cmd", true)) { - _controller->queue_command(req->getParam("cmd", true)->value()); - req->send(200); - } else { - req->send(400); - } - }); - _server->begin(); - MDNS.addService("http", "tcp", 80); -} - -void HTTPServer::_handle_upload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final) { - // https://www.gnu.org/software/tar/manual/html_node/Standard.html - // https://www.mkssoftware.com/docs/man4/tar.4.asp - - if (index == 0) { // Starting upload - _chunk = new uint8_t[512]; - _chunk_length = 0; - _upload_position = 0; - _file_size = 0; - _file_size_done = 0; - _need_header = true; - } - - uint32_t upload_offset = 0; - while (upload_offset < len) { - // Load a chunk - if (_chunk_length < 512 && len > upload_offset) { - uint16_t needed = 512 - _chunk_length; - if (needed > len - upload_offset) needed = len - upload_offset; - memcpy(_chunk + _chunk_length, data + upload_offset, needed); - _chunk_length += needed; - upload_offset += needed; - _upload_position += needed; - - if (_chunk_length == 512) { - // Process chunk - DEBUG("."); - 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) { - String path = "/"; - path += filename; - DEBUG("Opening file %s\n", path.c_str()); - uint8_t state = SPIMaster::state; - SPIMaster::disable(); - SPIMaster::select_sd(); - // Better safe than sorry. ;-) - _upload_file.close(); - _upload_file = SD.open(path, "w"); - SPIMaster::set_state(state); - } else if (type==5) { - String dirname = "/"; - dirname += filename; - dirname.remove(dirname.length()-1); - uint8_t state = SPIMaster::state; - SPIMaster::disable(); - SPIMaster::select_sd(); - bool res = SD.mkdir(dirname); - SPIMaster::set_state(state); - DEBUG("Creating folder '%s' returned %d.\n", dirname.c_str(), res); - } 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 { - uint32_t bytes_to_write = _file_size - _file_size_done; - if (bytes_to_write > 512) bytes_to_write=512; - uint8_t state = SPIMaster::state; - SPIMaster::disable(); - SPIMaster::select_sd(); - _upload_file.write(_chunk, bytes_to_write); - _file_size_done += bytes_to_write; - if (_file_size_done >= _file_size) { - _upload_file.close(); - _need_header = true; - } - SPIMaster::set_state(state); - } - - _chunk_length = 0; - } - } - } - - if (final == true) { - uint8_t state = SPIMaster::state; - SPIMaster::disable(); - SPIMaster::select_sd(); - _upload_file.close(); - SPIMaster::set_state(state); - delete _chunk; - _controller->update_playlist_manager(); - return; - } -} - -void HTTPServer::_onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) { - if (type==WS_EVT_CONNECT) { - _controller->inform_new_client(client); - } else if (type==WS_EVT_DATA) { - AwsFrameInfo* info = (AwsFrameInfo*) arg; - if (info->final && info->index==0 && info->len==len && info->opcode==WS_TEXT) { - data[len]='\0'; - DEBUG("Received ws message: %s\n", (char*)data); - _controller->queue_command((char*)data); - } - } -} diff --git a/src/index.html b/src/index.html deleted file mode 100644 index e9048f8..0000000 --- a/src/index.html +++ /dev/null @@ -1,359 +0,0 @@ - - - - - - ESMP3 - - - - - - - - - -
Not connected...
-
-
-
-

-
-
-

-

-
-
-
-
-
-
- -
-
-
-
-
-
-

-
-
- -
-
-

-
-
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
- -
- - - - - - - - - - - -
Nr.StatusTrack
-
- - - - - - - - - - diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index ecbb014..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,149 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include "main.h" -#include "config.h" -#include "controller.h" -#include "player.h" -#include "spi_master.h" -#include "http_server.h" -#include "playlist_manager.h" -#include "updater.h" - -Controller* controller; -Player* player; -PlaylistManager* pm; -HTTPServer* http_server; - -uint8_t SPIMaster::state = 0; - -bool debug_enabled = true; -bool trace_enabled = false; -Preferences prefs; - -void wifi_connect() { - INFO("Connecting to WiFi...\n"); - WiFiMulti wifi; - SPIMaster::select_sd(); - if (SD.exists("/_wifis.txt")) { - DEBUG("Reading /_wifis.txt\n"); - File f = SD.open("/_wifis.txt", "r"); - while (String line = f.readStringUntil('\n')) { - if (line.length()==0) { - break; - } else if (line.startsWith("#") || line.indexOf('=')==-1) { - continue; - } - String ssid = line.substring(0, line.indexOf('=')); - String pass = line.substring(line.indexOf('=')+1); - wifi.addAP(ssid.c_str(), pass.c_str()); - } - f.close(); - } else { - File f = SD.open("/_wifis.txt", "w"); - f.print("# WiFi definitions. Syntax: =. Lines starting with # are ignored. Example:\n# My WiFi=VerySecretPassword\n"); - f.close(); - } - SPIMaster::select_sd(false); - - #if defined(WIFI_SSID) and defined(WIFI_PASS) - wifi.addAP(WIFI_SSID, WIFI_PASS); - #endif - - if (wifi.run() == WL_CONNECTED) { - INFO("Connected to WiFi \"%s\".\n", WiFi.SSID().c_str()); - INFO("Local IP address: %s\n", WiFi.localIP().toString().c_str()); - } else { - DEBUG("No WiFi connection!\n"); - } -} - -void setup() { - // Small delay to give the Serial console a bit of time to connect. - delay(1000); - Serial.begin(115200); - Serial.println("Starting..."); - Serial.println("Started."); - INFO("Starting.\n"); - #ifdef VERSION - INFO("ESMP3 version %s (OTA_VERSION %d)\n", VERSION, OTA_VERSION); - #else - INFO("ESMP3, version unknown (OTA_VERSION %d)\n", OTA_VERSION); - #endif - INFO("Initializing...\n"); - prefs.begin("esmp3"); - debug_enabled = prefs.getBool("debug_enabled", true); - trace_enabled = prefs.getBool("trace_enabled", false); -delay(1000); - PIN_SPEAKER_L_SETUP();delay(1000); - PIN_SPEAKER_R_SETUP();delay(1000); - PIN_SPEAKER_L(LOW);delay(1000); - PIN_SPEAKER_R(LOW);delay(1000); - - DEBUG("Setting up SPI...\n");delay(1000); - SPI.begin(); - SPI.setHwCs(false); - SPIMaster::init(); - SPIMaster* spi = new SPIMaster(); - INFO("SPI initialized.\n"); - - DEBUG("Setting up SD card...\n"); - spi->select_sd(); - if (SD.begin(14, SPI, 25000000)) { - INFO("SD card initialized.\n"); - } else { - ERROR("Could not initialize SD card.\n"); - } - spi->select_sd(false); - - DEBUG("Initializing PlaylistManager...\n"); - pm = new PlaylistManager(); - - DEBUG("Initializing Player and Controller...\n"); - player = new Player(spi); - controller = new Controller(player, pm); - INFO("Player and controller initialized.\n"); - - wifi_connect(); - - ArduinoOTA.begin(); - - MDNS.begin("esmp3"); - - DEBUG("Setting up HTTP server...\n"); - http_server = new HTTPServer(player, controller); - controller->register_http_server(http_server); - - DEBUG("Starting NTP client...\n"); - // Taken from https://github.com/esp8266/Arduino/blob/master/cores/esp8266/TZ.h - configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "europe.pool.ntp.org"); - struct tm time; - if (getLocalTime(&time, 10000)) { - char buffer[100]; - strftime(buffer, 100, "%Y-%m-%d %H:%M:%S", &time); - DEBUG("Got time: %s\n", buffer); - } else { - INFO("Could not fetch current time via NTP.\n"); - } - - #ifdef VERSION - INFO("ESMP3 version %s (OTA_VERSION %d)\n", VERSION, OTA_VERSION); - #else - INFO("ESMP3, version unknown (OTA_VERSION %d)\n", OTA_VERSION); - #endif - - INFO("Initialization completed.\n"); -} - -void loop() { - bool more_data_needed = player->loop(); - if (more_data_needed) return; - - controller->loop(); - ArduinoOTA.handle(); -} diff --git a/src/player.cpp b/src/player.cpp deleted file mode 100644 index e3b4740..0000000 --- a/src/player.cpp +++ /dev/null @@ -1,753 +0,0 @@ -// Based on https://github.com/mpflaga/Arduino_Library-vs1053_for_SdFat/blob/master/src/vs1053_SdFat.cpp - -#include "player.h" -#include "spi_master.h" -#include - -//Player::_spi_settings - -Player::Player(SPIMaster* s) { - _spi = s; - PIN_VS1053_XRESET_SETUP(); - PIN_VS1053_XRESET(HIGH); - _speaker_off(); - _spi->disable(); - PIN_VS1053_DREQ_SETUP(); - - init(); -} - -void Player::register_controller(Controller* c) { - _controller = c; -} - -void Player::_reset() { - PIN_VS1053_XRESET(LOW); - delay(100); - PIN_VS1053_XRESET(HIGH); - delay(100); - _state = uninitialized; - _spi_settings = &_spi_settings_slow; // After reset, communication has to be slow -} - -void Player::init() { - DEBUG("Resetting VS1053...\n"); - _reset(); - - uint16_t result = _read_control_register(SCI_MODE); - DEBUG("SCI_MODE: 0x%04X\n", result); - if (result != 0x4800) { - ERROR("SCI_MODE was 0x%04X, expected was 0x4800. Rebooting.\n", result); - delay(500); - ESP.restart(); - } - result = _read_control_register(SCI_STATUS); - DEBUG("SCI_STATUS: 0x%04X\n", result); - if (result != 0x0040 && result != 0x0048) { - ERROR("SCI_STATUS was 0x%04X, expected was 0x0040 or 0x0048. Rebooting.\n", result); - delay(500); - ESP.restart(); - } - result = _read_control_register(SCI_CLOCKF); - DEBUG("SCI_CLOCKF: 0x%04X\n", result); - - DEBUG("VS1053 Init looking good.\n"); - DEBUG("Upping VS1053 multiplier...\n"); - - _write_control_register(SCI_CLOCKF, 0xC000); - delay(10); - - _spi_settings = &_spi_settings_fast; - - result = _read_control_register(SCI_CLOCKF); - DEBUG("SCI_CLOCKF: 0x%04X\n", result); - if (result != 0xC000) { - ERROR("Error: SCI_CLOCKF was 0x%04X, expected was 0xC000. Rebooting.\n", result); - delay(500); - ESP.restart(); - } - - set_volume(VOLUME_DEFAULT); - - INFO("VS1053 initialization completed.\n"); - - _state = idle; -} - -void Player::_speaker_off() { - DEBUG("Speaker off\n"); - PIN_SPEAKER_L(LOW); - PIN_SPEAKER_R(LOW); -} - -void Player::_speaker_on() { - DEBUG("Speaker on\n"); - PIN_SPEAKER_L(HIGH); - PIN_SPEAKER_R(HIGH); -} - -void Player::_sleep() { - DEBUG("VS1053 going to sleep.\n"); - _speaker_off(); - _write_control_register(SCI_CLOCKF, 0x0000); - _spi_settings = &_spi_settings_slow; - _write_control_register(SCI_AUDATA, 0x0010); - set_volume(0, false); - _state = sleeping; - TRACE("VS1053 is sleeping now.\n"); -} - -void Player::_wakeup() { - if (_state != sleeping && _state != recording) return; - _stopped_at = millis(); - DEBUG("Waking VS1053...\n"); - set_volume(_volume, false); - _write_control_register(SCI_AUDATA, 0x0000); - _write_control_register(SCI_CLOCKF, 0x6000); - _write_control_register(SCI_MODE, 0x4800 | SM_RESET); - delay(10); - //_speaker_on(); - _spi_settings = &_spi_settings_fast; - _state = idle; -} - -void Player::_record() { - // http://www.vlsi.fi/fileadmin/software/VS10XX/VS1053_VS1063_PcmRecorder.pdf - DEBUG("Starting recording.\n"); - set_volume(1, false); - - // Disable SCI_BASS - _write_control_register(SCI_BASS, 0); - - // Disable user applications - _write_control_register(SCI_AIADDR, 0); - - // Disable interrupts - _write_control_register(SCI_WRAMADDR, 0xC01A); - _write_control_register(SCI_WRAM, 0x0002); - - _patch_adpcm(); - - _write_control_register(SCI_MODE, SM_ADPCM); - - _write_control_register(SCI_AICTRL0, 0x8000); // Mono VU meter - _write_control_register(SCI_AICTRL1, 1024); // Manual gain, 1x - _write_control_register(SCI_AICTRL2, 0); // Maximum gain for autogain - ignored - _write_control_register(SCI_AICTRL3, 0); // status: record - - _write_control_register(SCI_AIADDR, 0x0034, false); - delay(1); - - DEBUG("Recording.\n"); - delay(10); - _state = recording; -} - -inline void Player::_wait() { - while(!PIN_VS1053_DREQ()); -} - -uint16_t Player::_read_control_register(uint8_t address, bool do_wait) { - if (do_wait) _wait(); - _spi->select_vs1053_xcs(); - SPI.beginTransaction(*_spi_settings); - SPI.transfer(CMD_READ); - SPI.transfer(address); - uint8_t b1 = SPI.transfer(0xFF); - _wait(); - uint8_t b2 = SPI.transfer(0xFF); - _wait(); - SPI.endTransaction(); - _spi->select_vs1053_xcs(false); - - return (b1 << 8) | b2; -} - -void Player::_write_control_register(uint8_t address, uint16_t value, bool do_wait) { - _wait(); - _spi->select_vs1053_xcs(); - SPI.beginTransaction(*_spi_settings); - SPI.transfer(CMD_WRITE); - SPI.transfer(address); - SPI.transfer(value >> 8); - SPI.transfer(value & 0xFF); - SPI.endTransaction(); - _spi->select_vs1053_xcs(false); - if (do_wait) _wait(); -} - -void Player::_patch_adpcm() { - static const uint16_t patch_data[] = { - 0x0007, 0x0001, 0xc01a, 0x0006, 0x0001, 0x0002, 0x0007, 0x0001, /* 0 */ - 0x0008, 0x0006, 0x8002, 0x0000, 0x0007, 0x0001, 0x000c, 0x0006, /* 8 */ - 0x0002, 0x7000, 0x0017, 0x0007, 0x0001, 0x8034, 0x0006, 0x0022, /* 10 */ - 0x0030, 0x0490, 0xb080, 0x0024, 0x3800, 0x0024, 0x0000, 0x1090, /* 18 */ - 0xf400, 0x5404, 0x0000, 0x0851, 0xf400, 0x5648, 0xf400, 0x5404, /* 20 */ - 0xf400, 0x5658, 0xf400, 0x5404, 0xf400, 0x5640, 0x0000, 0x800a, /* 28 */ - 0x2900, 0x9180, 0x0006, 0x2016, 0x2a00, 0x1bce, 0x2a00, 0x114e, /* 30 */ - 0x2a00, 0x168e, 0x0007, 0x0001, 0x1800, 0x0006, 0x8006, 0x0000, /* 38 */ - 0x0007, 0x0001, 0x8045, 0x0006, 0x002a, 0x3e12, 0xb817, 0x3e12, /* 40 */ - 0x7808, 0x3e18, 0x3821, 0x3e18, 0xb823, 0x3e15, 0x4024, 0x3e10, /* 48 */ - 0x7800, 0x48b2, 0x0024, 0x0000, 0x800a, 0x2900, 0x3e80, 0x3e10, /* 50 */ - 0x7800, 0x36f0, 0x5800, 0x2210, 0x0000, 0x36f0, 0x5800, 0x36f5, /* 58 */ - 0x4024, 0x36f8, 0x9823, 0x36f8, 0x1821, 0x36f2, 0x5808, 0x3602, /* 60 */ - 0x8024, 0x0030, 0x0717, 0x2100, 0x0000, 0x3f05, 0xdbd7, 0x0007, /* 68 */ - 0x0001, 0x805a, 0x0006, 0x002a, 0x3e12, 0xb817, 0x3e12, 0x7808, /* 70 */ - 0x3e18, 0x3821, 0x3e18, 0xb823, 0x3e15, 0x4024, 0x3e10, 0x7800, /* 78 */ - 0x48b2, 0x0024, 0x0000, 0x800a, 0x2900, 0x5e40, 0x3e10, 0x7800, /* 80 */ - 0x36f0, 0x5800, 0x2210, 0x0000, 0x36f0, 0x5800, 0x36f5, 0x4024, /* 88 */ - 0x36f8, 0x9823, 0x36f8, 0x1821, 0x36f2, 0x5808, 0x3602, 0x8024, /* 90 */ - 0x0030, 0x0717, 0x2100, 0x0000, 0x3f05, 0xdbd7, 0x0007, 0x0001, /* 98 */ - 0x806f, 0x0006, 0x0030, 0x3e12, 0xb817, 0x3e12, 0x7808, 0x3e18, /* a0 */ - 0x3821, 0x3e18, 0xb823, 0x3e10, 0x7800, 0xb880, 0x3855, 0x0030, /* a8 */ - 0x0497, 0x48b2, 0x3c00, 0x0000, 0x800a, 0x2900, 0x7300, 0x3e10, /* b0 */ - 0x7800, 0x36f0, 0x5800, 0x2210, 0x0000, 0x6890, 0x1bd5, 0x0030, /* b8 */ - 0x0497, 0x3f00, 0x0024, 0x36f0, 0x5800, 0x36f8, 0x9823, 0x36f8, /* c0 */ - 0x1821, 0x36f2, 0x5808, 0x3602, 0x8024, 0x0030, 0x0717, 0x2100, /* c8 */ - 0x0000, 0x3f05, 0xdbd7, 0x0007, 0x0001, 0x8010, 0x0006, 0x000e, /* d0 */ - 0x3e02, 0x8024, 0x0001, 0x000a, 0x6012, 0x0024, 0xfea2, 0x0024, /* d8 */ - 0x48b2, 0x1bca, 0x2000, 0x0000, 0x4180, 0x0024, 0x0007, 0x0001, /* e0 */ - 0x8087, 0x0006, 0x00e6, 0x3e00, 0x7843, 0x3e01, 0x3845, 0x3e04, /* e8 */ - 0x3812, 0x0006, 0x08d0, 0x3000, 0x4024, 0x6182, 0x0024, 0x0030, /* f0 */ - 0x06d0, 0x2800, 0x2655, 0xb882, 0x0024, 0x0000, 0x0201, 0x0000, /* f8 */ - 0x0005, 0x0030, 0x0210, 0xa016, 0x4004, 0x1fff, 0xfe01, 0xae1a, /* 100 */ - 0x0024, 0xc342, 0x0024, 0xb882, 0x2001, 0x0030, 0x06d0, 0x3800, /* 108 */ - 0x4024, 0x0006, 0x0890, 0x3004, 0x0024, 0x3000, 0x4024, 0x0006, /* 110 */ - 0x12d0, 0x6182, 0x0024, 0x3000, 0x4024, 0x2800, 0x3e05, 0xf400, /* 118 */ - 0x4050, 0x3009, 0x2000, 0x0006, 0x08d0, 0x0006, 0x0892, 0x3000, /* 120 */ - 0x4024, 0x6192, 0x0024, 0x3800, 0x4024, 0x0030, 0x0250, 0xb882, /* 128 */ - 0x2001, 0x0030, 0x0710, 0x3800, 0x4024, 0x0006, 0x12d0, 0x3000, /* 130 */ - 0x4024, 0x6192, 0x0024, 0x3800, 0x4024, 0x3204, 0x0024, 0x3023, /* 138 */ - 0x0024, 0x30e0, 0xc024, 0x6312, 0x0024, 0x0000, 0x00c3, 0x2800, /* 140 */ - 0x3141, 0x0000, 0x0024, 0x3033, 0x0024, 0x3a04, 0x0024, 0x3000, /* 148 */ - 0x4024, 0x6182, 0x0024, 0x0006, 0x0890, 0x2800, 0x2fd8, 0x0006, /* 150 */ - 0x0301, 0x3a00, 0x4024, 0x0000, 0x00c3, 0x3004, 0x0024, 0x3013, /* 158 */ - 0x0024, 0x3000, 0x4024, 0x0006, 0x12d0, 0x3800, 0x4024, 0x0030, /* 160 */ - 0x0310, 0xf000, 0x0001, 0x6236, 0x0024, 0x001f, 0xffc3, 0x2800, /* 168 */ - 0x3395, 0x0000, 0x0024, 0x0000, 0x0203, 0xa132, 0x0024, 0x001f, /* 170 */ - 0xffc3, 0xb136, 0x0024, 0x6306, 0x0024, 0x0000, 0x0024, 0x2800, /* 178 */ - 0x3611, 0x0000, 0x0024, 0x0020, 0x0003, 0xb132, 0x0024, 0x0000, /* 180 */ - 0x0024, 0x2800, 0x3a85, 0x0000, 0x0024, 0x0000, 0x0081, 0xb212, /* 188 */ - 0x0024, 0x0000, 0x0024, 0x2800, 0x3a05, 0x0000, 0x0024, 0x6892, /* 190 */ - 0x0024, 0xb212, 0x0024, 0x0000, 0x0005, 0x2800, 0x3c55, 0x0030, /* 198 */ - 0x0310, 0x0000, 0x3fc1, 0x3000, 0x8024, 0xb214, 0x0024, 0x003f, /* 1a0 */ - 0xc001, 0xb010, 0x0024, 0xc200, 0x0024, 0x0030, 0x0310, 0x3800, /* 1a8 */ - 0x0024, 0x36f4, 0x1812, 0x36f1, 0x1805, 0x36f0, 0x5803, 0x2000, /* 1b0 */ - 0x0000, 0x0000, 0x0024, 0x0030, 0x0310, 0x0000, 0x0005, 0x003f, /* 1b8 */ - 0xc001, 0x4088, 0x0002, 0xb214, 0x0024, 0x1fff, 0xfe01, 0xae12, /* 1c0 */ - 0x0024, 0x2800, 0x3a00, 0xc200, 0x0024, 0x2800, 0x28c0, 0x3800, /* 1c8 */ - 0x0024, 0x0007, 0x0001, 0x80fa, 0x0006, 0x00fe, 0x3e12, 0x0024, /* 1d0 */ - 0x3e05, 0xb814, 0x3615, 0x0024, 0x3e00, 0x3841, 0x3e00, 0xb843, /* 1d8 */ - 0x3e01, 0x3845, 0x3e04, 0x3851, 0x0030, 0x10d0, 0x3e04, 0x8024, /* 1e0 */ - 0x3010, 0x0024, 0x3000, 0x8024, 0x0006, 0x1190, 0x3000, 0x4024, /* 1e8 */ - 0x6182, 0x0024, 0x0000, 0x0024, 0x2800, 0x5dd5, 0x0000, 0x0024, /* 1f0 */ - 0x0030, 0x03d0, 0x0000, 0x00c1, 0x3000, 0xc024, 0xb318, 0x0024, /* 1f8 */ - 0x6896, 0x0024, 0x6436, 0x0024, 0x0020, 0x0003, 0x2800, 0x59c5, /* 200 */ - 0x0000, 0x0024, 0x0006, 0x1150, 0x3000, 0x4024, 0x6136, 0x0024, /* 208 */ - 0x0000, 0x0024, 0x2800, 0x4741, 0x0000, 0x0024, 0x0000, 0x0803, /* 210 */ - 0x4132, 0x0024, 0x3800, 0x4024, 0x0006, 0x0190, 0x0006, 0xf011, /* 218 */ - 0x2900, 0xb500, 0x3613, 0x0024, 0x0006, 0xf011, 0x0006, 0x1152, /* 220 */ - 0x0006, 0x0250, 0x4082, 0x0800, 0xfe82, 0x184c, 0x1fff, 0xfc41, /* 228 */ - 0x48ba, 0x0024, 0xae1a, 0x0024, 0x2900, 0xb500, 0x4280, 0x4103, /* 230 */ - 0x0006, 0x1110, 0x4084, 0x0800, 0xfe84, 0x0002, 0x48ba, 0x0024, /* 238 */ - 0xae12, 0x0024, 0xf400, 0x4001, 0x0000, 0x0180, 0x6200, 0x0024, /* 240 */ - 0x0000, 0x0080, 0x2800, 0x5241, 0x4200, 0x0024, 0x3800, 0x0024, /* 248 */ - 0x0006, 0x1090, 0x3004, 0x8024, 0xf400, 0x4491, 0x3113, 0x0024, /* 250 */ - 0x3804, 0x4024, 0x3a00, 0xc024, 0x3004, 0x8024, 0xf400, 0x4491, /* 258 */ - 0x3113, 0x0024, 0x3804, 0x4024, 0x3a00, 0x4024, 0x0006, 0x1081, /* 260 */ - 0x3000, 0x0024, 0x6012, 0x0024, 0x0006, 0x0f00, 0x2800, 0x5248, /* 268 */ - 0x0000, 0x0024, 0x3800, 0x0024, 0x0030, 0x0010, 0x0000, 0x0080, /* 270 */ - 0x3000, 0x4024, 0x0030, 0x0710, 0xb104, 0x0024, 0x0000, 0x0001, /* 278 */ - 0x3800, 0x4024, 0x0006, 0x08d0, 0x3001, 0x0024, 0x0006, 0x0910, /* 280 */ - 0x3000, 0x4024, 0x6100, 0x0024, 0x6042, 0x0024, 0x0030, 0x06d0, /* 288 */ - 0x2800, 0x5711, 0xb880, 0x0024, 0x2900, 0x21c0, 0x4380, 0x184c, /* 290 */ - 0xb880, 0x0024, 0x3800, 0x0024, 0x36f4, 0x8024, 0x36f4, 0x1811, /* 298 */ - 0x36f1, 0x1805, 0x36f0, 0x9803, 0x36f0, 0x1801, 0x3405, 0x9014, /* 2a0 */ - 0x36f3, 0x0024, 0x36f2, 0x0024, 0x2000, 0x0000, 0x0000, 0x0024, /* 2a8 */ - 0x0006, 0x1152, 0x0000, 0x0804, 0x3200, 0xc024, 0x6346, 0x0024, /* 2b0 */ - 0x6386, 0x2803, 0x0000, 0x0024, 0x2800, 0x4755, 0x0000, 0x0024, /* 2b8 */ - 0x3800, 0x4024, 0x0030, 0x0690, 0x0000, 0x0081, 0xb882, 0x22c1, /* 2c0 */ - 0x3800, 0x4024, 0x0030, 0x0590, 0x2800, 0x4740, 0x3800, 0x4024, /* 2c8 */ - 0x2800, 0x5700, 0x4190, 0x0024, 0x0007, 0x0001, 0x8179, 0x0006, /* 2d0 */ - 0x00a6, 0x3e12, 0x0024, 0x3e05, 0xb814, 0x3625, 0x0024, 0x3e00, /* 2d8 */ - 0x3841, 0x3e00, 0xb843, 0x3e04, 0x3851, 0x0006, 0x1110, 0x3e04, /* 2e0 */ - 0xb813, 0x3000, 0x0024, 0x6080, 0x0024, 0x0006, 0x11d2, 0x2800, /* 2e8 */ - 0x70c5, 0x0000, 0x0081, 0x6010, 0x984c, 0x3800, 0x0024, 0x0006, /* 2f0 */ - 0x10d0, 0x3200, 0x0024, 0xf100, 0x0011, 0xf100, 0x0024, 0xf102, /* 2f8 */ - 0x0400, 0x0006, 0x1311, 0x2900, 0x0400, 0x3100, 0x8024, 0x0030, /* 300 */ - 0x1293, 0x3413, 0x184c, 0x3c04, 0x4024, 0x3b00, 0x0024, 0x3004, /* 308 */ - 0xc024, 0xf400, 0x44d1, 0x3113, 0x0024, 0x3804, 0x4024, 0x3310, /* 310 */ - 0x0024, 0x3a00, 0x0024, 0x0006, 0x1212, 0x3200, 0x0024, 0xf100, /* 318 */ - 0x13d1, 0xf100, 0x0402, 0x2900, 0x0400, 0xf102, 0x0c00, 0x0030, /* 320 */ - 0x12d1, 0x0006, 0x1081, 0x3900, 0x0024, 0x3004, 0xc024, 0xf400, /* 328 */ - 0x44d1, 0x3113, 0x0024, 0x3804, 0x4024, 0x3300, 0x0024, 0x3a00, /* 330 */ - 0x0024, 0xf400, 0x4440, 0x6010, 0x0024, 0x1fee, 0xe002, 0x2800, /* 338 */ - 0x6bc8, 0x0006, 0x0f00, 0x3800, 0x0024, 0x0006, 0x0010, 0xb886, /* 340 */ - 0x0040, 0x30f0, 0x4024, 0x6c92, 0x40c3, 0x3810, 0x0024, 0xb182, /* 348 */ - 0x23c1, 0x0006, 0x0950, 0x3000, 0x0024, 0x6090, 0x0024, 0x6cd2, /* 350 */ - 0x2000, 0x0000, 0x0000, 0x2800, 0x70c8, 0x0000, 0x0024, 0x3800, /* 358 */ - 0x0024, 0x0000, 0x0210, 0x3010, 0x0024, 0x30f0, 0x4024, 0x6c92, /* 360 */ - 0x0024, 0x3810, 0x0024, 0x38f0, 0x4024, 0x36f4, 0x9813, 0x36f4, /* 368 */ - 0x1811, 0x36f0, 0x9803, 0x36f0, 0x1801, 0x3405, 0x9014, 0x36f3, /* 370 */ - 0x0024, 0x36f2, 0x0024, 0x2000, 0x0000, 0x0000, 0x0024, 0x0007, /* 378 */ - 0x0001, 0x81cc, 0x0006, 0x00f4, 0x3e00, 0x3841, 0x0000, 0x0201, /* 380 */ - 0x3e00, 0xb843, 0x3e01, 0x3845, 0x3e04, 0x3812, 0x0030, 0x0410, /* 388 */ - 0x3000, 0x0024, 0x6012, 0x0024, 0x0006, 0x08d0, 0x2800, 0x8045, /* 390 */ - 0x0000, 0x0181, 0x6012, 0x0024, 0x0006, 0x1250, 0x2800, 0x7e45, /* 398 */ - 0x0000, 0x05c1, 0x6012, 0x0024, 0x0030, 0x01d0, 0x2800, 0x7c45, /* 3a0 */ - 0x0000, 0x0581, 0x6010, 0x03cc, 0x0000, 0x0024, 0x2800, 0x7a95, /* 3a8 */ - 0x0000, 0x0024, 0x3000, 0x8024, 0x0006, 0x1250, 0x3000, 0x0024, /* 3b0 */ - 0x6092, 0x0024, 0x3800, 0x4024, 0xf400, 0x4010, 0x3800, 0x8024, /* 3b8 */ - 0x36f4, 0x1812, 0x36f1, 0x1805, 0x36f0, 0x9803, 0x36f0, 0x1801, /* 3c0 */ - 0x2000, 0x0000, 0x0000, 0x0024, 0x0030, 0x01d0, 0x3000, 0x0024, /* 3c8 */ - 0x0006, 0x1250, 0x3800, 0x0024, 0xf400, 0x4010, 0x3000, 0x0024, /* 3d0 */ - 0x0030, 0x0190, 0x2800, 0x7a80, 0x3800, 0x0024, 0x3000, 0x0024, /* 3d8 */ - 0x6090, 0x0024, 0x3800, 0x0024, 0xf400, 0x4010, 0x3000, 0x0024, /* 3e0 */ - 0x0030, 0x0190, 0x2800, 0x7a80, 0x3800, 0x0024, 0x3000, 0x0024, /* 3e8 */ - 0x6080, 0x0024, 0x0000, 0x0024, 0x2800, 0x8515, 0x0000, 0x0024, /* 3f0 */ - 0x0006, 0x1350, 0x0000, 0x0082, 0x0030, 0x0352, 0xb886, 0x0040, /* 3f8 */ - 0x30f0, 0x4024, 0x4cd2, 0x0024, 0x3810, 0x0024, 0x38f0, 0x4024, /* 400 */ - 0x3a00, 0x0024, 0x3010, 0x0024, 0x30f0, 0x4024, 0x0030, 0x0390, /* 408 */ - 0x2800, 0x7a80, 0x4180, 0x2001, 0x4090, 0x0024, 0x3800, 0x0024, /* 410 */ - 0x0030, 0x0250, 0x3800, 0x0024, 0x0006, 0x1290, 0x3000, 0x0024, /* 418 */ - 0x6090, 0x0024, 0x3800, 0x0024, 0x0006, 0x0850, 0x3004, 0x8024, /* 420 */ - 0x3223, 0x0024, 0x32e0, 0x4024, 0x6100, 0x0024, 0x0000, 0x0024, /* 428 */ - 0x2800, 0x8c81, 0x0000, 0x0024, 0x3233, 0x0024, 0x3804, 0x8024, /* 430 */ - 0x3200, 0x0024, 0x6080, 0x0024, 0x0006, 0x0300, 0x2800, 0x8b18, /* 438 */ - 0x0000, 0x0024, 0x3800, 0x0024, 0x0006, 0x0850, 0x3004, 0x0024, /* 440 */ - 0x3013, 0x0024, 0x3000, 0x0024, 0x0006, 0x1290, 0x3800, 0x0024, /* 448 */ - 0x0006, 0x0850, 0x3004, 0x0024, 0x3000, 0x0024, 0x0006, 0x1290, /* 450 */ - 0x6080, 0x0024, 0x3000, 0x0024, 0x2800, 0x9115, 0xf400, 0x4010, /* 458 */ - 0x3000, 0x0024, 0x0000, 0x0201, 0x0000, 0x0005, 0x0030, 0x0210, /* 460 */ - 0xa014, 0x4004, 0x1fff, 0xfe01, 0xae12, 0x0024, 0xc200, 0x0024, /* 468 */ - 0x2800, 0x8180, 0x3800, 0x0024, 0x2800, 0x8ec0, 0x3009, 0x0000, /* 470 */ - 0x0007, 0x0001, 0x8246, 0x0006, 0x0104, 0x0030, 0x1092, 0x0007, /* 478 */ - 0x9250, 0x003f, 0xfc42, 0xb880, 0x184c, 0x3e12, 0x0024, 0x3800, /* 480 */ - 0x0024, 0x0030, 0x0290, 0x38f0, 0x0024, 0x3800, 0x0024, 0x0030, /* 488 */ - 0x0050, 0x3000, 0x4024, 0xb122, 0x0024, 0x6894, 0x2001, 0x0000, /* 490 */ - 0x0141, 0x3a70, 0x4024, 0x0004, 0x1fc1, 0x3a00, 0x4024, 0x0030, /* 498 */ - 0x00d2, 0x0030, 0x0001, 0x3a00, 0x4024, 0x0030, 0x0552, 0x3a10, /* 4a0 */ - 0x0024, 0x3a00, 0x0024, 0x3000, 0x4024, 0xc122, 0x0024, 0x3800, /* 4a8 */ - 0x4024, 0x0030, 0x05d0, 0x0000, 0x03c1, 0x3820, 0x4024, 0x3800, /* 4b0 */ - 0x0024, 0x0000, 0x0310, 0x3010, 0x0024, 0x30f0, 0x4024, 0xf2c2, /* 4b8 */ - 0x0024, 0x3810, 0x0024, 0x0000, 0x3fc0, 0x38f0, 0x4024, 0x0030, /* 4c0 */ - 0x02d0, 0x3000, 0x4024, 0x2912, 0x1400, 0xb104, 0x0024, 0x0006, /* 4c8 */ - 0x1312, 0x6802, 0x0024, 0x000d, 0xac00, 0x6012, 0x2801, 0x0000, /* 4d0 */ - 0x0024, 0x2800, 0x9dc1, 0x0000, 0x0024, 0x3a00, 0x0024, 0x2909, /* 4d8 */ - 0x1b40, 0x3613, 0x0024, 0x0000, 0x0084, 0x0000, 0x1905, 0x2908, /* 4e0 */ - 0xbe80, 0x3613, 0x0024, 0x0000, 0x0000, 0x0006, 0x0302, 0x4002, /* 4e8 */ - 0x0024, 0x4012, 0x0024, 0x4212, 0x0024, 0xf400, 0x4050, 0x3000, /* 4f0 */ - 0x4024, 0x6182, 0x0024, 0x0006, 0x0350, 0x2800, 0xa6c8, 0x0000, /* 4f8 */ - 0x0024, 0x4002, 0x0024, 0x4014, 0x0024, 0x0006, 0x0301, 0x4124, /* 500 */ - 0x0024, 0x0000, 0x0081, 0x4212, 0x0024, 0x4002, 0x4050, 0x4014, /* 508 */ - 0x0003, 0x0006, 0x0301, 0x4122, 0x0024, 0x6192, 0x0024, 0x6090, /* 510 */ - 0x4050, 0x3000, 0x4024, 0x0006, 0x0910, 0x6312, 0x0024, 0x6194, /* 518 */ - 0x0001, 0x4122, 0x0024, 0x2800, 0x9f80, 0x3800, 0x4024, 0x0006, /* 520 */ - 0x12d2, 0x0006, 0x0991, 0x3000, 0x0024, 0x0006, 0x1290, 0x3a00, /* 528 */ - 0x0024, 0x3800, 0x0024, 0xf400, 0x4010, 0x2900, 0xb200, 0x0000, /* 530 */ - 0x0580, 0x0030, 0x0210, 0x0014, 0x9240, 0x003f, 0xf502, 0x003f, /* 538 */ - 0xffc3, 0x3800, 0x0024, 0x0000, 0x0580, 0x0006, 0x1350, 0x3200, /* 540 */ - 0x4024, 0x4102, 0x0024, 0x3a00, 0x4024, 0x3810, 0x8024, 0x38f0, /* 548 */ - 0xc024, 0x0006, 0x08d0, 0x3800, 0x0024, 0x0030, 0x0690, 0x0000, /* 550 */ - 0x8280, 0xb880, 0x2080, 0x3800, 0x0024, 0x6890, 0x2000, 0x0030, /* 558 */ - 0x0490, 0x3800, 0x0024, 0x0030, 0x0010, 0x0000, 0x0100, 0x3000, /* 560 */ - 0x584c, 0xb100, 0x0024, 0x0000, 0x0024, 0x2800, 0xb185, 0x0000, /* 568 */ - 0x0024, 0x003f, 0xfec1, 0x3000, 0x1bcc, 0xb010, 0x0024, 0x2908, /* 570 */ - 0x0b80, 0x3800, 0x0024, 0x3613, 0x0024, 0x2910, 0x0180, 0x0000, /* 578 */ - 0xae48, 0x0007, 0x0001, 0x1806, 0x0006, 0x8007, 0x0000, 0x0006, /* 580 */ - 0x002f, 0x0010, 0x17ff, 0x0000, 0x1a00, 0x1dff, 0x0000, 0x1f00, /* 588 */ - 0x3fff, 0x0001, 0x0000, 0x17ff, 0x0001, 0x1c00, 0x3fff, 0x0001, /* 590 */ - 0xe000, 0xfffd, 0xffff, 0x0000, 0x0000, 0x180c, 0x180c, 0x0000, /* 598 */ - 0x0000, 0x0000, 0x4952, 0x4646, 0xffff, 0xffff, 0x4157, 0x4556, /* 5a0 */ - 0x6d66, 0x2074, 0x0010, 0x0000, 0x0001, 0x0001, 0xbb80, 0x0000, /* 5a8 */ - 0x7700, 0x0001, 0x0002, 0x0010, 0x6164, 0x6174, 0xffff, 0xffff, /* 5b0 */ - 0x0006, 0x8006, 0x0000, 0x0006, 0x0005, 0x183c, 0x183c, 0x0000, /* 5b8 */ - 0x0020, 0x0040, 0x0006, 0x8003, 0x0000, 0x0007, 0x0001, 0x5bc0, /* 5c0 */ - 0x0006, 0x0009, 0x801c, 0x7fe4, 0x8039, 0x804e, 0x7fb2, 0x809d, /* 5c8 */ - 0x809c, 0x7f64, 0x8139, 0x0007, 0x0001, 0x82c8, 0x0006, 0x0018, /* 5d0 */ - 0x4080, 0x184c, 0x3e13, 0x780f, 0x2800, 0xb405, 0x4090, 0x380e, /* 5d8 */ - 0x2400, 0xb3c0, 0xf400, 0x4417, 0x3110, 0x0024, 0x3f10, 0x0024, /* 5e0 */ - 0x36f3, 0x8024, 0x36f3, 0x580f, 0x2000, 0x0000, 0x0000, 0x0024, /* 5e8 */ - 0x0007, 0x0001, 0x82d4, 0x0006, 0x002a, 0x3e11, 0xb807, 0x3009, /* 5f0 */ - 0x384a, 0x3e11, 0x3805, 0x3e10, 0xb803, 0x3e00, 0x4442, 0x0001, /* 5f8 */ - 0x800a, 0xbf8e, 0x8443, 0xfe06, 0x0045, 0x3011, 0x0401, 0x545e, /* 600 */ - 0x0385, 0x525e, 0x2040, 0x72ce, 0x1bc1, 0x48ba, 0x9803, 0x4588, /* 608 */ - 0x4885, 0x6fee, 0x1bc2, 0x4ffe, 0x9805, 0xf6fe, 0x1bc4, 0xf7f0, /* 610 */ - 0x2046, 0x3801, 0xdbca, 0x2000, 0x0000, 0x36f1, 0x9807, - }; - const uint16_t patch_size = 1567; - DEBUG("Patching...\n"); - - _spi->select_vs1053_xcs(); - SPI.beginTransaction(*_spi_settings); - - for (int i=0; i> 8); - SPI.transfer(val & 0xFF); - _wait(); - } - } else { /* Copy run, copy n samples */ - while (n--) { - val = patch_data[i++]; - SPI.transfer(val >> 8); - SPI.transfer(val & 0xFF); - _wait(); - } - } - } - - SPI.endTransaction(); - _spi->select_vs1053_xcs(false); - - DEBUG("Patch sent.\n"); -} - -void Player::_write_data(uint8_t* buffer) { - _spi->select_vs1053_xdcs(); - SPI.beginTransaction(*_spi_settings); - for (uint i=0; iselect_vs1053_xdcs(false); -} - -uint16_t Player::_read_wram(uint16_t address) { - DEBUG("Reading WRAM address 0x%04X...\n", address); - _write_control_register(SCI_WRAMADDR, address); - uint16_t r1 = _read_control_register(SCI_WRAM); - _write_control_register(SCI_WRAMADDR, address); - uint16_t r2 = _read_control_register(SCI_WRAM); - if (r1 == r2) return r1; - DEBUG("Reading WRAM resulted in different values: 0x%04X and 0x%04X.\n", r1, r2); - _write_control_register(SCI_WRAMADDR, address); - r1 = _read_control_register(SCI_WRAM); - if (r1 == r2) return r1; - DEBUG("Reading WRAM resulted in different values: 0x%04X and 0x%04X.\n", r1, r2); - _write_control_register(SCI_WRAMADDR, address); - r2 = _read_control_register(SCI_WRAM); - if (r1 == r2) return r1; - DEBUG("Reading WRAM resulted in different values: 0x%04X and 0x%04X.\n", r1, r2); - DEBUG("Returning last value (0x%04X)...\n", r2); - return r2; -} - -int8_t Player::_get_endbyte() { - int8_t endbyte = _read_wram(ADDR_ENDBYTE) & 0xFF; - DEBUG("Endbyte is 0x%02X.\n", endbyte); - return endbyte; -} - - -void Player::set_volume(uint8_t vol, bool save) { - if (save) { - _volume = vol; - } - INFO("Setting volume to %d\n", vol); - vol = 0xFF - vol; - uint16_t value = (vol<<8)|vol; - DEBUG("Setting volume register to 0x%04X\n", value); - _write_control_register(SCI_VOL, value); -} - -void Player::vol_up() { - if (!is_playing()) return; - uint16_t vol = _volume + VOLUME_STEP; - if (vol > VOLUME_MAX) vol=VOLUME_MAX; - set_volume(vol); -} - -void Player::vol_down() { - if (!is_playing()) return; - int16_t vol = _volume - VOLUME_STEP; - if (vol < VOLUME_MIN) vol=VOLUME_MIN; - set_volume(vol); -} - -void Player::_mute() { - INFO("Muting.\n"); - _speaker_off(); - set_volume(1, false); -} - -void Player::_unmute() { - INFO("Unmuting.\n"); - set_volume(_volume, false); - _speaker_on(); -} - -void Player::track_next() { - if (_state != playing) return; - if (!_current_playlist->has_track_next()) { - return; - } - stop(); - _current_playlist->track_next(); - play(); -} - -void Player::track_prev() { - if (_state != playing) return; - if (_current_play_position > 100000) { - stop(); - _current_playlist->track_restart(); - play(); - } else { - if (!_current_playlist->has_track_prev()) { - return; - } - stop(); - _current_playlist->track_prev(); - play(); - } -} - -void Player::set_track(uint8_t id) { - stop(); - _current_playlist->set_track(id); - play(); -} - -bool Player::is_playing() { - return _state == playing; -} - -bool Player::play(Playlist* p) { - _current_playlist = p; - return play(); -} - -bool Player::play() { - if (_state == sleeping || _state == recording) _wakeup(); - if (_state != idle) return false; - if (_current_playlist == NULL) return false; - if (_current_playlist->get_file_count()==0) return false; - _speaker_on(); - _current_playlist->start(); - String file; - if (!_current_playlist->get_current_file(&file)) { - return false; - } - uint32_t position = _current_playlist->get_position(); - _state = playing; - _play_file(file, position); - _controller->send_player_status(); - return true; -} - -void Player::_play_file(String file, uint32_t file_offset) { - INFO("play_file('%s', %d)\n", file.c_str(), file_offset); - _spi->select_sd(); - if (file.startsWith("/")) { - _file = new SDDataSource(file); - } else if (file.startsWith("http")) { - _file = new HTTPSDataSource(file); - } else { - return; - } - _file_size = _file->size(); - _spi->select_sd(false); - if (!_file || !_file->usable()) { - DEBUG("Could not open file %s", file.c_str()); - return; - } - - DEBUG("Resetting SCI_DECODE_TIME...\n"); - _write_control_register(SCI_DECODE_TIME, 0); - DEBUG("Resetting SS_DO_NOT_JUMP...\n"); - _write_control_register(SCI_STATUS, _read_control_register(SCI_STATUS) & ~SS_DO_NOT_JUMP); - delay(100); - - _spi->select_sd(); - if (file_offset == 0) { - _file->skip_id3_tag(); - } - _refills = 0; - _current_play_position = _file->position(); - _spi->select_sd(false); - _skip_to = file_offset; - if (_skip_to>0) _mute(); - else _speaker_on(); - INFO("Now playing.\n"); - _controller->send_player_status(); -} - -void Player::_flush(uint count, int8_t byte) { - _spi->select_vs1053_xdcs(); - SPI.beginTransaction(*_spi_settings); - for(uint i=0; iselect_vs1053_xdcs(false); -} - -void Player::_finish_playing() { - uint8_t endbyte = _get_endbyte(); - _flush(2052, endbyte); - _write_control_register(SCI_MODE, _read_control_register(SCI_MODE) | SM_CANCEL); - for (int i=0; i<64; i++) { - _flush(32, endbyte); - uint16_t mode = _read_control_register(SCI_MODE); - if ((mode & SM_CANCEL) == 0) return; - } - // If we reached this, the Chip didn't stop. That should not happen. - // (That's written in the manual.) - // Reset the chip. - init(); -} - -void Player::stop(bool turn_speaker_off) { - if (_state != playing) return; - INFO("Stopping...\n"); - _current_playlist->set_position(_current_play_position); - _controller->pm->persist(_current_playlist); - - _state = stopping; - _stop_delay = 0; - _write_control_register(SCI_MODE, _read_control_register(SCI_MODE) | SM_CANCEL); - uint8_t endbyte = _get_endbyte(); - while (true) { - _refill(); - uint16_t mode = _read_control_register(SCI_MODE); - if ((mode & SM_CANCEL) == 0) { - _flush(2052, endbyte); - _finish_stopping(turn_speaker_off); - break; - } else if (_stop_delay > 2048) { - init(); - break; - } - _stop_delay++; - } -} - -void Player::_finish_stopping(bool turn_speaker_off) { - if (turn_speaker_off) _speaker_off(); - _state = idle; - _stopped_at = millis(); - if (_file) { - _file->close(); - delete _file; - } - _current_play_position = 0; - _file_size = 0; - INFO("Stopped.\n"); - _controller->send_player_status(); -} - -void Player::_refill() { - _spi->select_sd(); - _refills++; - if (_refills % 1000 == 0) DEBUG("."); - uint8_t result = _file->read(_buffer, sizeof(_buffer)); - _spi->select_sd(false); - if (result == 0) { - // File is over. - DEBUG("EOF reached.\n"); - _skip_to = 0; - _finish_playing(); - _finish_stopping(false); - if (_current_playlist->has_track_next()) { - _current_playlist->track_next(); - play(); - } else { - _current_playlist->reset(); - _controller->send_player_status(); - } - - return; - } - _current_play_position+=result; - _write_data(_buffer); - - if (_skip_to > 0) { - if (_skip_to > _file->position()) { - uint16_t status = _read_control_register(SCI_STATUS); - if ((status & SS_DO_NOT_JUMP) == 0) { - DEBUG("Skipping to %d.\n", _skip_to); - _flush(2048, _get_endbyte()); - _spi->select_sd(); - _file->seek(_skip_to); - _spi->select_sd(false); - _skip_to = 0; - _unmute(); - _controller->send_position(); - } - } else { - _skip_to = 0; - _unmute(); - } - } -} - -bool Player::_refill_needed() { - return _state==playing || _state==stopping; -} - -bool Player::loop() { - if (PIN_VS1053_DREQ() && _refill_needed()) { - _refill(); - return true; - } - - if (_state == recording) { - DEBUG("r"); - uint16_t samples_available = _read_control_register(SCI_HDAT1, false); - uint16_t vu_value = _read_control_register(SCI_AICTRL0, false); - DEBUG("Samples available: %4d, VU meter: 0x%04X\n", samples_available, vu_value); - if (samples_available >= 500) { - unsigned long sum = 0; - for (int i=0; i<500; i++) { - uint16_t sample = _read_control_register(SCI_HDAT0, false); - sum += sample * sample; - } - double result = sqrt(sum / 500); - DEBUG("Loudness: %f", result); - } - } - - if (_state == idle && _stopped_at < millis() - VS1053_SLEEP_DELAY) { - _sleep(); - //_record(); - } - return false; -} - -String Player::json() { - DynamicJsonDocument json(10240); - json["_type"] = "player"; - json["playing"] = is_playing(); - if (_current_playlist) { - JsonObject playlist = json.createNestedObject("playlist"); - _current_playlist->json(playlist); - } else { - json["playlist"] = nullptr; - } - JsonObject volume = json.createNestedObject("volume"); - volume["current"] = _volume; - volume["min"] = VOLUME_MIN; - volume["max"] = VOLUME_MAX; - volume["step"] = VOLUME_STEP; - return json.as(); -} - -String Player::position_json() { - if (!is_playing()) return "null"; - DynamicJsonDocument json(200); - json["_type"] = "position"; - json["position"] = _current_play_position; - json["file_size"] = _file_size; - return json.as(); -} diff --git a/src/playlist.cpp b/src/playlist.cpp index 0428cce..d59c75e 100644 --- a/src/playlist.cpp +++ b/src/playlist.cpp @@ -1,399 +1,9 @@ -#include -#include "spi_master.h" -#include "config.h" -#include -#include -#include -#include -#include "main.h" +#include "playlist.h" -Playlist::Playlist(String path) { - _path = path; - if (path.startsWith("/")) { - persistence = PERSIST_TEMPORARY; - _add_path(path); - } else if (path.startsWith("http")) { - _examine_http_url(path); - } - if (_title.length()==0) _title=path; +void Playlist::add_file(String filename) { + files.push_back(filename); } -void Playlist::_add_path(String path) { - SPIMaster::select_sd(); - TRACE("Examining folder %s...\n", path.c_str()); - if (!path.startsWith("/")) path = String("/") + path; - if (!SD.exists(path)) { - DEBUG("Could not open path '%s'.\n", path.c_str()); - SPIMaster::select_sd(false); - return; - } - _title = path.substring(1); - int idx = _title.indexOf('/'); - if (idx>0) { - _title.remove(idx); - } - File dir = SD.open(path); - File entry; - while (entry = dir.openNextFile()) { - String filename = entry.name(); - filename = filename.substring(path.length() + 1); - String ext = filename.substring(filename.length() - 4); - if (!entry.isDirectory() && - !filename.startsWith(".") && - ( ext.equals(".mp3") || - ext.equals(".ogg") || - ext.equals(".wma") || - ext.equals(".mp4") || - ext.equals(".mpa"))) { - TRACE(" Adding entry %s\n", entry.name()); - String title = filename.substring(0, filename.length() - 4); - _files.push_back({.filename=entry.name(), .title=title, .id=String(_files.size())}); - bool non_ascii_chars = false; - for(int i=0; i= 0x7F) { - non_ascii_chars = true; - break; - } - } - if (non_ascii_chars) { - ERROR("WARNING: File '%s' contains non-ascii chars!\n", filename.c_str()); - } - } else { - TRACE(" Ignoring entry %s\n", filename.c_str()); - } - entry.close(); - } - dir.close(); - SPIMaster::select_sd(false); - std::sort(_files.begin(), _files.end()); -} - -void Playlist::_examine_http_url(String url) { - HTTPClientWrapper* http = new HTTPClientWrapper(); - if (!http->get(url)) { - DEBUG("Could not GET %s.\n", url.c_str()); - return; - } - String ct = http->getContentType(); - DEBUG("Content-Type is %s.\n", ct.c_str()); - if (ct.startsWith("audio/x-mpegurl")) { - _parse_m3u(http); - } else if (ct.startsWith("audio/")) { - persistence = PERSIST_NONE; - _files.push_back({.filename=url, .title=url, .id="none"}); - } else if (ct.startsWith("application/rss+xml")) { - persistence = PERSIST_PERMANENTLY; - _parse_rss(http); - } else if (ct.startsWith("application/pls+xml")) { - persistence = PERSIST_PERMANENTLY; - _parse_pls(http); - } else { - ERROR("Unknown content type %s.\n", ct.c_str()); - } - http->close(); - delete http; -} - -std::vector* xml_files_ptr = NULL; -String xml_last_tag = ""; -String xml_title = ""; -String xml_album_title = ""; -String xml_url = ""; -String xml_enclosure_url = ""; -String xml_guid = ""; -bool xml_enclosure_is_audio = false; - -void xmlcb(uint8_t status, char* tagName, uint16_t tagLen, char* data, uint16_t dataLen) { - String tag(tagName); - if (status & STATUS_START_TAG) xml_last_tag = tag; - - if (trace_enabled) { - if (status & STATUS_START_TAG) { - TRACE("Start of tag: %s\n", tagName); - } else if (status & STATUS_END_TAG) { - TRACE("End of tag: %s\n", tagName); - } - } - - if (tag.equals("/rss/channel/title") && (status & STATUS_TAG_TEXT)) { - xml_album_title = data; - } else if (tag.endsWith("/title") && (status & STATUS_TAG_TEXT)) { - xml_title = String(data); - } else if (tag.endsWith("/guid") && (status & STATUS_TAG_TEXT)) { - xml_guid = data; - //} else if (xml_last_tag.endsWith("/item/enclosure") && (status & STATUS_ATTR_TEXT)) { - // DEBUG("tag: %s, data: %s\n", tag.c_str(), data); - } else if (xml_last_tag.endsWith("/enclosure") && tag.equals("type") && (status & STATUS_ATTR_TEXT) && String(data).indexOf("audio/")>=0) { - DEBUG("enclosure is audio\n"); - xml_enclosure_is_audio = true; - } else if (xml_last_tag.endsWith("/enclosure") && tag.equals("url") && (status & STATUS_ATTR_TEXT)) { - DEBUG("found url\n"); - xml_enclosure_url = String(data); - } else if (tag.endsWith("/enclosure") && (status & STATUS_END_TAG)) { - DEBUG("end of enclosure. xml_enclosure_is_audio: %d, xml_enclosure_url: %s\n", xml_enclosure_is_audio, xml_enclosure_url.c_str()); - if (xml_enclosure_is_audio && xml_enclosure_url.length()>0) { - xml_url = xml_enclosure_url; - } - xml_enclosure_is_audio = false; - xml_enclosure_url = ""; - } else if (tag.endsWith("/item") && (status & STATUS_END_TAG || status & STATUS_START_TAG)) { - if (xml_title.length()>0 && xml_url.length()>0) { - if (xml_files_ptr->size() > 20) return; - DEBUG("Adding playlist entry: '%s' => '%s'\n", xml_title.c_str(), xml_url.c_str()); - xml_files_ptr->insert(xml_files_ptr->begin(), {.filename=xml_url, .title=xml_title, .id=xml_guid}); - } - xml_title = ""; - xml_url = ""; - xml_guid = ""; - } -} - -void Playlist::_parse_rss(HTTPClientWrapper* http) { - DEBUG("RSS parser running.\n"); - // http is already initialized - int i; - - TinyXML xml; - uint8_t* buffer = new uint8_t[150]; - xml.init(buffer, 150, &xmlcb); - xml_files_ptr = &_files; - xml_title = ""; - xml_album_title = ""; - xml_url = ""; - xml_enclosure_is_audio = false; - xml_enclosure_url = ""; - while ((i = http->read()) >= 0) { - xml.processChar(i); - } - _current_track = _files.size()-1; - xml_files_ptr = NULL; - if (xml_album_title.length()>0) { - _title = xml_album_title; - } - xml_album_title = ""; - delete buffer; - // don't close http at the end - DEBUG("RSS parser finished.\n"); -} - -void Playlist::_parse_m3u(HTTPClientWrapper* http) { - // http is already initialized - String line = ""; - String title = ""; - int i; - do { - i = http->read(); - char c = i; - if (i>=0 && c!='\r' && c!='\n') { - line += c; - } else { - if (line.equals("#EXTM3U")) { - // Do nothing - } else if (line.startsWith("#EXTINF")) { - int idx = line.indexOf(","); - if (idx>4) { - // Get the title - title = line.substring(idx+1); - if (_title.length()==0) _title=title; - } - } else if (line.startsWith("http")) { - if (title.length()==0) title = line; - _files.push_back({.filename=line, .title=title, .id="none"}); - title = ""; - } - line = ""; - } - } while (i>=0); - // don't close http at the end -} - -void Playlist::_parse_pls(HTTPClientWrapper* http) { - // http is already initialized - String line; - String title = ""; - String url = ""; - int last_index = -1; - int index; - - while(true) { - line = http->readLine(); - if (line.startsWith("Title")) { - uint8_t eq_idx = line.indexOf('='); - if (eq_idx==-1) continue; - - index = line.substring(5, eq_idx-4).toInt(); - title = line.substring(eq_idx+1); - if (index != last_index) { - url = ""; - last_index = index; - } - } else if (line.startsWith("File")) { - uint8_t eq_idx = line.indexOf('='); - if (eq_idx==-1) continue; - - index = line.substring(5, eq_idx-4).toInt(); - url = line.substring(eq_idx+1); - if (index != last_index) { - title = ""; - last_index = index; - } - } - - if (title.length()>0 && url.length()>0) { - _files.push_back({.filename=url, .title=title, .id="none"}); - last_index = -1; - title = ""; - url = ""; - } - } - // don't close http at the end -} - -String Playlist::path() { - return _path; -} - -uint16_t Playlist::get_file_count() { - return _files.size(); -} - -void Playlist::start() { - _started = true; -} - -bool Playlist::has_track_prev() { - return _current_track > 0; -} - -bool Playlist::has_track_next() { - return _current_track < _files.size()-1; -} - -bool Playlist::track_prev() { - if (_current_track > 0) { - _current_track--; - _position = 0; - return true; - } - return false; -} - -bool Playlist::track_next() { - if (_current_track < _files.size()-1) { - _current_track++; - _position = 0; - return true; - } - return false; -} - -bool Playlist::set_track(uint8_t track) { - if (track < _files.size()) { - _current_track = track; - _position = 0; - return true; - } - return false; -} - -void Playlist::set_track_by_id(String id) { - for (int i=0; i<_files.size(); i++) { - if (id.equals(_files[i].id)) { - set_track(i); - return; - } - } -} - -void Playlist::track_restart() { - _position = 0; -} - -void Playlist::shuffle(uint8_t random_offset) { - DEBUG("Shuffling the playlist with an offset of %d...\n", random_offset); - for (int i=random_offset; i<_files.size(); i++) { - int j = random(random_offset, _files.size()-1); - if (i!=j) { - TRACE(" Swapping elements %d and %d.\n", i, j); - PlaylistEntry temp = _files[i]; - _files[i] = _files[j]; - _files[j] = temp; - } - } - _shuffled = true; - TRACE("Done.\n"); -} - -void Playlist::advent_shuffle(uint8_t day) { - TRACE("advent_shuffle running...\n"); - - // Not enough songs till the current day? Play all songs in the default order. - if (day > _files.size()) { - return; - } - - // We are in the "different playlist every day" mode. So we don't persist it in order to not miss changes. - persistence = PERSIST_NONE; - - - _files.insert(_files.begin(), _files[day - 1]); - _files.erase(_files.begin() + day, _files.end()); -} - -void Playlist::reset() { - std::sort(_files.begin(), _files.end()); - _current_track = 0; - _position = 0; - _shuffled = false; - _started = false; -} - -String Playlist::get_current_track_id() { - if (_current_track > _files.size()) { - return ""; - } - return _files[_current_track].id; -} - -bool Playlist::get_current_file(String* dst) { - if (_current_track > _files.size()) { - return false; - } else { - dst->concat(_files[_current_track].filename); - return true; - } -} - -uint32_t Playlist::get_position() { - return _position; -} - -void Playlist::set_position(uint32_t p) { - _position = p; -} - -bool Playlist::is_fresh() { - return !_shuffled && !_started && _position==0 && _current_track==0; -} - -void Playlist::dump() { - for (int i=0; i<_files.size(); i++) { - DEBUG(" %02d %2s %s\n", i+1, (i==_current_track) ? "->" : "", _files[i].filename.c_str()); - } -} - -void Playlist::json(JsonObject json) { - json["_type"] = "playlist"; - json["title"] = _title; - JsonArray files = json.createNestedArray("files"); - for (PlaylistEntry entry: _files) { - JsonObject o = files.createNestedObject(); - o["filename"] = entry.filename; - o["title"] = entry.title; - o["id"] = entry.id; - } - json["current_track"] = _current_track; - json["has_track_next"] = has_track_next(); - json["has_track_prev"] = has_track_prev(); -} +void Playlist::sort() { + std::sort(files.begin(), files.end()); +} \ No newline at end of file diff --git a/src/playlist_manager.cpp b/src/playlist_manager.cpp index 45e0b34..dc9f3da 100644 --- a/src/playlist_manager.cpp +++ b/src/playlist_manager.cpp @@ -1,33 +1,29 @@ #include "playlist_manager.h" -#include #include "spi_master.h" -#include +#include PlaylistManager::PlaylistManager() { - scan_files(); -} + SPIMaster::enable_sd(); + current_rfid_tag_id = String(""); -void PlaylistManager::scan_files() { - SPIMaster::select_sd(); - std::vector folders; File root = SD.open("/"); - File entry; - while (entry = root.openNextFile()) { - Serial.println(entry.name()); - String foldername = entry.name(); - if (foldername.startsWith("/.")) continue; - foldername.remove(foldername.length()); - folders.push_back(foldername); - _check_for_special_chars(foldername); - entry.close(); + while(File entry = root.openNextFile()) { + if (entry.isDirectory()) { + String filename("/"); + filename.concat(entry.name()); + if (!filename.startsWith("/.")) { + Serial.println(filename); + dirs.push_back(filename); + } + } } - _map.clear(); - if (!SD.exists("/_mapping.txt\n")) { - ERROR("WARNING: File /_mapping.txt not found.\n"); + if (!SD.exists("/_mapping.txt")) { + Serial.println("WARNING: /_mapping.txt not found!"); } else { + map.clear(); File f = SD.open("/_mapping.txt"); - DEBUG("Reading /_mapping.txt...\n"); + Serial.println(" Reading /_mapping.txt..."); while (f.available()) { char buffer[512]; size_t pos = f.readBytesUntil('\n', buffer, 511); @@ -38,19 +34,19 @@ void PlaylistManager::scan_files() { if (eq>0 && eq%s\n", rfid_id.c_str(), folder.c_str()); - _map[rfid_id] = folder; + Serial.printf(" Adding mapping: %s=>%s\n", rfid_id.c_str(), folder.c_str()); + map[rfid_id] = folder; if (folder.charAt(0)=='/') { bool found=false; - for (String f: folders) { + for (String f: dirs) { if (f.equals(folder)) { found = true; break; } } if (!found) { - INFO("WARNING: Found mapping for RFID id %s which maps to non-existing folder %s!\n", rfid_id.c_str(), folder.c_str()); + Serial.printf("WARNING: Found mapping for RFID id %s which maps to non-existing folder %s!\n", rfid_id.c_str(), folder.c_str()); } } } @@ -58,183 +54,40 @@ void PlaylistManager::scan_files() { f.close(); } root.close(); - - _unmapped_folders.clear(); - for (String folder: folders) { - bool found = false; - for(std::map::iterator it = _map.begin(); it != _map.end(); it++) { - if (it->second.equals(folder)) { - found = true; - break; - } - } - if (!found) { - // Checking folder for media files - File dir = SD.open(folder); - while (entry = dir.openNextFile()) { - String filename = entry.name(); - filename = filename.substring(folder.length() + 1); - if (!filename.startsWith(".") && filename.endsWith(".mp3")) { - found = true; - } - entry.close(); - if (found) break; - } - if (found) { - _unmapped_folders.push_back(folder); - } - } - } - SPIMaster::select_sd(false); } -void PlaylistManager::_check_for_special_chars(String s) { - for(int i=0; i= 0x7F) { - ERROR("WARNING: Folder / file '%s' contains non-ascii chars!\n", s.c_str()); - return; - } - } -} - -Playlist* PlaylistManager::get_playlist_for_id(String id) { - if (!_map.count(id)) return NULL; - String folder = _map[id]; - return get_playlist_for_folder(folder); -} - -Playlist* PlaylistManager::get_playlist_for_folder(String folder) { - Playlist* p; - if (!_playlists.count(folder)) { - p = new Playlist(folder); - _playlists[folder] = p; - if (p->persistence == PERSIST_PERMANENTLY) { - String search = folder; - search += "="; - SPIMaster::select_sd(); - if (SD.exists("/_positions.txt")) { - File f = SD.open("/_positions.txt", "r"); - while (true) { - String s = f.readStringUntil('\n'); - if (s.length()==0) break; - if (s.startsWith(search)) { - s = s.substring(search.length()); - int idx = s.indexOf(','); - String title_index = s.substring(0, idx); - uint32_t position = s.substring(idx+1).toInt(); - p->set_track_by_id(title_index); - p->set_position(position); - break; - } - } - f.close(); - } - SPIMaster::select_sd(false); - } +Playlist PlaylistManager::get_playlist(String rfid_id) { + if (rfid_id.equals(current_rfid_tag_id)) { + return current_playlist; } else { - p = _playlists[folder]; - if (p->persistence == PERSIST_NONE) { - p->reset(); - } - } - return p; -} - -void PlaylistManager::dump_ids() { - for (std::map::iterator it = _map.begin(); it!=_map.end(); it++) { - INFO(" %s -> %s\n", it->first.c_str(), it->second.c_str()); - } -} - -String PlaylistManager::json() { - DynamicJsonDocument json(10240); - json["_type"] = "playlist_manager"; - JsonObject folders = json.createNestedObject("folders"); - for (std::map::iterator it = _map.begin(); it!=_map.end(); it++) { - folders[it->second] = it->first; - } - JsonArray started = json.createNestedArray("started"); - for (std::map::iterator it = _playlists.begin(); it!=_playlists.end(); it++) { - if (it->second->is_fresh()) continue; - started.add(it->first); - } - JsonArray unmapped = json.createNestedArray("unmapped"); - for(String folder : _unmapped_folders) { - unmapped.add(folder); - } - return json.as(); -} - -bool PlaylistManager::add_mapping(String id, String folder) { - DEBUG("Adding mapping: %s=>%s\n", id.c_str(), folder.c_str()); - _map[id] = folder; - _save_mapping(); - return true; -} - -void PlaylistManager::_save_mapping() { - SPIMaster::select_sd(); - File f = SD.open("/_mapping.txt", "w"); - String s = create_mapping_txt(); - unsigned char* buf = new unsigned char[s.length()]; - s.getBytes(buf, s.length()); - f.write(buf, s.length()-1); - f.close(); - SPIMaster::select_sd(false); - delete buf; -} - -String PlaylistManager::create_mapping_txt() { - String s; - for(std::map::iterator it = _map.begin(); it != _map.end(); it++) { - s += it->first; - s += "="; - s += it->second; - s += '\n'; - } - return s; -} - -void PlaylistManager::persist(Playlist* p) { - if (p->persistence == PERSIST_NONE) { - _playlists.erase(p->path()); - return; - } else if (p->persistence == PERSIST_PERMANENTLY) { - - String search = p->path(); - search += '='; - - bool old_file_existed = false; - - SPIMaster::select_sd(); - if (SD.exists("_positions.txt")) { - SD.rename("/_positions.txt", "/_positions.temp.txt"); - old_file_existed = true; - } - File dst = SD.open("/_positions.txt", "w"); - - if (old_file_existed) { - File src = SD.open("/_positions.temp.txt", "r"); - - while (true) { - String line = src.readStringUntil('\n'); - line.trim(); - if (line.startsWith(search)) continue; - dst.println(line); + if (map.count(rfid_id)==0) { + Serial.printf("No known playlist for id %s.\n", rfid_id); + return current_playlist; + } else { + current_playlist = Playlist(); + String entry = map[rfid_id]; + if (entry.startsWith("/")) { + File dir = SD.open(entry); + while(File entry = dir.openNextFile()) { + String filename = entry.name(); + String ext = filename.substring(filename.length()-4); + if (!entry.isDirectory() && + !filename.startsWith(".") && + ext.equals(".mp3")) { + Serial.printf("Adding %s to the list of files\n", filename); + current_playlist.add_file(filename); + } + entry.close(); + } + dir.close(); } - - src.close(); - SD.remove("/_positions.temp.txt"); + current_playlist.sort(); + current_rfid_tag_id = rfid_id; + return current_playlist; } - - dst.print(search); - dst.print(p->get_current_track_id()); - dst.print(','); - dst.println(p->get_position()); - dst.close(); - SPIMaster::select_sd(false); - - _playlists.erase(p->path()); } } + +void PlaylistManager::set_audio_current_time(uint32_t time) { + audio_current_time = time; +} \ No newline at end of file diff --git a/src/spi_master.cpp b/src/spi_master.cpp new file mode 100644 index 0000000..09661ad --- /dev/null +++ b/src/spi_master.cpp @@ -0,0 +1,26 @@ +#include "spi_master.h" +#include +#include "esmp3.h" + +void SPIMaster::initialize() { + pinMode(PIN_CS_SD, OUTPUT); + pinMode(PIN_CS_RFID, OUTPUT); + disable_all(); +} + +void SPIMaster::disable_all() { + digitalWrite(PIN_CS_SD, HIGH); + digitalWrite(PIN_CS_RFID, HIGH); +} + +void SPIMaster::enable_rfid() { + disable_all(); + digitalWrite(PIN_CS_RFID, LOW); + delay(5); +} + +void SPIMaster::enable_sd() { + disable_all(); + digitalWrite(PIN_CS_SD, LOW); + delay(5); +} \ No newline at end of file diff --git a/src/updater.cpp b/src/updater.cpp deleted file mode 100644 index 491e373..0000000 --- a/src/updater.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include -#include -#include "config.h" -#include "updater.h" -#include "http_client_wrapper.h" - -void Updater::run() { - DEBUG("Updater is running...\n"); - HTTPClientWrapper* http = new HTTPClientWrapper(); - DEBUG("Requesting update info...\n"); - bool result = http->get(OTA_UPDATE_URL); - if (!result) { - ERROR("Updater failed requesting %s.\n", OTA_UPDATE_URL); - return; - } - - String line_str = ""; - if (!read_line(&line_str, http, "VERSION")) { - return; - } - uint16_t version = line_str.toInt(); - if (version==0) { - ERROR("Could not parse version number.\n"); - return; - } - DEBUG("Found version %d. My version is %d.\n", version, OTA_VERSION); - if (version <= OTA_VERSION) { - return; - } - - String image_path = ""; - if (!read_line(&image_path, http, "IMAGE_PATH")) { - return; - } - - String image_md5 = ""; - if (!read_line(&image_md5, http, "IMAGE_MD5")) { - return; - } - - http->close(); - delete http; - - if(do_update(U_FLASH, image_path, image_md5)) { - DEBUG("Update done. Rebooting...\n"); - } else { - DEBUG("Update failed. Rebooting...\n"); - } - delay(1000); - ESP.restart(); -} - -bool Updater::read_line(String* dst, HTTPClientWrapper* http, String expected_key) { - expected_key += "="; - String line = http->readUntil("\n"); - if (!line.startsWith(expected_key)) { - ERROR("Expected line start with '%s', but it started with '%s'.\n", expected_key.c_str(), line.c_str()); - return false; - } - line = line.substring(expected_key.length()); - line.trim(); - dst->concat(line); - return true; -} - -bool Updater::do_update(int command, String url, String expected_md5) { - HTTPClientWrapper* http = new HTTPClientWrapper(); - bool result = http->get(url); - if (!result) { - ERROR("Updater failed requesting %s.\n", url.c_str()); - return false; - } - - result = Update.begin(http->getSize(), command); - if (!result) { - ERROR("Update could not be started.\n"); - return false; - } - Update.setMD5(expected_md5.c_str()); - uint8_t buf[512]; - uint16_t len; - while((len = http->read(buf, 512))) { - Update.write(buf, len); - } - - http->close(); - delete http; - - result = Update.end(); - if (!result) { - const char* error = Update.errorString(); - ERROR("Writing the update failed. The error was: %s\n", error); - return false; - } - return true; -} From 9c31f70c57f6f7bc3a351b94b07bf0aff981657b Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Sun, 21 Aug 2022 11:37:42 +0200 Subject: [PATCH 02/20] It's working more or less... --- include/controller.h | 14 +++++- include/esmp3.h | 27 ++++++++--- include/playlist.h | 12 ++++- include/playlist_manager.h | 1 + platformio.ini | 9 ++-- src/controller.cpp | 93 ++++++++++++++++++++++++++++++++++++-- src/esmp3.cpp | 92 +++++++++++++++++++++++++++++++------ src/playlist.cpp | 52 +++++++++++++++++++++ src/playlist_manager.cpp | 16 ++++--- 9 files changed, 281 insertions(+), 35 deletions(-) diff --git a/include/controller.h b/include/controller.h index 7b56efe..b615ad0 100644 --- a/include/controller.h +++ b/include/controller.h @@ -1,13 +1,25 @@ #pragma once #include +#include +#include +#include "esmp3.h" +#include "playlist.h" class Controller { private: void handle_buttons(); void handle_rfid(); + bool is_button_pressed(uint8_t pin); + Playlist current_playlist; + bool is_rfid_present = false; + unsigned long last_rfid_check = 0; public: void handle(); - + void next_track(); + void prev_track(); + void play(); + void play(String rfid_id); + void stop(); }; \ No newline at end of file diff --git a/include/esmp3.h b/include/esmp3.h index d17af5d..ad39985 100644 --- a/include/esmp3.h +++ b/include/esmp3.h @@ -1,11 +1,26 @@ #pragma once -#include "controller.h" -#define PIN_CS_SD 16 -#define PIN_CS_RFID 17 +#include "controller.h" +#include "playlist_manager.h" +#include + +#define PIN_CS_SD 22 +#define PIN_CS_RFID 21 + +#define PIN_BTN_VOL_UP 32 +#define PIN_BTN_VOL_DOWN 33 +#define PIN_BTN_TRACK_NEXT 34 +#define PIN_BTN_TRACK_PREV 35 #define I2S_DOUT 25 -#define I2S_BCLK 27 -#define I2S_LRC 26 +#define I2S_BCLK 26 +#define I2S_LRC 27 -extern Controller controller; \ No newline at end of file +class Controller; + +extern Controller controller; +extern Audio audio; +extern PlaylistManager* pm; +extern MFRC522* rfid; + +void save_audio_current_time(); \ No newline at end of file diff --git a/include/playlist.h b/include/playlist.h index 73691e3..4bd5731 100644 --- a/include/playlist.h +++ b/include/playlist.h @@ -7,9 +7,19 @@ class Playlist { private: std::vector files; uint8_t current_file = 0; - uint16_t current_time = 0; + uint32_t current_time = 0; + void set_current_position(uint8_t file, uint32_t position=0); + String rfid_id; public: + Playlist(String rfid_id=""); void add_file(String filename); void sort(); + String get_rfid_id(); + String get_current_file_name(); + bool next_track(); + bool prev_track(); + void restart(); + void set_current_time(uint32_t time); + uint32_t get_current_time(); }; diff --git a/include/playlist_manager.h b/include/playlist_manager.h index 551c40a..f8f5b31 100644 --- a/include/playlist_manager.h +++ b/include/playlist_manager.h @@ -16,6 +16,7 @@ class PlaylistManager { std::vector dirs; std::map map; Playlist get_playlist(String rfid_id); + bool has_playlist(String rfid_id); Playlist current_playlist; void set_audio_current_time(uint32_t time); }; \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 64fddb3..7b81aee 100644 --- a/platformio.ini +++ b/platformio.ini @@ -18,14 +18,16 @@ lib_deps = platform = espressif32 board = esp-wrover-kit framework = arduino -upload_speed = 115200 -build_flags = !./build_version.sh +upload_speed = 921600 +build_flags = -DCORE_DEBUG_LEVEL=5 -DCONFIG_ARDUHAL_LOG_COLORS=1 ; !./build_version.sh lib_deps = ${extra.lib_deps} esphome/ESP32-audioI2S@^2.1.0 -upload_port = 10.10.2.108 + computer991/Arduino_MFRC522v2@^2.0.1 +;upload_port = 10.10.2.108 monitor_speed = 115200 monitor_port = /dev/cu.usbserial-0001 +monitor_filters = time, esp32_exception_decoder [env:deploy] platform = espressif32 @@ -34,5 +36,6 @@ framework = arduino lib_deps = ${extra.lib_deps} esphome/ESP32-audioI2S@^2.1.0 + computer991/Arduino_MFRC522v2@^2.0.1 board_build.embed_txtfiles = src/index.html board_build.partitions = partitions.csv diff --git a/src/controller.cpp b/src/controller.cpp index f2e476d..9618c3e 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -1,13 +1,100 @@ #include "controller.h" +#include "esmp3.h" void Controller::handle() { - + if (last_rfid_check + 500 < millis() || last_rfid_check > millis()) { + handle_rfid(); + last_rfid_check = millis(); + } } void Controller::handle_buttons() { - + if (is_button_pressed(PIN_BTN_VOL_UP)) { + audio.setVolume(min(audio.getVolume()+1, 21)); + } else if (is_button_pressed(PIN_BTN_VOL_DOWN)) { + audio.setVolume(max(audio.getVolume()-1, 1)); + } else if (is_button_pressed(PIN_BTN_TRACK_NEXT)) { + next_track(); + } else if (is_button_pressed(PIN_BTN_TRACK_PREV)) { + prev_track(); + } } void Controller::handle_rfid() { - + if (is_rfid_present) { + byte buffer[2]; + byte buffer_size = 2; + MFRC522Constants::StatusCode status = rfid->PICC_WakeupA(buffer, &buffer_size); + if (status == MFRC522Constants::STATUS_OK) { + // Card is still present. + rfid->PICC_HaltA(); + } else { + Serial.printf("RFID status is %s\n", MFRC522Debug::GetStatusCodeName(status)); + is_rfid_present = false; + Serial.println("No more RFID card.\n"); + stop(); + } + } else { + if (rfid->PICC_IsNewCardPresent()) { + if (rfid->PICC_ReadCardSerial()) { + uint32_t uid = rfid->uid.uidByte[0]<<24 | rfid->uid.uidByte[1]<<16 | rfid->uid.uidByte[2]<<8 | rfid->uid.uidByte[3]; + Serial.printf("Found new rfid card with uid %x\n", uid); + is_rfid_present = true; + if (uid > 0) { + String temp = String(uid, HEX); + String s_uid = ""; + for (int i=0; i<(8-temp.length()); i++) { + s_uid.concat("0"); + } + s_uid.concat(temp); + play(s_uid); + } + rfid->PICC_HaltA(); + } + } + } +} + +void Controller::play(String rfid_id) { + if (!rfid_id.equals(current_playlist.get_rfid_id())) { + if (pm->has_playlist(rfid_id)) { + current_playlist = pm->get_playlist(rfid_id); + play(); + } else { + Serial.printf("There is no playlist for rfid_id %s\n", rfid_id.c_str()); + } + } else { + if (!audio.isRunning()) { + play(); + } + } +} + +void Controller::play() { + String file = current_playlist.get_current_file_name(); + Serial.printf("Playing file: %s\n", file.c_str()); + audio.connecttoFS(SD, file.c_str(), current_playlist.get_current_time()); +} + +void Controller::next_track() { + if (current_playlist.next_track()) { + play(); + } +} + +void Controller::prev_track() { + if (audio.getAudioCurrentTime() <= 5) { + current_playlist.restart(); + play(); + } else { + if (current_playlist.prev_track()) { + play(); + } + } +} + +void Controller::stop() { + if (audio.isRunning()) { + current_playlist.set_current_time(audio.stopSong()); + } } \ No newline at end of file diff --git a/src/esmp3.cpp b/src/esmp3.cpp index 1e3b830..96dfa3e 100644 --- a/src/esmp3.cpp +++ b/src/esmp3.cpp @@ -7,33 +7,53 @@ #include #include "esmp3.h" #include +#include +#include +#include +#include +#include +#include Controller controller; Audio audio; -Ticker ticker_save_audio_current_time; PlaylistManager* pm; +MFRC522* rfid; void setup() { + pinMode(PIN_CS_SD, OUTPUT); digitalWrite(PIN_CS_SD, HIGH); + pinMode(PIN_CS_RFID, OUTPUT); digitalWrite(PIN_CS_RFID, HIGH); Serial.begin(115200); WiFi.begin("Schlenz", "1410WischlingenPanda"); - Serial.print("Connecting to WiFi..."); + log_i("Connecting to WiFi..."); + uint8_t i=9; while(WiFi.status() != WL_CONNECTED) { - Serial.print("."); - delay(500); + Serial.print(i); + Serial.print("... "); + delay(1000); + i--; + if (i==0) { + Serial.println("Could not connect to WiFi. Restarting in 1s."); + delay(1000); + ESP.restart(); + } } Serial.println(); Serial.print("Connected to WiFi. IP address: "); Serial.println(WiFi.localIP()); - ArduinoOTA.begin(); + Serial.println("Waiting for OTA..."); + for(int i=0; i<20; i++) { + ArduinoOTA.handle(); + delay(100); + } Serial.println("Initializing SPI..."); SPI.begin(); - SPI.setHwCs(false); - SPIMaster::initialize(); + //SPI.setHwCs(false); + //SPIMaster::initialize(); Serial.print("Initializing SD card..."); - SPIMaster::enable_sd(); - while(!SD.begin(14, SPI, 25000000)) { + //SPIMaster::enable_sd(); + while(!SD.begin(PIN_CS_SD, SPI, 25000000)) { for(int i=0; i<10; i++) { if(SPI.transfer(0xFF)==0xFF) break; delay(10); @@ -46,15 +66,29 @@ void setup() { Serial.println("Initializing PlaylistManager..."); pm = new PlaylistManager(); + Serial.println("Setting up rfid reader..."); + pinMode(PIN_CS_RFID, OUTPUT); + MFRC522DriverPin* pin = new MFRC522DriverPinSimple(PIN_CS_RFID); + MFRC522Driver* spi = new MFRC522DriverSPI(*pin); + rfid = new MFRC522(*spi); + rfid->PCD_Init(); + MFRC522Debug::PCD_DumpVersionToSerial(*rfid, Serial); + + Serial.println("Setting up buttons..."); + pinMode(PIN_BTN_VOL_UP, INPUT_PULLUP); + pinMode(PIN_BTN_VOL_DOWN, INPUT_PULLUP); + pinMode(PIN_BTN_TRACK_NEXT, INPUT_PULLUP); + pinMode(PIN_BTN_TRACK_PREV, INPUT_PULLUP); + Serial.println("Setting up audio..."); audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT); audio.setVolume(15); audio.forceMono(true); - Serial.println("Setting up tickers..."); - ticker_save_audio_current_time.attach(5, save_audio_current_time); - Serial.println("Setup finished."); + + audio.setVolume(3); + audio.connecttospeech("Die Papabox ist nun einsatzbereit!", "de_DE"); } void loop() { @@ -63,6 +97,34 @@ void loop() { audio.loop(); } -void save_audio_current_time() { - pm->set_audio_current_time(audio.getAudioCurrentTime()); -} \ No newline at end of file +void audio_info(const char *info){ + Serial.print("info "); Serial.println(info); +} +void audio_id3data(const char *info){ //id3 metadata + Serial.print("id3data ");Serial.println(info); +} +void audio_eof_mp3(const char *info){ //end of file + Serial.print("eof_mp3 ");Serial.println(info); + controller.next_track(); +} +void audio_showstation(const char *info){ + Serial.print("station ");Serial.println(info); +} +void audio_showstreamtitle(const char *info){ + Serial.print("streamtitle ");Serial.println(info); +} +void audio_bitrate(const char *info){ + Serial.print("bitrate ");Serial.println(info); +} +void audio_commercial(const char *info){ //duration in sec + Serial.print("commercial ");Serial.println(info); +} +void audio_icyurl(const char *info){ //homepage + Serial.print("icyurl ");Serial.println(info); +} +void audio_lasthost(const char *info){ //stream URL played + Serial.print("lasthost ");Serial.println(info); +} +void audio_eof_speech(const char *info){ + Serial.print("eof_speech ");Serial.println(info); +} diff --git a/src/playlist.cpp b/src/playlist.cpp index d59c75e..4553afc 100644 --- a/src/playlist.cpp +++ b/src/playlist.cpp @@ -1,9 +1,61 @@ #include "playlist.h" +Playlist::Playlist(String id) { + rfid_id = id; +} + +String Playlist::get_rfid_id() { + return rfid_id; +} + void Playlist::add_file(String filename) { files.push_back(filename); } void Playlist::sort() { std::sort(files.begin(), files.end()); +} + +void Playlist::set_current_position(uint8_t file, uint32_t seconds) { + current_file = file; + current_time = seconds; +} + +String Playlist::get_current_file_name() { + if (current_file >= files.size()) { + Serial.printf("Requested a file number %d, which is not available in this playlist. Starting over.\n", current_file); + set_current_position(0); + } + return files[current_file]; +} + +bool Playlist::next_track() { + if (files.size() <= current_file + 1) { + Serial.println("next_track does not exist. Resetting playlist and returning false."); + set_current_position(0, 0); + return false; + } + set_current_position(current_file + 1, 0); + return true; +} + +bool Playlist::prev_track() { + if (current_file == 0) { + return false; + } + + set_current_position(current_file - 1, 0); + return true; +} + +void Playlist::restart() { + current_time = 0; +} + +void Playlist::set_current_time(uint32_t pos) { + current_time = pos; +} + +uint32_t Playlist::get_current_time() { + return current_time; } \ No newline at end of file diff --git a/src/playlist_manager.cpp b/src/playlist_manager.cpp index dc9f3da..1b4c2ee 100644 --- a/src/playlist_manager.cpp +++ b/src/playlist_manager.cpp @@ -64,18 +64,18 @@ Playlist PlaylistManager::get_playlist(String rfid_id) { Serial.printf("No known playlist for id %s.\n", rfid_id); return current_playlist; } else { - current_playlist = Playlist(); - String entry = map[rfid_id]; - if (entry.startsWith("/")) { - File dir = SD.open(entry); + current_playlist = Playlist(rfid_id); + String path = map[rfid_id]; + if (path.startsWith("/")) { + File dir = SD.open(path); while(File entry = dir.openNextFile()) { String filename = entry.name(); String ext = filename.substring(filename.length()-4); if (!entry.isDirectory() && !filename.startsWith(".") && ext.equals(".mp3")) { - Serial.printf("Adding %s to the list of files\n", filename); - current_playlist.add_file(filename); + Serial.printf("Adding %s to the list of files\n", (path + "/" + filename).c_str()); + current_playlist.add_file(path + "/" + filename); } entry.close(); } @@ -90,4 +90,8 @@ Playlist PlaylistManager::get_playlist(String rfid_id) { void PlaylistManager::set_audio_current_time(uint32_t time) { audio_current_time = time; +} + +bool PlaylistManager::has_playlist(String rfid_id) { + return map.count(rfid_id) == 1; } \ No newline at end of file From 4840c150c207c1cc396220afbd64feffeb3df696 Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Sun, 21 Aug 2022 14:00:52 +0200 Subject: [PATCH 03/20] Button handling implemented. --- include/controller.h | 3 +++ include/esmp3.h | 4 ++-- src/controller.cpp | 40 +++++++++++++++++++++++++++++++++++++--- src/playlist.cpp | 7 ++++--- 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/include/controller.h b/include/controller.h index b615ad0..149c3e8 100644 --- a/include/controller.h +++ b/include/controller.h @@ -14,6 +14,9 @@ class Controller { Playlist current_playlist; bool is_rfid_present = false; unsigned long last_rfid_check = 0; + uint8_t button_pressed = 0; + unsigned long button_pressed_since = 0; + bool button_already_processed = false; public: void handle(); diff --git a/include/esmp3.h b/include/esmp3.h index ad39985..84658fa 100644 --- a/include/esmp3.h +++ b/include/esmp3.h @@ -9,8 +9,8 @@ #define PIN_BTN_VOL_UP 32 #define PIN_BTN_VOL_DOWN 33 -#define PIN_BTN_TRACK_NEXT 34 -#define PIN_BTN_TRACK_PREV 35 +#define PIN_BTN_TRACK_NEXT 17 +#define PIN_BTN_TRACK_PREV 16 #define I2S_DOUT 25 #define I2S_BCLK 26 diff --git a/src/controller.cpp b/src/controller.cpp index 9618c3e..3e1b1b2 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -6,16 +6,25 @@ void Controller::handle() { handle_rfid(); last_rfid_check = millis(); } + handle_buttons(); } void Controller::handle_buttons() { if (is_button_pressed(PIN_BTN_VOL_UP)) { - audio.setVolume(min(audio.getVolume()+1, 21)); + log_i("BTN_VOL_UP pressed"); + uint8_t vol = min(audio.getVolume()+1, 21); + log_d("Setting new volume %d", vol); + audio.setVolume(vol); } else if (is_button_pressed(PIN_BTN_VOL_DOWN)) { - audio.setVolume(max(audio.getVolume()-1, 1)); + log_i("BTN_VOL_DOWN pressed"); + uint8_t vol = max(audio.getVolume()-1, 1); + log_d("Setting new volume %d", vol); + audio.setVolume(vol); } else if (is_button_pressed(PIN_BTN_TRACK_NEXT)) { + log_i("BTN_TRACK_NEXT pressed"); next_track(); } else if (is_button_pressed(PIN_BTN_TRACK_PREV)) { + log_i("BTN_TRACK_PREV pressed"); prev_track(); } } @@ -83,7 +92,10 @@ void Controller::next_track() { } void Controller::prev_track() { - if (audio.getAudioCurrentTime() <= 5) { + uint32_t time = audio.getAudioCurrentTime(); + log_d("prev_track() called. getAudioCurrentTime() returns %d", time); + if (time >= 5) { + log_d("Restarting current track."); current_playlist.restart(); play(); } else { @@ -97,4 +109,26 @@ void Controller::stop() { if (audio.isRunning()) { current_playlist.set_current_time(audio.stopSong()); } +} + +bool Controller::is_button_pressed(uint8_t pin) { + //log_d("Button %d state is %d", pin, digitalRead(pin)); + if (!digitalRead(pin)) { + // Button is pressed - let's debounce it. + if (button_pressed == pin) { + if (button_pressed_since + 150 < millis() && !button_already_processed) { + button_already_processed = true; + return true; + } + } else { + button_pressed = pin; + button_pressed_since = millis(); + button_already_processed = false; + } + } else { + if (button_pressed == pin) { + button_pressed = 0; + } + } + return false; } \ No newline at end of file diff --git a/src/playlist.cpp b/src/playlist.cpp index 4553afc..f5b9403 100644 --- a/src/playlist.cpp +++ b/src/playlist.cpp @@ -40,11 +40,12 @@ bool Playlist::next_track() { } bool Playlist::prev_track() { + log_d("Playlist::prev_track called. current_file is %d", current_file); if (current_file == 0) { - return false; + set_current_position(0, 0); + } else { + set_current_position(current_file - 1, 0); } - - set_current_position(current_file - 1, 0); return true; } From 45dfe0cfe0e6016bf9bb5d8135114b2c94743546 Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Sun, 21 Aug 2022 15:26:29 +0200 Subject: [PATCH 04/20] Don't read all dirs at startup. --- include/playlist_manager.h | 1 - src/esmp3.cpp | 3 ++- src/playlist_manager.cpp | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/include/playlist_manager.h b/include/playlist_manager.h index f8f5b31..ab07ad2 100644 --- a/include/playlist_manager.h +++ b/include/playlist_manager.h @@ -13,7 +13,6 @@ class PlaylistManager { public: PlaylistManager(); - std::vector dirs; std::map map; Playlist get_playlist(String rfid_id); bool has_playlist(String rfid_id); diff --git a/src/esmp3.cpp b/src/esmp3.cpp index 96dfa3e..b69b2b5 100644 --- a/src/esmp3.cpp +++ b/src/esmp3.cpp @@ -22,6 +22,7 @@ MFRC522* rfid; void setup() { pinMode(PIN_CS_SD, OUTPUT); digitalWrite(PIN_CS_SD, HIGH); pinMode(PIN_CS_RFID, OUTPUT); digitalWrite(PIN_CS_RFID, HIGH); + Serial.begin(115200); WiFi.begin("Schlenz", "1410WischlingenPanda"); log_i("Connecting to WiFi..."); @@ -41,7 +42,7 @@ void setup() { Serial.print("Connected to WiFi. IP address: "); Serial.println(WiFi.localIP()); ArduinoOTA.begin(); - Serial.println("Waiting for OTA..."); + log_i("Waiting for OTA..."); for(int i=0; i<20; i++) { ArduinoOTA.handle(); delay(100); diff --git a/src/playlist_manager.cpp b/src/playlist_manager.cpp index 1b4c2ee..9c0f635 100644 --- a/src/playlist_manager.cpp +++ b/src/playlist_manager.cpp @@ -6,6 +6,7 @@ PlaylistManager::PlaylistManager() { SPIMaster::enable_sd(); current_rfid_tag_id = String(""); + /* File root = SD.open("/"); while(File entry = root.openNextFile()) { if (entry.isDirectory()) { @@ -17,6 +18,8 @@ PlaylistManager::PlaylistManager() { } } } + root.close(); + */ if (!SD.exists("/_mapping.txt")) { Serial.println("WARNING: /_mapping.txt not found!"); @@ -37,7 +40,7 @@ PlaylistManager::PlaylistManager() { Serial.printf(" Adding mapping: %s=>%s\n", rfid_id.c_str(), folder.c_str()); map[rfid_id] = folder; - if (folder.charAt(0)=='/') { + /*if (folder.charAt(0)=='/') { bool found=false; for (String f: dirs) { if (f.equals(folder)) { @@ -48,12 +51,11 @@ PlaylistManager::PlaylistManager() { if (!found) { Serial.printf("WARNING: Found mapping for RFID id %s which maps to non-existing folder %s!\n", rfid_id.c_str(), folder.c_str()); } - } + }*/ } } f.close(); } - root.close(); } Playlist PlaylistManager::get_playlist(String rfid_id) { From 6ddf1efd629f0234e44fb5737e2fda5c7f276aa6 Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Sun, 21 Aug 2022 15:27:00 +0200 Subject: [PATCH 05/20] Prevent playing prev_track if there are no tracks. --- src/playlist.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/playlist.cpp b/src/playlist.cpp index f5b9403..ae7daab 100644 --- a/src/playlist.cpp +++ b/src/playlist.cpp @@ -46,7 +46,7 @@ bool Playlist::prev_track() { } else { set_current_position(current_file - 1, 0); } - return true; + return files.size()>0; } void Playlist::restart() { From 3272921db285d939e360a18a78607f0a97e322fc Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Sun, 21 Aug 2022 17:40:22 +0200 Subject: [PATCH 06/20] Daten von RFID-Karten (versuchen zu) lesen. --- include/controller.h | 3 ++- src/controller.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/include/controller.h b/include/controller.h index 149c3e8..bdf8394 100644 --- a/include/controller.h +++ b/include/controller.h @@ -17,7 +17,8 @@ class Controller { uint8_t button_pressed = 0; unsigned long button_pressed_since = 0; bool button_already_processed = false; - + String read_rfid_data(); + public: void handle(); void next_track(); diff --git a/src/controller.cpp b/src/controller.cpp index 3e1b1b2..473eabc 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -56,6 +56,9 @@ void Controller::handle_rfid() { s_uid.concat("0"); } s_uid.concat(temp); + + String data = read_rfid_data(); + play(s_uid); } rfid->PICC_HaltA(); @@ -64,6 +67,66 @@ void Controller::handle_rfid() { } } +String Controller::read_rfid_data() { + log_v("read_rfid_data() running..."); + static MFRC522::MIFARE_Key keys[8] = { + {{0xd3, 0xf7, 0xd3, 0xf7, 0xd3, 0xf7}}, // D3 F7 D3 F7 D3 F7 + {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, // FF FF FF FF FF FF = factory default + {{0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5}}, // A0 A1 A2 A3 A4 A5 + {{0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5}}, // B0 B1 B2 B3 B4 B5 + {{0x4d, 0x3a, 0x99, 0xc3, 0x51, 0xdd}}, // 4D 3A 99 C3 51 DD + {{0x1a, 0x98, 0x2c, 0x7e, 0x45, 0x9a}}, // 1A 98 2C 7E 45 9A + {{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}}, // AA BB CC DD EE FF + {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}} // 00 00 00 00 00 00 + }; + log_d("Trying to read RFID data..."); + + String data = ""; + MFRC522::PICC_Type type = rfid->PICC_GetType(rfid->uid.sak); + + uint8_t sectors = 0; + switch(type) { + case MFRC522Constants::PICC_TYPE_MIFARE_MINI: sectors = 5; break; + case MFRC522Constants::PICC_TYPE_MIFARE_1K: sectors = 16; break; + case MFRC522Constants::PICC_TYPE_MIFARE_4K: sectors = 40; break; + default: log_i("Unknown PICC type %s\n", String(MFRC522Debug::PICC_GetTypeName(type)).c_str()); + } + sectors = 2; // Pretend we have only two sectors, so we read only sector #1. + for (uint8_t sector=1; sectorkeyByte[0], k->keyByte[1], k->keyByte[2], k->keyByte[3], k->keyByte[4], k->keyByte[5]); + status = rfid->PCD_Authenticate(MFRC522Constants::PICC_CMD_MF_AUTH_KEY_A, block_offset, k, &rfid->uid); + if (status == MFRC522Constants::STATUS_OK) { + log_v("Authentication succeeded with key #%d\n", i); + break; + } + } + for (uint8_t block=0; blockMIFARE_Read(block_offset + block, buffer, &byte_count); + if (status != MFRC522Constants::STATUS_OK) { + log_d("MIFARE_Read() failed: %s\n", String(MFRC522Debug::GetStatusCodeName(status)).c_str()); + continue; + } + for (int i=0; i<16; i++) { + if (buffer[i]>=0x20 && buffer[i]<0x7F) data.concat((char)buffer[i]); + } + } + } + + //_rfid->PICC_HaltA(); + rfid->PCD_StopCrypto1(); + log_d("Data from RFID: %s\n", data.c_str()); + return data; +} + void Controller::play(String rfid_id) { if (!rfid_id.equals(current_playlist.get_rfid_id())) { if (pm->has_playlist(rfid_id)) { From 2908d23e6017653786f0fff6af4132e7a94dbb60 Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Sun, 21 Aug 2022 17:40:32 +0200 Subject: [PATCH 07/20] =?UTF-8?q?Alten=20Code=20gel=C3=B6scht.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/playlist_manager.cpp | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/src/playlist_manager.cpp b/src/playlist_manager.cpp index 9c0f635..91ac74b 100644 --- a/src/playlist_manager.cpp +++ b/src/playlist_manager.cpp @@ -6,21 +6,6 @@ PlaylistManager::PlaylistManager() { SPIMaster::enable_sd(); current_rfid_tag_id = String(""); - /* - File root = SD.open("/"); - while(File entry = root.openNextFile()) { - if (entry.isDirectory()) { - String filename("/"); - filename.concat(entry.name()); - if (!filename.startsWith("/.")) { - Serial.println(filename); - dirs.push_back(filename); - } - } - } - root.close(); - */ - if (!SD.exists("/_mapping.txt")) { Serial.println("WARNING: /_mapping.txt not found!"); } else { @@ -39,19 +24,6 @@ PlaylistManager::PlaylistManager() { String folder = data.substring(eq + 1); Serial.printf(" Adding mapping: %s=>%s\n", rfid_id.c_str(), folder.c_str()); map[rfid_id] = folder; - - /*if (folder.charAt(0)=='/') { - bool found=false; - for (String f: dirs) { - if (f.equals(folder)) { - found = true; - break; - } - } - if (!found) { - Serial.printf("WARNING: Found mapping for RFID id %s which maps to non-existing folder %s!\n", rfid_id.c_str(), folder.c_str()); - } - }*/ } } f.close(); From aed9c416bf23bffa3d6b0762d372164c6a3bacb0 Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Sun, 21 Aug 2022 17:40:50 +0200 Subject: [PATCH 08/20] =?UTF-8?q?Standard-Lautst=C3=A4rke=2012=20statt=203?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/esmp3.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/esmp3.cpp b/src/esmp3.cpp index b69b2b5..25ba11e 100644 --- a/src/esmp3.cpp +++ b/src/esmp3.cpp @@ -88,7 +88,7 @@ void setup() { Serial.println("Setup finished."); - audio.setVolume(3); + audio.setVolume(12); audio.connecttospeech("Die Papabox ist nun einsatzbereit!", "de_DE"); } From 9a39b00a65df4ffebbb99eb0cd9cfbf4ad00611e Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Sun, 21 Aug 2022 17:42:58 +0200 Subject: [PATCH 09/20] =?UTF-8?q?Lautst=C3=A4rke-=C3=84nderungen=20in=202e?= =?UTF-8?q?r-Schritten.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/controller.cpp b/src/controller.cpp index 473eabc..a40667d 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -12,12 +12,17 @@ void Controller::handle() { void Controller::handle_buttons() { if (is_button_pressed(PIN_BTN_VOL_UP)) { log_i("BTN_VOL_UP pressed"); - uint8_t vol = min(audio.getVolume()+1, 21); + uint8_t vol = min(audio.getVolume()+2, 21); log_d("Setting new volume %d", vol); audio.setVolume(vol); } else if (is_button_pressed(PIN_BTN_VOL_DOWN)) { log_i("BTN_VOL_DOWN pressed"); - uint8_t vol = max(audio.getVolume()-1, 1); + uint8_t vol; + if ((vol = audio.getVolume()) >= 3) { + vol -= 2; + } else { + vol = 1; + } log_d("Setting new volume %d", vol); audio.setVolume(vol); } else if (is_button_pressed(PIN_BTN_TRACK_NEXT)) { From cf433a48b28c13231151e4725e5757dc336fb928 Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Mon, 22 Aug 2022 08:51:19 +0200 Subject: [PATCH 10/20] Support for http(s) links in _mapping.txt --- src/controller.cpp | 10 ++++++++-- src/playlist_manager.cpp | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/controller.cpp b/src/controller.cpp index a40667d..1eab3d7 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -149,8 +149,14 @@ void Controller::play(String rfid_id) { void Controller::play() { String file = current_playlist.get_current_file_name(); - Serial.printf("Playing file: %s\n", file.c_str()); - audio.connecttoFS(SD, file.c_str(), current_playlist.get_current_time()); + + if (file.startsWith("/")) { + log_i("Playing file %s via connecttoFS", file.c_str()); + audio.connecttoFS(SD, file.c_str(), current_playlist.get_current_time()); + } else if (file.startsWith("http")) { + log_i("Playing URL %s via connecttohost", file.c_str()); + audio.connecttohost(file.c_str()); + } } void Controller::next_track() { diff --git a/src/playlist_manager.cpp b/src/playlist_manager.cpp index 91ac74b..75f2776 100644 --- a/src/playlist_manager.cpp +++ b/src/playlist_manager.cpp @@ -54,6 +54,9 @@ Playlist PlaylistManager::get_playlist(String rfid_id) { entry.close(); } dir.close(); + } else if (path.startsWith("http")) { + Serial.printf("Adding URL %s to the list of files\n", path.c_str()); + current_playlist.add_file(path); } current_playlist.sort(); current_rfid_tag_id = rfid_id; From 69892489709f3cf5a1f18485d101544415f7f7c3 Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Mon, 22 Aug 2022 08:51:29 +0200 Subject: [PATCH 11/20] Less debugging messages. --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 7b81aee..2cef77b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,7 +19,7 @@ platform = espressif32 board = esp-wrover-kit framework = arduino upload_speed = 921600 -build_flags = -DCORE_DEBUG_LEVEL=5 -DCONFIG_ARDUHAL_LOG_COLORS=1 ; !./build_version.sh +build_flags = -DCORE_DEBUG_LEVEL=3 -DCONFIG_ARDUHAL_LOG_COLORS=1 ; !./build_version.sh lib_deps = ${extra.lib_deps} esphome/ESP32-audioI2S@^2.1.0 From 7dcb0cb673d5895737f146384c50a6f35b77c361 Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Mon, 22 Aug 2022 08:52:09 +0200 Subject: [PATCH 12/20] Allocate a bigger (30KByte) Buffer for Audio. --- src/esmp3.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/esmp3.cpp b/src/esmp3.cpp index 25ba11e..3e1656e 100644 --- a/src/esmp3.cpp +++ b/src/esmp3.cpp @@ -47,6 +47,14 @@ void setup() { ArduinoOTA.handle(); delay(100); } + + Serial.println("Setting up audio..."); + audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT); + audio.setVolume(1); + audio.forceMono(true); + audio.setBufsize(30000, -1); + audio.setConnectionTimeout(1000, 1000); + Serial.println("Initializing SPI..."); SPI.begin(); //SPI.setHwCs(false); @@ -81,11 +89,6 @@ void setup() { pinMode(PIN_BTN_TRACK_NEXT, INPUT_PULLUP); pinMode(PIN_BTN_TRACK_PREV, INPUT_PULLUP); - Serial.println("Setting up audio..."); - audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT); - audio.setVolume(15); - audio.forceMono(true); - Serial.println("Setup finished."); audio.setVolume(12); From 3718f45983338a69fa6dd4645fee73c4b8f4f46d Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Mon, 22 Aug 2022 13:55:57 +0200 Subject: [PATCH 13/20] Loglevel up to full debugging. ;-) --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 2cef77b..7b81aee 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,7 +19,7 @@ platform = espressif32 board = esp-wrover-kit framework = arduino upload_speed = 921600 -build_flags = -DCORE_DEBUG_LEVEL=3 -DCONFIG_ARDUHAL_LOG_COLORS=1 ; !./build_version.sh +build_flags = -DCORE_DEBUG_LEVEL=5 -DCONFIG_ARDUHAL_LOG_COLORS=1 ; !./build_version.sh lib_deps = ${extra.lib_deps} esphome/ESP32-audioI2S@^2.1.0 From fb6b5bced604ffe2943affcf4e1af4b7bcaad510 Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Mon, 22 Aug 2022 13:56:43 +0200 Subject: [PATCH 14/20] Test for button press every 50ms. That's more than enough. --- include/controller.h | 1 + src/controller.cpp | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/controller.h b/include/controller.h index bdf8394..9a054a1 100644 --- a/include/controller.h +++ b/include/controller.h @@ -14,6 +14,7 @@ class Controller { Playlist current_playlist; bool is_rfid_present = false; unsigned long last_rfid_check = 0; + unsigned long last_button_check = 0; uint8_t button_pressed = 0; unsigned long button_pressed_since = 0; bool button_already_processed = false; diff --git a/src/controller.cpp b/src/controller.cpp index 1eab3d7..e491e7f 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -6,7 +6,10 @@ void Controller::handle() { handle_rfid(); last_rfid_check = millis(); } - handle_buttons(); + if (last_button_check + 10 < millis() || last_button_check > millis()) { + handle_buttons(); + last_button_check = millis(); + } } void Controller::handle_buttons() { From b9df55012fa9cdbb40e755220d84001a1d5a31e9 Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Mon, 22 Aug 2022 13:57:20 +0200 Subject: [PATCH 15/20] Better handling of eof_mp3 events: Keep playing as long as there is data remaining. --- include/controller.h | 1 + src/controller.cpp | 6 ++++++ src/esmp3.cpp | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/controller.h b/include/controller.h index 9a054a1..b9892cc 100644 --- a/include/controller.h +++ b/include/controller.h @@ -27,4 +27,5 @@ class Controller { void play(); void play(String rfid_id); void stop(); + void eof_mp3(); }; \ No newline at end of file diff --git a/src/controller.cpp b/src/controller.cpp index e491e7f..2d9f723 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -208,4 +208,10 @@ bool Controller::is_button_pressed(uint8_t pin) { } } return false; +} + +void Controller::eof_mp3() { + log_d("Handling eof. Keep playing until the file is finished."); + while(audio.isRunning()) { audio.loop(); yield; } + next_track(); } \ No newline at end of file diff --git a/src/esmp3.cpp b/src/esmp3.cpp index 3e1656e..8aeb14a 100644 --- a/src/esmp3.cpp +++ b/src/esmp3.cpp @@ -109,7 +109,7 @@ void audio_id3data(const char *info){ //id3 metadata } void audio_eof_mp3(const char *info){ //end of file Serial.print("eof_mp3 ");Serial.println(info); - controller.next_track(); + controller.eof_mp3(); } void audio_showstation(const char *info){ Serial.print("station ");Serial.println(info); From 15a65f7391d5f31b02a4a4052a58548597a23ba2 Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Mon, 22 Aug 2022 13:58:22 +0200 Subject: [PATCH 16/20] Added reaing of RFID contents. --- src/controller.cpp | 82 ++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 47 deletions(-) diff --git a/src/controller.cpp b/src/controller.cpp index 2d9f723..90e424a 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -77,61 +77,49 @@ void Controller::handle_rfid() { String Controller::read_rfid_data() { log_v("read_rfid_data() running..."); - static MFRC522::MIFARE_Key keys[8] = { - {{0xd3, 0xf7, 0xd3, 0xf7, 0xd3, 0xf7}}, // D3 F7 D3 F7 D3 F7 - {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, // FF FF FF FF FF FF = factory default - {{0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5}}, // A0 A1 A2 A3 A4 A5 - {{0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5}}, // B0 B1 B2 B3 B4 B5 - {{0x4d, 0x3a, 0x99, 0xc3, 0x51, 0xdd}}, // 4D 3A 99 C3 51 DD - {{0x1a, 0x98, 0x2c, 0x7e, 0x45, 0x9a}}, // 1A 98 2C 7E 45 9A - {{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}}, // AA BB CC DD EE FF - {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}} // 00 00 00 00 00 00 - }; - log_d("Trying to read RFID data..."); - - String data = ""; + MFRC522::StatusCode status; MFRC522::PICC_Type type = rfid->PICC_GetType(rfid->uid.sak); - - uint8_t sectors = 0; + uint16_t pageStart = 0; + uint16_t pages = 4; + uint16_t pageSize = 1; switch(type) { - case MFRC522Constants::PICC_TYPE_MIFARE_MINI: sectors = 5; break; - case MFRC522Constants::PICC_TYPE_MIFARE_1K: sectors = 16; break; - case MFRC522Constants::PICC_TYPE_MIFARE_4K: sectors = 40; break; - default: log_i("Unknown PICC type %s\n", String(MFRC522Debug::PICC_GetTypeName(type)).c_str()); - } - sectors = 2; // Pretend we have only two sectors, so we read only sector #1. - for (uint8_t sector=1; sectorkeyByte[0], k->keyByte[1], k->keyByte[2], k->keyByte[3], k->keyByte[4], k->keyByte[5]); - status = rfid->PCD_Authenticate(MFRC522Constants::PICC_CMD_MF_AUTH_KEY_A, block_offset, k, &rfid->uid); + case MFRC522Constants::PICC_TYPE_MIFARE_MINI: + case MFRC522Constants::PICC_TYPE_MIFARE_1K: + case MFRC522Constants::PICC_TYPE_MIFARE_4K: { + log_v("Trying to authenticate Mifare card."); + MFRC522::MIFARE_Key key = {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7}; + status = rfid->PCD_Authenticate(MFRC522Constants::PICC_CMD_MF_AUTH_KEY_A, 4, &key, &rfid->uid); if (status == MFRC522Constants::STATUS_OK) { - log_v("Authentication succeeded with key #%d\n", i); - break; + log_v("Authentication succeeded."); + } else { + log_v("Authentication failed. Trying to read anyway."); } + pageStart = 4; + break; } - for (uint8_t block=0; blockMIFARE_Read(block_offset + block, buffer, &byte_count); - if (status != MFRC522Constants::STATUS_OK) { - log_d("MIFARE_Read() failed: %s\n", String(MFRC522Debug::GetStatusCodeName(status)).c_str()); - continue; - } - for (int i=0; i<16; i++) { - if (buffer[i]>=0x20 && buffer[i]<0x7F) data.concat((char)buffer[i]); - } + case MFRC522Constants::PICC_TYPE_MIFARE_UL: + log_v("PICC type is Mifare Ultralight. No authentication necessary."); + pages = 16; + pageSize = 4; + break; + default: + log_v("Unexpected rfid card type %s. Trying to read anyway.", MFRC522Debug::PICC_GetTypeName(type)); + } + String data = ""; + for (uint8_t block=pageStart; blockMIFARE_Read(block, buffer, &byte_count); + if (status != MFRC522Constants::STATUS_OK) { + log_d("MIFARE_Read() failed: %s\n", String(MFRC522Debug::GetStatusCodeName(status)).c_str()); + continue; + } + for (int i=0; i<16; i++) { + if (buffer[i]>=0x20 && buffer[i]<0x7F) data.concat((char)buffer[i]); } } - - //_rfid->PICC_HaltA(); rfid->PCD_StopCrypto1(); - log_d("Data from RFID: %s\n", data.c_str()); + log_v("Read rfid data: '%s'", data.c_str()); return data; } From 13e62fea190d61f4ffb175e87876a338cf6f4b6c Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Mon, 22 Aug 2022 13:59:00 +0200 Subject: [PATCH 17/20] Shuffle the playlist before playing if "[random]" is in the rfid data. --- include/controller.h | 4 ++-- include/playlist.h | 1 + src/controller.cpp | 8 ++++++-- src/playlist.cpp | 4 ++++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/include/controller.h b/include/controller.h index b9892cc..a4d6fdb 100644 --- a/include/controller.h +++ b/include/controller.h @@ -19,13 +19,13 @@ class Controller { unsigned long button_pressed_since = 0; bool button_already_processed = false; String read_rfid_data(); - + public: void handle(); void next_track(); void prev_track(); void play(); - void play(String rfid_id); + void play(String rfid_id, bool shuffle=false); void stop(); void eof_mp3(); }; \ No newline at end of file diff --git a/include/playlist.h b/include/playlist.h index 4bd5731..1edbfce 100644 --- a/include/playlist.h +++ b/include/playlist.h @@ -22,4 +22,5 @@ class Playlist { void restart(); void set_current_time(uint32_t time); uint32_t get_current_time(); + void shuffle(); }; diff --git a/src/controller.cpp b/src/controller.cpp index 90e424a..7c874f9 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -67,7 +67,7 @@ void Controller::handle_rfid() { String data = read_rfid_data(); - play(s_uid); + play(s_uid, data.indexOf("[random]")>=0); } rfid->PICC_HaltA(); } @@ -123,10 +123,14 @@ String Controller::read_rfid_data() { return data; } -void Controller::play(String rfid_id) { +void Controller::play(String rfid_id, bool shuffle) { if (!rfid_id.equals(current_playlist.get_rfid_id())) { if (pm->has_playlist(rfid_id)) { current_playlist = pm->get_playlist(rfid_id); + if (shuffle) { + log_i("Shuffling the playlist."); + current_playlist.shuffle(); + } play(); } else { Serial.printf("There is no playlist for rfid_id %s\n", rfid_id.c_str()); diff --git a/src/playlist.cpp b/src/playlist.cpp index ae7daab..f6aa218 100644 --- a/src/playlist.cpp +++ b/src/playlist.cpp @@ -59,4 +59,8 @@ void Playlist::set_current_time(uint32_t pos) { uint32_t Playlist::get_current_time() { return current_time; +} + +void Playlist::shuffle() { + std::random_shuffle(files.begin(), files.end()); } \ No newline at end of file From 076a6993c7ca0777d1ccc02b07e54f1467dcd478 Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Thu, 25 Aug 2022 14:48:25 +0200 Subject: [PATCH 18/20] FTP-Server for managing the contents of the sd card. --- platformio.ini | 3 ++- src/esmp3.cpp | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 7b81aee..33b7be4 100644 --- a/platformio.ini +++ b/platformio.ini @@ -24,10 +24,11 @@ lib_deps = ${extra.lib_deps} esphome/ESP32-audioI2S@^2.1.0 computer991/Arduino_MFRC522v2@^2.0.1 + https://github.com/dplasa/FTPClientServer ;upload_port = 10.10.2.108 monitor_speed = 115200 monitor_port = /dev/cu.usbserial-0001 -monitor_filters = time, esp32_exception_decoder +monitor_filters = esp32_exception_decoder [env:deploy] platform = espressif32 diff --git a/src/esmp3.cpp b/src/esmp3.cpp index 8aeb14a..4a4e27c 100644 --- a/src/esmp3.cpp +++ b/src/esmp3.cpp @@ -13,11 +13,13 @@ #include #include #include +#include Controller controller; Audio audio; PlaylistManager* pm; MFRC522* rfid; +FTPServer ftp(SD); void setup() { pinMode(PIN_CS_SD, OUTPUT); digitalWrite(PIN_CS_SD, HIGH); @@ -92,13 +94,15 @@ void setup() { Serial.println("Setup finished."); audio.setVolume(12); - audio.connecttospeech("Die Papabox ist nun einsatzbereit!", "de_DE"); + + ftp.begin("", ""); } void loop() { ArduinoOTA.handle(); controller.handle(); audio.loop(); + ftp.handleFTP(); } void audio_info(const char *info){ From b2cf9d62771c9c6d5a36e3eb2a29d6680c83bed9 Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Thu, 25 Aug 2022 14:51:18 +0200 Subject: [PATCH 19/20] Systeminfos werden via mp3-Dateien in /system abgespielt. --- README.md | 178 ++----------------------------------------- include/controller.h | 2 +- src/controller.cpp | 16 +++- src/esmp3.cpp | 3 +- 4 files changed, 23 insertions(+), 176 deletions(-) diff --git a/README.md b/README.md index 2d9787f..7a75651 100644 --- a/README.md +++ b/README.md @@ -1,174 +1,8 @@ # ESMP3 -## What you need -Please note: This list is a "things I used", neither -"these are the best things for this stuff" nor "you -can only use these things". But please be aware that -using other stuff may lead to you having to make -more or less easy modifications. - -Prizes are more or less the cheapest I could find on -Aliexpress. - -| What? | For what? | Price (approx) | -|-------|-----------|----------------| -| ESP-32-WROOM-32D | Controlling everything |  4€ | -| WS1053B on a PCB with SD card slot | Play the MP3 files; provide an SD card slot |  5€ | -| MFRC522 | RFID reader | 1€ | -| 5V Amplifier(s) (e.g. 2x PAM-8302) | Single-channel Amplifier |  2€ | -| Speaker(s) matching your amp (e.g. 2pcs 4 Ohm 5W) | Enabling you to hear the sounds |  4€ | -| RFID tags (ISO14443A) - e.g. 10 cards | You can also get Keyfobs or stickers |  4€ | -| 4 buttons | Prev/Next track, Volume up/down |  1€ | - -You'all also need an SD card, some breadboard(s), jumper cables and a soldering iron. -Also, some kind of box for the finished player. - -## How to connect - -Schematics coming soon...ish... - -## How to install - -Format your SD card with FAT32 and put files on it: Every album has -to go into its own folder in the root of the SD card. Folders and files -should not contain special characters (meaning stuff like äöüß). Spaces -and dashes an alike are okay. Put the SD card into the SD card slot. - -Copy `include/config.sample.h` to `include/config.h`. Modify it to at -least contain the correct login details for your WiFi. - -The code then should compile in PlatformIO without errors. Upload it -to your ESP32. After that, upload static files using PlatformIO's task -"Upload file system image". - -The serial console in PlatformIO should give you more or less useful -messages about what's going on. There will also be a line saying -"WiFi connected. IP address: xxx.xxx.xxx.xxx" when the connection to -your WiFi succeeded. - -In your browser, enter "http://xxx.xxx.xxx.xxx/" (using the IP address) -from above. From there you can define mappings between RFID tag IDs and -folders on the SD card. - -## RFID-folder-mappings - -### Via webinterface - -To create a new mapping between an RFID tag and an folder, you can use -the web interface. Click the button with the cogs icon. After putting -your rfid tag on the reader (and possibly removing it again), its ID -will be shown in the dialog. Click the button with the arrows behind -the ID to start the mapping mode. - -The dialog showing all folders with media files will be shown. Click the -button with the arrows behind the correct folder, to create the mapping. - -### Manually - -Mapping are stored on the SD card in the file `/_mapping.txt`. Every -mapping goes on its own line. Lines should be separated by \n (Unix- -style line endings); the last line should also end with a newline. - -Format of a line is `=`. RFID id is the UID of an -RFID tag, expressed as 8 lowercase characters with leading 0 (if -necessary). Folder is the foldername to play; starting with a slash and -ending without one. - -A valid `_mapping.txt` could look like this: - -``` -1a2b3c4d=/Christmas Music Vol. 17 -003aab7f=/Let it go -b691a22c=/Frozen Audiobook -22cb6ae9=/Let it go - -``` - -(Yes, more than one tag can map to a folder.) - -## Technical details - -### Ports - -| Device | Port | Connected to | -| ------ | ---- | ------------ | -| VS1053 | CS | 16 | -| VS1053 | MISO | 19 | -| VS1053 | MOSI | 23 | -| VS1053 | SCK | 18 | -| VS1053 | XCS | 4 | -| VS1053 | XRESET | 0 | -| VS1053 | XDCS | 2 | -| VS1053 | DREQ | 15 | -| RC522 | SDA | 17 | -| RC522 | SCK | 18 | -| RC522 | MOSI | 23 | -| RC522 | MISO | 19 | -| AMP_L | SD | 27 | -| AMP_R | SD | 26 | -| BTN_PREV | | 22 | -| BTN_NEXT | | 33 | -| BTN_VOL_UP | | 21 | -| BTN_VOL_DOWN | | 32 | - -Buttons pull to GND if pushed -> Internal Pull-Up needed! - -### RFID tags -The mapping of rfid tags to files uses the ID of the -tag. A file called `_mapping.txt` in the root folder of -the SD card defines the mappings between RFID tag ids and -folders to play. - -The easiest way to create this file is to use the mapping -functionality of the webinterface. - -#### Special modes -You can also save data on the tags to further manipulate -the system. Position of the data is irrelevant, the whole -tag will be searched. - -Using `[random]` will play the files in a random order. -`[random:2]` will randomize everything except the first 2 -files. This can be useful for having the favorite song of -your kids playing, but after that getting a bit of randomness. - -Using `[lock]` will turn this key into a key for the locking -mode. Scanning the tag enables locking mode. The next album -started will keep running until the end. Removing the tag -will be deactivated, as are the buttons for prev and next -track. You can disable locking mode by again scanning the -lock tag again. - -`[advent]` is used for christmas time. An album with this tag -will only play in December. On December 1st, only track 1 -will play. On December 2nd, track 2 followed by track 1. On -December 3rd, tracks 3, 1 and 2. From December 24th on, track -24 followed by tracks 1-23. So your kid will get the "daily track" -first, followed by all previous tags in the right order. - -#### API -You can send commands to ESMP3 using three different ways: -* Through a websocket connection to `ws:///ws`. -* Through the serial console using an USB cable. -* Via HTTP POST request to `http:///cmd`, having the - command in the variable `cmd`. - -Supported commands are: -| Command | Action | -|---------|--------| -| `play ` | Starts playing the given path. Path may be a path on the -sd card or a http(s) URL of a webstream (direct links to mp3/4/ogg streams, -PLS files, M3U files or podcast XML feeds are supported). | -| `play` | Continues playing the previously played thing. | -| `stop` | Stops playing. | -| `volume=` | Sets the volume to X (0-255). | -| `track_prev` | Starts the previous track, if available. | -| `track_next` | Starts the next track, if available. | -| `track=` | Starts playing track no. X of the currently playing album. | -| `reset_vs1053` | Resets the VS1053 audio chip. | -| `reboot` | Reboots ESMP3. | -| `add_mapping==` | Adds a mapping between RFID card and path -. See `play` for valid path formats. | -| `update` | Runs an update check. | -| `debug=<0|1>` | Enables / disables debug messages. This value is persisted across reboots. | -| `trace=<0|1>` | Enables / disables tracing messages. This value is also persisted across reboots. | \ No newline at end of file +## Audio files +System messages are created using: + * https://ttsmp3.com/ + * German / Vicki + * "Dies ist ein Text." + * Download as MP3. \ No newline at end of file diff --git a/include/controller.h b/include/controller.h index a4d6fdb..428558a 100644 --- a/include/controller.h +++ b/include/controller.h @@ -27,5 +27,5 @@ class Controller { void play(); void play(String rfid_id, bool shuffle=false); void stop(); - void eof_mp3(); + void eof_mp3(String info); }; \ No newline at end of file diff --git a/src/controller.cpp b/src/controller.cpp index 7c874f9..acde59a 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -134,6 +134,9 @@ void Controller::play(String rfid_id, bool shuffle) { play(); } else { Serial.printf("There is no playlist for rfid_id %s\n", rfid_id.c_str()); + // This is working more or less, but downloading files is really, REALLY slow. (About 4 minutes for 10 MBytes). + //download_album(rfid_id); + audio.connecttoFS(SD, "/system/sys_unknown_card.mp3"); } } else { if (!audio.isRunning()) { @@ -150,6 +153,11 @@ void Controller::play() { audio.connecttoFS(SD, file.c_str(), current_playlist.get_current_time()); } else if (file.startsWith("http")) { log_i("Playing URL %s via connecttohost", file.c_str()); + audio.connecttoFS(SD, "/system/sys_connecting.mp3"); + while (audio.isRunning()) { + yield(); + audio.loop(); + } audio.connecttohost(file.c_str()); } } @@ -202,8 +210,12 @@ bool Controller::is_button_pressed(uint8_t pin) { return false; } -void Controller::eof_mp3() { +void Controller::eof_mp3(String info) { log_d("Handling eof. Keep playing until the file is finished."); while(audio.isRunning()) { audio.loop(); yield; } - next_track(); + if (info.startsWith("sys_")) { + log_d("File ending was a system audio file. Not running next_track."); + } else { + next_track(); + } } \ No newline at end of file diff --git a/src/esmp3.cpp b/src/esmp3.cpp index 4a4e27c..6e6ef99 100644 --- a/src/esmp3.cpp +++ b/src/esmp3.cpp @@ -94,6 +94,7 @@ void setup() { Serial.println("Setup finished."); audio.setVolume(12); + audio.connecttoFS(SD, "/system/sys_ready.mp3"); ftp.begin("", ""); } @@ -113,7 +114,7 @@ void audio_id3data(const char *info){ //id3 metadata } void audio_eof_mp3(const char *info){ //end of file Serial.print("eof_mp3 ");Serial.println(info); - controller.eof_mp3(); + controller.eof_mp3(info); } void audio_showstation(const char *info){ Serial.print("station ");Serial.println(info); From 913a64d46539ca4db922cb1e124862b7bcad974a Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Thu, 25 Aug 2022 14:52:12 +0200 Subject: [PATCH 20/20] Die Position in einer Playlist beilbt beibehalten, solange die Papabox nicht neu gestartet wird. --- include/controller.h | 1 + include/persisted_playlist.h | 9 +++++++++ include/playlist.h | 8 ++++++-- include/playlist_manager.h | 6 +++++- src/controller.cpp | 5 +++++ src/playlist.cpp | 24 ++++++++++++++++++++---- src/playlist_manager.cpp | 17 ++++++++++++++--- 7 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 include/persisted_playlist.h diff --git a/include/controller.h b/include/controller.h index 428558a..1d7f7ef 100644 --- a/include/controller.h +++ b/include/controller.h @@ -15,6 +15,7 @@ class Controller { bool is_rfid_present = false; unsigned long last_rfid_check = 0; unsigned long last_button_check = 0; + unsigned long last_position_save = 0; uint8_t button_pressed = 0; unsigned long button_pressed_since = 0; bool button_already_processed = false; diff --git a/include/persisted_playlist.h b/include/persisted_playlist.h new file mode 100644 index 0000000..d16ea28 --- /dev/null +++ b/include/persisted_playlist.h @@ -0,0 +1,9 @@ +#pragma once + + +struct PersistedPlaylist { + String dir; + uint16_t file = 0; + uint32_t position = 0; + PersistedPlaylist(String s="") : dir(s) {} +}; \ No newline at end of file diff --git a/include/playlist.h b/include/playlist.h index 1edbfce..6edb34e 100644 --- a/include/playlist.h +++ b/include/playlist.h @@ -2,17 +2,19 @@ #include #include +#include "persisted_playlist.h" class Playlist { private: std::vector files; uint8_t current_file = 0; uint32_t current_time = 0; - void set_current_position(uint8_t file, uint32_t position=0); String rfid_id; + PersistedPlaylist* pp; public: - Playlist(String rfid_id=""); + Playlist(); + Playlist(String rfid_id, PersistedPlaylist* p); void add_file(String filename); void sort(); String get_rfid_id(); @@ -23,4 +25,6 @@ class Playlist { void set_current_time(uint32_t time); uint32_t get_current_time(); void shuffle(); + void set_current_position(uint8_t file, uint32_t position=0); + void save_current_position(uint32_t position=0); }; diff --git a/include/playlist_manager.h b/include/playlist_manager.h index ab07ad2..52e1289 100644 --- a/include/playlist_manager.h +++ b/include/playlist_manager.h @@ -4,6 +4,9 @@ #include #include #include "playlist.h" +#include "persisted_playlist.h" + +class Playlist; class PlaylistManager { private: @@ -13,9 +16,10 @@ class PlaylistManager { public: PlaylistManager(); - std::map map; + std::map map; Playlist get_playlist(String rfid_id); bool has_playlist(String rfid_id); Playlist current_playlist; void set_audio_current_time(uint32_t time); + String pp_to_String(); }; \ No newline at end of file diff --git a/src/controller.cpp b/src/controller.cpp index acde59a..91e033b 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -10,6 +10,11 @@ void Controller::handle() { handle_buttons(); last_button_check = millis(); } + if (last_position_save + 10000 < millis() || last_position_save > millis()) { + current_playlist.save_current_position(audio.getFilePos()); + last_position_save = millis(); + //Serial.println(pm->pp_to_String().c_str()); + } } void Controller::handle_buttons() { diff --git a/src/playlist.cpp b/src/playlist.cpp index f6aa218..0218467 100644 --- a/src/playlist.cpp +++ b/src/playlist.cpp @@ -1,7 +1,10 @@ #include "playlist.h" -Playlist::Playlist(String id) { +Playlist::Playlist() {} + +Playlist::Playlist(String id, PersistedPlaylist* p) { rfid_id = id; + pp = p; } String Playlist::get_rfid_id() { @@ -16,9 +19,22 @@ void Playlist::sort() { std::sort(files.begin(), files.end()); } -void Playlist::set_current_position(uint8_t file, uint32_t seconds) { +void Playlist::set_current_position(uint8_t file, uint32_t bytes) { + log_d("Setting position: File %d, bytes %d.", file, bytes); current_file = file; - current_time = seconds; + current_time = bytes; + save_current_position(); +} + +void Playlist::save_current_position(uint32_t position) { + if (position==0) { + position = current_time; + } + log_d("Saving current position: File %d, bytes %d.", current_file, position); + if (pp != NULL) { + pp->file = current_file; + pp->position = position; + } } String Playlist::get_current_file_name() { @@ -54,7 +70,7 @@ void Playlist::restart() { } void Playlist::set_current_time(uint32_t pos) { - current_time = pos; + set_current_position(current_file, pos); } uint32_t Playlist::get_current_time() { diff --git a/src/playlist_manager.cpp b/src/playlist_manager.cpp index 75f2776..643ec12 100644 --- a/src/playlist_manager.cpp +++ b/src/playlist_manager.cpp @@ -23,7 +23,7 @@ PlaylistManager::PlaylistManager() { String rfid_id = data.substring(0, eq); String folder = data.substring(eq + 1); Serial.printf(" Adding mapping: %s=>%s\n", rfid_id.c_str(), folder.c_str()); - map[rfid_id] = folder; + map[rfid_id] = PersistedPlaylist(folder); } } f.close(); @@ -38,8 +38,10 @@ Playlist PlaylistManager::get_playlist(String rfid_id) { Serial.printf("No known playlist for id %s.\n", rfid_id); return current_playlist; } else { - current_playlist = Playlist(rfid_id); - String path = map[rfid_id]; + PersistedPlaylist* ap = &(map[rfid_id]); + log_d("PP status is: File %d, bytes %d.", ap->file, ap->position); + current_playlist = Playlist(rfid_id, ap); + String path = ap->dir; if (path.startsWith("/")) { File dir = SD.open(path); while(File entry = dir.openNextFile()) { @@ -54,6 +56,7 @@ Playlist PlaylistManager::get_playlist(String rfid_id) { entry.close(); } dir.close(); + current_playlist.set_current_position(ap->file, ap->position); } else if (path.startsWith("http")) { Serial.printf("Adding URL %s to the list of files\n", path.c_str()); current_playlist.add_file(path); @@ -71,4 +74,12 @@ void PlaylistManager::set_audio_current_time(uint32_t time) { bool PlaylistManager::has_playlist(String rfid_id) { return map.count(rfid_id) == 1; +} + +String PlaylistManager::pp_to_String() { + String s = ""; + for(const auto& kv : map) { + s += kv.first + "=" + kv.second.file + "," + kv.second.position + '\n'; + } + return s; } \ No newline at end of file