You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
mrdiy-audio-notifier/esp8266_mrdiy_mqtt_local_no...

408 lines
13 KiB

/* ==============================================================================================================
MrDIY is an locally-controlled over MQTT, internet-independent media notifier that can play MP3s, stream
icecast radios, read text and play RTTTL ringtones without any external components.
Connect your audio jack to Rx and GND - an external amplifier if required. You can build a simple amp
with a single 1kΩ resistor and an NPN 2N3904 transistor, like this:
2N3904 (NPN)
+---------+
| | +--|
| E B C | / S |
+-|--|--|-+ | P |
| | +------+ E |
| | | A |
ESP8266-GND ------------------+ | +------+ K |
| | | E |
ESP8266-I2SOUT (Rx) -----/\/\/\--+ | \ R |
1kΩ | +--|
USB 5V -----------------------------+
Commands, to:
- Play MP3 MQTT topic: "/mqttFullTopic()/play"
MQTT load: http://url-to-the-mp3-file/file.mp3
- Play Icecast Stream MQTT topic: "/mqttFullTopic()/stream"
MQTT load: http://url-to-the-icecast-stream/file.mp3, example: http://22203.live.streamtheworld.com/WHTAFM.mp3
- Play Ringtone MQTT topic: "/mqttFullTopic()/tone"
MQTT load: RTTTL formated text, example: Soap:d=8,o=5,b=125:g,a,c6,p,a,4c6,4p,a,g,e,c,4p,4g,a
- Say Text MQTT topic: "/mqttFullTopic()/say"
MQTT load: Text to be read, example: Hello There. How. Are. You?
- Change Volume MQTT topic: "/mqttFullTopic()/volume"
MQTT load: a double between 0.00 and 1.00, example: 0.7
- Stop Playing MQTT topic: "/mqttFullTopic()/stop"
To get status:
- The notifier sends status update on this MQTT topic: "/mqttFullTopic()/status"
"playing" either paying an mp3, streaming, playing a ringtone or saying a text
"idle" waiting for a command
"error" error when receiving a command: example: MP3 file URL can't be loaded
"connected" device just connected to MQTT server
- The notifier plays a 2 second audio clip when it is first booted and connected to Wifi & MQTT
To Upload to Wesmo D1 Mini:
- Set CPU Frequency to 160MHz
- Set IwIP to V2 Higher Bandwidth
- Update the SSID info
- Connect Rx to an AMP
Dependencies:
- ESP8266 https://github.com/esp8266/Arduino
- ESP8266Audio https://github.com/earlephilhower/ESP8266Audio
- ESP8266SAM https://github.com/earlephilhower/ESP8266SAM
- IotWebConf https://github.com/prampec/IotWebConf
- PubSubClient https://github.com/knolleary/pubsubclient
Many thanks to all the authors and contributors to the above libraries - you have done an amazing job!
For more info visit me at MrDIY.ca and find the Notifier Video on my YouTube channel:
https://youtu.be/SPa9SMyPU58
============================================================================================================== */
//#define DEBUG_FLAG
#include "Arduino.h"
#include "boot_sound.h"
#include "ESP8266WiFi.h"
#include "PubSubClient.h"
#include "AudioFileSourceHTTPStream.h"
#include "AudioFileSourceICYStream.h"
#include "AudioFileSourcePROGMEM.h"
#include "AudioFileSourceBuffer.h"
#include "AudioGeneratorMP3.h"
#include "AudioGeneratorWAV.h"
#include "AudioGeneratorRTTTL.h"
#include "AudioOutputI2SNoDAC.h"
#include "ESP8266SAM.h"
#include "IotWebConf.h"
AudioGeneratorMP3 *mp3 = NULL;
AudioGeneratorWAV *wav = NULL;
AudioGeneratorRTTTL *rtttl = NULL;
AudioFileSourceHTTPStream *file_http = NULL;
AudioFileSourcePROGMEM *file_progmem = NULL;
AudioFileSourceICYStream *file_icy = NULL;
AudioFileSourceBuffer *buff = NULL;
AudioOutputI2SNoDAC *out = NULL;
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
#define port 1883
#define MQTT_MSG_SIZE 256
// AudioRelated ---------------------------
float volume_level = 0.8;
String playing_status;
const int preallocateBufferSize = 2048;
void *preallocateBuffer = NULL;
byte i;
// WifiManager -----------------------------
#define thingName "MrDIY Notifier"
#define wifiInitialApPassword "mrdiy.ca"
char mqttServer[16];
char mqttUserName[32];
char mqttUserPassword[32];
char mqttTopicPrefix[32];
char mqttTopic[MQTT_MSG_SIZE];
DNSServer dnsServer;
WebServer server(80);
IotWebConf iotWebConf(thingName, &dnsServer, &server, wifiInitialApPassword, "mrd2");
IotWebConfParameter mqttServerParam = IotWebConfParameter("MQTT server", "mqttServer", mqttServer, sizeof(mqttServer) );
IotWebConfParameter mqttUserNameParam = IotWebConfParameter("MQTT username", "mqttUser", mqttUserName, sizeof(mqttUserName));
IotWebConfParameter mqttUserPasswordParam = IotWebConfParameter("MQTT password", "mqttPass", mqttUserPassword, sizeof(mqttUserPassword), "password");
IotWebConfParameter mqttTopicParam = IotWebConfParameter("MQTT Topic", "mqttTopic", mqttTopicPrefix, sizeof(mqttTopicPrefix));
/* ################################## Setup ############################################# */
void setup() {
#ifdef DEBUG_FLAG
Serial.begin(115200);
#endif
iotWebConf.addParameter(&mqttServerParam);
iotWebConf.addParameter(&mqttUserNameParam);
iotWebConf.addParameter(&mqttUserPasswordParam);
iotWebConf.addParameter(&mqttTopicParam);
iotWebConf.setWifiConnectionCallback(&wifiConnected);
iotWebConf.setFormValidator(&formValidator);
iotWebConf.setStatusPin(LED_BUILTIN);
iotWebConf.skipApStartup();
boolean validConfig = iotWebConf.init();
if (!validConfig)
{
mqttServer[0] = '\0';
mqttUserName[0] = '\0';
mqttUserPassword[0] = '\0';
}
server.on("/", [] { iotWebConf.handleConfig(); });
server.onNotFound([] { iotWebConf.handleNotFound(); });
out = new AudioOutputI2SNoDAC();
out->SetGain(volume_level);
}
/* ##################################### Loop ############################################# */
void loop() {
mqttReconnect();
mqttClient.loop();
// give processor priority to audio
if (!mp3) iotWebConf.doLoop();
if (mp3 && !mp3->loop()) stopPlaying();
if (wav && !wav->loop()) stopPlaying();
if (rtttl && !rtttl->loop()) stopPlaying();
#ifdef DEBUG_FLAG
if (mp3 && mp3->isRunning()) {
static int unsigned long lastms = 0;
if (millis() - lastms > 1000) {
lastms = millis();
Serial.print(F("Free: "));
Serial.print(ESP.getFreeHeap(), DEC);
Serial.print(F(" ("));
Serial.print( ESP.getFreeHeap() - ESP.getMaxFreeBlockSize(), DEC);
Serial.print(F(" lost)"));
Serial.print(F(" Fragmentation: "));
Serial.print(ESP.getHeapFragmentation(), DEC);
Serial.print(F("%"));
if ( ESP.getHeapFragmentation() > 40) Serial.print(F(" ----------- "));
Serial.println();
}
}
#endif
}
/* ############################### Audio ############################################ */
void playBootSound() {
file_progmem = new AudioFileSourcePROGMEM(boot_sound, sizeof(boot_sound));
wav = new AudioGeneratorWAV();
wav->begin(file_progmem, out);
}
void stopPlaying() {
if (mp3) {
#ifdef DEBUG_FLAG
Serial.print(F("...#"));
Serial.println(F("Interrupted!"));
Serial.println();
#endif
mp3->stop();
delete mp3;
mp3 = NULL;
}
if (wav) {
wav->stop();
delete wav;
wav = NULL;
}
if (rtttl) {
rtttl->stop();
delete rtttl;
rtttl = NULL;
}
if (buff) {
buff->close();
delete buff;
buff = NULL;
}
if (file_http) {
file_http->close();
delete file_http;
file_http = NULL;
}
if (file_progmem) {
file_progmem->close();
delete file_progmem;
file_progmem = NULL;
}
if (file_icy) {
file_icy->close();
delete file_icy;
file_icy = NULL;
}
broadcastStatus("idle");
}
/* ################################## MQTT ############################################### */
void onMqttMessage(char* topic, byte* payload, unsigned int length) {
char newMsg[MQTT_MSG_SIZE];
if (length > 0) {
memset(newMsg, '\0' , sizeof(newMsg));
memcpy(newMsg, payload, length);
#ifdef DEBUG_FLAG
Serial.println();
Serial.print(F("Requested ["));
Serial.print(topic);
Serial.print(F("] "));
Serial.println(newMsg);
#endif
// got a new URL to play ------------------------------------------------
if ( !strcmp(topic, mqttFullTopic("play") ) ) {
stopPlaying();
file_http = new AudioFileSourceHTTPStream();
if ( file_http->open(newMsg)) {
broadcastStatus("playing");
buff = new AudioFileSourceBuffer(file_http, preallocateBuffer, preallocateBufferSize);
mp3 = new AudioGeneratorMP3();
mp3->begin(buff, out);
} else {
stopPlaying();
broadcastStatus("error");
broadcastStatus("idle");
}
}
// got a new URL to play ------------------------------------------------
if ( !strcmp(topic, mqttFullTopic("stream"))) {
stopPlaying();
file_icy = new AudioFileSourceICYStream();
if ( file_icy->open(newMsg)) {
broadcastStatus("playing");
buff = new AudioFileSourceBuffer(file_icy, preallocateBuffer, preallocateBufferSize);
mp3 = new AudioGeneratorMP3();
mp3->begin(buff, out);
} else {
stopPlaying();
broadcastStatus("error");
broadcastStatus("idle");
}
}
// got a tone request --------------------------------------------------
if ( !strcmp(topic, mqttFullTopic("tone") ) ) {
stopPlaying();
broadcastStatus("playing");
file_progmem = new AudioFileSourcePROGMEM( newMsg, sizeof(newMsg) );
rtttl = new AudioGeneratorRTTTL();
rtttl->begin(file_progmem, out);
broadcastStatus("idle");
}
//got a TTS request ----------------------------------------------------
if ( !strcmp(topic, mqttFullTopic("say"))) {
stopPlaying();
broadcastStatus("playing");
ESP8266SAM *sam = new ESP8266SAM;
sam->Say(out, newMsg);
delete sam;
stopPlaying();
broadcastStatus("idle");
}
// got a volume request, expecting double [0.0,1.0] ---------------------
if ( !strcmp(topic, mqttFullTopic("volume"))) {
volume_level = atof(newMsg);
if ( volume_level < 0.0 ) volume_level = 0;
if ( volume_level > 1.0 ) volume_level = 0.7;
out->SetGain(volume_level);
}
// got a stop request --------------------------------------------------
if ( !strcmp(topic, mqttFullTopic("stop"))) {
stopPlaying();
}
}
}
void broadcastStatus(String msg) {
if ( playing_status != msg) {
char charBuf[msg.length() + 1];
msg.toCharArray(charBuf, msg.length() + 1);
mqttClient.publish(mqttFullTopic("status"), charBuf);
playing_status = msg;
#ifdef DEBUG_FLAG
Serial.println();
Serial.print("status: ");
Serial.println(msg);
#endif
}
}
void mqttReconnect() {
if (!mqttClient.connected()) {
if (mqttClient.connect("MrDIY Notifier", mqttUserName, mqttUserPassword)) {
broadcastStatus("connected");
mqttClient.subscribe(mqttFullTopic("play"));
mqttClient.subscribe(mqttFullTopic("stream"));
mqttClient.subscribe(mqttFullTopic("tone"));
mqttClient.subscribe(mqttFullTopic("say"));
mqttClient.subscribe(mqttFullTopic("stop"));
mqttClient.subscribe(mqttFullTopic("volume"));
#ifdef DEBUG_FLAG
Serial.println();
Serial.print(F("Connected to MQTT: "));
Serial.println(F("mrdiynotifier"));
#endif
broadcastStatus("idle");
}
}
}
/* ############################ WifiManager ############################################# */
void wifiConnected() {
#ifdef DEBUG_FLAG
Serial.println();
Serial.println(F("=================================================================="));
Serial.println(F(" MrDIY Notifier"));
Serial.println(F("=================================================================="));
Serial.println();
Serial.print(F("Connected to Wifi ["));
Serial.print(WiFi.localIP());
Serial.println(F("]"));
#endif
playBootSound();
mqttClient.setServer(mqttServer, port);
mqttClient.setCallback(onMqttMessage);
mqttClient.setBufferSize(MQTT_MSG_SIZE);
mqttReconnect();
}
boolean formValidator() {
boolean valid = true;
int l = server.arg(mqttServerParam.getId()).length();
if (l == 0) {
mqttServerParam.errorMessage = "Please provide the MQTT server address";
valid = false;
}
return valid;
}
char* mqttFullTopic(char action[]) {
strcpy (mqttTopic, "/");
strcat (mqttTopic, mqttTopicPrefix);
strcat (mqttTopic, "/");
strcat (mqttTopic, action);
return mqttTopic;
}