diff --git a/README.md b/README.md index bd47c02..fdf0f4a 100644 --- a/README.md +++ b/README.md @@ -198,13 +198,20 @@ download _http://\/data/playlist.csv_ and _http://\/data --- ## Version history +#### 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 MUTE_PIN to be able to control the audio output +- fixed js/html bugs (a [full update](#update) is required) + #### v0.4.298 -- fixed playlist scrollbar in Chrome +- fixed playlist scrollbar in Chrome (required [full update](#update)) #### v0.4.297 - fix _"Could not decode a text frame as UTF-8"_ websocket error _//Thanks for [Verholazila](https://4pda.to/forum/index.php?s=&showtopic=1010378&view=findpost&p=113551446)_ - fix display of non-latin characters in the web interface -- fix css in Chrome +- fix css in Chrome (a [full update](#update) is required) #### v0.4.293 - IR repeat fix diff --git a/yoRadio/controls.cpp b/yoRadio/controls.cpp index c7050d0..7929844 100644 --- a/yoRadio/controls.cpp +++ b/yoRadio/controls.cpp @@ -1,3 +1,4 @@ +#include "Arduino.h" #include "controls.h" #include "options.h" #include "config.h" @@ -34,7 +35,7 @@ OneButton btnright(BTN_RIGHT, true, BTN_INTERNALPULLUP); #include #include -byte irVolRepeat=0; +byte irVolRepeat = 0; const uint16_t kCaptureBufferSize = 1024; const uint8_t kTimeout = IR_TIMEOUT; const uint16_t kMinUnknownSize = 12; @@ -71,6 +72,7 @@ void initControls() { btnright.attachDoubleClick(onRightDoubleClick); #endif #if IR_PIN!=255 + pinMode(IR_PIN, INPUT); assert(irutils::lowLevelSanityCheck() == 0); #if DECODE_HASH irrecv.setUnknownThreshold(kMinUnknownSize); @@ -112,9 +114,30 @@ void encoderLoop() { #endif #if IR_PIN!=255 + +void irBlink() { + if (player.mode == STOPPED) { + for(byte i=0; i<7; i++) { + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); + delay(100); + } + } +} + +void irNum(byte num) { + uint16_t s; + if (display.numOfNextStation == 0 && num == 0) return; + if (display.mode == PLAYER) display.swichMode(NUMBERS); + if (display.numOfNextStation > UINT16_MAX / 10) return; + s = display.numOfNextStation * 10 + num; + if (s > config.store.countStation) return; + display.numOfNextStation = s; + display.drawNextStationNum(s); +} + void irLoop() { if (irrecv.decode(&irResults)) { - if(IR_DEBUG) { + if (IR_DEBUG) { Serial.print(resultToHumanReadableBasic(&irResults)); return; } @@ -133,6 +156,13 @@ void irLoop() { } switch (irResults.value) { case IR_CODE_PLAY: { + irBlink(); + if (display.mode == NUMBERS) { + display.swichMode(PLAYER); + player.play(display.numOfNextStation); + display.numOfNextStation = 0; + break; + } onEncClick(); break; } @@ -155,15 +185,64 @@ void irLoop() { break; } case IR_CODE_HASH: { + if (display.mode == NUMBERS) { + display.swichMode(PLAYER); + display.numOfNextStation = 0; + break; + } display.swichMode(display.mode == PLAYER ? STATIONS : PLAYER); break; } + case IR_CODE_NUM0: { + irNum(0); + break; + } + case IR_CODE_NUM1: { + irNum(1); + break; + } + case IR_CODE_NUM2: { + irNum(2); + break; + } + case IR_CODE_NUM3: { + irNum(3); + break; + } + case IR_CODE_NUM4: { + irNum(4); + break; + } + case IR_CODE_NUM5: { + irNum(5); + break; + } + case IR_CODE_NUM6: { + irNum(6); + break; + } + case IR_CODE_NUM7: { + irNum(7); + break; + } + case IR_CODE_NUM8: { + irNum(8); + break; + } + case IR_CODE_NUM9: { + irNum(9); + break; + } } } } #endif void onEncClick() { + if (display.mode == NUMBERS) { + display.numOfNextStation = 0; + display.swichMode(PLAYER); + } if (display.mode == PLAYER) { player.toggle(); } @@ -183,6 +262,10 @@ void onEncLPStart() { } void controlsEvent(bool toRight) { + if (display.mode == NUMBERS) { + display.numOfNextStation = 0; + display.swichMode(PLAYER); + } if (display.mode != STATIONS) { display.swichMode(VOL); player.stepVol(toRight); diff --git a/yoRadio/controls.h b/yoRadio/controls.h index 3955528..19bd38b 100644 --- a/yoRadio/controls.h +++ b/yoRadio/controls.h @@ -9,7 +9,8 @@ void onEncDoubleClick(); void onEncLPStart(); void encoderLoop(); void irLoop(); - +void irNum(byte num); +void irBlink(); void controlsEvent(bool toRight); void onLeftClick(); diff --git a/yoRadio/data/data/.gitignore b/yoRadio/data/data/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/yoRadio/data/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/yoRadio/data/data/wifi.csv b/yoRadio/data/data/wifi.csv index e69de29..d00491f 100644 --- a/yoRadio/data/data/wifi.csv +++ b/yoRadio/data/data/wifi.csv @@ -0,0 +1 @@ +1 diff --git a/yoRadio/data/www/index.html b/yoRadio/data/www/index.html index f5740f0..fc2e92f 100644 --- a/yoRadio/data/www/index.html +++ b/yoRadio/data/www/index.html @@ -67,34 +67,37 @@

WiFi Settings

+
  • 1.
    -
    +
  • 2.
    -
    +
  • 3.
    -
    +
  • 4.
    -
    +
  • 5.
    -
    +
+
+
Export
Cancel
Save
diff --git a/yoRadio/data/www/script.js b/yoRadio/data/www/script.js index 7064416..14e7274 100644 --- a/yoRadio/data/www/script.js +++ b/yoRadio/data/www/script.js @@ -1,9 +1,12 @@ var gateway = `ws://${window.location.hostname}/ws`; var websocket; var currentItem = 0; +var wserrcnt = 0; +var wstimeout; window.addEventListener('load', onLoad); function initWebSocket() { + clearTimeout(wstimeout); console.log('Trying to open a WebSocket connection...'); websocket = new WebSocket(gateway); websocket.onopen = onOpen; @@ -12,13 +15,14 @@ function initWebSocket() { } function onOpen(event) { console.log('Connection opened'); + wserrcnt=0; } function onClose(event) { - console.log('Connection closed'); + //console.log('Connection closed'); + wserrcnt++; document.getElementById('playbutton').setAttribute("class", "stopped"); - setTimeout(initWebSocket, 2000); + wstimeout=setTimeout(initWebSocket, wserrcnt<10?2000:120000); } - function onMessage(event) { var data = JSON.parse(event.data); if(data.nameset) document.getElementById('nameset').innerHTML = data.nameset; @@ -138,7 +142,6 @@ function setVolRangeValue(el, val=null){ var value = (el.value-el.min)/(el.max-el.min)*100; el.style.background = 'linear-gradient(to right, #bfa73e 0%, #bfa73e ' + value + '%, #272727 ' + value + '%, #272727 100%)'; } - function onRangeVolChange(value) { xhr = new XMLHttpRequest(); xhr.open("POST","/",true); @@ -162,10 +165,12 @@ function onRangeBalChange(el){ } function showSettings(){ document.getElementById('pleditorwrap').hidden=true; + document.getElementById('equalizerbg').hidden=true; document.getElementById('settings').hidden=false; } function showEditor(){ document.getElementById('settings').hidden=true; + document.getElementById('equalizerbg').hidden=true; initPLEditor(); document.getElementById('pleditorwrap').hidden=false; } @@ -176,6 +181,10 @@ function doCancel() { function doExport() { window.open("/data/playlist.csv"); } +function doWifiExport() { + document.getElementById('settings').hidden=true; + window.open("/data/wifi.csv"); +} function doUpload(finput) { var formData = new FormData(); formData.append("plfile", finput.files[0]); @@ -231,7 +240,8 @@ function submitWiFi(){ for (var i = 0; i <= items.length - 1; i++) { inputs=items[i].getElementsByTagName("input"); if(inputs[0].value == "") continue; - output+=inputs[0].value+"\t"+inputs[1].value+"\n"; + let ps=inputs[1].value==""?inputs[1].getAttribute('data-pass'):inputs[1].value; + output+=inputs[0].value+"\t"+ps+"\n"; } if(output!=""){ // Well, let's say, quack. xhr = new XMLHttpRequest(); diff --git a/yoRadio/display.cpp b/yoRadio/display.cpp index ed12b2c..91cc0f9 100644 --- a/yoRadio/display.cpp +++ b/yoRadio/display.cpp @@ -204,6 +204,9 @@ void Display::swichMode(displayMode_e newmode) { if (newmode == VOL) { dsp.frameTitle("VOLUME"); } + if (newmode == NUMBERS) { + dsp.frameTitle("STATION"); + } if (newmode == STATIONS) { currentPlItem = config.store.lastStation; plCurrent.reset(); @@ -235,6 +238,10 @@ void Display::drawPlaylist() { plCurrent.setText(dsp.utf8Rus(buf, true)); } +void Display::drawNextStationNum(uint16_t num) { + dsp.drawNextStationNum(num); +} + void Display::loop() { switch (mode) { case PLAYER: { @@ -245,6 +252,9 @@ void Display::loop() { drawVolume(); break; } + case NUMBERS: { + break; + } case STATIONS: { plCurrent.loop(); break; diff --git a/yoRadio/display.h b/yoRadio/display.h index e86538f..38327e1 100644 --- a/yoRadio/display.h +++ b/yoRadio/display.h @@ -5,7 +5,7 @@ #include #include "config.h" -enum displayMode_e { PLAYER, VOL, STATIONS }; +enum displayMode_e { PLAYER, VOL, STATIONS, NUMBERS }; class Scroll { public: @@ -42,6 +42,7 @@ class Display { uint16_t screenwidth, screenheight; displayMode_e mode; uint16_t currentPlItem; + uint16_t numOfNextStation; public: Display() {}; void init(); @@ -58,6 +59,7 @@ class Display { void ip(); void swichMode(displayMode_e newmode); void drawPlaylist(); + void drawNextStationNum(uint16_t num); private: Ticker timer; Scroll meta, title1, title2, plCurrent; diff --git a/yoRadio/options.h b/yoRadio/options.h index 62740b3..5f0db5b 100644 --- a/yoRadio/options.h +++ b/yoRadio/options.h @@ -1,7 +1,7 @@ #ifndef options_h #define options_h -#define VERSION "0.4.298" +#define VERSION "0.4.315" #if __has_include("myoptions.h") #include "myoptions.h" // <- write your variable values here @@ -115,6 +115,13 @@ #define VOL_STEP 1 // Encoder vol step #endif +#ifndef MUTE_PIN + #define MUTE_PIN 255 // MUTE Pin +#endif +#ifndef MUTE_VAL + #define MUTE_VAL HIGH // Write this to MUTE_PIN when player is stopped +#endif + /* *** ST7735 display submodel *** INITR_BLACKTAB // 1.8' https://aliexpress.ru/item/1005002822797745.html diff --git a/yoRadio/player.cpp b/yoRadio/player.cpp index b6f7eac..0cc3a4d 100644 --- a/yoRadio/player.cpp +++ b/yoRadio/player.cpp @@ -28,6 +28,7 @@ Player::Player() {} void Player::init() { + if(MUTE_PIN!=255) pinMode(MUTE_PIN, OUTPUT); #if I2S_DOUT!=255 setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT); #else @@ -55,7 +56,8 @@ void Player::loop() { Audio::loop(); } else { if (isRunning()) { - digitalWrite(LED_BUILTIN, LOW); + //digitalWrite(LED_BUILTIN, LOW); + setOutputPins(false); display.title("[stopped]"); stopSong(); stopInfo(); @@ -84,9 +86,15 @@ void Player::zeroRequest() { request.doSave = false; } -void Player::play(byte stationId) { +void Player::setOutputPins(bool isPlaying) { + digitalWrite(LED_BUILTIN, isPlaying); + if(MUTE_PIN!=255) digitalWrite(MUTE_PIN, isPlaying?!MUTE_VAL:MUTE_VAL); +} + +void Player::play(uint16_t stationId) { stopSong(); - digitalWrite(LED_BUILTIN, LOW); + //digitalWrite(LED_BUILTIN, LOW); + setOutputPins(false); display.title("[connecting]"); telnet.printf("##CLI.META#: %s\n", config.station.title); config.loadStation(stationId); @@ -97,7 +105,8 @@ void Player::play(byte stationId) { mode = PLAYING; config.setSmartStart(1); netserver.requestOnChange(MODE, 0); - digitalWrite(LED_BUILTIN, HIGH); + //digitalWrite(LED_BUILTIN, HIGH); + setOutputPins(true); requesToStart = true; }else{ Serial.println("Some Unknown Bug..."); diff --git a/yoRadio/player.h b/yoRadio/player.h index 0d650c5..98b6998 100644 --- a/yoRadio/player.h +++ b/yoRadio/player.h @@ -27,7 +27,7 @@ class Player: public Audio { void init(); void loop(); void zeroRequest(); - void play(byte stationId); + void play(uint16_t stationId); void prev(); void next(); void toggle(); @@ -35,6 +35,7 @@ class Player: public Audio { void setVol(byte volume, bool inside); byte volToI2S(byte volume); void stopInfo(); + void setOutputPins(bool isPlaying); }; extern Player player; diff --git a/yoRadio/src/displays/displayDummy.cpp b/yoRadio/src/displays/displayDummy.cpp index 86449f2..d90521b 100644 --- a/yoRadio/src/displays/displayDummy.cpp +++ b/yoRadio/src/displays/displayDummy.cpp @@ -137,6 +137,10 @@ void DisplayDummy::drawVolumeBar(bool withNumber) { } +void DisplayDummy::drawNextStationNum(uint16_t num) { + +} + void DisplayDummy::frameTitle(const char* str) { } diff --git a/yoRadio/src/displays/displayDummy.h b/yoRadio/src/displays/displayDummy.h index 0236d6e..5028b07 100644 --- a/yoRadio/src/displays/displayDummy.h +++ b/yoRadio/src/displays/displayDummy.h @@ -28,6 +28,7 @@ class DisplayDummy { void printClock(const char* timestr); void displayHeapForDebug(); void drawVolumeBar(bool withNumber); + void drawNextStationNum(uint16_t num); char* utf8Rus(const char* str, bool uppercase); void drawScrollFrame(uint16_t texttop, uint16_t textheight, uint16_t bg); void getScrolBbounds(const char* text, const char* separator, byte textsize, uint16_t &tWidth, uint16_t &tHeight, uint16_t &sWidth); diff --git a/yoRadio/src/displays/displayN5110.cpp b/yoRadio/src/displays/displayN5110.cpp index 99fb7be..337cdc5 100644 --- a/yoRadio/src/displays/displayN5110.cpp +++ b/yoRadio/src/displays/displayN5110.cpp @@ -250,6 +250,21 @@ void DisplayN5110::drawVolumeBar(bool withNumber) { } } +void DisplayN5110::drawNextStationNum(uint16_t num) { + setTextSize(1); + setTextColor(TFT_FG); + char numstr[7]; + uint16_t wv, hv; + int16_t x1, y1; + sprintf(numstr, "%d", num); + setFont(&DS_DIGI15pt7b); + getTextBounds(numstr, 0, 0, &x1, &y1, &wv, &hv); + fillRect(TFT_FRAMEWDT, 24-10, swidth - TFT_FRAMEWDT / 2, hv + 3, TFT_BG); + setCursor((swidth - wv) / 2, 24+8); + print(numstr); + setFont(); +} + void DisplayN5110::frameTitle(const char* str) { setTextSize(1); centerText(str, TFT_FRAMEWDT, TFT_LOGO, TFT_BG); diff --git a/yoRadio/src/displays/displayN5110.h b/yoRadio/src/displays/displayN5110.h index 7e34c7d..cbc32d8 100644 --- a/yoRadio/src/displays/displayN5110.h +++ b/yoRadio/src/displays/displayN5110.h @@ -33,6 +33,7 @@ class DisplayN5110: public Adafruit_PCD8544 { void printClock(const char* timestr); void displayHeapForDebug(); void drawVolumeBar(bool withNumber); + void drawNextStationNum(uint16_t num); char* utf8Rus(const char* str, bool uppercase); void drawScrollFrame(uint16_t texttop, uint16_t textheight, uint16_t bg); void getScrolBbounds(const char* text, const char* separator, byte textsize, uint16_t &tWidth, uint16_t &tHeight, uint16_t &sWidth); diff --git a/yoRadio/src/displays/displaySSD1306.cpp b/yoRadio/src/displays/displaySSD1306.cpp index 76488d5..83f3166 100644 --- a/yoRadio/src/displays/displaySSD1306.cpp +++ b/yoRadio/src/displays/displaySSD1306.cpp @@ -245,6 +245,19 @@ void DisplaySSD1306::drawVolumeBar(bool withNumber) { } } +void DisplaySSD1306::drawNextStationNum(uint16_t num) { + setTextSize(2); + setTextColor(TFT_FG); + char numstr[7]; + uint16_t wv, hv; + int16_t x1, y1; + sprintf(numstr, "%d", num); + getTextBounds(numstr, 0, 0, &x1, &y1, &wv, &hv); + fillRect(TFT_FRAMEWDT, 24, swidth - TFT_FRAMEWDT / 2, hv + 3, TFT_BG); + setCursor((swidth - wv) / 2, 24); + print(numstr); +} + void DisplaySSD1306::frameTitle(const char* str) { setTextSize(2); centerText(str, TFT_FRAMEWDT, TFT_LOGO, TFT_BG); diff --git a/yoRadio/src/displays/displaySSD1306.h b/yoRadio/src/displays/displaySSD1306.h index b6f6468..db30f9c 100644 --- a/yoRadio/src/displays/displaySSD1306.h +++ b/yoRadio/src/displays/displaySSD1306.h @@ -30,6 +30,7 @@ class DisplaySSD1306: public Adafruit_SSD1306 { void printClock(const char* timestr); void displayHeapForDebug(); void drawVolumeBar(bool withNumber); + void drawNextStationNum(uint16_t num); char* utf8Rus(const char* str, bool uppercase); void drawScrollFrame(uint16_t texttop, uint16_t textheight, uint16_t bg); void getScrolBbounds(const char* text, const char* separator, byte textsize, uint16_t &tWidth, uint16_t &tHeight, uint16_t &sWidth); diff --git a/yoRadio/src/displays/displayST7735.cpp b/yoRadio/src/displays/displayST7735.cpp index dc2a064..8ce31f7 100644 --- a/yoRadio/src/displays/displayST7735.cpp +++ b/yoRadio/src/displays/displayST7735.cpp @@ -275,6 +275,21 @@ void DisplayST7735::drawVolumeBar(bool withNumber) { } } +void DisplayST7735::drawNextStationNum(uint16_t num) { + setTextSize(1); + setTextColor(TFT_FG); + setFont(&DS_DIGI28pt7b); + char numstr[7]; + uint16_t wv, hv; + int16_t x1, y1; + sprintf(numstr, "%d", num); + getTextBounds(numstr, 0, 0, &x1, &y1, &wv, &hv); + fillRect(TFT_FRAMEWDT, 48, swidth - TFT_FRAMEWDT / 2, hv + 3, TFT_BG); + setCursor((swidth - wv) / 2, 48 + hv); + print(numstr); + setFont(); +} + void DisplayST7735::frameTitle(const char* str) { setTextSize(2); centerText(str, TFT_FRAMEWDT, TFT_LOGO, TFT_BG); diff --git a/yoRadio/src/displays/displayST7735.h b/yoRadio/src/displays/displayST7735.h index fbe36ed..de45f66 100644 --- a/yoRadio/src/displays/displayST7735.h +++ b/yoRadio/src/displays/displayST7735.h @@ -31,6 +31,7 @@ class DisplayST7735: public Adafruit_ST7735 { void printClock(const char* timestr); void displayHeapForDebug(); void drawVolumeBar(bool withNumber); + void drawNextStationNum(uint16_t num); char* utf8Rus(const char* str, bool uppercase); void drawScrollFrame(uint16_t texttop, uint16_t textheight, uint16_t bg); void getScrolBbounds(const char* text, const char* separator, byte textsize, uint16_t &tWidth, uint16_t &tHeight, uint16_t &sWidth); diff --git a/yoRadio/telnet.cpp b/yoRadio/telnet.cpp index 31340d6..52b4f2d 100644 --- a/yoRadio/telnet.cpp +++ b/yoRadio/telnet.cpp @@ -260,7 +260,7 @@ void Telnet::on_input(const char* str, byte clientId) { printf(clientId, "> "); return; } - uint8_t sb; + uint16_t sb; if (sscanf(str, "play(%d)", &sb) == 1 || sscanf(str, "cli.play(\"%d\")", &sb) == 1 || sscanf(str, "play %d", &sb) == 1 ) { if (sb < 1) sb = 1; if (sb >= config.store.countStation) sb = config.store.countStation; diff --git a/yoRadio/yoRadio.ino b/yoRadio/yoRadio.ino index 9897095..358738b 100644 --- a/yoRadio/yoRadio.ino +++ b/yoRadio/yoRadio.ino @@ -13,10 +13,11 @@ void setup() { Serial.begin(115200); pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, LOW); + //digitalWrite(LED_BUILTIN, LOW); config.init(); display.init(); player.init(); + player.setOutputPins(false); network.begin(); if (network.status != CONNECTED) { netserver.begin();