├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SpotifyClient.cpp ├── SpotifyClient.h ├── TouchControllerWS.cpp ├── TouchControllerWS.h ├── esp8266-spotify-remote.ino ├── esp8266-spotify-remote.sln ├── esp8266-spotify-remote.vcxproj ├── esp8266-spotify-remote.vcxproj.filters ├── images.h ├── images ├── SpotifyAppSettings.png ├── SpotifyAppSettingsSave.png ├── SpotifyAppSignUp1.png ├── SpotifyConnectScreen.png ├── SpotifyCredentials.png ├── SpotifyDashboard.png └── SpotifyppSignUp3.png ├── mem-analysis.txt └── settings.h /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Make sure all boxes are checked (add 'x' inside the brackets) when you submit your contribution, **remove this sentence** before doing so. 2 | 3 | - [ ] This PR is compliant with the [other contributing guidelines](https://github.com/ThingPulse/esp8266-spotify-remote/blob/master/CONTRIBUTING.md) as well (if not, please describe why). 4 | - [ ] I have thoroughly tested my contribution. 5 | - [ ] I have updated README if my changes require changes to documentation there. 6 | 7 | \ 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | [Ss]tyle[Cc]op.* 182 | ~$* 183 | *~ 184 | *.dbmdl 185 | *.dbproj.schemaview 186 | *.pfx 187 | *.publishsettings 188 | node_modules/ 189 | orleans.codegen.cs 190 | 191 | # RIA/Silverlight projects 192 | Generated_Code/ 193 | 194 | # Backup & report files from converting an old project file 195 | # to a newer Visual Studio version. Backup files are not needed, 196 | # because we have git ;-) 197 | _UpgradeReport_Files/ 198 | Backup*/ 199 | UpgradeLog*.XML 200 | UpgradeLog*.htm 201 | 202 | # SQL Server files 203 | *.mdf 204 | *.ldf 205 | 206 | # Business Intelligence projects 207 | *.rdl.data 208 | *.bim.layout 209 | *.bim_*.settings 210 | 211 | # Microsoft Fakes 212 | FakesAssemblies/ 213 | 214 | # GhostDoc plugin setting file 215 | *.GhostDoc.xml 216 | 217 | # Node.js Tools for Visual Studio 218 | .ntvs_analysis.dat 219 | 220 | # Visual Studio 6 build log 221 | *.plg 222 | 223 | # Visual Studio 6 workspace options file 224 | *.opt 225 | 226 | # Visual Studio LightSwitch build output 227 | **/*.HTMLClient/GeneratedArtifacts 228 | **/*.DesktopClient/GeneratedArtifacts 229 | **/*.DesktopClient/ModelManifest.xml 230 | **/*.Server/GeneratedArtifacts 231 | **/*.Server/ModelManifest.xml 232 | _Pvt_Extensions 233 | 234 | # LightSwitch generated files 235 | GeneratedArtifacts/ 236 | ModelManifest.xml 237 | 238 | # Paket dependency manager 239 | .paket/paket.exe 240 | 241 | # FAKE - F# Make 242 | .fake/ 243 | 244 | __vm/*.* 245 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to ThingPulse Spotify Remote 2 | 3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: 4 | 5 | The following is a set of guidelines for contributing to the ThingPulse Spotify Remote project on GitHub. These are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request. 6 | 7 | It is appreciated if you raise an issue _before_ you start changing the code, discussing the proposed change; emphasizing that you are proposing to develop the patch yourself, and outlining the strategy for implementation. This type of discussion is what we should be doing on the issues list and it is better to do this before or in parallel to developing the patch rather than having "you should have done it this way" type of feedback on the PR itself. 8 | 9 | ### Table Of Contents 10 | * [General remarks](#general-remarks) 11 | * [Writing Documentation](#writing-documentation) 12 | * [Working with Git and GitHub](#working-with-git-and-github) 13 | * [General flow](#general-flow) 14 | * [Keeping your fork in sync](#keeping-your-fork-in-sync) 15 | * [Commit messages](#commit-messages) 16 | 17 | ## General remarks 18 | We are a friendly and welcoming community and look forward to your contributions. Once your contribution is integrated into this repository we feel responsible for it. Therefore, be prepared for constructive feedback. Before we merge anything we need to ensure that it fits in and is consistent with the rest of code. 19 | If you made something really cool but won't spend the time to integrate it into this upstream project please still share it in your fork on GitHub. If you mention it in an issue we'll take a look at it anyway. 20 | 21 | ## Writing Documentation 22 | If you contribute features for this project that require altering the documentation in the README then please include those in your PR. 23 | 24 | ## Working with Git and GitHub 25 | 26 | Avoid intermediate merge commits. [Rebase](https://www.atlassian.com/git/tutorials/merging-vs-rebasing) your feature branch onto `master` to pull updates and verify your local changes against them before placing the pull request. 27 | 28 | ### General flow 29 | 1. [Fork](https://help.github.com/articles/fork-a-repo) this repository on GitHub. 30 | 1. [Create a branch](https://help.github.com/articles/creating-and-deleting-branches-within-your-repository/#creating-a-branch) in your fork on GitHub **based on the `master` branch**. 31 | 1. Clone the fork on your machine with `git clone https://github.com//.git` 32 | 1. `cd ` then run `git remote add upstream https://github.com/ThingPulse/espaper-weatherstation` 33 | 1. `git checkout ` 34 | 1. Make changes to the code base and commit them using e.g. `git commit -a -m 'Look ma, I did it'` 35 | 1. When you're done: 36 | 1. Think about [squashing (some of) your commits](http://www.andrewconnell.com/blog/squash-multiple-git-commits-into-one). There are [several ways](http://stackoverflow.com/a/5201642/131929) to do this. There's no need to squash everything into a single commit as GitHub offers to do this when we merge your changes. However, you might want to trim your commit history to relevant chunks. 37 | 1. Bring your fork up-to-date with the upstream repo ([see below](#keeping-your-fork-in-sync)). Then rebase your branch on `master` running `git rebase master`. 38 | 1. `git push` 39 | 1. [Create a pull request](https://help.github.com/articles/creating-a-pull-request/) (PR) on GitHub. 40 | 41 | This is just one way of doing things. If you're proficient in Git matters you're free to choose your own. If you want to read more then the [GitHub chapter in the Git book](http://git-scm.com/book/en/v2/GitHub-Contributing-to-a-Project#The-GitHub-Flow) is a way to start. [GitHub's own documentation](https://help.github.com/categories/collaborating/) contains a wealth of information as well. 42 | 43 | ### Keeping your fork in sync 44 | You need to sync your fork with the upstream repository from time to time, latest before you rebase (see flow above). 45 | 46 | 1. `git fetch upstream` 47 | 1. `git checkout master` 48 | 1. `git merge upstream/master` 49 | 50 | ### Commit messages 51 | 52 | From: [http://git-scm.com/book/ch5-2.html](http://git-scm.com/book/ch5-2.html) 53 |
54 | Short (50 chars or less) summary of changes
55 | 
56 | More detailed explanatory text, if necessary.  Wrap it to about 72
57 | characters or so.  In some contexts, the first line is treated as the
58 | subject of an email and the rest of the text as the body.  The blank
59 | line separating the summary from the body is critical (unless you omit
60 | the body entirely); tools like rebase can get confused if you run the
61 | two together.
62 | 
63 | Further paragraphs come after blank lines.
64 | 
65 |  - Bullet points are okay, too
66 | 
67 |  - Typically a hyphen or asterisk is used for the bullet, preceded by a
68 |    single space, with blank lines in between, but conventions vary here
69 | 
70 | 71 | Don't forget to [reference affected issues](https://help.github.com/articles/closing-issues-via-commit-messages/) in the commit message to have them closed automatically on GitHub. 72 | 73 | [Amend](https://help.github.com/articles/changing-a-commit-message/) your commit messages if necessary to make sure what the world sees on GitHub is as expressive and meaningful as possible. 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 ThingPulse GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ThingPulse esp8266-spotify-remote 2 | 3 | [![ThingPulse logo](https://thingpulse.com/assets/ThingPulse-w300.svg)](https://thingpulse.com) 4 | 5 | ## Purpose of this project 6 | 7 | This project lets you control a Spotify player (phone, browser, etc) from an ESP8266. Album artworks as well as title and artist name 8 | are fetched from Spotify's Web API over WiFi and displayed on a ILI9341 color TFT screen. The currently played song can be 9 | paused, played and skipped to the next or previous song in the playlist. 10 | 11 | A full OAuth 2.0 web flow is used to acquire the necessary access and refresh tokens to permit the user to control the player. In order to 12 | run this project on your device you will have to setup an application on Spotify's developer dashboard. 13 | 14 | ## Features 15 | 16 | - Artwork Download 17 | - Control Player on touch screen: Play, Pause, Next, Prev 18 | - Authentication and Authorization (OAuth 2.0 flow) On device. 19 | 20 | Demo Video: 21 | 22 | [![Demo Video](https://img.youtube.com/vi/xKmXMUoo8ps/0.jpg)](https://www.youtube.com/watch?v=xKmXMUoo8ps) 23 | 24 | ## Recommended Hardware 25 | 26 | We developed this project specifically for our [ESP8266 Color Kit](https://thingpulse.com/product/esp8266-wifi-color-display-kit-2-4/). If you appreciate the hard work and our willingness to open-source projects like this one then please support us by buying our hardware. 27 | 28 | 29 | 30 | 31 | 32 | 33 | ## Contributions 34 | 35 | Please see our [Guidelines](CONTRIBUTING.md) if you want to contribute to this project. Contributions are more than welcome! 36 | 37 | ## Service level promise 38 | 39 |
40 | This is a ThingPulse community project. See our open-source commitment declaration for what this means.
41 | 42 | ## Setup Instructions 43 | 44 | ### Precondition 45 | 46 | The below instructions assume that your Arduino IDE is properly setup to work with the ThingPulse Color Kit. See our standalone tutorials for [setting up the IDE](https://docs.thingpulse.com/how-tos/Arduino-IDE-for-ESP8266/) and [installing the drivers](https://docs.thingpulse.com/how-tos/install-drivers/) if you need help with that. 47 | 48 | ### Prepare Project in Arduino IDE 49 | 50 | 1. Download this project either as ZIP file or check it out with Git 51 | 1. Open the project in the Arduino IDE. 52 | 1. Install the required Arduino libraries. Go to Sketch > Include Library... > Manage Libraries.... Then, for each library, put its name into the text field to have its metadata pulled from the internet and displayed below. Select the latest version and install it. 53 | 1. [Mini Grafx](https://www.arduinolibraries.info/libraries/mini-grafx) library (by ThingPulse, V 1.0.0 or later) 54 | 1. [JPEGDecoder](https://www.arduinolibraries.info/libraries/jpeg-decoder) library (by Bodmer, Makoto Kurauchi, Rich Geldreich, v1.7.8 or later). After installing the library go to your Arduino sketchbook folder (the one configured in Arduino Preferences > Sketchbook location). In that folder open file `libraries/JPEGDecoder/src/User_config.h` and comment line 5 so that it reads `//#define LOAD_SD_LIBRARY`. 55 | 1. [JSON Streaming Parser](https://www.arduinolibraries.info/libraries/json-streaming-parser) library (by Daniel Eichhorn, v1.0.5 or later) 56 | 1. [XPT2046 Touch](https://www.arduinolibraries.info/libraries/xpt2046_touchscreen) (by Paul Stoffregen, v1.4 or later) 57 | 1. Set your *WiFi credentials* in the `settings.h` file 58 | 1. Complete the steps below to get the values for the *Spotify settings* required in `settings.h` 59 | 60 | ### Get Access to the Spotify API 61 | 62 | 1. Go to [https://developer.spotify.com/dashboard/login](https://developer.spotify.com/dashboard/login) and login to or sign up for the Spotify Developer Dashboard 63 | 64 | 2. Click on "My New App" 65 | 66 | 67 | 3. Fill out the form. Give your new app a name you can attribute to this project. It's safe to select "I don't know" for the type of application. 68 | 69 | 70 | 4. At the end of the 3 steps click "Submit" 71 | 72 | 73 | 5. Set the unique Client ID and Client Secret as values for the respective variables in `settings.h` 74 | 75 | 76 | 6. Click on "Edit Settings". Add "http://esp8266.local/callback/" to the Redirect URIs section. 77 | 78 | **NOTE** If you're running more than ThingPulse Spotify Remote in the same WiFi network you should choose a unique name rather than "esp8266". Regardless of what you choose it has to reflect what you set as `espotifierNodeName` in `settings.h` 79 | 80 | 81 | 7. Don't forget to save your settings. 82 | 83 | 84 | ### Compile and run the application 85 | 86 | After all this configuration it's about time to run the application! 87 | 88 | 1. First check the configuration in `settings.h` one more time to ensure the Spotify values match those set on the Spotify Developer Dashboard. Better safe than sorry, they say. 89 | 90 | 2. Attach your [ESP8266 Color Kit](https://thingpulse.com/product/esp8266-wifi-color-display-kit-2-4/) to your computer and select the correct serial port and flash settings in the Arduino IDE as per https://docs.thingpulse.com/guides/wifi-color-display-kit/#select-flash-mode-and-flash-size 91 | 92 | 3. Upload the code to your ESP8266 93 | 94 | 4. When you run this the first time you'll have to go through additional steps. The display will ask you to open the browser at a specific location. This will redirect you to the dialog below. 95 | 96 | 97 | 5. The next step is to calibrate the screen. Click on the white circles 98 | 99 | 6. Now open your Spotify player and start a song. If everything worked out you'll see the song information with artwork on the TFT screen! 100 | -------------------------------------------------------------------------------- /SpotifyClient.cpp: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #include "SpotifyClient.h" 29 | 30 | SpotifyClient::SpotifyClient(String clientId, String clientSecret, String redirectUri, WiFiClientSecure *wifiClient) { 31 | this->clientId = clientId; 32 | this->clientSecret = clientSecret; 33 | this->redirectUri = redirectUri; 34 | this->wifiClient = wifiClient; 35 | } 36 | 37 | uint16_t SpotifyClient::update(SpotifyData *data, SpotifyAuth *auth) { 38 | this->data = data; 39 | 40 | level = 0; 41 | isDataCall = true; 42 | currentParent = ""; 43 | JsonStreamingParser parser; 44 | parser.setListener(this); 45 | 46 | String host = "api.spotify.com"; 47 | const int port = 443; 48 | String url = "/v1/me/player/currently-playing"; 49 | if (!wifiClient->connect(host.c_str(), port)) { 50 | Serial.println("connection failed"); 51 | return 0; 52 | } 53 | 54 | Serial.print("Requesting URL: "); 55 | String request = "GET " + url + " HTTP/1.1\r\n" + 56 | "Host: " + host + "\r\n" + 57 | "Authorization: Bearer " + auth->accessToken + "\r\n" + 58 | "Connection: close\r\n\r\n"; 59 | // This will send the request to the server 60 | Serial.println(request); 61 | wifiClient->print(request); 62 | 63 | int retryCounter = 0; 64 | while (!wifiClient->available()) { 65 | executeCallback(); 66 | retryCounter++; 67 | if (retryCounter > 10) { 68 | return 0; 69 | } 70 | delay(10); 71 | } 72 | uint16_t bufLen = 1024; 73 | unsigned char buf[bufLen]; 74 | boolean isBody = false; 75 | char c = ' '; 76 | 77 | int size = 0; 78 | wifiClient->setNoDelay(false); 79 | uint16_t httpCode = 0; 80 | while (wifiClient->connected() || wifiClient->available()) { 81 | while ((size = wifiClient->available()) > 0) { 82 | 83 | if (isBody) { 84 | uint16_t len = _min(bufLen, size); 85 | c = wifiClient->readBytes(buf, len); 86 | for (uint16_t i = 0; i < len; i++) { 87 | parser.parse(buf[i]); 88 | // Serial.print((char)buf[i]); 89 | } 90 | } else { 91 | String line = wifiClient->readStringUntil('\r'); 92 | Serial.println(line); 93 | if (line.startsWith("HTTP/1.")) { 94 | httpCode = line.substring(9, line.indexOf(' ', 9)).toInt(); 95 | Serial.printf("HTTP Code: %d\n", httpCode); 96 | } 97 | if (line == "\r" || line == "\n" || line == "") { 98 | Serial.println("Body starts now"); 99 | isBody = true; 100 | } 101 | } 102 | executeCallback(); 103 | } 104 | } 105 | if (httpCode == 200) { 106 | this->data->isPlayerActive = true; 107 | } else if (httpCode == 204) { 108 | this->data->isPlayerActive = false; 109 | } 110 | // client.flush(); 111 | // client.stop(); 112 | this->data = nullptr; 113 | return httpCode; 114 | } 115 | 116 | uint16_t SpotifyClient::playerCommand(SpotifyAuth *auth, String method, String command) { 117 | 118 | level = 0; 119 | isDataCall = true; 120 | currentParent = ""; 121 | JsonStreamingParser parser; 122 | parser.setListener(this); 123 | 124 | String host = "api.spotify.com"; 125 | const int port = 443; 126 | String url = "/v1/me/player/" + command; 127 | if (!wifiClient->connect(host.c_str(), port)) { 128 | Serial.println("connection failed"); 129 | return 0; 130 | } 131 | 132 | Serial.print("Requesting URL: "); 133 | String request = method + " " + url + " HTTP/1.1\r\n" + 134 | "Host: " + host + "\r\n" + 135 | "Authorization: Bearer " + auth->accessToken + "\r\n" + 136 | "Content-Length: 0\r\n" + 137 | "Connection: close\r\n\r\n"; 138 | // This will send the request to the server 139 | Serial.println(request); 140 | wifiClient->print(request); 141 | 142 | int retryCounter = 0; 143 | while (!wifiClient->available()) { 144 | executeCallback(); 145 | 146 | retryCounter++; 147 | if (retryCounter > 10) { 148 | return 0; 149 | } 150 | delay(10); 151 | } 152 | uint16_t bufLen = 1024; 153 | unsigned char buf[bufLen]; 154 | boolean isBody = false; 155 | char c = ' '; 156 | 157 | int size = 0; 158 | wifiClient->setNoDelay(false); 159 | uint16_t httpCode = 0; 160 | while (wifiClient->connected() || wifiClient->available()) { 161 | while ((size = wifiClient->available()) > 0) { 162 | if (isBody) { 163 | uint16_t len = _min(bufLen, size); 164 | c = wifiClient->readBytes(buf, len); 165 | for (uint16_t i = 0; i < len; i++) { 166 | parser.parse(buf[i]); 167 | // Serial.print((char)buf[i]); 168 | } 169 | } else { 170 | String line = wifiClient->readStringUntil('\r'); 171 | Serial.println(line); 172 | if (line.startsWith("HTTP/1.")) { 173 | httpCode = line.substring(9, line.indexOf(' ', 9)).toInt(); 174 | Serial.printf("HTTP Code: %d\n", httpCode); 175 | } 176 | if (line == "\r" || line == "\n" || line == "") { 177 | Serial.println("Body starts now"); 178 | isBody = true; 179 | } 180 | } 181 | } 182 | executeCallback(); 183 | } 184 | return httpCode; 185 | } 186 | 187 | void SpotifyClient::getToken(SpotifyAuth *auth, String grantType, String code) { 188 | this->auth = auth; 189 | isDataCall = false; 190 | JsonStreamingParser parser; 191 | parser.setListener(this); 192 | 193 | // https://accounts.spotify.com/api/token 194 | const char *host = "accounts.spotify.com"; 195 | const int port = 443; 196 | String url = "/api/token"; 197 | if (!wifiClient->connect(host, port)) { 198 | Serial.printf("Connection to %s:%d failed; returning.\n", host, port); 199 | wifiClient->stop(); 200 | return; 201 | } 202 | 203 | Serial.print("Requesting URL: "); 204 | String codeParam = "code"; 205 | if (grantType == "refresh_token") { 206 | codeParam = "refresh_token"; 207 | } 208 | String authorizationRaw = clientId + ":" + clientSecret; 209 | String authorization = base64::encode(authorizationRaw, false); 210 | // This will send the request to the server 211 | String content = "grant_type=" + grantType + "&" + codeParam + "=" + code + "&redirect_uri=" + redirectUri; 212 | String request = String("POST ") + url + " HTTP/1.1\r\n" + 213 | "Host: " + host + "\r\n" + 214 | "Authorization: Basic " + authorization + "\r\n" + 215 | "Content-Length: " + String(content.length()) + "\r\n" + 216 | "Content-Type: application/x-www-form-urlencoded\r\n" + 217 | "Connection: close\r\n\r\n" + 218 | content; 219 | Serial.println(request); 220 | wifiClient->print(request); 221 | 222 | char c; 223 | boolean isBody = false; 224 | unsigned long timeoutMillis = 10000UL; 225 | unsigned long startMillis = millis(); 226 | while (wifiClient->connected() || wifiClient->available()) { 227 | if (wifiClient->available()) { 228 | if ((millis() - startMillis) > timeoutMillis) { 229 | Serial.printf("HTTP timeout after %ds.\n", (timeoutMillis/1000)); 230 | wifiClient->stop(); 231 | ESP.restart(); 232 | } 233 | c = wifiClient->read(); 234 | Serial.print(c); 235 | if (c == '{' || c == '[') { 236 | isBody = true; 237 | } 238 | if (isBody) { 239 | parser.parse(c); 240 | } 241 | } 242 | executeCallback(); 243 | // give WiFi and TCP/IP libraries a chance to handle pending events 244 | yield(); 245 | } 246 | wifiClient->stop(); 247 | 248 | this->data = nullptr; 249 | } 250 | 251 | String SpotifyClient::startConfigPortal(const String mDnsName) { 252 | String oneWayCode = ""; 253 | 254 | Serial.println("Starting config portal"); 255 | server.on("/", [this]() { 256 | Serial.println("Serving resource '/'"); 257 | Serial.println("Sending HTTP 302 to Spotify"); 258 | server.sendHeader( 259 | "Location", 260 | String("https://accounts.spotify.com/authorize/?client_id=" + 261 | this->clientId + 262 | "&response_type=code&redirect_uri=" + this->redirectUri + 263 | "&scope=user-read-private%20user-read-currently-playing%20user-" 264 | "read-playback-state%20user-modify-playback-state"), 265 | true); 266 | server.send(302, "text/plain", ""); 267 | }); 268 | 269 | server.on("/callback/", [this, &oneWayCode]() { 270 | if (!server.hasArg("code")) { 271 | server.send(500, "text/plain", "BAD ARGS"); 272 | return; 273 | } 274 | 275 | oneWayCode = server.arg("code"); 276 | Serial.printf("Code: %s\n", oneWayCode.c_str()); 277 | String message = "Succesfully authentiated this device with Spotify. Restart your device now"; 278 | server.send(200, "text/html", message); 279 | }); 280 | 281 | server.begin(); 282 | 283 | Serial.println("HTTP server started"); 284 | 285 | if (!MDNS.begin(mDnsName)) { 286 | Serial.println("Error setting up MDNS responder!"); 287 | } 288 | Serial.println("MDNS responder started"); 289 | Serial.println("Open browser at http://" + mDnsName + ".local"); 290 | 291 | while (oneWayCode == "") { 292 | MDNS.update(); 293 | server.handleClient(); 294 | yield(); 295 | } 296 | 297 | Serial.println("Stopping HTTP server"); 298 | server.stop(); 299 | Serial.println("Stopping MDNS responder"); 300 | MDNS.close(); 301 | return oneWayCode; 302 | } 303 | 304 | void SpotifyClient::whitespace(char c) { 305 | Serial.println("whitespace"); 306 | } 307 | 308 | void SpotifyClient::startDocument() { 309 | Serial.println("start document"); 310 | level = 0; 311 | } 312 | 313 | void SpotifyClient::key(String key) { 314 | currentKey = String(key); 315 | rootPath[level] = key; 316 | } 317 | 318 | String SpotifyClient::getRootPath() { 319 | String path = ""; 320 | for (uint8_t i = 1; i <= level; i++) { 321 | String currentLevel = rootPath[i]; 322 | if (currentLevel == "") { 323 | break; 324 | } 325 | if (i > 1) { 326 | path += "."; 327 | } 328 | path += currentLevel; 329 | } 330 | return path; 331 | } 332 | 333 | void SpotifyClient::value(String value) { 334 | if (isDataCall) { 335 | 336 | String rootPath = this->getRootPath(); 337 | // Serial.printf("%s = %s\n", rootPath.c_str(), value.c_str()); 338 | // Serial.printf("%s = %s\n", currentKey.c_str(), value.c_str()); 339 | // progress_ms = 37516 uint32_t progressMs; 340 | if (currentKey == "progress_ms") { 341 | data->progressMs = value.toInt(); 342 | } 343 | // duration_ms = 259120 uint32_t durationMs; 344 | if (currentKey == "duration_ms") { 345 | data->durationMs = value.toInt(); 346 | } 347 | // name = Lost in My MindString name; 348 | if (currentKey == "name") { 349 | data->title = value; 350 | } 351 | // is_playing = true boolean isPlaying; 352 | if (currentKey == "is_playing") { 353 | data->isPlaying = (value == "true" ? true : false); 354 | } 355 | if (currentKey == "height") { 356 | currentImageHeight = value.toInt(); 357 | } 358 | if (currentKey == "url") { 359 | Serial.printf("HREF: %s = %s", rootPath.c_str(), value.c_str()); 360 | 361 | if (rootPath == "item.album.images.url") { 362 | if (currentImageHeight == 640) { 363 | data->image640Href = value; 364 | } 365 | if (currentImageHeight > 250 && currentImageHeight < 350) { 366 | data->image300Href = value; 367 | } 368 | if (currentImageHeight == 64) { 369 | data->image64Href = value; 370 | } 371 | } 372 | } 373 | if (rootPath == "item.album.artists.name") { 374 | data->artistName = value; 375 | } 376 | 377 | } else { 378 | Serial.printf("\n%s=%s\n", currentKey.c_str(), value.c_str()); 379 | // "access_token": "XXX" String accessToken; 380 | if (currentKey == "access_token") { 381 | auth->accessToken = value; 382 | } 383 | // "token_type":"Bearer", String tokenType; 384 | if (currentKey == "token_type") { 385 | auth->tokenType = value; 386 | } 387 | // "expires_in":3600, uint16_t expiresIn; 388 | if (currentKey == "expires_in") { 389 | auth->expiresIn = value.toInt(); 390 | } 391 | // "refresh_token":"XX", String refreshToken; 392 | if (currentKey == "refresh_token") { 393 | auth->refreshToken = value; 394 | } 395 | // "scope":"user-modify-playback-state user-read-playback-state user-read-currently-playing user-read-private String scope; 396 | if (currentKey == "scope") { 397 | auth->scope = value; 398 | } 399 | } 400 | } 401 | 402 | void SpotifyClient::endArray() {} 403 | 404 | void SpotifyClient::startObject() { 405 | // Serial.println("Starting new object: " + currentKey); 406 | currentParent = currentKey; 407 | // rootPath[level] = currentKey; 408 | level++; 409 | 410 | // level++;*/ 411 | } 412 | 413 | void SpotifyClient::endObject() { 414 | // rootPath[level] = ""; 415 | level--; 416 | currentParent = ""; 417 | } 418 | 419 | void SpotifyClient::endDocument() {} 420 | 421 | void SpotifyClient::startArray() {} 422 | 423 | void SpotifyClient::executeCallback() { 424 | if (drawingCallback != nullptr) { 425 | (*this->drawingCallback)(); 426 | } 427 | } 428 | 429 | void SpotifyClient::downloadFile(String url, String filename) { 430 | Serial.println("Downloading " + url + " and saving as " + filename); 431 | 432 | // wait for WiFi connection 433 | // TODO - decide if there's a different action for first call or subsequent 434 | // calls boolean isFirstCall = true; 435 | HTTPClient http; 436 | 437 | Serial.print("[HTTP] begin...\n"); 438 | 439 | // configure server and url 440 | http.begin(*wifiClient, url); 441 | 442 | Serial.print("[HTTP] GET...\n"); 443 | // start connection and send HTTP header 444 | int httpCode = http.GET(); 445 | if (httpCode > 0) { 446 | // SPIFFS.remove(filename); 447 | fs::File f = SPIFFS.open(filename, "w+"); 448 | if (!f) { 449 | Serial.println("file open failed"); 450 | return; 451 | } 452 | // HTTP header has been send and Server response header has been handled 453 | Serial.printf("[HTTP] GET... code: %d\n", httpCode); 454 | 455 | // file found at server 456 | if (httpCode == HTTP_CODE_OK) { 457 | 458 | // get lenght of document (is -1 when Server sends no Content-Length 459 | // header) 460 | int total = http.getSize(); 461 | int len = total; 462 | // progressCallback(filename, 0,total, true); 463 | // create buffer for read 464 | uint8_t buff[128] = {0}; 465 | 466 | // get tcp stream 467 | WiFiClient *stream = http.getStreamPtr(); 468 | 469 | // read all data from server 470 | while (http.connected() && (len > 0 || len == -1)) { 471 | // get available data size 472 | size_t size = stream->available(); 473 | 474 | if (size) { 475 | // read up to 128 byte 476 | int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size)); 477 | 478 | // write it to Serial 479 | f.write(buff, c); 480 | 481 | if (len > 0) { 482 | len -= c; 483 | } 484 | // progressCallback(filename, total - len,total, false); 485 | // isFirstCall = false; 486 | executeCallback(); 487 | } 488 | delay(1); 489 | } 490 | 491 | Serial.println(); 492 | Serial.print("[HTTP] connection closed or file end.\n"); 493 | } 494 | f.close(); 495 | } else { 496 | Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); 497 | } 498 | 499 | http.end(); 500 | } 501 | -------------------------------------------------------------------------------- /SpotifyClient.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #pragma once 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | typedef void (*DrawingCallback)(); 34 | 35 | typedef struct SpotifyAuth { 36 | // "access_token": "XXX" 37 | String accessToken; 38 | // "token_type":"Bearer", 39 | String tokenType; 40 | // "expires_in":3600, 41 | uint16_t expiresIn; 42 | // "refresh_token":"XX", 43 | String refreshToken; 44 | // "scope":"user-modify-playback-state user-read-playback-state user-read-currently-playing user-read-private 45 | String scope; 46 | 47 | } SpotifyAuth; 48 | 49 | typedef struct SpotifyData { 50 | // timestamp = 1527690461156 51 | // progress_ms = 37516 52 | uint32_t progressMs; 53 | // is_playing = true 54 | boolean isPlaying; 55 | boolean isPlayerActive; 56 | // Starting new object: item 57 | // Starting new object: album 58 | // album_type = album 59 | // Starting new object: artists 60 | // Starting new object: external_urls 61 | // spotify = https://open.spotify.com/artist/0n94vC3S9c3mb2HyNAOcjg 62 | // href = https://api.spotify.com/v1/artists/0n94vC3S9c3mb2HyNAOcjg 63 | // id = 0n94vC3S9c3mb2HyNAOcjg 64 | // name = The Head and the Heart item.album.artists.name 65 | String artistName; 66 | // type = artist 67 | // uri = spotify:artist:0n94vC3S9c3mb2HyNAOcjg 68 | // Starting new object: external_urls 69 | // spotify = https://open.spotify.com/album/0xWfhCMYmaiCXtLOuyPoLF 70 | // href = https://api.spotify.com/v1/albums/0xWfhCMYmaiCXtLOuyPoLF 71 | String image640Href; 72 | String image300Href; 73 | String image64Href; 74 | // id = 0xWfhCMYmaiCXtLOuyPoLF 75 | // Starting new object: images 76 | // height = 639 77 | // url = https://i.scdn.co/image/c08c25d478dc3a645c04abc000602201ce1634d8 78 | // width = 630 79 | // Starting new object: width 80 | // height = 300 81 | // url = https://i.scdn.co/image/6b0e2580ede853b1173198693301f0df1979b5cf 82 | // width = 296 83 | // Starting new object: width 84 | // height = 64 85 | // url = https://i.scdn.co/image/7d20358db1b1502a09a05f860ba9d3641e552f8d 86 | // width = 63 87 | // name = The Head and the Heart 88 | // release_date = 2010 89 | // release_date_precision = year 90 | // type = album 91 | // uri = spotify:album:0xWfhCMYmaiCXtLOuyPoLF 92 | // Starting new object: artists 93 | // Starting new object: external_urls 94 | // spotify = https://open.spotify.com/artist/0n94vC3S9c3mb2HyNAOcjg 95 | // href = https://api.spotify.com/v1/artists/0n94vC3S9c3mb2HyNAOcjg 96 | // id = 0n94vC3S9c3mb2HyNAOcjg 97 | // name = The Head and the Heart 98 | // type = artist 99 | // uri = spotify:artist:0n94vC3S9c3mb2HyNAOcjg 100 | // disc_number = 1 101 | // duration_ms = 259120 102 | uint32_t durationMs; 103 | // explicit = false 104 | // Starting new object: external_ids 105 | // isrc = USSUB1191507 106 | // Starting new object: external_urls 107 | // spotify = https://open.spotify.com/track/3gvAGvbMCRvVDDp8ZaIPV5 108 | // href = https://api.spotify.com/v1/tracks/3gvAGvbMCRvVDDp8ZaIPV5 109 | // id = 3gvAGvbMCRvVDDp8ZaIPV5 110 | // is_local = false 111 | // name = Lost in My Mind 112 | String title; 113 | // popularity = 68 114 | // preview_url = null 115 | // track_number = 7 116 | // type = track 117 | // uri = spotify:track:3gvAGvbMCRvVDDp8ZaIPV5 118 | // Starting new object: context 119 | // Starting new object: external_urls 120 | // spotify = https://open.spotify.com/user/spotify/playlist/37i9dQZF1DWUNIrSzKgQbP 121 | // href = https://api.spotify.com/v1/users/spotify/playlists/37i9dQZF1DWUNIrSzKgQbP 122 | // type = playlist 123 | // uri = spotify:user:spotify:playlist:37i9dQZF1DWUNIrSzKgQbP 124 | 125 | 126 | } SpotifyData; 127 | 128 | class SpotifyClient: public JsonListener { 129 | private: 130 | 131 | String currentKey; 132 | String currentParent; 133 | SpotifyData *data; 134 | SpotifyAuth *auth; 135 | bool isDataCall; 136 | String rootPath[10]; 137 | uint8_t level = 0; 138 | uint16_t currentImageHeight; 139 | DrawingCallback *drawingCallback; 140 | String clientId; 141 | String clientSecret; 142 | String redirectUri; 143 | WiFiClientSecure *wifiClient; 144 | ESP8266WebServer server; 145 | 146 | String getRootPath(); 147 | void executeCallback(); 148 | 149 | public: 150 | 151 | SpotifyClient(String clientId, String clientSecret, String redirectUri, WiFiClientSecure *wifiClient); 152 | 153 | uint16_t update(SpotifyData *data, SpotifyAuth *auth); 154 | 155 | uint16_t playerCommand(SpotifyAuth *auth, String method, String command); 156 | 157 | void getToken(SpotifyAuth *auth, String grantType, String code); 158 | 159 | void downloadFile(String url, String filename); 160 | 161 | void setDrawingCallback(DrawingCallback *drawingCallback) { 162 | this->drawingCallback = drawingCallback; 163 | } 164 | 165 | String startConfigPortal(const String mDnsName); 166 | 167 | virtual void whitespace(char c); 168 | 169 | virtual void startDocument(); 170 | 171 | virtual void key(String key); 172 | 173 | virtual void value(String value); 174 | 175 | virtual void endArray(); 176 | 177 | virtual void endObject(); 178 | 179 | virtual void endDocument(); 180 | 181 | virtual void startArray(); 182 | 183 | virtual void startObject(); 184 | 185 | }; 186 | -------------------------------------------------------------------------------- /TouchControllerWS.cpp: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #include "TouchControllerWS.h" 25 | 26 | TouchControllerWS::TouchControllerWS(XPT2046_Touchscreen *touchScreen) { 27 | this->touchScreen = touchScreen; 28 | } 29 | 30 | bool TouchControllerWS::loadCalibration() { 31 | // always use this to "mount" the filesystem 32 | bool result = SPIFFS.begin(); 33 | Serial.println("SPIFFS opened: " + result); 34 | 35 | // this opens the file "f.txt" in read-mode 36 | File f = SPIFFS.open("/calibration.txt", "r"); 37 | 38 | if (!f) { 39 | return false; 40 | } else { 41 | 42 | //Lets read line by line from the file 43 | String dxStr = f.readStringUntil('\n'); 44 | String dyStr = f.readStringUntil('\n'); 45 | String axStr = f.readStringUntil('\n'); 46 | String ayStr = f.readStringUntil('\n'); 47 | 48 | dx = dxStr.toFloat(); 49 | dy = dyStr.toFloat(); 50 | ax = axStr.toInt(); 51 | ay = ayStr.toInt(); 52 | 53 | } 54 | f.close(); 55 | return true; 56 | } 57 | 58 | bool TouchControllerWS::saveCalibration() { 59 | bool result = SPIFFS.begin(); 60 | if (result) { 61 | Serial.println("SPIFFS started successfully"); 62 | // open the file in write mode 63 | File f = SPIFFS.open("/calibration.txt", "w"); 64 | if (!f) { 65 | Serial.println("file creation failed"); 66 | } 67 | // now write two lines in key/value style with end-of-line characters 68 | f.println(dx); 69 | f.println(dy); 70 | f.println(ax); 71 | f.println(ay); 72 | 73 | f.close(); 74 | } 75 | else { 76 | Serial.println("SPIFFS failed to start!"); 77 | } 78 | return result; 79 | } 80 | 81 | void TouchControllerWS::startCalibration(CalibrationCallback *calibrationCallback) { 82 | state = 0; 83 | this->calibrationCallback = calibrationCallback; 84 | (*calibrationCallback)(10, 10); 85 | 86 | } 87 | 88 | void TouchControllerWS::continueCalibration() { 89 | TS_Point p = touchScreen->getPoint(); 90 | 91 | if (state == 0) { 92 | 93 | if (touchScreen->touched()) { 94 | p1 = p; 95 | state++; 96 | (*calibrationCallback)(230, 310); 97 | lastStateChange = millis(); 98 | } 99 | 100 | } else if (state == 1) { 101 | if (touchScreen->touched() && (millis() - lastStateChange > 1000)) { 102 | 103 | p2 = p; 104 | state++; 105 | lastStateChange = millis(); 106 | dx = 240.0 / abs(p1.y - p2.y); 107 | dy = 320.0 / abs(p1.x - p2.x); 108 | ax = p1.y < p2.y ? p1.y : p2.y; 109 | ay = p1.x < p2.x ? p1.x : p2.x; 110 | Serial.printf("dx: %f, dy: %f, ax: %f, ay: %f\n", dx, dy, ax, ay); 111 | } 112 | 113 | } 114 | } 115 | bool TouchControllerWS::isCalibrationFinished() { 116 | return state == 2; 117 | } 118 | 119 | bool TouchControllerWS::isTouched() { 120 | bool result; 121 | result = touchScreen->touched(); 122 | return result; 123 | } 124 | 125 | bool TouchControllerWS::isTouched(int16_t debounceMillis) { 126 | if (touchScreen->touched() && (long)millis() - lastTouched > debounceMillis) { 127 | lastTouched = millis(); 128 | return true; 129 | } 130 | return false; 131 | } 132 | TS_Point TouchControllerWS::getPoint() { 133 | TS_Point p = touchScreen->getPoint(); 134 | int x = 240 - (p.y - ax) * dx; 135 | int y = (p.x - ay) * dy; 136 | p.x = x; 137 | p.y = y; 138 | return p; 139 | } 140 | -------------------------------------------------------------------------------- /TouchControllerWS.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #include 25 | #include 26 | 27 | #ifndef _TOUCH_CONTROLLERWSH_ 28 | #define _TOUCH_CONTROLLERWSH_ 29 | 30 | typedef void (*CalibrationCallback)(int16_t x, int16_t y); 31 | 32 | class TouchControllerWS { 33 | public: 34 | TouchControllerWS(XPT2046_Touchscreen *touchScreen); 35 | bool loadCalibration(); 36 | bool saveCalibration(); 37 | void startCalibration(CalibrationCallback *callback); 38 | void continueCalibration(); 39 | bool isCalibrationFinished(); 40 | bool isTouched(); 41 | bool isTouched(int16_t debounceMillis); 42 | TS_Point getPoint(); 43 | 44 | private: 45 | XPT2046_Touchscreen *touchScreen; 46 | float dx = 0.0; 47 | float dy = 0.0; 48 | int ax = 0; 49 | int ay = 0; 50 | int state = 0; 51 | long lastStateChange = 0; 52 | long lastTouched = 0; 53 | CalibrationCallback *calibrationCallback; 54 | TS_Point p1, p2; 55 | 56 | }; 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /esp8266-spotify-remote.ino: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "SpotifyClient.h" 32 | #include "settings.h" 33 | #include "TouchControllerWS.h" 34 | #include "images.h" 35 | 36 | // Statements like: 37 | // #pragma message(Reminder "Fix this problem!") 38 | // Which will cause messages like: 39 | // C:\Source\Project\main.cpp(47): Reminder: Fix this problem! 40 | // to show up during compiles. Note that you can NOT use the 41 | // words "error" or "warning" in your reminders, since it will 42 | // make the IDE think it should abort execution. You can double 43 | // click on these messages and jump to the line in question. 44 | // 45 | // see https://stackoverflow.com/questions/5966594/how-can-i-use-pragma-message-so-that-the-message-points-to-the-filelineno 46 | // 47 | #define Stringize( L ) #L 48 | #define MakeString( M, L ) M(L) 49 | #define $Line MakeString( Stringize, __LINE__ ) 50 | #define Reminder __FILE__ "(" $Line ") : Reminder: " 51 | #ifdef LOAD_SD_LIBRARY 52 | #pragma message(Reminder "Comment out the line with LOAD_SD_LIBRARY /JPEGDecoder/src/User_config.h !") 53 | #endif 54 | 55 | int BUFFER_WIDTH = 240; 56 | int BUFFER_HEIGHT = 160; 57 | // Limited to 4 colors due to memory constraints 58 | int BITS_PER_PIXEL = 2; // 2^2 = 4 colors 59 | 60 | XPT2046_Touchscreen ts(TOUCH_CS, TOUCH_IRQ); 61 | TouchControllerWS touchController(&ts); 62 | 63 | ILI9341_SPI tft = ILI9341_SPI(TFT_CS, TFT_DC); 64 | MiniGrafx gfx = MiniGrafx(&tft, BITS_PER_PIXEL, palette, BUFFER_WIDTH, BUFFER_HEIGHT); 65 | 66 | WiFiClientSecure wifiClient; 67 | SpotifyClient client(clientId, clientSecret, redirectUri, &wifiClient); 68 | SpotifyData data; 69 | SpotifyAuth auth; 70 | 71 | String currentImageUrl = ""; 72 | uint32_t lastDrawingUpdate = 0; 73 | uint16_t counter = 0; 74 | long lastUpdate = 0; 75 | TS_Point lastTouchPoint; 76 | uint32_t lastTouchMillis = 0; 77 | boolean isDownloadingCover = false; 78 | 79 | void setClock(); 80 | void drawJPEGFromSpiffs(String filename, MiniGrafx *gfx, int xpos, int ypos, DrawingCallback *drawingCallback); 81 | void calibrationCallback(int16_t x, int16_t y); 82 | CalibrationCallback calibration = &calibrationCallback; 83 | String formatTime(uint32_t time); 84 | void saveRefreshToken(String refreshToken); 85 | String loadRefreshToken(); 86 | void displayLogo(); 87 | 88 | void drawSongInfo(); 89 | DrawingCallback drawSongInfoCallback = &drawSongInfo; 90 | 91 | void printFreeHeap(String msg) { 92 | #ifdef MEMORY_DEBUG 93 | Serial.println("*** Memory stats " + msg + " ***"); 94 | Serial.printf("\tFree heap: %d\n", ESP.getFreeHeap()); 95 | Serial.printf("\tMax free block size: %d\n", ESP.getMaxFreeBlockSize()); 96 | Serial.printf("\tHeap fragmentation: %d%\n\n", ESP.getHeapFragmentation()); 97 | #endif 98 | } 99 | 100 | void setup() { 101 | Serial.begin(115200); 102 | Serial.println(""); 103 | printFreeHeap("right after setup()"); 104 | wifiClient.setBufferSizes(512, 512); 105 | 106 | Serial.println(); 107 | Serial.print("connecting to "); 108 | Serial.println(ssid); 109 | WiFi.mode(WIFI_STA); 110 | WiFi.begin(ssid, password); 111 | while (WiFi.status() != WL_CONNECTED) { 112 | delay(500); 113 | Serial.print("."); 114 | } 115 | Serial.println(""); 116 | Serial.print("WiFi connected at IP address: "); 117 | Serial.println(WiFi.localIP()); 118 | 119 | printFreeHeap("after WiFi connection"); 120 | 121 | setClock(); 122 | 123 | pinMode(TFT_LED, OUTPUT); 124 | digitalWrite(TFT_LED, HIGH); // HIGH to Turn on; 125 | gfx.init(); 126 | gfx.fillBuffer(MINI_BLACK); 127 | gfx.setColor(MINI_YELLOW); 128 | gfx.setFont(ArialMT_Plain_16); 129 | gfx.setTextAlignment(TEXT_ALIGN_CENTER); 130 | displayLogo(); 131 | gfx.fillBuffer(MINI_BLACK); 132 | gfx.commit(0, 160); 133 | 134 | printFreeHeap("after 1st GFX commit"); 135 | 136 | boolean mounted = SPIFFS.begin(); 137 | if (!mounted) { 138 | Serial.println("FS not formatted. Doing that now"); 139 | gfx.drawString(120, 10, "Formatting file system.\nPlease stand by..."); 140 | gfx.commit(0, 0); 141 | SPIFFS.format(); 142 | Serial.println("FS formatted..."); 143 | SPIFFS.begin(); 144 | } 145 | 146 | printFreeHeap("after SPIFFS mounted"); 147 | 148 | // init the touch screen 149 | ts.begin(); 150 | 151 | boolean isCalibrationAvailable = touchController.loadCalibration(); 152 | if (!isCalibrationAvailable) { 153 | Serial.println("Calibration not available"); 154 | touchController.startCalibration(&calibration); 155 | while (!touchController.isCalibrationFinished()) { 156 | touchController.continueCalibration(); 157 | yield(); 158 | } 159 | touchController.saveCalibration(); 160 | } 161 | 162 | printFreeHeap("after touch screen initialized"); 163 | 164 | String code = ""; 165 | String grantType = ""; 166 | String refreshToken = loadRefreshToken(); 167 | if (refreshToken == "") { 168 | Serial.println("No refresh token found. Requesting through browser"); 169 | gfx.fillBuffer(MINI_BLACK); 170 | gfx.setColor(MINI_YELLOW); 171 | gfx.setTextAlignment(TEXT_ALIGN_CENTER); 172 | gfx.setFont(ArialMT_Plain_16); 173 | 174 | gfx.drawString(120, 10, "Authentication required.\nOpen browser at\nhttp://" + espotifierNodeName + ".local"); 175 | gfx.commit(0, 0); 176 | 177 | code = client.startConfigPortal(espotifierNodeName); 178 | grantType = "authorization_code"; 179 | } else { 180 | Serial.println("Using refresh token found on the FS"); 181 | code = refreshToken; 182 | grantType = "refresh_token"; 183 | } 184 | client.getToken(&auth, grantType, code); 185 | Serial.printf("Refresh token: %s\nAccess Token: %s\n", auth.refreshToken.c_str(), auth.accessToken.c_str()); 186 | if (auth.refreshToken != "") { 187 | saveRefreshToken(auth.refreshToken); 188 | } 189 | client.setDrawingCallback(&drawSongInfoCallback); 190 | } 191 | 192 | void loop() { 193 | if (millis() - lastUpdate > 1000) { 194 | uint16_t responseCode = client.update(&data, &auth); 195 | Serial.printf("HREF: %s\n", data.image300Href.c_str()); 196 | lastUpdate = millis(); 197 | Serial.printf("--------Response Code: %d\n", responseCode); 198 | Serial.printf("--------Free mem: %d\n", ESP.getFreeHeap()); 199 | if (responseCode == 401) { 200 | client.getToken(&auth, "refresh_token", auth.refreshToken); 201 | if (auth.refreshToken != "") { 202 | saveRefreshToken(auth.refreshToken); 203 | } 204 | } 205 | if (responseCode == 200) { 206 | String selectedImageHref = data.image300Href; 207 | selectedImageHref.replace("https", "http"); 208 | 209 | if (selectedImageHref != currentImageUrl) { 210 | 211 | Serial.println("New Image. Downloading it"); 212 | 213 | isDownloadingCover = true; 214 | client.downloadFile(selectedImageHref, "/cover.jpg"); 215 | isDownloadingCover = false; 216 | currentImageUrl = selectedImageHref; 217 | drawJPEGFromSpiffs("/cover.jpg", 45, 0); 218 | gfx.setColor(ILI9341_YELLOW); 219 | } 220 | } 221 | if (responseCode == 400) { 222 | gfx.fillBuffer(MINI_BLACK); 223 | gfx.setColor(MINI_WHITE); 224 | gfx.setTextAlignment(TEXT_ALIGN_CENTER); 225 | gfx.drawString(120, 20, "Please define\nclientId and clientSecret"); 226 | gfx.commit(0, 160); 227 | } 228 | } 229 | drawSongInfo(); 230 | 231 | if (touchController.isTouched(500) && millis() - lastTouchMillis > 1000) { 232 | TS_Point p = touchController.getPoint(); 233 | lastTouchPoint = p; 234 | lastTouchMillis = millis(); 235 | String command = ""; 236 | String method = ""; 237 | if (p.y > 160) { 238 | if (p.x < 80) { 239 | method = "POST"; 240 | command = "previous"; 241 | } else if (p.x >= 80 && p.x <= 160) { 242 | method = "PUT"; 243 | command = "play"; 244 | if (data.isPlaying) { 245 | command = "pause"; 246 | } 247 | data.isPlaying = !data.isPlaying; 248 | } else if (p.x > 160) { 249 | method = "POST"; 250 | command = "next"; 251 | } 252 | uint16_t responseCode = client.playerCommand(&auth, method, command); 253 | Serial.print("playerCommand response ="); 254 | Serial.println(responseCode); 255 | } 256 | } 257 | } 258 | 259 | // Set time via NTP, as required for x.509 validation 260 | void setClock() { 261 | configTime(TIMEZONE, "pool.ntp.org"); 262 | 263 | Serial.print("Waiting for NTP time sync: "); 264 | time_t now = time(nullptr); 265 | while (now < 8 * 3600 * 2) { 266 | delay(500); 267 | Serial.print("."); 268 | now = time(nullptr); 269 | } 270 | Serial.println(""); 271 | struct tm timeinfo; 272 | gmtime_r(&now, &timeinfo); 273 | Serial.print("Current time UTC: "); 274 | Serial.print(asctime(&timeinfo)); 275 | } 276 | 277 | void drawSongInfo() { 278 | if (millis() - lastDrawingUpdate < 333) { 279 | return; 280 | } 281 | lastDrawingUpdate = millis(); 282 | long timeSinceUpdate = 0; 283 | if (data.isPlaying) { 284 | timeSinceUpdate = millis() - lastUpdate; 285 | } 286 | drawProgress(_min(data.progressMs + timeSinceUpdate, data.durationMs), data.durationMs, data.title, data.artistName, data.isPlaying, data.isPlayerActive); 287 | } 288 | 289 | void drawProgress(uint64_t progressMs, uint64_t durationMs, String songTitle, String artistName, boolean isPlaying, boolean isPlayerActive) { 290 | counter++; 291 | 292 | if (isDownloadingCover) { 293 | gfx.fillBuffer(MINI_BLACK); 294 | gfx.setColor(MINI_WHITE); 295 | gfx.drawCircle(100, 80, 5); 296 | gfx.drawCircle(120, 80, 5); 297 | gfx.drawCircle(140, 80, 5); 298 | gfx.fillCircle(100 + 20 * (counter % 3), 80, 5); 299 | gfx.commit(0, 0); 300 | } 301 | 302 | gfx.fillBuffer(MINI_BLACK); 303 | if (isPlayerActive) { 304 | gfx.setTextAlignment(TEXT_ALIGN_LEFT); 305 | 306 | uint8_t percentage = 100.0 * progressMs / durationMs; 307 | gfx.setColor(MINI_WHITE); 308 | gfx.setTextAlignment(TEXT_ALIGN_LEFT); 309 | gfx.drawString(10, 70, formatTime(progressMs)); 310 | gfx.setTextAlignment(TEXT_ALIGN_RIGHT); 311 | gfx.drawString(230, 70, formatTime(data.durationMs)); 312 | gfx.setColor(MINI_WHITE); 313 | gfx.setColor(MINI_GRAY); 314 | uint16_t progressX = 10 + 220 * percentage / 100; 315 | uint16_t progressY = 65; 316 | gfx.drawLine(progressX, progressY, 230, progressY); 317 | gfx.setColor(MINI_WHITE); 318 | gfx.drawLine(10, progressY, progressX, progressY); 319 | gfx.fillCircle(progressX, progressY, 5); 320 | 321 | gfx.setTextAlignment(TEXT_ALIGN_LEFT); 322 | String animatedTitle = songTitle; 323 | uint8_t maxChar = 30; 324 | uint8_t excessChars = songTitle.length() - maxChar; 325 | uint8_t currentPos = counter % excessChars; 326 | if (songTitle.length() > maxChar) { 327 | animatedTitle = songTitle.substring(currentPos, currentPos + maxChar); 328 | } 329 | gfx.setTextAlignment(TEXT_ALIGN_CENTER); 330 | gfx.drawString(120, 10, animatedTitle); 331 | gfx.setColor(MINI_GRAY); 332 | gfx.drawString(120, 30, artistName); 333 | gfx.setColor(MINI_YELLOW); 334 | gfx.drawPalettedBitmapFromPgm(88, 90, isPlaying ? miniPause : miniPlay); 335 | 336 | gfx.drawPalettedBitmapFromPgm(30, 90, miniPrevious); 337 | gfx.drawPalettedBitmapFromPgm(190, 90, miniNext); 338 | if (millis() - lastTouchMillis < SHOW_TOUCH_POINT_MILLIS) { 339 | gfx.setColor(MINI_YELLOW); 340 | gfx.fillCircle(lastTouchPoint.x, lastTouchPoint.y - 160, 5); 341 | } 342 | gfx.commit(0, 160); 343 | } else { 344 | displayLogo(); 345 | } 346 | } 347 | 348 | void displayLogo() { 349 | gfx.setTextAlignment(TEXT_ALIGN_CENTER); 350 | gfx.setFont(ArialMT_Plain_16); 351 | gfx.setColor(MINI_YELLOW); 352 | gfx.drawPalettedBitmapFromPgm(20, 20, ThingPulseLogo); 353 | gfx.drawString(120, 110, "ESPotify-Remote"); 354 | gfx.commit(0, 0); 355 | } 356 | 357 | void calibrationCallback(int16_t x, int16_t y) { 358 | gfx.fillBuffer(MINI_BLACK); 359 | 360 | gfx.drawString(120, 10, "Please calibrate\ntouch screen by\ntouch point"); 361 | gfx.setColor(MINI_WHITE); 362 | if (y < BUFFER_HEIGHT) { 363 | gfx.fillCircle(x, y, 10); 364 | gfx.commit(0, 0); 365 | gfx.fillBuffer(MINI_BLACK); 366 | gfx.commit(0, BUFFER_HEIGHT); 367 | } else { 368 | gfx.fillCircle(x, y - BUFFER_HEIGHT, 10); 369 | gfx.commit(0, BUFFER_HEIGHT); 370 | gfx.fillBuffer(MINI_BLACK); 371 | gfx.commit(0, 0); 372 | } 373 | } 374 | 375 | String formatTime(uint32_t time) { 376 | char time_str[10]; 377 | uint8_t minutes = time / (1000 * 60); 378 | uint8_t seconds = (time / 1000) % 60; 379 | sprintf(time_str, "%2d:%02d", minutes, seconds); 380 | return String(time_str); 381 | } 382 | 383 | void saveRefreshToken(String refreshToken) { 384 | File f = SPIFFS.open("/refreshToken.txt", "w+"); 385 | if (!f) { 386 | Serial.println("Failed to open config file"); 387 | return; 388 | } 389 | f.println(refreshToken); 390 | f.close(); 391 | } 392 | 393 | String loadRefreshToken() { 394 | Serial.println("Loading config"); 395 | File f = SPIFFS.open("/refreshToken.txt", "r"); 396 | if (!f) { 397 | Serial.println("Failed to open config file"); 398 | return ""; 399 | } 400 | while (f.available()) { 401 | // Lets read line by line from the file 402 | String token = f.readStringUntil('\r'); 403 | Serial.printf("Refresh Token: %s\n", token.c_str()); 404 | f.close(); 405 | return token; 406 | } 407 | return ""; 408 | } 409 | 410 | void drawJPEGFromSpiffs(String filename, int xpos, int ypos) { 411 | char buffer[filename.length() + 1]; 412 | filename.toCharArray(buffer, filename.length() + 1); 413 | 414 | JpegDec.decodeFile(buffer); 415 | uint8_t zoomFactor = 2; 416 | uint16_t *pImg; 417 | uint16_t mcu_w = JpegDec.MCUWidth; 418 | uint16_t mcu_h = JpegDec.MCUHeight; 419 | Serial.printf("MCU W/H: %d, %d\n", mcu_w, mcu_h); 420 | // uint32_t mcu_pixels = mcu_w * mcu_h; // total pixels 421 | // TODO immplmenet something to track drawtime performance 422 | // uint32_t drawTime = millis(); 423 | 424 | while (JpegDec.read()) { 425 | pImg = JpegDec.pImage; 426 | int mcu_x = (JpegDec.MCUx * mcu_w) / zoomFactor + xpos; 427 | int mcu_y = (JpegDec.MCUy * mcu_h) / zoomFactor + ypos; 428 | 429 | tft.setAddrWindow(mcu_x, mcu_y, mcu_x + (mcu_w / zoomFactor) - 1, mcu_y + (mcu_h / zoomFactor) - 1); 430 | 431 | for (uint8_t y = 0; y < mcu_h; y++) { 432 | for (uint8_t x = 0; x < mcu_w; x++) { 433 | if (x % zoomFactor == 0 && y % zoomFactor == 0) { 434 | tft.pushColor(*pImg); 435 | } 436 | *pImg++; 437 | } 438 | } 439 | drawSongInfo(); 440 | } 441 | } 442 | -------------------------------------------------------------------------------- /esp8266-spotify-remote.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.2043 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "esp8266-spotify-remote", "esp8266-spotify-remote.vcxproj", "{C5F80730-F44F-4478-BDAE-6634EFC2CA88}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x86 = Debug|x86 11 | Release|x86 = Release|x86 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Debug|x86.ActiveCfg = Debug|Win32 15 | {C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Debug|x86.Build.0 = Debug|Win32 16 | {C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Release|x86.ActiveCfg = Release|Win32 17 | {C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Release|x86.Build.0 = Release|Win32 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {F2D4E34A-4599-4719-AAD6-8A664670EBB8} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /esp8266-spotify-remote.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {C5F80730-F44F-4478-BDAE-6634EFC2CA88} 15 | esp8266_spotify_remote 16 | esp8266-spotify-remote 17 | 18 | 19 | 20 | Application 21 | true 22 | 23 | 24 | MultiByte 25 | 26 | 27 | Application 28 | false 29 | 30 | 31 | true 32 | MultiByte 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | Level3 48 | Disabled 49 | true 50 | $(ProjectDir)..\esp8266-spotify-remote;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\libraries\ESP8266WiFi\src;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\libraries\minigrafx\src;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\libraries\JPEGDecoder\src;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\libraries\SD\src;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\libraries\SPI;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\libraries\ESP8266mDNS;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\libraries\json-streaming-parser;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\libraries\ESP8266WebServer\src;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\libraries\XPT2046_Touchscreen;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\libraries\ESP8266HTTPClient\src;$(ProjectDir)..\..\Program Files (x86)\Arduino\libraries;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\libraries;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\libraries;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\cores\esp8266;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\cores\esp8266\avr;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\cores\esp8266\libb64;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\cores\esp8266\spiffs;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\cores\esp8266\umm_malloc;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\variants\nodemcu;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\tools\sdk\include;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\tools\sdk\lwip2\include;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\tools\sdk\libc\xtensa-lx106-elf\include;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\tools\xtensa-lx106-elf\xtensa-lx106-elf\include\c++\4.8.2;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\tools\xtensa-lx106-elf\xtensa-lx106-elf\include\c++\4.8.2\xtensa-lx106-elf;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\tools\xtensa-lx106-elf\xtensa-lx106-elf\include;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\tools\xtensa-lx106-elf\lib\gcc\xtensa-lx106-elf\4.8.2\include;$(ProjectDir)..\..\Users\$(USERNAME)\Documents\Arduino\hardware\esp8266com\esp8266\tools\sdk\include;%(AdditionalIncludeDirectories) 51 | $(ProjectDir)__vm\.esp8266-spotify-remote.vsarduino.h;%(ForcedIncludeFiles) 52 | false 53 | __ESP8266_ESp8266__;__ESP8266_ESP8266__;_VMDEBUG=1;__ets__;ICACHE_FLASH;F_CPU=80000000L;LWIP_OPEN_SRC;TCP_MSS=536;DEBUG_ESP_PORT=Serial;DEBUG_ESP_SSL;DEBUG_ESP_TLS_MEM;DEBUG_ESP_HTTP_CLIENT;DEBUG_ESP_HTTP_SERVER;DEBUG_ESP_CORE;DEBUG_ESP_WIFI;DEBUG_ESP_HTTP_UPDATE;DEBUG_ESP_UPDATER;DEBUG_ESP_OTA;DEBUG_ESP_OOM;ARDUINO=10805;ARDUINO_ESP8266_NODEMCU;ARDUINO_ARCH_ESP8266;ESP8266;__cplusplus=201103L;_VMICRO_INTELLISENSE;%(PreprocessorDefinitions) 54 | 55 | 56 | true 57 | 58 | 59 | 60 | 61 | Level3 62 | MaxSpeed 63 | true 64 | true 65 | true 66 | 67 | 68 | true 69 | true 70 | true 71 | 72 | 73 | 74 | 75 | 76 | 77 | VisualMicroDebugger 78 | 79 | 80 | 81 | 82 | CppCode 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /esp8266-spotify-remote.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | Header Files 38 | 39 | 40 | 41 | 42 | Source Files 43 | 44 | 45 | Source Files 46 | 47 | 48 | 49 | 50 | Resource Files 51 | 52 | 53 | Resource Files 54 | 55 | 56 | Resource Files 57 | 58 | 59 | Resource Files 60 | 61 | 62 | Resource Files 63 | 64 | 65 | Resource Files 66 | 67 | 68 | Resource Files 69 | 70 | 71 | -------------------------------------------------------------------------------- /images.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | const char ThingPulseLogo[] PROGMEM = { 25 | 0x01, // Version: 1 26 | 0x02, // BitDepth: 2 27 | 0x00, 0xC8, // Width: 200 28 | 0x00, 0x50, // Height: 80 29 | // Round width to next byte: 200 30 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 31 | 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 32 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 33 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, 0xAA, 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xA0, 34 | 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 35 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 36 | 0xAA, 0xAA, 0x80, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xA0, 0x0F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 37 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 38 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xA0, 39 | 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 40 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 41 | 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xA0, 0x0F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 42 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 43 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA0, 0x00, 0x00, 0x0A, 0xAA, 0xAA, 0xA0, 44 | 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 45 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 46 | 0xAA, 0xAA, 0xAA, 0xA8, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xA0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 47 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 48 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xA0, 49 | 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 50 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xA0, 0x00, 51 | 0x0A, 0xAA, 0xAA, 0xAA, 0x80, 0x02, 0xAA, 0xAA, 0xAA, 0xA0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 52 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 53 | 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xA0, 0x0A, 0xAA, 0xAA, 0xAA, 0x00, 54 | 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 55 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0x80, 0x00, 0x00, 56 | 0x00, 0x02, 0xAA, 0xAA, 0xA0, 0x2A, 0xAA, 0xAA, 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 57 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 58 | 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xA0, 0x2A, 0xAA, 0xAA, 0x00, 0x00, 59 | 0x0F, 0xF0, 0x00, 0x3F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 60 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xA0, 0x00, 0x00, 0x00, 61 | 0x00, 0x00, 0x0A, 0xAA, 0x80, 0xAA, 0xAA, 0xA8, 0x00, 0x00, 0x0F, 0xFF, 0xC0, 0x0F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 62 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 63 | 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0x82, 0xAA, 0xAA, 0xA0, 0x00, 0x00, 64 | 0x0F, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 65 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 66 | 0x00, 0x00, 0x00, 0xAA, 0x02, 0xAA, 0xAA, 0x80, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 67 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 68 | 0x00, 0x00, 0x00, 0x0A, 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0x0A, 0xAA, 0xAA, 0x00, 0x00, 0x00, 69 | 0x0F, 0xFF, 0xFF, 0xC0, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 70 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 71 | 0x00, 0x00, 0x00, 0x28, 0x0A, 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 72 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 73 | 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0A, 0xAA, 0xA0, 0x00, 0x00, 0x00, 74 | 0x0F, 0xFF, 0xFF, 0xFC, 0x0F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 75 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 76 | 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFC, 0x03, 0xFF, 0xFC, 0x00, 0x00, 0x00, 77 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 78 | 0x00, 0x00, 0x00, 0x2A, 0xAA, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xA0, 0x00, 0x00, 0x00, 79 | 0x00, 0x3F, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 80 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x80, 0x00, 0x00, 0x00, 0x00, 81 | 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 82 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 83 | 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0x80, 0x00, 0x00, 0x00, 84 | 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 85 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 86 | 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x80, 0x00, 0x00, 0x00, 0x3F, 0x00, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 87 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 88 | 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x03, 89 | 0xFF, 0xC0, 0x3F, 0xFF, 0xC0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 90 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 91 | 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xF0, 0x3F, 0xFF, 0xC0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 92 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 93 | 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x0F, 94 | 0xFF, 0xF0, 0x3F, 0xFF, 0xC0, 0x3F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 95 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 96 | 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 97 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 98 | 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x03, 99 | 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 100 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 101 | 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 102 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 103 | 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 104 | 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 105 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 106 | 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00, 107 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 108 | 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 109 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 110 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x80, 0x00, 0x00, 0x00, 0x00, 111 | 0x00, 0x00, 0x00, 0x02, 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0x00, 0x00, 0x00, 112 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 113 | 0x00, 0x00, 0x00, 0x2A, 0xAA, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 114 | 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 115 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 116 | 0x00, 0x00, 0x00, 0x0A, 0xAA, 0xA8, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, 0xA8, 0x00, 0x00, 0x00, 117 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 118 | 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, 0xA0, 0x28, 0x00, 0x00, 0x00, 119 | 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 120 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 121 | 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xA0, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xA0, 0x00, 0x00, 0x00, 122 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 123 | 0x00, 0x00, 0x00, 0x0A, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xA0, 0xAA, 0x00, 0x00, 0x00, 124 | 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 125 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0x80, 0x00, 0x00, 0x00, 126 | 0x00, 0x00, 0x02, 0xAA, 0xAA, 0x80, 0xAA, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x80, 0x00, 0x00, 0x00, 127 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 128 | 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, 0xAA, 0x82, 0xAA, 0xA0, 0x00, 0x00, 129 | 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 130 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xA8, 0x00, 0x00, 0x00, 131 | 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0x02, 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 132 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 133 | 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xA8, 0x0A, 0xAA, 0xAA, 0x80, 0x00, 134 | 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 135 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xA0, 0x00, 0x00, 136 | 0x00, 0x0A, 0xAA, 0xAA, 0xA8, 0x0A, 0xAA, 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x0A, 0xAA, 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 137 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 138 | 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xA0, 0x02, 0xAA, 0xAA, 0xAA, 0x80, 139 | 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 140 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 141 | 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x00, 0x00, 0x00, 142 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 143 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 144 | 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 145 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 146 | 0xAA, 0xAA, 0xAA, 0xA8, 0x00, 0x00, 0x0A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 147 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 148 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 149 | 0xAA, 0xAA, 0xAA, 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 150 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 151 | 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 152 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 153 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, 0xAA, 154 | 0xAA, 0xAA, 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 155 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 156 | 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 157 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 158 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 159 | 0xAA, 0xAA, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 160 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 161 | 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 162 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 163 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 164 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 165 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 166 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 167 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 168 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 169 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 170 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 171 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 172 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 173 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 174 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 175 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 176 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 177 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 178 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 179 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 180 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 181 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 182 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 183 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 184 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 185 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 186 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 187 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 188 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 189 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 190 | 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xC0, 0x00, 0x00, 0x3F, 0xC0, 0x3F, 0xC0, 0xFF, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0xFF, 191 | 0xFF, 0xFF, 0xFC, 0x0A, 0xAA, 0xAA, 0xAA, 0xA8, 0x00, 0x0A, 0xA0, 0x00, 0x00, 0x2A, 0x80, 0x2A, 0x80, 0x00, 0x00, 0x00, 192 | 0x00, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xC0, 0x00, 0x00, 0x3F, 193 | 0xC0, 0x3F, 0xC0, 0xFF, 0xC0, 0x00, 0x3F, 0xC0, 0x03, 0xFF, 0xFF, 0xFF, 0xFC, 0x0A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x0A, 194 | 0xA0, 0x00, 0x00, 0x2A, 0x80, 0x2A, 0x80, 0x00, 0x00, 0x00, 0x0A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 195 | 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xC0, 0x00, 0x00, 0x3F, 0xC0, 0x3F, 0xC0, 0xFF, 0xF0, 0x00, 0x3F, 0xC0, 0x0F, 0xFF, 196 | 0xFF, 0xFF, 0xFC, 0x0A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x0A, 0xA0, 0x00, 0x00, 0x2A, 0x80, 0x2A, 0x80, 0x00, 0x00, 0x00, 197 | 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xC0, 0x00, 0x00, 0x3F, 198 | 0xC0, 0x3F, 0xC0, 0xFF, 0xFC, 0x00, 0x3F, 0xC0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFC, 0x0A, 0xAA, 0xAA, 0xAA, 0xAA, 0xA0, 0x0A, 199 | 0xA0, 0x00, 0x00, 0x2A, 0x80, 0x2A, 0x80, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 200 | 0x00, 0x03, 0xFC, 0x00, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x3F, 0xC0, 0x3F, 0xC0, 0xFF, 0xFF, 0x00, 0x3F, 0xC0, 0x3F, 0xC0, 201 | 0x00, 0x00, 0x00, 0x0A, 0xA0, 0x00, 0x00, 0x0A, 0xA0, 0x0A, 0xA0, 0x00, 0x00, 0x2A, 0x80, 0x2A, 0x80, 0x00, 0x00, 0x00, 202 | 0x2A, 0x80, 0x00, 0x00, 0x00, 0x02, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFC, 0x00, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x3F, 203 | 0xC0, 0x3F, 0xC0, 0xFF, 0xFF, 0xC0, 0x3F, 0xC0, 0x3F, 0xC0, 0x00, 0x00, 0x00, 0x0A, 0xA0, 0x00, 0x00, 0x0A, 0xA0, 0x0A, 204 | 0xA0, 0x00, 0x00, 0x2A, 0x80, 0x2A, 0x80, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x02, 0xA8, 0x00, 0x00, 0x00, 205 | 0x00, 0x03, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x3F, 0xC0, 0xFF, 0x3F, 0xF0, 0x3F, 0xC0, 0x3F, 0xC0, 206 | 0x0F, 0xFF, 0xF0, 0x0A, 0xA0, 0x00, 0x00, 0x0A, 0xA0, 0x0A, 0xA0, 0x00, 0x00, 0x2A, 0x80, 0x2A, 0x80, 0x00, 0x00, 0x00, 207 | 0x2A, 0xAA, 0xAA, 0xAA, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xA8, 0x00, 0x03, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 208 | 0xC0, 0x3F, 0xC0, 0xFF, 0x0F, 0xFC, 0x3F, 0xC0, 0x3F, 0xC0, 0x0F, 0xFF, 0xFC, 0x0A, 0xAA, 0xAA, 0xAA, 0xAA, 0xA0, 0x0A, 209 | 0xA0, 0x00, 0x00, 0x2A, 0x80, 0x2A, 0x80, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA0, 0x02, 0xAA, 0xAA, 0xAA, 0xA8, 210 | 0x00, 0x03, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x3F, 0xC0, 0xFF, 0x03, 0xFF, 0x3F, 0xC0, 0x3F, 0xC0, 211 | 0x0F, 0xFF, 0xFC, 0x0A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x0A, 0xA0, 0x00, 0x00, 0x2A, 0x80, 0x2A, 0x80, 0x00, 0x00, 0x00, 212 | 0x0A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xA8, 0x00, 0x03, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 213 | 0xC0, 0x3F, 0xC0, 0xFF, 0x00, 0xFF, 0x3F, 0xC0, 0x3F, 0xC0, 0x0F, 0xFF, 0xFC, 0x0A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x0A, 214 | 0xA0, 0x00, 0x00, 0x2A, 0x80, 0x2A, 0x80, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xA8, 215 | 0x00, 0x03, 0xFC, 0x00, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x3F, 0xC0, 0x3F, 0xC0, 0xFF, 0x00, 0xFF, 0xFF, 0xC0, 0x3F, 0xC0, 216 | 0x00, 0x03, 0xFC, 0x0A, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x0A, 0xA0, 0x00, 0x00, 0x2A, 0x80, 0x2A, 0x80, 0x00, 0x00, 0x00, 217 | 0x00, 0x00, 0x00, 0x02, 0xA8, 0x02, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFC, 0x00, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x3F, 218 | 0xC0, 0x3F, 0xC0, 0xFF, 0x00, 0x3F, 0xFF, 0xC0, 0x3F, 0xC0, 0x00, 0x03, 0xFC, 0x0A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x0A, 219 | 0xA0, 0x00, 0x00, 0x2A, 0x80, 0x2A, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0x02, 0xA8, 0x00, 0x00, 0x00, 220 | 0x00, 0x03, 0xFC, 0x00, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x3F, 0xC0, 0x3F, 0xC0, 0xFF, 0x00, 0x0F, 0xFF, 0xC0, 0x3F, 0xFF, 221 | 0xFF, 0xFF, 0xFC, 0x0A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x2A, 0xAA, 0xAA, 0xAA, 0x80, 222 | 0x0A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x03, 0xFC, 0x00, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x3F, 223 | 0xC0, 0x3F, 0xC0, 0xFF, 0x00, 0x03, 0xFF, 0xC0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFC, 0x0A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x0A, 224 | 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x2A, 0xAA, 0xAA, 0xAA, 0x80, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 225 | 0x00, 0x03, 0xFC, 0x00, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x3F, 0xC0, 0x3F, 0xC0, 0xFF, 0x00, 0x00, 0xFF, 0xC0, 0x0F, 0xFF, 226 | 0xFF, 0xFF, 0xFC, 0x0A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0x80, 227 | 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x03, 0xFC, 0x00, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x3F, 228 | 0xC0, 0x3F, 0xC0, 0xFF, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0x0A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 229 | 0xAA, 0xAA, 0xAA, 0xA8, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0x80, 0x2A, 0xAA, 0xAA, 0xAA, 0x80, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 230 | }; 231 | 232 | const char miniNext[] PROGMEM = { 233 | 0x01, // Version: 1 234 | 0x02, // BitDepth: 2 235 | 0x00, 0x13, // Width: 19 236 | 0x00, 0x40, // Height: 64 237 | // Round width to next byte: 24 238 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 239 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 240 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 241 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 242 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 243 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 244 | 0x0F, 0x00, 0x00, 0x0D, 0x40, 0x05, 0x70, 0x00, 0x0D, 0x40, 0x05, 0x5C, 0x00, 0x0D, 0x40, 0x05, 0x55, 0xC0, 0x0D, 0x40, 245 | 0x05, 0x55, 0x5C, 0x0D, 0x40, 0x05, 0x55, 0x55, 0x0D, 0x40, 0x05, 0x55, 0x55, 0x75, 0x40, 0x05, 0x55, 0x55, 0x55, 0x40, 246 | 0x05, 0x55, 0x55, 0x55, 0x40, 0x05, 0x55, 0x55, 0xCD, 0x40, 0x05, 0x55, 0x5C, 0x0D, 0x40, 0x05, 0x55, 0x40, 0x0D, 0x40, 247 | 0x05, 0x57, 0x00, 0x0D, 0x40, 0x05, 0x70, 0x00, 0x0D, 0x40, 0x0F, 0x00, 0x00, 0x0D, 0x40, 0x00, 0x00, 0x00, 0x03, 0xC0, 248 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 249 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 250 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 251 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 252 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 253 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 254 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 255 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 256 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 257 | 0x00, 0x00, 0x00, 0x00, }; 258 | 259 | const char miniPrevious[] PROGMEM = { 260 | 0x01, // Version: 1 261 | 0x02, // BitDepth: 2 262 | 0x00, 0x13, // Width: 19 263 | 0x00, 0x40, // Height: 64 264 | // Round width to next byte: 24 265 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 266 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 267 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 268 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 269 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 270 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 271 | 0x0D, 0x40, 0x00, 0x00, 0xC0, 0x0D, 0x40, 0x00, 0x0D, 0x70, 0x0D, 0x40, 0x00, 0xD5, 0x70, 0x0D, 0x40, 0x0D, 0x55, 0x70, 272 | 0x0D, 0x40, 0x35, 0x55, 0x70, 0x0D, 0x43, 0x55, 0x55, 0x70, 0x0D, 0x75, 0x55, 0x55, 0x70, 0x0D, 0x55, 0x55, 0x55, 0x70, 273 | 0x0D, 0x55, 0x55, 0x55, 0x70, 0x0D, 0x41, 0x55, 0x55, 0x70, 0x0D, 0x40, 0xD5, 0x55, 0x70, 0x0D, 0x40, 0x0D, 0x55, 0x70, 274 | 0x0D, 0x40, 0x00, 0xD5, 0x70, 0x0D, 0x40, 0x00, 0x05, 0x70, 0x0D, 0x40, 0x00, 0x03, 0x40, 0x03, 0xC0, 0x00, 0x00, 0x00, 275 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 276 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 277 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 278 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 279 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 280 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 281 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 282 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 283 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 284 | 0x00, 0x00, 0x00, 0x00, }; 285 | 286 | const char miniPlay[] PROGMEM = { 287 | 0x01, // Version: 1 288 | 0x02, // BitDepth: 2 289 | 0x00, 0x40, // Width: 64 290 | 0x00, 0x40, // Height: 64 291 | // Round width to next byte: 64 292 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 293 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 294 | 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, 0xFF, 0xC0, 0x00, 295 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 296 | 0x00, 0x00, 0x00, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 297 | 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 298 | 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 299 | 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 300 | 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 301 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 302 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 303 | 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 304 | 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x0F, 0x00, 0x00, 305 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 306 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 307 | 0x00, 0x00, 0x0C, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 308 | 0x00, 0xF0, 0x00, 0x00, 0x00, 0x0D, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0xC0, 0x00, 0x00, 309 | 0x00, 0x0D, 0x55, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x0D, 0x55, 0x5C, 310 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x55, 0x55, 0xC0, 0x00, 0x00, 0x00, 311 | 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x55, 0x55, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 312 | 0x03, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x55, 0x55, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x00, 313 | 0x00, 0x0D, 0x55, 0x55, 0x55, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x55, 0x55, 314 | 0x55, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x55, 0x55, 0x55, 0x55, 0x40, 0x00, 315 | 0x00, 0x00, 0x00, 0x30, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x55, 0x55, 0x55, 0x55, 0x5C, 0x00, 0x00, 0x00, 0x00, 0x30, 316 | 0x0C, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x55, 0x55, 0x55, 0x55, 0x55, 0xC0, 0x00, 0x00, 0x00, 0x30, 0x0C, 0x00, 0x00, 0x00, 317 | 0x00, 0x0D, 0x55, 0x55, 0x55, 0x55, 0x55, 0xC0, 0x00, 0x00, 0x00, 0x30, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x55, 0x55, 318 | 0x55, 0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x55, 0x55, 0x55, 0x55, 0x5C, 0x00, 319 | 0x00, 0x00, 0x00, 0x30, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x55, 0x55, 0x55, 0x55, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x30, 320 | 0x0C, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x55, 0x55, 0x55, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0F, 0x00, 0x00, 0x00, 321 | 0x00, 0x0D, 0x55, 0x55, 0x55, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x55, 0x55, 322 | 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x55, 0x55, 0x70, 0x00, 0x00, 0x00, 323 | 0x00, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 324 | 0x03, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x55, 0x5C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x00, 325 | 0x00, 0x0D, 0x55, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x0D, 0x5C, 0x00, 326 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x01, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 327 | 0x00, 0x00, 0x03, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 328 | 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0x00, 329 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 330 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 331 | 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 332 | 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 333 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 334 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 335 | 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 336 | 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 337 | 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 338 | 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 339 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 340 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 341 | 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 342 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 343 | 0x00, 0x00, 0x00, 0x00, }; 344 | 345 | const char miniPause[] PROGMEM = { 346 | 0x01, // Version: 1 347 | 0x02, // BitDepth: 2 348 | 0x00, 0x40, // Width: 64 349 | 0x00, 0x40, // Height: 64 350 | // Round width to next byte: 64 351 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 352 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 353 | 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, 0xFF, 0xC0, 0x00, 354 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 355 | 0x00, 0x00, 0x00, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 356 | 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 357 | 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 358 | 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 359 | 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 360 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 361 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 362 | 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 363 | 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x0F, 0x00, 0x00, 364 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 365 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 366 | 0x00, 0x00, 0x0C, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x0F, 0x00, 367 | 0x00, 0xF0, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0xC0, 0x00, 0x00, 368 | 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 369 | 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 370 | 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x00, 0xC0, 371 | 0x03, 0x00, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x00, 372 | 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 373 | 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 374 | 0x00, 0x00, 0x00, 0x30, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x00, 0x30, 375 | 0x0C, 0x00, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0C, 0x00, 0x00, 0x00, 376 | 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 377 | 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 378 | 0x00, 0x00, 0x00, 0x30, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x00, 0x30, 379 | 0x0C, 0x00, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0F, 0x00, 0x00, 0x00, 380 | 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 381 | 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 382 | 0x00, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x00, 0xC0, 383 | 0x03, 0x00, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x00, 384 | 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 385 | 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 0x00, 0xD5, 0x50, 0x00, 386 | 0x00, 0x00, 0x03, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x15, 0x5C, 0x00, 0x00, 0xD5, 0x50, 0x00, 0x00, 0x00, 0x0F, 0x00, 387 | 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0x00, 388 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 389 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 390 | 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 391 | 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 392 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 393 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 394 | 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 395 | 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 396 | 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 397 | 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 398 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 399 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 400 | 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 401 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 402 | 0x00, 0x00, 0x00, 0x00, }; 403 | -------------------------------------------------------------------------------- /images/SpotifyAppSettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThingPulse/esp8266-spotify-remote/13727e1e8c79464aed92540a2a51917f0916f01f/images/SpotifyAppSettings.png -------------------------------------------------------------------------------- /images/SpotifyAppSettingsSave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThingPulse/esp8266-spotify-remote/13727e1e8c79464aed92540a2a51917f0916f01f/images/SpotifyAppSettingsSave.png -------------------------------------------------------------------------------- /images/SpotifyAppSignUp1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThingPulse/esp8266-spotify-remote/13727e1e8c79464aed92540a2a51917f0916f01f/images/SpotifyAppSignUp1.png -------------------------------------------------------------------------------- /images/SpotifyConnectScreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThingPulse/esp8266-spotify-remote/13727e1e8c79464aed92540a2a51917f0916f01f/images/SpotifyConnectScreen.png -------------------------------------------------------------------------------- /images/SpotifyCredentials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThingPulse/esp8266-spotify-remote/13727e1e8c79464aed92540a2a51917f0916f01f/images/SpotifyCredentials.png -------------------------------------------------------------------------------- /images/SpotifyDashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThingPulse/esp8266-spotify-remote/13727e1e8c79464aed92540a2a51917f0916f01f/images/SpotifyDashboard.png -------------------------------------------------------------------------------- /images/SpotifyppSignUp3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThingPulse/esp8266-spotify-remote/13727e1e8c79464aed92540a2a51917f0916f01f/images/SpotifyppSignUp3.png -------------------------------------------------------------------------------- /mem-analysis.txt: -------------------------------------------------------------------------------- 1 | 21:42:19.037 -> SDK:2.2.2-dev(38a443e)/Core:2.7.3-3-g2843a5ac=20703003/lwIP:STABLE-2_1_2_RELEASE/glue:1.2-30-g92add50/BearSSL:5c771be 2 | 21:42:19.037 -> 3 | 21:42:19.037 -> *** Memory stats right after setup() *** 4 | 21:42:19.037 -> Free heap: 34184 5 | 21:42:19.037 -> Max free block size: 34112 6 | 21:42:19.037 -> Heap fragmentation: 1 7 | 21:42:19.037 -> 8 | 21:42:19.037 -> 9 | 21:42:19.037 -> connecting to SheDJs 10 | 21:42:19.037 -> scandone 11 | 21:42:19.142 -> scandone 12 | 21:42:19.142 -> state: 0 -> 2 (b0) 13 | 21:42:19.142 -> state: 2 -> 3 (0) 14 | 21:42:19.142 -> state: 3 -> 5 (10) 15 | 21:42:19.142 -> add 0 16 | 21:42:19.142 -> aid 4 17 | 21:42:19.142 -> cnt 18 | 21:42:19.142 -> 19 | 21:42:19.142 -> connected with ******, channel 1 20 | 21:42:19.142 -> dhcp client start... 21 | 21:42:19.179 -> ip:192.168.0.53,mask:255.255.255.0,gw:192.168.0.1 22 | 21:42:19.535 -> . 23 | 21:42:19.535 -> WiFi connected 24 | 21:42:19.535 -> IP address: 25 | 21:42:19.535 -> 192.168.0.53 26 | 21:42:19.535 -> *** Memory stats after WiFi connection *** 27 | 21:42:19.535 -> Free heap: 33608 28 | 21:42:19.535 -> Max free block size: 32864 29 | 21:42:19.535 -> Heap fragmentation: 3 30 | 21:42:19.535 -> 31 | 21:42:19.535 -> Waiting for NTP time sync: . 32 | 21:42:20.035 -> Current time: Fri Sep 11 19:42:19 2020 33 | 21:42:20.109 -> BSSL:_connectSSL: start connection 34 | 21:42:20.844 -> BSSL:Connected! 35 | 21:42:20.844 -> Connection to Spotify established. 36 | 21:42:20.844 -> Stack used: 1200 37 | 21:42:20.844 -> 38 | 21:42:20.844 -> *** Memory stats after socket connection established *** 39 | 21:42:20.844 -> Free heap: 31336 40 | 21:42:20.844 -> Max free block size: 31200 41 | 21:42:20.844 -> Heap fragmentation: 1 42 | 21:42:20.844 -> 43 | 21:42:21.059 -> *** Memory stats after 1st GFX commit *** 44 | 21:42:21.059 -> Free heap: 21800 45 | 21:42:21.059 -> Max free block size: 21584 46 | 21:42:21.059 -> Heap fragmentation: 1 47 | 21:42:21.059 -> 48 | 21:42:21.416 -> *** Memory stats after SPIFFS mounted *** 49 | 21:42:21.416 -> Free heap: 19600 50 | 21:42:21.416 -> Max free block size: 19384 51 | 21:42:21.416 -> Heap fragmentation: 2 52 | 21:42:21.416 -> 53 | 21:42:21.416 -> PIFFS opened: 54 | 21:42:21.416 -> *** Memory stats after touch screen initialized *** 55 | 21:42:21.416 -> Free heap: 19600 56 | 21:42:21.416 -> Max free block size: 19384 57 | 21:42:21.416 -> Heap fragmentation: 2 58 | 21:42:21.416 -> 59 | 21:42:21.416 -> Loading config 60 | 21:42:21.772 -> Failed to open config file 61 | 21:42:21.772 -> No refresh token found. Requesting through browser 62 | 21:42:21.809 -> Starting config portal 63 | 21:42:21.809 -> HTTP server started 64 | 21:42:21.809 -> MDNS responder started 65 | 21:42:21.809 -> Open browser at http://esp8266.local 66 | 21:42:29.125 -> pm open,type:2 0 67 | 21:45:48.142 -> Serving resource '/' 68 | 21:45:48.142 -> Sending HTTP 302 to Spotify 69 | 21:45:48.318 -> Code: ********************************************************************************** 70 | 21:45:48.318 -> Stopping HTTP server 71 | 21:45:48.318 -> Stopping MDNS responder 72 | 21:45:48.358 -> BSSL:_connectSSL: start connection 73 | 21:45:48.358 -> BSSL:_connectSSL: OOM error 74 | 21:45:48.358 -> Connection to accounts.spotify.com:443 failed; returning. 75 | 21:45:48.358 -> Refresh token: 76 | 21:45:48.358 -> Access Token: 77 | 21:45:48.358 -> BSSL:_connectSSL: start connection 78 | 21:45:48.358 -> BSSL:_connectSSL: OOM error 79 | 21:45:48.358 -> connection failed 80 | 21:45:48.358 -> HREF: 81 | 21:45:48.358 -> --------Response Code: 0 82 | 21:45:48.358 -> --------Free mem: 18424 83 | -------------------------------------------------------------------------------- /settings.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2018 by ThingPulse Ltd., https://thingpulse.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #include 25 | 26 | // **************************************************************************** 27 | // User settings 28 | // **************************************************************************** 29 | 30 | // WiFi settings 31 | const char* ssid = "yourssid"; 32 | const char* password = "yourpassw0rd"; 33 | 34 | // Spotify settings 35 | String clientId = "API CLIENT ID"; 36 | String clientSecret = "API CLIENT SECRET"; 37 | // These next two lines have to match. A client clientSpotifierHostName makes the 38 | // node listen on http://esp8266.local. Spotify needs an exact match of the redirect url 39 | String espotifierNodeName = "esp8266"; 40 | String redirectUri = "http%3A%2F%2F" + espotifierNodeName + ".local%2Fcallback%2F"; 41 | 42 | // Pick a timezone at https://github.com/esp8266/Arduino/blob/master/cores/esp8266/TZ.h#L389 43 | #define TIMEZONE TZ_Europe_Zurich 44 | 45 | // **************************************************************************** 46 | // System settings - do not modify unless you understand what you are doing! 47 | // **************************************************************************** 48 | 49 | // DigiCert Global Root CA exported from Firefox as x509.pem format 50 | static const char digicertRootCaCert[] PROGMEM = R"EOF( 51 | -----BEGIN CERTIFICATE----- 52 | MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh 53 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 54 | d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD 55 | QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT 56 | MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j 57 | b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG 58 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB 59 | CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 60 | nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt 61 | 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P 62 | T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 63 | gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO 64 | BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR 65 | TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw 66 | DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr 67 | hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg 68 | 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF 69 | PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls 70 | YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk 71 | CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= 72 | -----END CERTIFICATE----- 73 | )EOF"; 74 | 75 | // UI Settings 76 | #define SHOW_TOUCH_POINT_MILLIS 1500 77 | 78 | // Hardware settings 79 | #define TFT_DC D2 80 | #define TFT_CS D1 81 | #define TFT_LED D8 82 | 83 | #define TOUCH_CS D3 84 | #define TOUCH_IRQ D4 85 | 86 | // Display settings 87 | #define MINI_BLACK 0 88 | #define MINI_WHITE 1 89 | #define MINI_YELLOW 2 90 | #define MINI_GRAY 3 91 | 92 | // defines the colors usable in the paletted 16 color frame buffer 93 | uint16_t palette[] = {ILI9341_BLACK, // 0 94 | ILI9341_WHITE, // 1 95 | ILI9341_YELLOW, // 2 96 | 0x840F 97 | }; //3 98 | 99 | #define MEMORY_DEBUG 100 | --------------------------------------------------------------------------------