├── uploadfs.sh ├── sync-playlist.sh ├── .gitignore ├── netease-fav-list-sync ├── .babelrc ├── package.json ├── index.js └── .gitignore ├── src ├── http │ ├── HTTPClient_TE.cpp │ └── HTTPClient_TE.h ├── wiring.h ├── wifi │ ├── WiFiConnector.h │ └── WiFiConnector.cpp ├── pod │ ├── FavMusicPod.cpp │ ├── FavMusicPod.h │ ├── RadioPod.h │ ├── RadioPod.cpp │ ├── Pod.h │ ├── SmartPod.h │ ├── BasePod.h │ ├── SmartPod.cpp │ └── BaseListPod.h ├── stream │ ├── MediaStream.cpp │ ├── MediaStream.h │ ├── LocalMediaStream.h │ ├── HTTPMediaStream.h │ ├── LocalMediaStream.cpp │ └── HTTPMediaStream.cpp ├── player │ ├── MediaPlayer.h │ └── MediaPlayer.cpp ├── controller │ ├── ButtonController.h │ └── ButtonController.cpp └── main.cpp ├── lib ├── URLParser │ ├── URLParser.h │ ├── URL.h │ └── URLParser.cpp ├── Console │ ├── Console.h │ └── Console.cpp ├── readme.txt └── VS1053 │ ├── VS1053.h │ └── VS1053.cpp ├── data ├── radio-pod.m3u └── fav-music-pod.m3u ├── platformio.ini ├── .travis.yml └── README.md /uploadfs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pio run --target uploadfs 4 | -------------------------------------------------------------------------------- /sync-playlist.sh: -------------------------------------------------------------------------------- 1 | cd netease-fav-list-sync 2 | npm start 3 | cd ../ 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pioenvs 2 | .clang_complete 3 | .gcc-flags.json 4 | .piolibdeps -------------------------------------------------------------------------------- /netease-fav-list-sync/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0"] 3 | } 4 | -------------------------------------------------------------------------------- /src/http/HTTPClient_TE.cpp: -------------------------------------------------------------------------------- 1 | #include "HTTPClient_TE.h" 2 | 3 | transferEncoding_t HTTPClient_TE::getTransferEncoding() 4 | { 5 | return this->_transferEncoding; 6 | } 7 | -------------------------------------------------------------------------------- /lib/URLParser/URLParser.h: -------------------------------------------------------------------------------- 1 | #ifndef URL_PARSER_H 2 | #define URL_PARSER_H 3 | 4 | #include 5 | 6 | #include "URL.h" 7 | 8 | URL parseURL(String url); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/wiring.h: -------------------------------------------------------------------------------- 1 | // VS1053 2 | #define VS1053_XCS_PIN D1 3 | #define VS1053_XDCS_PIN D0 4 | #define VS1053_DREQ_PIN D2 5 | 6 | // Buttons 7 | #define LEFT_BUTTON_PIN D4 8 | #define RIGHT_BUTTON_PIN D3 9 | #define MIDDLE_BUTTON_PIN D8 10 | -------------------------------------------------------------------------------- /src/wifi/WiFiConnector.h: -------------------------------------------------------------------------------- 1 | #ifndef WIFI_CONNECTOR_H 2 | #define WIFI_CONNECTOR_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | class WiFiConnector 9 | { 10 | public: 11 | static bool begin(); 12 | }; 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /lib/URLParser/URL.h: -------------------------------------------------------------------------------- 1 | #ifndef URL_H 2 | #define URL_H 3 | 4 | #include 5 | 6 | typedef struct 7 | { 8 | bool isValid = false; 9 | String protocol; 10 | String host; 11 | int port; 12 | String path; 13 | } URL; 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/http/HTTPClient_TE.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTP_CLIENT_2_H 2 | #define HTTP_CLIENT_2_H 3 | 4 | #include 5 | 6 | class HTTPClient_TE : public HTTPClient 7 | { 8 | public: 9 | transferEncoding_t getTransferEncoding(); 10 | }; 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/pod/FavMusicPod.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "FavMusicPod.h" 4 | 5 | FavMusicPod::FavMusicPod(MediaPlayer *mediaPlayer) : BaseListPod(mediaPlayer) 6 | { 7 | 8 | } 9 | 10 | void FavMusicPod::loadPlaylist() 11 | { 12 | loadPlayListFromLocal("/fav-music-pod.m3u"); 13 | } 14 | -------------------------------------------------------------------------------- /src/pod/FavMusicPod.h: -------------------------------------------------------------------------------- 1 | #ifndef FAV_MUSIC_POD_H 2 | #define FAV_MUSIC_POD_H 3 | 4 | #include 5 | 6 | #include "BaseListPod.h" 7 | 8 | class FavMusicPod : public BaseListPod 9 | { 10 | public: 11 | FavMusicPod(MediaPlayer *mediaPlayer); 12 | 13 | void loadPlaylist() override; 14 | }; 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /src/stream/MediaStream.cpp: -------------------------------------------------------------------------------- 1 | #include "MediaStream.h" 2 | 3 | size_t MediaStream::write(uint8_t byte) 4 | { 5 | // MediaInputStream is read-only. 6 | return 0; 7 | } 8 | 9 | bool MediaStream::isValid() 10 | { 11 | return _valid; 12 | } 13 | 14 | bool MediaStream::isClosed() 15 | { 16 | return _closed; 17 | } 18 | -------------------------------------------------------------------------------- /data/radio-pod.m3u: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | 3 | #EXTINF: ,CNR 经济之声 4 | http://http.qingting.fm/387.mp3 5 | 6 | #EXINF: ,HIT FM 7 | http://http.qingting.fm/1007.mp3 8 | 9 | #EXINF: ,轻松调频 10 | http://http.qingting.fm/1006.mp3 11 | 12 | #EXINF: ,南京音乐广播 13 | http://http.qingting.fm/4963.mp3 14 | 15 | #EXINF: ,CCTV 新闻频道 16 | http://http.qingting.fm/2256689.mp3 17 | -------------------------------------------------------------------------------- /src/pod/RadioPod.h: -------------------------------------------------------------------------------- 1 | #ifndef RADIO_POD_H 2 | #define RADIO_POD_H 3 | 4 | #include 5 | 6 | #include "BaseListPod.h" 7 | 8 | class RadioPod : public BaseListPod 9 | { 10 | public: 11 | RadioPod(MediaPlayer *mediaPlayer); 12 | 13 | void loadPlaylist(); 14 | void pause() override; 15 | void stop() override; 16 | }; 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/pod/RadioPod.cpp: -------------------------------------------------------------------------------- 1 | #include "RadioPod.h" 2 | 3 | RadioPod::RadioPod(MediaPlayer *mediaPlayer) : BaseListPod(mediaPlayer) 4 | { 5 | 6 | } 7 | 8 | void RadioPod::loadPlaylist() 9 | { 10 | loadPlayListFromLocal("/radio-pod.m3u"); 11 | } 12 | 13 | void RadioPod::pause() 14 | { 15 | stop(); 16 | } 17 | 18 | void RadioPod::stop() 19 | { 20 | if (isPlaying()) 21 | { 22 | _playing = false; 23 | } 24 | _mediaPlayer->close(); 25 | } 26 | -------------------------------------------------------------------------------- /src/stream/MediaStream.h: -------------------------------------------------------------------------------- 1 | #ifndef MEDIA_STREAM_H 2 | #define MEDIA_STREAM_H 3 | 4 | #include "Arduino.h" 5 | 6 | // Abstract 7 | class MediaStream : public Stream 8 | { 9 | public: 10 | bool isValid(); 11 | bool isClosed(); 12 | size_t write(uint8_t byte) override; 13 | 14 | virtual bool open(String location) = 0; 15 | virtual void close() = 0; 16 | 17 | virtual int totalSize() = 0; 18 | 19 | protected: 20 | bool _valid = false; 21 | bool _closed = true; 22 | }; 23 | 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter, extra scripting 4 | ; Upload options: custom port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; 7 | ; Please visit documentation for the other options and examples 8 | ; http://docs.platformio.org/en/stable/projectconf.html 9 | 10 | [env:d1_mini] 11 | platform = espressif8266 12 | board = d1_mini 13 | framework = arduino 14 | upload_speed = 921600 15 | upload_port = 192.168.2.242 16 | -------------------------------------------------------------------------------- /netease-fav-list-sync/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "netease-fav-list-sync", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "babel-node index.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "babel-cli": "^6.18.0", 14 | "babel-preset-es2015": "^6.18.0", 15 | "babel-preset-stage-0": "^6.16.0" 16 | }, 17 | "dependencies": { 18 | "request": "^2.79.0", 19 | "request-promise": "^4.1.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /netease-fav-list-sync/index.js: -------------------------------------------------------------------------------- 1 | import request from "request-promise"; 2 | import fs from "fs"; 3 | 4 | const NETEASE_USER_ID = "34176019"; 5 | const apiUrl = `http://music.163.com/api/playlist/detail?id=${NETEASE_USER_ID}`; 6 | 7 | async function loadPlaylist() 8 | { 9 | const res = await request({ 10 | uri: apiUrl, 11 | json: true 12 | }); 13 | 14 | let m3u = "#EXTM3U\n"; 15 | res.result.tracks.forEach(track => { 16 | m3u += track.mp3Url + "\n"; 17 | }); 18 | 19 | fs.writeFile("../data/fav-music-pod.m3u", m3u); 20 | } 21 | 22 | loadPlaylist(); 23 | -------------------------------------------------------------------------------- /src/stream/LocalMediaStream.h: -------------------------------------------------------------------------------- 1 | #ifndef LOCAL_MEDIA_STREAM 2 | #define LOCAL_MEDIA_STREAM 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "MediaStream.h" 9 | 10 | class LocalMediaStream : public MediaStream 11 | { 12 | public: 13 | LocalMediaStream(); 14 | bool open(String path) override; 15 | void close(); 16 | int available() override; 17 | int read() override; 18 | int peek() override; 19 | void flush() override; 20 | 21 | int totalSize() override; 22 | 23 | private: 24 | File _fileStream; 25 | int _totalSize = 0; 26 | }; 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/stream/HTTPMediaStream.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTP_MEDIA_STREAM 2 | #define HTTP_MEDIA_STREAM 3 | 4 | #include 5 | #include "../http/HTTPClient_TE.h" 6 | 7 | #include "MediaStream.h" 8 | 9 | class HTTPMediaStream : public MediaStream 10 | { 11 | public: 12 | HTTPMediaStream(); 13 | bool open(String url) override; 14 | void close() override; 15 | int available() override; 16 | int read() override; 17 | int peek() override; 18 | void flush() override; 19 | 20 | int totalSize() override; 21 | 22 | private: 23 | int _chunkSize; 24 | int _chunkIndex; 25 | int _totalSize; 26 | HTTPClient_TE _httpClient; 27 | WiFiClient* _httpStream; 28 | 29 | void _readChunkSize(); 30 | }; 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /netease-fav-list-sync/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # Built assets 40 | assets 41 | -------------------------------------------------------------------------------- /lib/Console/Console.h: -------------------------------------------------------------------------------- 1 | #ifndef CONSOLE_H 2 | #define CONSOLE_H 3 | 4 | #include 5 | 6 | #define DEBUG_BUFFER_SIZE 100 7 | 8 | typedef enum 9 | { 10 | DEBUG, 11 | INFO, 12 | WARN, 13 | ERROR, 14 | FATAL, 15 | NONE 16 | } ConsoleLogLevel; 17 | 18 | class Console 19 | { 20 | public: 21 | static const ConsoleLogLevel logLevel = DEBUG; 22 | 23 | static void begin(int baudRate = 115200); 24 | static char *debug(const char *format, ...); 25 | static char *info(const char *format, ...); 26 | static char *warn(const char *format, ...); 27 | static char *error(const char *format, ...); 28 | static char *fatal(const char *format, ...); 29 | static void line(); 30 | 31 | private: 32 | static char *appendEntry(ConsoleLogLevel level, char *message); 33 | }; 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /src/pod/Pod.h: -------------------------------------------------------------------------------- 1 | #ifndef POD_H 2 | #define POD_H 3 | 4 | #include 5 | 6 | #include 7 | #include "../player/MediaPlayer.h" 8 | 9 | class Pod 10 | { 11 | public: 12 | Pod(MediaPlayer* mediaPlayer) 13 | { 14 | _mediaPlayer = mediaPlayer; 15 | } 16 | 17 | virtual void handle() = 0; 18 | virtual void activate() = 0; 19 | virtual void deactivate() = 0; 20 | virtual bool isPlaying() = 0; 21 | virtual void play() = 0; 22 | virtual void pause() = 0; 23 | virtual void stop() = 0; 24 | virtual void next() = 0; 25 | virtual void prev() = 0; 26 | virtual uint8_t getVolume() = 0; 27 | virtual void setVolume(uint8_t volume) = 0; 28 | 29 | protected: 30 | bool _playing = false; 31 | uint8_t _volume = 90; 32 | 33 | MediaPlayer* _mediaPlayer; 34 | }; 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/player/MediaPlayer.h: -------------------------------------------------------------------------------- 1 | #ifndef MEDIA_PLAYER_H 2 | #define MEDIA_PLAYER_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "../stream/MediaStream.h" 9 | #include "../stream/LocalMediaStream.h" 10 | #include "../stream/HTTPMediaStream.h" 11 | 12 | #define MEDIA_PLAYER_BUFFER_SIZE 32 13 | 14 | class MediaPlayer 15 | { 16 | public: 17 | MediaPlayer(VS1053* vs1053); 18 | 19 | VS1053* getVS1053(); 20 | 21 | bool open(String location); 22 | void close(); 23 | void handle(); 24 | 25 | bool isClosed(); 26 | bool isEOF(); 27 | 28 | protected: 29 | VS1053* _vs1053; 30 | LocalMediaStream* _localMediaStream; 31 | HTTPMediaStream* _httpMediaStream; 32 | MediaStream* _mediaStream; 33 | 34 | long _totalBytes = 0; 35 | bool _eof = false; 36 | 37 | void _handleMediaStream(); 38 | }; 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /lib/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for the project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link to executable file. 4 | 5 | The source code of each library should be placed in separate directory, like 6 | "lib/private_lib/[here are source files]". 7 | 8 | For example, see how can be organized `Foo` and `Bar` libraries: 9 | 10 | |--lib 11 | | |--Bar 12 | | | |--docs 13 | | | |--examples 14 | | | |--src 15 | | | |- Bar.c 16 | | | |- Bar.h 17 | | |--Foo 18 | | | |- Foo.c 19 | | | |- Foo.h 20 | | |- readme.txt --> THIS FILE 21 | |- platformio.ini 22 | |--src 23 | |- main.c 24 | 25 | Then in `src/main.c` you should use: 26 | 27 | #include 28 | #include 29 | 30 | // rest H/C/CPP code 31 | 32 | PlatformIO will find your libraries automatically, configure preprocessor's 33 | include paths and build them. 34 | 35 | More information about PlatformIO Library Dependency Finder 36 | - http://docs.platformio.org/page/librarymanager/ldf.html 37 | -------------------------------------------------------------------------------- /src/controller/ButtonController.h: -------------------------------------------------------------------------------- 1 | #ifndef BUTTON_CONTROLLER_H 2 | #define BUTTON_CONTROLLER_H 3 | 4 | #include 5 | 6 | #include "../pod/SmartPod.h" 7 | 8 | #define BUTTON_PRESS_BETWEEN 200 9 | #define BUTTON_REPEAT_UNTIL 500 10 | #define BUTTON_REPEAT_DURATION 200 11 | 12 | 13 | class ButtonController 14 | { 15 | public: 16 | ButtonController(SmartPod *smartPod); 17 | void begin(); 18 | void handle(); 19 | 20 | private: 21 | SmartPod* _smartPod; 22 | 23 | long _middleButtonDown = 0; 24 | long _middleButtonUp = 0; 25 | bool _middleButtonLongPressed = false; 26 | void _middleButton_onPressed(); 27 | void _middleButton_onLongPressed(); 28 | 29 | long _leftButtonDown = 0; 30 | long _leftButtonUp = 0; 31 | long _leftButtonHold = 0; 32 | void _leftButton_onPressed(); 33 | void _leftButton_onLongPressed(); 34 | 35 | long _rightButtonDown = 0; 36 | long _rightButtonUp = 0; 37 | long _rightButtonHold = 0; 38 | void _rightButton_onPressed(); 39 | void _rightButton_onLongPressed(); 40 | }; 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /src/pod/SmartPod.h: -------------------------------------------------------------------------------- 1 | #ifndef SMART_POD_H 2 | #define SMART_POD_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "../wiring.h" 9 | #include "./player/MediaPlayer.h" 10 | 11 | #include "Pod.h" 12 | #include "FavMusicPod.h" 13 | #include "RadioPod.h" 14 | 15 | typedef enum { 16 | RADIO_POD, 17 | FAV_MUSIC_POD 18 | } SmartPodMode; 19 | 20 | class SmartPod : public Pod 21 | { 22 | public: 23 | SmartPod(); 24 | 25 | bool begin(); 26 | void handle() override; 27 | 28 | void restart(); 29 | void sleep(); 30 | 31 | void switchMode(SmartPodMode mode); 32 | void switchMode(); 33 | 34 | void activate() override; 35 | void deactivate() override; 36 | bool isPlaying() override; 37 | void play() override; 38 | void pause() override; 39 | void stop() override; 40 | void next() override; 41 | void prev() override; 42 | uint8_t getVolume() override; 43 | void setVolume(uint8_t volume) override; 44 | 45 | void playPause(); 46 | void setVolumeUp(); 47 | void setVolumeDown(); 48 | 49 | private: 50 | SmartPodMode _mode = RADIO_POD; 51 | VS1053 _vs1053; 52 | 53 | Pod* _activePod; 54 | RadioPod* _radioPod; 55 | FavMusicPod* _favMusicPod; 56 | }; 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /lib/URLParser/URLParser.cpp: -------------------------------------------------------------------------------- 1 | #include "URL.h" 2 | 3 | #include "URLParser.h" 4 | 5 | URL parseURL(String url) 6 | { 7 | URL result; 8 | bool hasPort = false; 9 | int index = url.indexOf(':'); 10 | if (index < 0) 11 | { 12 | return result; 13 | } 14 | 15 | result.protocol = url.substring(0, index); 16 | url.remove(0, (index + 3)); // remove http:// or https:// 17 | 18 | index = url.indexOf('/'); 19 | String host = url.substring(0, index); 20 | url.remove(0, index); // remove host part 21 | 22 | // get port 23 | index = host.indexOf(':'); 24 | if (index >= 0) 25 | { 26 | result.host = host.substring(0, index); // hostname 27 | host.remove(0, (index + 1)); // remove hostname + : 28 | result.port = host.toInt(); // get port 29 | hasPort = true; 30 | } 31 | else 32 | { 33 | result.host = host; 34 | } 35 | if (!hasPort) 36 | { 37 | if (result.protocol.equals("http")) 38 | { 39 | result.port = 80; 40 | } 41 | else if (result.protocol.equals("https")) 42 | { 43 | result.port = 443; 44 | } 45 | } 46 | 47 | result.path = url; 48 | result.isValid = true; 49 | } 50 | -------------------------------------------------------------------------------- /src/pod/BasePod.h: -------------------------------------------------------------------------------- 1 | #ifndef BASE_POD_H 2 | #define BASE_POD_H 3 | 4 | #include 5 | 6 | #include "Pod.h" 7 | 8 | class BasePod : public Pod 9 | { 10 | public: 11 | BasePod(MediaPlayer* mediaPlayer) : Pod(mediaPlayer) 12 | { 13 | 14 | } 15 | 16 | virtual bool isPlaying() 17 | { 18 | return _playing; 19 | } 20 | 21 | virtual void deactivate() 22 | { 23 | if (isPlaying()) 24 | { 25 | stop(); 26 | } 27 | } 28 | 29 | virtual void handle() 30 | { 31 | if (isPlaying()) 32 | { 33 | _mediaPlayer->handle(); 34 | } 35 | } 36 | 37 | 38 | virtual void play() 39 | { 40 | if (!isPlaying()) 41 | { 42 | _playing = true; 43 | } 44 | } 45 | 46 | virtual void pause() 47 | { 48 | if (isPlaying()) 49 | { 50 | _playing = false; 51 | } 52 | } 53 | 54 | virtual void stop() 55 | { 56 | pause(); 57 | _mediaPlayer->close(); 58 | } 59 | 60 | 61 | 62 | virtual uint8_t getVolume() 63 | { 64 | return _volume; 65 | } 66 | 67 | virtual void setVolume(uint8_t volume) 68 | { 69 | if (volume > 100) 70 | { 71 | _volume = 100; 72 | } 73 | else if (volume < 0) 74 | { 75 | _volume = 0; 76 | } 77 | else 78 | { 79 | _volume = volume; 80 | } 81 | _mediaPlayer->getVS1053()->setVolume(_volume); 82 | } 83 | }; 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /src/wifi/WiFiConnector.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "WiFiConnector.h" 4 | 5 | bool WiFiConnector::begin() 6 | { 7 | WiFi.mode(WIFI_STA); 8 | 9 | // Auto scan WiFi connection 10 | String prefSSID = "none"; 11 | String prefPassword; 12 | Console::info("Scanning WiFi..."); 13 | int ssidCount = WiFi.scanNetworks(); 14 | if (ssidCount == -1) 15 | { 16 | Console::error("Couldn't get a WiFi connection."); 17 | return false; 18 | } 19 | for (int i = 0; i < ssidCount; i++) 20 | { 21 | String ssid = WiFi.SSID(i); 22 | if (ssid.equals("Henry's Living Room 2.4GHz")) 23 | { 24 | prefSSID = ssid; 25 | prefPassword = "13913954971"; 26 | break; 27 | } 28 | else if (ssid.equals("Henry's TP-LINK")) 29 | { 30 | prefSSID = ssid; 31 | prefPassword = "13913954971"; 32 | break; 33 | } 34 | else if (ssid.equals("Henry's iPhone 6")) 35 | { 36 | prefSSID = ssid; 37 | prefPassword = "13913954971"; 38 | // Don't break, cause this will connect to 4G network. 39 | // It's absolutely not a first choise. 40 | } 41 | } 42 | if (prefSSID.equals("none")) 43 | { 44 | Console::error("Couldn't find a recognized WiFi connection."); 45 | return false; 46 | } 47 | WiFi.begin(prefSSID.c_str(), prefPassword.c_str()); 48 | Console::info("Connecting to \"%s\"", prefSSID.c_str()); 49 | while (WiFi.status() != WL_CONNECTED) 50 | { 51 | delay(500); 52 | Serial.print("."); 53 | } 54 | Serial.println(""); 55 | Console::info("Got IP: %s", WiFi.localIP().toString().c_str()); 56 | return true; 57 | } 58 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < http://docs.platformio.org/en/stable/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < http://docs.platformio.org/en/stable/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < http://docs.platformio.org/en/stable/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choice one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # 39 | # script: 40 | # - platformio run 41 | 42 | 43 | # 44 | # Template #2: The project is intended to by used as a library with examples 45 | # 46 | 47 | # language: python 48 | # python: 49 | # - "2.7" 50 | # 51 | # sudo: false 52 | # cache: 53 | # directories: 54 | # - "~/.platformio" 55 | # 56 | # env: 57 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 58 | # - PLATFORMIO_CI_SRC=examples/file.ino 59 | # - PLATFORMIO_CI_SRC=path/to/test/directory 60 | # 61 | # install: 62 | # - pip install -U platformio 63 | # 64 | # script: 65 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 66 | -------------------------------------------------------------------------------- /src/stream/LocalMediaStream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "LocalMediaStream.h" 4 | 5 | LocalMediaStream::LocalMediaStream() 6 | { 7 | 8 | } 9 | 10 | bool LocalMediaStream::open(String path) 11 | { 12 | if (!_closed) 13 | { 14 | close(); 15 | } 16 | 17 | Console::info("Loading %s...", path.c_str()); 18 | _fileStream = SPIFFS.open(path, "r"); // Open the file 19 | _totalSize = _fileStream.size(); 20 | if (!_fileStream) 21 | { 22 | Console::info("Error opening file %s", path.c_str()); 23 | _closed = true; 24 | _valid = false; 25 | return false; 26 | } 27 | else 28 | { 29 | Console::info("%s has been loaded.", path.c_str()); 30 | Console::debug("File size: %d", _fileStream.size()); 31 | _closed = false; 32 | _valid = true; 33 | return true; 34 | } 35 | } 36 | 37 | void LocalMediaStream::close() 38 | { 39 | if (!_closed) 40 | { 41 | _fileStream.close(); 42 | Console::info("LocalMediaStream closed."); 43 | } 44 | _closed = true; 45 | _valid = false; 46 | _totalSize = 0; 47 | } 48 | 49 | int LocalMediaStream::available() 50 | { 51 | return !_closed ? _fileStream.available() : 0; 52 | } 53 | 54 | int LocalMediaStream::read() 55 | { 56 | if (!_closed) 57 | { 58 | return _fileStream.read(); 59 | } 60 | else 61 | { 62 | return 0; 63 | } 64 | } 65 | 66 | int LocalMediaStream::peek() 67 | { 68 | if (!_closed) 69 | { 70 | return _fileStream.peek(); 71 | } 72 | else 73 | { 74 | return 0; 75 | } 76 | } 77 | 78 | void LocalMediaStream::flush() 79 | { 80 | if (!_closed) 81 | { 82 | _fileStream.flush(); 83 | } 84 | } 85 | 86 | 87 | 88 | 89 | int LocalMediaStream::totalSize() 90 | { 91 | return _totalSize; 92 | } 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # smart-pod 2 | An ESP8266 and VS1053 driven WebRadio and Internet music player. 3 | 4 | ## Features 5 | 6 | + Internet radio 7 | + Internet music player 8 | + OLED display 9 | + Remote control over computers or mobile devices 10 | + OTA(Over The Air) firmware update 11 | 12 | ## Enclosure 13 | 14 | + 3D printed case 15 | + Specially design for Bose Sound Mini II as dock 16 | 17 | ## Services 18 | + **FM / Radio** - Support MP3 and ACC. In my presets, I use a Chinese WebRadio service provider named [QingtingFM](http://qingting.fm/) 19 | + **Music** - Support MP3, ACC and wma. By default, it will automatically load my favourite music collection from [Netease Music](http://music.163.com). 20 | 21 | ## Hardwares 22 | 23 | + **ESP8266** (ESP12 / NodeMCU / WeMos) 24 | 25 | Yet, the most popular WiFi MCU. 26 | 27 | > **US$2** on AliExpress 28 | 29 | ![](http://www.cnx-software.com/wp-content/uploads/2016/02/Wemos_D1_mini.jpg) 30 | 31 | + **VS1053** - US$6.50 32 | 33 | A powerful MP3 / ACC / WMA decoder. 34 | 35 | > Less than **US$7** on AliExpress. 36 | 37 | ![](http://img.dxcdn.com/productimages/sku_221526_1.jpg) 38 | 39 | 40 | ## Wiring 41 | | ESP8266 / ESP-12 | VS1053 | Middle Button | Left Button | Right Button | 42 | | ---------------- | ------ | ------------- | ----------- | ------------ | 43 | | D0 | XDCS | | | | 44 | | D1 | XCS | | | | 45 | | D2 | DREQ | | | | 46 | | D3 | | | | PIN1 | 47 | | D4 | | | PIN1 | | 48 | | D5 | SCLK | | | | 49 | | D6 | MISO | | | | 50 | | D7 | MOSI | | | | 51 | | D8 | | PIN1 | | | 52 | | **RST** | XRST | | | | 53 | | **5v** | VCC | PIN2 | | | 54 | | **GND** | GND | | PIN2 | PIN2 | 55 | 56 | 57 | 58 | ## Controlling 59 | 60 | ### Button Controller 61 | 62 | * **Power Button** 63 | * **Left Button** (D3, pull-up) 64 | - Single click - play previous song / switch to previous station. 65 | - Long-press - decrease volume. 66 | - Press and hold for 1.5 seconds - restart SmartPod. 67 | * **Right Button** (D4, pull-up) 68 | - Single click - play next song / switch to next station. 69 | - Long-press - increase volume. 70 | * **Middle Button** (A0, connect to 5v) 71 | - Single click - play / pause. 72 | - Long-press - switch mode between radio and music. 73 | -------------------------------------------------------------------------------- /lib/Console/Console.cpp: -------------------------------------------------------------------------------- 1 | #include "Console.h" 2 | 3 | void Console::begin(int baudRate) 4 | { 5 | Serial.begin(baudRate); 6 | Serial.println("\n\n"); 7 | } 8 | 9 | char *Console::appendEntry(ConsoleLogLevel level, char *message) 10 | { 11 | if (level >= Console::logLevel) 12 | { 13 | switch (level) 14 | { 15 | case DEBUG: 16 | Serial.print("[DBG]"); 17 | Serial.print(" "); 18 | Serial.println(message); 19 | break; 20 | case INFO: 21 | Serial.print("[INF]"); 22 | Serial.print(" "); 23 | Serial.println(message); 24 | break; 25 | case WARN: 26 | Serial.print("[WRN]"); 27 | Serial.print(" ** "); 28 | Serial.print(message); 29 | Serial.println(" ** "); 30 | break; 31 | case ERROR: 32 | Serial.print("[ERR]"); 33 | Serial.print(" >> "); 34 | Serial.print(message); 35 | Serial.println(" << "); 36 | break; 37 | case FATAL: 38 | Serial.println(); 39 | Serial.println("=============================== [FATAL] ==============================="); 40 | Serial.println(message); 41 | Serial.println(); 42 | break; 43 | } 44 | } 45 | return message; 46 | } 47 | 48 | 49 | 50 | void Console::line() 51 | { 52 | if (INFO >= Console::logLevel) 53 | { 54 | Serial.println("--------------------------------------------------------------------------------"); 55 | } 56 | } 57 | 58 | char *Console::debug(const char *format, ...) 59 | { 60 | static char sbuf[DEBUG_BUFFER_SIZE]; 61 | va_list varArgs; 62 | va_start(varArgs, format); 63 | vsnprintf(sbuf, sizeof(sbuf), format, varArgs); 64 | va_end(varArgs); 65 | return appendEntry(DEBUG, sbuf); 66 | } 67 | 68 | char *Console::info(const char *format, ...) 69 | { 70 | static char sbuf[DEBUG_BUFFER_SIZE]; 71 | va_list varArgs; 72 | va_start(varArgs, format); 73 | vsnprintf(sbuf, sizeof(sbuf), format, varArgs); 74 | va_end(varArgs); 75 | 76 | return appendEntry(INFO, sbuf); 77 | } 78 | 79 | char *Console::warn(const char *format, ...) 80 | { 81 | static char sbuf[DEBUG_BUFFER_SIZE]; 82 | va_list varArgs; 83 | va_start(varArgs, format); 84 | vsnprintf(sbuf, sizeof(sbuf), format, varArgs); 85 | va_end(varArgs); 86 | return appendEntry(WARN, sbuf); 87 | } 88 | 89 | char *Console::error(const char *format, ...) 90 | { 91 | static char sbuf[DEBUG_BUFFER_SIZE]; 92 | va_list varArgs; 93 | va_start(varArgs, format); 94 | vsnprintf(sbuf, sizeof(sbuf), format, varArgs); 95 | va_end(varArgs); 96 | return appendEntry(ERROR, sbuf); 97 | } 98 | 99 | char *Console::fatal(const char *format, ...) 100 | { 101 | static char sbuf[DEBUG_BUFFER_SIZE]; 102 | va_list varArgs; 103 | va_start(varArgs, format); 104 | vsnprintf(sbuf, sizeof(sbuf), format, varArgs); 105 | va_end(varArgs); 106 | return appendEntry(FATAL, sbuf); 107 | } 108 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Smart Radio - A ESP8266 and VS1053 driven WebRadio and Internet music player. 3 | * 4 | * Creator: Henry Li 5 | */ 6 | 7 | // Libraries for Arduino 8 | #include 9 | #include 10 | 11 | // Libraries for ESP8266 - Arduino 12 | #include 13 | #include 14 | #include 15 | 16 | // Libraries for Hardwares 17 | #include 18 | 19 | // Libraries by me 20 | #include 21 | #include 22 | 23 | #include "./controller/ButtonController.h" 24 | #include "./pod/SmartPod.h" 25 | #include "./wifi/WiFiConnector.h" 26 | 27 | // In order to invoke system_update_cpu_freq(), 28 | // we have to include "user_interface.h". 29 | extern "C" { 30 | #include "user_interface.h" 31 | } 32 | 33 | 34 | // Global Variables 35 | 36 | // Global Objects 37 | SmartPod smartPod; 38 | ButtonController buttonController(&smartPod); 39 | 40 | 41 | void setup() 42 | { 43 | Console::begin(); 44 | Console::line(); 45 | Console::info("SmartPod is now starting..."); 46 | 47 | 48 | // ** Setup CPU ** 49 | // Set to 160 MHz in order to get better I/O performance 50 | // Set to 80 MHz in order to save power 51 | // With ESP8266 running at 80 MHz, it is capable of handling up to 256 kb bitrate. 52 | // With ESP8266 running at 160 MHz, it is capable of handling up to 320 kb bitrate. 53 | const int CPU_SPEED = 80; 54 | Console::info("Setting CPU frequency to %d Mhz...", CPU_SPEED); 55 | system_update_cpu_freq(CPU_SPEED); 56 | 57 | 58 | // ** Setup File System ** 59 | // Here we use SPIFFS(ESP8266 built-in File System) to store stations and other settings, 60 | // as well as short sound effects. 61 | Console::info("Setting up file system...."); 62 | SPIFFS.begin(); 63 | Console::info("Files:"); 64 | Dir dir = SPIFFS.openDir("/"); // Show files in FS 65 | while (dir.next()) 66 | { 67 | Console::info("%32s - %7d", dir.fileName().c_str(), dir.fileSize()); 68 | } 69 | 70 | 71 | // ** Setup WiFi ** 72 | // According to my personal experiments, WiFi must be started before VS1053, 73 | // otherwise WiFi could not be well connected. 74 | // I don't know why, maybe can be solved in the future. 75 | Console::info("Setting up WiFi..."); 76 | bool wifiConnected = WiFiConnector::begin(); 77 | if (!wifiConnected) 78 | { 79 | Console::fatal("SmartPod failed to start up."); 80 | return; 81 | } 82 | // Setup OTA firmware update 83 | Console::info("Setting up OTA..."); 84 | ArduinoOTA.begin(); 85 | 86 | 87 | // ** Setup VS1053 ** 88 | SPI.begin(); 89 | bool smartPodEnabled = smartPod.begin(); 90 | if (!smartPodEnabled) 91 | { 92 | Console::fatal("SmartPod failed to start up."); 93 | Console::info("Reboot after 1 second."); 94 | delay(1000); 95 | ESP.restart(); 96 | return; 97 | } 98 | 99 | 100 | // Setup Buttons 101 | buttonController.begin(); 102 | 103 | 104 | /** 105 | * YES, WE'RE READY TO PLAY! 106 | */ 107 | Console::info("SmartPod is now running..."); 108 | 109 | smartPod.switchMode(RADIO_POD); 110 | } 111 | 112 | void loop() 113 | { 114 | // buttonController.handle(); 115 | ArduinoOTA.handle(); 116 | buttonController.handle(); 117 | smartPod.handle(); 118 | } 119 | -------------------------------------------------------------------------------- /src/player/MediaPlayer.cpp: -------------------------------------------------------------------------------- 1 | #include "MediaPlayer.h" 2 | 3 | #include 4 | #include 5 | 6 | MediaPlayer::MediaPlayer(VS1053 *vs1053) 7 | { 8 | _vs1053 = vs1053; 9 | } 10 | 11 | VS1053* MediaPlayer::getVS1053() 12 | { 13 | return _vs1053; 14 | } 15 | 16 | bool MediaPlayer::open(String location) 17 | { 18 | close(); 19 | 20 | _totalBytes = 0; 21 | _eof = false; 22 | 23 | bool success = false; 24 | 25 | if (location.startsWith("/")) 26 | { 27 | if (!_localMediaStream) 28 | { 29 | _localMediaStream = new LocalMediaStream(); 30 | } 31 | _mediaStream = _localMediaStream; 32 | success = _mediaStream->open(location); 33 | if (!success) 34 | { 35 | _eof = true; 36 | } 37 | } 38 | else 39 | { 40 | URL url = parseURL(location); 41 | if (url.isValid) 42 | { 43 | if (url.protocol.equalsIgnoreCase("http")) 44 | { 45 | if (!_httpMediaStream) 46 | { 47 | _httpMediaStream = new HTTPMediaStream(); 48 | } 49 | _mediaStream = _httpMediaStream; 50 | success = _mediaStream->open(location); 51 | if (!success) 52 | { 53 | _eof = true; 54 | } 55 | } 56 | else 57 | { 58 | Console::error("Invalid protocol. Currently SmartPod only support HTTP protocol."); 59 | _eof = true; 60 | return false; 61 | } 62 | } 63 | else 64 | { 65 | Console::error("Invalid URL."); 66 | _eof = true; 67 | return false; 68 | } 69 | } 70 | 71 | if (_mediaStream && _mediaStream->isValid()) 72 | { 73 | Console::debug("File size: %d", _mediaStream->totalSize()); 74 | return true; 75 | } 76 | else 77 | { 78 | return false; 79 | } 80 | } 81 | 82 | void MediaPlayer::close() 83 | { 84 | if (_mediaStream) 85 | { 86 | _mediaStream->close(); 87 | } 88 | } 89 | 90 | bool MediaPlayer::isClosed() 91 | { 92 | if (_mediaStream) 93 | { 94 | if (_mediaStream->isValid()) 95 | { 96 | return _mediaStream->isClosed(); 97 | } 98 | } 99 | return true; 100 | } 101 | 102 | bool MediaPlayer::isEOF() 103 | { 104 | return _eof; 105 | } 106 | 107 | void MediaPlayer::handle() 108 | { 109 | _handleMediaStream(); 110 | } 111 | 112 | void MediaPlayer::_handleMediaStream() 113 | { 114 | if (!_mediaStream || !_mediaStream->isValid() || _mediaStream->isClosed() || isEOF()) return; 115 | 116 | static __attribute__((aligned(4))) char buffer[MEDIA_PLAYER_BUFFER_SIZE]; 117 | 118 | // First we need to confirm whether VS1053 is enable to receive data. 119 | if (_vs1053->data_request()) 120 | { 121 | size_t len = _mediaStream->available(); 122 | if (len == 0) 123 | { 124 | return; 125 | } 126 | 127 | // Check if the buffer is too small for inputStream 128 | // In most case, it is. 129 | if (len > MEDIA_PLAYER_BUFFER_SIZE) 130 | { 131 | len = MEDIA_PLAYER_BUFFER_SIZE; 132 | } 133 | 134 | len = _mediaStream->readBytes(buffer, len); 135 | if (len > 0) 136 | { 137 | _totalBytes += len; 138 | _vs1053->playChunk((uint8 *)buffer, len); 139 | if (_mediaStream->totalSize() > 0 && _totalBytes == _mediaStream->totalSize()) 140 | { 141 | _eof = true; 142 | Console::debug("End of media stream."); 143 | } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/pod/SmartPod.cpp: -------------------------------------------------------------------------------- 1 | #include "SmartPod.h" 2 | 3 | #include 4 | 5 | SmartPod::SmartPod() : _vs1053(VS1053_XCS_PIN, VS1053_XDCS_PIN, VS1053_DREQ_PIN), 6 | Pod(NULL) 7 | { 8 | _mediaPlayer = new MediaPlayer(&_vs1053); 9 | } 10 | 11 | bool SmartPod::begin() 12 | { 13 | bool vs1053Enabled = _vs1053.begin(); 14 | if (!vs1053Enabled) 15 | { 16 | return false; 17 | } 18 | // Set the initial volume 19 | setVolume(80); 20 | return true; 21 | } 22 | 23 | void SmartPod::switchMode(SmartPodMode mode) 24 | { 25 | // Deactive the current Pod 26 | if (_activePod) 27 | { 28 | _activePod->deactivate(); 29 | } 30 | 31 | _mode = mode; 32 | if (_mode == RADIO_POD) 33 | { 34 | Console::info("Switch to [RadioPod] mode."); 35 | if (!_radioPod) 36 | { 37 | _radioPod = new RadioPod(_mediaPlayer); 38 | } 39 | _activePod = _radioPod; 40 | } 41 | else if (_mode == FAV_MUSIC_POD) 42 | { 43 | Console::info("Switch to [FavMusicPod] mode."); 44 | if (!_favMusicPod) 45 | { 46 | _favMusicPod = new FavMusicPod(_mediaPlayer); 47 | } 48 | _activePod = _favMusicPod; 49 | } 50 | 51 | activate(); 52 | } 53 | 54 | void SmartPod::switchMode() 55 | { 56 | if (_mode == RADIO_POD) 57 | { 58 | switchMode(FAV_MUSIC_POD); 59 | } 60 | else if (_mode == FAV_MUSIC_POD) 61 | { 62 | switchMode(RADIO_POD); 63 | } 64 | } 65 | 66 | 67 | 68 | void SmartPod::activate() 69 | { 70 | if (_activePod) 71 | { 72 | _activePod->activate(); 73 | } 74 | } 75 | 76 | void SmartPod::deactivate() 77 | { 78 | if (_activePod) 79 | { 80 | _activePod->deactivate(); 81 | } 82 | } 83 | 84 | void SmartPod::handle() 85 | { 86 | if (_activePod) 87 | { 88 | _activePod->handle(); 89 | } 90 | } 91 | 92 | bool SmartPod::isPlaying() 93 | { 94 | if (_activePod) 95 | { 96 | return _activePod->isPlaying(); 97 | } 98 | else 99 | { 100 | return false; 101 | } 102 | } 103 | 104 | void SmartPod::play() 105 | { 106 | if (_activePod) 107 | { 108 | _activePod->play(); 109 | } 110 | } 111 | 112 | void SmartPod::pause() 113 | { 114 | if (_activePod) 115 | { 116 | _activePod->pause(); 117 | } 118 | } 119 | 120 | void SmartPod::playPause() 121 | { 122 | if (_activePod) 123 | { 124 | if (isPlaying()) 125 | { 126 | pause(); 127 | } 128 | else 129 | { 130 | play(); 131 | } 132 | } 133 | } 134 | 135 | void SmartPod::stop() 136 | { 137 | if (_activePod) 138 | { 139 | _activePod->stop(); 140 | } 141 | } 142 | 143 | void SmartPod::next() 144 | { 145 | if (_activePod) 146 | { 147 | _activePod->next(); 148 | } 149 | } 150 | 151 | void SmartPod::prev() 152 | { 153 | if (_activePod) 154 | { 155 | _activePod->prev(); 156 | } 157 | } 158 | 159 | uint8_t SmartPod::getVolume() 160 | { 161 | if (_activePod) 162 | { 163 | return _activePod->getVolume(); 164 | } 165 | else 166 | { 167 | return 0; 168 | } 169 | } 170 | 171 | void SmartPod::setVolume(uint8_t volume) 172 | { 173 | if (_activePod) 174 | { 175 | _activePod->setVolume(volume); 176 | } 177 | } 178 | 179 | void SmartPod::setVolumeUp() 180 | { 181 | setVolume(getVolume() + 5); 182 | } 183 | 184 | void SmartPod::setVolumeDown() 185 | { 186 | setVolume(getVolume() - 5); 187 | } 188 | 189 | void SmartPod::restart() 190 | { 191 | Console::warn("SmartRadio is going to restart."); 192 | delay(500); 193 | ESP.restart(); 194 | } 195 | 196 | void SmartPod::sleep() 197 | { 198 | Console::warn("SmartRadio is going to sleep."); 199 | delay(500); 200 | ESP.deepSleep(0); 201 | } 202 | -------------------------------------------------------------------------------- /src/pod/BaseListPod.h: -------------------------------------------------------------------------------- 1 | #ifndef BASE_LIST_POD_H 2 | #define BASE_LIST_POD_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "BasePod.h" 12 | 13 | using namespace std; 14 | 15 | class BaseListPod : public BasePod 16 | { 17 | public: 18 | BaseListPod(MediaPlayer* mediaPlayer) : BasePod(mediaPlayer) 19 | { 20 | 21 | } 22 | 23 | 24 | virtual void handle() override 25 | { 26 | if (isPlaying()) 27 | { 28 | _mediaPlayer->handle(); 29 | if (_mediaPlayer->isEOF()) 30 | { 31 | next(); 32 | } 33 | } 34 | } 35 | 36 | 37 | 38 | virtual void loadPlaylist() 39 | { 40 | 41 | } 42 | 43 | virtual void loadPlayListFromLocal(String path) 44 | { 45 | clearPlaylist(); 46 | Console::info("Loading playlist from %s...", path.c_str()); 47 | File playlistStream = SPIFFS.open(path, "r"); 48 | if (playlistStream) 49 | { 50 | while (playlistStream.available()) 51 | { 52 | String line = playlistStream.readStringUntil('\n'); 53 | line.trim(); 54 | if (line.length() == 0 || line.startsWith("#")) 55 | { 56 | continue; 57 | } 58 | addToPlaylist(line); 59 | } 60 | playlistStream.close(); 61 | Console::info("%d items has been loaded to the playlist.", _playlist.size()); 62 | } 63 | else 64 | { 65 | Console::error("Can not load %s.", path.c_str()); 66 | } 67 | _playIndex = 0; 68 | } 69 | 70 | virtual void addToPlaylist(String location) 71 | { 72 | _playlist.push_back(location); 73 | } 74 | 75 | virtual void clearPlaylist() 76 | { 77 | _playlist.clear(); 78 | } 79 | 80 | 81 | 82 | 83 | virtual void activate() 84 | { 85 | if (_playlist.size() == 0) 86 | { 87 | loadPlaylist(); 88 | _playIndex = -1; 89 | _playIndex = getNextPlayIndex(); 90 | } 91 | setVolume(getVolume()); 92 | play(); 93 | } 94 | 95 | virtual void play() override 96 | { 97 | if (!isPlaying()) 98 | { 99 | if (_mediaPlayer->isClosed()) 100 | { 101 | openCurrentMedia(); 102 | } 103 | _playing = true; 104 | } 105 | } 106 | 107 | virtual int getNextPlayIndex() 108 | { 109 | int playIndex = _playIndex + 1; 110 | if (playIndex > _playlist.size() - 1) 111 | { 112 | // Forward to the first item 113 | playIndex = 0; 114 | } 115 | return playIndex; 116 | } 117 | 118 | virtual int getPrevPlayIndex() 119 | { 120 | int playIndex = _playIndex - 1; 121 | if (_playIndex < 0) 122 | { 123 | // Back to the last item 124 | _playIndex = _playlist.size() - 1; 125 | } 126 | return playIndex; 127 | } 128 | 129 | virtual void next() override 130 | { 131 | _playIndex = getNextPlayIndex(); 132 | openCurrentMedia(); 133 | play(); 134 | } 135 | 136 | virtual void prev() override 137 | { 138 | _playIndex = getPrevPlayIndex(); 139 | openCurrentMedia(); 140 | play(); 141 | } 142 | 143 | virtual void openCurrentMedia() 144 | { 145 | if (_playlist.size() == 0) 146 | { 147 | _playIndex = 0; 148 | return; 149 | } 150 | if (_playIndex < 0) 151 | { 152 | _playIndex = _playlist.size() - 1; 153 | } 154 | else if (_playIndex > _playlist.size() - 1) 155 | { 156 | _playIndex = 0; 157 | } 158 | String location = _playlist[_playIndex]; 159 | Console::info("Playing %s", location.c_str()); 160 | _mediaPlayer->open(location); 161 | } 162 | 163 | protected: 164 | vector _playlist; 165 | int _playIndex = 0; 166 | }; 167 | 168 | #endif 169 | -------------------------------------------------------------------------------- /src/stream/HTTPMediaStream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "HTTPMediaStream.h" 4 | 5 | HTTPMediaStream::HTTPMediaStream() 6 | { 7 | 8 | } 9 | 10 | bool HTTPMediaStream::open(String url) 11 | { 12 | Console::info("Loading %s...", url.c_str()); 13 | bool result = _httpClient.begin(url); 14 | if (!result) 15 | { 16 | Console::error("Can not open %s.", url.c_str()); 17 | _valid = false; 18 | _closed = true; 19 | return false; 20 | } 21 | else 22 | { 23 | _httpStream = _httpClient.getStreamPtr(); 24 | int httpCode = _httpClient.GET(); 25 | if (httpCode > 0) 26 | { 27 | if (httpCode == HTTP_CODE_OK) 28 | { 29 | _httpStream = _httpClient.getStreamPtr(); 30 | Console::info("%s has been loaded.", url.c_str()); 31 | 32 | if (_httpClient.getTransferEncoding() == HTTPC_TE_IDENTITY) 33 | { 34 | _totalSize = _httpClient.getSize(); 35 | } 36 | else if (_httpClient.getTransferEncoding() == HTTPC_TE_CHUNKED) 37 | { 38 | Console::debug("Transfer-encoding: chunked"); 39 | _totalSize = 0; 40 | _chunkSize = 0; 41 | 42 | _readChunkSize(); 43 | } 44 | _valid = true; 45 | _closed = false; 46 | return true; 47 | } 48 | else 49 | { 50 | Console::error("%s can not be loaded.", url.c_str()); 51 | Console::error("The server response with HTTP code %d", httpCode); 52 | _valid = false; 53 | _closed = true; 54 | return false; 55 | } 56 | } 57 | else 58 | { 59 | Console::error("Can not open %s.", url.c_str()); 60 | _valid = false; 61 | _closed = true; 62 | return false; 63 | } 64 | } 65 | } 66 | 67 | void HTTPMediaStream::close() 68 | { 69 | if (!_closed) 70 | { 71 | Console::info("Closing HTTPMediaStream..."); 72 | _httpClient.end(); 73 | if (_httpStream) 74 | { 75 | _httpStream->flush(); 76 | _httpStream->stop(); 77 | while (_httpStream->connected()) 78 | { 79 | _httpStream->flush(); 80 | _httpStream->stop(); 81 | delay(100); 82 | } 83 | } 84 | Console::info("HTTPMediaStream closed."); 85 | } 86 | _valid = false; 87 | _closed = true; 88 | _totalSize = 0; 89 | _chunkSize = 0; 90 | _chunkIndex = 0; 91 | } 92 | 93 | int HTTPMediaStream::available() 94 | { 95 | if (!_closed) 96 | { 97 | return _httpStream->available(); 98 | } 99 | else 100 | { 101 | return 0; 102 | } 103 | } 104 | 105 | int HTTPMediaStream::read() 106 | { 107 | if (!_closed && _httpStream) 108 | { 109 | if (_httpClient.getTransferEncoding() == HTTPC_TE_CHUNKED) 110 | { 111 | _chunkIndex++; 112 | if (_chunkIndex >= _chunkSize - 1 && _httpStream->peek() == '\r') 113 | { 114 | _httpStream->read(); 115 | _httpStream->read(); 116 | _readChunkSize(); 117 | _chunkIndex++; 118 | } 119 | } 120 | return _httpStream->read(); 121 | } 122 | else 123 | { 124 | return 0; 125 | } 126 | } 127 | 128 | int HTTPMediaStream::peek() 129 | { 130 | if (!_closed && _httpStream) 131 | { 132 | return _httpStream->peek(); 133 | } 134 | else 135 | { 136 | return 0; 137 | } 138 | } 139 | 140 | void HTTPMediaStream::flush() 141 | { 142 | if (!_closed && _httpStream) 143 | { 144 | _httpStream->flush(); 145 | } 146 | } 147 | 148 | int HTTPMediaStream::totalSize() 149 | { 150 | return _totalSize; 151 | } 152 | 153 | 154 | int __parseHex(const char *str) 155 | { 156 | return (int) strtol(str, 0, 16); 157 | } 158 | 159 | void HTTPMediaStream::_readChunkSize() 160 | { 161 | // plus the last chunkSize 162 | String chunkSizeStr = _httpStream->readStringUntil('\n'); 163 | _chunkSize = __parseHex(chunkSizeStr.c_str()); 164 | _chunkIndex = -1; 165 | //_totalSize += _chunkSize; 166 | 167 | //Console::debug("Chunk size in hex: %s", chunkSizeStr.c_str()); 168 | //Console::debug("Chunk size: %d", _chunkSize); 169 | } 170 | -------------------------------------------------------------------------------- /lib/VS1053/VS1053.h: -------------------------------------------------------------------------------- 1 | #ifndef VS1053_H 2 | #define VS1053_H 3 | 4 | #include 5 | #include 6 | 7 | class VS1053 8 | { 9 | private: 10 | uint8_t cs_pin ; // Pin where CS line is connected 11 | uint8_t dcs_pin ; // Pin where DCS line is connected 12 | uint8_t dreq_pin ; // Pin where DREQ line is connected 13 | uint8_t curvol ; // Current volume setting 0..100% 14 | const uint8_t vs1053_chunk_size = 32 ; 15 | // SCI Register 16 | const uint8_t SCI_MODE = 0x0 ; 17 | const uint8_t SCI_BASS = 0x2 ; 18 | const uint8_t SCI_CLOCKF = 0x3 ; 19 | const uint8_t SCI_AUDATA = 0x5 ; 20 | const uint8_t SCI_WRAM = 0x6 ; 21 | const uint8_t SCI_WRAMADDR = 0x7 ; 22 | const uint8_t SCI_AIADDR = 0xA ; 23 | const uint8_t SCI_VOL = 0xB ; 24 | const uint8_t SCI_AICTRL0 = 0xC ; 25 | const uint8_t SCI_AICTRL1 = 0xD ; 26 | const uint8_t SCI_num_registers = 0xF ; 27 | // SCI_MODE bits 28 | const uint8_t SM_SDINEW = 11 ; // Bitnumber in SCI_MODE always on 29 | const uint8_t SM_RESET = 2 ; // Bitnumber in SCI_MODE soft reset 30 | const uint8_t SM_CANCEL = 3 ; // Bitnumber in SCI_MODE cancel song 31 | const uint8_t SM_TESTS = 5 ; // Bitnumber in SCI_MODE for tests 32 | const uint8_t SM_LINE1 = 14 ; // Bitnumber in SCI_MODE for Line input 33 | SPISettings VS1053_SPI ; // SPI settings for this slave 34 | uint8_t endFillByte ; // Byte to send when stopping song 35 | protected: 36 | inline void await_data_request() const 37 | { 38 | while ( !digitalRead ( dreq_pin ) ) 39 | { 40 | yield() ; // Very short delay 41 | } 42 | } 43 | 44 | inline void control_mode_on() const 45 | { 46 | SPI.beginTransaction ( VS1053_SPI ) ; // Prevent other SPI users 47 | digitalWrite ( dcs_pin, HIGH ) ; // Bring slave in control mode 48 | digitalWrite ( cs_pin, LOW ) ; 49 | } 50 | 51 | inline void control_mode_off() const 52 | { 53 | digitalWrite ( cs_pin, HIGH ) ; // End control mode 54 | SPI.endTransaction() ; // Allow other SPI users 55 | } 56 | 57 | inline void data_mode_on() const 58 | { 59 | SPI.beginTransaction ( VS1053_SPI ) ; // Prevent other SPI users 60 | digitalWrite ( cs_pin, HIGH ) ; // Bring slave in data mode 61 | digitalWrite ( dcs_pin, LOW ) ; 62 | } 63 | 64 | inline void data_mode_off() const 65 | { 66 | digitalWrite ( dcs_pin, HIGH ) ; // End data mode 67 | SPI.endTransaction() ; // Allow other SPI users 68 | } 69 | 70 | uint16_t read_register ( uint8_t _reg ) const ; 71 | void write_register ( uint8_t _reg, uint16_t _value ) const ; 72 | void sdi_send_buffer ( uint8_t* data, size_t len ) ; 73 | void sdi_send_fillers ( size_t length ) ; 74 | void wram_write ( uint16_t address, uint16_t data ) ; 75 | uint16_t wram_read ( uint16_t address ) ; 76 | 77 | public: 78 | // Constructor. Only sets pin values. Doesn't touch the chip. Be sure to call begin()! 79 | VS1053 ( uint8_t _cs_pin, uint8_t _dcs_pin, uint8_t _dreq_pin ) ; 80 | bool begin() ; // Begin operation. Sets pins correctly, 81 | // and prepares SPI bus. 82 | void startSong() ; // Prepare to start playing. Call this each 83 | // time a new song starts. 84 | void playChunk ( uint8_t* data, size_t len ) ; // Play a chunk of data. Copies the data to 85 | // the chip. Blocks until complete. 86 | void stopSong() ; // Finish playing a song. Call this after 87 | // the last playChunk call. 88 | void setVolume ( uint8_t vol ) ; // Set the player volume.Level from 0-100, 89 | // higher is louder. 90 | void setTone ( uint8_t* rtone ) ; // Set the player baas/treble, 4 nibbles for 91 | // treble gain/freq and bass gain/freq 92 | uint8_t getVolume() ; // Get the currenet volume setting. 93 | // higher is louder. 94 | void printDetails ( const char *header ) ; // Print configuration details to serial output. 95 | void softReset() ; // Do a soft reset 96 | bool testComm ( const char *header ) ; // Test communication with module 97 | inline bool data_request() const 98 | { 99 | return ( digitalRead ( dreq_pin ) == HIGH ) ; 100 | } 101 | }; 102 | 103 | #endif 104 | -------------------------------------------------------------------------------- /src/controller/ButtonController.cpp: -------------------------------------------------------------------------------- 1 | #include "ButtonController.h" 2 | 3 | #include 4 | 5 | #include "../wiring.h" 6 | 7 | ButtonController::ButtonController(SmartPod *smartPod) 8 | { 9 | _smartPod = smartPod; 10 | } 11 | 12 | void ButtonController::begin() 13 | { 14 | pinMode(MIDDLE_BUTTON_PIN, INPUT); 15 | pinMode(RIGHT_BUTTON_PIN, INPUT_PULLUP); 16 | pinMode(LEFT_BUTTON_PIN, INPUT_PULLUP); 17 | } 18 | 19 | void ButtonController::handle() 20 | { 21 | bool pressed; 22 | 23 | pressed = digitalRead(MIDDLE_BUTTON_PIN) == HIGH; 24 | if (_middleButtonDown == 0) 25 | { 26 | // If the button has just been pressed. 27 | if (pressed && millis() - _middleButtonUp > BUTTON_PRESS_BETWEEN) 28 | { 29 | _middleButtonDown = millis(); 30 | } 31 | } 32 | else 33 | { 34 | // If the button has just been released. 35 | if (pressed) 36 | { 37 | // Holding 38 | if (!_middleButtonLongPressed && 39 | millis() - _middleButtonDown > BUTTON_REPEAT_UNTIL + BUTTON_REPEAT_DURATION) 40 | { 41 | _middleButtonLongPressed = true; 42 | _middleButton_onLongPressed(); 43 | } 44 | else if (millis() - _middleButtonDown > 1500) 45 | { 46 | //_smartPod->sleep(); 47 | return; 48 | } 49 | } 50 | else 51 | { 52 | _middleButtonDown = 0; 53 | _middleButtonUp = millis(); 54 | if (!_middleButtonLongPressed) 55 | { 56 | _middleButtonLongPressed = false; 57 | _middleButton_onPressed(); 58 | } 59 | else 60 | { 61 | _middleButtonLongPressed = false; 62 | } 63 | } 64 | } 65 | 66 | 67 | pressed = digitalRead(LEFT_BUTTON_PIN) == LOW; 68 | if (_leftButtonDown == 0) 69 | { 70 | // If the button has just been pressed. 71 | if (pressed && millis() - _leftButtonUp > BUTTON_PRESS_BETWEEN) 72 | { 73 | _leftButtonDown = millis(); 74 | } 75 | } 76 | else 77 | { 78 | // If the button has just been released. 79 | if (pressed) 80 | { 81 | // Holding 82 | if (millis() - _leftButtonDown > BUTTON_REPEAT_UNTIL && 83 | (_leftButtonHold == 0 || millis() - _leftButtonHold > BUTTON_REPEAT_DURATION)) 84 | { 85 | _leftButtonHold = millis(); 86 | _leftButton_onLongPressed(); 87 | } 88 | } 89 | else 90 | { 91 | _leftButtonDown = 0; 92 | _leftButtonUp = millis(); 93 | if (_leftButtonHold == 0) 94 | { 95 | _leftButton_onPressed(); 96 | } 97 | else 98 | { 99 | // Long hold 100 | _leftButtonHold = 0; 101 | } 102 | } 103 | } 104 | 105 | 106 | pressed = digitalRead(RIGHT_BUTTON_PIN) == LOW; 107 | if (_rightButtonDown == 0) 108 | { 109 | // If the button has just been pressed. 110 | if (pressed && millis() - _rightButtonUp > BUTTON_PRESS_BETWEEN) 111 | { 112 | _rightButtonDown = millis(); 113 | } 114 | } 115 | else 116 | { 117 | // If the button has just been released. 118 | if (pressed) 119 | { 120 | // Holding 121 | if (millis() - _rightButtonDown > BUTTON_REPEAT_UNTIL && 122 | (_rightButtonHold == 0 || millis() - _rightButtonHold > BUTTON_REPEAT_DURATION)) 123 | { 124 | _rightButtonHold = millis(); 125 | _rightButton_onLongPressed(); 126 | } 127 | } 128 | else 129 | { 130 | _rightButtonDown = 0; 131 | _rightButtonUp = millis(); 132 | if (_rightButtonHold == 0) 133 | { 134 | _rightButton_onPressed(); 135 | } 136 | else 137 | { 138 | // Long hold 139 | _rightButtonHold = 0; 140 | } 141 | } 142 | } 143 | } 144 | 145 | 146 | void ButtonController::_middleButton_onPressed() 147 | { 148 | Console::debug("[P] PLAY / PAUSE"); 149 | _smartPod->playPause(); 150 | } 151 | void ButtonController::_middleButton_onLongPressed() 152 | { 153 | Console::debug("[M] SWITCH MODE"); 154 | _smartPod->switchMode(); 155 | } 156 | 157 | 158 | void ButtonController::_rightButton_onPressed() 159 | { 160 | Console::debug("[>] NEXT"); 161 | _smartPod->next(); 162 | } 163 | void ButtonController::_rightButton_onLongPressed() 164 | { 165 | Console::debug("[+] VOLUME UP"); 166 | _smartPod->setVolumeUp(); 167 | } 168 | 169 | 170 | void ButtonController::_leftButton_onPressed() 171 | { 172 | Console::debug("[<] PREVIOUS"); 173 | _smartPod->prev(); 174 | } 175 | void ButtonController::_leftButton_onLongPressed() 176 | { 177 | Console::debug("[-] VOLUME DOWN"); 178 | _smartPod->setVolumeDown(); 179 | } 180 | -------------------------------------------------------------------------------- /lib/VS1053/VS1053.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "VS1053.h" 4 | 5 | VS1053::VS1053(uint8_t _cs_pin, uint8_t _dcs_pin, uint8_t _dreq_pin) 6 | : cs_pin(_cs_pin), dcs_pin(_dcs_pin), dreq_pin(_dreq_pin) 7 | { 8 | } 9 | 10 | uint16_t VS1053::read_register(uint8_t _reg) const 11 | { 12 | uint16_t result; 13 | 14 | control_mode_on(); 15 | SPI.write(3); // Read operation 16 | SPI.write(_reg); // Register to write (0..0xF) 17 | // Note: transfer16 does not seem to work 18 | result = (SPI.transfer(0xFF) << 8) | // Read 16 bits data 19 | (SPI.transfer(0xFF)); 20 | await_data_request(); // Wait for DREQ to be HIGH again 21 | control_mode_off(); 22 | return result; 23 | } 24 | 25 | void VS1053::write_register(uint8_t _reg, uint16_t _value) const 26 | { 27 | control_mode_on(); 28 | SPI.write(2); // Write operation 29 | SPI.write(_reg); // Register to write (0..0xF) 30 | SPI.write16(_value); // Send 16 bits data 31 | await_data_request(); 32 | control_mode_off(); 33 | } 34 | 35 | void VS1053::sdi_send_buffer(uint8_t *data, size_t len) 36 | { 37 | size_t chunk_length; // Length of chunk 32 byte or shorter 38 | 39 | data_mode_on(); 40 | while (len) // More to do? 41 | { 42 | await_data_request(); // Wait for space available 43 | chunk_length = len; 44 | if (len > vs1053_chunk_size) 45 | { 46 | chunk_length = vs1053_chunk_size; 47 | } 48 | len -= chunk_length; 49 | SPI.writeBytes(data, chunk_length); 50 | data += chunk_length; 51 | } 52 | data_mode_off(); 53 | } 54 | 55 | void VS1053::sdi_send_fillers(size_t len) 56 | { 57 | size_t chunk_length; // Length of chunk 32 byte or shorter 58 | 59 | data_mode_on(); 60 | while (len) // More to do? 61 | { 62 | await_data_request(); // Wait for space available 63 | chunk_length = len; 64 | if (len > vs1053_chunk_size) 65 | { 66 | chunk_length = vs1053_chunk_size; 67 | } 68 | len -= chunk_length; 69 | while (chunk_length--) 70 | { 71 | SPI.write(endFillByte); 72 | } 73 | } 74 | data_mode_off(); 75 | } 76 | 77 | void VS1053::wram_write(uint16_t address, uint16_t data) 78 | { 79 | write_register(SCI_WRAMADDR, address); 80 | write_register(SCI_WRAM, data); 81 | } 82 | 83 | uint16_t VS1053::wram_read(uint16_t address) 84 | { 85 | write_register(SCI_WRAMADDR, address); // Start reading from WRAM 86 | return read_register(SCI_WRAM); // Read back result 87 | } 88 | 89 | bool VS1053::testComm(const char *header) 90 | { 91 | // Test the communication with the VS1053 module. The result wille be returned. 92 | // If DREQ is low, there is problably no VS1053 connected. Pull the line HIGH 93 | // in order to prevent an endless loop waiting for this signal. The rest of the 94 | // software will still work, but readbacks from VS1053 will fail. 95 | int i; // Loop control 96 | uint16_t r1, r2, cnt = 0; 97 | uint16_t delta = 300; // 3 for fast SPI 98 | 99 | if (!digitalRead(dreq_pin)) 100 | { 101 | Console::error("VS1053 not properly installed!"); 102 | // Allow testing without the VS1053 module 103 | pinMode(dreq_pin, INPUT_PULLUP); // DREQ is now input with pull-up 104 | return false; // Return bad result 105 | } 106 | // Further TESTING. Check if SCI bus can write and read without errors. 107 | // We will use the volume setting for this. 108 | // Will give warnings on serial output if DEBUG is active. 109 | // A maximum of 20 errors will be reported. 110 | if (strstr(header, "Fast")) 111 | { 112 | delta = 3; // Fast SPI, more loops 113 | } 114 | Console::info(header); // Show a header 115 | for (i = 0; (i < 0xFFFF) && (cnt < 20); i += delta) 116 | { 117 | write_register(SCI_VOL, i); // Write data to SCI_VOL 118 | r1 = read_register(SCI_VOL); // Read back for the first time 119 | r2 = read_register(SCI_VOL); // Read back a second time 120 | if (r1 != r2 || i != r1 || i != r2) // Check for 2 equal reads 121 | { 122 | Console::error("VS1053 error retry SB:%04X R1:%04X R2:%04X", i, r1, r2); 123 | cnt++; 124 | delay(10); 125 | } 126 | yield(); // Allow ESP firmware to do some bookkeeping 127 | } 128 | return (cnt == 0); // Return the result 129 | } 130 | 131 | bool VS1053::begin() 132 | { 133 | bool result = false; 134 | pinMode(dreq_pin, INPUT); // DREQ is an input 135 | pinMode(cs_pin, OUTPUT); // The SCI and SDI signals 136 | pinMode(dcs_pin, OUTPUT); 137 | digitalWrite(dcs_pin, HIGH); // Start HIGH for SCI en SDI 138 | digitalWrite(cs_pin, HIGH); 139 | delay(100); 140 | Console::info("Reset VS1053..."); 141 | digitalWrite(dcs_pin, LOW); // Low & Low will bring reset pin low 142 | digitalWrite(cs_pin, LOW); 143 | delay(500); 144 | Console::info("End reset VS1053..."); 145 | digitalWrite(dcs_pin, HIGH); // Back to normal again 146 | digitalWrite(cs_pin, HIGH); 147 | delay(500); 148 | // Init SPI in slow mode ( 0.2 MHz ) 149 | VS1053_SPI = SPISettings(200000, MSBFIRST, SPI_MODE0); 150 | // printDetails ( "Right after reset/startup" ) ; 151 | delay(20); 152 | // printDetails ( "20 msec after reset" ) ; 153 | result = testComm("Slow SPI,Testing VS1053 read/write registers..."); 154 | // Most VS1053 modules will start up in midi mode. The result is that there is no audio 155 | // when playing MP3. You can modify the board, but there is a more elegant way: 156 | wram_write(0xC017, 3); // GPIO DDR = 3 157 | wram_write(0xC019, 0); // GPIO ODATA = 0 158 | delay(100); 159 | // printDetails ( "After test loop" ) ; 160 | softReset(); // Do a soft reset 161 | // Switch on the analog parts 162 | write_register(SCI_AUDATA, 44100 + 1); // 44.1kHz + stereo 163 | // The next clocksetting allows SPI clocking at 5 MHz, 4 MHz is safe then. 164 | write_register(SCI_CLOCKF, 6 << 12); // Normal clock settings multiplyer 3.0 = 12.2 MHz 165 | // SPI Clock to 4 MHz. Now you can set high speed SPI clock. 166 | VS1053_SPI = SPISettings(4000000, MSBFIRST, SPI_MODE0); 167 | write_register(SCI_MODE, _BV(SM_SDINEW) | _BV(SM_LINE1)); 168 | result = testComm("Fast SPI, Testing VS1053 read/write registers again..."); 169 | delay(10); 170 | await_data_request(); 171 | endFillByte = wram_read(0x1E06) & 0xFF; 172 | Console::info("endFillByte is %X", endFillByte); 173 | // printDetails ( "After last clocksetting" ) ; 174 | delay(100); 175 | return result; 176 | } 177 | 178 | void VS1053::setVolume(uint8_t vol) 179 | { 180 | // Set volume. Both left and right. 181 | // Input value is 0..100. 100 is the loudest. 182 | uint16_t value; // Value to send to SCI_VOL 183 | 184 | if (vol != curvol) 185 | { 186 | curvol = vol; // Save for later use 187 | value = map(vol, 0, 100, 0xFF, 0x00); // 0..100% to one channel 188 | value = (value << 8) | value; 189 | write_register(SCI_VOL, value); // Volume left and right 190 | } 191 | } 192 | 193 | void VS1053::setTone(uint8_t *rtone) // Set bass/treble (4 nibbles) 194 | { 195 | // Set tone characteristics. See documentation for the 4 nibbles. 196 | uint16_t value = 0; // Value to send to SCI_BASS 197 | int i; // Loop control 198 | 199 | for (i = 0; i < 4; i++) 200 | { 201 | value = (value << 4) | rtone[i]; // Shift next nibble in 202 | } 203 | write_register(SCI_BASS, value); // Volume left and right 204 | } 205 | 206 | uint8_t VS1053::getVolume() // Get the currenet volume setting. 207 | { 208 | return curvol; 209 | } 210 | 211 | void VS1053::startSong() 212 | { 213 | sdi_send_fillers(10); 214 | } 215 | 216 | void VS1053::playChunk(uint8_t *data, size_t len) 217 | { 218 | sdi_send_buffer(data, len); 219 | } 220 | 221 | void VS1053::stopSong() 222 | { 223 | uint16_t modereg; // Read from mode register 224 | int i; // Loop control 225 | 226 | sdi_send_fillers(2052); 227 | delay(10); 228 | write_register(SCI_MODE, _BV(SM_SDINEW) | _BV(SM_CANCEL)); 229 | for (i = 0; i < 200; i++) 230 | { 231 | sdi_send_fillers(32); 232 | modereg = read_register(SCI_MODE); // Read status 233 | if ((modereg & _BV(SM_CANCEL)) == 0) 234 | { 235 | sdi_send_fillers(2052); 236 | Console::info("Song stopped correctly after %d msec", i * 10); 237 | return; 238 | } 239 | delay(10); 240 | } 241 | printDetails("Song stopped incorrectly!"); 242 | } 243 | 244 | void VS1053::softReset() 245 | { 246 | write_register(SCI_MODE, _BV(SM_SDINEW) | _BV(SM_RESET)); 247 | delay(10); 248 | await_data_request(); 249 | } 250 | 251 | void VS1053::printDetails(const char *header) 252 | { 253 | uint16_t regbuf[16]; 254 | uint8_t i; 255 | 256 | Console::info(header); 257 | Console::info("REG Contents"); 258 | Console::info("--- -----"); 259 | for (i = 0; i <= SCI_num_registers; i++) 260 | { 261 | regbuf[i] = read_register(i); 262 | } 263 | for (i = 0; i <= SCI_num_registers; i++) 264 | { 265 | delay(5); 266 | Console::info("%3X - %5X", i, regbuf[i]); 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /data/fav-music-pod.m3u: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | http://m2.music.126.net/YWmZdbsbSery80VGxl0M4Q==/18598239184278161.mp3 3 | http://m2.music.126.net/zaGgmMZiqLCzJTv2GYKkaA==/1365593465215365.mp3 4 | http://m2.music.126.net/jdhQEW6xQVpWiYmfD7p9Ag==/3088528162423768.mp3 5 | http://m2.music.126.net/94oxyVYyEiw1YQfwDGbJAg==/6049512976658657.mp3 6 | http://m2.music.126.net/uM9eHRgJDeNDu8SrsO39yg==/5787829208849643.mp3 7 | http://m2.music.126.net/Frh5YECw42RkRlUHV-toFg==/2067081860237702.mp3 8 | http://m2.music.126.net/jeTtozs1ixJN9bvoAoFJpw==/1109407232433778.mp3 9 | http://m2.music.126.net/mJUPadSGwGOmKrVMlEhXeA==/1164382813822936.mp3 10 | http://m2.music.126.net/AYnQ7kVZXTFei0RtQ_PGTA==/3244658815248018.mp3 11 | http://m2.music.126.net/NG4I9FVAm9jCQCvszfLB8Q==/1377688074172063.mp3 12 | http://m2.music.126.net/rnNlqsHIx7_XxS_l2gVHXQ==/1234751558029817.mp3 13 | http://m2.music.126.net/jWq7b0rgXKq65R1PmBkSsA==/1364493953126545.mp3 14 | http://m2.music.126.net/qoQrTiDl40piNoVuPXE5zg==/6063806627596073.mp3 15 | http://m2.music.126.net/fqN9tQtFLyEgBe3OOC3vzg==/1365593464855669.mp3 16 | http://m2.music.126.net/gjNsDhI591ElEskmJAcRUg==/1288627627772374.mp3 17 | http://m2.music.126.net/XuE3okxRmu7d2wKnOOL_2Q==/1228154488234673.mp3 18 | http://m2.music.126.net/qbgaPKMQIa4pGDIaQacdNA==/1136895023128229.mp3 19 | http://m2.music.126.net/2tAmEVPDTtD9zcxtlQTjTQ==/6022025185922105.mp3 20 | http://m2.music.126.net/FwS2uMO9a3fotu2LBSpVWw==/1067625790579471.mp3 21 | http://m2.music.126.net/Asb7Dp0kYlNVvXN6TXLgXQ==/6657542906299325.mp3 22 | http://m2.music.126.net/rPwAef8p1zPXT9jJodTYtg==/2809252208969987.mp3 23 | http://m2.music.126.net/ycaK5RHqw-Rnz3XvWudaNw==/2808152697345527.mp3 24 | http://m2.music.126.net/feplW2VPVs9Y8lE_I08BQQ==/1386484166585821.mp3 25 | http://m2.music.126.net/ZSM67eYjJz6ypSGy4pxRsg==/1028043371977839.mp3 26 | http://m2.music.126.net/K1D1BRy27_DqsG2ZKztggw==/3380998256726946.mp3 27 | http://m2.music.126.net/K39_veGYFyc6tmCjMTq2mg==/1240249116135648.mp3 28 | http://m2.music.126.net/pQFX9iDQKNkXqGKX8zGqdQ==/6053911022934590.mp3 29 | http://m2.music.126.net/rQbWryOE5TXp6p3uUVoL2A==/7996748070345692.mp3 30 | http://m2.music.126.net/rSUd1fJ9q0xx9ROWFLVYyg==/3417282150115538.mp3 31 | http://m2.music.126.net/qqLu9d--wAbEdUkk258Xhg==/1401877333091957.mp3 32 | http://m2.music.126.net/npJzxDTTGakiLHW2GdIj1g==/7996748070345908.mp3 33 | http://m2.music.126.net/hDrQ4OGIV1C25vw3H03MLA==/1213860837073174.mp3 34 | http://m2.music.126.net/PJ6tlPX_yvKDqeHac9EH7w==/3391993375341328.mp3 35 | http://m2.music.126.net/_o3XoyDcrM6eCJXe4EUb-Q==/3274345627589199.mp3 36 | http://m2.music.126.net/Dnl5IrjEaGZ76cBCpbIoww==/3265549534553483.mp3 37 | http://m2.music.126.net/P2JkAyaXLBvOqfyDUhdBxg==/6625657069032802.mp3 38 | http://m2.music.126.net/dsvnXJo8ZeOd1X8HfhF4TA==/1058829697558980.mp3 39 | http://m2.music.126.net/JJZcYbtFDwlKP68ze8DSpw==/5803222371401991.mp3 40 | http://m2.music.126.net/IRWywrioM8iX4OYFuJOHRQ==/1374389535415469.mp3 41 | http://m2.music.126.net/01-CRaSs6J285om_CH_r8A==/1364493979857069.mp3 42 | http://m2.music.126.net/WO9d282kU2iGYcztn1xNdw==/1234751557993488.mp3 43 | http://m2.music.126.net/i0jb3gLi5dxE-e6fKwbeSA==/3382097774538356.mp3 44 | http://m2.music.126.net/9Py8jhtwE3vDfV_regBavQ==/5964850581190727.mp3 45 | http://m2.music.126.net/ZvVFAsK6r8DmJx0JHQ7IGQ==/5923069139065828.mp3 46 | http://m2.music.126.net/uoVN94g6iLEsn9GHfMgQpg==/3297435380176023.mp3 47 | http://m2.music.126.net/XoyApQJOikAFBJCH0f9EFQ==/6005532510999753.mp3 48 | http://m2.music.126.net/6il3oBfnE0Cr-E2BK1MrAA==/2912606303137308.mp3 49 | http://m2.music.126.net/1GTR7chYs6UgHoUHYdo0CQ==/2040693581171673.mp3 50 | http://m2.music.126.net/jk-p1w6jA7nFRcqz9RvE1g==/1223756441722865.mp3 51 | http://m2.music.126.net/oS5vxdXopn8lWGyLB4LZJQ==/1050033604534161.mp3 52 | http://m2.music.126.net/Koc0l6fkXhurH0vojcFQ_g==/3349112419384632.mp3 53 | http://m2.music.126.net/RlYVistLRk05EmAROb3j5Q==/5909874999382911.mp3 54 | http://m2.music.126.net/pv4J9ecXl2Dd7DxVeTK1AQ==/7831821325731355.mp3 55 | http://m2.music.126.net/4YQ0L52EW-UVs8M-TovmjA==/3125911557829955.mp3 56 | http://m2.music.126.net/0sKET98qZFhEr96tMbwRbQ==/1890060488196560.mp3 57 | http://m2.music.126.net/3QLP4g7tl9NYvu5jvxXRVw==/997257046399788.mp3 58 | http://m2.music.126.net/F3l8CNk2SrKp8DzWHy64Tg==/3437073349214945.mp3 59 | http://m2.music.126.net/2dMcLFxEx-HW0xYuftduwQ==/3428277256131350.mp3 60 | http://m2.music.126.net/gTc81DdVLxAuw3dbDWaIGw==/1247945697569561.mp3 61 | http://m2.music.126.net/l5_pkO7mrdNyRM2d1M8geg==/2013205790470521.mp3 62 | http://m2.music.126.net/82r0xBRCJHtOB6AqtbT4OQ==/2045091627676388.mp3 63 | http://m2.music.126.net/E80_yLuQQ4qhqYePYai6ew==/1302921278932296.mp3 64 | http://m2.music.126.net/dzFgYQ7CDN1v80Dp4p8gjg==/1985717999775280.mp3 65 | http://m2.music.126.net/FBlXeTgL7dxmvVgZOBxkaw==/2089072092775348.mp3 66 | http://m2.music.126.net/MQxloE-qjDCcfwsWKmob2g==/2006608720702461.mp3 67 | http://m2.music.126.net/swS-ctH7SlSdzhbq4XpVDw==/2024200906736471.mp3 68 | http://m2.music.126.net/1MRFJPgZIPPIlpLVucpvcA==/5762540441288426.mp3 69 | http://m2.music.126.net/V4Ychf_KWEwXEhRkhPrV7A==/2030797976513596.mp3 70 | http://m2.music.126.net/rj6wpM4NMlQ28IABf9fb1A==/3284241232244639.mp3 71 | http://m2.music.126.net/9mcS83LSZ47nA-xfbOEWQw==/2531075769234985.mp3 72 | http://m2.music.126.net/r22btSztTs6hPyS-SNYJSg==/3255653929909959.mp3 73 | http://m2.music.126.net/XyB8AbqHEn42pSPrDWo4Bg==/1404076361331957.mp3 74 | http://m2.music.126.net/-fE8w8NTDQuR01_H1A34nQ==/3352410953177268.mp3 75 | http://m2.music.126.net/HKbYQbZzcuuhdXJMJHNRlA==/2079176488135751.mp3 76 | http://m2.music.126.net/_no1zabKHqZZXQVeZitpYg==/5995636906746122.mp3 77 | http://m2.music.126.net/1NVXmtKB8DvknG0p3xgJqA==/5941760836909431.mp3 78 | http://m2.music.126.net/as4U5Aks5CrZ3zObPHpxIg==/7976956861429744.mp3 79 | http://m2.music.126.net/jKLwz4c-0bLstwr1dPOQKA==/7931876884312007.mp3 80 | http://m2.music.126.net/MUlGPKR-DlWF5l3zDYuW8g==/1262239348702541.mp3 81 | http://m2.music.126.net/b4M4bQl-mFiO2rzaK-Ur4Q==/1235851069638091.mp3 82 | http://m2.music.126.net/CBr13lQd4TzaCH2DAuc2QA==/6625657069053492.mp3 83 | http://m2.music.126.net/TYAejEn6Mh7UgObRbCFw7Q==/1136895023128225.mp3 84 | http://m2.music.126.net/UwkcR0OiKAAnCER_JPGgkg==/2051688697431477.mp3 85 | http://m2.music.126.net/jCCrol59NNPINSnbbbxJuw==/3351311441518400.mp3 86 | http://m2.music.126.net/cz83oty7WK6loomeVgrNtQ==/1000555581286636.mp3 87 | http://m2.music.126.net/PfYRuZnlYmk9VPvxvF_N6A==/7972558813545769.mp3 88 | http://m2.music.126.net/A_Ri2JCCF9-u0W5MEP-OFQ==/7968160768710196.mp3 89 | http://m2.music.126.net/BuMtFyQiQusNgDSgaLNy9g==/3252355395019652.mp3 90 | http://m2.music.126.net/qHSLb1_KbEIjvBgruCqsGg==/5881287697089597.mp3 91 | http://m2.music.126.net/GedtBRlYP9Y2ye3rKglwsw==/1201766209172422.mp3 92 | http://m2.music.126.net/D6nDvvPPBHfJZZCihoEarg==/1190771092889316.mp3 93 | http://m2.music.126.net/u9nJupi9yzpKzJB_sgSa1A==/3268848069435294.mp3 94 | http://m2.music.126.net/MvOAxLmt8wwbfNjgFdF-yQ==/1159984767311461.mp3 95 | http://m2.music.126.net/n4T_67OlAl-GZUv__aNoeg==/1190771092888960.mp3 96 | http://m2.music.126.net/ngOpHhl3VvH76STIZ8-X6g==/3412884106751497.mp3 97 | http://m2.music.126.net/-QRwWG0o2lL6xm60vlbOmw==/2797157581107175.mp3 98 | http://m2.music.126.net/UnWJ-OxJ33-Rqe5EMBzjOQ==/2752077604370416.mp3 99 | http://m2.music.126.net/gEBtvF9Oz7GukKuzbPedsA==/2869725348520160.mp3 100 | http://m2.music.126.net/Yk-Dxh6g7YhL9pdgWaQaTg==/18616930882652967.mp3 101 | http://m2.music.126.net/jSb5OKqm8dCnwSkKhOFvxA==/1203965232423404.mp3 102 | http://m2.music.126.net/g1xeLNaAcuvJlNViBko8hA==/1313916395200473.mp3 103 | http://m2.music.126.net/Pmzlb2yP2gG278PdTt3FGQ==/1341404185894446.mp3 104 | http://m2.music.126.net/n_ntp7YRqZstZmV6I0E8qQ==/2026399930000361.mp3 105 | http://m2.music.126.net/oupZ0i2E0dIuY-00MFwkLg==/3116015953126573.mp3 106 | http://m2.music.126.net/b-BbEQuYYRw_CK3jfj8yLA==/5651489766872003.mp3 107 | http://m2.music.126.net/n7v0WwZU6Xx05Z9pbbcf-Q==/3271047092887096.mp3 108 | http://m2.music.126.net/gvuFlxNNClN_5ZFcoySSdA==/2126455488124783.mp3 109 | http://m2.music.126.net/NJLbTt3uIsyzlFsGPFbjkA==/5664683906360566.mp3 110 | http://m2.music.126.net/6DkXzyhqdMFMTzh6k6zG5Q==/5630599045911496.mp3 111 | http://m2.music.126.net/XjxRFh1SgftVhnAGhhogHg==/18566353347467942.mp3 112 | http://m2.music.126.net/0dcvt33he2VONYW1zl631g==/1905453650985007.mp3 113 | http://m2.music.126.net/KYybKrzLcLwZm7kgfWA5mA==/1098412116157355.mp3 114 | http://m2.music.126.net/QOOkvneIJc75K4laD22zxg==/5947258395119217.mp3 115 | http://m2.music.126.net/9YtNqQuXzGrT5ymFhaGqPg==/5990139348705795.mp3 116 | http://m2.music.126.net/KBhnymD9-wFVA6C7D60k-A==/5990139348705789.mp3 117 | http://m2.music.126.net/IYXXu3SultWKVE-SSspcmw==/5947258395119220.mp3 118 | http://m2.music.126.net/l2UMjcX98d48QUAnPysZDg==/5980243744055279.mp3 119 | http://m2.music.126.net/51hQqEufoHt0CmY7ASC2Fw==/5998935441727943.mp3 120 | http://m2.music.126.net/O_vf1xHXO0y9zhOBRAvsMA==/2869725348520145.mp3 121 | http://m2.music.126.net/ZI0X9zB3-zIpuxAqF7Gqqw==/2869725348520148.mp3 122 | http://m2.music.126.net/EJKD4joGu0UewXIpNZC0UQ==/3203976883342086.mp3 123 | http://m2.music.126.net/RmAUgBxGtrxyFIC17_eQJw==/3185285185670031.mp3 124 | http://m2.music.126.net/5JRhJPp5fGH0DD9jEMuNDQ==/3185285185670028.mp3 125 | http://m2.music.126.net/sBPOVzM2kR2N2joCJxg7lg==/1367792479285424.mp3 126 | http://m2.music.126.net/93j4vIgzsVpu0C0DzLmuKw==/3203976883342087.mp3 127 | http://m2.music.126.net/bgSNpjG2tURX3b_JUap-5A==/1140193558011944.mp3 128 | http://m2.music.126.net/cMlOV_XZceHNLA1GpvkyLQ==/7990151000576820.mp3 129 | http://m2.music.126.net/IPrIcKG1HH-T8gulVYUeOQ==/7962663209891978.mp3 130 | http://m2.music.126.net/qh_7jdMm6NezeYKKBF1Nlg==/2785062953164777.mp3 131 | http://m2.music.126.net/rd00CYYmTsV3DICUFxqBIQ==/7967061256393814.mp3 132 | http://m2.music.126.net/PfhAQ9shxvLlvqXh_DEPZw==/1379887092889771.mp3 133 | http://m2.music.126.net/jqThteD_mQT_C6kWyyw3Ng==/3276544650831084.mp3 134 | http://m2.music.126.net/HW3whPtqXzcagZKPe4QaZQ==/1094014069645039.mp3 135 | http://m2.music.126.net/Vr5qBpI7C68ZqLQ0da4KgQ==/1019247278956113.mp3 136 | http://m2.music.126.net/s51bvO3NT-s-ENK9o0tW8w==/2009907255585797.mp3 137 | http://m2.music.126.net/kdmVoHkL1G-RbeXnsr1rnw==/6624557558012901.mp3 138 | http://m2.music.126.net/n1PwHxO24g-fb1hi5q-74A==/7705377489711813.mp3 139 | http://m2.music.126.net/L0C55vlEJU1eX4k5h2oqfg==/1153387697546160.mp3 140 | http://m2.music.126.net/wYRPxwJxFCQl_TP7gf1Ktw==/3355709488029833.mp3 141 | http://m2.music.126.net/xVdJ0riTR864J6YpoaEqnw==/8000046605472064.mp3 142 | http://m2.music.126.net/I5u9FNms3ZzT0XOan4sQxQ==/1032441418489938.mp3 143 | http://m2.music.126.net/8-6oAu5G9Piv-4M7BqNEnw==/1221557418467266.mp3 144 | http://m2.music.126.net/mEpUSQbphWf0zhZyGOOIBw==/3252355395019646.mp3 145 | http://m2.music.126.net/KKp21TQGnXNi4qpsEf0aoA==/1196268651031652.mp3 146 | http://m2.music.126.net/lLIbBFeAjQc-NslK7THH3A==/6067105162702941.mp3 147 | http://m2.music.126.net/WSJDWdZiNY5Kvsv2maJ8ig==/6067105162702945.mp3 148 | http://m2.music.126.net/_XDsFIX6V62uqlCEzSxgTg==/1089616023133579.mp3 149 | http://m2.music.126.net/gE-zSDZTMsSDjBOkovCnVQ==/1212761325444253.mp3 150 | http://m2.music.126.net/iDb1r9eGdFdAqbDFi68xWQ==/1142392581266634.mp3 151 | http://m2.music.126.net/Pn9l4gSR0IaMjNN1GU49oQ==/1352399302178704.mp3 152 | http://m2.music.126.net/Cws6z_gxRsJFf6EPEclOzQ==/1177576953362257.mp3 153 | http://m2.music.126.net/7m3vVNIkNrz7e6TH2teXtA==/3135807162425963.mp3 154 | http://m2.music.126.net/QZu18wVZ9w5Hvq2huICxTw==/1002754604539933.mp3 155 | http://m2.music.126.net/URsCbdHdjhJixV7x3m1oGw==/1217159371956094.mp3 156 | http://m2.music.126.net/BU9UrUAaKxBFN1-c09drAA==/3122613022905773.mp3 157 | http://m2.music.126.net/mJmlquq1eJhL1t3VwAZPUw==/3156697883395736.mp3 158 | http://m2.music.126.net/vXnnAZFGZ30g5eCK_ccnUA==/1207263767307060.mp3 159 | http://m2.music.126.net/uaUtfWEUhvYlZ0SiXGGUMA==/6058309069503414.mp3 160 | http://m2.music.126.net/Q-iLjEnh-hwbro8SgtUoqA==/2069280883483485.mp3 161 | http://m2.music.126.net/UNMUm0k4f0IMpy0Q75zXsw==/1014849232446220.mp3 162 | http://m2.music.126.net/a5RHhN_F21v1KwcGOtiWqw==/1007152651051083.mp3 163 | http://m2.music.126.net/1MV8AvWbsQhLOQQpN5yuDA==/7978056372666719.mp3 164 | http://m2.music.126.net/at0wmUoxoCMqDJbJt1QFeQ==/5935163767130356.mp3 165 | http://m2.music.126.net/V74J3gDPErqqI5mCxNan5Q==/3307330976899900.mp3 166 | http://m2.music.126.net/GrDhpWfE23Yg7PZ47cvpPA==/3108319371797657.mp3 167 | http://m2.music.126.net/TJlSiCl6AmUgnHxUiCg71w==/3324923162493467.mp3 168 | http://m2.music.126.net/u6MDDz9PM2t4X2xaumQGcg==/7929677861734938.mp3 169 | http://m2.music.126.net/FZgFZFkO86iEkDEGt8b5hw==/2818048302032814.mp3 170 | --------------------------------------------------------------------------------