Changed the playing code to use Playlists managed by a PlaylistManager. This allows you to have randomized playlists and stuff. Also, you can now access special functions via the contents of RFID tags. See the README for a list of available modes.

This commit is contained in:
Fabian Schlenz 2019-11-14 20:42:02 +01:00
parent 6e05900b5a
commit e471a57578
11 changed files with 412 additions and 344 deletions

View File

@ -22,3 +22,29 @@
| BTN_VOL_DOWN | | 32 | | BTN_VOL_DOWN | | 32 |
Buttons pull to GND if pushed -> Internal Pull-Up needed! Buttons pull to GND if pushed -> Internal Pull-Up needed!
# RFID stuff
The mapping of rfid tags to files uses the ID of the
tag. Create a file `ids.txt` in a folder containing
one or more IDs will lead to the folder beginning to play
when a tag with that id is on the reader.
The ID should be a 8 character long, downcase string
containing the ID in hexadecimal. E.g. `23b1aa7d`.
## 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.

View File

@ -3,14 +3,19 @@
#include <Arduino.h> #include <Arduino.h>
#include "config.h" #include "config.h"
#include "player.h" #include "player.h"
#include "playlist.h"
#include "playlist_manager.h"
#include "mqtt_client.h" #include "mqtt_client.h"
#include <MFRC522.h> #include <MFRC522.h>
enum ControllerState { NORMAL, LOCKING, LOCKED };
class Controller { class Controller {
private: private:
MFRC522* _rfid; MFRC522* _rfid;
SPIMaster* _spi;
MQTTClient* _mqtt_client; MQTTClient* _mqtt_client;
PlaylistManager* _pm;
ControllerState _state = NORMAL;
bool _rfid_enabled = true; bool _rfid_enabled = true;
void _check_rfid(); void _check_rfid();
void _check_serial(); void _check_serial();
@ -33,7 +38,7 @@ private:
unsigned long _last_mqtt_report_at = 0; unsigned long _last_mqtt_report_at = 0;
void _send_mqtt_report(); void _send_mqtt_report();
public: public:
Controller(Player* p, SPIMaster* s); Controller(Player* p, PlaylistManager* pm);
void set_mqtt_client(MQTTClient* m); void set_mqtt_client(MQTTClient* m);
String get_status_json(); String get_status_json();
void loop(); void loop();

View File

@ -2,9 +2,8 @@
#include "config.h" #include "config.h"
#include <SPI.h> #include <SPI.h>
#include <SD.h> #include <SD.h>
#include <list>
#include <map>
#include "spi_master.h" #include "spi_master.h"
#include "playlist.h"
#define SCI_MODE 0x00 #define SCI_MODE 0x00
#define SCI_STATUS 0x01 #define SCI_STATUS 0x01
@ -38,13 +37,7 @@
class Player { class Player {
private: private:
enum state { uninitialized, idle, playing, stopping, enum state { uninitialized, idle, playing, stopping,
system_sound_while_playing, system_sound_while_stopped,
sleeping, recording }; sleeping, recording };
struct album_state {
uint8_t index;
uint32_t position;
};
void _check_system_sound(String filename);
void _reset(); void _reset();
void _init(); void _init();
void _wait(); void _wait();
@ -59,11 +52,8 @@ private:
void _flush_and_cancel(); void _flush_and_cancel();
int8_t _get_endbyte(); int8_t _get_endbyte();
void _flush(uint count, int8_t fill_byte); void _flush(uint count, int8_t fill_byte);
void _set_last_track(const char* album, uint8_t track, uint32_t position);
std::map<String, album_state> _last_tracks;
String _random_album();
void _play_file(String filename, uint32_t offset);
uint32_t _id3_tag_offset(File f); uint32_t _id3_tag_offset(File f);
void _play_file(String filename, uint32_t offset);
void _finish_playing(); void _finish_playing();
void _finish_stopping(bool turn_speaker_off); void _finish_stopping(bool turn_speaker_off);
void _mute(); void _mute();
@ -74,21 +64,15 @@ private:
void _patch_adpcm(); void _patch_adpcm();
void _speaker_off(); void _speaker_off();
void _speaker_on(); void _speaker_on();
void _fill_id_to_folder_map();
String _foldername_for_id(String id);
SPISettings _spi_settings_slow = SPISettings(250000, MSBFIRST, SPI_MODE0); SPISettings _spi_settings_slow = SPISettings(250000, MSBFIRST, SPI_MODE0);
SPISettings _spi_settings_fast = SPISettings(4000000, MSBFIRST, SPI_MODE0); SPISettings _spi_settings_fast = SPISettings(4000000, MSBFIRST, SPI_MODE0);
SPISettings* _spi_settings = &_spi_settings_slow; SPISettings* _spi_settings = &_spi_settings_slow;
std::list<String> _files_in_dir(String dir);
String _find_album_dir(String album);
File _file; File _file;
uint8_t _buffer[32]; uint8_t _buffer[32];
String _playing_album;
uint8_t _playing_index;
uint8_t _playing_album_songs;
uint32_t _current_play_position; uint32_t _current_play_position;
Playlist* _current_playlist;
uint _refills; uint _refills;
uint8_t _volume; uint8_t _volume;
uint16_t _stop_delay; uint16_t _stop_delay;
@ -96,26 +80,17 @@ private:
SPIMaster* _spi; SPIMaster* _spi;
unsigned long _stopped_at; unsigned long _stopped_at;
public: public:
std::map<String, String> id_to_folder_map;
Player(SPIMaster* s); Player(SPIMaster* s);
void vol_up(); void vol_up();
void vol_down(); void vol_down();
void track_next(); void track_next();
void track_prev(); void track_prev();
bool is_playing(); bool is_playing();
bool play();
bool play_id(String id); bool play(Playlist* p);
bool play_album(String album);
void play_random_album();
bool play_song(String album, uint8_t song_index, uint32_t offset=0);
void play_system_sound(String filename);
void stop(bool turn_speaker_off=true); void stop(bool turn_speaker_off=true);
bool loop(); bool loop();
void set_volume(uint8_t vol, bool save = true); void set_volume(uint8_t vol, bool save = true);
std::list<String> ls(String path, bool withFiles=true, bool withDirs=true, bool withHidden=false);
String album() { return _playing_album; }
uint8_t track() { return _playing_index; }
uint32_t position() { return _current_play_position; } uint32_t position() { return _current_play_position; }
uint8_t volume() { return _volume; } uint8_t volume() { return _volume; }
}; };

25
include/playlist.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include <Arduino.h>
#include <vector>
class Playlist {
private:
uint32_t _position = 0;
uint32_t _current_track = 0;
bool _shuffled = false;
std::vector<String> _files;
public:
Playlist(String path);
bool has_track_next();
bool has_track_prev();
bool track_next();
bool track_prev();
void track_restart();
void reset();
bool is_empty();
String get_current_file();
uint32_t get_position();
void set_position(uint32_t p);
void shuffle(uint8_t random_offset=0);
bool is_fresh();
};

View File

@ -0,0 +1,14 @@
#pragma once
#include <map>
#include "playlist.h"
class PlaylistManager {
private:
std::map<String, String> _map;
std::map<String, Playlist*> _playlists;
public:
PlaylistManager();
Playlist* get_playlist_for_id(String id);
void dump_ids();
};

View File

@ -6,7 +6,7 @@
class SPIMaster { class SPIMaster {
public: public:
SPIMaster() { static void init() {
PIN_SD_CS_SETUP(); PIN_SD_CS_SETUP();
PIN_VS1053_XCS_SETUP(); PIN_VS1053_XCS_SETUP();
PIN_VS1053_XDCS_SETUP(); PIN_VS1053_XDCS_SETUP();
@ -14,27 +14,27 @@ public:
disable(); disable();
} }
void select_sd(bool enabled=true) { static void select_sd(bool enabled=true) {
PIN_SD_CS(enabled ? LOW : HIGH); PIN_SD_CS(enabled ? LOW : HIGH);
delayMicroseconds(MCP_SPI_SETTING_DELAY); delayMicroseconds(MCP_SPI_SETTING_DELAY);
} }
void select_vs1053_xcs(bool enabled=true) { static void select_vs1053_xcs(bool enabled=true) {
PIN_VS1053_XCS(enabled ? LOW : HIGH); PIN_VS1053_XCS(enabled ? LOW : HIGH);
delayMicroseconds(MCP_SPI_SETTING_DELAY); delayMicroseconds(MCP_SPI_SETTING_DELAY);
} }
void select_vs1053_xdcs(bool enabled=true) { static void select_vs1053_xdcs(bool enabled=true) {
PIN_VS1053_XDCS(enabled ? LOW : HIGH); PIN_VS1053_XDCS(enabled ? LOW : HIGH);
delayMicroseconds(MCP_SPI_SETTING_DELAY); delayMicroseconds(MCP_SPI_SETTING_DELAY);
} }
void select_rc522(bool enabled=true) { static void select_rc522(bool enabled=true) {
PIN_RC522_CS(enabled ? LOW : HIGH); PIN_RC522_CS(enabled ? LOW : HIGH);
delayMicroseconds(MCP_SPI_SETTING_DELAY); delayMicroseconds(MCP_SPI_SETTING_DELAY);
} }
void disable() { static void disable() {
PIN_SD_CS(HIGH); PIN_SD_CS(HIGH);
PIN_VS1053_XCS(HIGH); PIN_VS1053_XCS(HIGH);
PIN_VS1053_XDCS(HIGH); PIN_VS1053_XDCS(HIGH);

View File

@ -1,10 +1,11 @@
#include "controller.h" #include "controller.h"
#include "spi_master.h" #include "spi_master.h"
#include "config.h" #include "config.h"
#include "playlist.h"
Controller::Controller(Player* p, SPIMaster* s) { Controller::Controller(Player* p, PlaylistManager* pm) {
_player = p; _player = p;
_spi = s; _pm = pm;
_rfid = new MFRC522(17, MFRC522::UNUSED_PIN); _rfid = new MFRC522(17, MFRC522::UNUSED_PIN);
BTN_NEXT_SETUP(); BTN_NEXT_SETUP();
@ -12,13 +13,13 @@ Controller::Controller(Player* p, SPIMaster* s) {
BTN_VOL_UP_SETUP(); BTN_VOL_UP_SETUP();
BTN_VOL_DOWN_SETUP(); BTN_VOL_DOWN_SETUP();
_spi->select_rc522(); SPIMaster::select_rc522();
DEBUG("Initializing RC522...\n"); DEBUG("Initializing RC522...\n");
_rfid->PCD_Init(); _rfid->PCD_Init();
#ifdef SHOW_DEBUG #ifdef SHOW_DEBUG
_rfid->PCD_DumpVersionToSerial(); _rfid->PCD_DumpVersionToSerial();
#endif #endif
_spi->select_rc522(false); SPIMaster::select_rc522(false);
INFO("RC522 initialized.\n"); INFO("RC522 initialized.\n");
for (uint8_t i=0; i<NUM_BUTTONS; i++) _button_last_pressed_at[i]=0; for (uint8_t i=0; i<NUM_BUTTONS; i++) _button_last_pressed_at[i]=0;
@ -44,7 +45,7 @@ void Controller::loop() {
} }
uint32_t Controller::_get_rfid_card_uid() { uint32_t Controller::_get_rfid_card_uid() {
_spi->select_rc522(); SPIMaster::select_rc522();
if (!_rfid->PICC_ReadCardSerial()) { if (!_rfid->PICC_ReadCardSerial()) {
if (!_rfid->PICC_IsNewCardPresent()) { if (!_rfid->PICC_IsNewCardPresent()) {
return 0; return 0;
@ -53,7 +54,7 @@ uint32_t Controller::_get_rfid_card_uid() {
return 0; return 0;
} }
} }
_spi->select_rc522(false); 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]; uint32_t uid = _rfid->uid.uidByte[0]<<24 | _rfid->uid.uidByte[1]<<16 | _rfid->uid.uidByte[2]<<8 | _rfid->uid.uidByte[3];
return uid; return uid;
} }
@ -63,19 +64,21 @@ void Controller::_check_rfid() {
if (_rfid_present) { if (_rfid_present) {
byte buffer[2]; byte buffer[2];
byte buffer_size = 2; byte buffer_size = 2;
_spi->select_rc522(); SPIMaster::select_rc522();
status = _rfid->PICC_WakeupA(buffer, &buffer_size); status = _rfid->PICC_WakeupA(buffer, &buffer_size);
if (status == MFRC522::STATUS_OK) { if (status == MFRC522::STATUS_OK) {
// Card is still present. // Card is still present.
_rfid->PICC_HaltA(); _rfid->PICC_HaltA();
_spi->select_rc522(false); SPIMaster::select_rc522(false);
return; return;
} }
_spi->select_rc522(false); SPIMaster::select_rc522(false);
// Card is now gone // Card is now gone
_rfid_present = false; _rfid_present = false;
INFO("No more RFID card.\n"); INFO("No more RFID card.\n");
if (_state != LOCKED) {
_player->stop(); _player->stop();
}
} else { } else {
uint32_t uid = _get_rfid_card_uid(); uint32_t uid = _get_rfid_card_uid();
if (uid > 0) { if (uid > 0) {
@ -92,23 +95,63 @@ void Controller::_check_rfid() {
String data = _read_rfid_data(); String data = _read_rfid_data();
_player->play_id(s_uid); 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'.", s_uid.c_str());
return;
}
int index;
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);
} }
} }
} }
String Controller::_read_rfid_data() { String Controller::_read_rfid_data() {
_spi->select_rc522(); 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"); DEBUG("Trying to read RFID data...\n");
String data = ""; String data = "";
MFRC522::MIFARE_Key key;
key.keyByte[0] = 0xD3;
key.keyByte[1] = 0xF7;
key.keyByte[2] = 0xD3;
key.keyByte[3] = 0xF7;
key.keyByte[4] = 0xD3;
key.keyByte[5] = 0xF7;
MFRC522::PICC_Type type = _rfid->PICC_GetType(_rfid->uid.sak); MFRC522::PICC_Type type = _rfid->PICC_GetType(_rfid->uid.sak);
uint8_t sectors = 0; uint8_t sectors = 0;
@ -119,17 +162,26 @@ String Controller::_read_rfid_data() {
default: INFO("Unknown PICC type %s\n", String(MFRC522::PICC_GetTypeName(type)).c_str()); default: INFO("Unknown PICC type %s\n", String(MFRC522::PICC_GetTypeName(type)).c_str());
} }
int good_key_index = -1;
for (uint8_t sector=1; sector<sectors; sector++) { for (uint8_t sector=1; sector<sectors; sector++) {
uint8_t blocks = (sector < 32) ? 4 : 16; uint8_t blocks = (sector < 32) ? 4 : 16;
uint8_t block_offset = (sector < 32) ? sector * 4 : 128 + (sector - 32) * 16; uint8_t block_offset = (sector < 32) ? sector * 4 : 128 + (sector - 32) * 16;
MFRC522::StatusCode status; MFRC522::StatusCode status;
status = _rfid->PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, block_offset, &key, &_rfid->uid);
if (status != MFRC522::STATUS_OK) {
DEBUG("PCD_Authenticate() for sector %d failed: %s\n", sector, String(_rfid->GetStatusCodeName(status)).c_str());
continue;
}
for (int i=0; i<8; i++) {
MFRC522::MIFARE_Key *k = &keys[i];
TRACE("Trying MIFARE key %02X %02X %02X %02X %02X %02X...\n", k->keyByte[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; block<blocks-1; block++) { for (uint8_t block=0; block<blocks-1; block++) {
byte buffer[18]; byte buffer[18];
uint8_t byte_count = 18; uint8_t byte_count = 18;
@ -143,11 +195,12 @@ String Controller::_read_rfid_data() {
} }
} }
} }
}
_rfid->PICC_HaltA(); _rfid->PICC_HaltA();
_rfid->PCD_StopCrypto1(); _rfid->PCD_StopCrypto1();
DEBUG("Data from RFID: %s", data.c_str()); DEBUG("Data from RFID: %s\n", data.c_str());
_spi->select_rc522(false); SPIMaster::select_rc522(false);
return data; return data;
} }
@ -169,16 +222,15 @@ void Controller::_check_serial() {
void Controller::_execute_serial_command(String cmd) { void Controller::_execute_serial_command(String cmd) {
DEBUG("Executing command: %s\n", cmd.c_str()); DEBUG("Executing command: %s\n", cmd.c_str());
if (cmd.equals("ls")) { if (cmd.startsWith("play ")) {
_execute_command_ls("/"); Playlist* p = _pm->get_playlist_for_id(cmd.substring(5));
} else if (cmd.startsWith("ls ")) { _player->play(p);
_execute_command_ls(cmd.substring(3)); //} else if (cmd.equals("ls")) {
} else if (cmd.equals("play")) { // _execute_command_ls("/");
_player->play_random_album(); //} else if (cmd.startsWith("ls ")) {
} else if (cmd.startsWith("play ")) { // _execute_command_ls(cmd.substring(3));
_player->play_id(cmd.substring(5)); //} else if (cmd.equals("play")) {
} else if (cmd.startsWith("sys ")) { // _player->play_random_album();
_player->play_system_sound(cmd.substring(4));
} else if (cmd.equals("stop")) { } else if (cmd.equals("stop")) {
_player->stop(); _player->stop();
} else if (cmd.equals("help")) { } else if (cmd.equals("help")) {
@ -192,7 +244,7 @@ void Controller::_execute_serial_command(String cmd) {
} else if (cmd.equals("n")) { } else if (cmd.equals("n")) {
_player->track_next(); _player->track_next();
} else if (cmd.equals("ids")) { } else if (cmd.equals("ids")) {
_execute_command_ids(); _pm->dump_ids();
} else { } else {
ERROR("Unknown command: %s\n", cmd.c_str()); ERROR("Unknown command: %s\n", cmd.c_str());
} }
@ -201,25 +253,19 @@ void Controller::_execute_serial_command(String cmd) {
void Controller::_execute_command_ls(String path) { void Controller::_execute_command_ls(String path) {
INFO("Listing contents of %s:\n", path.c_str()); INFO("Listing contents of %s:\n", path.c_str());
std::list<String> files = _player->ls(path); // TODO
for(std::list<String>::iterator it=files.begin(); it!=files.end(); ++it) { //std::list<String> files = _player->ls(path);
INFO(" %s\n", (*it).c_str()); //for(std::list<String>::iterator it=files.begin(); it!=files.end(); ++it) {
} // INFO(" %s\n", (*it).c_str());
} //}
void Controller::_execute_command_ids() {
for (std::map<String, String>::iterator it = _player->id_to_folder_map.begin(); it!=_player->id_to_folder_map.end(); ++it) {
INFO(" %s -> %s\n", it->first.c_str(), it->second.c_str());
}
} }
void Controller::_execute_command_help() { void Controller::_execute_command_help() {
INFO("Valid commands are:"); INFO("Valid commands are:");
INFO(" help - Displays this help\n"); INFO(" help - Displays this help\n");
INFO(" ls [dir] - Lists the contents of [dir] or, if not given, of /\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(" ids - Lists all known ID-to-folder mappings\n");
INFO(" play [id] - Plays the album with the given id\n"); INFO(" play [id] - Plays the album with the given id\n");
INFO(" sys [file]- Plays the file as system sound\n");
INFO(" stop - Stops playback\n"); INFO(" stop - Stops playback\n");
INFO(" - / + - Decrease or increase the volume\n"); INFO(" - / + - Decrease or increase the volume\n");
INFO(" p / n - Previous or next track\n"); INFO(" p / n - Previous or next track\n");
@ -227,13 +273,21 @@ void Controller::_execute_command_help() {
void Controller::_check_buttons() { void Controller::_check_buttons() {
if (BTN_PREV() && _debounce_button(0)) { if (BTN_PREV() && _debounce_button(0)) {
if (_state == NORMAL) {
_player->track_prev(); _player->track_prev();
} else {
DEBUG("Ignoring btn_prev because state is LOCKED.\n");
}
} else if (BTN_VOL_UP() && _debounce_button(1)) { } else if (BTN_VOL_UP() && _debounce_button(1)) {
_player->vol_up(); _player->vol_up();
} else if (BTN_VOL_DOWN() && _debounce_button(2)) { } else if (BTN_VOL_DOWN() && _debounce_button(2)) {
_player->vol_down(); _player->vol_down();
} else if (BTN_NEXT() && _debounce_button(3)) { } else if (BTN_NEXT() && _debounce_button(3)) {
if (_state == NORMAL) {
_player->track_next(); _player->track_next();
} else {
DEBUG("Ignoring btn_next because state is LOCKED.\n");
}
} }
} }
@ -252,13 +306,14 @@ String Controller::get_status_json() {
response.concat(_player->is_playing() ? "playing" : "idle"); response.concat(_player->is_playing() ? "playing" : "idle");
response.concat("\", "); response.concat("\", ");
if (_player->is_playing()) { if (_player->is_playing()) {
response.concat("\"album\": \""); // TODO
response.concat(_player->album()); //response.concat("\"album\": \"");
response.concat("\", \"track\": "); //response.concat(_player->album());
response.concat(_player->track()); //response.concat("\", \"track\": ");
response.concat(", \"position\": "); //response.concat(_player->track());
response.concat(_player->position()); //response.concat(", \"position\": ");
response.concat(", "); //response.concat(_player->position());
//response.concat(", ");
} }
response.concat("\"volume\": "); response.concat("\"volume\": ");
response.concat(_player->volume()); response.concat(_player->volume());

View File

@ -8,10 +8,12 @@
#include "spi_master.h" #include "spi_master.h"
#include "http_server.h" #include "http_server.h"
#include "mqtt_client.h" #include "mqtt_client.h"
#include "playlist_manager.h"
#include <ESP8266FtpServer.h> #include <ESP8266FtpServer.h>
Controller* controller; Controller* controller;
Player* player; Player* player;
PlaylistManager* pm;
//HTTPServer* http_server; //HTTPServer* http_server;
FtpServer* ftp_server; FtpServer* ftp_server;
MQTTClient* mqtt_client; MQTTClient* mqtt_client;
@ -33,6 +35,7 @@ void setup() {
DEBUG("Setting up SPI...\n"); DEBUG("Setting up SPI...\n");
SPI.begin(); SPI.begin();
SPI.setHwCs(false); SPI.setHwCs(false);
SPIMaster::init();
SPIMaster* spi = new SPIMaster(); SPIMaster* spi = new SPIMaster();
INFO("SPI initialized.\n"); INFO("SPI initialized.\n");
@ -45,9 +48,13 @@ void setup() {
} }
spi->select_sd(false); spi->select_sd(false);
DEBUG("Initializing PlaylistManager...\n");
pm = new PlaylistManager();
DEBUG("done.\n");
DEBUG("Initializing Player and Controller...\n"); DEBUG("Initializing Player and Controller...\n");
player = new Player(spi); player = new Player(spi);
controller = new Controller(player, spi); controller = new Controller(player, pm);
INFO("Player and controller initialized.\n"); INFO("Player and controller initialized.\n");
DEBUG("Connecting to wifi \"%s\"...\n", WIFI_SSID); DEBUG("Connecting to wifi \"%s\"...\n", WIFI_SSID);

View File

@ -15,8 +15,6 @@ Player::Player(SPIMaster* s) {
_spi->disable(); _spi->disable();
PIN_VS1053_DREQ_SETUP(); PIN_VS1053_DREQ_SETUP();
_fill_id_to_folder_map();
_init(); _init();
} }
@ -70,14 +68,6 @@ void Player::_init() {
INFO("VS1053 initialization completed.\n"); INFO("VS1053 initialization completed.\n");
INFO("Checking system sounds...\n");
_spi->select_sd();
_check_system_sound("no_prev_song.mp3");
_check_system_sound("no_next_song.mp3");
_check_system_sound("volume_max.mp3");
_check_system_sound("volume_min.mp3");
_spi->select_sd(false);
_state = idle; _state = idle;
} }
@ -149,15 +139,6 @@ void Player::_record() {
_state = recording; _state = recording;
} }
void Player::_check_system_sound(String filename) {
String path = String("/system/") + filename;
if (!SD.exists(path)) {
ERROR("System sound %s is missing on the sd card!\n", path.c_str());
} else {
DEBUG("%s found.\n", path.c_str());
}
}
inline void Player::_wait() { inline void Player::_wait() {
while(!PIN_VS1053_DREQ()); while(!PIN_VS1053_DREQ());
} }
@ -476,15 +457,15 @@ void Player::set_volume(uint8_t vol, bool save) {
} }
void Player::vol_up() { void Player::vol_up() {
if (_volume == VOLUME_MAX) play_system_sound("volume_max.mp3"); uint8_t vol = _volume + VOLUME_STEP;
else if (_volume + VOLUME_STEP > VOLUME_MAX) set_volume(VOLUME_MAX); if (vol > VOLUME_MAX) vol=VOLUME_MAX;
else set_volume(_volume + VOLUME_STEP); set_volume(vol);
} }
void Player::vol_down() { void Player::vol_down() {
if (_volume >= VOLUME_MIN + VOLUME_STEP) set_volume(_volume - VOLUME_STEP); uint8_t vol = _volume - VOLUME_STEP;
else if (_volume == VOLUME_MIN) play_system_sound("volume_min.mp3"); if (vol < VOLUME_MIN) vol=VOLUME_MIN;
else set_volume(VOLUME_MIN); set_volume(vol);
} }
void Player::_mute() { void Player::_mute() {
@ -501,26 +482,27 @@ void Player::_unmute() {
void Player::track_next() { void Player::track_next() {
if (_state != playing) return; if (_state != playing) return;
if (_playing_index + 1 >= _playing_album_songs) { if (!_current_playlist->has_track_next()) {
play_system_sound("no_next_song.mp3");
return; return;
} }
stop(); stop();
play_song(_playing_album, _playing_index + 1); _current_playlist->track_next();
play();
} }
void Player::track_prev() { void Player::track_prev() {
if (_state != playing) return; if (_state != playing) return;
if (_current_play_position > 100000) { if (_current_play_position > 100000) {
stop(); stop();
play_song(_playing_album, _playing_index); _current_playlist->track_restart();
play();
} else { } else {
if (_playing_index == 0) { if (!_current_playlist->has_track_prev()) {
play_system_sound("no_prev_song.mp3");
return; return;
} }
stop(); stop();
play_song(_playing_album, _playing_index - 1); _current_playlist->track_prev();
play();
} }
} }
@ -528,195 +510,21 @@ bool Player::is_playing() {
return _state == playing; return _state == playing;
} }
std::list<String> Player::ls(String path, bool withFiles, bool withDirs, bool withHidden) { bool Player::play(Playlist* p) {
_spi->select_sd(); _current_playlist = p;
std::list<String> result; return play();
if (!SD.exists(path)) return result;
File dir = SD.open(path);
File entry;
while (entry = dir.openNextFile()) {
if (!withDirs && entry.isDirectory()) continue;
if (!withFiles && !entry.isDirectory()) continue;
String filename = entry.name();
if (!withHidden && filename.startsWith(".")) continue;
if (entry.isDirectory()) filename.concat("/");
result.push_back(filename);
}
_spi->select_sd(false);
result.sort();
return result;
} }
String Player::_find_album_dir(String id) { bool Player::play() {
_spi->select_sd();
if (id.endsWith("/")) id = id.substring(0, id.length() - 1);
String id_with_divider = id + " - ";
File root = SD.open("/");
File entry;
String result = String("");
while ((result.length()==0) && (entry = root.openNextFile())) {
String name = entry.name() + 1;
TRACE("Checking if '%s' startsWith '%s'...\n", name.c_str(), id.c_str());
if (entry.isDirectory() && (name.startsWith(id_with_divider) || name.equals(id))) {
result = name;
}
entry.close();
}
root.close();
_spi->select_sd(false);
return result;
}
std::list<String> Player::_files_in_dir(String path) {
_spi->select_sd();
TRACE("Examining folder %s...\n", path.c_str());
if (!path.startsWith("/")) path = String("/") + path;
//if (!path.endsWith("/")) path.concat("/");
std::list<String> result;
if (!SD.exists(path)) {
DEBUG("Could not open path '%s'.\n", path.c_str());
_spi->select_sd(false);
return result;
}
File dir = SD.open(path);
File entry;
while (entry = dir.openNextFile()) {
String filename = entry.name();
filename = filename.substring(path.length() + 1);
if (!entry.isDirectory() &&
!filename.startsWith(".") &&
( filename.endsWith(".mp3") ||
filename.endsWith(".ogg") ||
filename.endsWith(".wma") ||
filename.endsWith(".mp4") ||
filename.endsWith(".mpa"))) {
TRACE(" Adding entry %s\n", entry.name());
result.push_back(entry.name());
} else {
TRACE(" Ignoring entry %s\n", filename.c_str());
}
entry.close();
}
dir.close();
_spi->select_sd(false);
result.sort();
return result;
}
void Player::_fill_id_to_folder_map() {
DEBUG("_fill_id_to_folder_map() running...");
_spi->select_sd();
File root = SD.open("/");
File entry;
while (entry = root.openNextFile()) {
String foldername = entry.name();
// Remove trailing slash
foldername.remove(foldername.length());
TRACE("Looking at %s...\n", foldername.c_str());
if (!entry.isDirectory() || foldername.startsWith("/.")) continue;
if (!SD.exists(foldername + "/ids.txt")) {
TRACE("Folder %s does not contain ids.txt -> ignoring\n", foldername.c_str());
continue;
}
TRACE("Reading contents of %s...\n", (foldername + "/ids.txt").c_str());
File f = SD.open(foldername + "/ids.txt");
String buffer = "";
while (f.available()) {
char c = f.read();
if (c=='\n' || c=='\r') {
if (buffer.length() > 0) {
id_to_folder_map[buffer] = foldername;
DEBUG("Adding mapping '%s'=>'%s'\n", buffer.c_str(), foldername.c_str());
buffer = "";
}
} else {
buffer.concat(c);
}
}
f.close();
if (buffer.length() > 0) {
id_to_folder_map[buffer] = foldername;
DEBUG("Adding mapping '%s'=>'%s'\n", buffer.c_str(), foldername.c_str());
}
entry.close();
}
root.close();
DEBUG("fill_id_to_folder_map done.\n");
_spi->select_sd(false);
}
String Player::_random_album() {
std::list<String> albums = ls("/", false, true, false);
uint8_t rnd = random(albums.size());
std::list<String>::iterator it = albums.begin();
for (int i=0; i<rnd; i++) { it++; }
return *it;
}
void Player::play_random_album() {
play_album(_random_album());
}
bool Player::play_id(String id) {
String folder = _foldername_for_id(id);
if (folder.length()==0) return false;
return play_album(folder);
}
String Player::_foldername_for_id(String id) {
DEBUG("Searching for id %s...\n", id.c_str());
std::map<String, String>::iterator it = id_to_folder_map.find(id);
if (it != id_to_folder_map.end()) {
DEBUG("Found folder '%s' for id %s.\n", it->first.c_str(), it->second.c_str());
return it->second;
}
DEBUG("No folder found for id %s.\n", id.c_str());
return "";
}
bool Player::play_album(String album) {
album_state s = _last_tracks[album.c_str()];
DEBUG("Last index for album '%s' was %d,%d\n", album.c_str(), s.index, s.position);
return play_song(album, s.index, s.position);
}
bool Player::play_song(String album, uint8_t index, uint32_t skip_to) {
if (_state == sleeping || _state == recording) _wakeup(); if (_state == sleeping || _state == recording) _wakeup();
if (_state != idle) return false; if (_state != idle) return false;
DEBUG("Trying to play song at index %d, offset %d of album %s\n", index, skip_to, album.c_str()); String file = _current_playlist->get_current_file();
std::list<String> files = _files_in_dir(album); uint32_t position = _current_playlist->get_position();
_playing_album_songs = files.size();
DEBUG("Found %d songs in album\n", files.size());
if (index >= files.size()) {
ERROR("No matching file found - not playing.\n");
return false;
}
String file = *(std::next(files.begin(), index));
_state = playing; _state = playing;
_playing_album = album; _play_file(file, position);
_playing_index = index;
_set_last_track(album.c_str(), index, skip_to);
_play_file(file, skip_to);
return true; return true;
} }
void Player::play_system_sound(String filename) {
String file = filename;
if (!SD.exists(file)) {
ERROR("File %s does not exist!\n", file.c_str());
return;
}
if (_state == playing) {
stop();
_state = system_sound_while_playing;
} else {
_state = system_sound_while_stopped;
}
_play_file(file, 0);
}
void Player::_play_file(String file, uint32_t file_offset) { void Player::_play_file(String file, uint32_t file_offset) {
INFO("play_file('%s', %d)\n", file.c_str(), file_offset); INFO("play_file('%s', %d)\n", file.c_str(), file_offset);
_spi->select_sd(); _spi->select_sd();
@ -797,11 +605,10 @@ void Player::_finish_playing() {
} }
void Player::stop(bool turn_speaker_off) { void Player::stop(bool turn_speaker_off) {
if (_state != playing /* && _state != system_sound_while_playing && _state != system_sound_while_stopped*/) return; if (_state != playing) return;
INFO("Stopping...\n"); INFO("Stopping...\n");
if (_state == playing) { _current_playlist->set_position(_current_play_position);
_set_last_track(_playing_album.c_str(), _playing_index, (uint32_t)_file.position());
}
_state = stopping; _state = stopping;
_stop_delay = 0; _stop_delay = 0;
_write_control_register(SCI_MODE, _read_control_register(SCI_MODE) | SM_CANCEL); _write_control_register(SCI_MODE, _read_control_register(SCI_MODE) | SM_CANCEL);
@ -842,18 +649,12 @@ void Player::_refill() {
DEBUG("EOF reached.\n"); DEBUG("EOF reached.\n");
_skip_to = 0; _skip_to = 0;
_finish_playing(); _finish_playing();
if (_state == system_sound_while_playing) {
_finish_stopping(false); _finish_stopping(false);
play_album(_playing_album); if (_current_playlist->has_track_next()) {
return; _current_playlist->track_next();
} else if (_state == system_sound_while_stopped) { play();
_finish_stopping(true); } else {
return; _current_playlist->reset();
}
_finish_stopping(false);
bool result = play_song(_playing_album, _playing_index + 1);
if (!result) {
_set_last_track(_playing_album.c_str(), 0, 0);
} }
return; return;
} }
@ -880,10 +681,7 @@ void Player::_refill() {
} }
bool Player::_refill_needed() { bool Player::_refill_needed() {
return _state==playing || return _state==playing || _state==stopping;
_state==stopping ||
_state==system_sound_while_playing ||
_state==system_sound_while_stopped;
} }
bool Player::loop() { bool Player::loop() {
@ -914,8 +712,3 @@ bool Player::loop() {
} }
return false; return false;
} }
void Player::_set_last_track(const char* album, uint8_t index, uint32_t position) {
DEBUG("Setting _last_track[%s]=%d,%d.\n", album, index, position);
_last_tracks[album] = {index, position};
}

108
src/playlist.cpp Normal file
View File

@ -0,0 +1,108 @@
#include <playlist.h>
#include "spi_master.h"
#include "config.h"
#include <SD.h>
#include <algorithm>
Playlist::Playlist(String path) {
// Add files to _files
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;
}
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());
_files.push_back(entry.name());
} else {
TRACE(" Ignoring entry %s\n", filename.c_str());
}
entry.close();
}
dir.close();
SPIMaster::select_sd(false);
std::sort(_files.begin(), _files.end());
}
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;
}
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);
String temp = _files[i];
_files[i] = _files[j];
_files[j] = temp;
}
}
_shuffled = true;
TRACE("Done.\n");
}
void Playlist::reset() {
std::sort(_files.begin(), _files.end());
_current_track = 0;
_position = 0;
_shuffled = false;
}
String Playlist::get_current_file() {
return _files[_current_track];
}
uint32_t Playlist::get_position() {
return _position;
}
void Playlist::set_position(uint32_t p) {
_position = p;
}
bool Playlist::is_fresh() {
return !_shuffled && _position==0 && _current_track==0;
}

60
src/playlist_manager.cpp Normal file
View File

@ -0,0 +1,60 @@
#include "playlist_manager.h"
#include <SD.h>
#include "spi_master.h"
PlaylistManager::PlaylistManager() {
SPIMaster::select_sd();
File root = SD.open("/");
File entry;
while (entry = root.openNextFile()) {
String foldername = entry.name();
// Remove trailing slash
foldername.remove(foldername.length());
TRACE("Looking at %s...", foldername.c_str());
if (!entry.isDirectory() || foldername.startsWith("/.")) continue;
if (!SD.exists(foldername + "/ids.txt")) {
TRACE("No ids.txt -> ignoring\n");
continue;
}
File f = SD.open(foldername + "/ids.txt");
String buffer = "";
if (f.available()) {
do {
char c = f.read();
if (!f.available() && c!='\n' && c!='\r') {
buffer.concat(c);
c='\n';
}
if (c=='\n' || c=='\r') {
if (buffer.length() > 0) {
_map[buffer] = foldername;
TRACE(" ID %s", buffer.c_str());
buffer="";
}
} else {
buffer.concat(c);
}
} while(f.available());
}
f.close();
entry.close();
}
root.close();
SPIMaster::select_sd(false);
}
Playlist* PlaylistManager::get_playlist_for_id(String id) {
if (!_map.count(id)) return NULL;
String folder = _map[id];
if (!_playlists.count(folder)) {
_playlists[folder] = new Playlist(folder);
}
return _playlists[folder];
}
void PlaylistManager::dump_ids() {
for (std::map<String, String>::iterator it = _map.begin(); it!=_map.end(); it++) {
INFO(" %s -> %s\n", it->first.c_str(), it->second.c_str());
}
}