diff --git a/README.md b/README.md index b01df27..b516311 100644 --- a/README.md +++ b/README.md @@ -225,11 +225,28 @@ download _http://\/data/playlist.csv_ and _http://\/data --- ## Plugins -There is no documentation yet, you will have to deal with the examples, which is in directory [examples/plugins/](https://github.com/e2002/yoradio/tree/main/examples/plugins).\ +The `Plugin` class serves as a base class for creating plugins that hook into various system events. +To use it, inherit from `Plugin` and override the necessary virtual methods. +Place your new class in the `src/plugins/` directory +More details can be found in the comments within the `yoRadio/src/pluginsManager/pluginsManager.h` file and at [here](https://github.com/e2002/yoradio/blob/main/yoRadio/src/pluginsManager/README.md). +Additional examples are provided in the `examples/plugins` folder. Work is in progress... --- ## Version history +#### v0.9.350 +- **Added parameters for configuring `LED_BUILTIN` on ESP32S3 modules:** + - `USE_BUILTIN_LED`: Determines whether to use the built-in `LED_BUILTIN` (default is `true`). + - `LED_BUILTIN_S3`: Specifies a custom pin for the built-in `LED_BUILTIN`. Used in combination with `USE_BUILTIN_LED = false` (default is `255`). + + **Note:** For ESP32S3 boards, no changes are required by default; the onboard LED will work as expected. + These settings were added to allow disabling the built-in LED or reassigning it to a custom pin. + +- **New class for plugin management**, enabling multiple plugins to be assigned to each function. + More details can be found in the comments within the `yoRadio/src/pluginsManager/pluginsManager.h` file and at [here](https://github.com/e2002/yoradio/blob/main/yoRadio/src/pluginsManager/README.md). + Additional examples are provided in the `examples/plugins` folder. + **Backward compatibility:** The old method of adding plugins will remain functional for some time in future versions but will eventually be deprecated and removed. + #### v0.9.342b - fixed compilation error for OLED displays diff --git a/examples/plugins/backlightControls/backlightcontrols.cpp b/examples/plugins/backlightControls/backlightcontrols.cpp new file mode 100644 index 0000000..c0458c5 --- /dev/null +++ b/examples/plugins/backlightControls/backlightcontrols.cpp @@ -0,0 +1,46 @@ +/** + * Example of display backlight control depending on playback. + * To connect the plugin, copy its folder to the src/plugins directory. + */ +#include "backlightcontrols.h" +#include +#include +#include "../../core/options.h" + +Ticker backlightTicker; +backlightControls blc; + +const uint8_t backlightPin = 13; +const uint8_t backlightInitValue = HIGH; +const uint16_t turnBlOffInterval = 120; /* 2 min */ + +void backlightOff(){ + backlightTicker.detach(); + digitalWrite(backlightPin, !backlightInitValue); +} + +backlightControls::backlightControls() { + registerPlugin(); + log_i("Plugin is registered"); +} + +void backlightControls::on_setup(){ + log_i("%s called", __func__ ); + pinMode(backlightPin, OUTPUT); + digitalWrite(backlightPin, backlightInitValue); + backlightTicker.attach(turnBlOffInterval, backlightOff); +} + +void backlightControls::on_track_change(){ + log_i("%s called", __func__ ); + digitalWrite(backlightPin, backlightInitValue); + backlightTicker.detach(); + backlightTicker.attach(turnBlOffInterval, backlightOff); +} + +void backlightControls::on_stop_play(){ + log_i("%s called", __func__ ); + digitalWrite(backlightPin, backlightInitValue); + backlightTicker.detach(); + backlightTicker.attach(turnBlOffInterval, backlightOff); +} diff --git a/examples/plugins/backlightControls/backlightcontrols.h b/examples/plugins/backlightControls/backlightcontrols.h new file mode 100644 index 0000000..81fb244 --- /dev/null +++ b/examples/plugins/backlightControls/backlightcontrols.h @@ -0,0 +1,23 @@ +/** + * Example of display backlight control depending on playback. + * To connect the plugin, copy its folder to the src/plugins directory. + */ +#ifndef BACKLIGHTCONTROLS_H +#define BACKLIGHTCONTROLS_H + +#include "../../pluginsManager/pluginsManager.h" + +class backlightControls : public Plugin { +public: + backlightControls(); +/** + * See src/pluginsManager/pluginsManager.h for available events + */ + void on_setup(); + void on_track_change(); + void on_stop_play(); +}; + + +#endif // BACKLIGHTCONTROLS_H + diff --git a/examples/plugins/backlightcontrols.ino b/examples/plugins/backlightcontrols.ino index cc65875..afe0f22 100644 --- a/examples/plugins/backlightcontrols.ino +++ b/examples/plugins/backlightcontrols.ino @@ -1,3 +1,10 @@ +/* + ******************************************************************************************* + * Attention! + * This method of connecting plugins no longer works and is left here for history. + ******************************************************************************************* + +*/ /************************************************************** Example of display backlight control depending on playback. diff --git a/examples/plugins/deepSleep/deepsleep.cpp b/examples/plugins/deepSleep/deepsleep.cpp new file mode 100644 index 0000000..da3cefa --- /dev/null +++ b/examples/plugins/deepSleep/deepsleep.cpp @@ -0,0 +1,54 @@ +/** + * Example of esp32 deep sleep when playback is stopped. + * To connect the plugin, copy its folder to the src/plugins directory. + */ + +#include "deepsleep.h" +#include +#include +#include "../../core/options.h" +#include "../../core/display.h" + +#define SLEEP_DELAY 60 /* 1 min deep sleep delay */ +#define WAKEUP_PIN ENC_BTNB /* wakeup pin (one of: BTN_XXXX, ENC_BTNB, ENC2_BTNB) */ + /* must be one of: 0,2,4,12,13,14,15,25,26,27,32,33,34,35,36,39 */ +#define WAKEUP_LEVEL LOW /* wakeup level (usually LOW) */ + +Ticker deepSleepTicker; +deepSleep dsleep; + +deepSleep::deepSleep() { + registerPlugin(); + log_i("Plugin is registered"); +} + +void goToSleep(){ + if(BRIGHTNESS_PIN!=255) analogWrite(BRIGHTNESS_PIN, 0); /* BRIGHTNESS_PIN added in v0.7.330 */ + if(display.deepsleep()) { /* if deep sleep is possible */ + esp_deep_sleep_start(); /* go to sleep */ + }else{ /* else */ + deepSleepTicker.detach(); /* detach the timer */ + } +} + +void deepSleep::on_setup(){ /* occurs during loading */ + log_i("%s called", __func__ ); + if(WAKEUP_PIN!=255){ + esp_sleep_enable_ext0_wakeup((gpio_num_t)WAKEUP_PIN, WAKEUP_LEVEL); /* enable wakeup pin */ + deepSleepTicker.attach(SLEEP_DELAY, goToSleep); /* attach to delay */ + } +} + +void deepSleep::on_start_play(){ /* occurs during player is start playing */ + log_i("%s called", __func__ ); + if(WAKEUP_PIN!=255){ + deepSleepTicker.detach(); /* detach the timer */ + } +} + +void deepSleep::on_stop_play(){ /* occurs during player is stop playing */ + log_i("%s called", __func__ ); + if(WAKEUP_PIN!=255){ + deepSleepTicker.attach(SLEEP_DELAY, goToSleep); /* attach to delay */ + } +} diff --git a/examples/plugins/deepSleep/deepsleep.h b/examples/plugins/deepSleep/deepsleep.h new file mode 100644 index 0000000..b984679 --- /dev/null +++ b/examples/plugins/deepSleep/deepsleep.h @@ -0,0 +1,23 @@ +/** + * Example of esp32 deep sleep when playback is stopped. + * To connect the plugin, copy its folder to the src/plugins directory. + */ +#ifndef DEEPSLEEP_H +#define DEEPSLEEP_H + +#include "../../pluginsManager/pluginsManager.h" + +class deepSleep : public Plugin { +public: + deepSleep(); +/** + * See src/pluginsManager/pluginsManager.h for available events + */ + void on_setup(); + void on_start_play(); + void on_stop_play(); +}; + + +#endif // DEEPSLEEP_H + diff --git a/examples/plugins/deepsleep.ino b/examples/plugins/deepsleep.ino index 8fa788a..5a14baa 100644 --- a/examples/plugins/deepsleep.ino +++ b/examples/plugins/deepsleep.ino @@ -1,3 +1,10 @@ +/* + ******************************************************************************************* + * Attention! + * This method of connecting plugins no longer works and is left here for history. + ******************************************************************************************* + +*/ /****************************************************************************************************************** Example of esp32 deep sleep when playback is stopped. diff --git a/examples/plugins/helloWorld/helloworld.cpp b/examples/plugins/helloWorld/helloworld.cpp new file mode 100644 index 0000000..e9e1e5f --- /dev/null +++ b/examples/plugins/helloWorld/helloworld.cpp @@ -0,0 +1,62 @@ +/** + * Example of a plugin. + * To connect the plugin, copy its folder to the src/plugins directory. + */ + +#include "helloworld.h" +#include "../../core/options.h" + +helloWorld hellow; + +helloWorld::helloWorld() { + registerPlugin(); + log_i("Plugin is registered"); +} + +void helloWorld::on_setup(){ + log_i("%s called", __func__ ); +} + +void helloWorld::on_end_setup(){ + log_i("%s called", __func__ ); +} + +void helloWorld::on_connect(){ + log_i("%s called", __func__ ); +} + +void helloWorld::on_start_play(){ + log_i("%s called", __func__ ); +} + +void helloWorld::on_stop_play(){ + log_i("%s called", __func__ ); +} + +void helloWorld::on_track_change(){ + log_i("%s called", __func__ ); +} + +void helloWorld::on_station_change(){ + log_i("%s called", __func__ ); +} + +void helloWorld::on_display_queue(requestParams_t &request, bool& result){ + result = true; + log_i("%s called, type=%d, payload=%d ", __func__ , request.type, request.payload); +} + +void helloWorld::on_display_player(){ + log_i("%s called", __func__ ); +} + +void helloWorld::on_ticker(){ + log_i("%s called", __func__ ); +} + +void helloWorld::on_btn_click(controlEvt_e &btnid){ + log_i("%s called, btnid=%d", __func__ , btnid); +} + + + diff --git a/examples/plugins/helloWorld/helloworld.h b/examples/plugins/helloWorld/helloworld.h new file mode 100644 index 0000000..adf864c --- /dev/null +++ b/examples/plugins/helloWorld/helloworld.h @@ -0,0 +1,31 @@ +/** + * Example of a plugin. + * To connect the plugin, copy its folder to the src/plugins directory. + */ +#ifndef HELLOWORLD_H +#define HELLOWORLD_H + +#include "../../pluginsManager/pluginsManager.h" + +class helloWorld : public Plugin { +public: + helloWorld(); +/** + * See src/pluginsManager/pluginsManager.h for available events + */ + void on_setup(); + void on_end_setup(); + void on_connect(); + void on_start_play(); + void on_stop_play(); + void on_track_change(); + void on_station_change(); + void on_display_queue(requestParams_t &request, bool& result); + void on_display_player(); + void on_ticker(); + void on_btn_click(controlEvt_e &btnid); +}; + + +#endif // HELLOWORLD_H + diff --git a/yoRadio/src/AsyncWebServer/AsyncWebSocket.cpp b/yoRadio/src/AsyncWebServer/AsyncWebSocket.cpp index 766c1b9..f85952b 100644 --- a/yoRadio/src/AsyncWebServer/AsyncWebSocket.cpp +++ b/yoRadio/src/AsyncWebServer/AsyncWebSocket.cpp @@ -833,11 +833,7 @@ void AsyncWebSocketClient::binary(AsyncWebSocketMessageBuffer * buffer) IPAddress AsyncWebSocketClient::remoteIP() { if(!_client) { -#if ESP_IDF_VERSION_MAJOR < 5 - return IPAddress(0U); -#else - return IPAddress(0ul); -#endif + return IPAddress((uint32_t)0); } return _client->remoteIP(); } diff --git a/yoRadio/src/core/common.h b/yoRadio/src/core/common.h new file mode 100644 index 0000000..08a4d1d --- /dev/null +++ b/yoRadio/src/core/common.h @@ -0,0 +1,18 @@ +#ifndef COMMON_H +#define COMMON_H + +enum displayMode_e { PLAYER, VOL, STATIONS, NUMBERS, LOST, UPDATING, INFO, SETTINGS, TIMEZONE, WIFI, CLEAR, SLEEPING, SDCHANGE }; +enum pages_e : uint8_t { PG_PLAYER=0, PG_DIALOG=1, PG_PLAYLIST=2 }; + +enum displayRequestType_e { BOOTSTRING, NEWMODE, CLOCK, NEWTITLE, NEWSTATION, NEXTSTATION, DRAWPLAYLIST, DRAWVOL, DBITRATE, AUDIOINFO, SHOWVUMETER, DSPRSSI, SHOWWEATHER, NEWWEATHER, PSTOP, PSTART, DSP_START, WAITFORSD, SDFILEINDEX, NEWIP, NOPE }; +struct requestParams_t +{ + displayRequestType_e type; + int payload; +}; + +enum controlEvt_e { EVT_NONE=255, EVT_BTNLEFT=0, EVT_BTNCENTER=1, EVT_BTNRIGHT=2, EVT_ENCBTNB=3, EVT_BTNUP=4, EVT_BTNDOWN=5, EVT_ENC2BTNB=6, EVT_BTNMODE=7 }; + + + +#endif diff --git a/yoRadio/src/core/config.cpp b/yoRadio/src/core/config.cpp index f9f2244..312c0ef 100644 --- a/yoRadio/src/core/config.cpp +++ b/yoRadio/src/core/config.cpp @@ -745,9 +745,11 @@ void Config::doSleep(){ #ifdef USE_NEXTION nextion.sleep(); #endif +#if !defined(ARDUINO_ESP32C3_DEV) if(WAKE_PIN!=255) esp_sleep_enable_ext0_wakeup((gpio_num_t)WAKE_PIN, LOW); esp_sleep_enable_timer_wakeup(config.sleepfor * 60 * 1000000ULL); esp_deep_sleep_start(); +#endif } void Config::doSleepW(){ @@ -756,8 +758,10 @@ void Config::doSleepW(){ #ifdef USE_NEXTION nextion.sleep(); #endif +#if !defined(ARDUINO_ESP32C3_DEV) if(WAKE_PIN!=255) esp_sleep_enable_ext0_wakeup((gpio_num_t)WAKE_PIN, LOW); esp_deep_sleep_start(); +#endif } void Config::sleepForAfter(uint16_t sf, uint16_t sa){ diff --git a/yoRadio/src/core/config.h b/yoRadio/src/core/config.h index 5101bcc..13811e3 100644 --- a/yoRadio/src/core/config.h +++ b/yoRadio/src/core/config.h @@ -7,6 +7,7 @@ //#include "SD.h" #include "options.h" #include "rtcsupport.h" +#include "../pluginsManager/pluginsManager.h" #define EEPROM_SIZE 768 #define EEPROM_START 500 diff --git a/yoRadio/src/core/controls.cpp b/yoRadio/src/core/controls.cpp index 5d828cd..ed8d435 100644 --- a/yoRadio/src/core/controls.cpp +++ b/yoRadio/src/core/controls.cpp @@ -204,10 +204,10 @@ void encoder2Loop() { #if IR_PIN!=255 void irBlink() { - if(LED_BUILTIN==255) return; + if(REAL_LEDBUILTIN==255) return; if (player.status() == STOPPED) { for (uint8_t i = 0; i < 7; i++) { - digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); + digitalWrite(REAL_LEDBUILTIN, !digitalRead(REAL_LEDBUILTIN)); delay(100); } } @@ -465,8 +465,10 @@ void controlsEvent(bool toRight, int8_t volDelta) { void onBtnClick(int id) { bool passBnCenter = (controlEvt_e)id==EVT_BTNCENTER || (controlEvt_e)id==EVT_ENCBTNB || (controlEvt_e)id==EVT_ENC2BTNB; + controlEvt_e btnid = static_cast(id); + pm.on_btn_click(btnid); if (network.status != CONNECTED && network.status!=SDREADY && (controlEvt_e)id!=EVT_BTNMODE && !passBnCenter) return; - switch ((controlEvt_e)id) { + switch (btnid) { case EVT_BTNLEFT: { controlsEvent(false); break; diff --git a/yoRadio/src/core/controls.h b/yoRadio/src/core/controls.h index f096ff6..bf2919d 100644 --- a/yoRadio/src/core/controls.h +++ b/yoRadio/src/core/controls.h @@ -1,13 +1,12 @@ #ifndef controls_h #define controls_h #include "options.h" +#include "common.h" #if (ENC_BTNL!=255 && ENC_BTNR!=255) || (ENC2_BTNL!=255 && ENC2_BTNR!=255) #include "../yoEncoder/yoEncoder.h" #endif -enum controlEvt_e { EVT_BTNLEFT, EVT_BTNCENTER, EVT_BTNRIGHT, EVT_ENCBTNB, EVT_BTNUP, EVT_BTNDOWN, EVT_ENC2BTNB, EVT_BTNMODE }; - //enum tsDirection_e { TSD_STAY, TSD_LEFT, TSD_RIGHT, TSD_UP, TSD_DOWN, TDS_REQUEST }; #if IR_PIN!=255 diff --git a/yoRadio/src/core/display.cpp b/yoRadio/src/core/display.cpp index ee08fe5..4fdc288 100644 --- a/yoRadio/src/core/display.cpp +++ b/yoRadio/src/core/display.cpp @@ -19,7 +19,7 @@ DspCore dsp; Page *pages[] = { new Page(), new Page(), new Page() }; #ifndef DSQ_SEND_DELAY - #define DSQ_SEND_DELAY portMAX_DELAY + #define DSQ_SEND_DELAY portMAX_DELAY #endif #ifndef CORE_STACK_SIZE @@ -93,7 +93,7 @@ void Display::_buildPager(){ _plcurrent.init("*", playlistConf, config.theme.plcurrent, config.theme.plcurrentbg); #endif #if !defined(DSP_LCD) - _plcurrent.moveTo({TFT_FRAMEWDT, (uint16_t)(dsp.plYStart+dsp.plCurrentPos*dsp.plItemHeight), (int16_t)playlistConf.width}); + _plcurrent.moveTo({TFT_FRAMEWDT, (uint16_t)(dsp.plYStart+dsp.plCurrentPos*dsp.plItemHeight), (int16_t)playlistConf.width}); #endif #ifndef HIDE_TITLE2 _title2 = new ScrollWidget("*", title2Conf, config.theme.title2, config.theme.background); @@ -240,6 +240,7 @@ void Display::_start() { _station(); _time(false); _bootStep = 2; + pm.on_display_player(); } void Display::_showDialog(const char *title){ @@ -266,9 +267,9 @@ void Display::_swichMode(displayMode_e newmode) { _mode = newmode; dsp.setScrollId(NULL); if (newmode == PLAYER) { - #ifdef DSP_LCD - dsp.clearDsp(); - #endif + #ifdef DSP_LCD + dsp.clearDsp(); + #endif numOfNextStation = 0; _returnTicker.detach(); #ifdef META_MOVE @@ -278,6 +279,7 @@ void Display::_swichMode(displayMode_e newmode) { _meta.setText(config.station.name); _nums.setText(""); _pager.setPage( pages[PG_PLAYER]); + pm.on_display_player(); } if (newmode == VOL) { #ifndef HIDE_IP @@ -299,6 +301,7 @@ void Display::_swichMode(displayMode_e newmode) { currentPlItem = config.store.lastStation; _drawPlaylist(); } + } void Display::resetQueue(){ @@ -368,82 +371,85 @@ void Display::loop() { #endif requestParams_t request; if(xQueueReceive(displayQueue, &request, DSP_QUEUE_TICKS)){ - switch (request.type){ - case NEWMODE: _swichMode((displayMode_e)request.payload); break; - case CLOCK: - if(_mode==PLAYER) _time(); - /*#ifdef USE_NEXTION - if(_mode==TIMEZONE) nextion.localTime(network.timeinfo); - if(_mode==INFO) nextion.rssi(); - #endif*/ - break; - case NEWTITLE: _title(); break; - case NEWSTATION: _station(); break; - case NEXTSTATION: _drawNextStationNum(request.payload); break; - case DRAWPLAYLIST: _drawPlaylist(); break; - case DRAWVOL: _volume(); break; - case DBITRATE: { - char buf[20]; - snprintf(buf, 20, bitrateFmt, config.station.bitrate); - if(_bitrate) { _bitrate->setText(config.station.bitrate==0?"":buf); } - if(_fullbitrate) { - _fullbitrate->setBitrate(config.station.bitrate); - _fullbitrate->setFormat(config.configFmt); - } + bool pm_result; + pm.on_display_queue(request, pm_result); + if(pm_result) + switch (request.type){ + case NEWMODE: _swichMode((displayMode_e)request.payload); break; + case CLOCK: + if(_mode==PLAYER) _time(); + /*#ifdef USE_NEXTION + if(_mode==TIMEZONE) nextion.localTime(network.timeinfo); + if(_mode==INFO) nextion.rssi(); + #endif*/ + break; + case NEWTITLE: _title(); break; + case NEWSTATION: _station(); break; + case NEXTSTATION: _drawNextStationNum(request.payload); break; + case DRAWPLAYLIST: _drawPlaylist(); break; + case DRAWVOL: _volume(); break; + case DBITRATE: { + char buf[20]; + snprintf(buf, 20, bitrateFmt, config.station.bitrate); + if(_bitrate) { _bitrate->setText(config.station.bitrate==0?"":buf); } + if(_fullbitrate) { + _fullbitrate->setBitrate(config.station.bitrate); + _fullbitrate->setFormat(config.configFmt); + } + } + break; + case AUDIOINFO: if(_heapbar) { _heapbar->lock(!config.store.audioinfo); _heapbar->setValue(player.inBufferFilled()); } break; + case SHOWVUMETER: { + if(_vuwidget){ + _vuwidget->lock(!config.store.vumeter); + _layoutChange(player.isRunning()); + } + break; } - break; - case AUDIOINFO: if(_heapbar) { _heapbar->lock(!config.store.audioinfo); _heapbar->setValue(player.inBufferFilled()); } break; - case SHOWVUMETER: { - if(_vuwidget){ - _vuwidget->lock(!config.store.vumeter); - _layoutChange(player.isRunning()); + case SHOWWEATHER: { + if(_weather) _weather->lock(!config.store.showweather); + if(!config.store.showweather){ + #ifndef HIDE_IP + if(_volip) _volip->setText(WiFi.localIP().toString().c_str(), iptxtFmt); + #endif + }else{ + if(_weather) _weather->setText(const_getWeather); + } + break; } - break; - } - case SHOWWEATHER: { - if(_weather) _weather->lock(!config.store.showweather); - if(!config.store.showweather){ + case NEWWEATHER: { + if(_weather && network.weatherBuf) _weather->setText(network.weatherBuf); + break; + } + case BOOTSTRING: { + if(_bootstring) _bootstring->setText(config.ssids[request.payload].ssid, bootstrFmt); + /*#ifdef USE_NEXTION + char buf[50]; + snprintf(buf, 50, bootstrFmt, config.ssids[request.payload].ssid); + nextion.bootString(buf); + #endif*/ + break; + } + case WAITFORSD: { + if(_bootstring) _bootstring->setText(const_waitForSD); + break; + } + case SDFILEINDEX: { + if(_mode == SDCHANGE) _nums.setText(request.payload, "%d"); + break; + } + case DSPRSSI: if(_rssi){ _setRSSI(request.payload); } if (_heapbar && config.store.audioinfo) _heapbar->setValue(player.isRunning()?player.inBufferFilled():0); break; + case PSTART: _layoutChange(true); break; + case PSTOP: _layoutChange(false); break; + case DSP_START: _start(); break; + case NEWIP: { #ifndef HIDE_IP - if(_volip) _volip->setText(WiFi.localIP().toString().c_str(), iptxtFmt); + if(_volip) _volip->setText(WiFi.localIP().toString().c_str(), iptxtFmt); #endif - }else{ - if(_weather) _weather->setText(const_getWeather); + break; } - break; + default: break; } - case NEWWEATHER: { - if(_weather && network.weatherBuf) _weather->setText(network.weatherBuf); - break; - } - case BOOTSTRING: { - if(_bootstring) _bootstring->setText(config.ssids[request.payload].ssid, bootstrFmt); - /*#ifdef USE_NEXTION - char buf[50]; - snprintf(buf, 50, bootstrFmt, config.ssids[request.payload].ssid); - nextion.bootString(buf); - #endif*/ - break; - } - case WAITFORSD: { - if(_bootstring) _bootstring->setText(const_waitForSD); - break; - } - case SDFILEINDEX: { - if(_mode == SDCHANGE) _nums.setText(request.payload, "%d"); - break; - } - case DSPRSSI: if(_rssi){ _setRSSI(request.payload); } if (_heapbar && config.store.audioinfo) _heapbar->setValue(player.isRunning()?player.inBufferFilled():0); break; - case PSTART: _layoutChange(true); break; - case PSTOP: _layoutChange(false); break; - case DSP_START: _start(); break; - case NEWIP: { - #ifndef HIDE_IP - if(_volip) _volip->setText(WiFi.localIP().toString().c_str(), iptxtFmt); - #endif - break; - } - default: break; - } } dsp.loop(); } @@ -502,6 +508,7 @@ void Display::_title() { if(_title2) _title2->setText(""); } if (player_on_track_change) player_on_track_change(); + pm.on_track_change(); } void Display::_time(bool redraw) { diff --git a/yoRadio/src/core/display.h b/yoRadio/src/core/display.h index c6133c0..6d2247a 100644 --- a/yoRadio/src/core/display.h +++ b/yoRadio/src/core/display.h @@ -5,19 +5,10 @@ #include "Arduino.h" #include #include "config.h" - +#include "common.h" #include "../displays/dspcore.h" -enum displayMode_e { PLAYER, VOL, STATIONS, NUMBERS, LOST, UPDATING, INFO, SETTINGS, TIMEZONE, WIFI, CLEAR, SLEEPING, SDCHANGE }; -enum pages_e : uint8_t { PG_PLAYER=0, PG_DIALOG=1, PG_PLAYLIST=2 }; -//enum dialogType_e : uint8_t { DG_NONE=0, DG_VOLUME=1, DG_LOST=2, DG_UPDATING=3, DG_NEXTION=4 }; -enum displayRequestType_e { BOOTSTRING, NEWMODE, CLOCK, NEWTITLE, NEWSTATION, NEXTSTATION, DRAWPLAYLIST, DRAWVOL, DBITRATE, AUDIOINFO, SHOWVUMETER, DSPRSSI, SHOWWEATHER, NEWWEATHER, PSTOP, PSTART, DSP_START, WAITFORSD, SDFILEINDEX, NEWIP }; -struct requestParams_t -{ - displayRequestType_e type; - int payload; -}; #if NEXTION_RX!=255 && NEXTION_TX!=255 #define USE_NEXTION diff --git a/yoRadio/src/core/network.cpp b/yoRadio/src/core/network.cpp index 93a8776..a6e6a76 100644 --- a/yoRadio/src/core/network.cpp +++ b/yoRadio/src/core/network.cpp @@ -21,7 +21,7 @@ void doSync(void * pvParameters); void ticks() { if(!display.ready()) return; //waiting for SD is ready - + pm.on_ticker(); static const uint16_t weatherSyncInterval=1800; //static const uint16_t weatherSyncIntervalFail=10; #if RTCSUPPORTED @@ -121,7 +121,7 @@ bool MyNetwork::wifiBegin(bool silent){ while (WiFi.status() != WL_CONNECTED) { if(!silent) Serial.print("."); delay(500); - if(LED_BUILTIN!=255 && !silent) digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); + if(REAL_LEDBUILTIN!=255 && !silent) digitalWrite(REAL_LEDBUILTIN, !digitalRead(REAL_LEDBUILTIN)); errcnt++; if (errcnt > WIFI_ATTEMPTS) { errcnt = 0; @@ -182,7 +182,7 @@ void MyNetwork::begin() { } Serial.println("##[BOOT]#\tdone"); - if(LED_BUILTIN!=255) digitalWrite(LED_BUILTIN, LOW); + if(REAL_LEDBUILTIN!=255) digitalWrite(REAL_LEDBUILTIN, LOW); #if RTCSUPPORTED rtc.getTime(&network.timeinfo); @@ -191,6 +191,7 @@ void MyNetwork::begin() { #endif ctimer.attach(1, ticks); if (network_on_connect) network_on_connect(); + pm.on_connect(); } void MyNetwork::setWifiParams(){ diff --git a/yoRadio/src/core/options.h b/yoRadio/src/core/options.h index 5b82eca..32c8840 100644 --- a/yoRadio/src/core/options.h +++ b/yoRadio/src/core/options.h @@ -1,7 +1,7 @@ #ifndef options_h #define options_h -#define YOVERSION "0.9.342b" +#define YOVERSION "0.9.350" /******************************************************* DO NOT EDIT THIS FILE. @@ -244,11 +244,11 @@ The connection tables are located here https://github.com/e2002/yoradio#connecti /* RTC */ #define RTC_MODULE_UNDEFINED 0 -#define DS3231 1 -#define DS1307 2 +#define DS3231 1 +#define DS1307 2 #ifndef RTC_MODULE - #define RTC_MODULE RTC_MODULE_UNDEFINED /* DS3231 or DS1307 */ + #define RTC_MODULE RTC_MODULE_UNDEFINED /* DS3231 or DS1307 */ #endif #ifndef RTC_SDA #define RTC_SDA 255 @@ -258,10 +258,24 @@ The connection tables are located here https://github.com/e2002/yoradio#connecti #endif /* ESP DEVBOARD */ -#ifndef ARDUINO_ESP32S3_DEV +#if defined(ARDUINO_ESP32S3_DEV) || defined(ARDUINO_ESP32C3_DEV) + #define ESP_S3C3 1 + #ifndef USE_BUILTIN_LED + #define USE_BUILTIN_LED true + #endif + #ifndef LED_BUILTIN_S3 + #define LED_BUILTIN_S3 255 + #endif + #if USE_BUILTIN_LED + #define REAL_LEDBUILTIN LED_BUILTIN + #else + #define REAL_LEDBUILTIN LED_BUILTIN_S3 + #endif +#else #ifndef LED_BUILTIN #define LED_BUILTIN 255 #endif + #define REAL_LEDBUILTIN LED_BUILTIN #endif /* Other settings. You can overwrite them in the myoptions.h file */ #ifndef MUTE_PIN @@ -325,7 +339,7 @@ The connection tables are located here https://github.com/e2002/yoradio#connecti #define SD_AUTOPLAY true // auto play from SD card when inserted #endif #ifndef SD_MAX_LEVELS - #define SD_MAX_LEVELS 3 // search depth for files on the SD card + #define SD_MAX_LEVELS 3 // search depth for files on the SD card #endif /* *** ST7735 display submodel *** diff --git a/yoRadio/src/core/optionschecker.h b/yoRadio/src/core/optionschecker.h index 1cd88b8..f520c9d 100644 --- a/yoRadio/src/core/optionschecker.h +++ b/yoRadio/src/core/optionschecker.h @@ -1,11 +1,11 @@ #ifndef optionschecker_h #define optionschecker_h -#if LED_BUILTIN==TFT_RST +#if REAL_LEDBUILTIN==TFT_RST # error LED_BUILTIN IS THE SAME AS TFT_RST. Check it in myoptions.h #endif -#if LED_BUILTIN==VS1053_RST +#if REAL_LEDBUILTIN==VS1053_RST # error LED_BUILTIN IS THE SAME AS VS1053_RST. Check it in myoptions.h #endif @@ -13,7 +13,7 @@ # error YOU MUST CHOOSE BETWEEN I2S DAC AND VS1053 BY DISABLING THE SECOND MODULE IN THE myoptions.h #endif -#if !(defined(ARDUINO_ESP32_DEV) || defined(ARDUINO_ESP32S3_DEV)) +#if !(defined(ARDUINO_ESP32_DEV) || defined(ARDUINO_ESP32S3_DEV) || defined(ARDUINO_ESP32C3_DEV)) # error ONLY MODULES "ESP32 Dev Module", "ESP32 Wrover Module" AND "ESP32 S3 Dev Module" ARE SUPPORTED. PLEASE SELECT ONE OF THEM IN THE MENU >> TOOLS >> BOARD #endif diff --git a/yoRadio/src/core/player.cpp b/yoRadio/src/core/player.cpp index 400e796..a6dd524 100644 --- a/yoRadio/src/core/player.cpp +++ b/yoRadio/src/core/player.cpp @@ -109,6 +109,7 @@ void Player::_stop(bool alreadyStopped){ if(!alreadyStopped) stopSong(); if(!lockOutput) stopInfo(); if (player_on_stop_play) player_on_stop_play(); + pm.on_stop_play(); } void Player::initHeaders(const char *file) { @@ -136,6 +137,7 @@ void Player::loop() { } _play((uint16_t)abs(requestP.payload)); if (player_on_station_change) player_on_station_change(); + pm.on_station_change(); break; } case PR_VOL: { @@ -168,7 +170,7 @@ void Player::loop() { } void Player::setOutputPins(bool isPlaying) { - if(LED_BUILTIN!=255) digitalWrite(LED_BUILTIN, LED_INVERT?!isPlaying:isPlaying); + if(REAL_LEDBUILTIN!=255) digitalWrite(REAL_LEDBUILTIN, LED_INVERT?!isPlaying:isPlaying); bool _ml = MUTE_LOCK?!MUTE_VAL:(isPlaying?!MUTE_VAL:MUTE_VAL); if(MUTE_PIN!=255) digitalWrite(MUTE_PIN, _ml); } @@ -215,6 +217,7 @@ void Player::_play(uint16_t stationId) { setOutputPins(true); display.putRequest(PSTART); if (player_on_start_play) player_on_start_play(); + pm.on_start_play(); }else{ telnet.printf("##ERROR#:\tError connecting to %s\n", config.station.url); SET_PLAY_ERROR("Error connecting to %s", config.station.url); @@ -239,6 +242,7 @@ void Player::browseUrl(){ setOutputPins(true); display.putRequest(PSTART); if (player_on_start_play) player_on_start_play(); + pm.on_start_play(); }else{ telnet.printf("##ERROR#:\tError connecting to %s\n", burl); SET_PLAY_ERROR("Error connecting to %s", burl); diff --git a/yoRadio/src/plugins/README.md b/yoRadio/src/plugins/README.md new file mode 100644 index 0000000..d5871e7 --- /dev/null +++ b/yoRadio/src/plugins/README.md @@ -0,0 +1 @@ +### Directory for placing your plugins. diff --git a/yoRadio/src/pluginsManager/README.md b/yoRadio/src/pluginsManager/README.md new file mode 100644 index 0000000..e81e6e7 --- /dev/null +++ b/yoRadio/src/pluginsManager/README.md @@ -0,0 +1,84 @@ +# Plugin Class + +The `Plugin` class serves as a base class for creating plugins that hook into various system events. +To use it, inherit from `Plugin` and override the necessary virtual methods. +Examples of plugin usage can be found in the `examples/plugins` folder. +Place your new class in the `src/plugins/` directory +--- + +## Public Methods + +### Constructor +`Plugin();` +Initializes the plugin when an instance is created. + +--- + +### Destructor +`virtual ~Plugin();` +Cleans up resources when the plugin is destroyed. + +--- + +## Virtual Methods (Override in derived classes) + +### `on_setup()` +- **Description:** Called at the beginning of the sketch setup process. +- **Location:** `setup()`, `yoRadio.ino`. + +### `on_end_setup()` +- **Description:** Called at the end of the sketch setup process. +- **Location:** `setup()`, `yoRadio.ino`. + +### `on_connect()` +- **Description:** Triggered after a successful network connection. +- **Location:** `MyNetwork::begin()`, `yoRadio/src/core/network.cpp`. + +### `on_start_play()` +- **Description:** Triggered when playback starts. +- **Location:** + - `Player::_play(uint16_t stationId)`, `yoRadio/src/core/player.cpp`. + - `Player::browseUrl()`, `yoRadio/src/core/player.cpp`. + +### `on_stop_play()` +- **Description:** Triggered when playback stops. +- **Location:** `Player::_stop(bool alreadyStopped)`, `yoRadio/src/core/player.cpp`. + +### `on_track_change()` +- **Description:** Triggered when the current track changes. +- **Location:** `Display::_title()`, `yoRadio/src/core/display.cpp`. + +### `on_station_change()` +- **Description:** Triggered when the current station changes. +- **Location:** `Player::loop()`, `yoRadio/src/core/player.cpp`. + +### `on_display_queue(requestParams_t &request, bool &result)` +- **Description:** Triggered when a command is dequeued for the display. +- **Parameters:** + - `request`: Reference to a `requestParams_t` structure (defined in `yoRadio/src/core/common.h`). + - `result`: Set to `false` to stop queue processing or `true` to continue. +- **Location:** `Display::loop()`, `yoRadio/src/core/display.cpp`. + +### `on_display_player()` +- **Description:** Triggered when the player UI is displayed. +- **Location:** + - `Display::_start()`, `display.cpp`. + - `Display::_swichMode(displayMode_e newmode)`, `yoRadio/src/core/display.cpp`. + +### `on_ticker()` +- **Description:** Triggered once every second. +- **Location:** `ticks()`, `yoRadio/src/core/network.cpp`. + +### `on_btn_click(controlEvt_e &btnid)` +- **Description:** Triggered when a button is clicked. +- **Parameters:** + - `btnid`: Reference to the button ID (defined in `yoRadio/src/core/common.h`). +- **Location:** `onBtnClick(int id)`, `yoRadio/src/core/controls.cpp`. + +--- + +## Protected Methods + +### `registerPlugin()` +- **Description:** Registers the plugin with the plugin manager. + diff --git a/yoRadio/src/pluginsManager/pluginsManager.cpp b/yoRadio/src/pluginsManager/pluginsManager.cpp new file mode 100644 index 0000000..871e1bf --- /dev/null +++ b/yoRadio/src/pluginsManager/pluginsManager.cpp @@ -0,0 +1,26 @@ +#include "pluginsManager.h" + +pluginsManager pm; + +Plugin::Plugin() { +} + +void Plugin::registerPlugin() { + pm.add(this); +} + +void pluginsManager::add(Plugin* plugin) { + plugins.push_back(plugin); +} + +size_t pluginsManager::count() const { + return plugins.size(); +} + +Plugin* pluginsManager::get(size_t index) { + if (index < plugins.size()) { + return plugins[index]; + } + return nullptr; +} + diff --git a/yoRadio/src/pluginsManager/pluginsManager.h b/yoRadio/src/pluginsManager/pluginsManager.h new file mode 100644 index 0000000..758e443 --- /dev/null +++ b/yoRadio/src/pluginsManager/pluginsManager.h @@ -0,0 +1,149 @@ +#ifndef PLUGINSMANAGER_H +#define PLUGINSMANAGER_H + +#include +#include +#include +#include "../core/common.h" + +class pluginsManager; + +/** + * Plugin Class + * + * Base class for creating plugins that hook into various system events. + * To use it, inherit from Plugin and override the necessary virtual methods. + * Examples of plugin usage can be found in the `examples/plugins` folder. + * Place your new class in the src/plugins/ directory + */ +class Plugin { +public: + /** + * Constructor + * Initializes the plugin when an instance is created. + */ + Plugin(); + + /** + * Destructor + * Cleans up resources when the plugin is destroyed. + */ + virtual ~Plugin() {} + + // Virtual Methods (Override in derived classes): + + /** + * Called at the beginning of the sketch setup process. + * Location: setup(), yoRadio.ino + */ + virtual void on_setup() __attribute__((weak)) {} + + /** + * Called at the end of the sketch setup process. + * Location: setup(), yoRadio.ino + */ + virtual void on_end_setup() __attribute__((weak)) {} + + /** + * Triggered after a successful network connection. + * Location: MyNetwork::begin(), yoRadio/src/core/network.cpp + */ + virtual void on_connect() __attribute__((weak)) {} + + /** + * Triggered when playback starts. + * Location: + * - Player::_play(uint16_t stationId), yoRadio/src/core/player.cpp + * - Player::browseUrl(), yoRadio/src/core/player.cpp + */ + virtual void on_start_play() __attribute__((weak)) {} + + /** + * Triggered when playback stops. + * Location: Player::_stop(bool alreadyStopped), yoRadio/src/core/player.cpp + */ + virtual void on_stop_play() __attribute__((weak)) {} + + /** + * Triggered when the current track changes. + * Location: Display::_title(), yoRadio/src/core/display.cpp + */ + virtual void on_track_change() __attribute__((weak)) {} + + /** + * Triggered when the current station changes. + * Location: Player::loop(), yoRadio/src/core/player.cpp + */ + virtual void on_station_change() __attribute__((weak)) {} + + /** + * Triggered when a command is dequeued for the display. + * Parameters: + * - request: Reference to a requestParams_t structure (defined in yoRadio/src/core/common.h). + * - result: Set to `false` to stop queue processing or `true` to continue. + * Location: Display::loop(), yoRadio/src/core/display.cpp + */ + virtual void on_display_queue(requestParams_t &request, bool &result) __attribute__((weak)) {} + + /** + * Triggered when the player UI is displayed. + * Location: + * - Display::_start(), yoRadio/src/core/display.cpp + * - Display::_swichMode(displayMode_e newmode), yoRadio/src/core/display.cpp + */ + virtual void on_display_player() __attribute__((weak)) {} + + /** + * Triggered once every second. + * Location: ticks(), yoRadio/src/core/network.cpp + */ + virtual void on_ticker() __attribute__((weak)) {} + + /** + * Triggered when a button is clicked. + * Parameters: + * - btnid: Reference to the button ID (defined in yoRadio/src/core/common.h). + * Location: onBtnClick(int id), yoRadio/src/core/controls.cpp + */ + virtual void on_btn_click(controlEvt_e &btnid) __attribute__((weak)) {} + +protected: + /** + * Registers the plugin with the plugin manager. + */ + void registerPlugin(); +}; + +class pluginsManager { +public: + void add(Plugin* plugin); + size_t count() const; + Plugin* get(size_t index); + requestParams_t *request; + template + void call_event(Func&& func, Args&&... args){ + for (auto* plugin : plugins) { + if (plugin) { + (plugin->*func)(std::forward(args)...); + } + } + } + void on_setup(){ call_event(&Plugin::on_setup); } + void on_end_setup(){ call_event(&Plugin::on_end_setup); } + void on_connect(){ call_event(&Plugin::on_connect); } + void on_start_play(){ call_event(&Plugin::on_start_play); } + void on_stop_play(){ call_event(&Plugin::on_stop_play); } + void on_track_change(){ call_event(&Plugin::on_track_change); } + void on_station_change(){ call_event(&Plugin::on_station_change); } + void on_display_queue(requestParams_t &request, bool& result){ call_event(&Plugin::on_display_queue, request, result); } + void on_display_player(){ call_event(&Plugin::on_display_player); } + void on_ticker(){ call_event(&Plugin::on_ticker); } + void on_btn_click(controlEvt_e &btnid){ call_event(&Plugin::on_btn_click, btnid); } +private: + std::vector plugins; +}; + +extern pluginsManager pm; + +#endif // PLUGINSMANAGER_H + diff --git a/yoRadio/yoRadio.ino b/yoRadio/yoRadio.ino index 3baf5b8..f77ad36 100644 --- a/yoRadio/yoRadio.ino +++ b/yoRadio/yoRadio.ino @@ -31,8 +31,9 @@ extern __attribute__((weak)) void yoradio_on_setup(); void setup() { Serial.begin(115200); - if(LED_BUILTIN!=255) pinMode(LED_BUILTIN, OUTPUT); + if(REAL_LEDBUILTIN!=255) pinMode(REAL_LEDBUILTIN, OUTPUT); if (yoradio_on_setup) yoradio_on_setup(); + pm.on_setup(); config.init(); display.init(); player.init(); @@ -60,6 +61,7 @@ void setup() { if (config.getMode()==PM_SDCARD) player.initHeaders(config.station.url); player.lockOutput=false; if (config.store.smartstart == 1) player.sendCommand({PR_PLAY, config.store.lastStation}); + pm.on_end_setup(); } void loop() {