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.
441 lines
19 KiB
441 lines
19 KiB
#include <Arduino.h>
|
|
#include <WiFi.h>
|
|
#include "settings.h"
|
|
#include "Mqtt.h"
|
|
#include "AudioPlayer.h"
|
|
#include "Led.h"
|
|
#include "Log.h"
|
|
#include "MemX.h"
|
|
#include "System.h"
|
|
#include "Queues.h"
|
|
#include "Wlan.h"
|
|
|
|
#ifdef MQTT_ENABLE
|
|
#define MQTT_SOCKET_TIMEOUT 1 // https://github.com/knolleary/pubsubclient/issues/403
|
|
#include <PubSubClient.h>
|
|
#endif
|
|
|
|
constexpr uint8_t stillOnlineInterval = 60u; // Interval 'I'm still alive' is sent via MQTT (in seconds)
|
|
|
|
// MQTT-helper
|
|
#ifdef MQTT_ENABLE
|
|
static WiFiClient Mqtt_WifiClient;
|
|
static PubSubClient Mqtt_PubSubClient(Mqtt_WifiClient);
|
|
#endif
|
|
|
|
// Please note: all of them are defaults that can be changed later via GUI
|
|
char *gMqttServer = x_strndup((char *) "192.168.2.43", mqttServerLength); // IP-address of MQTT-server (if not found in NVS this one will be taken)
|
|
char *gMqttUser = x_strndup((char *) "mqtt-user", mqttUserLength); // MQTT-user
|
|
char *gMqttPassword = x_strndup((char *) "mqtt-password", mqttPasswordLength); // MQTT-password*/
|
|
uint16_t gMqttPort = 1883; // MQTT-Port
|
|
|
|
// MQTT
|
|
static bool Mqtt_Enabled = true;
|
|
|
|
static void Mqtt_ClientCallback(const char *topic, const byte *payload, uint32_t length);
|
|
static bool Mqtt_Reconnect(void);
|
|
static void Mqtt_PostHeartbeatViaMqtt(void);
|
|
|
|
void Mqtt_Init() {
|
|
#ifdef MQTT_ENABLE
|
|
// Get MQTT-enable from NVS
|
|
uint8_t nvsEnableMqtt = gPrefsSettings.getUChar("enableMQTT", 99);
|
|
switch (nvsEnableMqtt) {
|
|
case 99:
|
|
gPrefsSettings.putUChar("enableMQTT", Mqtt_Enabled);
|
|
Log_Println((char *) FPSTR(wroteMqttFlagToNvs), LOGLEVEL_ERROR);
|
|
break;
|
|
case 1:
|
|
Mqtt_Enabled = nvsEnableMqtt;
|
|
snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *) FPSTR(restoredMqttActiveFromNvs), nvsEnableMqtt);
|
|
Log_Println(Log_Buffer, LOGLEVEL_INFO);
|
|
break;
|
|
case 0:
|
|
Mqtt_Enabled = nvsEnableMqtt;
|
|
snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *) FPSTR(restoredMqttDeactiveFromNvs), nvsEnableMqtt);
|
|
Log_Println(Log_Buffer, LOGLEVEL_INFO);
|
|
break;
|
|
}
|
|
|
|
// Get MQTT-server from NVS
|
|
String nvsMqttServer = gPrefsSettings.getString("mqttServer", "-1");
|
|
if (!nvsMqttServer.compareTo("-1")) {
|
|
gPrefsSettings.putString("mqttServer", (String)gMqttServer);
|
|
Log_Println((char *) FPSTR(wroteMqttServerToNvs), LOGLEVEL_ERROR);
|
|
} else {
|
|
strncpy(gMqttServer, nvsMqttServer.c_str(), mqttServerLength);
|
|
snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *) FPSTR(restoredMqttServerFromNvs), nvsMqttServer.c_str());
|
|
Log_Println(Log_Buffer, LOGLEVEL_INFO);
|
|
}
|
|
|
|
// Get MQTT-user from NVS
|
|
String nvsMqttUser = gPrefsSettings.getString("mqttUser", "-1");
|
|
if (!nvsMqttUser.compareTo("-1")) {
|
|
gPrefsSettings.putString("mqttUser", (String)gMqttUser);
|
|
Log_Println((char *) FPSTR(wroteMqttUserToNvs), LOGLEVEL_ERROR);
|
|
} else {
|
|
strncpy(gMqttUser, nvsMqttUser.c_str(), mqttUserLength);
|
|
snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *) FPSTR(restoredMqttUserFromNvs), nvsMqttUser.c_str());
|
|
Log_Println(Log_Buffer, LOGLEVEL_INFO);
|
|
}
|
|
|
|
// Get MQTT-password from NVS
|
|
String nvsMqttPassword = gPrefsSettings.getString("mqttPassword", "-1");
|
|
if (!nvsMqttPassword.compareTo("-1")) {
|
|
gPrefsSettings.putString("mqttPassword", (String)gMqttPassword);
|
|
Log_Println((char *) FPSTR(wroteMqttPwdToNvs), LOGLEVEL_ERROR);
|
|
} else {
|
|
strncpy(gMqttPassword, nvsMqttPassword.c_str(), mqttPasswordLength);
|
|
snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *) FPSTR(restoredMqttPwdFromNvs), nvsMqttPassword.c_str());
|
|
Log_Println(Log_Buffer, LOGLEVEL_INFO);
|
|
}
|
|
|
|
// Get MQTT-password from NVS
|
|
uint32_t nvsMqttPort = gPrefsSettings.getUInt("mqttPort", 99999);
|
|
if (nvsMqttPort == 99999) {
|
|
gPrefsSettings.putUInt("mqttPort", gMqttPort);
|
|
} else {
|
|
gMqttPort = nvsMqttPort;
|
|
snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *) FPSTR(restoredMqttPortFromNvs), gMqttPort);
|
|
Log_Println(Log_Buffer, LOGLEVEL_INFO);
|
|
}
|
|
|
|
// Only enable MQTT if requested
|
|
if (Mqtt_Enabled) {
|
|
Mqtt_PubSubClient.setServer(gMqttServer, gMqttPort);
|
|
Mqtt_PubSubClient.setCallback(Mqtt_ClientCallback);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Mqtt_Cyclic(void) {
|
|
#ifdef MQTT_ENABLE
|
|
if (Mqtt_Enabled && Wlan_IsConnected()) {
|
|
Mqtt_Reconnect();
|
|
Mqtt_PubSubClient.loop();
|
|
Mqtt_PostHeartbeatViaMqtt();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Mqtt_Exit(void) {
|
|
#ifdef MQTT_ENABLE
|
|
publishMqtt((char *) FPSTR(topicState), "Offline", false);
|
|
publishMqtt((char *) FPSTR(topicTrackState), "---", false);
|
|
Mqtt_PubSubClient.disconnect();
|
|
#endif
|
|
}
|
|
|
|
bool Mqtt_IsEnabled(void) {
|
|
return Mqtt_Enabled;
|
|
}
|
|
|
|
/* Wrapper-functions for MQTT-publish */
|
|
bool publishMqtt(const char *topic, const char *payload, bool retained) {
|
|
#ifdef MQTT_ENABLE
|
|
if (strcmp(topic, "") != 0) {
|
|
if (Mqtt_PubSubClient.connected()) {
|
|
Mqtt_PubSubClient.publish(topic, payload, retained);
|
|
delay(100);
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
bool publishMqtt(const char *topic, int32_t payload, bool retained) {
|
|
#ifdef MQTT_ENABLE
|
|
char buf[11];
|
|
snprintf(buf, sizeof(buf) / sizeof(buf[0]), "%d", payload);
|
|
return publishMqtt(topic, buf, retained);
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool publishMqtt(const char *topic, unsigned long payload, bool retained) {
|
|
#ifdef MQTT_ENABLE
|
|
char buf[11];
|
|
snprintf(buf, sizeof(buf) / sizeof(buf[0]), "%lu", payload);
|
|
return publishMqtt(topic, buf, retained);
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool publishMqtt(const char *topic, uint32_t payload, bool retained) {
|
|
#ifdef MQTT_ENABLE
|
|
char buf[11];
|
|
snprintf(buf, sizeof(buf) / sizeof(buf[0]), "%u", payload);
|
|
return publishMqtt(topic, buf, retained);
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
/* Cyclic posting via MQTT that ESP is still alive. Use case: when ESPuino is switched off, it will post via
|
|
MQTT it's gonna be offline now. But when unplugging ESPuino e.g. openHAB doesn't know ESPuino is offline.
|
|
One way to recognize this is to determine, when a topic has been updated for the last time. So by
|
|
telling openHAB connection is timed out after 2mins for instance, this is the right topic to check for. */
|
|
void Mqtt_PostHeartbeatViaMqtt(void) {
|
|
#ifdef MQTT_ENABLE
|
|
static unsigned long lastOnlineTimestamp = 0u;
|
|
|
|
if (millis() - lastOnlineTimestamp >= stillOnlineInterval * 1000) {
|
|
lastOnlineTimestamp = millis();
|
|
if (publishMqtt((char *) FPSTR(topicState), "Online", false)) {
|
|
Log_Println((char *) FPSTR(stillOnlineMqtt), LOGLEVEL_DEBUG);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Connects/reconnects to MQTT-Broker unless connection is not already available.
|
|
Manages MQTT-subscriptions.
|
|
*/
|
|
bool Mqtt_Reconnect() {
|
|
#ifdef MQTT_ENABLE
|
|
static uint32_t mqttLastRetryTimestamp = 0u;
|
|
uint8_t connect = false;
|
|
uint8_t i = 0;
|
|
|
|
if (!mqttLastRetryTimestamp || millis() - mqttLastRetryTimestamp >= mqttRetryInterval * 1000) {
|
|
mqttLastRetryTimestamp = millis();
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
while (!Mqtt_PubSubClient.connected() && i < mqttMaxRetriesPerInterval) {
|
|
i++;
|
|
snprintf(Log_Buffer, Log_BufferLength, "%s %s", (char *) FPSTR(tryConnectMqttS), gMqttServer);
|
|
Log_Println(Log_Buffer, LOGLEVEL_NOTICE);
|
|
|
|
// Try to connect to MQTT-server. If username AND password are set, they'll be used
|
|
if (strlen(gMqttUser) < 1 || strlen(gMqttPassword) < 1) {
|
|
Log_Println((char *) FPSTR(mqttWithoutPwd), LOGLEVEL_NOTICE);
|
|
if (Mqtt_PubSubClient.connect(DEVICE_HOSTNAME)) {
|
|
connect = true;
|
|
}
|
|
} else {
|
|
Log_Println((char *) FPSTR(mqttWithPwd), LOGLEVEL_NOTICE);
|
|
if (Mqtt_PubSubClient.connect(DEVICE_HOSTNAME, gMqttUser, gMqttPassword)) {
|
|
connect = true;
|
|
}
|
|
}
|
|
if (connect) {
|
|
Log_Println((char *) FPSTR(mqttOk), LOGLEVEL_NOTICE);
|
|
|
|
// Deepsleep-subscription
|
|
Mqtt_PubSubClient.subscribe((char *) FPSTR(topicSleepCmnd));
|
|
|
|
// RFID-Tag-ID-subscription
|
|
Mqtt_PubSubClient.subscribe((char *) FPSTR(topicRfidCmnd));
|
|
|
|
// Loudness-subscription
|
|
Mqtt_PubSubClient.subscribe((char *) FPSTR(topicLoudnessCmnd));
|
|
|
|
// Sleep-Timer-subscription
|
|
Mqtt_PubSubClient.subscribe((char *) FPSTR(topicSleepTimerCmnd));
|
|
|
|
// Next/previous/stop/play-track-subscription
|
|
Mqtt_PubSubClient.subscribe((char *) FPSTR(topicTrackControlCmnd));
|
|
|
|
// Lock controls
|
|
Mqtt_PubSubClient.subscribe((char *) FPSTR(topicLockControlsCmnd));
|
|
|
|
// Current repeat-Mode
|
|
Mqtt_PubSubClient.subscribe((char *) FPSTR(topicRepeatModeCmnd));
|
|
|
|
// LED-brightness
|
|
Mqtt_PubSubClient.subscribe((char *) FPSTR(topicLedBrightnessCmnd));
|
|
|
|
// Publish some stuff
|
|
publishMqtt((char *) FPSTR(topicState), "Online", false);
|
|
publishMqtt((char *) FPSTR(topicTrackState), "---", false);
|
|
publishMqtt((char *) FPSTR(topicLoudnessState), AudioPlayer_GetCurrentVolume(), false);
|
|
publishMqtt((char *) FPSTR(topicSleepTimerState), System_GetSleepTimerTimeStamp(), false);
|
|
publishMqtt((char *) FPSTR(topicLockControlsState), "OFF", false);
|
|
publishMqtt((char *) FPSTR(topicPlaymodeState), gPlayProperties.playMode, false);
|
|
publishMqtt((char *) FPSTR(topicLedBrightnessState), Led_GetBrightness(), false);
|
|
publishMqtt((char *) FPSTR(topicRepeatModeState), 0, false);
|
|
publishMqtt((char *) FPSTR(topicCurrentIPv4IP), Wlan_GetIpAddress().c_str(), false);
|
|
|
|
return Mqtt_PubSubClient.connected();
|
|
} else {
|
|
snprintf(Log_Buffer, Log_BufferLength, "%s: rc=%i (%d / %d)", (char *) FPSTR(mqttConnFailed), Mqtt_PubSubClient.state(), i, mqttMaxRetriesPerInterval);
|
|
Log_Println(Log_Buffer, LOGLEVEL_ERROR);
|
|
}
|
|
}
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
// Is called if there's a new MQTT-message for us
|
|
void Mqtt_ClientCallback(const char *topic, const byte *payload, uint32_t length) {
|
|
#ifdef MQTT_ENABLE
|
|
char *receivedString = x_strndup((char *) payload, length);
|
|
char *mqttTopic = x_strdup(topic);
|
|
|
|
snprintf(Log_Buffer, Log_BufferLength, "%s: [Topic: %s] [Command: %s]", (char *) FPSTR(mqttMsgReceived), mqttTopic, receivedString);
|
|
Log_Println(Log_Buffer, LOGLEVEL_INFO);
|
|
|
|
// Go to sleep?
|
|
if (strcmp_P(topic, topicSleepCmnd) == 0) {
|
|
if ((strcmp(receivedString, "OFF") == 0) || (strcmp(receivedString, "0") == 0)) {
|
|
System_RequestSleep();
|
|
}
|
|
}
|
|
// New track to play? Take RFID-ID as input
|
|
else if (strcmp_P(topic, topicRfidCmnd) == 0) {
|
|
char *_rfidId = x_strdup(receivedString);
|
|
xQueueSend(gRfidCardQueue, _rfidId, 0);
|
|
}
|
|
// Loudness to change?
|
|
else if (strcmp_P(topic, topicLoudnessCmnd) == 0) {
|
|
unsigned long vol = strtoul(receivedString, NULL, 10);
|
|
AudioPlayer_VolumeToQueueSender(vol, true);
|
|
}
|
|
// Modify sleep-timer?
|
|
else if (strcmp_P(topic, topicSleepTimerCmnd) == 0) {
|
|
if (gPlayProperties.playMode == NO_PLAYLIST) { // Don't allow sleep-modications if no playlist is active
|
|
Log_Println((char *) FPSTR(modificatorNotallowedWhenIdle), LOGLEVEL_INFO);
|
|
publishMqtt((char *) FPSTR(topicSleepState), 0, false);
|
|
System_IndicateError();
|
|
return;
|
|
}
|
|
if (strcmp(receivedString, "EOP") == 0) {
|
|
gPlayProperties.sleepAfterPlaylist = true;
|
|
Log_Println((char *) FPSTR(sleepTimerEOP), LOGLEVEL_NOTICE);
|
|
System_IndicateOk();
|
|
return;
|
|
} else if (strcmp(receivedString, "EOT") == 0) {
|
|
gPlayProperties.sleepAfterCurrentTrack = true;
|
|
Log_Println((char *) FPSTR(sleepTimerEOT), LOGLEVEL_NOTICE);
|
|
System_IndicateOk();
|
|
return;
|
|
} else if (strcmp(receivedString, "EO5T") == 0) {
|
|
if ((gPlayProperties.numberOfTracks - 1) >= (gPlayProperties.currentTrackNumber + 5)) {
|
|
gPlayProperties.playUntilTrackNumber = gPlayProperties.currentTrackNumber + 5;
|
|
} else {
|
|
gPlayProperties.sleepAfterPlaylist = true;
|
|
}
|
|
Log_Println((char *) FPSTR(sleepTimerEO5), LOGLEVEL_NOTICE);
|
|
System_IndicateOk();
|
|
return;
|
|
} else if (strcmp(receivedString, "0") == 0) {
|
|
if (System_IsSleepTimerEnabled()) {
|
|
System_DisableSleepTimer();
|
|
Log_Println((char *) FPSTR(sleepTimerStop), LOGLEVEL_NOTICE);
|
|
System_IndicateOk();
|
|
publishMqtt((char *) FPSTR(topicSleepState), 0, false);
|
|
return;
|
|
} else {
|
|
Log_Println((char *) FPSTR(sleepTimerAlreadyStopped), LOGLEVEL_INFO);
|
|
System_IndicateError();
|
|
return;
|
|
}
|
|
}
|
|
System_SetSleepTimer((uint8_t)strtoul(receivedString, NULL, 10));
|
|
snprintf(Log_Buffer, Log_BufferLength, "%s: %u Minute(n)", (char *) FPSTR(sleepTimerSetTo), System_GetSleepTimer());
|
|
Log_Println(Log_Buffer, LOGLEVEL_NOTICE);
|
|
System_IndicateOk();
|
|
|
|
gPlayProperties.sleepAfterPlaylist = false;
|
|
gPlayProperties.sleepAfterCurrentTrack = false;
|
|
}
|
|
// Track-control (pause/play, stop, first, last, next, previous)
|
|
else if (strcmp_P(topic, topicTrackControlCmnd) == 0) {
|
|
uint8_t controlCommand = strtoul(receivedString, NULL, 10);
|
|
AudioPlayer_TrackControlToQueueSender(controlCommand);
|
|
}
|
|
|
|
// Check if controls should be locked
|
|
else if (strcmp_P(topic, topicLockControlsCmnd) == 0) {
|
|
if (strcmp(receivedString, "OFF") == 0) {
|
|
System_SetLockControls(false);
|
|
Log_Println((char *) FPSTR(allowButtons), LOGLEVEL_NOTICE);
|
|
System_IndicateOk();
|
|
} else if (strcmp(receivedString, "ON") == 0) {
|
|
System_SetLockControls(true);
|
|
Log_Println((char *) FPSTR(lockButtons), LOGLEVEL_NOTICE);
|
|
System_IndicateOk();
|
|
}
|
|
}
|
|
|
|
// Check if playmode should be adjusted
|
|
else if (strcmp_P(topic, topicRepeatModeCmnd) == 0) {
|
|
char rBuf[2];
|
|
uint8_t repeatMode = strtoul(receivedString, NULL, 10);
|
|
Serial.printf("Repeat: %d", repeatMode);
|
|
if (gPlayProperties.playMode != NO_PLAYLIST) {
|
|
if (gPlayProperties.playMode == NO_PLAYLIST) {
|
|
snprintf(rBuf, 2, "%u", AudioPlayer_GetRepeatMode());
|
|
publishMqtt((char *) FPSTR(topicRepeatModeState), rBuf, false);
|
|
Log_Println((char *) FPSTR(noPlaylistNotAllowedMqtt), LOGLEVEL_ERROR);
|
|
System_IndicateError();
|
|
} else {
|
|
switch (repeatMode) {
|
|
case NO_REPEAT:
|
|
gPlayProperties.repeatCurrentTrack = false;
|
|
gPlayProperties.repeatPlaylist = false;
|
|
snprintf(rBuf, 2, "%u", AudioPlayer_GetRepeatMode());
|
|
publishMqtt((char *) FPSTR(topicRepeatModeState), rBuf, false);
|
|
Log_Println((char *) FPSTR(modeRepeatNone), LOGLEVEL_INFO);
|
|
System_IndicateOk();
|
|
break;
|
|
|
|
case TRACK:
|
|
gPlayProperties.repeatCurrentTrack = true;
|
|
gPlayProperties.repeatPlaylist = false;
|
|
snprintf(rBuf, 2, "%u", AudioPlayer_GetRepeatMode());
|
|
publishMqtt((char *) FPSTR(topicRepeatModeState), rBuf, false);
|
|
Log_Println((char *) FPSTR(modeRepeatTrack), LOGLEVEL_INFO);
|
|
System_IndicateOk();
|
|
break;
|
|
|
|
case PLAYLIST:
|
|
gPlayProperties.repeatCurrentTrack = false;
|
|
gPlayProperties.repeatPlaylist = true;
|
|
snprintf(rBuf, 2, "%u", AudioPlayer_GetRepeatMode());
|
|
publishMqtt((char *) FPSTR(topicRepeatModeState), rBuf, false);
|
|
Log_Println((char *) FPSTR(modeRepeatPlaylist), LOGLEVEL_INFO);
|
|
System_IndicateOk();
|
|
break;
|
|
|
|
case TRACK_N_PLAYLIST:
|
|
gPlayProperties.repeatCurrentTrack = true;
|
|
gPlayProperties.repeatPlaylist = true;
|
|
snprintf(rBuf, 2, "%u", AudioPlayer_GetRepeatMode());
|
|
publishMqtt((char *) FPSTR(topicRepeatModeState), rBuf, false);
|
|
Log_Println((char *) FPSTR(modeRepeatTracknPlaylist), LOGLEVEL_INFO);
|
|
System_IndicateOk();
|
|
break;
|
|
|
|
default:
|
|
System_IndicateError();
|
|
snprintf(rBuf, 2, "%u", AudioPlayer_GetRepeatMode());
|
|
publishMqtt((char *) FPSTR(topicRepeatModeState), rBuf, false);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if LEDs should be dimmed
|
|
else if (strcmp_P(topic, topicLedBrightnessCmnd) == 0) {
|
|
Led_SetBrightness(strtoul(receivedString, NULL, 10));
|
|
}
|
|
|
|
// Requested something that isn't specified?
|
|
else {
|
|
snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *) FPSTR(noValidTopic), topic);
|
|
Log_Println(Log_Buffer, LOGLEVEL_ERROR);
|
|
System_IndicateError();
|
|
}
|
|
|
|
free(receivedString);
|
|
free(mqttTopic);
|
|
#endif
|
|
}
|