#include #include #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 #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 }