diff --git a/README.md b/README.md index fdf0f4a..b8d3e35 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ - [Hardware setup](#hardware-setup) - [Quick start](#quick-start) - [Update](#update) +- [MQTT](#mqtt) - [More features](#more-features) - [Version history](#version-history) --- @@ -101,7 +102,9 @@ _\** GPIOs 34-39 don't have software pullup/down functions. For encoder/buttons ## Dependencies #### Libraries: **Library Manager**: Adafruit_GFX, Adafruit_ST7735\*, Adafruit_SSD1306\*, Adafruit_PCD8544\*, (\* depending on display model), ESP32Encoder, OneButton, IRremoteESP8266 \ -**Github**: [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer), [AsyncTCP](https://github.com/me-no-dev/AsyncTCP) +**Github**: [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer), [AsyncTCP](https://github.com/me-no-dev/AsyncTCP), [async-mqtt-client](https://github.com/marvinroger/async-mqtt-client)* \ +\* _if you need MQTT support_ + #### Tool: [ESP32 Filesystem Uploader](https://randomnerdtutorials.com/install-esp32-filesystem-uploader-arduino-ide/) @@ -171,6 +174,12 @@ download _http://\/data/playlist.csv_ and _http://\/data 4. Go to page _http://\/_ in the browser and press Ctrl+F5 to update the scripts. 5. Well done! +--- +## MQTT +1. Copy file exsamples/mqttoptions.h to yoRadio/ directory +2. In the mqtoptions.h file, change the options to the ones you need +3. Well done! + --- ## More features - Сan add up to 65535 stations to a playlist. Supports and imports [KaRadio](https://github.com/karawin/Ka-Radio32) playlists (WebStations.txt) @@ -196,12 +205,33 @@ download _http://\/data/playlist.csv_ and _http://\/data **sys.tzo("h:m")** _or_ **tzo(h:m)** _or_ **tzo h:m** - set timezone offset \ **sys.tzo("h")** _or_ **tzo(h)** _or_ **tzo h** - set timezone offset in hours only +- MQTT support \ + **Topics**: \ + **MQTT_ROOT_TOPIC/command** - Commands \ + **MQTT_ROOT_TOPIC/status** - Player status \ + **MQTT_ROOT_TOPIC/playlist** - Playlist URL \ + **MQTT_ROOT_TOPIC/volume** - Current volume + + **Commands**: \ + **prev** - prev station \ + **next** - next station \ + **toggle** - start/stop playing \ + **stop** - stop playing \ + **start, play** - start playing \ + **boot, reboot** - reboot \ + **vol x** - set volume \ + **play x** - play station x --- ## Version history +#### v0.4.320 +- MQTT support + + + #### v0.4.315 - added support for digital buttons for the IR control \ (num keys - enter number of station, ok - play, hash - cancel) -- added buttons for exporting settings from the web interface +- added buttons for exporting settings from the web interface - added MUTE_PIN to be able to control the audio output - fixed js/html bugs (a [full update](#update) is required) diff --git a/exsamples/mqttoptions.h b/exsamples/mqttoptions.h new file mode 100644 index 0000000..926eb1a --- /dev/null +++ b/exsamples/mqttoptions.h @@ -0,0 +1,24 @@ +#define MQTT_HOST "192.168.3.100" +#define MQTT_PORT 1883 +#define MQTT_USER "" +#define MQTT_PASS "" + +#define MQTT_ROOT_TOPIC "yoradio/100/" + +/* +Topics: +MQTT_ROOT_TOPIC/command // Commands +MQTT_ROOT_TOPIC/status // Player status +MQTT_ROOT_TOPIC/playlist // Playlist URL +MQTT_ROOT_TOPIC/volume // Current volume + +Commands: +prev // prev station +next // next station +toggle // start/stop playing +stop // stop playing +start, play // start playing +boot, reboot // reboot +vol x // set volume +play x // play station x +*/ diff --git a/images/mqtt.jpg b/images/mqtt.jpg new file mode 100644 index 0000000..8876d99 Binary files /dev/null and b/images/mqtt.jpg differ diff --git a/yoRadio/mqtt.cpp b/yoRadio/mqtt.cpp new file mode 100644 index 0000000..432d932 --- /dev/null +++ b/yoRadio/mqtt.cpp @@ -0,0 +1,112 @@ +#include "mqtt.h" + +#ifdef MQTT_HOST +#include "WiFi.h" + +#include "telnet.h" +#include "player.h" +#include "config.h" + +AsyncMqttClient mqttClient; +TimerHandle_t mqttReconnectTimer; + +void connectToMqtt() { + mqttClient.connect(); +} + +void mqttInit() { + mqttReconnectTimer = xTimerCreate("mqttTimer", pdMS_TO_TICKS(2000), pdFALSE, (void*)0, reinterpret_cast(connectToMqtt)); + mqttClient.onConnect(onMqttConnect); + mqttClient.onDisconnect(onMqttDisconnect); + mqttClient.onMessage(onMqttMessage); + if(MQTT_USER!="") mqttClient.setCredentials(MQTT_USER, MQTT_PASS); + mqttClient.setServer(MQTT_HOST, MQTT_PORT); + connectToMqtt(); +} + +void onMqttConnect(bool sessionPresent) { + char buf[140]; + sprintf(buf, "%s%s", MQTT_ROOT_TOPIC, "command"); + mqttClient.subscribe(buf, 2); + mqttPublishStatus(); + mqttPublishVolume(); + mqttPublishPlaylist(); +} + +void mqttPublishStatus() { + if(mqttClient.connected()){ + char topic[140], status[255]; + sprintf(topic, "%s%s", MQTT_ROOT_TOPIC, "status"); + sprintf(status, "{\"status\": %d, \"station\": %d, \"name\": \"%s\", \"title\": \"%s\"}", player.mode==PLAYING?1:0, config.store.lastStation, config.station.name, config.station.title); + mqttClient.publish(topic, 0, true, status); + } +} + +void mqttPublishPlaylist() { + if(mqttClient.connected()){ + char topic[140], playlist[140]; + sprintf(topic, "%s%s", MQTT_ROOT_TOPIC, "playlist"); + sprintf(playlist, "http://%s%s", WiFi.localIP().toString().c_str(), PLAYLIST_PATH); + mqttClient.publish(topic, 0, true, playlist); + } +} + +void mqttPublishVolume(){ + if(mqttClient.connected()){ + char topic[140], vol[5]; + sprintf(topic, "%s%s", MQTT_ROOT_TOPIC, "volume"); + sprintf(vol, "%d", config.store.volume); + mqttClient.publish(topic, 0, true, vol); + } +} + +void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { + if (WiFi.isConnected()) { + xTimerStart(mqttReconnectTimer, 0); + } +} + +void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { + if (strlen(payload) == 0) return; + if (strcmp(payload, "prev") == 0) { + player.prev(); + return; + } + if (strcmp(payload, "next") == 0) { + player.next(); + return; + } + if (strcmp(payload, "toggle") == 0) { + player.toggle(); + return; + } + if (strcmp(payload, "stop") == 0) { + player.mode = STOPPED; + telnet.info(); + return; + } + if (strcmp(payload, "start") == 0 || strcmp(payload, "play") == 0) { + player.play(config.store.lastStation); + return; + } + if (strcmp(payload, "boot") == 0 || strcmp(payload, "reboot") == 0) { + ESP.restart(); + return; + } + int volume; + if ( sscanf(payload, "vol %d", &volume) == 1) { + if (volume < 0) volume = 0; + if (volume > 254) volume = 254; + player.setVol(volume, false); + return; + } + uint16_t sb; + if (sscanf(payload, "play %d", &sb) == 1 ) { + if (sb < 1) sb = 1; + if (sb >= config.store.countStation) sb = config.store.countStation; + player.play(sb); + return; + } +} + +#endif // ifdef MQTT_HOST diff --git a/yoRadio/mqtt.h b/yoRadio/mqtt.h new file mode 100644 index 0000000..224870c --- /dev/null +++ b/yoRadio/mqtt.h @@ -0,0 +1,21 @@ +#ifndef mqtt_h +#define mqtt_h + +#if __has_include("mqttoptions.h") +#include "mqttoptions.h" +#include + + +void mqttInit(); +void connectToMqtt(); +void onMqttConnect(bool sessionPresent); +void onMqttDisconnect(AsyncMqttClientDisconnectReason reason); +void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total); +void mqttPublishStatus(); +void mqttPublishPlaylist(); +void mqttPublishVolume(); + +#endif // if __has_include("mqttoptions.h") + + +#endif diff --git a/yoRadio/netserver.cpp b/yoRadio/netserver.cpp index 1c5391d..ced44fa 100644 --- a/yoRadio/netserver.cpp +++ b/yoRadio/netserver.cpp @@ -6,6 +6,7 @@ #include "display.h" #include "options.h" #include "network.h" +#include "mqtt.h" NetServer netserver; @@ -183,6 +184,9 @@ void NetServer::requestOnChange(requestType_e request, uint8_t clientId) { config.indexPlaylist(); config.initPlaylist(); getPlaylist(clientId); +#ifdef MQTT_HOST + mqttPublishPlaylist(); +#endif break; } case STATION: { @@ -200,6 +204,9 @@ void NetServer::requestOnChange(requestType_e request, uint8_t clientId) { } case VOLUME: { sprintf (buf, "{\"vol\": %d}", config.store.volume); +#ifdef MQTT_HOST + if (clientId == 0) mqttPublishVolume(); +#endif break; } case NRSSI: { @@ -226,6 +233,9 @@ void NetServer::requestOnChange(requestType_e request, uint8_t clientId) { if (strlen(buf) > 0) { if (clientId == 0) { websocket.textAll(buf); +#ifdef MQTT_HOST + if(request==STATION || request==ITEM || request==TITLE || request==MODE) mqttPublishStatus(); +#endif } else { websocket.text(clientId, buf); } diff --git a/yoRadio/options.h b/yoRadio/options.h index 5f0db5b..12bd0aa 100644 --- a/yoRadio/options.h +++ b/yoRadio/options.h @@ -1,7 +1,7 @@ #ifndef options_h #define options_h -#define VERSION "0.4.315" +#define VERSION "0.4.320" #if __has_include("myoptions.h") #include "myoptions.h" // <- write your variable values here diff --git a/yoRadio/yoRadio.ino b/yoRadio/yoRadio.ino index 358738b..a408b22 100644 --- a/yoRadio/yoRadio.ino +++ b/yoRadio/yoRadio.ino @@ -9,6 +9,7 @@ #include "network.h" #include "netserver.h" #include "controls.h" +#include "mqtt.h" void setup() { Serial.begin(115200); @@ -30,6 +31,9 @@ void setup() { player.setVol(config.store.volume, true); display.start(); if(config.store.smartstart==1) player.play(config.store.lastStation); +#ifdef MQTT_HOST + mqttInit(); +#endif } void loop() {