112 Commits
0.1 ... master

Author SHA1 Message Date
ab51af637e Merge branch 'feature/rewrite-i2s' 2022-08-25 14:53:43 +02:00
913a64d465 Die Position in einer Playlist beilbt beibehalten, solange die Papabox nicht neu gestartet wird. 2022-08-25 14:52:12 +02:00
b2cf9d6277 Systeminfos werden via mp3-Dateien in /system abgespielt. 2022-08-25 14:51:18 +02:00
076a6993c7 FTP-Server for managing the contents of the sd card. 2022-08-25 14:48:25 +02:00
13e62fea19 Shuffle the playlist before playing if "[random]" is in the rfid data. 2022-08-22 13:59:00 +02:00
15a65f7391 Added reaing of RFID contents. 2022-08-22 13:58:22 +02:00
b9df55012f Better handling of eof_mp3 events: Keep playing as long as there is data remaining. 2022-08-22 13:57:46 +02:00
fb6b5bced6 Test for button press every 50ms. That's more than enough. 2022-08-22 13:56:43 +02:00
3718f45983 Loglevel up to full debugging. ;-) 2022-08-22 13:55:57 +02:00
7dcb0cb673 Allocate a bigger (30KByte) Buffer for Audio. 2022-08-22 08:52:09 +02:00
6989248970 Less debugging messages. 2022-08-22 08:51:29 +02:00
cf433a48b2 Support for http(s) links in _mapping.txt 2022-08-22 08:51:19 +02:00
9a39b00a65 Lautstärke-Änderungen in 2er-Schritten. 2022-08-21 17:42:58 +02:00
aed9c416bf Standard-Lautstärke 12 statt 3. 2022-08-21 17:40:50 +02:00
2908d23e60 Alten Code gelöscht. 2022-08-21 17:40:32 +02:00
3272921db2 Daten von RFID-Karten (versuchen zu) lesen. 2022-08-21 17:40:22 +02:00
6ddf1efd62 Prevent playing prev_track if there are no tracks. 2022-08-21 15:27:00 +02:00
45dfe0cfe0 Don't read all dirs at startup. 2022-08-21 15:26:29 +02:00
4840c150c2 Button handling implemented. 2022-08-21 14:00:52 +02:00
9c31f70c57 It's working more or less... 2022-08-21 11:37:42 +02:00
978b25c34d Started rewrite for using an I2S amplifier with less unnecessary features. 2022-08-19 10:37:03 +02:00
dcca828197 Kleine Verbesserungen. 2022-08-18 16:38:07 +02:00
fa208858d9 Webinterface: Added an overlay to display when the websocket isn't connected. 2019-12-04 05:59:52 +01:00
6d452ecbc0 Added tracing stuff to RSSParser. 2019-12-04 05:58:25 +01:00
23fbddb055 TinyXML is broken, but I couldn't find an alternative nor fix it. So the code now is pretty hack-y to work around the bugs. 2019-12-04 05:57:58 +01:00
fe2a209e44 Debug and Trace modes can now be (de)activated via API commands and are persisted across reboots. 2019-11-30 13:53:50 +01:00
82905a8cdd Fixed M3U parser for last lines ending without a newline. 2019-11-30 13:38:34 +01:00
3751904cb4 Start with speakers off. 2019-11-30 13:37:35 +01:00
bcf7625285 deploy.sh: Fix calculation error. 2019-11-30 00:14:02 +01:00
4a3e79f02e Added empty (for now) update.manifest. 2019-11-30 00:12:02 +01:00
68e1073858 Deploy.sh: Don't show the commands being executed. 2019-11-30 00:09:36 +01:00
f73d45404f Merge branch 'develop' 2019-11-30 00:06:56 +01:00
ecc7c46b8d Playlist: Restrict RSS feeds to the newest 20 entries. Keep them in the correct order, starting with the last (newest) one. 2019-11-29 21:25:17 +01:00
0dd5937707 Player: Prevent overflows in vol_up() and vol_down(). 2019-11-29 21:24:41 +01:00
547080acf5 Main: Fix WiFi connection stuff for multiple WiFis. 2019-11-29 21:24:10 +01:00
d3c699aefa Webinterface: Typo fix. 2019-11-29 21:23:38 +01:00
a8d19cd6e1 HTTPClientWrapper: Initialize buffer variables. D'Oh. 2019-11-29 21:23:01 +01:00
38d48ab0e4 Controller: Only read sector 1 from RFID cards. That's big enough and much faster. 2019-11-29 21:22:35 +01:00
51bef05465 Fixed advent mode and persistence stuff. 2019-11-29 21:20:19 +01:00
4eef69516e Made TRACE a bit less talkative. 2019-11-29 21:18:28 +01:00
9175193b67 Disabled the custom partitioning stuff because the ESP32 entered a boot loop after flashing it. So we are back at ~85% memory used... 2019-11-29 21:16:10 +01:00
65118fbc42 Play position in stuff like podcasts can now be permanently persisted. 2019-11-29 17:41:16 +01:00
076f0e9dfd Changed the baud rate of the serial port to 115200 to match the ESP32 boot loader. 2019-11-29 06:14:45 +01:00
571e969bc4 deploy.sh: Show last tags. 2019-11-29 06:13:16 +01:00
8e15f87cd3 Moved index.html from SPIFFS to program memory. Removed dependencies to SPIFFS. Also, we can use a different partition scheme with much more program space instead of reserving some of it for SPIFFS. 2019-11-29 06:10:17 +01:00
dd9e1538c8 Playlist: More defensive proramming for when trying to play an empty playlist. 2019-11-29 05:52:00 +01:00
001e275131 Include information about versions and WiFi connection to controller's json. 2019-11-29 05:46:26 +01:00
196021bef5 Try to (re)connect to WiFi every 5 minutes. (Only when not playing at the moment.) 2019-11-29 05:45:21 +01:00
63b9616677 Removed "redefined macro" warnings from MFRC522 library. 2019-11-29 05:36:59 +01:00
d4c9a6d582 Documented API commands. 2019-11-29 05:36:22 +01:00
5fe66fdaef Added a command update to run the update check. 2019-11-29 05:35:47 +01:00
6445dc0fb8 Unly call Updater on startup if the SPIFFS needs an update. 2019-11-29 05:32:22 +01:00
7a20cf4b04 Better deploy script. 2019-11-29 05:29:01 +01:00
bbf77c6b1e Added deploy script. 2019-11-28 19:26:35 +01:00
b805d1b183 Updater: Better flow for performing updates; added MD5 validation. 2019-11-28 06:48:16 +01:00
07b1ea3a5c Added Updater to automatically perform OTA updates without user interaction. 2019-11-28 06:42:30 +01:00
3b0410f560 Merge branch 'feature-webstreams' 2019-11-28 06:30:16 +01:00
8f19b990ff PlaylistManager: Only search for folders, don't try it with webstreams. 2019-11-28 06:29:51 +01:00
519ac0e3bd Playlist: Album title now gets handled better. 2019-11-28 06:20:57 +01:00
651843fb06 Webinterface: Fixed usage of filenames as titles. 2019-11-28 06:20:07 +01:00
fcbbdce118 Playlist: Initialization of PlaylistEntries now uses designators. 2019-11-28 06:19:11 +01:00
6f8683ba9d WIP: Lots of streaming stuff 2019-11-27 06:51:20 +01:00
710b8a2cdc Add UserAgent, remove superfluous form of location mapping. 2019-11-20 06:17:18 +01:00
b989784fb9 You can now also play MP3s streamed from the internet. (Very rough & wonky code. More or less proof-of-concept right now.) 2019-11-20 06:13:15 +01:00
94489618ca Moved reading of SD card data into a dedicated class DataSource. 2019-11-20 05:04:27 +01:00
82d8f07eea Player: Only change volume and report a position if we are actually playing something. 2019-11-19 20:48:43 +01:00
20041dd483 Extended http_server to provide new endpoints:
/_mapping.txt, /player.json, /playlist_manager.json, /controller.json and /position.json to get the matching data as well as /cmd to send commands to.
2019-11-19 20:48:11 +01:00
4f9174d362 PlaylistManager: Extracted create_mapping_txt from _save_mapping. 2019-11-19 20:46:54 +01:00
68ecc05712 Made player and playlist_manager pubilc members of Controller. 2019-11-19 20:46:04 +01:00
5fad39ee0e Added File System Upload step to installation instructions. 2019-11-19 20:44:03 +01:00
01f513c97b More typographic fixes. 2019-11-17 19:34:38 +01:00
3bfbea92d8 Typo. -_- 2019-11-17 19:33:25 +01:00
d818624287 Added much more info to README.md 2019-11-17 19:31:59 +01:00
d92388d11f Add config.h to .gitignore. 2019-11-17 17:39:50 +01:00
37df309127 Removed MQTT client. Was more or less unused, anyways. 2019-11-17 17:38:41 +01:00
be8a124803 Prepared config.sample.h for making it public. 2019-11-17 17:31:52 +01:00
104236dd0c Remove old files from .vscode. 2019-11-17 14:27:43 +01:00
e1dd004cf5 Reload folder mappings after modifying a mapping. 2019-11-17 14:26:23 +01:00
b5ec78ab41 When chaging to another track, play it from the beginning. 2019-11-17 14:25:47 +01:00
fff9d9bc61 Small fix. 2019-11-17 14:25:22 +01:00
ef47c771ef You can now get and set the state of all CS pins at once. 2019-11-17 14:24:01 +01:00
9f442259e9 Worked on the upload stuff. It works, but the SD card seems to die after a few MB... :-/ 2019-11-17 14:23:17 +01:00
8e5a3195b9 Scrollable modals, working add_mapping feature. 2019-11-17 14:22:22 +01:00
cc4729eb6b Ignore .vscode folder. 2019-11-17 14:21:39 +01:00
f7c4b0d70a Removed timestamp from build_version.sh cause it forced a complete recompile of everything, all the time. 2019-11-17 14:20:55 +01:00
566068f7cd Increased the SD card speed from 4 MHz to 25 MHz for faster directory reading. 2019-11-17 00:35:55 +01:00
5c15a7d4cb index.html is now served from SPIFFS. You can add rfid tag -> folder mappings via the webinterface. And I've added the missing controller json data messages. 2019-11-17 00:35:23 +01:00
b9a4770ff2 Massive changes. Added a quite nice webinterface with live data using WebSockets. Removed the FTP server (wasn't that useful anyways). JSON creating using ArduinoJson instead of String concatenation. Ans, and, and. 2019-11-16 23:03:13 +01:00
e471a57578 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. 2019-11-14 20:42:02 +01:00
6e05900b5a More changes for the recording... It's still not working. -_- 2019-11-14 06:50:13 +01:00
15f6d78128 RFID reading now includes RFID data, enabling you to save additional data (still to come...) to the rfid card. 2019-11-14 06:48:21 +01:00
b32f7d1228 Experimental code to read RFID card data. 2019-11-13 20:14:09 +01:00
45fef23bad Another try in getting the recording stuff to work... 2019-11-13 20:13:52 +01:00
e20e6b7d3e Added serial command ids; id-to-folder mapping now works. 2019-11-13 06:51:38 +01:00
0531b599fe Internal changes. 2019-11-13 06:50:29 +01:00
a5751eec79 Merge branch 'master' of https://git.schle.nz/fabian/esmp3 2019-11-12 19:31:31 +01:00
e02b8571f6 Tries to get the recording functionality to work. As of yet it's not working. :-/ 2019-11-12 19:31:26 +01:00
303a8d3877 Removed old MCP23017 pin definitions. 2019-11-12 19:30:42 +01:00
6d00474315 ID of album to play now comes from a file ids.txt within the folder. This is as of yet untested. 2019-11-12 19:29:55 +01:00
46fb4c7615 Modified build_version.sh to also include date and time of the build. 2019-11-12 19:29:03 +01:00
48c93ed043 Added recording mode to detect volumes. 2019-11-11 17:40:47 +01:00
2d1f049444 Changed from ESP8266 to ESP32. Works much better.
HTTP server is disabled (for now).
2019-11-11 05:32:41 +01:00
c313f6eb72 Changed from MCP23S17 to MCP23017. Lots of changes.
Doesn't really work because of timing stuff.
2019-11-10 14:45:33 +01:00
cccdc9cedb Merge branch 'master' of https://git.schle.nz/fabian/esmp3 2019-09-03 06:07:07 +02:00
429979c6d1 Start Wifi after initializing VS1053 - faster bootup when multiple tries setting up VS1053 are needed. 2019-09-03 06:06:51 +02:00
e28a541fe9 Better default volumes. 2019-09-03 06:05:26 +02:00
0f2b8c6564 Fixed LIST command of FTP server. It now returns something akin to 'ls -la'. 2019-08-21 20:50:27 +02:00
5f682c303f Added MQTT client, better speaker handling, fixed bug in FTP server, ... 2019-08-14 21:01:01 +02:00
dcbb42f5ef Add code for switching the speaker respective their amps on or off. 2019-08-14 06:36:26 +02:00
235ef8c39d Added FTP server and automatic version generation. 2019-08-13 19:39:03 +02:00
4d59c66354 Format changes. 2019-08-13 06:16:08 +02:00
25fa963752 Added sleep mode for VS1053, HTTP server, tar upload, JSON status, RFID card removal debouncing, ... 2019-08-12 20:15:00 +02:00
22 changed files with 770 additions and 846 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
.pio
.pioenvs
.piolibdeps
.vscode
include/config.h

3
DEPLOY.md Normal file
View File

@ -0,0 +1,3 @@
# Deploying a new version
* Use `deploy.sh`.

8
README.md Normal file
View File

@ -0,0 +1,8 @@
# ESMP3
## Audio files
System messages are created using:
* https://ttsmp3.com/
* German / Vicki
* "Dies ist ein Text.<break time="1s"/>"
* Download as MP3.

3
bin/update.manifest Normal file
View File

@ -0,0 +1,3 @@
VERSION=1
IMAGE_PATH=https://files.schle.nz/esmp3/firmware.bin
IMAGE_MD5=00000000000000000000000000000000

2
build_version.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
echo -n "-DVERSION=\\\"`git describe --tags --dirty`\\\""

53
deploy.sh Executable file
View File

@ -0,0 +1,53 @@
#!/usr/bin/env bash
#set -x
set -e
if ! git diff-index --quiet HEAD ; then
echo "Git isn't clean. Cant deploy."
exit 1
fi
branch_name=$(git symbolic-ref -q HEAD)
branch_name=${branch_name##refs/heads/}
branch_name=${branch_name:-HEAD}
if [ "$branch_name" != "master" ]; then
echo "We are not on master branch. Can't deploy."
exit 1
fi
echo
echo
echo "Last tags:"
vers=`git tag --sort=-version:refname | head -n 5`
while read version; do
echo " $version"
done <<< "$vers"
read -p "Version to generate: " VERSION
OTA_VERSION=`grep "VERSION=" bin/update.manifest | cut -d"=" -f2`
OTA_VERSION=$(( $OTA_VERSION + 1 ))
sed -i.bak "s/#define OTA_VERSION .*/#define OTA_VERSION $OTA_VERSION/" include/config.h include/config.sample.h
rm include/config.h.bak include/config.sample.h.bak
PLATFORMIO_BUILD_FLAGS='-DVERSION=\"$VERSION\"' pio run -e deploy -t buildprog || exit 1
cp .pio/build/deploy/firmware.bin bin/firmware.bin || exit 1
sed -i.bak "s/VERSION=.*/VERSION=$OTA_VERSION/" bin/update.manifest
MD5=`md5sum --binary bin/firmware.bin | cut -d" " -f1`
sed -i.bak "s/IMAGE_MD5=.*/IMAGE_MD5=$MD5/" bin/update.manifest
rm bin/update.manifest.bak
echo; echo; echo; echo; echo
echo "Please check the git diff, if everything looks okay:"
git diff
read -p "Press ENTER to continue, Ctrl-C to abort. " foo
git add bin/firmware.bin bin/update.manifest
git commit -m "Deploying version $VERSION."
git tag -a -m "Deploying version $VERSION" $VERSION
git push --follow-tags

View File

@ -1,30 +1,32 @@
#pragma once
#include <Arduino.h>
#include "config.h"
#include "player.h"
#include <MFRC522.h>
#include <MCP23S17/MCP23S17.h>
#include <MFRC522v2.h>
#include <MFRC522Debug.h>
#include "esmp3.h"
#include "playlist.h"
class Controller {
private:
MFRC522* _rfid;
MCP* _mcp;
bool _rfid_enabled = true;
void _check_rfid();
void _check_serial();
void _check_buttons();
uint32_t _get_rfid_card_uid();
uint32_t _last_rfid_card_uid = 0;
Player* _player;
unsigned long _last_rfid_scan_at = 0;
String _serial_buffer = String();
void _execute_serial_command(String cmd);
void _execute_command_ls(String path);
void _execute_command_help();
unsigned long _button_last_pressed_at[NUM_BUTTONS];
bool _check_button(uint8_t btn);
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;
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;
String read_rfid_data();
public:
Controller(Player* p, MCP* m);
void loop();
void handle();
void next_track();
void prev_track();
void play();
void play(String rfid_id, bool shuffle=false);
void stop();
void eof_mp3(String info);
};

26
include/esmp3.h Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include "controller.h"
#include "playlist_manager.h"
#include <Audio.h>
#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 17
#define PIN_BTN_TRACK_PREV 16
#define I2S_DOUT 25
#define I2S_BCLK 26
#define I2S_LRC 27
class Controller;
extern Controller controller;
extern Audio audio;
extern PlaylistManager* pm;
extern MFRC522* rfid;
void save_audio_current_time();

View File

@ -0,0 +1,9 @@
#pragma once
struct PersistedPlaylist {
String dir;
uint16_t file = 0;
uint32_t position = 0;
PersistedPlaylist(String s="") : dir(s) {}
};

View File

@ -1,92 +0,0 @@
#pragma once
#include "config.h"
#include <SPI.h>
#include <SD.h>
#include <list>
#include <map>
#include <MCP23S17/MCP23S17.h>
#define SCI_MODE 0x00
#define SCI_STATUS 0x01
#define SCI_CLOCKF 0x03
#define SCI_DECODE_TIME 0x04
#define SCI_VOL 0x0B
#define SCI_WRAMADDR 0x07
#define SCI_WRAM 0x06
#define CMD_WRITE 0x02
#define CMD_READ 0x03
#define ADDR_ENDBYTE 0x1E06
#define SM_CANCEL 0x0008
#define SS_DO_NOT_JUMP 0x8000
#define XRESET PIN_VS1053_XRESET
#define DREQ PIN_VS1053_DREQ
#define XCS PIN_VS1053_XCS
#define XDCS PIN_VS1053_XDCS
class Player {
private:
enum state { uninitialized, idle, playing, stopping,
system_sound_while_playing, system_sound_while_stopped };
struct album_state {
uint8_t index;
uint32_t position;
};
void _check_system_sound(String filename);
void _reset();
void _init();
void _wait();
uint16_t _read_control_register(uint8_t address);
void _write_control_register(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 _set_last_track(const char* album, uint8_t track, uint32_t position);
std::map<String, album_state> _last_tracks;
void _play_file(String filename, uint32_t offset);
uint32_t _id3_tag_offset(File f);
void _finish_playing();
void _finish_stopping();
void _mute();
void _unmute();
SPISettings _spi_settings_slow = SPISettings(250000, MSBFIRST, SPI_MODE0);
SPISettings _spi_settings_fast = SPISettings(4000000, MSBFIRST, SPI_MODE0);
SPISettings* _spi_settings = &_spi_settings_slow;
std::list<String> _files_in_dir(String dir);
String _find_album_dir(String album);
File _file;
uint8_t _buffer[32];
String _playing_album;
uint8_t _playing_index;
uint8_t _playing_album_songs;
uint32_t _current_play_position;
uint _refills;
uint8_t _volume;
uint16_t _stop_delay;
uint32_t _skip_to;
MCP* _mcp;
public:
Player(MCP* m);
void vol_up();
void vol_down();
void track_next();
void track_prev();
bool play_album(String album);
bool play_song(String album, uint8_t song_index, uint32_t offset=0);
void play_system_sound(String filename);
void stop();
bool loop();
void set_volume(uint8_t vol, bool save = true);
std::list<String> ls(String path);
};

30
include/playlist.h Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <vector>
#include <Arduino.h>
#include "persisted_playlist.h"
class Playlist {
private:
std::vector<String> files;
uint8_t current_file = 0;
uint32_t current_time = 0;
String rfid_id;
PersistedPlaylist* pp;
public:
Playlist();
Playlist(String rfid_id, PersistedPlaylist* p);
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();
void shuffle();
void set_current_position(uint8_t file, uint32_t position=0);
void save_current_position(uint32_t position=0);
};

View File

@ -0,0 +1,25 @@
#pragma once
#include <vector>
#include <map>
#include <Arduino.h>
#include "playlist.h"
#include "persisted_playlist.h"
class Playlist;
class PlaylistManager {
private:
Playlist get_playlist_for_tag_id(String id);
String current_rfid_tag_id;
uint32_t audio_current_time = 0;
public:
PlaylistManager();
std::map<String, PersistedPlaylist> 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();
};

View File

@ -1,35 +1,9 @@
#pragma once
#include <Arduino.h>
#include <SPI.h>
#include "config.h"
class SPIMaster {
public:
static void init() {
SPI.setHwCs(false);
pinMode(PIN_SD_CS, OUTPUT);
pinMode(PIN_VS1053_XCS, OUTPUT);
pinMode(PIN_VS1053_XDCS, OUTPUT);
pinMode(PIN_MCP, OUTPUT);
}
static void enable(uint8_t pin) {
digitalWrite(PIN_SD_CS, pin==PIN_SD_CS ? LOW : HIGH);
digitalWrite(PIN_VS1053_XCS, pin==PIN_VS1053_XCS ? LOW : HIGH);
digitalWrite(PIN_VS1053_XDCS, pin==PIN_VS1053_XDCS ? LOW : HIGH);
digitalWrite(PIN_MCP, pin==PIN_MCP ? LOW : HIGH);
}
static void printStatus() {
Serial.printf("CS state: SD:%d, VS1053_XCS:%d, VS1053_XDCS:%d, MCP:%d\n",
digitalRead(PIN_SD_CS),
digitalRead(PIN_VS1053_XCS),
digitalRead(PIN_VS1053_XDCS),
digitalRead(PIN_MCP));
}
static void disable() {
enable(142);
}
static void enable_sd();
static void enable_rfid();
static void disable_all();
static void initialize();
};

6
partitions.csv Normal file
View File

@ -0,0 +1,6 @@
# Custom partition table without SPIFFS.
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x220000,
app1, app, ota_1, 0x230000,0x220000,
1 # Custom partition table without SPIFFS.
2 # Name, Type, SubType, Offset, Size, Flags
3 nvs, data, nvs, 0x9000, 0x5000,
4 otadata, data, ota, 0xe000, 0x2000,
5 app0, app, ota_0, 0x10000, 0x220000,
6 app1, app, ota_1, 0x230000,0x220000,

View File

@ -8,11 +8,35 @@
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:esp12e]
platform = espressif8266
board = esp12e
[platformio]
default_envs = esp32
[extra]
lib_deps =
[env:esp32]
platform = espressif32
board = esp-wrover-kit
framework = arduino
upload_speed = 512000
lib_deps = 63
https://github.com/n0mjs710/MCP23S17.git
upload_port = /dev/cu.wchusbserial1420
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
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 = esp32_exception_decoder
[env:deploy]
platform = espressif32
board = esp-wrover-kit
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

View File

@ -1,168 +1,226 @@
#include "controller.h"
#include "spi_master.h"
#include "esmp3.h"
Controller::Controller(Player* p, MCP* m) {
_player = p;
_mcp = m;
_rfid = new MFRC522(PIN_RC522_CS, MFRC522::UNUSED_PIN);
SPIMaster::enable(PIN_MCP);
_mcp->pinMode(1, INPUT); _mcp->pullupMode(1, HIGH);
_mcp->pinMode(2, INPUT); _mcp->pullupMode(2, HIGH);
_mcp->pinMode(3, INPUT); _mcp->pullupMode(3, HIGH);
_mcp->pinMode(4, INPUT); _mcp->pullupMode(4, HIGH);
SPIMaster::enable(PIN_RC522_CS);
DEBUG("Initializing RC522...");
_rfid->PCD_Init();
#ifdef SHOW_DEBUG
_rfid->PCD_DumpVersionToSerial();
#endif
SPIMaster::disable();
INFO("RC522 initialized.\n");
for (uint8_t i=0; i<NUM_BUTTONS; i++) _button_last_pressed_at[i]=0;
void Controller::handle() {
if (last_rfid_check + 500 < millis() || last_rfid_check > millis()) {
handle_rfid();
last_rfid_check = millis();
}
void Controller::loop() {
unsigned long now = millis();
if ((_last_rfid_scan_at < now - RFID_SCAN_INTERVAL) || (now < _last_rfid_scan_at)) {
_check_rfid();
_last_rfid_scan_at = now;
if (last_button_check + 10 < millis() || last_button_check > millis()) {
handle_buttons();
last_button_check = millis();
}
_check_serial();
_check_buttons();
}
uint32_t Controller::_get_rfid_card_uid() {
SPIMaster::enable(PIN_RC522_CS);
if (!_rfid->PICC_ReadCardSerial()) {
if (!_rfid->PICC_IsNewCardPresent()) {
return 0;
}
if (!_rfid->PICC_ReadCardSerial()) {
return 0;
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());
}
}
uint32_t uid = _rfid->uid.uidByte[0]<<24 | _rfid->uid.uidByte[1]<<16 | _rfid->uid.uidByte[2]<<8 | _rfid->uid.uidByte[3];
SPIMaster::disable();
return uid;
void Controller::handle_buttons() {
if (is_button_pressed(PIN_BTN_VOL_UP)) {
log_i("BTN_VOL_UP pressed");
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;
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)) {
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();
}
}
void Controller::_check_rfid() {
uint32_t uid = _get_rfid_card_uid();
if (uid != _last_rfid_card_uid) {
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) {
INFO("New RFID card uid: %08x\n", uid);
String s_uid = String(uid, HEX);
_player->play_album(s_uid);
} else {
INFO("No more RFID card.");
_player->stop();
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);
String data = read_rfid_data();
play(s_uid, data.indexOf("[random]")>=0);
}
rfid->PICC_HaltA();
}
}
_last_rfid_card_uid = uid;
}
}
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) {
_execute_serial_command(_serial_buffer);
_serial_buffer = String();
String Controller::read_rfid_data() {
log_v("read_rfid_data() running...");
MFRC522::StatusCode status;
MFRC522::PICC_Type type = rfid->PICC_GetType(rfid->uid.sak);
uint16_t pageStart = 0;
uint16_t pages = 4;
uint16_t pageSize = 1;
switch(type) {
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.");
} else {
log_v("Authentication failed. Trying to read anyway.");
}
pageStart = 4;
break;
}
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; block<pages+pageStart; block+=pageSize) {
byte buffer[18];
uint8_t byte_count = 18;
status = rfid->MIFARE_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->PCD_StopCrypto1();
log_v("Read rfid data: '%s'", data.c_str());
return data;
}
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());
// 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 {
_serial_buffer.concat(c);
if (!audio.isRunning()) {
play();
}
}
}
void Controller::_execute_serial_command(String cmd) {
DEBUG("Executing command: %s", cmd.c_str());
void Controller::play() {
String file = current_playlist.get_current_file_name();
if (cmd.equals("ls")) {
_execute_command_ls("/");
} else if (cmd.startsWith("ls ")) {
_execute_command_ls(cmd.substring(3));
} else if (cmd.startsWith("play ")) {
_player->play_album(cmd.substring(5));
} else if (cmd.startsWith("sys ")) {
_player->play_system_sound(cmd.substring(4));
} 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.equals("p")) {
_player->track_prev();
} else if (cmd.equals("n")) {
_player->track_next();
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.connecttoFS(SD, "/system/sys_connecting.mp3");
while (audio.isRunning()) {
yield();
audio.loop();
}
audio.connecttohost(file.c_str());
}
}
void Controller::next_track() {
if (current_playlist.next_track()) {
play();
}
}
void Controller::prev_track() {
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 {
ERROR("Unknown command: %s\n", cmd.c_str());
if (current_playlist.prev_track()) {
play();
}
return;
}
void Controller::_execute_command_ls(String path) {
INFO("Listing contents of %s:\n", path.c_str());
std::list<String> files = _player->ls(path);
for(std::list<String>::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(" 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(" - / + - Decrease or increase the volume\n");
INFO(" p / n - Previous or next track\n");
void Controller::stop() {
if (audio.isRunning()) {
current_playlist.set_current_time(audio.stopSong());
}
}
void Controller::_check_buttons() {
SPIMaster::enable(PIN_MCP);
SPI.beginTransaction(SPISettings(250000, MSBFIRST, SPI_MODE0));
/*if (millis()%100==0) {
Serial.printf("Buttons: %d %d %d %d\n", _mcp->digitalRead(1), _mcp->digitalRead(2), _mcp->digitalRead(3), _mcp->digitalRead(4));
}*/
if (_check_button(0)) {
_player->track_prev();
} else if (_check_button(1)) {
_player->vol_up();
} else if (_check_button(2)) {
_player->vol_down();
} else if (_check_button(3)) {
_player->track_next();
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;
}
SPI.endTransaction();
SPIMaster::disable();
} else {
button_pressed = pin;
button_pressed_since = millis();
button_already_processed = false;
}
} else {
if (button_pressed == pin) {
button_pressed = 0;
}
}
return false;
}
bool Controller::_check_button(uint8_t index) {
if (index >= NUM_BUTTONS) return false;
bool ret = false;
uint8_t sum = 0;
while (1) {
sum = 0;
for (int i=0; i<8; i++) {
sum += _mcp->digitalRead(index + 1) == HIGH ? 1 : 0;
void Controller::eof_mp3(String info) {
log_d("Handling eof. Keep playing until the file is finished.");
while(audio.isRunning()) { audio.loop(); yield; }
if (info.startsWith("sys_")) {
log_d("File ending was a system audio file. Not running next_track.");
} else {
next_track();
}
if (sum==0 || sum==8) break;
}
if (sum == 0) {
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;
}

139
src/esmp3.cpp Normal file
View File

@ -0,0 +1,139 @@
#include <WiFi.h>
#include <ArduinoOTA.h>
#include <SD.h>
#include "spi_master.h"
#include "playlist_manager.h"
#include "controller.h"
#include <Audio.h>
#include "esmp3.h"
#include <Ticker.h>
#include <MFRC522v2.h>
#include <MFRC522DriverSPI.h>
#include <MFRC522DriverPinSimple.h>
#include <MFRC522Debug.h>
#include <Arduino.h>
#include <Wire.h>
#include <FTPServer.h>
Controller controller;
Audio audio;
PlaylistManager* pm;
MFRC522* rfid;
FTPServer ftp(SD);
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...");
uint8_t i=9;
while(WiFi.status() != WL_CONNECTED) {
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();
log_i("Waiting for OTA...");
for(int i=0; i<20; i++) {
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);
//SPIMaster::initialize();
Serial.print("Initializing SD card...");
//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);
}
Serial.print(".");
delay(100);
}
Serial.println();
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("Setup finished.");
audio.setVolume(12);
audio.connecttoFS(SD, "/system/sys_ready.mp3");
ftp.begin("", "");
}
void loop() {
ArduinoOTA.handle();
controller.handle();
audio.loop();
ftp.handleFTP();
}
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.eof_mp3(info);
}
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);
}

View File

@ -1,49 +0,0 @@
#include <Arduino.h>
#include <SPI.h>
#include <SD.h>
#include <MCP23S17/MCP23S17.h>
#include "config.h"
#include "controller.h"
#include "player.h"
#include "spi_master.h"
Controller* controller;
Player* player;
MCP* mcp;
void setup() {
delay(500);
Serial.begin(74880);
INFO("Starting.\n");
INFO("Initializing...\n");
DEBUG("Setting up SPI...\n");
SPI.begin();
SPIMaster::init();
INFO("SPI initialized.\n");
DEBUG("Setting up MCP...\n");
SPIMaster::enable(PIN_MCP);
mcp = new MCP(0, PIN_MCP);
INFO("MCP initialized.");
DEBUG("Setting up SD card...\n");
SPIMaster::enable(PIN_SD_CS);
if (SD.begin(PIN_SD_CS)) {
INFO("SD card initialized.\n");
} else {
ERROR("Could not initialize SD card. Halting.\n");
while(1);
}
player = new Player(mcp);
controller = new Controller(player, mcp);
INFO("Initialization completed.\n");
}
void loop() {
bool more_data_needed = player->loop();
if (more_data_needed) return;
controller->loop();
}

View File

@ -1,492 +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"
//Player::_spi_settings
Player::Player(MCP* m) {
_mcp = m;
_mcp->pinMode(XRESET, OUTPUT);
_mcp->digitalWrite(XRESET, HIGH);
pinMode(DREQ, INPUT);
_init();
}
void Player::_reset() {
_mcp->digitalWrite(XRESET, LOW);
delay(100);
_mcp->digitalWrite(XRESET, HIGH);
delay(100);
_state = uninitialized;
_spi_settings = &_spi_settings_slow; // After reset, communication has to be slow
}
void Player::_init() {
SPIMaster::disable();
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.\n", result);
return;
}
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.\n", result);
return;
}
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, 0x6000);
delay(10);
_spi_settings = &_spi_settings_fast;
result = _read_control_register(SCI_CLOCKF);
DEBUG("SCI_CLOCKF: 0x%04X\n", result);
if (result != 0x6000) {
ERROR("Error: SCI_CLOCKF was 0x%04X, expected was 0x6000.\n", result);
return;
}
set_volume(VOLUME_DEFAULT);
INFO("VS1053 initialization completed.\n");
INFO("Checking system sounds...\n");
SPIMaster::enable(PIN_SD_CS);
_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");
_state = idle;
}
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() {
while(!digitalRead(DREQ));
}
uint16_t Player::_read_control_register(uint8_t address) {
_wait();
SPIMaster::enable(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();
SPIMaster::disable();
return (b1 << 8) | b2;
}
void Player::_write_control_register(uint8_t address, uint16_t value) {
uint8_t b1 = value >> 8;
uint8_t b2 = value & 0xFF;
_wait();
SPIMaster::enable(XCS);
SPI.beginTransaction(*_spi_settings);
SPI.transfer(CMD_WRITE);
SPI.transfer(address);
SPI.transfer(b1);
SPI.transfer(b2);
_wait();
SPI.endTransaction();
SPIMaster::disable();
}
void Player::_write_data(uint8_t* buffer) {
SPIMaster::enable(XDCS);
SPI.beginTransaction(*_spi_settings);
for (uint i=0; i<sizeof(_buffer); i++) {
SPI.transfer(_buffer[i]);
}
SPI.endTransaction();
SPIMaster::disable();
}
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;
if (vol==0xFF) vol=0xFE;
uint16_t value = (vol<<8)|vol;
DEBUG("Setting volume register to 0x%04X\n", value);
_write_control_register(SCI_VOL, value);
}
void Player::vol_up() {
if (_volume == VOLUME_MAX) play_system_sound("volume_max.mp3");
else if (_volume + VOLUME_STEP > VOLUME_MAX) set_volume(VOLUME_MAX);
else set_volume(_volume + VOLUME_STEP);
}
void Player::vol_down() {
if (_volume >= VOLUME_MIN + VOLUME_STEP) set_volume(_volume - VOLUME_STEP);
else if (_volume == VOLUME_MIN) play_system_sound("volume_min.mp3");
else set_volume(VOLUME_MIN);
}
void Player::_mute() {
INFO("Muting.");
set_volume(0, false);
}
void Player::_unmute() {
INFO("Unmuting.");
set_volume(_volume, false);
}
void Player::track_next() {
if (_state != playing) return;
if (_playing_index + 1 >= _playing_album_songs) {
play_system_sound("no_next_song.mp3");
return;
}
stop();
play_song(_playing_album, _playing_index + 1);
}
void Player::track_prev() {
if (_state != playing) return;
if (_current_play_position > 100000) {
stop();
play_song(_playing_album, _playing_index);
} else {
if (_playing_index == 0) {
play_system_sound("no_prev_song.mp3");
return;
}
stop();
play_song(_playing_album, _playing_index - 1);
}
}
std::list<String> Player::ls(String path) {
SPIMaster::enable(PIN_SD_CS);
std::list<String> result;
if (!SD.exists(path)) return result;
File dir = SD.open(path);
File entry;
while (entry = dir.openNextFile()) {
String filename = entry.name();
if (entry.isDirectory()) filename.concat("/");
result.push_back(filename);
}
return result;
}
String Player::_find_album_dir(String id) {
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();
if (entry.isDirectory() && (name.startsWith(id_with_divider) || name.equals(id))) {
result = name;
}
entry.close();
}
root.close();
return result;
}
std::list<String> Player::_files_in_dir(String path) {
DEBUG("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)) return result;
File dir = SD.open(path);
File entry;
while (entry = dir.openNextFile()) {
String filename = entry.name();
if (!entry.isDirectory() &&
!filename.startsWith(".") &&
( filename.endsWith(".mp3") ||
filename.endsWith(".ogg") ||
filename.endsWith(".wma") ||
filename.endsWith(".mp4") ||
filename.endsWith(".mpa"))) {
DEBUG(" Adding entry %s\n", filename.c_str());
result.push_back(path + filename);
} else {
DEBUG(" Ignoring entry %s\n", filename.c_str());
}
entry.close();
}
dir.close();
result.sort();
return result;
}
bool Player::play_album(String album) {
//if (_state==playing) stop();
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 != idle) return false;
DEBUG("Trying to play song at index %d, offset %d of album %s\n", index, skip_to, album.c_str());
String path = _find_album_dir(album);
if (path.length()==0) {
ERROR("Could not find album.\n");
return false;
} else {
INFO("Found album in dir '%s'.\n", path.c_str());
}
std::list<String> files = _files_in_dir(path);
_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;
}
//std::list<String>::iterator it = files.begin();
//std::advance(it, index);
String file = *(std::next(files.begin(), index));
_state = playing;
_playing_album = album;
_playing_index = index;
_set_last_track(album.c_str(), index, skip_to);
_play_file(file, skip_to);
return true;
}
void Player::play_system_sound(String filename) {
//String file = String("/system/") + 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) {
INFO("play_file('%s', %d)\n", file.c_str(), file_offset);
_file = SD.open(file);
if (!_file) 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);
if (file_offset == 0) {
_file.seek(_id3_tag_offset(_file));
}
_refills = 0;
_current_play_position = _file.position();
_skip_to = file_offset;
if (_skip_to>0) _mute();
INFO("Now playing.\n");
}
uint32_t Player::_id3_tag_offset(File f) {
uint32_t original_position = f.position();
uint32_t offset = 0;
if (f.read()=='I' && f.read()=='D' && f.read()=='3') {
DEBUG("ID3 tag found\n");
// Skip ID3 tag version
f.read(); f.read();
byte tags = f.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 & f.read());
}
offset += 10;
if (footer_present) offset += 10;
DEBUG("ID3 tag length is %d bytes.\n", offset);
} else {
DEBUG("No ID3 tag found\n");
}
f.seek(original_position);
return offset;
}
void Player::_flush(uint count, int8_t byte) {
SPIMaster::enable(XDCS);
SPI.beginTransaction(*_spi_settings);
for(uint i=0; i<count; i++) {
_wait();
SPI.transfer(byte);
}
SPI.endTransaction();
}
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() {
if (_state != playing /* && _state != system_sound_while_playing && _state != system_sound_while_stopped*/) return;
INFO("Stopping...\n");
if (_state == playing) {
_set_last_track(_playing_album.c_str(), _playing_index, (uint32_t)_file.position());
}
_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();
break;
} else if (_stop_delay > 2048) {
init();
break;
}
_stop_delay++;
}
}
void Player::_finish_stopping() {
_state = idle;
if (_file) {
_file.close();
}
INFO("Stopped.\n");
}
void Player::_refill() {
SPIMaster::enable(PIN_SD_CS);
_refills++;
if (_refills % 1000 == 0) DEBUG(".");
uint8_t result = _file.read(_buffer, sizeof(_buffer));
if (result == 0) {
// File is over.
DEBUG("EOF reached.\n");
_finish_playing();
if (_state == system_sound_while_playing) {
_finish_stopping();
play_album(_playing_album);
return;
} else if (_state == system_sound_while_stopped) {
_finish_stopping();
return;
}
_finish_stopping();
bool result = play_song(_playing_album, _playing_index + 1);
if (!result) {
_set_last_track(_playing_album.c_str(), 0, 0);
}
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());
_file.seek(_skip_to);
_skip_to = 0;
_unmute();
}
} else {
_skip_to = 0;
_unmute();
}
}
}
bool Player::_refill_needed() {
return _state==playing ||
_state==stopping ||
_state==system_sound_while_playing ||
_state==system_sound_while_stopped;
}
bool Player::loop() {
if (digitalRead(DREQ) && _refill_needed()) {
_refill();
return true;
}
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};
}

82
src/playlist.cpp Normal file
View File

@ -0,0 +1,82 @@
#include "playlist.h"
Playlist::Playlist() {}
Playlist::Playlist(String id, PersistedPlaylist* p) {
rfid_id = id;
pp = p;
}
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 bytes) {
log_d("Setting position: File %d, bytes %d.", file, bytes);
current_file = file;
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() {
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() {
log_d("Playlist::prev_track called. current_file is %d", current_file);
if (current_file == 0) {
set_current_position(0, 0);
} else {
set_current_position(current_file - 1, 0);
}
return files.size()>0;
}
void Playlist::restart() {
current_time = 0;
}
void Playlist::set_current_time(uint32_t pos) {
set_current_position(current_file, pos);
}
uint32_t Playlist::get_current_time() {
return current_time;
}
void Playlist::shuffle() {
std::random_shuffle(files.begin(), files.end());
}

85
src/playlist_manager.cpp Normal file
View File

@ -0,0 +1,85 @@
#include "playlist_manager.h"
#include "spi_master.h"
#include <SD.h>
PlaylistManager::PlaylistManager() {
SPIMaster::enable_sd();
current_rfid_tag_id = String("");
if (!SD.exists("/_mapping.txt")) {
Serial.println("WARNING: /_mapping.txt not found!");
} else {
map.clear();
File f = SD.open("/_mapping.txt");
Serial.println(" Reading /_mapping.txt...");
while (f.available()) {
char buffer[512];
size_t pos = f.readBytesUntil('\n', buffer, 511);
buffer[pos] = '\0';
String data = buffer;
uint8_t eq = data.indexOf('=');
if (eq>0 && eq<data.length()-1) {
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] = PersistedPlaylist(folder);
}
}
f.close();
}
}
Playlist PlaylistManager::get_playlist(String rfid_id) {
if (rfid_id.equals(current_rfid_tag_id)) {
return current_playlist;
} else {
if (map.count(rfid_id)==0) {
Serial.printf("No known playlist for id %s.\n", rfid_id);
return current_playlist;
} else {
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()) {
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", (path + "/" + filename).c_str());
current_playlist.add_file(path + "/" + filename);
}
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);
}
current_playlist.sort();
current_rfid_tag_id = rfid_id;
return current_playlist;
}
}
}
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;
}
String PlaylistManager::pp_to_String() {
String s = "";
for(const auto& kv : map) {
s += kv.first + "=" + kv.second.file + "," + kv.second.position + '\n';
}
return s;
}

26
src/spi_master.cpp Normal file
View File

@ -0,0 +1,26 @@
#include "spi_master.h"
#include <Arduino.h>
#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);
}