Created
January 3, 2021 13:01
-
-
Save tueddy/50ec8868b52118fd1ef2e09f7bab4245 to your computer and use it in GitHub Desktop.
Pietbox
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
###### | |
# # # ###### ##### ##### #### # # | |
# # # # # # # # # # # | |
###### # ##### # ##### # # ## | |
# # # # # # # # ## | |
# # # # # # # # # # | |
# # ###### # ##### #### # # | |
PietBox | |
Copyright (c) 2018/2019 Dirk Carstensen. All rights reserved. | |
This library is free software; you can redistribute it and/or | |
modify it under the terms of the GNU Lesser General Public | |
License as published by the Free Software Foundation; either | |
version 2.1 of the License, or (at your option) any later version. | |
This library is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
Lesser General Public License for more details. | |
You should have received a copy of the GNU Lesser General Public | |
License along with this library; if not, write to the Free Software | |
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
*/ | |
// PietBox | |
#include "Settings.h" | |
#include "Configuration.h" | |
#include "Button.h" | |
#include "carddata.h" | |
// WIFI | |
#include <WiFi.h> | |
#include "SDEditor.h" | |
#include <ESPmDNS.h> | |
#include <AsyncTCP.h> | |
#include <ESPAsyncWebServer.h> | |
#include "MyAsyncWiFiManager.h" | |
#include <DNSServer.h> | |
#include <esp_wifi.h> | |
//#include <ArduinoOTA.h> | |
// SD card | |
#include <FS.h> | |
#include <SD.h> | |
#include <SPI.h> | |
// rotary encoder | |
#include <Encoder.h> | |
// audio | |
#include <AudioFileSourceSD.h> | |
#include <AudioFileSourceSPIFFS.h> | |
#include <AudioFileSourceICYStream.h> | |
#include <AudioFileSourceID3.h> | |
#include <AudioFileSourceBuffer.h> | |
#include <AudioGenerator.h> | |
#include <AudioGeneratorMP3.h> | |
#include <AudioGeneratorWAV.h> | |
#include <AudioGeneratorAAC.h> | |
#include <AudioOutputI2S.h> | |
// NFC | |
#include <MFRC522Extended.h> | |
#include <PN532_SPI.h> | |
#include "PN532.h" | |
//#include "Wiegand.h" | |
// Neopixel | |
#include <NeoPixelBus.h> | |
#include <NeoPixelAnimator.h> | |
//for LED status | |
#include <Ticker.h> | |
// google TTS | |
#include <google-tts.h> | |
const char PROGMEM cardsFileName[] = "/cards.json"; | |
// classes | |
MFRC522Extended rfid_522(PIN_RFID_SS, PIN_RFID_RST); | |
PN532_SPI pn532spi(SPI, PIN_RFID_SS); | |
PN532 rfid_532(pn532spi); | |
AsyncWebServer server(80); | |
DNSServer dns; | |
AsyncWebSocket ws("/ws"); | |
AsyncEventSource events("/events"); | |
SPIClass SPI2; | |
Encoder myEnc(PIN_ENCODER_1, PIN_ENCODER_2); | |
Button encoderButton(PIN_ENCODER_BUTTON, LOW); | |
// NeoPixel | |
const uint8_t AnimationChannels = 1; | |
unsigned long startTimeNeoPixel; | |
NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod> pixels(NEOPIXEL_NUM + 1, PIN_NEOPIXEL_DIN); | |
NeoPixelAnimator animations(AnimationChannels); | |
#define colorSaturation 128 | |
RgbColor red(colorSaturation, 0, 0); | |
RgbColor green(0, colorSaturation, 0); | |
RgbColor blue(0, 0, colorSaturation); | |
RgbColor white(colorSaturation); | |
RgbColor black(0); | |
// what is stored for state is specific to the need, in this case, the colors. | |
// basically what ever you need inside the animation update function | |
struct MyAnimationState | |
{ | |
RgbColor StartingColor; | |
RgbColor EndingColor; | |
}; | |
// one entry per pixel to match the animation timing manager | |
MyAnimationState animationState[AnimationChannels]; | |
Ticker ticker; | |
// variables (RTC Memory) | |
RTC_DATA_ATTR uint32_t sleepCount; | |
static bool hasSD = false; | |
static bool hasExternalPower = false; | |
RTC_DATA_ATTR rfidfReaderType readerType = rtUnknown; | |
boolean card_present = false; | |
static bool shutdownFlag = false; | |
static bool togggleState = false; | |
static float maxVolume = 0.25; | |
static bool WiFiStarted = false; | |
String uid = ""; | |
uint32_t lastTimeButtonPressed; | |
AudioFileSource *audio_in; | |
AudioFileSourceBuffer *audio_buff; | |
AudioFileSourceID3 *audio_id3; | |
AudioGenerator *audio_generator; | |
AudioOutputI2S *audio_out; | |
String currentTitle; | |
String currentPerformer; | |
String currentAlbum; | |
// simple blend function | |
void BlendAnimUpdate(const AnimationParam& param) | |
{ | |
// this gets called for each animation on every time step | |
// progress will start at 0.0 and end at 1.0 | |
// we use the blend function on the RgbColor to mix | |
// color based on the progress given to us in the animation | |
RgbColor updatedColor = RgbColor::LinearBlend( | |
animationState[param.index].StartingColor, | |
animationState[param.index].EndingColor, | |
param.progress); | |
// show current volume on neopixel ring | |
uint32_t neopixelValue = map(config.currentVolume, 0, stepsPerTurn, 0, (NEOPIXEL_NUM) * 255); | |
uint8_t fullLEDs = (neopixelValue / 255); | |
uint8_t lastLED = (neopixelValue % 255); | |
uint8_t i; | |
// apply the color to the strip | |
for(i=0; i < (fullLEDs); i++) { | |
pixels.SetPixelColor(i + 1, updatedColor); | |
} | |
// set last LED | |
if (lastLED != 0) { | |
pixels.SetPixelColor(fullLEDs + 1, updatedColor); | |
} | |
} | |
/* | |
* download text to speech file (in a separate task) | |
*/ | |
void ttsDownloadTaskFunc( void * parameter ) | |
{ | |
while ((!shutdownFlag) && (WiFi.status() != WL_CONNECTED)) { // +++ timeout +++ | |
delay(250); | |
} | |
String url; | |
TTS tts; | |
String text = "Petebox spielt " + currentTitle + " von " + currentPerformer; | |
Serial.println("download text-to-speech file for: " + text + '"'); | |
url = tts.getSpeechUrl(text, "de"); | |
Serial.println("text to speech url: '" + url + "'"); | |
Serial.println("text-to-speech task finished"); | |
Serial.flush(); | |
vTaskDelete( NULL ); | |
} | |
/* | |
* audio status callback: Called when there's a warning or error (like a buffer underflow or decode hiccup | |
*/ | |
void audioStatusCallback(void *cbData, int code, const char *string) | |
{ | |
const char *ptr = reinterpret_cast<const char *>(cbData); | |
// Note that the string may be in PROGMEM, so copy it to RAM for printf | |
char s1[64]; | |
strncpy_P(s1, string, sizeof(s1)); | |
s1[sizeof(s1)-1]=0; | |
Serial.printf("AUDIO STATUS(%s) '%d' = '%s'\n", ptr, code, s1); | |
Serial.flush(); | |
} | |
/* | |
* metadata callback for ID3 tag, an ICY block, etc. | |
* ID3 callback for: Title = 'Der kleine Drache Kokosnuss - 01' | |
* ID3 callback for: Performer = 'Ingo Siegner' | |
* ID3 callback for: Album = '01/Der kleine Drache Kokosnuss' | |
* ID3 callback for: Year = '2010' | |
*/ | |
void audioMetadataCallback(void *cbData, const char *type, bool isUnicode, const char *string) | |
{ | |
(void)cbData; | |
String s; | |
Serial.printf("ID3 callback for: %s = '", type); | |
if (isUnicode) { | |
string += 2; | |
} | |
while (*string) { | |
char a = *(string++); | |
if (isUnicode) { | |
string++; | |
} | |
s = s + a; | |
Serial.printf("%c", a); | |
} | |
Serial.printf("'\n"); | |
Serial.flush(); | |
// get interesting metadata | |
if (strcmp(type, "Title") == 0) { | |
currentTitle = s; | |
currentTitle.trim(); | |
} | |
if (strcmp(type, "Performer") == 0) { | |
currentPerformer = s; | |
currentPerformer.trim(); | |
} | |
if (strcmp(type, "Album") == 0) { | |
currentAlbum = s; | |
currentAlbum.trim(); | |
} | |
} | |
/* | |
* get the audio source | |
*/ | |
audioSource getAudiofileSource(String filename) { | |
String myFilename = filename; | |
filename.toLowerCase(); | |
if (myFilename.startsWith("http")) { | |
return asICY; | |
} else | |
if (myFilename.startsWith("/")) { | |
return asSDCard; | |
} else | |
return asUnsupported; | |
} | |
/* | |
* get the audio format | |
*/ | |
audioFormat getAudiofileFormat(String filename) { | |
String myFilename = filename; | |
myFilename.toLowerCase(); | |
myFilename.trim(); | |
if (myFilename.endsWith(".mp3")) { | |
return afMP3; | |
} else | |
if (myFilename.endsWith(".wav")) { | |
return afWAV; | |
} else | |
if (myFilename.endsWith(".aac")) { | |
return afAAC; | |
} else | |
return afUnsupported; | |
} | |
/* | |
* check is valid audio | |
*/ | |
bool isAudiofile(String filename) { | |
String myFilename = filename; | |
filename.toLowerCase(); | |
filename.trim(); | |
if ((myFilename.endsWith(".mp3")) || (myFilename.endsWith(".wav")) || (myFilename.endsWith(".aac"))) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
/* | |
* read one line from file | |
*/ | |
String readLine(File file) { | |
String received = ""; | |
char ch; | |
while(file.available()){ | |
ch = file.read(); | |
if(ch == '\n') { | |
return received; | |
} else { | |
received+=ch; | |
} | |
} | |
return received; | |
} | |
/* | |
* stop playing audio | |
*/ | |
void stopPlaying(){ | |
// close currently playing file first | |
if (audio_generator && (audio_generator->isRunning())){ | |
Serial.println("stop playing " + config.currentPlaylist + " (" + String(config.currentFileIndex) + ")"); | |
audio_generator->stop(); | |
// wait for playing has stopped | |
while (audio_generator->isRunning()){ | |
delay(50); | |
} | |
audio_in->close(); | |
delete(audio_generator); | |
audio_generator = NULL; | |
Serial.println("audio closed"); | |
} | |
currentTitle = ""; | |
currentPerformer = ""; | |
currentAlbum = ""; | |
// shutdown amplifier | |
Serial.println("shutdown amplifier/SD-card.."); | |
digitalWrite(PIN_DAC_SD, LOW); | |
if (!WiFiStarted) { | |
// turn of neopixel LED | |
Serial.println("turn of neopixel LED.."); | |
uint8_t i; | |
for(i=0; i <= (NEOPIXEL_NUM); i++) { | |
pixels.SetPixelColor(i, black); | |
} | |
pixels.Show(); | |
} | |
} | |
/* | |
* get a file from dir with index | |
*/ | |
String getFileFromDir(File dir, int currentFileIndex){ | |
if (!dir) { | |
return ""; | |
} | |
dir.rewindDirectory(); | |
int idx = 0; | |
int fileFound = false; | |
String FileToPlay; | |
while (!fileFound) { | |
File entry = dir.openNextFile(); | |
if (! entry) { | |
return ""; | |
} | |
FileToPlay = entry.name(); | |
if (isAudiofile(FileToPlay)) { | |
Serial.println("search getFileFromDir, index: " + String(idx) + ": '" + FileToPlay + "'"); //Printing for debugging purpose | |
if (idx == currentFileIndex) { | |
fileFound = true; | |
return FileToPlay; | |
} | |
idx = idx + 1; | |
} | |
entry.close(); | |
} | |
} | |
/* | |
* play audio file | |
*/ | |
bool playFile(String currentPlaylist, int currentFileIndex, long filepos){ | |
// wakeup amplifier/sd card | |
Serial.println("wakeup the amplifier/SD-card.."); | |
pinMode(PIN_DAC_SD, OUTPUT); | |
digitalWrite(PIN_DAC_SD, HIGH); | |
delay(2); | |
if (!SD.exists(currentPlaylist)) { | |
Serial.print("Playlist file not found: "); Serial.println(currentPlaylist); | |
digitalWrite(PIN_DAC_SD, LOW); | |
return false; | |
} | |
// open the playlist | |
File myFile = SD.open(currentPlaylist); | |
if (!myFile) { | |
Serial.print("Cannot open the playlist file: "); Serial.println(currentPlaylist); | |
return false; | |
} | |
// get the file to play from m3u playlist | |
String FileToPlay; | |
int idx = 0; | |
int fileFound = false; | |
while (myFile.available() && (!fileFound)) { | |
FileToPlay = readLine(myFile); | |
Serial.println("index: " + String(idx) + ": '" + FileToPlay + "'"); //Printing for debugging purpose | |
// do some action here | |
if (idx == currentFileIndex) { | |
fileFound = true; | |
break; | |
} | |
idx = idx + 1; | |
} | |
// close the file properly | |
myFile.close(); | |
// index out of bounds? | |
if (!fileFound) { | |
Serial.print("index: " + String(idx) + " not found in playlist file: "); Serial.println(currentPlaylist); | |
return false; | |
} | |
// is the audio source supported? | |
audioSource currentAudioSource = getAudiofileSource(FileToPlay); | |
if (currentAudioSource == asUnsupported) { | |
Serial.println("Audio source not supported: '" + FileToPlay + "'"); | |
// turn on red LED | |
pixels.SetPixelColor(0, red); | |
pixels.Show(); | |
return false; | |
} | |
// is the audio type supported? | |
audioFormat currentAudioFormat = getAudiofileFormat(FileToPlay); | |
if (currentAudioFormat == afUnsupported) { | |
Serial.println("Audio format not supported: '" + FileToPlay + "'"); | |
// turn on red LED | |
pixels.SetPixelColor(0, red); | |
pixels.Show(); | |
return false; | |
} | |
// source is web URL? | |
if (currentAudioSource == asICY) { | |
Serial.println("Play URL from playlist: '" + FileToPlay + "', Index(" + String(currentFileIndex) + ")"); | |
Serial.println("start WIFI for streaming.."); | |
startWIFI(); | |
} | |
else | |
{ | |
// check file is a directory | |
File myDir = SD.open(FileToPlay); | |
if (!myDir) { | |
File myDir = SD.open(FileToPlay); | |
} | |
if (myDir) { | |
Serial.println("Play from playlist: '" + FileToPlay + "', Index(" + String(currentFileIndex) + ")"); | |
if (myDir.isDirectory()) { | |
// play files from directory | |
FileToPlay = getFileFromDir(myDir, currentFileIndex); | |
fileFound = SD.exists(FileToPlay); | |
Serial.println("Play directory from playlist: '" + FileToPlay + "', Index(" + String(currentFileIndex) + ")"); | |
} | |
else | |
{ | |
// play single file | |
fileFound = SD.exists(FileToPlay); | |
} | |
myDir.close(); | |
} | |
else { | |
Serial.println("Play File from playlist: '" + FileToPlay + "', Index(" + String(currentFileIndex) + ")"); | |
fileFound = SD.exists(FileToPlay); | |
} | |
// for SD/SPIFFS check file exists | |
if (!fileFound) { | |
Serial.println("File not found: '" + FileToPlay + "'"); | |
// turn on red LED | |
pixels.SetPixelColor(0, red); | |
pixels.Show(); | |
return false; | |
} | |
} | |
// create audio source | |
if (currentAudioSource == asSDCard) { | |
Serial.println("create new audio source: SDCard.."); | |
audio_in = new AudioFileSourceSD(FileToPlay.c_str()); | |
} else | |
if (currentAudioSource == asSPIFFS) { | |
Serial.println("create new audio source: SPIFFS.."); | |
audio_in = new AudioFileSourceSPIFFS(FileToPlay.c_str()); | |
} else | |
if (currentAudioSource == asICY) { | |
// start wifi | |
Serial.println("create new audio source: ICY.."); | |
audio_in = new AudioFileSourceICYStream(FileToPlay.c_str()); | |
} else | |
{ | |
// no audio source found | |
Serial.println("no audio source found"); | |
return false; | |
} | |
if (audio_id3) { | |
//Serial.println("delete audio id3.."); | |
audio_id3->close(); | |
delete(audio_id3); | |
audio_id3 = NULL; | |
} | |
if (audio_buff) { | |
//Serial.println("delete audio buffer.."); | |
audio_buff->close(); | |
delete(audio_buff); | |
audio_buff = NULL; | |
} | |
// ++ delete old audio generator +++ | |
// setup buffer and ID3-TAG decoder | |
audio_buff = new AudioFileSourceBuffer(audio_in, 4096); | |
audio_buff->RegisterStatusCB(audioStatusCallback, (void*)"buffer"); | |
audio_id3 = new AudioFileSourceID3(audio_buff); | |
// audio_id3->RegisterMetadataCB(audioMetadataCallback, (void*)"ID3TAG"); ++++ Jim Knopf +++ | |
// set audio generator depending on audio type (MP3, WAV, AAC) | |
if (currentAudioFormat == afMP3) { | |
Serial.println("create new audio generator: MP3.."); | |
audio_generator = new AudioGeneratorMP3; | |
} else | |
if (currentAudioFormat == afWAV) { | |
Serial.println("create new audio generator: WAV.."); | |
audio_generator = new AudioGeneratorWAV; | |
} else | |
if (currentAudioFormat == afAAC) { | |
Serial.println("create new audio generator: AAC.."); | |
audio_generator = new AudioGeneratorAAC; | |
} else | |
{ | |
// no audio generator found | |
Serial.println("no audio generator found"); | |
return false; | |
} | |
// wakeup amplifier/sd card/neopixel | |
Serial.println("wakeup the amplifier/SD-card.."); | |
pinMode(PIN_DAC_SD, OUTPUT); | |
digitalWrite(PIN_DAC_SD, HIGH); | |
delay(10); | |
// seek to last position | |
if (filepos > 0) { | |
audio_id3->seek(filepos, SEEK_SET); | |
} | |
else | |
{ | |
if (audio_id3->getPos() != 0) { | |
audio_id3->seek(0, SEEK_SET); | |
} | |
} | |
// turn on green LED | |
pixels.SetPixelColor(0, green); | |
pixels.Show(); | |
// begin playing | |
audio_generator->begin(audio_id3, audio_out); | |
config.currentPlaylist = currentPlaylist; | |
config.currentFileIndex = currentFileIndex; | |
config.currentPos = filepos; | |
Serial.println("Play file: STARTED, filename: '" + String(FileToPlay) + "'; filepos: " + String(filepos) + "; Free heap: " + String(ESP.getFreeHeap())); | |
return true; | |
} | |
/* | |
* play first audio file in current directory | |
*/ | |
void playFirstFile(){ | |
Serial.println("Play first file"); | |
stopPlaying(); | |
int nextFileIndex = 0; | |
config.currentFileIndex = 0; | |
Serial.println("Play first file from " + config.currentPlaylist + ", Index: " + String(nextFileIndex)); | |
playFile(config.currentPlaylist, nextFileIndex, 0); | |
} | |
/* | |
* play next audio file in current directory | |
*/ | |
bool playNextFile(){ | |
Serial.println("Play next file"); | |
stopPlaying(); | |
int nextFileIndex = config.currentFileIndex + 1; | |
config.currentFileIndex = nextFileIndex; | |
Serial.println("Play next file from " + config.currentPlaylist + ", Index: " + String(nextFileIndex)); | |
return playFile(config.currentPlaylist, nextFileIndex, 0); | |
} | |
/* | |
* play previous audio file in current directory | |
*/ | |
bool playPreviousFile(){ | |
Serial.println("Play previous file"); | |
stopPlaying(); | |
int prevFileIndex = config.currentFileIndex - 1; | |
if (prevFileIndex < 0) { | |
prevFileIndex = 0; | |
} | |
config.currentFileIndex = prevFileIndex; | |
Serial.println("Play previous file from " + config.currentPlaylist + ", Index: " + String(prevFileIndex)); | |
return playFile(config.currentPlaylist, prevFileIndex, 0); | |
} | |
/* | |
* Async Webserver events | |
*/ | |
void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ | |
if(type == WS_EVT_CONNECT){ | |
Serial.printf("ws[%s][%u] connect\n", server->url(), client->id()); | |
client->printf("Hello Client %u :)", client->id()); | |
client->ping(); | |
} else if(type == WS_EVT_DISCONNECT){ | |
Serial.printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id()); | |
} else if(type == WS_EVT_ERROR){ | |
Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data); | |
} else if(type == WS_EVT_PONG){ | |
Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:""); | |
} else if(type == WS_EVT_DATA){ | |
AwsFrameInfo * info = (AwsFrameInfo*)arg; | |
String msg = ""; | |
if(info->final && info->index == 0 && info->len == len){ | |
//the whole message is in a single frame and we got all of it's data | |
Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len); | |
if(info->opcode == WS_TEXT){ | |
for(size_t i=0; i < info->len; i++) { | |
msg += (char) data[i]; | |
} | |
} else { | |
char buff[3]; | |
for(size_t i=0; i < info->len; i++) { | |
sprintf(buff, "%02x ", (uint8_t) data[i]); | |
msg += buff ; | |
} | |
} | |
Serial.printf("%s\n",msg.c_str()); | |
if(info->opcode == WS_TEXT) | |
client->text("I got your text message"); | |
else | |
client->binary("I got your binary message"); | |
} else { | |
//message is comprised of multiple frames or the frame is split into multiple packets | |
if(info->index == 0){ | |
if(info->num == 0) | |
Serial.printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); | |
Serial.printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len); | |
} | |
Serial.printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len); | |
if(info->opcode == WS_TEXT){ | |
for(size_t i=0; i < info->len; i++) { | |
msg += (char) data[i]; | |
} | |
} else { | |
char buff[3]; | |
for(size_t i=0; i < info->len; i++) { | |
sprintf(buff, "%02x ", (uint8_t) data[i]); | |
msg += buff ; | |
} | |
} | |
Serial.printf("%s\n",msg.c_str()); | |
if((info->index + len) == info->len){ | |
Serial.printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len); | |
if(info->final){ | |
Serial.printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); | |
if(info->message_opcode == WS_TEXT) | |
client->text("I got your text message"); | |
else | |
client->binary("I got your binary message"); | |
} | |
} | |
} | |
} | |
} | |
void handleNotFound(AsyncWebServerRequest *request){ | |
Serial.printf("NOT_FOUND: "); | |
if(request->method() == HTTP_GET) | |
Serial.printf("GET"); | |
else if(request->method() == HTTP_POST) | |
Serial.printf("POST"); | |
else if(request->method() == HTTP_DELETE) | |
Serial.printf("DELETE"); | |
else if(request->method() == HTTP_PUT) | |
Serial.printf("PUT"); | |
else if(request->method() == HTTP_PATCH) | |
Serial.printf("PATCH"); | |
else if(request->method() == HTTP_HEAD) | |
Serial.printf("HEAD"); | |
else if(request->method() == HTTP_OPTIONS) | |
Serial.printf("OPTIONS"); | |
else | |
Serial.printf("UNKNOWN"); | |
Serial.printf(" http://%s%s\n", request->host().c_str(), request->url().c_str()); | |
if(request->contentLength()){ | |
Serial.printf("_CONTENT_TYPE: %s\n", request->contentType().c_str()); | |
Serial.printf("_CONTENT_LENGTH: %u\n", request->contentLength()); | |
} | |
int headers = request->headers(); | |
int i; | |
for(i=0;i<headers;i++){ | |
AsyncWebHeader* h = request->getHeader(i); | |
Serial.printf("_HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str()); | |
} | |
int params = request->params(); | |
for(i=0;i<params;i++){ | |
AsyncWebParameter* p = request->getParam(i); | |
if(p->isFile()){ | |
Serial.printf("_FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size()); | |
} else if(p->isPost()){ | |
Serial.printf("_POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); | |
} else { | |
Serial.printf("_GET[%s]: %s\n", p->name().c_str(), p->value().c_str()); | |
} | |
} | |
Serial.println("Free heap: " + String(ESP.getFreeHeap())); | |
request->send(404); | |
} | |
/* | |
* gets called when WiFiManager enters configuration mode | |
*/ | |
void configModeCallback (AsyncWiFiManager *myWiFiManager) { | |
Serial.println("Entered config mode"); | |
Serial.println(WiFi.softAPIP()); | |
// if you used auto generated SSID, print it | |
Serial.println(myWiFiManager->getConfigPortalSSID()); | |
// entered config mode, make led toggle faster | |
ticker.attach(0.2, tick); | |
} | |
/* | |
* ticker event for blue blinking LED | |
*/ | |
void tick() | |
{ | |
// toggle blue LED | |
togggleState = !togggleState; | |
digitalWrite(MYLED_BUILTIN, togggleState); // set pin to the opposite state | |
if (togggleState) { | |
pixels.SetPixelColor(0, blue); | |
} | |
else { | |
pixels.SetPixelColor(0, black); | |
} | |
pixels.Show(); | |
} | |
/* | |
* Method to print the reason by which ESP32 has been awaken from sleep | |
*/ | |
void print_wakeup_reason(){ | |
esp_sleep_wakeup_cause_t wakeup_reason; | |
wakeup_reason = esp_sleep_get_wakeup_cause(); | |
switch(wakeup_reason) | |
{ | |
case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break; | |
case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break; | |
case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break; | |
case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break; | |
case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break; | |
default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break; | |
} | |
} | |
/* | |
* Fast reset method for RFID reader | |
*/ | |
void mfrc522_fast_Reset() | |
{ | |
digitalWrite(PIN_RFID_RST, HIGH); | |
rfid_522.PCD_Reset(); | |
rfid_522.PCD_WriteRegister(rfid_522.TModeReg, 0x80); // TAuto=1; timer starts automatically at the end of the transmission in all communication modes at all speeds | |
rfid_522.PCD_WriteRegister(rfid_522.TPrescalerReg, 0x43); // 10μs. | |
rfid_522.PCD_WriteRegister(rfid_522.TReloadRegH, 0x00); // Reload timer with 0x064 = 30, ie 0.3ms before timeout. | |
rfid_522.PCD_WriteRegister(rfid_522.TReloadRegL, 0x1E); | |
//rfid_522.PCD_WriteRegister(rfid_522.TReloadRegL, 0x1E); | |
rfid_522.PCD_WriteRegister(rfid_522.TxASKReg, 0x40); // Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting | |
rfid_522.PCD_WriteRegister(rfid_522.ModeReg, 0x3D); // Default 0x3F. Set the preset value for the CRC coprocessor for the CalcCRC command to 0x6363 (ISO 14443-3 part 6.2.4) | |
rfid_522.PCD_AntennaOn(); // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset) | |
} | |
/* | |
* Show battery voltage | |
*/ | |
void printBatteryVoltage() | |
{ | |
// Battery Voltage | |
float VBAT = (127.0f/100.0f) * 3.30f * float(analogRead(PIN_VBAT)) / 4095.0f; // LiPo battery | |
Serial.print("Battery Voltage = "); Serial.print(VBAT, 2); Serial.println(" V"); | |
} | |
AsyncWebHandler SDWebHandler; | |
//AsyncWebHandler WSHandler; | |
/* | |
* start WIFI | |
*/ | |
void startWIFI() | |
{ | |
// Wifi already started ? | |
if (WiFiStarted) { | |
Serial.println("Wifi started."); | |
return; | |
} | |
WiFiStarted = true; | |
Serial.println("starting Wifi."); | |
// shutdown amplifier | |
bool wasAmplifierStarted = digitalRead(PIN_DAC_SD); | |
digitalWrite(PIN_DAC_SD, HIGH); | |
///WiFi manager | |
//Local intialization. Once its business is done, there is no need to keep it around | |
AsyncWiFiManager wifiManager(&server, &dns); | |
// sets timeout until configuration portal gets turned off | |
wifiManager.setTimeout(180); | |
// start ticker with 0.6 because we start in AP mode and try to connect | |
ticker.attach(0.6, tick); | |
// turn on blue LED | |
pixels.SetPixelColor(0, blue); | |
uint8_t i; | |
for(i=1; i <= (NEOPIXEL_NUM); i++) { | |
pixels.SetPixelColor(i, black); | |
} | |
pixels.Show(); | |
//set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode | |
wifiManager.setAPCallback(configModeCallback); | |
if (!wifiManager.autoConnect(hostname)) { | |
Serial.println("failed to connect and hit timeout, restarting"); | |
//reset and try a, or maybe put it to deep sleep | |
ESP.restart(); | |
delay(3000); | |
return; | |
} | |
// check connection is established | |
while (WiFi.status() != WL_CONNECTED) { | |
Serial.println("...Connecting to WiFi"); | |
delay(1000); | |
} | |
WiFi.setHostname(hostname); | |
// if you get here you have connected to the WiFi | |
Serial.print("Connected! Host: "); | |
Serial.print(hostname); | |
Serial.print("IP address: "); | |
Serial.println(WiFi.localIP()); | |
//reset settings - for testing | |
// wifiManager.resetSettings(); | |
/*esp_wifi_restore(); | |
delay(1000); | |
ESP.restart(); | |
*/ | |
// show signal strength | |
long rssi = WiFi.RSSI(); | |
Serial.print("RSSI:"); | |
Serial.println(rssi); | |
// stop blinking, turn on blue LED | |
ticker.detach(); | |
digitalWrite(MYLED_BUILTIN, LOW); | |
pixels.SetPixelColor(0, blue); | |
pixels.Show(); | |
// Start mDNS service so we can connect to http://pietbox.local (if Bonjour installed on Windows) | |
if (MDNS.begin(hostname)) { | |
MDNS.addService("http", "tcp", 80); | |
Serial.println("MDNS responder started"); | |
Serial.println("You can now connect to http://" + String(hostname) + ".local"); | |
} | |
// connect HTTP server events | |
// websocket | |
/* ws.onEvent(onWsEvent); | |
WSHandler = server.addHandler(&ws); | |
events.onConnect([](AsyncEventSourceClient *client){ | |
client->send("hello!", NULL, millis(), 1000); | |
}); | |
server.addHandler(&events); | |
*/ | |
// default file is index.html | |
server.serveStatic("/", SDFileSystem, "/").setDefaultFile("index.htm"); | |
// SD file system editor | |
SDWebHandler = server.addHandler(new SDEditor(SDFileSystem, http_username, http_password)); | |
// get free heap | |
server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){ | |
Serial.println("HTTP: request free heap"); | |
AsyncResponseStream *response = request->beginResponseStream("text/plain"); | |
response->addHeader("Access-Control-Allow-Origin", "*"); // to allow usage from local web page for test & development | |
response->printf( String(ESP.getFreeHeap()).c_str() ); | |
request->send(response); | |
}); | |
// get battery voltage | |
server.on("/vbatabs", HTTP_GET, [](AsyncWebServerRequest *request){ | |
Serial.println("HTTP: request battery charge in volt"); | |
float VBAT = (127.0f/100.0f) * 3.30f * float(analogRead(PIN_VBAT)) / 4095.0f; // LiPo battery in volt | |
request->send(200, "text/plain", String(VBAT)); | |
}); | |
// get battery in percent | |
server.on("/vbat", HTTP_GET, [](AsyncWebServerRequest *request){ | |
float VBAT = (127.0f/100.0f) * 3.30f * float(analogRead(PIN_VBAT)) / 4095.0f; // LiPo battery in volt | |
uint8_t percent = constrain(int(((VBAT - VBAT_MIN) / (VBAT_MAX - VBAT_MIN)) * 100.0f), 0, 100); // 0% = VBAT_MIN (3.0V) - 100% = VBAT_MAX (4.2V) | |
if (digitalRead(PIN_VUSB)) { | |
Serial.println("HTTP: request battery charge: " + String(percent) + " % - charging.."); | |
request->send(200, "text/plain", String(percent) + " % - charging.."); | |
} else { | |
Serial.println("HTTP: request battery charge: " + String(percent) + " %"); | |
request->send(200, "text/plain", String(percent) + " %"); | |
} | |
}); | |
// reset | |
server.on("/reset", HTTP_POST, [](AsyncWebServerRequest *request){ | |
Serial.println("HTTP: request reset.."); | |
Serial.println("resetting.."); | |
request->send(200, "text/plain", "Reset and restarting, please wait.."); | |
delay(2000); | |
ESP.restart(); | |
}); | |
// reset WIFI settings | |
server.on("/resetwifi", HTTP_GET, [](AsyncWebServerRequest *request){ | |
Serial.println("HTTP: request reset WIFI.."); | |
request->send(200, "text/plain","Reset WIFI and restarting, please wait.."); | |
WiFi.enableSTA(true); | |
WiFi.persistent(true); | |
WiFi.disconnect(true); | |
WiFi.persistent(false); | |
delay(2000); | |
ESP.restart(); | |
}); | |
// handle current track metadata | |
server.on("/currentTitle", HTTP_GET, [](AsyncWebServerRequest *request){ | |
Serial.println("HTTP: request currentTitle: '" + currentTitle + "'"); | |
request->send(200, "text/plain", currentTitle); | |
}); | |
server.on("/currentPerformer", HTTP_GET, [](AsyncWebServerRequest *request){ | |
Serial.println("HTTP: request currentPerformer: '" + currentPerformer + "'"); | |
request->send(200, "text/plain", currentPerformer); | |
}); | |
server.on("/currentAlbum", HTTP_GET, [](AsyncWebServerRequest *request){ | |
Serial.println("HTTP: request currentAlbum: '" + currentAlbum + "'"); | |
request->send(200, "text/plain", currentAlbum); | |
}); | |
server.on("/currentVolume", HTTP_GET, [](AsyncWebServerRequest *request){ | |
Serial.println("HTTP: request currentVolume: '" + String(config.currentVolume) + "'"); | |
request->send(200, "text/plain", String(config.currentVolume)); | |
}); | |
// handle not found | |
server.onNotFound(handleNotFound); | |
// handle file upload | |
server.onFileUpload([](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final){ | |
if(!index) | |
Serial.printf("UploadStart: %s\n", filename.c_str()); | |
Serial.printf("%s", (const char*)data); | |
if(final) | |
Serial.printf("UploadEnd: %s (%u)\n", filename.c_str(), index+len); | |
}); | |
// handle request body | |
server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){ | |
if(!index) | |
Serial.printf("BodyStart: %u\n", total); | |
Serial.printf("%s", (const char*)data); | |
if(index + len == total) | |
Serial.printf("BodyEnd: %u\n", total); | |
}); | |
// start HTTP server | |
server.begin(); | |
Serial.println("HTTP server started"); | |
// reactivate amplifier? | |
if (wasAmplifierStarted) { | |
Serial.println("wakeup amplifier/SD-card"); | |
digitalWrite(PIN_DAC_SD, HIGH); | |
} | |
} | |
/* | |
* stop WIFI | |
*/ | |
void stopWIFI(){ | |
// Wifi already started ? | |
if (!WiFiStarted) { | |
return; | |
} | |
Serial.println("stop webserver.."); | |
// remove events | |
server.onNotFound(NULL); | |
server.onFileUpload(NULL); | |
server.onRequestBody(NULL); | |
/* events.onConnect(NULL); | |
ws.onEvent(NULL); | |
server.removeHandler(&WSHandler); +++ | |
server.removeHandler(&events); | |
*/ | |
// remove handlers | |
server.removeHandler(&SDWebHandler); | |
// reset HTTP server | |
server.reset(); | |
Serial.println("disable Wifi.."); | |
// stop MDNS | |
MDNS.end(); | |
WiFi.disconnect(false); | |
// turn off blue LED | |
pixels.SetPixelColor(0, black); | |
pixels.Show(); | |
WiFiStarted = false; | |
} | |
#define DEBUG | |
#ifdef DEBUG | |
#define DEBUG_PRINT(x) Serial.println (x) | |
#else | |
#define DEBUG_PRINT(x) | |
#endif | |
/*************************************** | |
* Setup | |
***************************************/ | |
void setup(void){ | |
#ifdef DEBUG | |
// Initialize serial communications with the PC | |
Serial.begin(115200); | |
Serial.println(""); | |
Serial.println(""); | |
Serial.println(" ###### "); | |
Serial.println(" # # # ###### ##### ##### #### # #"); | |
Serial.println(" # # # # # # # # # # # "); | |
Serial.println(" ###### # ##### # ##### # # ## "); | |
Serial.println(" # # # # # # # # ## "); | |
Serial.println(" # # # # # # # # # # "); | |
Serial.println(" # # ###### # ##### #### # #"); | |
Serial.println(""); | |
Serial.println(""); | |
#endif | |
// turn Wifi/BLE off | |
Serial.println("turn Wifi/BLE off.."); | |
WiFi.mode(WIFI_OFF); | |
// btStop(); | |
//Increment boot number and print it every reboot | |
++sleepCount; | |
#ifdef DEBUG | |
Serial.println("Sleep number: " + String(sleepCount)); | |
Serial.println("Free heap: " + String(ESP.getFreeHeap())); | |
Serial.println("SDK version: " + String(ESP.getSdkVersion())); | |
//Print the wakeup reason for ESP32 | |
print_wakeup_reason(); | |
#endif | |
/* | |
First we configure the wake up source | |
We set our ESP32 to wake up every short/middle/long seconds | |
*/ | |
esp_sleep_enable_ext0_wakeup(PIN_ENCODER_BUTTON, 0); //1 = High, 0 = Low | |
int timeToSleep = TIME_TO_SLEEP_SHORT; | |
if (sleepCount > 30) | |
timeToSleep = TIME_TO_SLEEP_MIDDLE; | |
if (sleepCount > 60) | |
timeToSleep = TIME_TO_SLEEP_LONG; | |
if (sleepCount > (60 * 60)) | |
timeToSleep = TIME_TO_SLEEP_VERY_LONG; | |
esp_sleep_enable_timer_wakeup(timeToSleep * 1000000); | |
#ifdef DEBUG | |
Serial.println("Setup ESP32 to sleep for every " + String(timeToSleep) + " Seconds"); | |
#endif | |
/* | |
Next we decide what all peripherals to shut down/keep on | |
By default, ESP32 will automatically power down the peripherals | |
not needed by the wakeup source, but if you want to be a poweruser | |
this is for you. Read in detail at the API docs | |
http://esp-idf.readthedocs.io/en/latest/api-reference/system/deep_sleep.html | |
Left the line commented as an example of how to configure peripherals. | |
The line below turns off all RTC peripherals in deep sleep. | |
*/ | |
// esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); | |
//Serial.println("Configured all RTC Peripherals to be powered down in sleep"); | |
// check for external power supply | |
hasExternalPower = digitalRead(PIN_VUSB); | |
if (!hasExternalPower) { | |
// Print battery voltage | |
printBatteryVoltage(); | |
} | |
// turn on internal LED | |
pinMode(MYLED_BUILTIN, OUTPUT); | |
digitalWrite(MYLED_BUILTIN, LOW); | |
// Initialize the NeoPixel library and turn off the stripe | |
Serial.println("Init neopixel.."); | |
pixels.Begin(); | |
pixels.Show(); | |
// sleeping amplifier, neopixel & SD card power | |
pinMode(PIN_DAC_SD, OUTPUT); | |
digitalWrite(PIN_DAC_SD, LOW); | |
// VUSB power with internal pulldown | |
pinMode(PIN_VUSB, INPUT_PULLDOWN); | |
// RESET pin for RFID reader | |
pinMode(PIN_RFID_RST, OUTPUT); | |
digitalWrite(PIN_RFID_RST, LOW); | |
// switch heartbeat LED OFF | |
digitalWrite(MYLED_BUILTIN, HIGH); | |
/* | |
Now that we have setup a wake cause and if needed setup the | |
peripherals state in deep sleep, we can now start going to | |
deep sleep. | |
In the case that no wake up sources were provided but deep | |
sleep was started, it will sleep forever unless hardware | |
reset occurs. | |
*/ | |
if ((readerType == rtUnknown) || (readerType == rtMFRC522)) { | |
// Init MFRC522 | |
Serial.println("Try to detect MFRC522 reader.."); | |
SPI.begin(); | |
delay(10); | |
rfid_522.PCD_Init(); | |
// If you set Antenna Gain to Max it will increase reading distance | |
// BUT you will lose reading ability when tag is right on the sensor! | |
// rfid_522.PCD_SetAntennaGain(rfid_522.RxGain_max); | |
// delay(10); | |
// Get the MFRC522 software version | |
byte v = rfid_522.PCD_ReadRegister(rfid_522.VersionReg); | |
Serial.print(F("MFRC522 Software Version: 0x")); | |
Serial.print(v, HEX); | |
if (v == 0x91) | |
Serial.print(F(" = v1.0")); | |
else if (v == 0x92) | |
Serial.print(F(" = v2.0")); | |
else | |
Serial.print(F(" (unknown)")); | |
Serial.println(""); | |
if ((v == 0x91) || (v == 0x92)) | |
readerType = rtMFRC522; | |
} | |
// check for PN532 reader | |
if ((readerType == rtUnknown) || (readerType == rtPN532)) { | |
Serial.print("Try to detect PN532 reader.."); | |
digitalWrite(PIN_RFID_RST, HIGH); | |
delay(100); | |
rfid_532.begin(); | |
uint32_t versiondata = rfid_532.getFirmwareVersion(); | |
if (versiondata) { | |
readerType = rtPN532; | |
} else { | |
Serial.println("Didn't find PN53x board"); | |
} | |
// Got ok data, print it out! | |
Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); | |
Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); | |
Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); | |
Serial.println(' '); | |
// Set the max number of retry attempts to read from a card | |
// This prevents us from waiting forever for a card, which is | |
// the default behaviour of the PN532. | |
rfid_532.setPassiveActivationRetries(0xFF); | |
// configure board to read RFID tags | |
rfid_532.SAMConfig(); | |
} | |
// check RFID is available | |
if (sleepCount < 2) { | |
if (readerType == rtUnknown) { | |
Serial.println("RFID-reader failed, start WIFI.."); | |
startWIFI(); | |
} | |
} | |
uid = ""; | |
// get the RFID reader card status | |
if (readerType == rtMFRC522) { | |
rfid_522.PICC_ReadCardSerial(); //Always fails | |
rfid_522.PICC_IsNewCardPresent(); //Does RequestA | |
if ((!rfid_522.PICC_ReadCardSerial()) && (!WiFiStarted)) { //Okay. This does the same PICC_Select as the previous ReadCardSerial(), but this one fails if there is no card on the reader. Funny. | |
Serial.println("PCD_SoftPowerDown"); | |
rfid_522.PCD_SoftPowerDown(); | |
// stop SPI | |
SPI.end(); | |
// turn Wifi/BLE off | |
Serial.println("turn Wifi/BLE off"); | |
WiFi.mode(WIFI_OFF); | |
// btStop(); | |
// esp_wifi_stop(); | |
// esp_wifi_deinit(); | |
Serial.println("Going to sleep now"); | |
// close serial | |
Serial.end(); | |
// put neopixel pin to input to save current | |
pinMode(PIN_NEOPIXEL_DIN, INPUT); | |
// turn off power to SD card/DAC | |
digitalWrite(PIN_DAC_SD, LOW); | |
esp_deep_sleep_start(); | |
// ESP is sleeping now! | |
} | |
// Wake up now ! | |
Serial.println("PCD dump:"); | |
rfid_522.PCD_DumpVersionToSerial(); | |
// Get PICC's UID and store on a variable | |
for (byte i = 0; i < rfid_522.uid.size; i++) { | |
uid += String(rfid_522.uid.uidByte[i], HEX); | |
} | |
}; | |
if (readerType == rtPN532) { | |
// Wait for an ISO14443A type cards (Mifare, etc.). When one is found | |
// 'uid' will be populated with the UID, and uidLength will indicate | |
// if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) | |
uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) | |
uint8_t myuid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID | |
boolean success; | |
Serial.println("PN532: ISO14443A read passive target id.."); | |
success = rfid_532.readPassiveTargetID(PN532_MIFARE_ISO14443A, &myuid[0], &uidLength, 100); | |
if (!success) { | |
Serial.println("PN532: ISO14443B read passive target id.."); | |
success = rfid_532.readPassiveTargetID(PN532_ISO14443B, &myuid[0], &uidLength, 100); | |
} | |
if (!success) { | |
Serial.println("Going to sleep now"); | |
// close serial | |
Serial.end(); | |
//rfid_532.PCD_SoftPowerDown(); ++ shutdown ++ | |
// stop SPI | |
SPI.end(); | |
// turn Wifi/BLE off | |
WiFi.mode(WIFI_OFF); | |
//btStop(); | |
esp_wifi_stop(); | |
// hard reset the RFID reader | |
digitalWrite(PIN_RFID_RST, LOW); | |
// put neopixel pin to input to save current | |
pinMode(PIN_NEOPIXEL_DIN, INPUT); | |
// turn off power to SD card/DAC | |
digitalWrite(PIN_DAC_SD, LOW); | |
esp_wifi_deinit(); | |
esp_deep_sleep_start(); | |
// ESP is sleeping now! | |
} | |
// Get PICC's UID and store on a variable | |
for (byte i = 0; i < uidLength; i++) { | |
uid += String(myuid[i], HEX); | |
} | |
} | |
uid.trim(); | |
uid.toUpperCase(); | |
card_present = (uid!=""); | |
if (card_present) | |
Serial.println("RFID-UID: '" + String(uid) + "'"); | |
else | |
Serial.println("no card detected"); | |
// wakeup | |
sleepCount = 0; | |
/* | |
//Send OTA events to the browser | |
ArduinoOTA.onStart([]() { events.send("Update Start", "ota"); }); | |
ArduinoOTA.onEnd([]() { events.send("Update End", "ota"); }); | |
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { | |
char p[32]; | |
sprintf(p, "Progress: %u%%\n", (progress/(total/100))); | |
events.send(p, "ota"); | |
}); | |
ArduinoOTA.onError([](ota_error_t error) { | |
if(error == OTA_AUTH_ERROR) events.send("Auth Failed", "ota"); | |
else if(error == OTA_BEGIN_ERROR) events.send("Begin Failed", "ota"); | |
else if(error == OTA_CONNECT_ERROR) events.send("Connect Failed", "ota"); | |
else if(error == OTA_RECEIVE_ERROR) events.send("Recieve Failed", "ota"); | |
else if(error == OTA_END_ERROR) events.send("End Failed", "ota"); | |
}); | |
ArduinoOTA.setHostname(hostname); | |
ArduinoOTA.begin(); | |
*/ | |
/* | |
Create the audio classes | |
*/ | |
Serial.println("initialize audio objects.."); | |
audio_in = NULL; | |
audio_buff = NULL; | |
audio_id3 = NULL; | |
audio_out = new AudioOutputI2S(); | |
audio_out->SetOutputModeMono(true); | |
audio_generator = new AudioGeneratorMP3; | |
// wakeup amplifier | |
Serial.println("wakeup the amplifier/SD-card.."); | |
digitalWrite(PIN_DAC_SD, HIGH); | |
delay(2); | |
Serial.println("Init SD card"); | |
SPI2.begin(PIN_SD_CARD_SCK, PIN_SD_CARD_MISO, PIN_SD_CARD_MOSI, PIN_SD_CARD_SS); // SCK, MISO, MOSI, SS | |
delay(10); | |
int TryCounter = 0; | |
int state = 0; | |
// check multiple times for valid SD card | |
while ((!SD.begin(PIN_SD_CARD_SS, SPI2, 4000000)) && (TryCounter < 10)) { | |
// while ((!SD.begin(PIN_SD_CARD_SS, SPI2, 80000000)) && (TryCounter < 10)) { // 80000000 sometimes fails | |
Serial.println(F("Failed to initialize SD library")); | |
state = !state; | |
if (state) { | |
pixels.SetPixelColor(0, red); | |
} | |
else { | |
pixels.SetPixelColor(0, black); | |
} | |
pixels.Show(); | |
TryCounter++; | |
delay(200); | |
} | |
if (TryCounter < 10) { | |
Serial.println("SD Card initialized."); | |
hasSD = true; | |
uint8_t cardType = SD.cardType(); | |
if(cardType == CARD_NONE){ | |
Serial.println("No SD card attached"); | |
return; | |
} | |
Serial.print("SD Card Type: "); | |
if(cardType == CARD_MMC){ | |
Serial.println("MMC"); | |
} else if(cardType == CARD_SD){ | |
Serial.println("SDSC"); | |
} else if(cardType == CARD_SDHC){ | |
Serial.println("SDHC"); | |
} else { | |
Serial.println("UNKNOWN"); | |
} | |
uint64_t cardSize = SD.cardSize() / (1024 * 1024); | |
Serial.printf("SD Card Size: %lluMB\n", cardSize); | |
// Should load default config if run for the first time | |
Serial.println(F("Loading configuration...")); | |
loadConfiguration(filename, config); | |
// set default/last save volume | |
if (config.currentVolume > stepsPerTurn) config.currentVolume = stepsPerTurn; | |
// startup with minimum volume | |
if (config.currentVolume < 2) config.currentVolume = 2; | |
Serial.println("Set volume to " + String(config.currentVolume)); | |
setVolume(config.currentVolume); | |
// initialize the encoder pushbutton pin as an input (internal pullup not available for gpio 34-39) | |
pinMode(PIN_ENCODER_BUTTON, INPUT); | |
// pushbutton pressed? start the WLAN | |
if (!digitalRead(PIN_ENCODER_BUTTON)) { | |
startWIFI(); | |
} | |
else | |
{ | |
// check playlist exists for the UID, if not create it | |
String currentPlaylist = "/" + uid + ".m3u"; | |
if ((!SD.exists(currentPlaylist) && (uid != ""))) { | |
Serial.println("No playlist found for UID " + uid); | |
File myFile = SD.open(currentPlaylist, FILE_WRITE); | |
myFile.close(); | |
Serial.println("New playlist created for UID " + uid); | |
startWIFI(); | |
} | |
if (!(currentPlaylist == config.currentPlaylist)) { | |
Serial.println("Play a new playlist " + currentPlaylist); | |
config.currentPlaylist = currentPlaylist; | |
config.currentFileIndex = 0; | |
config.currentPos = 0; | |
} | |
// play first file | |
if (!playFile(config.currentPlaylist, config.currentFileIndex, config.currentPos)) { | |
config.currentFileIndex = 0; | |
config.currentPos = 0; | |
playFile(config.currentPlaylist, config.currentFileIndex, config.currentPos); | |
} | |
} | |
} | |
else | |
{ | |
Serial.println("SD Card not initialized."); | |
Serial.flush(); | |
// only goto sleep if a rfid reader is connected | |
if (readerType != rtUnknown) { | |
// close serial | |
Serial.end(); | |
digitalWrite(MYLED_BUILTIN, LOW); | |
// turn on red LED | |
pixels.SetPixelColor(0, black); | |
pixels.Show(); | |
// put neopixel pin to input to save current | |
pinMode(PIN_NEOPIXEL_DIN, INPUT); | |
// turn off power to SD card/DAC | |
digitalWrite(PIN_DAC_SD, LOW); | |
esp_wifi_stop(); | |
esp_wifi_deinit(); | |
esp_deep_sleep_start(); | |
// ESP is sleeping now! | |
} | |
} | |
// Task function for asynchron checking for a NFC card | |
xTaskCreate(RFIDReaderTaskFunc, /* Task function. */ | |
"RFIDReader", /* String with name of task. */ | |
10000, /* Stack size in words. */ | |
NULL, /* Parameter passed as input of the task */ | |
1, /* Priority of the task. */ | |
NULL); /* Task handle. */ | |
// print free heap after setup | |
Serial.println(""); | |
Serial.println("Free heap after initialization: " + String(ESP.getFreeHeap())); | |
Serial.println(""); | |
} | |
/* | |
* go to sleep | |
*/ | |
void goToSleep() | |
{ | |
// Save configuration file | |
if (hasSD) { | |
Serial.println(F("Saving configuration..")); | |
saveConfiguration(filename, config); | |
} | |
// turn off the RFID reader | |
Serial.println("soft powerdown RFID-reader.."); | |
if (readerType == rtMFRC522) { | |
rfid_522.PCD_SoftPowerDown(); | |
} else | |
if (readerType == rtPN532) { | |
digitalWrite(PIN_RFID_RST, LOW); | |
}; | |
// turn off SD card | |
Serial.println("close SD-card.."); | |
SD.end(); | |
// stop SPI | |
SPI2.end(); | |
SPI.end(); | |
digitalWrite(PIN_SD_CARD_SS, LOW); | |
// shutdown amplifier | |
Serial.println("shutdown amplifier/SD-card.."); | |
digitalWrite(PIN_DAC_SD, LOW); | |
// turn of neopixel LED | |
Serial.println("turn of neopixel LED.."); | |
uint8_t i; | |
for(i=0; i <= (NEOPIXEL_NUM); i++) { | |
pixels.SetPixelColor(i, black); | |
} | |
pixels.Show(); | |
// turn off WIFI | |
Serial.println(" turn off WIFI.."); | |
WiFi.disconnect(true); //esp_wifi_stop(); | |
// turn Wifi/BLE off | |
WiFi.mode(WIFI_OFF); | |
//btStop(); | |
// esp_wifi_stop(); saw a crash here. Properly stop Wifi & Bluetooth to save power +++ | |
// esp_wifi_deinit(); | |
// going into deep sleep | |
Serial.println("Going to sleep now"); | |
// close serial | |
Serial.end(); | |
delay(50); //time to write to serial | |
// put neopixel pin to input to save current | |
pinMode(PIN_NEOPIXEL_DIN, INPUT); | |
// turn off power to SD card/DAC | |
digitalWrite(PIN_DAC_SD, LOW); | |
esp_deep_sleep_start(); | |
// ESP is sleeping now! | |
Serial.println("This will never be printed"); | |
} | |
/* | |
* RFID reader task | |
*/ | |
void RFIDReaderTaskFunc( void * parameter ) | |
{ | |
Serial.println("start RFIDReader task"); | |
// check RFID reader is available | |
while (!shutdownFlag) { | |
if (readerType == rtUnknown) { | |
// RFID reader not available, check for MFRC522 | |
rfid_522.PCD_Init(); | |
// Get the MFRC522 software version | |
byte v = rfid_522.PCD_ReadRegister(rfid_522.VersionReg); | |
Serial.print(F("MFRC522 Software Version: 0x")); | |
Serial.println(v, HEX); | |
if ((v == 0x91) || (v == 0x92)) { | |
readerType = rtMFRC522; | |
} | |
} | |
card_present = false; | |
if (readerType == rtMFRC522) { | |
//Serial.println("MFRC522: PICC_ReadCardSerial"); | |
rfid_522.PICC_ReadCardSerial(); // Always fails | |
//Serial.println("MFRC522: PICC_IsNewCardPresent"); | |
rfid_522.PICC_IsNewCardPresent(); // Does RequestA | |
// check RFID-reader and card is present | |
card_present = rfid_522.PICC_ReadCardSerial(); | |
//Serial.println("MFRC522: card_present: " + card_present); | |
} else | |
if (readerType == rtPN532) { | |
uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID | |
uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) | |
card_present = rfid_532.readPassiveTargetID(PN532_MIFARE_ISO14443A, &uid[0], &uidLength, 100); | |
if (!card_present) { | |
card_present = rfid_532.readPassiveTargetID(PN532_JEWEL, &uid[0], &uidLength, 100); | |
} | |
} | |
if (!card_present) { | |
Serial.println("vTask (RFIDReader): card removed"); | |
card_present = false; | |
if (audio_generator && (audio_generator->isRunning())) { | |
// save current audio file, position and volume ++ | |
unsigned int p = audio_in->getPos(); | |
Serial.println("get current filepos: " + String(p)); | |
config.currentPos = p; | |
} | |
else | |
{ | |
config.currentPos = 0; | |
} | |
// goToSleep is done in loop thread | |
shutdownFlag = true; | |
} | |
delay(1000); | |
} | |
Serial.println("delete RFIDReader task"); | |
vTaskDelete( NULL ); | |
} | |
void setVolume(int newVolume){ | |
// ensure volume is in range | |
newVolume = constrain(newVolume, 0, stepsPerTurn); | |
config.currentVolume = newVolume; | |
Serial.println("Volume " + String(config.currentVolume)); | |
Serial.println("Free heap: " + String(ESP.getFreeHeap())); | |
myEnc.write(config.currentVolume); | |
// set gain | |
audio_out->SetGain(((float)config.currentVolume)/stepsPerTurn * maxVolume); | |
// show current volume on neopixel ring | |
uint32_t neopixelValue = map(newVolume, 0, stepsPerTurn, 0, (NEOPIXEL_NUM) * 255); | |
uint8_t fullLEDs = (neopixelValue / 255); | |
uint8_t lastLED = (neopixelValue % 255); | |
uint8_t i; | |
// clear all LED's | |
for(i=0; i < (NEOPIXEL_NUM); i++) { | |
pixels.SetPixelColor(i + 1, black); | |
} | |
// set "full" LED's | |
for(i=0; i < (fullLEDs); i++) { | |
pixels.SetPixelColor(i + 1, RgbColor(255)); | |
} | |
// set last LED | |
if (lastLED != 0) { | |
pixels.SetPixelColor(fullLEDs + 1, RgbColor(lastLED)); | |
} | |
pixels.Show(); | |
// start fade out after 2000 ms | |
startTimeNeoPixel = millis(); | |
} | |
/* ************************************* | |
* Loop | |
***************************************/ | |
void loop(void){ | |
// handle rotary encoder | |
long newVolume = myEnc.read(); | |
if (newVolume != config.currentVolume) { | |
setVolume(newVolume); | |
} | |
// handle over the air updates | |
// ArduinoOTA.handle(); | |
// handle audio | |
if ((!shutdownFlag) && (audio_generator) && (audio_generator->isRunning()) && (audio_in->isOpen())) { | |
if (!audio_generator->loop()) { | |
if (!playNextFile()) { | |
Serial.println("Last track, stopped playing."); | |
stopPlaying(); | |
} | |
} | |
} | |
// handle shutdown | |
if (shutdownFlag) { | |
stopPlaying(); | |
stopWIFI(); | |
goToSleep(); | |
} | |
// handle volume NeoPixels | |
if (animations.IsAnimating()) { | |
// the normal loop just needs these two to run the active animations | |
animations.UpdateAnimations(); | |
pixels.Show(); | |
} | |
if ((startTimeNeoPixel != 0) && ((millis() - startTimeNeoPixel) > 2000)) { | |
// starting fade out | |
Serial.println("start fadeout volume NeoPixels.."); | |
animationState[0].StartingColor = pixels.GetPixelColor(1); | |
animationState[0].EndingColor = RgbColor(0); | |
animations.StartAnimation(0, 800, BlendAnimUpdate); | |
startTimeNeoPixel = 0; | |
} | |
if (hasExternalPower != digitalRead(PIN_VUSB)) { | |
// external USB-power has changed | |
hasExternalPower = digitalRead(PIN_VUSB); | |
if (hasExternalPower) { | |
Serial.println("external power is available now.."); | |
startWIFI(); | |
} else { | |
Serial.println("external power is gone.."); | |
stopWIFI(); | |
} | |
} | |
// pushbutton pressed? start the WLAN | |
if (encoderButton.pressed()) { | |
// check for reset command ( > 5 seconds) | |
if ((lastTimeButtonPressed != 0) && (millis() - lastTimeButtonPressed > 5000)) { | |
//reset and try a, or maybe put it to deep sleep | |
ESP.restart(); | |
delay(3000); | |
return; | |
} | |
lastTimeButtonPressed = millis(); | |
Serial.println("Encoder button pressed."); | |
if (WiFiStarted && (!digitalRead(PIN_VUSB))) { | |
// handle button with Wifi started | |
shutdownFlag = true; | |
} else { | |
if (!playNextFile()) { | |
playFirstFile(); | |
} | |
} | |
} else | |
lastTimeButtonPressed = 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment