Merge branch 'feature/rewrite-i2s'
This commit is contained in:
commit
ab51af637e
178
README.md
178
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>=<folder>`. 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://<IP>/ws`.
|
||||
* Through the serial console using an USB cable.
|
||||
* Via HTTP POST request to `http://<IP>/cmd`, having the
|
||||
command in the variable `cmd`.
|
||||
|
||||
Supported commands are:
|
||||
| Command | Action |
|
||||
|---------|--------|
|
||||
| `play <PATH>` | 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=<X>` | Sets the volume to X (0-255). |
|
||||
| `track_prev` | Starts the previous track, if available. |
|
||||
| `track_next` | Starts the next track, if available. |
|
||||
| `track=<X>` | Starts playing track no. X of the currently playing album. |
|
||||
| `reset_vs1053` | Resets the VS1053 audio chip. |
|
||||
| `reboot` | Reboots ESMP3. |
|
||||
| `add_mapping=<ID>=<PATH>` | Adds a mapping between RFID card <ID> and path
|
||||
<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. |
|
||||
## Audio files
|
||||
System messages are created using:
|
||||
* https://ttsmp3.com/
|
||||
* German / Vicki
|
||||
* "Dies ist ein Text.<break time="1s"/>"
|
||||
* Download as MP3.
|
@ -1,84 +0,0 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
|
||||
// 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
|
@ -1,61 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include "config.h"
|
||||
|
||||
class Controller;
|
||||
|
||||
#include "player.h"
|
||||
#include <MFRC522v2.h>
|
||||
#include <MFRC522Debug.h>
|
||||
#include "esmp3.h"
|
||||
#include "playlist.h"
|
||||
#include "playlist_manager.h"
|
||||
#include "http_server.h"
|
||||
|
||||
#undef DEPRECATED
|
||||
#include <MFRC522.h>
|
||||
|
||||
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 = "";
|
||||
|
||||
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();
|
||||
};
|
||||
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;
|
||||
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:
|
||||
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);
|
||||
};
|
@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <SD.h>
|
||||
#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();
|
||||
};
|
26
include/esmp3.h
Normal file
26
include/esmp3.h
Normal 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();
|
@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <HTTPClient.h>
|
||||
#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();
|
||||
};
|
@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
class HTTPServer;
|
||||
|
||||
#include "player.h"
|
||||
#include "controller.h"
|
||||
#include <AsyncTCP.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
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;
|
||||
};
|
@ -1,9 +0,0 @@
|
||||
#pragma once
|
||||
#include <Preferences.h>
|
||||
|
||||
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;
|
9
include/persisted_playlist.h
Normal file
9
include/persisted_playlist.h
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
struct PersistedPlaylist {
|
||||
String dir;
|
||||
uint16_t file = 0;
|
||||
uint32_t position = 0;
|
||||
PersistedPlaylist(String s="") : dir(s) {}
|
||||
};
|
104
include/player.h
104
include/player.h
@ -1,104 +0,0 @@
|
||||
#pragma once
|
||||
#include "config.h"
|
||||
#include <SPI.h>
|
||||
#include <SD.h>
|
||||
#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();
|
||||
};
|
@ -1,59 +1,30 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <vector>
|
||||
#include <ArduinoJson.h>
|
||||
#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 <Arduino.h>
|
||||
#include "persisted_playlist.h"
|
||||
|
||||
class Playlist {
|
||||
private:
|
||||
uint32_t _position = 0;
|
||||
uint32_t _current_track = 0;
|
||||
bool _started = false;
|
||||
bool _shuffled = false;
|
||||
std::vector<PlaylistEntry> _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<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);
|
||||
};
|
||||
|
@ -1,24 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <Arduino.h>
|
||||
#include "playlist.h"
|
||||
#include "persisted_playlist.h"
|
||||
|
||||
class Playlist;
|
||||
|
||||
class PlaylistManager {
|
||||
private:
|
||||
std::map<String, String> _map;
|
||||
std::map<String, Playlist*> _playlists;
|
||||
std::vector<String> _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::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();
|
||||
};
|
@ -1,70 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
#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;
|
||||
}
|
||||
};
|
||||
public:
|
||||
static void enable_sd();
|
||||
static void enable_rfid();
|
||||
static void disable_all();
|
||||
static void initialize();
|
||||
};
|
||||
|
@ -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);
|
||||
};
|
@ -12,29 +12,31 @@
|
||||
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
|
||||
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
|
||||
board_build.embed_txtfiles = src/index.html
|
||||
;board_build.partitions = partitions.csv
|
||||
;monitor_port = /dev/cu.wchusbserial1420
|
||||
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}
|
||||
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
|
||||
|
@ -1,463 +1,226 @@
|
||||
#include "controller.h"
|
||||
#include "main.h"
|
||||
#include "spi_master.h"
|
||||
#include "config.h"
|
||||
#include "playlist.h"
|
||||
#include "http_server.h"
|
||||
#include "updater.h"
|
||||
#include <ArduinoJson.h>
|
||||
#include "esmp3.h"
|
||||
|
||||
Controller::Controller(Player* p, PlaylistManager* playlist_manager) {
|
||||
player = p;
|
||||
pm = playlist_manager;
|
||||
_rfid = new MFRC522(17, MFRC522::UNUSED_PIN);
|
||||
|
||||
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<NUM_BUTTONS; i++) _button_last_pressed_at[i]=0;
|
||||
}
|
||||
|
||||
void Controller::register_http_server(HTTPServer* h) {
|
||||
_http_server = h;
|
||||
}
|
||||
|
||||
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;
|
||||
void Controller::handle() {
|
||||
if (last_rfid_check + 500 < millis() || last_rfid_check > millis()) {
|
||||
handle_rfid();
|
||||
last_rfid_check = millis();
|
||||
}
|
||||
if ((_last_position_info_at < now - POSITION_SEND_INTERVAL) || (now < _last_position_info_at)) {
|
||||
send_position();
|
||||
_last_position_info_at = now;
|
||||
if (last_button_check + 10 < millis() || last_button_check > millis()) {
|
||||
handle_buttons();
|
||||
last_button_check = millis();
|
||||
}
|
||||
_check_serial();
|
||||
_check_buttons();
|
||||
if (_cmd_queue.length() > 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;
|
||||
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 Controller::_get_rfid_card_uid() {
|
||||
SPIMaster::select_rc522();
|
||||
if (!_rfid->PICC_ReadCardSerial()) {
|
||||
if (!_rfid->PICC_IsNewCardPresent()) {
|
||||
return 0;
|
||||
}
|
||||
if (!_rfid->PICC_ReadCardSerial()) {
|
||||
return 0;
|
||||
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();
|
||||
}
|
||||
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) {
|
||||
void Controller::handle_rfid() {
|
||||
if (is_rfid_present) {
|
||||
byte buffer[2];
|
||||
byte buffer_size = 2;
|
||||
SPIMaster::select_rc522();
|
||||
status = _rfid->PICC_WakeupA(buffer, &buffer_size);
|
||||
if (status == MFRC522::STATUS_OK) {
|
||||
MFRC522Constants::StatusCode status = rfid->PICC_WakeupA(buffer, &buffer_size);
|
||||
if (status == MFRC522Constants::STATUS_OK) {
|
||||
// Card is still present.
|
||||
_rfid->PICC_HaltA();
|
||||
SPIMaster::select_rc522(false);
|
||||
return;
|
||||
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();
|
||||
}
|
||||
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;
|
||||
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);
|
||||
|
||||
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;
|
||||
String data = read_rfid_data();
|
||||
|
||||
play(s_uid, data.indexOf("[random]")>=0);
|
||||
}
|
||||
rfid->PICC_HaltA();
|
||||
}
|
||||
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;
|
||||
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 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; sector<sectors; sector++) {
|
||||
uint8_t blocks = (sector < 32) ? 4 : 16;
|
||||
uint8_t block_offset = (sector < 32) ? sector * 4 : 128 + (sector - 32) * 16;
|
||||
|
||||
MFRC522::StatusCode status;
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
if (good_key_index == -1) {
|
||||
TRACE("Could not find a valid MIFARE key.\n");
|
||||
} else {
|
||||
for (uint8_t block=0; block<blocks-1; block++) {
|
||||
byte buffer[18];
|
||||
uint8_t byte_count = 18;
|
||||
status = _rfid->MIFARE_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]);
|
||||
}
|
||||
}
|
||||
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->PICC_HaltA();
|
||||
_rfid->PCD_StopCrypto1();
|
||||
DEBUG("Data from RFID: %s\n", data.c_str());
|
||||
SPIMaster::select_rc522(false);
|
||||
rfid->PCD_StopCrypto1();
|
||||
log_v("Read rfid data: '%s'", data.c_str());
|
||||
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();
|
||||
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 {
|
||||
if (!audio.isRunning()) {
|
||||
play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::play() {
|
||||
String file = current_playlist.get_current_file_name();
|
||||
|
||||
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 {
|
||||
if (current_playlist.prev_track()) {
|
||||
play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
_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);
|
||||
button_pressed = pin;
|
||||
button_pressed_since = millis();
|
||||
button_already_processed = false;
|
||||
}
|
||||
} 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<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(" 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");
|
||||
if (button_pressed == pin) {
|
||||
button_pressed = 0;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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; |