This commit is contained in:
e2002
2022-10-14 11:00:49 +03:00
parent dbe0f32e14
commit 1c4b1dec6e
94 changed files with 5320 additions and 6613 deletions

534
yoRadio/src/core/config.cpp Normal file
View File

@@ -0,0 +1,534 @@
#include "config.h"
#include <EEPROM.h>
#include <SPIFFS.h>
#include "display.h"
#include "player.h"
Config config;
void u8fix(char *src){
char last = src[strlen(src)-1];
if ((uint8_t)last >= 0xC2) src[strlen(src)-1]='\0';
}
void Config::init() {
EEPROM.begin(EEPROM_SIZE);
#if IR_PIN!=255
irindex=-1;
#endif
eepromRead(EEPROM_START, store);
if (store.config_set != 4262) setDefaults();
//if (!SPIFFS.begin(false, "/spiffs", 30)) {
if (!SPIFFS.begin(false)) {
return;
}
loadTheme();
ssidsCount = 0;
initPlaylist();
if (store.lastStation == 0 && store.countStation > 0) {
store.lastStation = 1;
save();
}
loadStation(store.lastStation);
#if IR_PIN!=255
eepromRead(EEPROM_START_IR, ircodes);
if(ircodes.ir_set!=4224){
ircodes.ir_set=4224;
memset(ircodes.irVals, 0, sizeof(ircodes.irVals));
}
#endif
#if BRIGHTNESS_PIN!=255
pinMode(BRIGHTNESS_PIN, OUTPUT);
setBrightness(false);
#endif
}
uint16_t Config::color565(uint8_t r, uint8_t g, uint8_t b)
{
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
void Config::loadTheme(){
theme.background = color565(COLOR_BACKGROUND);
theme.meta = color565(COLOR_STATION_NAME);
theme.metabg = color565(COLOR_STATION_BG);
theme.metafill = color565(COLOR_STATION_FILL);
theme.title1 = color565(COLOR_SNG_TITLE_1);
theme.title2 = color565(COLOR_SNG_TITLE_2);
theme.digit = color565(COLOR_DIGITS);
theme.div = color565(COLOR_DIVIDER);
theme.weather = color565(COLOR_WEATHER);
theme.vumax = color565(COLOR_VU_MAX);
theme.vumin = color565(COLOR_VU_MIN);
theme.clock = color565(COLOR_CLOCK);
theme.seconds = color565(COLOR_SECONDS);
theme.dow = color565(COLOR_DAY_OF_W);
theme.date = color565(COLOR_DATE);
theme.heap = color565(COLOR_HEAP);
theme.buffer = color565(COLOR_BUFFER);
theme.ip = color565(COLOR_IP);
theme.vol = color565(COLOR_VOLUME_VALUE);
theme.rssi = color565(COLOR_RSSI);
theme.bitrate = color565(COLOR_BITRATE);
theme.volbarout = color565(COLOR_VOLBAR_OUT);
theme.volbarin = color565(COLOR_VOLBAR_IN);
theme.playlist[0] = color565(COLOR_PLAYLIST_0);
theme.playlist[1] = color565(COLOR_PLAYLIST_1);
theme.playlist[2] = color565(COLOR_PLAYLIST_2);
theme.playlist[3] = color565(COLOR_PLAYLIST_3);
theme.playlist[4] = color565(COLOR_PLAYLIST_4);
}
template <class T> int Config::eepromWrite(int ee, const T& value) {
const byte* p = (const byte*)(const void*)&value;
int i;
for (i = 0; i < sizeof(value); i++)
EEPROM.write(ee++, *p++);
EEPROM.commit();
return i;
}
template <class T> int Config::eepromRead(int ee, T& value) {
byte* p = (byte*)(void*)&value;
int i;;
for (i = 0; i < sizeof(value); i++)
*p++ = EEPROM.read(ee++);
return i;
}
void Config::setDefaults() {
store.config_set = 4262;
store.volume = 12;
store.balance = 0;
store.trebble = 0;
store.middle = 0;
store.bass = 0;
store.lastStation = 0;
store.countStation = 0;
store.lastSSID = 0;
store.audioinfo = false;
store.smartstart = 2;
store.tzHour = 3;
store.tzMin = 0;
store.timezoneOffset = 0;
store.vumeter=false;
store.softapdelay=0;
store.flipscreen=false;
store.invertdisplay=false;
store.numplaylist=false;
store.fliptouch=false;
store.dbgtouch=false;
store.dspon=true;
store.brightness=100;
store.contrast=55;
strlcpy(store.sntp1,"pool.ntp.org", 35);
strlcpy(store.sntp2,"1.ru.pool.ntp.org", 35);
store.showweather=false;
strlcpy(store.weatherlat,"55.7512", 10);
strlcpy(store.weatherlon,"37.6184", 10);
strlcpy(store.weatherkey,"", 64);
store.volsteps = 1;
store.encacc = 200;
store.irto = 80;
store.irtlp = 35;
store.btnpullup = true;
store.btnlongpress = 200;
store.btnclickticks = 300;
store.btnpressticks = 500;
store.encpullup = false;
store.enchalf = false;
store.enc2pullup = false;
store.enc2half = false;
store.forcemono = false;
store.i2sinternal = false;
store.rotate90 = false;
}
void Config::setTimezone(int8_t tzh, int8_t tzm) {
store.tzHour = tzh;
store.tzMin = tzm;
save();
}
void Config::setTimezoneOffset(uint16_t tzo) {
store.timezoneOffset = tzo;
save();
}
uint16_t Config::getTimezoneOffset() {
return 0; // TODO
}
void Config::save() {
eepromWrite(EEPROM_START, store);
}
#if IR_PIN!=255
void Config::saveIR(){
eepromWrite(EEPROM_START_IR, ircodes);
}
#endif
void Config::saveVolume(){
EEPROM.write(EEPROM_START + sizeof(store.config_set), store.volume);
EEPROM.commit();
}
byte Config::setVolume(byte val) {
store.volume = val;
return store.volume;
}
void Config::setTone(int8_t bass, int8_t middle, int8_t trebble) {
store.bass = bass;
store.middle = middle;
store.trebble = trebble;
save();
}
void Config::setSmartStart(byte ss) {
if (store.smartstart < 2) {
store.smartstart = ss;
save();
}
}
void Config::setBalance(int8_t balance) {
store.balance = balance;
save();
}
byte Config::setLastStation(byte val) {
store.lastStation = val;
save();
return store.lastStation;
}
byte Config::setCountStation(byte val) {
store.countStation = val;
save();
return store.countStation;
}
byte Config::setLastSSID(byte val) {
store.lastSSID = val;
save();
return store.lastSSID;
}
void Config::setTitle(const char* title) {
memset(config.station.title, 0, BUFLEN);
strlcpy(config.station.title, title, BUFLEN);
u8fix(config.station.title);
display.putRequest(NEWTITLE);
}
void Config::setStation(const char* station) {
memset(config.station.name, 0, BUFLEN);
strlcpy(config.station.name, station, BUFLEN);
u8fix(config.station.title);
}
void Config::indexPlaylist() {
File playlist = SPIFFS.open(PLAYLIST_PATH, "r");
if (!playlist) {
return;
}
char sName[BUFLEN], sUrl[BUFLEN];
int sOvol;
File index = SPIFFS.open(INDEX_PATH, "w");
while (playlist.available()) {
uint32_t pos = playlist.position();
if (parseCSV(playlist.readStringUntil('\n').c_str(), sName, sUrl, sOvol)) {
index.write((byte *) &pos, 4);
}
}
index.close();
playlist.close();
}
void Config::initPlaylist() {
store.countStation = 0;
if (!SPIFFS.exists(INDEX_PATH)) indexPlaylist();
if (SPIFFS.exists(INDEX_PATH)) {
File index = SPIFFS.open(INDEX_PATH, "r");
store.countStation = index.size() / 4;
index.close();
save();
}
}
void Config::loadStation(uint16_t ls) {
char sName[BUFLEN], sUrl[BUFLEN];
int sOvol;
if (store.countStation == 0) {
memset(station.url, 0, BUFLEN);
memset(station.name, 0, BUFLEN);
strncpy(station.name, "ёRadio", BUFLEN);
station.ovol = 0;
return;
}
if (ls > store.countStation) {
ls = 1;
}
File playlist = SPIFFS.open(PLAYLIST_PATH, "r");
File index = SPIFFS.open(INDEX_PATH, "r");
index.seek((ls - 1) * 4, SeekSet);
uint32_t pos;
index.readBytes((char *) &pos, 4);
index.close();
playlist.seek(pos, SeekSet);
if (parseCSV(playlist.readStringUntil('\n').c_str(), sName, sUrl, sOvol)) {
memset(station.url, 0, BUFLEN);
memset(station.name, 0, BUFLEN);
strncpy(station.name, sName, BUFLEN);
strncpy(station.url, sUrl, BUFLEN);
station.ovol = sOvol;
setLastStation(ls);
}
playlist.close();
}
void Config::fillPlMenu(char plmenu[][40], int from, byte count, bool removeNum) {
int ls = from;
byte c = 0;
bool finded = false;
char sName[BUFLEN], sUrl[BUFLEN];
int sOvol;
if (store.countStation == 0) {
return;
}
File playlist = SPIFFS.open(PLAYLIST_PATH, "r");
File index = SPIFFS.open(INDEX_PATH, "r");
while (true) {
if (ls < 1) {
ls++;
c++;
continue;
}
if (!finded) {
index.seek((ls - 1) * 4, SeekSet);
uint32_t pos;
index.readBytes((char *) &pos, 4);
finded = true;
index.close();
playlist.seek(pos, SeekSet);
}
while (playlist.available()) {
if (parseCSV(playlist.readStringUntil('\n').c_str(), sName, sUrl, sOvol)) {
if(config.store.numplaylist){
if(removeNum){
strlcpy(plmenu[c], sName, 39);
}else{
char buf[BUFLEN+10];
sprintf(buf, "%d %s", (int)(from+c), sName);
strlcpy(plmenu[c], buf, 39);
}
}else{
strlcpy(plmenu[c], sName, 39);
}
c++;
}
if (c >= count) break;
}
break;
}
playlist.close();
}
bool Config::parseCSV(const char* line, char* name, char* url, int &ovol) {
char *tmpe;
const char* cursor = line;
char buf[5];
tmpe = strstr(cursor, "\t");
if (tmpe == NULL) return false;
strlcpy(name, cursor, tmpe - cursor + 1);
if (strlen(name) == 0) return false;
cursor = tmpe + 1;
tmpe = strstr(cursor, "\t");
if (tmpe == NULL) return false;
strlcpy(url, cursor, tmpe - cursor + 1);
if (strlen(url) == 0) return false;
cursor = tmpe + 1;
if (strlen(cursor) == 0) return false;
strlcpy(buf, cursor, 4);
ovol = atoi(buf);
return true;
}
bool Config::parseJSON(const char* line, char* name, char* url, int &ovol) {
char* tmps, *tmpe;
const char* cursor = line;
char port[8], host[246], file[254];
tmps = strstr(cursor, "\":\"");
if (tmps == NULL) return false;
tmpe = strstr(tmps, "\",\"");
if (tmpe == NULL) return false;
strlcpy(name, tmps + 3, tmpe - tmps - 3 + 1);
if (strlen(name) == 0) return false;
cursor = tmpe + 3;
tmps = strstr(cursor, "\":\"");
if (tmps == NULL) return false;
tmpe = strstr(tmps, "\",\"");
if (tmpe == NULL) return false;
strlcpy(host, tmps + 3, tmpe - tmps - 3 + 1);
if (strlen(host) == 0) return false;
if (strstr(host, "http://") == NULL && strstr(host, "https://") == NULL) {
sprintf(file, "http://%s", host);
strlcpy(host, file, strlen(file) + 1);
}
cursor = tmpe + 3;
tmps = strstr(cursor, "\":\"");
if (tmps == NULL) return false;
tmpe = strstr(tmps, "\",\"");
if (tmpe == NULL) return false;
strlcpy(file, tmps + 3, tmpe - tmps - 3 + 1);
cursor = tmpe + 3;
tmps = strstr(cursor, "\":\"");
if (tmps == NULL) return false;
tmpe = strstr(tmps, "\",\"");
if (tmpe == NULL) return false;
strlcpy(port, tmps + 3, tmpe - tmps - 3 + 1);
int p = atoi(port);
if (p > 0) {
sprintf(url, "%s:%d%s", host, p, file);
} else {
sprintf(url, "%s%s", host, file);
}
cursor = tmpe + 3;
tmps = strstr(cursor, "\":\"");
if (tmps == NULL) return false;
tmpe = strstr(tmps, "\"}");
if (tmpe == NULL) return false;
strlcpy(port, tmps + 3, tmpe - tmps - 3 + 1);
ovol = atoi(port);
return true;
}
bool Config::parseWsCommand(const char* line, char* cmd, char* val, byte cSize) {
char *tmpe;
tmpe = strstr(line, "=");
if (tmpe == NULL) return false;
memset(cmd, 0, cSize);
strlcpy(cmd, line, tmpe - line + 1);
if (strlen(tmpe + 1) == 0) return false;
memset(val, 0, cSize);
strlcpy(val, tmpe + 1, strlen(line) - strlen(cmd) + 1);
return true;
}
bool Config::parseSsid(const char* line, char* ssid, char* pass) {
char *tmpe;
tmpe = strstr(line, "\t");
if (tmpe == NULL) return false;
uint16_t pos = tmpe - line;
if (pos > 19 || strlen(line) > 61) return false;
memset(ssid, 0, 20);
strlcpy(ssid, line, pos + 1);
memset(pass, 0, 40);
strlcpy(pass, line + pos + 1, strlen(line) - pos);
return true;
}
bool Config::saveWifiFromNextion(const char* post){
File file = SPIFFS.open(SSIDS_PATH, "w");
if (!file) {
return false;
} else {
file.print(post);
file.close();
ESP.restart();
return true;
}
}
bool Config::saveWifi() {
if (!SPIFFS.exists(TMP_PATH)) return false;
SPIFFS.remove(SSIDS_PATH);
SPIFFS.rename(TMP_PATH, SSIDS_PATH);
ESP.restart();
return true;
}
bool Config::initNetwork() {
File file = SPIFFS.open(SSIDS_PATH, "r");
if (!file || file.isDirectory()) {
return false;
}
char ssidval[20], passval[40];
byte c = 0;
while (file.available()) {
if (parseSsid(file.readStringUntil('\n').c_str(), ssidval, passval)) {
strlcpy(ssids[c].ssid, ssidval, 20);
strlcpy(ssids[c].password, passval, 40);
ssidsCount++;
c++;
}
}
file.close();
return true;
}
void Config::setBrightness(bool dosave){
#if BRIGHTNESS_PIN!=255
if(!store.dspon && dosave) {
display.wakeup();
}
//analogWrite(BRIGHTNESS_PIN, config.store.dspon?map(store.brightness, 0, 100, 0, 255):0);
analogWrite(BRIGHTNESS_PIN, map(store.brightness, 0, 100, 0, 255));
if(!store.dspon) store.dspon = true;
if(dosave) save();
#endif
#ifdef USE_NEXTION
// if(!store.dspon && dosave) {
nextion.wake();
// }
char cmd[15];
snprintf(cmd, 15, "dims=%d", store.brightness);
nextion.putcmd(cmd);
if(!store.dspon) store.dspon = true;
if(dosave) save();
#endif
}
void Config::setDspOn(bool dspon){
store.dspon = dspon;
save();
#ifdef USE_NEXTION
if(!dspon) nextion.sleep();
else nextion.wake();
#endif
if(!dspon){
#if BRIGHTNESS_PIN!=255
analogWrite(BRIGHTNESS_PIN, 0);
#endif
display.deepsleep();
}else{
display.wakeup();
#if BRIGHTNESS_PIN!=255
analogWrite(BRIGHTNESS_PIN, map(store.brightness, 0, 100, 0, 255));
#endif
}
}
void Config::doSleep(){
if(BRIGHTNESS_PIN!=255) analogWrite(BRIGHTNESS_PIN, 0);
display.deepsleep();
#ifdef USE_NEXTION
nextion.sleep();
#endif
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();
}
void Config::sleepForAfter(uint16_t sf, uint16_t sa){
sleepfor = sf;
if(sa > 0) _sleepTimer.attach(sa * 60, doSleep);
else doSleep();
}

185
yoRadio/src/core/config.h Normal file
View File

@@ -0,0 +1,185 @@
#ifndef config_h
#define config_h
#include "Arduino.h"
#include <Ticker.h>
#include "options.h"
#define EEPROM_SIZE 768
#define EEPROM_START 500
#define EEPROM_START_IR 0
#define EEPROM_START_2 10
#define BUFLEN 140
#define PLAYLIST_PATH "/data/playlist.csv"
#define SSIDS_PATH "/data/wifi.csv"
#define TMP_PATH "/data/tmpfile.txt"
#define INDEX_PATH "/data/index.dat"
#ifdef DEBUG_V
#define DBGH() { Serial.printf("[%s:%s:%d] Heap: %d\n", __PRETTY_FUNCTION__, __FILE__, __LINE__, xPortGetFreeHeapSize()); }
#define DBGVB( ... ) { char buf[200]; sprintf( buf, __VA_ARGS__ ) ; Serial.print("[DEBUG]\t"); Serial.println(buf); }
#else
#define DBGVB( ... )
#define DBGH()
#endif
#define EVERY_MS(x) static uint32_t tmr; bool flag = millis() - tmr >= (x); if (flag) tmr += (x); if (flag)
void u8fix(char *src);
struct theme_t {
uint16_t background;
uint16_t meta;
uint16_t metabg;
uint16_t metafill;
uint16_t title1;
uint16_t title2;
uint16_t digit;
uint16_t div;
uint16_t weather;
uint16_t vumax;
uint16_t vumin;
uint16_t clock;
uint16_t seconds;
uint16_t dow;
uint16_t date;
uint16_t heap;
uint16_t buffer;
uint16_t ip;
uint16_t vol;
uint16_t rssi;
uint16_t bitrate;
uint16_t volbarout;
uint16_t volbarin;
uint16_t playlist[5];
};
struct config_t
{
unsigned int config_set; //must be 4262
byte volume;
int8_t balance;
int8_t trebble;
int8_t middle;
int8_t bass;
uint16_t lastStation;
uint16_t countStation;
byte lastSSID;
bool audioinfo;
byte smartstart;
int8_t tzHour;
int8_t tzMin;
uint16_t timezoneOffset;
bool vumeter;
uint8_t softapdelay;
bool flipscreen;
bool invertdisplay;
bool numplaylist;
bool fliptouch;
bool dbgtouch;
bool dspon;
uint8_t brightness;
uint8_t contrast;
char sntp1[35];
char sntp2[35];
bool showweather;
char weatherlat[10];
char weatherlon[10];
char weatherkey[64];
uint8_t volsteps;
uint16_t encacc;
uint8_t irto;
uint8_t irtlp;
bool btnpullup;
uint16_t btnlongpress;
uint16_t btnclickticks;
uint16_t btnpressticks;
bool encpullup;
bool enchalf;
bool enc2pullup;
bool enc2half;
bool forcemono;
bool i2sinternal;
bool rotate90;
};
#if IR_PIN!=255
struct ircodes_t
{
unsigned int ir_set; //must be 4224
uint64_t irVals[20][3];
};
#endif
struct station_t
{
char name[BUFLEN];
char url[BUFLEN];
char title[BUFLEN];
uint16_t bitrate;
int ovol;
};
struct neworkItem
{
char ssid[20];
char password[40];
};
class Config {
public:
config_t store;
station_t station;
theme_t theme;
#if IR_PIN!=255
int irindex;
uint8_t irchck;
ircodes_t ircodes;
#endif
neworkItem ssids[5];
byte ssidsCount;
uint16_t sleepfor;
public:
Config() {};
void save();
#if IR_PIN!=255
void saveIR();
#endif
void init();
void loadTheme();
byte setVolume(byte val);
void saveVolume();
void setTone(int8_t bass, int8_t middle, int8_t trebble);
void setBalance(int8_t balance);
byte setLastStation(byte val);
byte setCountStation(byte val);
byte setLastSSID(byte val);
void setTitle(const char* title);
void setStation(const char* station);
bool parseCSV(const char* line, char* name, char* url, int &ovol);
bool parseJSON(const char* line, char* name, char* url, int &ovol);
bool parseWsCommand(const char* line, char* cmd, char* val, byte cSize);
bool parseSsid(const char* line, char* ssid, char* pass);
void loadStation(uint16_t station);
bool initNetwork();
bool saveWifi();
bool saveWifiFromNextion(const char* post);
void setSmartStart(byte ss);
void initPlaylist();
void indexPlaylist();
void fillPlMenu(char plmenu[][40], int from, byte count, bool removeNum = false);
void setTimezone(int8_t tzh, int8_t tzm);
void setTimezoneOffset(uint16_t tzo);
uint16_t getTimezoneOffset();
void setBrightness(bool dosave=false);
void setDspOn(bool dspon);
void sleepForAfter(uint16_t sleepfor, uint16_t sleepafter=0);
private:
template <class T> int eepromWrite(int ee, const T& value);
template <class T> int eepromRead(int ee, T& value);
void setDefaults();
Ticker _sleepTimer;
static void doSleep();
uint16_t color565(uint8_t r, uint8_t g, uint8_t b);
};
extern Config config;
#endif

View File

@@ -0,0 +1,635 @@
#include "Arduino.h"
#include "controls.h"
#include "options.h"
#include "config.h"
#include "player.h"
#include "display.h"
#include "netserver.h"
long encOldPosition = 0;
long enc2OldPosition = 0;
int lpId = -1;
#define ISPUSHBUTTONS BTN_LEFT!=255 || BTN_CENTER!=255 || BTN_RIGHT!=255 || ENC_BTNB!=255 || BTN_UP!=255 || BTN_DOWN!=255 || ENC2_BTNB!=255
#if ISPUSHBUTTONS
#include "OneButton.h"
OneButton button[] {{BTN_LEFT, true, BTN_INTERNALPULLUP}, {BTN_CENTER, true, BTN_INTERNALPULLUP}, {BTN_RIGHT, true, BTN_INTERNALPULLUP}, {ENC_BTNB, true, ENC_INTERNALPULLUP}, {BTN_UP, true, BTN_INTERNALPULLUP}, {BTN_DOWN, true, BTN_INTERNALPULLUP}, {ENC2_BTNB, true, ENC2_INTERNALPULLUP}};
constexpr uint8_t nrOfButtons = sizeof(button) / sizeof(button[0]);
#endif
#if ENC_HALFQUARD==false
#define ENCODER_STEPS 4
#elif ENC_HALFQUARD==true
#define ENCODER_STEPS 2
#elif ENC_HALFQUARD==255
#define ENCODER_STEPS 1
#endif
#if ENC2_HALFQUARD==false
#define ENCODER2_STEPS 4
#elif ENC2_HALFQUARD==true
#define ENCODER2_STEPS 2
#elif ENC2_HALFQUARD==255
#define ENCODER2_STEPS 1
#endif
#if (ENC_BTNL!=255 && ENC_BTNR!=255) || (ENC2_BTNL!=255 && ENC2_BTNR!=255)
#include "../yoEncoder/yoEncoder.h"
#if (ENC_BTNL!=255 && ENC_BTNR!=255)
yoEncoder encoder = yoEncoder(ENC_BTNL, ENC_BTNR, ENCODER_STEPS, ENC_INTERNALPULLUP);
#endif
#if (ENC2_BTNL!=255 && ENC2_BTNR!=255)
yoEncoder encoder2 = yoEncoder(ENC2_BTNL, ENC2_BTNR, ENCODER2_STEPS, ENC2_INTERNALPULLUP);
#endif
#endif
#if TS_CS!=255
#include <XPT2046_Touchscreen.h>
XPT2046_Touchscreen ts(TS_CS);
#endif
#if IR_PIN!=255
#include <assert.h>
#include <IRrecv.h>
#include <IRremoteESP8266.h>
#include <IRac.h>
#include <IRtext.h>
#include <IRutils.h>
byte irVolRepeat = 0;
const uint16_t kCaptureBufferSize = 1024;
const uint8_t kTimeout = IR_TIMEOUT;
const uint16_t kMinUnknownSize = 12;
#define LEGACY_TIMING_INFO false
IRrecv irrecv(IR_PIN, kCaptureBufferSize, kTimeout, true);
decode_results irResults;
#endif
#if ENC_BTNL!=255
void IRAM_ATTR readEncoderISR()
{
encoder.readEncoder_ISR();
}
#endif
#if ENC2_BTNL!=255
void IRAM_ATTR readEncoder2ISR()
{
encoder2.readEncoder_ISR();
}
#endif
void initControls() {
#if ENC_BTNL!=255
encoder.begin();
encoder.setup(readEncoderISR);
encoder.setBoundaries(0, 254, true);
encoder.setAcceleration(config.store.encacc);
#endif
#if ENC2_BTNL!=255
encoder2.begin();
encoder2.setup(readEncoder2ISR);
encoder2.setBoundaries(0, 254, true);
encoder2.setAcceleration(config.store.encacc);
#endif
#if ISPUSHBUTTONS
for (int i = 0; i < nrOfButtons; i++)
{
if ((i == 0 && BTN_LEFT == 255) || (i == 1 && BTN_CENTER == 255) || (i == 2 && BTN_RIGHT == 255) || (i == 3 && ENC_BTNB == 255) || (i == 4 && BTN_UP == 255) || (i == 5 && BTN_DOWN == 255) || (i == 6 && ENC2_BTNB == 255)) continue;
button[i].attachClick([](void* p) {
onBtnClick((int)p);
}, (void*)i);
button[i].attachDoubleClick([](void* p) {
onBtnDoubleClick((int)p);
}, (void*)i);
button[i].attachLongPressStart([](void* p) {
onBtnLongPressStart((int)p);
}, (void*)i);
button[i].attachLongPressStop([](void* p) {
onBtnLongPressStop((int)p);
}, (void*)i);
button[i].setClickTicks(BTN_CLICK_TICKS);
button[i].setPressTicks(BTN_PRESS_TICKS);
}
#endif
#if TS_CS!=255
ts.begin();
ts.setRotation(config.store.fliptouch?3:1);
#endif
#if IR_PIN!=255
pinMode(IR_PIN, INPUT);
assert(irutils::lowLevelSanityCheck() == 0);
#if DECODE_HASH
irrecv.setUnknownThreshold(kMinUnknownSize);
#endif // DECODE_HASH
irrecv.setTolerance(config.store.irtlp);
irrecv.enableIRIn();
#endif // IR_PIN!=255
}
void loopControls() {
if(display.mode()==LOST || display.mode()==UPDATING) return;
if (ctrls_on_loop) ctrls_on_loop();
#if ENC_BTNL!=255
encoderLoop();
#endif
#if ENC2_BTNL!=255
encoder2Loop();
#endif
#if ISPUSHBUTTONS
for (unsigned i = 0; i < nrOfButtons; i++)
{
if ((i == 0 && BTN_LEFT == 255) || (i == 1 && BTN_CENTER == 255) || (i == 2 && BTN_RIGHT == 255) || (i == 3 && ENC_BTNB == 255) || (i == 4 && BTN_UP == 255) || (i == 5 && BTN_DOWN == 255) || (i == 6 && ENC2_BTNB == 255)) continue;
button[i].tick();
if (lpId >= 0) {
if (DSP_MODEL == DSP_DUMMY && (lpId == 4 || lpId == 5)) continue;
onBtnDuringLongPress(lpId);
yield();
}
yield();
}
#endif
#if IR_PIN!=255
irLoop();
#endif
#if TS_CS!=255
touchLoop();
#endif
yield();
}
#if ENC_BTNL!=255
void encoderLoop() {
int8_t encoderDelta = encoder.encoderChanged();
if (encoderDelta!=0)
{
controlsEvent(encoderDelta > 0, encoderDelta);
}
}
#endif
#if ENC2_BTNL!=255
void encoder2Loop() {
int8_t encoderDelta = encoder2.encoderChanged();
if (encoderDelta!=0)
{
uint8_t bp = 2;
if (ENC2_BTNB != 255) {
bp = digitalRead(ENC2_BTNB);
}
if (bp == HIGH && display.mode() == PLAYER) {
display.putRequest(NEWMODE, STATIONS);
while(display.mode() != STATIONS) {delay(10);}
}
controlsEvent(encoderDelta > 0, encoderDelta);
}
}
#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;
display.putRequest(NEWMODE, NUMBERS);
if (display.numOfNextStation > UINT16_MAX / 10) return;
s = display.numOfNextStation * 10 + num;
if (s > config.store.countStation) return;
display.numOfNextStation = s;
display.putRequest(NEXTSTATION, s);
}
void irLoop() {
if (irrecv.decode(&irResults)) {
if(irResults.value<256) return;
if (netserver.irRecordEnable) {
Serial.print(resultToHumanReadableBasic(&irResults));
Serial.println("--------------------------");
config.ircodes.irVals[config.irindex][config.irchck]=irResults.value;
netserver.irToWs(typeToString(irResults.decode_type, irResults.repeat).c_str(), irResults.value);
return;
}
if (!irResults.repeat/* && irResults.command!=0*/) {
irVolRepeat = 0;
}
switch (irVolRepeat) {
case 1: {
controlsEvent(display.mode() == STATIONS ? false : true);
break;
}
case 2: {
controlsEvent(display.mode() == STATIONS ? true : false);
break;
}
}
for(int target=0; target<17; target++){
for(int j=0; j<3; j++){
if(config.ircodes.irVals[target][j]==irResults.value){
switch (target){
case IR_PLAY: {
irBlink();
if (display.mode() == NUMBERS) {
display.putRequest(NEWMODE, PLAYER);
player.play(display.numOfNextStation);
display.numOfNextStation = 0;
break;
}
onBtnClick(1);
break;
}
case IR_PREV: {
player.prev();
break;
}
case IR_NEXT: {
player.next();
break;
}
case IR_UP: {
controlsEvent(display.mode() == STATIONS ? false : true);
irVolRepeat = 1;
break;
}
case IR_DOWN: {
controlsEvent(display.mode() == STATIONS ? true : false);
irVolRepeat = 2;
break;
}
case IR_HASH: {
if (display.mode() == NUMBERS) {
display.putRequest(NEWMODE, PLAYER);
display.numOfNextStation = 0;
break;
}
display.putRequest(NEWMODE, display.mode() == PLAYER ? STATIONS : PLAYER);
break;
}
case IR_0: {
irNum(0);
break;
}
case IR_1: {
irNum(1);
break;
}
case IR_2: {
irNum(2);
break;
}
case IR_3: {
irNum(3);
break;
}
case IR_4: {
irNum(4);
break;
}
case IR_5: {
irNum(5);
break;
}
case IR_6: {
irNum(6);
break;
}
case IR_7: {
irNum(7);
break;
}
case IR_8: {
irNum(8);
break;
}
case IR_9: {
irNum(9);
break;
}
case IR_AST: {
break;
}
} /* switch (target) */
target=17;
break;
} /* if(config.ircodes.irVals[target][j]==irResults.value) */
} /* for(int j=0; j<3; j++) */
} /* for(int target=0; target<16; target++) */
} /* if (irrecv.decode(&irResults)) */
}
#endif // if IR_PIN!=255
#if TS_CS!=255
#ifndef TS_X_MIN
#define TS_X_MIN 400
#endif
#ifndef TS_X_MAX
#define TS_X_MAX 3800
#endif
#ifndef TS_Y_MIN
#define TS_Y_MIN 260
#endif
#ifndef TS_Y_MAX
#define TS_Y_MAX 3800
#endif
#ifndef TS_STEPS
#define TS_STEPS 40
#endif
boolean wastouched = true;
unsigned long touchdelay;
uint16_t touchVol, touchStation;
uint16_t oldTouchP[2];
tsDirection_e direct;
unsigned long touchLongPress;
tsDirection_e tsDirection(uint16_t x, uint16_t y) {
int16_t dX = x - oldTouchP[0];
int16_t dY = y - oldTouchP[1];
if (abs(dX) > 20 || abs(dY) > 20) {
if (abs(dX) > abs(dY)) {
if (dX > 0) {
return TSD_RIGHT;
} else {
return TSD_LEFT;
}
} else {
if (dY > 0) {
return TSD_DOWN;
} else {
return TSD_UP;
}
}
} else {
return TDS_REQUEST;
}
}
void touchLoop() {
if (!checklpdelay(100, touchdelay)) return;
boolean istouched = ts.touched();
if (istouched) {
TS_Point p = ts.getPoint();
uint16_t touchX = map(p.x, TS_X_MIN, TS_X_MAX, 0, dsp.width());
uint16_t touchY = map(p.y, TS_Y_MIN, TS_Y_MAX, 0, dsp.height());
if (!wastouched) { /* START TOUCH */
oldTouchP[0] = touchX;
oldTouchP[1] = touchY;
touchVol = touchX;
touchStation = touchY;
direct = TDS_REQUEST;
touchLongPress=millis();
} else { /* SWIPE TOUCH */
direct = tsDirection(touchX, touchY);
switch (direct) {
case TSD_LEFT:
case TSD_RIGHT: {
touchLongPress=millis();
if(display.mode()==PLAYER || display.mode()==VOL){
int16_t xDelta = map(abs(touchVol - touchX), 0, dsp.width(), 0, TS_STEPS);
display.putRequest(NEWMODE, VOL);
if (xDelta>1) {
controlsEvent((touchVol - touchX)<0);
touchVol = touchX;
}
}
break;
}
case TSD_UP:
case TSD_DOWN: {
touchLongPress=millis();
if(display.mode()==PLAYER || display.mode()==STATIONS){
int16_t yDelta = map(abs(touchStation - touchY), 0, dsp.height(), 0, TS_STEPS);
display.putRequest(NEWMODE, STATIONS);
if (yDelta>1) {
controlsEvent((touchStation - touchY)<0);
touchStation = touchY;
}
}
break;
}
default:
break;
}
}
if (config.store.dbgtouch) {
Serial.print(", x = ");
Serial.print(p.x);
Serial.print(", y = ");
Serial.println(p.y);
}
} else {
if (wastouched) {/* END TOUCH */
if (direct == TDS_REQUEST) {
uint32_t pressTicks = millis()-touchLongPress;
if( pressTicks < BTN_PRESS_TICKS*2){
if(pressTicks > 50) onBtnClick(EVT_BTNCENTER);
}else{
display.putRequest(NEWMODE, display.mode() == PLAYER ? STATIONS : PLAYER);
}
}
direct = TSD_STAY;
}
}
wastouched = istouched;
}
#endif // if TS_CS!=255
void onBtnLongPressStart(int id) {
switch ((controlEvt_e)id) {
case EVT_BTNLEFT:
case EVT_BTNRIGHT:
case EVT_BTNUP:
case EVT_BTNDOWN: {
lpId = id;
break;
}
case EVT_BTNCENTER:
case EVT_ENCBTNB: {
display.putRequest(NEWMODE, display.mode() == PLAYER ? STATIONS : PLAYER);
break;
}
case EVT_ENC2BTNB: {
display.putRequest(NEWMODE, display.mode() == PLAYER ? VOL : PLAYER);
break;
}
default:
break;
}
}
void onBtnLongPressStop(int id) {
switch ((controlEvt_e)id) {
case EVT_BTNLEFT:
case EVT_BTNRIGHT:
case EVT_BTNUP:
case EVT_BTNDOWN: {
lpId = -1;
break;
}
default:
break;
}
}
unsigned long lpdelay;
boolean checklpdelay(int m, unsigned long &tstamp) {
if (millis() - tstamp > m) {
tstamp = millis();
return true;
} else {
return false;
}
}
void onBtnDuringLongPress(int id) {
if (checklpdelay(BTN_LONGPRESS_LOOP_DELAY, lpdelay)) {
switch ((controlEvt_e)id) {
case EVT_BTNLEFT: {
controlsEvent(false);
break;
}
case EVT_BTNRIGHT: {
controlsEvent(true);
break;
}
case EVT_BTNUP:
case EVT_BTNDOWN: {
if (display.mode() == PLAYER) {
display.putRequest(NEWMODE, STATIONS);
}
if (display.mode() == STATIONS) {
controlsEvent(id == EVT_BTNDOWN);
}
break;
}
default:
break;
}
}
}
void controlsEvent(bool toRight, int8_t volDelta) {
if (display.mode() == NUMBERS) {
display.numOfNextStation = 0;
display.putRequest(NEWMODE, PLAYER);
}
if (display.mode() != STATIONS) {
display.putRequest(NEWMODE, VOL);
if(volDelta!=0){
int nv = config.store.volume+volDelta;
if(nv<0) nv=0;
if(nv>254) nv=254;
player.setVol((byte)nv, false);
}else{
player.stepVol(toRight);
}
}
if (display.mode() == STATIONS) {
display.resetQueue();
int p = toRight ? display.currentPlItem + 1 : display.currentPlItem - 1;
if (p < 1) p = config.store.countStation;
if (p > config.store.countStation) p = 1;
display.currentPlItem = p;
display.putRequest(DRAWPLAYLIST, p);
}
}
void onBtnClick(int id) {
switch ((controlEvt_e)id) {
case EVT_BTNLEFT: {
controlsEvent(false);
break;
}
case EVT_BTNCENTER:
case EVT_ENCBTNB:
case EVT_ENC2BTNB: {
if (display.mode() == NUMBERS) {
display.numOfNextStation = 0;
display.putRequest(NEWMODE, PLAYER);
}
if (display.mode() == PLAYER) {
player.toggle();
}
if (display.mode() == STATIONS) {
display.putRequest(NEWMODE, PLAYER);
player.play(display.currentPlItem);
}
break;
}
case EVT_BTNRIGHT: {
controlsEvent(true);
break;
}
case EVT_BTNUP:
case EVT_BTNDOWN: {
if (DSP_MODEL == DSP_DUMMY) {
if (id == EVT_BTNUP) {
player.next();
} else {
player.prev();
}
} else {
if (display.mode() == PLAYER) {
display.putRequest(NEWMODE, STATIONS);
}
if (display.mode() == STATIONS) {
controlsEvent(id == EVT_BTNDOWN);
}
}
break;
}
}
}
void onBtnDoubleClick(int id) {
switch ((controlEvt_e)id) {
case EVT_BTNLEFT: {
if (display.mode() != PLAYER) return;
player.prev();
break;
}
case EVT_BTNCENTER:
case EVT_ENCBTNB:
case EVT_ENC2BTNB: {
display.putRequest(NEWMODE, display.mode() == PLAYER ? VOL : PLAYER);
break;
}
case EVT_BTNRIGHT: {
if (display.mode() != PLAYER) return;
player.next();
break;
}
default:
break;
}
}
void setIRTolerance(uint8_t tl){
config.store.irtlp=tl;
config.save();
#if IR_PIN!=255
irrecv.setTolerance(config.store.irtlp);
#endif
}
void setEncAcceleration(uint16_t acc){
config.store.encacc=acc;
config.save();
#if ENC_BTNL!=255
encoder.setAcceleration(config.store.encacc);
#endif
#if ENC2_BTNL!=255
encoder2.setAcceleration(config.store.encacc);
#endif
}
void flipTS(){
#if TS_CS!=255
ts.setRotation(config.store.fliptouch?3:1);
#endif
}

View File

@@ -0,0 +1,38 @@
#ifndef controls_h
#define controls_h
#include "options.h"
enum controlEvt_e { EVT_BTNLEFT, EVT_BTNCENTER, EVT_BTNRIGHT, EVT_ENCBTNB, EVT_BTNUP, EVT_BTNDOWN, EVT_ENC2BTNB };
enum tsDirection_e { TSD_STAY, TSD_LEFT, TSD_RIGHT, TSD_UP, TSD_DOWN, TDS_REQUEST };
#if IR_PIN!=255
enum : uint8_t { IR_UP=0, IR_PREV=1, IR_PLAY=2, IR_NEXT=3, IR_DOWN=4, IR_1=5, IR_2=6, IR_3=7, IR_4=8, IR_5=9, IR_6=10, IR_7=11, IR_8=12, IR_9=13, IR_AST=14, IR_0=15, IR_HASH=16 };
#endif
boolean checklpdelay(int m, unsigned long &tstamp);
void initControls();
void loopControls();
void encoderLoop();
void encoder2Loop();
void irLoop();
void touchLoop();
void irNum(byte num);
void irBlink();
void controlsEvent(bool toRight, int8_t volDelta = 0);
void onBtnClick(int id);
void onBtnDoubleClick(int id);
void onBtnDuringLongPress(int id);
void onBtnLongPressStart(int id);
void onBtnLongPressStop(int id);
tsDirection_e tsDirection(uint16_t x, uint16_t y);
void setIRTolerance(uint8_t tl);
void setEncAcceleration(uint16_t acc);
void flipTS();
extern __attribute__((weak)) void ctrls_on_loop();
#endif

View File

@@ -0,0 +1,511 @@
#include "options.h"
#include "WiFi.h"
#include "time.h"
#include "display.h"
#include "player.h"
#include "network.h"
Display display;
#ifdef USE_NEXTION
Nextion nextion;
#endif
#ifndef DUMMYDISPLAY
//============================================================================================================================
DspCore dsp;
Page *pages[] = { new Page(), new Page(), new Page() };
#ifndef CORE_STACK_SIZE
#define CORE_STACK_SIZE 1024*3
#endif
#ifndef DSP_TASK_DELAY
#define DSP_TASK_DELAY 2
#endif
TaskHandle_t DspTask;
QueueHandle_t displayQueue;
void returnPlayer(){
display.putRequest(NEWMODE, PLAYER);
}
void Display::_createDspTask(){
xTaskCreatePinnedToCore(loopDspTask, "DspTask", CORE_STACK_SIZE, NULL, 4, &DspTask, !xPortGetCoreID());
}
void loopDspTask(void * pvParameters){
while(true){
if(displayQueue==NULL) break;
display.loop();
vTaskDelay(DSP_TASK_DELAY);
}
vTaskDelete( NULL );
DspTask=NULL;
}
void Display::init() {
#ifdef USE_NEXTION
nextion.begin();
#endif
_bootStep = 0;
dsp.initDisplay();
displayQueue=NULL;
displayQueue = xQueueCreate( 5, sizeof( requestParams_t ) );
while(displayQueue==NULL){;}
_createDspTask();
while(!_bootStep==0) { delay(10); }
//_pager.begin();
//_bootScreen();
}
void Display::_bootScreen(){
_boot = new Page();
_boot->addWidget(new ProgressWidget(bootWdtConf, bootPrgConf, BOOT_PRG_COLOR, 0));
_bootstring = (TextWidget*) &_boot->addWidget(new TextWidget(bootstrConf, 50, true, BOOT_TXT_COLOR, 0));
_pager.addPage(_boot);
_pager.setPage(_boot, true);
dsp.drawLogo(bootLogoTop);
_bootStep = 1;
}
void Display::_buildPager(){
_meta.init("*", metaConf, config.theme.meta, config.theme.metabg);
_title1.init("*", title1Conf, config.theme.title1, config.theme.background);
_clock.init(clockConf, 0, 0);
#if DSP_MODEL==DSP_NOKIA5110
_plcurrent.init("*", playlistConf, 0, 1);
#else
_plcurrent.init("*", playlistConf, config.theme.meta, config.theme.metabg);
#endif
#ifndef HIDE_TITLE2
_title2 = new ScrollWidget("*", title2Conf, config.theme.title2, config.theme.background);
#endif
#if !defined(DSP_LCD) && DSP_MODEL!=DSP_NOKIA5110
_plbackground = new FillWidget(playlBGConf, config.theme.metafill);
_metabackground = new FillWidget(metaBGConf, config.theme.metafill);
#endif
#if DSP_MODEL==DSP_NOKIA5110
_plbackground = new FillWidget(playlBGConf, 1);
//_metabackground = new FillWidget(metaBGConf, 1);
#endif
#ifndef HIDE_VU
_vuwidget = new VuWidget(vuConf, bandsConf, config.theme.vumax, config.theme.vumin, config.theme.background);
#endif
#ifndef HIDE_VOLBAR
_volbar = new SliderWidget(volbarConf, config.theme.volbarin, config.theme.background, 254, config.theme.volbarout);
#endif
#ifndef HIDE_HEAPBAR
_heapbar = new SliderWidget(heapbarConf, config.theme.buffer, config.theme.background, psramInit()?300000:1600 * AUDIOBUFFER_MULTIPLIER2);
#endif
#ifndef HIDE_VOL
_voltxt = new TextWidget(voltxtConf, 10, false, config.theme.vol, config.theme.background);
#endif
#ifndef HIDE_IP
_volip = new TextWidget(iptxtConf, 30, false, config.theme.ip, config.theme.background);
#endif
#ifndef HIDE_RSSI
_rssi = new TextWidget(rssiConf, 20, false, config.theme.rssi, config.theme.background);
#endif
_nums.init(numConf, 10, false, config.theme.digit, config.theme.background);
#ifndef HIDE_WEATHER
_weather = new ScrollWidget("*", weatherConf, config.theme.weather, config.theme.background);
#endif
if(_volbar) _footer.addWidget( _volbar);
if(_voltxt) _footer.addWidget( _voltxt);
if(_volip) _footer.addWidget( _volip);
if(_rssi) _footer.addWidget( _rssi);
if(_heapbar) _footer.addWidget( _heapbar);
if(_metabackground) pages[PG_PLAYER]->addWidget( _metabackground);
pages[PG_PLAYER]->addWidget(&_meta);
pages[PG_PLAYER]->addWidget(&_title1);
if(_title2) pages[PG_PLAYER]->addWidget(_title2);
if(_weather) pages[PG_PLAYER]->addWidget(_weather);
#ifdef BITRATE_FULL
_fullbitrate = new BitrateWidget(fullbitrateConf, config.theme.bitrate, config.theme.background);
pages[PG_PLAYER]->addWidget( _fullbitrate);
#else
_bitrate = new TextWidget(bitrateConf, 30, false, config.theme.bitrate, config.theme.background);
pages[PG_PLAYER]->addWidget( _bitrate);
#endif
if(_vuwidget) pages[PG_PLAYER]->addWidget( _vuwidget);
pages[PG_PLAYER]->addWidget(&_clock);
pages[PG_PLAYER]->addPage(&_footer);
if(_metabackground) pages[PG_DIALOG]->addWidget( _metabackground);
pages[PG_DIALOG]->addWidget(&_meta);
pages[PG_DIALOG]->addWidget(&_nums);
#if !defined(DSP_LCD) && DSP_MODEL!=DSP_NOKIA5110
pages[PG_DIALOG]->addPage(&_footer);
#endif
if(_plbackground) pages[PG_PLAYLIST]->addWidget( _plbackground);
pages[PG_PLAYLIST]->addWidget(&_plcurrent);
for(const auto& p: pages) _pager.addPage(p);
}
void Display::_apScreen() {
if(_boot) _pager.removePage(_boot);
#ifndef DSP_LCD
_boot = new Page();
#if DSP_MODEL!=DSP_NOKIA5110
_boot->addWidget(new FillWidget(metaBGConf, config.theme.metafill));
#endif
ScrollWidget *bootTitle = (ScrollWidget*) &_boot->addWidget(new ScrollWidget("*", apTitleConf, config.theme.meta, config.theme.metabg));
bootTitle->setText("ёRadio AP Mode");
TextWidget *apname = (TextWidget*) &_boot->addWidget(new TextWidget(apNameConf, 30, false, config.theme.title1, config.theme.background));
apname->setText(apNameTxt);
TextWidget *apname2 = (TextWidget*) &_boot->addWidget(new TextWidget(apName2Conf, 30, false, config.theme.metabg, config.theme.background));
apname2->setText(apSsid);
TextWidget *appass = (TextWidget*) &_boot->addWidget(new TextWidget(apPassConf, 30, false, config.theme.title1, config.theme.background));
appass->setText(apPassTxt);
TextWidget *appass2 = (TextWidget*) &_boot->addWidget(new TextWidget(apPass2Conf, 30, false, config.theme.metabg, config.theme.background));
appass2->setText(apPassword);
ScrollWidget *bootSett = (ScrollWidget*) &_boot->addWidget(new ScrollWidget("*", apSettConf, config.theme.title2, config.theme.background));
bootSett->setText(WiFi.softAPIP().toString().c_str(), apSettFmt);
_pager.addPage(_boot);
_pager.setPage(_boot);
#else
dsp.apScreen();
#endif
}
void Display::_start() {
if(_boot) _pager.removePage(_boot);
#ifdef USE_NEXTION
nextion.wake();
#endif
if (network.status != CONNECTED) {
_apScreen();
#ifdef USE_NEXTION
nextion.apScreen();
#endif
_bootStep = 2;
return;
}
#ifdef USE_NEXTION
nextion.putcmd("page player");
nextion.start();
#endif
_buildPager();
_mode = PLAYER;
config.setTitle(const_PlReady);
if(_heapbar) _heapbar->lock(!config.store.audioinfo);
if(_weather) _weather->lock(!config.store.showweather);
if(_weather && config.store.showweather) _weather->setText(const_getWeather);
if(_vuwidget) _vuwidget->lock();
if(_rssi) _rssi->setText(WiFi.RSSI(), rssiFmt);
#ifndef HIDE_IP
if(_volip) _volip->setText(WiFi.localIP().toString().c_str(), iptxtFmt);
#endif
_pager.setPage( pages[PG_PLAYER]);
_volume();
_station();
_time(false);
_bootStep = 2;
}
void Display::_showDialog(const char *title){
dsp.setScrollId(NULL);
_pager.setPage( pages[PG_DIALOG]);
#ifdef META_MOVE
_meta.moveTo(metaMove);
#endif
_meta.setAlign(WA_CENTER);
_meta.setText(title);
}
void Display::_setReturnTicker(uint8_t time_s){
_returnTicker.detach();
_returnTicker.once(time_s, returnPlayer);
}
void Display::_swichMode(displayMode_e newmode) {
#ifdef USE_NEXTION
nextion.swichMode(newmode);
#endif
if (newmode == _mode || network.status != CONNECTED) return;
_mode = newmode;
dsp.setScrollId(NULL);
if (newmode == PLAYER) {
numOfNextStation = 0;
_returnTicker.detach();
#ifdef META_MOVE
_meta.moveBack();
#endif
_meta.setAlign(WA_LEFT);
_meta.setText(config.station.name);
_nums.setText("");
_pager.setPage( pages[PG_PLAYER]);
}
if (newmode == VOL) {
#ifndef HIDE_IP
_showDialog(const_DlgVolume);
#else
_showDialog(WiFi.localIP().toString().c_str());
#endif
}
if (newmode == LOST) _showDialog(const_DlgLost);
if (newmode == UPDATING) _showDialog(const_DlgUpdate);
if (newmode == INFO || newmode == SETTINGS || newmode == TIMEZONE || newmode == WIFI) _showDialog(const_DlgNextion);
if (newmode == NUMBERS) _showDialog("");
if (newmode == STATIONS) {
_pager.setPage( pages[PG_PLAYLIST]);
_plcurrent.setText("");
currentPlItem = config.store.lastStation;
_drawPlaylist();
}
}
void Display::resetQueue(){
xQueueReset(displayQueue);
}
void Display::_drawPlaylist() {
char buf[PLMITEMLENGHT];
dsp.drawPlaylist(currentPlItem, buf);
_plcurrent.setText(buf);
/*#ifdef USE_NEXTION
nextion.drawPlaylist(currentPlItem);
#endif*/
_setReturnTicker(30);
}
void Display::_drawNextStationNum(uint16_t num) {
char plMenu[1][40];
char currentItemText[40] = {0};
config.fillPlMenu(plMenu, num, 1, true);
strlcpy(currentItemText, plMenu[0], 39);
_setReturnTicker(30);
_meta.setText(currentItemText);
_nums.setText(num, "%d");
/*#ifdef USE_NEXTION
nextion.drawNextStationNum(num);
#endif*/
}
void Display::putRequest(displayRequestType_e type, int payload){
if(displayQueue==NULL) return;
requestParams_t request;
request.type = type;
request.payload = payload;
xQueueSend(displayQueue, &request, portMAX_DELAY);
#ifdef USE_NEXTION
nextion.putRequest(request);
#endif
}
void Display::_layoutChange(bool played){
if(config.store.vumeter){
if(played){
if(_vuwidget) _vuwidget->unlock();
_clock.moveTo(clockMove);
if(_weather) _weather->moveTo(weatherMoveVU);
}else{
if(_vuwidget) if(!_vuwidget->locked()) _vuwidget->lock();
_clock.moveBack();
if(_weather) _weather->moveBack();
}
}else{
if(played){
if(_weather) _weather->moveTo(weatherMove);
_clock.moveBack();
}else{
if(_weather) _weather->moveBack();
_clock.moveBack();
}
}
}
void Display::loop() {
if(_bootStep==0) {
_pager.begin();
_bootScreen();
return;
}
if(displayQueue==NULL) return;
_pager.loop();
#ifdef USE_NEXTION
nextion.loop();
#endif
requestParams_t request;
if(xQueueReceive(displayQueue, &request, 20)){
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(BF_MP3);
}
}
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;
}
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;
}
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 DSPRSSI: if(_rssi){ _rssi->setText(request.payload, rssiFmt); } if (_heapbar && config.store.audioinfo) _heapbar->setValue(player.inBufferFilled()); break;
case PSTART: _layoutChange(true); break;
case PSTOP: _layoutChange(false); break;
case DSP_START: _start(); break;
default: break;
}
}
dsp.loop();
}
void Display::_station() {
_meta.setAlign(WA_LEFT);
_meta.setText(config.station.name);
/*#ifdef USE_NEXTION
nextion.newNameset(config.station.name);
nextion.bitrate(config.station.bitrate);
nextion.bitratePic(ICON_NA);
#endif*/
}
char *split(char *str, const char *delim) {
char *dmp = strstr(str, delim);
if (dmp == NULL) return NULL;
*dmp = '\0';
return dmp + strlen(delim);
}
void Display::_title() {
if (strlen(config.station.title) > 0) {
char tmpbuf[strlen(config.station.title)+1];
strlcpy(tmpbuf, config.station.title, strlen(config.station.title)+1);
char *stitle = split(tmpbuf, " - ");
if(stitle && _title2){
_title1.setText(tmpbuf);
_title2->setText(stitle);
}else{
_title1.setText(config.station.title);
if(_title2) _title2->setText("");
}
/*#ifdef USE_NEXTION
nextion.newTitle(config.station.title);
#endif*/
if (player_on_track_change) player_on_track_change();
}
}
void Display::_time(bool redraw) {
_clock.draw();
/*#ifdef USE_NEXTION
nextion.printClock(network.timeinfo);
#endif*/
}
void Display::_volume() {
if(_volbar) _volbar->setValue(config.store.volume);
#ifndef HIDE_VOL
if(_voltxt) _voltxt->setText(config.store.volume, voltxtFmt);
#endif
if(_mode==VOL) {
_setReturnTicker(3);
_nums.setText(config.store.volume, numtxtFmt);
}
/*#ifdef USE_NEXTION
nextion.setVol(config.store.volume, _mode == VOL);
#endif*/
}
void Display::flip(){ dsp.flip(); }
void Display::invert(){ dsp.invert(); }
void Display::setContrast(){
#if DSP_MODEL==DSP_NOKIA5110
dsp.setContrast(config.store.contrast);
#endif
}
bool Display::deepsleep(){
#if defined(LCD_I2C) || defined(DSP_OLED) || BRIGHTNESS_PIN!=255
dsp.sleep();
return true;
#endif
return false;
}
void Display::wakeup(){
#if defined(LCD_I2C) || defined(DSP_OLED) || BRIGHTNESS_PIN!=255
dsp.wake();
#endif
}
//============================================================================================================================
#else // !DUMMYDISPLAY
//============================================================================================================================
void Display::init(){
#ifdef USE_NEXTION
nextion.begin(true);
#endif
}
void Display::_start(){
#ifdef USE_NEXTION
nextion.start();
#endif
}
void Display::putRequest(displayRequestType_e type, int payload){
if(type==DSP_START) _start();
#ifdef USE_NEXTION
requestParams_t request;
request.type = type;
request.payload = payload;
nextion.putRequest(request);
#endif
}
//============================================================================================================================
#endif // DUMMYDISPLAY

113
yoRadio/src/core/display.h Normal file
View File

@@ -0,0 +1,113 @@
#ifndef display_h
#define display_h
#include "options.h"
#include "Arduino.h"
#include <Ticker.h>
#include "config.h"
#include "../displays/dspcore.h"
enum displayMode_e { PLAYER, VOL, STATIONS, NUMBERS, LOST, UPDATING, INFO, SETTINGS, TIMEZONE, WIFI, CLEAR };
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 };
struct requestParams_t
{
displayRequestType_e type;
int payload;
};
#if NEXTION_RX!=255 && NEXTION_TX!=255
#define USE_NEXTION
#include "../displays/nextion.h"
#endif
#ifndef DUMMYDISPLAY
void loopDspTask(void * pvParameters);
class Display {
public:
uint16_t currentPlItem;
uint16_t numOfNextStation;
displayMode_e _mode;
public:
Display() {};
displayMode_e mode() { return _mode; }
void mode(displayMode_e m) { _mode=m; }
void init();
void loop();
void _start();
bool ready() { return _bootStep==2; }
void resetQueue();
void putRequest(displayRequestType_e type, int payload=0);
void flip();
void invert();
bool deepsleep();
void wakeup();
void setContrast();
private:
ScrollWidget _meta, _title1, _plcurrent;
ScrollWidget *_weather;
ScrollWidget *_title2;
BitrateWidget *_fullbitrate;
FillWidget *_metabackground, *_plbackground;
SliderWidget *_volbar, *_heapbar;
Pager _pager;
Page _footer;
VuWidget *_vuwidget;
NumWidget _nums;
ProgressWidget _testprogress;
ClockWidget _clock;
Page *_boot;
TextWidget *_bootstring, *_volip, *_voltxt, *_rssi, *_bitrate;
Ticker _returnTicker;
uint8_t _bootStep;
void _time(bool redraw = false);
void _apScreen();
void _swichMode(displayMode_e newmode);
void _drawPlaylist();
void _volume();
void _title();
void _station();
void _drawNextStationNum(uint16_t num);
void _createDspTask();
void _showDialog(const char *title);
void _buildPager();
void _bootScreen();
void _setReturnTicker(uint8_t time_s);
void _layoutChange(bool played);
};
#else
class Display {
public:
uint16_t currentPlItem;
uint16_t numOfNextStation;
displayMode_e _mode;
public:
Display() {};
displayMode_e mode() { return _mode; }
void mode(displayMode_e m) { _mode=m; }
void init();
void _start();
void putRequest(displayRequestType_e type, int payload=0);
void loop(){}
bool ready() { return true; }
void resetQueue(){}
void centerText(const char* text, byte y, uint16_t fg, uint16_t bg){}
void rightText(const char* text, byte y, uint16_t fg, uint16_t bg){}
void flip(){}
void invert(){}
void setContrast(){}
bool deepsleep(){return true;}
void wakeup(){}
};
#endif
extern Display display;
#endif

126
yoRadio/src/core/mqtt.cpp Normal file
View File

@@ -0,0 +1,126 @@
#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<TimerCallbackFunction_t>(connectToMqtt));
mqttClient.onConnect(onMqttConnect);
mqttClient.onDisconnect(onMqttDisconnect);
mqttClient.onMessage(onMqttMessage);
if(strlen(MQTT_USER)>0) 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[BUFLEN*3];
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 (len == 0) return;
char buf[20];
strlcpy(buf, payload, len+1);
if (strcmp(buf, "prev") == 0) {
player.prev();
return;
}
if (strcmp(buf, "next") == 0) {
player.next();
return;
}
if (strcmp(buf, "toggle") == 0) {
player.toggle();
return;
}
if (strcmp(buf, "stop") == 0) {
player.mode = STOPPED;
telnet.info();
return;
}
if (strcmp(buf, "start") == 0 || strcmp(buf, "play") == 0) {
//player.play(config.store.lastStation);
player.request.station = config.store.lastStation;
return;
}
if (strcmp(buf, "boot") == 0 || strcmp(buf, "reboot") == 0) {
ESP.restart();
return;
}
if (strcmp(buf, "volm") == 0) {
player.stepVol(false);
return;
}
if (strcmp(buf, "volp") == 0) {
player.stepVol(true);
return;
}
int volume;
if ( sscanf(buf, "vol %d", &volume) == 1) {
if (volume < 0) volume = 0;
if (volume > 254) volume = 254;
player.setVol(volume, false);
return;
}
//uint16_t sb;
int sb;
if (sscanf(buf, "play %d", &sb) == 1 ) {
if (sb < 1) sb = 1;
if (sb >= config.store.countStation) sb = config.store.countStation;
//player.play(sb);
player.request.station = (uint16_t)sb;
player.request.doSave = true;
return;
}
}
#endif // ifdef MQTT_HOST

21
yoRadio/src/core/mqtt.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef mqtt_h
#define mqtt_h
#if __has_include("../../mqttoptions.h")
#include "../../mqttoptions.h"
#include <AsyncMqttClient.h>
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

View File

@@ -0,0 +1,738 @@
#include "netserver.h"
#include <SPIFFS.h>
#include "config.h"
#include "player.h"
#include "telnet.h"
#include "display.h"
#include "options.h"
#include "network.h"
#include "mqtt.h"
#include "controls.h"
#include <Update.h>
#ifndef MIN_MALLOC
#define MIN_MALLOC 24112
#endif
//#define CORS_DEBUG
NetServer netserver;
AsyncWebServer webserver(80);
AsyncWebSocket websocket("/ws");
AsyncUDP udp;
String processor(const String& var);
void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
void handleUpdate(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
void handleHTTPArgs(AsyncWebServerRequest * request);
void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len);
bool shouldReboot = false;
#ifdef MQTT_HOST
Ticker mqttplaylistticker;
bool mqttplaylistblock = false;
void mqttplaylistSend() {
mqttplaylistblock = true;
mqttplaylistticker.detach();
mqttPublishPlaylist();
mqttplaylistblock = false;
}
#endif
char* updateError() {
static char ret[140] = {0};
sprintf(ret, "Update failed with error (%d)<br /> %s", (int)Update.getError(), Update.errorString());
return ret;
}
bool NetServer::begin() {
importRequest = IMDONE;
irRecordEnable = false;
webserver.on("/", HTTP_ANY, handleHTTPArgs);
webserver.on(PLAYLIST_PATH, HTTP_GET, handleHTTPArgs);
webserver.on(INDEX_PATH, HTTP_GET, handleHTTPArgs);
webserver.on(SSIDS_PATH, HTTP_GET, handleHTTPArgs);
webserver.on("/upload", HTTP_POST, beginUpload, handleUpload);
webserver.on("/update", HTTP_GET, handleHTTPArgs);
webserver.on("/update", HTTP_POST, beginUpdate, handleUpdate);
webserver.on("/settings", HTTP_GET, handleHTTPArgs);
if (IR_PIN != 255) webserver.on("/ir", HTTP_GET, handleHTTPArgs);
webserver.serveStatic("/", SPIFFS, "/www/").setCacheControl("max-age=31536000");
#ifdef CORS_DEBUG
DefaultHeaders::Instance().addHeader(F("Access-Control-Allow-Origin"), F("*"));
DefaultHeaders::Instance().addHeader(F("Access-Control-Allow-Headers"), F("content-type"));
#endif
webserver.begin();
websocket.onEvent(onWsEvent);
webserver.addHandler(&websocket);
//echo -n "helle?" | socat - udp-datagram:255.255.255.255:44490,broadcast
if (udp.listen(44490)) {
udp.onPacket([](AsyncUDPPacket packet) {
if (strcmp((char*)packet.data(), "helle?") == 0)
packet.println(WiFi.localIP());
});
}
return true;
}
void NetServer::beginUpdate(AsyncWebServerRequest *request) {
shouldReboot = !Update.hasError();
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", shouldReboot ? "OK" : updateError());
response->addHeader("Connection", "close");
request->send(response);
}
void handleUpdate(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
if (!index) {
int target = (request->getParam("updatetarget", true)->value() == "spiffs") ? U_SPIFFS : U_FLASH;
Serial.printf("Update Start: %s\n", filename.c_str());
player.mode = STOPPED;
display.putRequest(NEWMODE, UPDATING);
if (!Update.begin(UPDATE_SIZE_UNKNOWN, target)) {
Update.printError(Serial);
request->send(200, "text/html", updateError());
}
}
if (!Update.hasError()) {
if (Update.write(data, len) != len) {
Update.printError(Serial);
request->send(200, "text/html", updateError());
}
}
if (final) {
if (Update.end(true)) {
Serial.printf("Update Success: %uB\n", index + len);
} else {
Update.printError(Serial);
request->send(200, "text/html", updateError());
}
}
}
void NetServer::beginUpload(AsyncWebServerRequest *request) {
if (request->hasParam("plfile", true, true)) {
netserver.importRequest = IMPL;
request->send(200);
} else if (request->hasParam("wifile", true, true)) {
netserver.importRequest = IMWIFI;
request->send(200);
} else {
request->send(404);
}
}
size_t NetServer::chunkedHtmlPageCallback(uint8_t* buffer, size_t maxLen, size_t index){
File requiredfile = SPIFFS.open(netserver.chunkedPathBuffer, "r");
if (!requiredfile) return 0;
size_t filesize = requiredfile.size();
size_t needread = filesize - index;
if (!needread) return 0;
size_t canread = (needread > maxLen) ? maxLen : needread;
DBGVB("[%s] seek to %d in %s and read %d bytes with maxLen=%d", __func__, index, netserver.chunkedPathBuffer, canread, maxLen);
requiredfile.seek(index, SeekSet);
requiredfile.read(buffer, canread);
index += canread;
if (requiredfile) requiredfile.close();
return canread;
}
void NetServer::chunkedHtmlPage(const String& contentType, AsyncWebServerRequest *request, const char * path, bool gzip) {
memset(chunkedPathBuffer, 0, sizeof(chunkedPathBuffer));
strlcpy(chunkedPathBuffer, path, sizeof(chunkedPathBuffer)-1);
AsyncWebServerResponse *response = request->beginChunkedResponse(contentType, chunkedHtmlPageCallback, processor);
xSemaphoreTake(player.playmutex, portMAX_DELAY);
request->send(response);
xSemaphoreGive(player.playmutex);
}
void NetServer::loop() {
if (shouldReboot) {
Serial.println("Rebooting...");
delay(100);
ESP.restart();
}
websocket.cleanupClients();
switch (importRequest) {
case IMPL: importPlaylist(); importRequest = IMDONE; break;
case IMWIFI: config.saveWifi(); importRequest = IMDONE; break;
default: break;
}
if (rssi < 255) requestOnChange(NRSSI, 0);
}
#if IR_PIN!=255
void NetServer::irToWs(const char* protocol, uint64_t irvalue) {
char buf[BUFLEN] = { 0 };
sprintf (buf, "{\"ircode\": %llu, \"protocol\": \"%s\"}", irvalue, protocol);
websocket.textAll(buf);
}
void NetServer::irValsToWs() {
if (!irRecordEnable) return;
char buf[BUFLEN] = { 0 };
sprintf (buf, "{\"irvals\": [%llu, %llu, %llu]}", config.ircodes.irVals[config.irindex][0], config.ircodes.irVals[config.irindex][1], config.ircodes.irVals[config.irindex][2]);
websocket.textAll(buf);
}
#endif
void NetServer::onWsMessage(void *arg, uint8_t *data, size_t len, uint8_t clientId) {
AwsFrameInfo *info = (AwsFrameInfo*)arg;
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
data[len] = 0;
char cmd[65], val[65];
if (config.parseWsCommand((const char*)data, cmd, val, 65)) {
if (strcmp(cmd, "getmode") == 0 ) { requestOnChange(GETMODE, clientId); return; }
if (strcmp(cmd, "getindex") == 0 ) { requestOnChange(GETINDEX, clientId); return; }
if (strcmp(cmd, "getsystem") == 0 ) { requestOnChange(GETSYSTEM, clientId); return; }
if (strcmp(cmd, "getscreen") == 0 ) { requestOnChange(GETSCREEN, clientId); return; }
if (strcmp(cmd, "gettimezone") == 0 ) { requestOnChange(GETTIMEZONE, clientId); return; }
if (strcmp(cmd, "getcontrols") == 0 ) { requestOnChange(GETCONTROLS, clientId); return; }
if (strcmp(cmd, "getweather") == 0 ) { requestOnChange(GETWEATHER, clientId); return; }
if (strcmp(cmd, "getactive") == 0 ) { requestOnChange(GETACTIVE, clientId); return; }
if (strcmp(cmd, "smartstart") == 0) {
byte valb = atoi(val);
config.store.smartstart = valb == 1 ? 1 : 2;
if (!player.isRunning() && config.store.smartstart == 1) config.store.smartstart = 0;
config.save();
return;
}
if (strcmp(cmd, "audioinfo") == 0) {
byte valb = atoi(val);
config.store.audioinfo = valb;
config.save();
display.putRequest(AUDIOINFO);
return;
}
if (strcmp(cmd, "vumeter") == 0) {
byte valb = atoi(val);
config.store.vumeter = valb;
config.save();
display.putRequest(SHOWVUMETER);
return;
}
if (strcmp(cmd, "softap") == 0) {
byte valb = atoi(val);
config.store.softapdelay = valb;
config.save();
return;
}
if (strcmp(cmd, "invertdisplay") == 0) {
byte valb = atoi(val);
config.store.invertdisplay = valb;
config.save();
display.invert();
return;
}
if (strcmp(cmd, "numplaylist") == 0) {
byte valb = atoi(val);
config.store.numplaylist = valb;
config.save();
display.putRequest(NEWMODE, CLEAR); display.putRequest(NEWMODE, PLAYER);
return;
}
if (strcmp(cmd, "fliptouch") == 0) {
byte valb = atoi(val);
config.store.fliptouch = valb == 1;
config.save();
flipTS();
return;
}
if (strcmp(cmd, "dbgtouch") == 0) {
byte valb = atoi(val);
config.store.dbgtouch = valb == 1;
config.save();
return;
}
if (strcmp(cmd, "flipscreen") == 0) {
byte valb = atoi(val);
config.store.flipscreen = valb;
config.save();
display.flip();
display.putRequest(NEWMODE, CLEAR); display.putRequest(NEWMODE, PLAYER);
return;
}
if (strcmp(cmd, "brightness") == 0) {
byte valb = atoi(val);
if (!config.store.dspon) requestOnChange(DSPON, 0);
config.store.brightness = valb;
config.setBrightness(true);
return;
}
if (strcmp(cmd, "screenon") == 0) {
byte valb = atoi(val);
config.setDspOn(valb == 1);
return;
}
if (strcmp(cmd, "contrast") == 0) {
byte valb = atoi(val);
config.store.contrast = valb;
config.save();
display.setContrast();
return;
}
if (strcmp(cmd, "tzh") == 0) {
int vali = atoi(val);
config.store.tzHour = vali;
return;
}
if (strcmp(cmd, "tzm") == 0) {
int vali = atoi(val);
config.store.tzMin = vali;
return;
}
if (strcmp(cmd, "sntp2") == 0) {
strlcpy(config.store.sntp2, val, 35);
return;
}
if (strcmp(cmd, "sntp1") == 0) {
strlcpy(config.store.sntp1, val, 35);
bool tzdone = false;
if (strlen(config.store.sntp1) > 0 && strlen(config.store.sntp2) > 0) {
configTime(config.store.tzHour * 3600 + config.store.tzMin * 60, config.getTimezoneOffset(), config.store.sntp1, config.store.sntp2);
tzdone = true;
} else if (strlen(config.store.sntp1) > 0) {
configTime(config.store.tzHour * 3600 + config.store.tzMin * 60, config.getTimezoneOffset(), config.store.sntp1);
tzdone = true;
}
if (tzdone) {
//network.requestTimeSync(true);
network.forceTimeSync = true;
config.save();
}
return;
}
if (strcmp(cmd, "volsteps") == 0) {
uint8_t valb = atoi(val);
config.store.volsteps = valb;
config.save();
return;
}
if (strcmp(cmd, "encacceleration") == 0) {
uint16_t valb = atoi(val);
setEncAcceleration(valb);
config.store.encacc = valb;
config.save();
return;
}
if (strcmp(cmd, "irtlp") == 0) {
uint8_t valb = atoi(val);
setIRTolerance(valb);
return;
}
if (strcmp(cmd, "showweather") == 0) {
uint8_t valb = atoi(val);
config.store.showweather = valb == 1;
config.save();
network.trueWeather=false;
network.forceWeather = true;
display.putRequest(SHOWWEATHER);
return;
}
if (strcmp(cmd, "lat") == 0) {
strlcpy(config.store.weatherlat, val, 10);
return;
}
if (strcmp(cmd, "lon") == 0) {
strlcpy(config.store.weatherlon, val, 10);
return;
}
if (strcmp(cmd, "key") == 0) {
strlcpy(config.store.weatherkey, val, 64);
config.save();
network.trueWeather=false;
display.putRequest(NEWMODE, CLEAR); display.putRequest(NEWMODE, PLAYER);
return;
}
/* RESETS */
if (strcmp(cmd, "reset") == 0) {
if (strcmp(val, "system") == 0) {
config.store.smartstart = 2;
config.store.audioinfo = false;
config.store.vumeter = false;
config.store.softapdelay = 0;
config.save();
display.putRequest(NEWMODE, CLEAR); display.putRequest(NEWMODE, PLAYER);
requestOnChange(GETSYSTEM, clientId);
return;
}
if (strcmp(val, "screen") == 0) {
config.store.flipscreen = false;
display.flip();
config.store.invertdisplay = false;
display.invert();
config.store.dspon = true;
config.store.brightness = 100;
config.setBrightness(false);
config.store.contrast = 55;
display.setContrast();
config.store.numplaylist = false;
config.save();
display.putRequest(NEWMODE, CLEAR); display.putRequest(NEWMODE, PLAYER);
requestOnChange(GETSCREEN, clientId);
return;
}
if (strcmp(val, "timezone") == 0) {
config.store.tzHour = 3;
config.store.tzMin = 0;
strlcpy(config.store.sntp1, "pool.ntp.org", 35);
strlcpy(config.store.sntp2, "0.ru.pool.ntp.org", 35);
config.save();
configTime(config.store.tzHour * 3600 + config.store.tzMin * 60, config.getTimezoneOffset(), config.store.sntp1, config.store.sntp2);
network.forceTimeSync = true;
requestOnChange(GETTIMEZONE, clientId);
return;
}
if (strcmp(val, "weather") == 0) {
config.store.showweather = 0;
strlcpy(config.store.weatherlat, "55.7512", 10);
strlcpy(config.store.weatherlon, "37.6184", 10);
strlcpy(config.store.weatherkey, "", 64);
config.save();
network.trueWeather=false;
display.putRequest(NEWMODE, CLEAR); display.putRequest(NEWMODE, PLAYER);
requestOnChange(GETWEATHER, clientId);
return;
}
if (strcmp(val, "controls") == 0) {
config.store.volsteps = 1;
config.store.fliptouch = false;
config.store.dbgtouch = false;
setEncAcceleration(200);
setIRTolerance(40);
requestOnChange(GETCONTROLS, clientId);
return;
}
} /* EOF RESETS */
if (strcmp(cmd, "volume") == 0) {
byte v = atoi(val);
player.setVol(v, false);
}
if (strcmp(cmd, "balance") == 0) {
int8_t valb = atoi(val);
player.setBalance(valb);
config.setBalance(valb);
netserver.requestOnChange(BALANCE, 0);
return;
}
if (strcmp(cmd, "treble") == 0) {
int8_t valb = atoi(val);
player.setTone(config.store.bass, config.store.middle, valb);
config.setTone(config.store.bass, config.store.middle, valb);
netserver.requestOnChange(EQUALIZER, 0);
return;
}
if (strcmp(cmd, "middle") == 0) {
int8_t valb = atoi(val);
player.setTone(config.store.bass, valb, config.store.trebble);
config.setTone(config.store.bass, valb, config.store.trebble);
netserver.requestOnChange(EQUALIZER, 0);
return;
}
if (strcmp(cmd, "bass") == 0) {
int8_t valb = atoi(val);
player.setTone(valb, config.store.middle, config.store.trebble);
config.setTone(valb, config.store.middle, config.store.trebble);
netserver.requestOnChange(EQUALIZER, 0);
return;
}
if (strcmp(cmd, "submitplaylist") == 0) {
// xSemaphoreTake(player.playmutex, portMAX_DELAY);
return;
}
if (strcmp(cmd, "submitplaylistdone") == 0) {
#ifdef MQTT_HOST
//mqttPublishPlaylist();
mqttplaylistticker.attach(5, mqttplaylistSend);
#endif
// xSemaphoreGive(player.playmutex);
if (player.isRunning()) {
player.request.station = config.store.lastStation;
player.request.doSave = false;
}
return;
}
#if IR_PIN!=255
if (strcmp(cmd, "irbtn") == 0) {
config.irindex = atoi(val);
irRecordEnable = (config.irindex >= 0);
config.irchck = 0;
irValsToWs();
if (config.irindex < 0) config.saveIR();
}
if (strcmp(cmd, "chkid") == 0) {
config.irchck = atoi(val);
}
if (strcmp(cmd, "irclr") == 0) {
byte cl = atoi(val);
config.ircodes.irVals[config.irindex][cl] = 0;
}
#endif
}
}
}
void NetServer::getPlaylist(uint8_t clientId) {
char buf[160] = {0};
sprintf(buf, "{\"file\": \"http://%s%s\"}", WiFi.localIP().toString().c_str(), PLAYLIST_PATH);
if (clientId == 0) { websocket.textAll(buf); } else { websocket.text(clientId, buf); }
}
bool NetServer::importPlaylist() {
File tempfile = SPIFFS.open(TMP_PATH, "r");
if (!tempfile) {
return false;
}
char sName[BUFLEN], sUrl[BUFLEN];
int sOvol;
String line = tempfile.readStringUntil('\n');
if (config.parseCSV(line.c_str(), sName, sUrl, sOvol)) {
File playlistfile = SPIFFS.open(PLAYLIST_PATH, "w");
playlistfile.println(line);
while (tempfile.available()) {
line = tempfile.readStringUntil('\n');
if (config.parseCSV(line.c_str(), sName, sUrl, sOvol)) {
playlistfile.println(line);
}
}
playlistfile.close();
tempfile.close();
SPIFFS.remove(TMP_PATH);
requestOnChange(PLAYLISTSAVED, 0);
return true;
}
if (config.parseJSON(line.c_str(), sName, sUrl, sOvol)) {
File playlistfile = SPIFFS.open(PLAYLIST_PATH, "w");
String wline = String(sName) + "\t" + String(sUrl) + "\t" + String(sOvol);
playlistfile.println(wline);
while (tempfile.available()) {
line = tempfile.readStringUntil('\n');
if (config.parseJSON(line.c_str(), sName, sUrl, sOvol)) {
wline = String(sName) + "\t" + String(sUrl) + "\t" + String(sOvol);
playlistfile.println(wline);
}
}
playlistfile.close();
tempfile.close();
SPIFFS.remove(TMP_PATH);
requestOnChange(PLAYLISTSAVED, 0);
return true;
}
tempfile.close();
SPIFFS.remove(TMP_PATH);
return false;
}
#ifndef DSP_NOT_FLIPPED
#define DSP_CAN_FLIPPED true
#else
#define DSP_CAN_FLIPPED false
#endif
#if !defined(HIDE_WEATHER) && (!defined(DUMMYDISPLAY) && !defined(USE_NEXTION))
#define SHOW_WEATHER true
#else
#define SHOW_WEATHER false
#endif
void NetServer::requestOnChange(requestType_e request, uint8_t clientId) {
char buf[BUFLEN * 2] = { 0 };
switch (request) {
case PLAYLIST: getPlaylist(clientId); break;
case PLAYLISTSAVED: config.indexPlaylist(); config.initPlaylist(); getPlaylist(clientId); break;
case GETACTIVE: {
bool dbgact = false, nxtn=false;
String act = F("\"group_wifi\",");
if (network.status == CONNECTED) {
act += F("\"group_system\",");
if (BRIGHTNESS_PIN != 255 || DSP_CAN_FLIPPED || DSP_MODEL == DSP_NOKIA5110 || dbgact) act += F("\"group_display\",");
#ifdef USE_NEXTION
act += F("\"group_nextion\",");
if (!SHOW_WEATHER || dbgact) act += F("\"group_weather\",");
nxtn=true;
#endif
#if defined(LCD_I2C) || defined(DSP_OLED)
act += F("\"group_oled\",");
#endif
#ifndef HIDE_VU
act += F("\"group_vu\",");
#endif
if (BRIGHTNESS_PIN != 255 || nxtn || dbgact) act += F("\"group_brightness\",");
if (DSP_CAN_FLIPPED || dbgact) act += F("\"group_tft\",");
if (TS_CS != 255 || dbgact) act += F("\"group_touch\",");
if (DSP_MODEL == DSP_NOKIA5110) act += F("\"group_nokia\",");
act += F("\"group_timezone\",");
if (SHOW_WEATHER || dbgact) act += F("\"group_weather\",");
act += F("\"group_controls\",");
if (ENC_BTNL != 255 || ENC2_BTNL != 255 || dbgact) act += F("\"group_encoder\",");
if (IR_PIN != 255 || dbgact) act += F("\"group_ir\",");
}
act = act.substring(0, act.length() - 1);
sprintf (buf, "{\"act\":[%s]}", act.c_str());
break;
}
case GETMODE: sprintf (buf, "{\"pmode\":\"%s\"}", network.status == CONNECTED ? "player" : "ap"); break;
case GETINDEX: requestOnChange(STATION, clientId); requestOnChange(TITLE, clientId); requestOnChange(VOLUME, clientId); requestOnChange(EQUALIZER, clientId); requestOnChange(BALANCE, clientId); requestOnChange(BITRATE, clientId); requestOnChange(MODE, clientId); return; break;
case GETSYSTEM: sprintf (buf, "{\"sst\":%d,\"aif\":%d,\"vu\":%d,\"softr\":%d}", config.store.smartstart != 2, config.store.audioinfo, config.store.vumeter, config.store.softapdelay); break;
case GETSCREEN: sprintf (buf, "{\"flip\":%d,\"inv\":%d,\"nump\":%d,\"tsf\":%d,\"tsd\":%d,\"dspon\":%d,\"br\":%d,\"con\":%d}", config.store.flipscreen, config.store.invertdisplay, config.store.numplaylist, config.store.fliptouch, config.store.dbgtouch, config.store.dspon, config.store.brightness, config.store.contrast); break;
case GETTIMEZONE: sprintf (buf, "{\"tzh\":%d,\"tzm\":%d,\"sntp1\":\"%s\",\"sntp2\":\"%s\"}", config.store.tzHour, config.store.tzMin, config.store.sntp1, config.store.sntp2); break;
case GETWEATHER: sprintf (buf, "{\"wen\":%d,\"wlat\":\"%s\",\"wlon\":\"%s\",\"wkey\":\"%s\"}", config.store.showweather, config.store.weatherlat, config.store.weatherlon, config.store.weatherkey); break;
case GETCONTROLS: sprintf (buf, "{\"vols\":%d,\"enca\":%d,\"irtl\":%d}", config.store.volsteps, config.store.encacc, config.store.irtlp); break;
case DSPON: sprintf (buf, "{\"dspontrue\":%d}", 1); break;
case STATION: sprintf (buf, "{\"nameset\": \"%s\"}", config.station.name); requestOnChange(ITEM, clientId); break;
case ITEM: sprintf (buf, "{\"current\": %d}", config.store.lastStation); break;
case TITLE: sprintf (buf, "{\"meta\": \"%s\"}", config.station.title); if (player.requestToStart) { telnet.info(); player.requestToStart = false; } else { telnet.printf("##CLI.META#: %s\n> ", config.station.title); } break;
case VOLUME: sprintf (buf, "{\"vol\": %d}", config.store.volume); break;
case NRSSI: sprintf (buf, "{\"rssi\": %d}", rssi); rssi = 255; break;
case BITRATE: sprintf (buf, "{\"bitrate\": %d}", config.station.bitrate); break;
case MODE: sprintf (buf, "{\"mode\": \"%s\"}", player.mode == PLAYING ? "playing" : "stopped"); break;
case EQUALIZER: sprintf (buf, "{\"bass\": %d, \"middle\": %d, \"trebble\": %d}", config.store.bass, config.store.middle, config.store.trebble); break;
case BALANCE: sprintf (buf, "{\"balance\": %d}", config.store.balance); break;
}
if (strlen(buf) > 0) {
if (clientId == 0) { websocket.textAll(buf); }else{ websocket.text(clientId, buf); }
#ifdef MQTT_HOST
if (clientId == 0 && (request == STATION || request == ITEM || request == TITLE || request == MODE)) mqttPublishStatus();
if (clientId == 0 && request == VOLUME) mqttPublishVolume();
#endif
}
}
String processor(const String& var) { // %Templates%
if (var == "VERSION") return VERSION;
return String();
}
void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
if (!index) {
request->_tempFile = SPIFFS.open(TMP_PATH , "w");
}
if (len) {
request->_tempFile.write(data, len);
//TODO check index+len size
}
if (final) {
request->_tempFile.close();
}
}
void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
switch (type) {
case WS_EVT_CONNECT: if (config.store.audioinfo) Serial.printf("[WEBSOCKET] client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); break;
case WS_EVT_DISCONNECT: if (config.store.audioinfo) Serial.printf("[WEBSOCKET] client #%u disconnected\n", client->id()); break;
case WS_EVT_DATA: netserver.onWsMessage(arg, data, len, client->id()); break;
case WS_EVT_PONG:
case WS_EVT_ERROR:
break;
}
}
void handleHTTPArgs(AsyncWebServerRequest * request) {
if (request->method() == HTTP_GET) {
DBGVB("[%s] client ip=%s request of %s", __func__, request->client()->remoteIP().toString().c_str(), request->url().c_str());
if (strcmp(request->url().c_str(), PLAYLIST_PATH) == 0 || strcmp(request->url().c_str(), SSIDS_PATH) == 0 || strcmp(request->url().c_str(), INDEX_PATH) == 0 || strcmp(request->url().c_str(), TMP_PATH) == 0) {
#ifdef MQTT_HOST
if (strcmp(request->url().c_str(), PLAYLIST_PATH) == 0) while (mqttplaylistblock) vTaskDelay(5);
#endif
netserver.chunkedHtmlPage("application/octet-stream", request, request->url().c_str());
return;
}
if (strcmp(request->url().c_str(), "/") == 0 && request->params() == 0) {
netserver.chunkedHtmlPage(String(), request, network.status == CONNECTED ? "/www/index.html" : "/www/settings.html");
return;
}
if (strcmp(request->url().c_str(), "/update") == 0 || strcmp(request->url().c_str(), "/settings") == 0 || strcmp(request->url().c_str(), "/ir") == 0) {
char buf[40] = { 0 };
sprintf(buf, "/www%s.html", request->url().c_str());
netserver.chunkedHtmlPage(String(), request, buf);
return;
}
}
if (network.status == CONNECTED) {
bool commandFound=false;
if (request->hasArg("start")) { player.request.station = config.store.lastStation; commandFound=true; }
if (request->hasArg("stop")) { player.mode = STOPPED; config.setTitle(const_PlStopped); commandFound=true; }
if (request->hasArg("toggle")) { player.toggle(); commandFound=true; }
if (request->hasArg("prev")) { player.prev(); commandFound=true; }
if (request->hasArg("next")) { player.next(); commandFound=true; }
if (request->hasArg("volm")) { player.stepVol(false); commandFound=true; }
if (request->hasArg("volp")) { player.stepVol(true); commandFound=true; }
if (request->hasArg("trebble") && request->hasArg("middle") && request->hasArg("bass")) {
AsyncWebParameter* pt = request->getParam("trebble", request->method() == HTTP_POST);
AsyncWebParameter* pm = request->getParam("middle", request->method() == HTTP_POST);
AsyncWebParameter* pb = request->getParam("bass", request->method() == HTTP_POST);
int t = atoi(pt->value().c_str());
int m = atoi(pm->value().c_str());
int b = atoi(pb->value().c_str());
player.setTone(b, m, t);
config.setTone(b, m, t);
netserver.requestOnChange(EQUALIZER, 0);
commandFound=true;
}
if (request->hasArg("ballance")) {
AsyncWebParameter* p = request->getParam("ballance", request->method() == HTTP_POST);
int b = atoi(p->value().c_str());
player.setBalance(b);
config.setBalance(b);
netserver.requestOnChange(BALANCE, 0);
commandFound=true;
}
if (request->hasArg("playstation") || request->hasArg("play")) {
AsyncWebParameter* p = request->getParam(request->hasArg("playstation") ? "playstation" : "play", request->method() == HTTP_POST);
int id = atoi(p->value().c_str());
if (id < 1) id = 1;
if (id > config.store.countStation) id = config.store.countStation;
player.request.station = id;
player.request.doSave = true;
commandFound=true;
DBGVB("[%s] play=%d", __func__, id);
}
if (request->hasArg("vol")) {
AsyncWebParameter* p = request->getParam("vol", request->method() == HTTP_POST);
int v = atoi(p->value().c_str());
if (v < 0) v = 0;
if (v > 254) v = 254;
config.store.volume = v;
player.setVol(v, false);
commandFound=true;
DBGVB("[%s] vol=%d", __func__, v);
}
if (request->hasArg("dspon")) {
AsyncWebParameter* p = request->getParam("dspon", request->method() == HTTP_POST);
int d = atoi(p->value().c_str());
config.setDspOn(d!=0);
commandFound=true;
}
if (request->hasArg("dim")) {
AsyncWebParameter* p = request->getParam("dim", request->method() == HTTP_POST);
int d = atoi(p->value().c_str());
if (d < 0) d = 0;
if (d > 100) d = 100;
config.store.brightness = (uint8_t)d;
config.setBrightness(true);
commandFound=true;
}
if (request->hasArg("sleep")) {
AsyncWebParameter* sfor = request->getParam("sleep", request->method() == HTTP_POST);
int sford = atoi(sfor->value().c_str());
int safterd = 0;
if(request->hasArg("after")){
AsyncWebParameter* safter = request->getParam("after", request->method() == HTTP_POST);
safterd = atoi(safter->value().c_str());
}
if(sford > 0 && safterd >= 0){
request->send(200);
config.sleepForAfter(sford, safterd);
commandFound=true;
}
}
if (request->params() > 0) {
request->send(commandFound?200:404);
return;
}
} else {
if (request->params() > 0) {
request->send(404);
return;
}
}
}

View File

@@ -0,0 +1,41 @@
#ifndef netserver_h
#define netserver_h
#include "Arduino.h"
#include "ESPAsyncWebServer.h"
#include "AsyncUDP.h"
enum requestType_e : uint8_t { PLAYLIST=1, STATION=2, ITEM=3, TITLE=4, VOLUME=5, NRSSI=6, BITRATE=7, MODE=8, EQUALIZER=9, BALANCE=10, PLAYLISTSAVED=11, GETMODE=12, GETINDEX=13, GETACTIVE=14, GETSYSTEM=15, GETSCREEN=16, GETTIMEZONE=17, GETWEATHER=18, GETCONTROLS=19, DSPON=20 };
enum import_e : uint8_t { IMDONE=0, IMPL=1, IMWIFI=2 };
class NetServer {
public:
import_e importRequest;
bool resumePlay;
char chunkedPathBuffer[40];
public:
NetServer() {};
bool begin();
void loop();
void requestOnChange(requestType_e request, uint8_t clientId);
void setRSSI(int val) { rssi = val; };
void chunkedHtmlPage(const String& contentType, AsyncWebServerRequest *request, const char * path, bool gzip = false);
void onWsMessage(void *arg, uint8_t *data, size_t len, uint8_t clientId);
#if IR_PIN!=255
bool irRecordEnable;
void irToWs(const char* protocol, uint64_t irvalue);
void irValsToWs();
#endif
private:
requestType_e request;
int rssi;
void getPlaylist(uint8_t clientId);
bool importPlaylist();
static size_t chunkedHtmlPageCallback(uint8_t* buffer, size_t maxLen, size_t index);
static void beginUpload(AsyncWebServerRequest *request);
static void beginUpdate(AsyncWebServerRequest *request);
};
extern NetServer netserver;
#endif

View File

@@ -0,0 +1,288 @@
#include "network.h"
#include "WiFi.h"
#include "display.h"
#include "options.h"
#include "config.h"
#include "telnet.h"
#include "netserver.h"
Network network;
TaskHandle_t syncTaskHandle;
bool getWeather(char *wstr);
void doSync(void * pvParameters);
void ticks() {
static const uint16_t weatherSyncInterval=1800;
//static const uint16_t weatherSyncIntervalFail=10;
static const uint16_t timeSyncInterval=3600;
static uint16_t timeSyncTicks = 0;
static uint16_t weatherSyncTicks = 0;
static bool divrssi;
timeSyncTicks++;
weatherSyncTicks++;
divrssi = !divrssi;
if(network.forceTimeSync || network.forceWeather){
xTaskCreatePinnedToCore(doSync, "doSync", 1024 * 4, NULL, 0, &syncTaskHandle, 0);
}
if(timeSyncTicks >= timeSyncInterval){
timeSyncTicks=0;
network.forceTimeSync = true;
}
if(weatherSyncTicks >= weatherSyncInterval){
weatherSyncTicks=0;
network.forceWeather = true;
}
if(network.timeinfo.tm_year>100) {
network.timeinfo.tm_sec++;
mktime(&network.timeinfo);
display.putRequest(CLOCK);
}
if(divrssi) {
int rs = WiFi.RSSI();
netserver.setRSSI(rs);
display.putRequest(DSPRSSI, rs);
}
}
#define DBGAP false
void Network::begin() {
config.initNetwork();
ctimer.detach();
forceTimeSync = forceWeather = true;
if (config.ssidsCount == 0 || DBGAP) {
raiseSoftAP();
return;
}
byte ls = (config.store.lastSSID == 0 || config.store.lastSSID > config.ssidsCount) ? 0 : config.store.lastSSID - 1;
byte startedls = ls;
byte errcnt = 0;
WiFi.mode(WIFI_STA);
while (true) {
Serial.printf("[BOOT]\tAttempt to connect to %s...\n", config.ssids[ls].ssid);
display.putRequest(BOOTSTRING, ls);
WiFi.begin(config.ssids[ls].ssid, config.ssids[ls].password);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
errcnt++;
if (errcnt > 16) {
errcnt = 0;
ls++;
if (ls > config.ssidsCount - 1) ls = 0;
break;
}
}
if (WiFi.status() != WL_CONNECTED && ls == startedls) {
raiseSoftAP();
return;
}
if (WiFi.status() == WL_CONNECTED) {
config.setLastSSID(ls + 1);
break; // отстрелялись
}
}
Serial.println(".");
digitalWrite(LED_BUILTIN, LOW);
status = CONNECTED;
WiFi.setSleep(false);
weatherBuf=NULL;
trueWeather = false;
#if (DSP_MODEL!=DSP_DUMMY || defined(USE_NEXTION)) && !defined(HIDE_WEATHER)
weatherBuf = (char *) malloc(sizeof(char) * WEATHER_STRING_L);
memset(weatherBuf, 0, WEATHER_STRING_L);
#endif
if(strlen(config.store.sntp1)>0 && strlen(config.store.sntp2)>0){
configTime(config.store.tzHour * 3600 + config.store.tzMin * 60, config.getTimezoneOffset(), config.store.sntp1, config.store.sntp2);
}else if(strlen(config.store.sntp1)>0){
configTime(config.store.tzHour * 3600 + config.store.tzMin * 60, config.getTimezoneOffset(), config.store.sntp1);
}
ctimer.attach(1, ticks);
if (network_on_connect) network_on_connect();
}
void Network::requestTimeSync(bool withTelnetOutput, uint8_t clientId) {
if (withTelnetOutput) {
char timeStringBuff[50];
strftime(timeStringBuff, sizeof(timeStringBuff), "%Y-%m-%dT%H:%M:%S", &timeinfo);
if (config.store.tzHour < 0) {
telnet.printf(clientId, "##SYS.DATE#: %s%03d:%02d\n> ", timeStringBuff, config.store.tzHour, config.store.tzMin);
} else {
telnet.printf(clientId, "##SYS.DATE#: %s+%02d:%02d\n> ", timeStringBuff, config.store.tzHour, config.store.tzMin);
}
}
}
void rebootTime() {
ESP.restart();
}
void Network::raiseSoftAP() {
WiFi.mode(WIFI_AP);
WiFi.softAP(apSsid, apPassword);
Serial.printf("\n\nRunning in AP mode.\nConnect to AP %s with password %s for settings.\n\n", apSsid, apPassword);
status = SOFT_AP;
if(config.store.softapdelay>0)
rtimer.once(config.store.softapdelay*60, rebootTime);
}
void Network::requestWeatherSync(){
display.putRequest(NEWWEATHER);
}
void doSync( void * pvParameters ) {
static uint8_t tsFailCnt = 0;
//static uint8_t wsFailCnt = 0;
if(network.forceTimeSync){
network.forceTimeSync = false;
if(getLocalTime(&network.timeinfo)){
tsFailCnt = 0;
network.forceTimeSync = false;
mktime(&network.timeinfo);
display.putRequest(CLOCK);
network.requestTimeSync(true);
}else{
if(tsFailCnt<4){
network.forceTimeSync = true;
tsFailCnt++;
}else{
network.forceTimeSync = false;
tsFailCnt=0;
}
}
}
if(network.weatherBuf && (strlen(config.store.weatherkey)!=0 && config.store.showweather) && network.forceWeather){
network.forceWeather = false;
network.trueWeather=getWeather(network.weatherBuf);
}
vTaskDelete( NULL );
}
bool getWeather(char *wstr) {
#if (DSP_MODEL!=DSP_DUMMY || defined(USE_NEXTION)) && !defined(HIDE_WEATHER)
WiFiClient client;
const char* host = "api.openweathermap.org";
if (!client.connect(host, 80)) {
Serial.println("## OPENWEATHERMAP ###: connection failed");
return false;
}
char httpget[250] = {0};
sprintf(httpget, "GET /data/2.5/weather?lat=%s&lon=%s&units=%s&lang=%s&appid=%s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", config.store.weatherlat, config.store.weatherlon, weatherUnits, weatherLang, config.store.weatherkey, host);
client.print(httpget);
unsigned long timeout = millis();
while (client.available() == 0) {
if (millis() - timeout > 2000UL) {
Serial.println("## OPENWEATHERMAP ###: client available timeout !");
client.stop();
return false;
}
}
timeout = millis();
String line = "";
if (client.connected()) {
while (client.available())
{
line = client.readStringUntil('\n');
if (strstr(line.c_str(), "\"temp\"") != NULL) {
client.stop();
break;
}
if ((millis() - timeout) > 500)
{
client.stop();
Serial.println("## OPENWEATHERMAP ###: client read timeout !");
return false;
}
}
}
if (strstr(line.c_str(), "\"temp\"") == NULL) {
Serial.println("## OPENWEATHERMAP ###: weather not found !");
return false;
}
char *tmpe;
char *tmps;
const char* cursor = line.c_str();
char desc[120], temp[20], hum[20], press[20], icon[5];
tmps = strstr(cursor, "\"description\":\"");
if (tmps == NULL) { Serial.println("## OPENWEATHERMAP ###: description not found !"); return false;}
tmps += 15;
tmpe = strstr(tmps, "\",\"");
if (tmpe == NULL) { Serial.println("## OPENWEATHERMAP ###: description not found !"); return false;}
strlcpy(desc, tmps, tmpe - tmps + 1);
cursor = tmpe + 2;
// "ясно","icon":"01d"}],
tmps = strstr(cursor, "\"icon\":\"");
if (tmps == NULL) { Serial.println("## OPENWEATHERMAP ###: icon not found !"); return false;}
tmps += 8;
tmpe = strstr(tmps, "\"}");
if (tmpe == NULL) { Serial.println("## OPENWEATHERMAP ###: icon not found !"); return false;}
strlcpy(icon, tmps, tmpe - tmps + 1);
cursor = tmpe + 2;
tmps = strstr(cursor, "\"temp\":");
if (tmps == NULL) { Serial.println("## OPENWEATHERMAP ###: temp not found !"); return false;}
tmps += 7;
tmpe = strstr(tmps, ",\"");
if (tmpe == NULL) { Serial.println("## OPENWEATHERMAP ###: temp not found !"); return false;}
strlcpy(temp, tmps, tmpe - tmps + 1);
cursor = tmpe + 2;
float tempf = atof(temp);
tmps = strstr(cursor, "\"pressure\":");
if (tmps == NULL) { Serial.println("## OPENWEATHERMAP ###: pressure not found !"); return false;}
tmps += 11;
tmpe = strstr(tmps, ",\"");
if (tmpe == NULL) { Serial.println("## OPENWEATHERMAP ###: pressure not found !"); return false;}
strlcpy(press, tmps, tmpe - tmps + 1);
cursor = tmpe + 2;
int pressi = (float)atoi(press) / 1.333;
tmps = strstr(cursor, "humidity\":");
if (tmps == NULL) { Serial.println("## OPENWEATHERMAP ###: humidity not found !"); return false;}
tmps += 10;
tmpe = strstr(tmps, ",\"");
if (tmpe == NULL) { Serial.println("## OPENWEATHERMAP ###: humidity not found !"); return false;}
strlcpy(hum, tmps, tmpe - tmps + 1);
#ifdef USE_NEXTION
nextion.putcmdf("press_txt.txt=\"%dmm\"", pressi);
nextion.putcmdf("hum_txt.txt=\"%d%%\"", atoi(hum));
char cmd[30];
snprintf(cmd, sizeof(cmd)-1,"temp_txt.txt=\"%.1f\"", tempf);
nextion.putcmd(cmd);
int iconofset;
if(strstr(icon,"01")!=NULL) iconofset = 0;
else if(strstr(icon,"02")!=NULL) iconofset = 1;
else if(strstr(icon,"03")!=NULL) iconofset = 2;
else if(strstr(icon,"04")!=NULL) iconofset = 3;
else if(strstr(icon,"09")!=NULL) iconofset = 4;
else if(strstr(icon,"10")!=NULL) iconofset = 5;
else if(strstr(icon,"11")!=NULL) iconofset = 6;
else if(strstr(icon,"13")!=NULL) iconofset = 7;
else if(strstr(icon,"50")!=NULL) iconofset = 8;
else iconofset = 9;
nextion.putcmd("cond_img.pic", 50+iconofset);
nextion.weatherVisible(1);
#endif
Serial.printf("## OPENWEATHERMAP ###: description: %s, temp:%.1f C, pressure:%dmmHg, humidity:%s%%\n", desc, tempf, pressi, hum);
#ifdef WEATHER_FMT_SHORT
sprintf(wstr, weatherFmt, tempf, pressi, hum);
#else
sprintf(wstr, weatherFmt, desc, tempf, pressi, hum);
#endif
network.requestWeatherSync();
return true;
#endif // if (DSP_MODEL!=DSP_DUMMY || defined(USE_NEXTION)) && !defined(HIDE_WEATHER)
return false;
}

View File

@@ -0,0 +1,37 @@
#ifndef network_h
#define network_h
#include <Ticker.h>
#include "time.h"
#define apSsid "yoRadioAP"
#define apPassword "12345987"
//#define TSYNC_DELAY 10800000 // 1000*60*60*3 = 3 hours
#define TSYNC_DELAY 3600000 // 1000*60*60 = 1 hour
#define WEATHER_STRING_L 254
enum n_Status_e { CONNECTED, SOFT_AP, FAILED };
class Network {
public:
n_Status_e status;
struct tm timeinfo;
bool firstRun, forceTimeSync, forceWeather;
//uint8_t tsFailCnt, wsFailCnt;
public:
Network() {};
void begin();
void requestTimeSync(bool withTelnetOutput=false, uint8_t clientId=0);
void requestWeatherSync();
Ticker ctimer;
char *weatherBuf;
bool trueWeather;
private:
Ticker rtimer;
void raiseSoftAP();
};
extern Network network;
extern __attribute__((weak)) void network_on_connect();
#endif

335
yoRadio/src/core/options.h Normal file
View File

@@ -0,0 +1,335 @@
#ifndef options_h
#define options_h
#define VERSION "0.8.00b"
/*******************************************************
DO NOT EDIT THIS FILE.
ALL YOUR SETTINGS WILL BE OVERWRITTEN DURING THE UPDATE.
STORE YOUR SETTINGS IN THE *** myoptions.h *** FILE.
********************************************************/
#if __has_include("../../myoptions.h")
#include "../../myoptions.h" /* <- write your variable values here */
#endif
#if __has_include("../../mytheme.h")
#include "../../mytheme.h" /* <- Theme file */
#endif
/*******************************************************
The connection tables are located here https://github.com/e2002/yoradio#connection-tables
********************************************************/
#define DSP_DUMMY 0 // without display
#define DSP_ST7735 1 // 160x128 1.8' or 128x128 1.44' or 160x80 0.96' https://aliexpress.com/item/1005002822797745.html
#define DSP_SSD1306 2 // 128x64 0.96' https://aliexpress.com/item/1005001621806398.html
#define DSP_NOKIA5110 3 // 84x48 1.6' https://aliexpress.com/item/1005001621837569.html
#define DSP_ST7789 4 // 320x240 2.4' https://aliexpress.com/item/32960241206.html
#define DSP_SH1106 5 // 128x64 1.3' https://aliexpress.com/item/32683094040.html
#define DSP_1602I2C 6 // 16x2 https://aliexpress.com/item/32305776560.html
#define DSP_SSD1306x32 7 // 128x32 0.91' https://aliexpress.com/item/32798439084.html
#define DSP_SSD1327 8 // 128x128 1.5' https://aliexpress.com/item/1005001414175498.html
#define DSP_ILI9341 9 // 320x240 3.2' https://aliexpress.com/item/33048191074.html
#define DSP_SSD1305 10 // 128x64 2.4' SSD1305 and SSD1309 SPI https://aliexpress.com/item/32950307344.html
#define DSP_SH1107 11 // 128x64 1.3' https://aliexpress.com/item/4000551696674.html
#define DSP_1602 12 // 16x2 https://aliexpress.com/item/32685016568.html
#define DSP_GC9106 13 // 160x80 0.96' (looks like ST7735S, but it's not him) https://aliexpress.com/item/32947890530.html
#define DSP_2004I2C 14 // 20x4 https://aliexpress.com/item/32783128355.html
#define DSP_2004 15 // 20x4 https://aliexpress.com/item/32783128355.html
#define DSP_SSD1305I2C 16 // 128x64 2.4' SSD1305 and SSD1309 I2C https://aliexpress.com/item/32950307344.html
#define DSP_ILI9225 17 // 220x176 2.0' https://aliexpress.com/item/32952021835.html
#define DSP_ST7789_240 18 // 240x240 1.3' https://aliexpress.com/item/32996979276.html
/* !!! DSP_ST7789_240 requires further development when used in conjunction with the VS1053 module !!! See the link https://www.instructables.com/Adding-CS-Pin-to-13-LCD/ */
#define DSP_CUSTOM 101 // your display
#ifndef DSP_MODEL
#define DSP_MODEL DSP_DUMMY
#endif
/* TFT DISPLAY */
#ifndef TFT_CS
#define TFT_CS 5
#endif
#ifndef TFT_RST
#define TFT_RST 15 // Or set to -1 and connect to Esp EN pin
#endif
#ifndef TFT_DC
#define TFT_DC 4
#endif
/* NEXTION */
#ifndef NEXTION_RX
#define NEXTION_RX 255
#endif
#ifndef NEXTION_TX
#define NEXTION_TX 255
#endif
/* OLED I2C DISPLAY */
#ifndef I2C_SDA
#define I2C_SDA 21
#endif
#ifndef I2C_SCL
#define I2C_SCL 22
#endif
#ifndef I2C_RST
#define I2C_RST -1
#endif
/* VS1053 */
#ifndef VS1053_CS
#define VS1053_CS 255 // 27
#endif
#ifndef VS1053_DCS
#define VS1053_DCS 25
#endif
#ifndef VS1053_DREQ
#define VS1053_DREQ 26
#endif
#ifndef VS1053_RST
#define VS1053_RST -1 // set to -1 if connected to Esp EN pin
#endif
/* I2S DAC */
#ifndef I2S_DOUT
#define I2S_DOUT 27 // DIN connection
#endif
#ifndef I2S_BCLK
#define I2S_BCLK 26 // BCLK Bit clock
#endif
#ifndef I2S_LRC
#define I2S_LRC 25 // WSEL Left Right Clock
#endif
/* ENCODER */
#ifndef ENC_BTNL
#define ENC_BTNL 255
#endif
#ifndef ENC_BTNB
#define ENC_BTNB 255
#endif
#ifndef ENC_BTNR
#define ENC_BTNR 255
#endif
#ifndef ENC_INTERNALPULLUP // Thanks for Buska1968. See this topic: https://4pda.to/forum/index.php?s=&showtopic=1010378&view=findpost&p=113385448
#define ENC_INTERNALPULLUP true
#endif
#ifndef ENC_HALFQUARD
#define ENC_HALFQUARD false
#endif
#ifndef ENC2_BTNL
#define ENC2_BTNL 255
#endif
#ifndef ENC2_BTNB
#define ENC2_BTNB 255
#endif
#ifndef ENC2_BTNR
#define ENC2_BTNR 255
#endif
#ifndef ENC2_INTERNALPULLUP
#define ENC2_INTERNALPULLUP true
#endif
#ifndef ENC2_HALFQUARD
#define ENC2_HALFQUARD false
#endif
/* BUTTONS */
#ifndef BTN_LEFT
#define BTN_LEFT 255
#endif
#ifndef BTN_CENTER
#define BTN_CENTER 255
#endif
#ifndef BTN_RIGHT
#define BTN_RIGHT 255
#endif
#ifndef BTN_UP
#define BTN_UP 255
#endif
#ifndef BTN_DOWN
#define BTN_DOWN 255
#endif
#ifndef BTN_INTERNALPULLUP
#define BTN_INTERNALPULLUP true
#endif
#ifndef BTN_LONGPRESS_LOOP_DELAY
#define BTN_LONGPRESS_LOOP_DELAY 200 // delay between calling DuringLongPress event
#endif
#ifndef BTN_CLICK_TICKS
#define BTN_CLICK_TICKS 300
#endif
#ifndef BTN_PRESS_TICKS
#define BTN_PRESS_TICKS 500
#endif
/* TOUCH SCREEN */
#ifndef TS_CS
#define TS_CS 255
#endif
/* LCD DISPLAY */
#ifndef LCD_RS
#define LCD_RS 255
#endif
#ifndef LCD_E
#define LCD_E 255
#endif
#ifndef LCD_D4
#define LCD_D4 255
#endif
#ifndef LCD_D5
#define LCD_D5 255
#endif
#ifndef LCD_D6
#define LCD_D6 255
#endif
#ifndef LCD_D7
#define LCD_D7 255
#endif
/* ESP DEVBOARD */
#ifndef LED_BUILTIN
#define LED_BUILTIN 2
#endif
/* Other settings. You can overwrite them in the myoptions.h file */
#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
#ifndef BRIGHTNESS_PIN
#define BRIGHTNESS_PIN 255 // BRIGHTNESS Pin
#endif
#ifndef PLAYER_FORCE_MONO
#define PLAYER_FORCE_MONO false // mono option - false stereo, true mono
#endif
#ifndef I2S_INTERNAL
#define I2S_INTERNAL false // If true - use esp32 internal DAC
#endif
#ifndef ROTATE_90
#define ROTATE_90 false // Optional 90 degree rotation for square displays
#endif
#ifndef WAKE_PIN
#define WAKE_PIN 255 // Wake Pin (for manual wakeup from sleep mode. can match with 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)
#endif
/*
*** ST7735 display submodel ***
INITR_BLACKTAB // 1.8' https://aliexpress.ru/item/1005002822797745.html
See this note If INITR_BLACKTAB have a noisy line on one side of the screen https://github.com/e2002/yoradio#note-if-initr_blacktab-dsp-have-a-noisy-line-on-one-side-of-the-screen-then-in-adafruit_st7735cpp
INITR_144GREENTAB // 1.44' https://aliexpress.ru/item/1005002822797745.html
INITR_MINI160x80 // 0.96' 160x80 ST7735S https://????
INITR_GREENTAB
INITR_REDTAB
*/
#ifndef DTYPE
#define DTYPE INITR_BLACKTAB
#endif
/* IR */
#ifndef IR_PIN
#define IR_PIN 255
#endif
#ifndef IR_TIMEOUT
#define IR_TIMEOUT 80 // kTimeout, see IRremoteESP8266 documentation
#endif
/* THEMES */
/* color name R G B */
#ifndef COLOR_BACKGROUND
#define COLOR_BACKGROUND 0, 0, 0
#endif
#ifndef COLOR_STATION_NAME
#define COLOR_STATION_NAME 0, 0, 0
#endif
#ifndef COLOR_STATION_BG
#define COLOR_STATION_BG 231, 211, 90
#endif
#ifndef COLOR_STATION_FILL
#define COLOR_STATION_FILL 231, 211, 90
#endif
#ifndef COLOR_SNG_TITLE_1
#define COLOR_SNG_TITLE_1 255, 255, 255
#endif
#ifndef COLOR_SNG_TITLE_2
#define COLOR_SNG_TITLE_2 165, 162, 132
#endif
#ifndef COLOR_WEATHER
#define COLOR_WEATHER 255, 150, 0
#endif
#ifndef COLOR_VU_MAX
#define COLOR_VU_MAX 231, 211, 90
#endif
#ifndef COLOR_VU_MIN
#define COLOR_VU_MIN 123, 125, 123
#endif
#ifndef COLOR_CLOCK
#define COLOR_CLOCK 231, 211, 90
#endif
#ifndef COLOR_SECONDS
#define COLOR_SECONDS 231, 211, 90
#endif
#ifndef COLOR_DAY_OF_W
#define COLOR_DAY_OF_W 255, 255, 255
#endif
#ifndef COLOR_DATE
#define COLOR_DATE 255, 255, 255
#endif
#ifndef COLOR_HEAP
#define COLOR_HEAP 41, 40, 41
#endif
#ifndef COLOR_BUFFER
#define COLOR_BUFFER 165, 162, 132
#endif
#ifndef COLOR_IP
#define COLOR_IP 165, 162, 132
#endif
#ifndef COLOR_VOLUME_VALUE
#define COLOR_VOLUME_VALUE 165, 162, 132
#endif
#ifndef COLOR_RSSI
#define COLOR_RSSI 165, 162, 132
#endif
#ifndef COLOR_VOLBAR_OUT
#define COLOR_VOLBAR_OUT 231, 211, 90
#endif
#ifndef COLOR_VOLBAR_IN
#define COLOR_VOLBAR_IN 231, 211, 90
#endif
#ifndef COLOR_DIGITS
#define COLOR_DIGITS 255, 255, 255
#endif
#ifndef COLOR_DIVIDER
#define COLOR_DIVIDER 165, 162, 132
#endif
#ifndef COLOR_PLAYLIST_0
#define COLOR_PLAYLIST_0 115, 115, 115
#endif
#ifndef COLOR_PLAYLIST_1
#define COLOR_PLAYLIST_1 89, 89, 89
#endif
#ifndef COLOR_PLAYLIST_2
#define COLOR_PLAYLIST_2 56, 56, 56
#endif
#ifndef COLOR_PLAYLIST_3
#define COLOR_PLAYLIST_3 35, 35, 35
#endif
#ifndef COLOR_PLAYLIST_4
#define COLOR_PLAYLIST_4 25, 25, 25
#endif
#ifndef COLOR_BITRATE
#define COLOR_BITRATE 231, 211, 90
#endif
#define EN 1
#define RU 2
#ifndef L10N_LANGUAGE
#define L10N_LANGUAGE EN
#endif
#endif

200
yoRadio/src/core/player.cpp Normal file
View File

@@ -0,0 +1,200 @@
#include "options.h"
#include "player.h"
#include "config.h"
#include "telnet.h"
#include "display.h"
#include "netserver.h"
Player player;
#if VS1053_CS!=255 && !I2S_INTERNAL
Player::Player(): Audio(VS1053_CS, VS1053_DCS, VS1053_DREQ) {
}
void ResetChip(){
pinMode(VS1053_RST, OUTPUT);
digitalWrite(VS1053_RST, LOW);
delay(30);
digitalWrite(VS1053_RST, HIGH);
delay(100);
}
#else
#if !I2S_INTERNAL
Player::Player() {}
#else
Player::Player(): Audio(true, I2S_DAC_CHANNEL_BOTH_EN) {}
#endif
#endif
void Player::init() {
if(MUTE_PIN!=255) pinMode(MUTE_PIN, OUTPUT);
#if I2S_DOUT!=255
#if !I2S_INTERNAL
setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
#endif
#else
SPI.begin();
if(VS1053_RST>0) ResetChip();
begin();
#endif
setBalance(config.store.balance);
setTone(config.store.bass, config.store.middle, config.store.trebble);
setVolume(0);
mode = STOPPED;
setOutputPins(false);
requestToStart = true;
volTimer=false;
zeroRequest();
playmutex = xSemaphoreCreateMutex();
}
void Player::stopInfo() {
config.setSmartStart(0);
telnet.info();
netserver.requestOnChange(MODE, 0);
requestToStart = true;
}
void Player::stop(const char *nttl){
mode = STOPPED;
setOutputPins(false);
if(nttl) config.setTitle(nttl);
else config.setTitle((display.mode()==LOST || display.mode()==UPDATING)?"":const_PlStopped);
netserver.requestOnChange(TITLE, 0);
config.station.bitrate = 0;
#ifdef USE_NEXTION
nextion.bitrate(config.station.bitrate);
#endif
netserver.requestOnChange(BITRATE, 0);
display.putRequest(DBITRATE);
display.putRequest(PSTOP);
setDefaults();
stopInfo();
if (player_on_stop_play) player_on_stop_play();
}
void Player::loop() {
if (mode == PLAYING) {
xSemaphoreTake(playmutex, portMAX_DELAY);
Audio::loop();
xSemaphoreGive(playmutex);
} else {
if (isRunning()) stop();
}
if (request.station > 0) {
if (request.doSave) {
config.setLastStation(request.station);
}
play(request.station);
if (player_on_station_change) player_on_station_change();
zeroRequest();
}
if (request.volume >= 0) {
config.setVolume(request.volume);
telnet.printf("##CLI.VOL#: %d\n", config.store.volume);
Audio::setVolume(volToI2S(request.volume));
zeroRequest();
display.putRequest(DRAWVOL);
netserver.requestOnChange(VOLUME, 0);
}
if(volTimer){
if((millis()-volTicks)>3000){
config.saveVolume();
volTimer=false;
}
}
}
void Player::zeroRequest() {
request.station = 0;
request.volume = -1;
request.doSave = false;
}
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) {
display.putRequest(PSTOP);
setDefaults();
setOutputPins(false);
config.setTitle(const_PlConnect);
config.station.bitrate=0;
netserver.requestOnChange(TITLE, 0);
config.loadStation(stationId);
setVol(config.store.volume, true);
display.putRequest(NEWSTATION);
netserver.requestOnChange(STATION, 0);
telnet.printf("##CLI.NAMESET#: %d %s\n", config.store.lastStation, config.station.name);
if (connecttohost(config.station.url)) {
mode = PLAYING;
config.setSmartStart(1);
netserver.requestOnChange(MODE, 0);
setOutputPins(true);
requestToStart = true;
display.putRequest(PSTART);
if (player_on_start_play) player_on_start_play();
}else{
Serial.println("some unknown bug...");
};
}
void Player::prev() {
if (config.store.lastStation == 1) config.store.lastStation = config.store.countStation; else config.store.lastStation--;
request.station = config.store.lastStation;
request.doSave = true;
}
void Player::next() {
if (config.store.lastStation == config.store.countStation) config.store.lastStation = 1; else config.store.lastStation++;
request.station = config.store.lastStation;
request.doSave = true;
}
void Player::toggle() {
if (mode == PLAYING) {
mode = STOPPED;
} else {
request.station = config.store.lastStation;
}
}
void Player::stepVol(bool up) {
if (up) {
if (config.store.volume <= 254 - config.store.volsteps) {
setVol(config.store.volume + config.store.volsteps, false);
}else{
setVol(254, false);
}
} else {
if (config.store.volume >= config.store.volsteps) {
setVol(config.store.volume - config.store.volsteps, false);
}else{
setVol(0, false);
}
}
}
byte Player::volToI2S(byte volume) {
int vol = map(volume, 0, 254 - config.station.ovol * 3 , 0, 254);
if (vol > 254) vol = 254;
if (vol < 0) vol = 0;
return vol;
}
void Player::setVol(byte volume, bool inside) {
if (inside) {
setVolume(volToI2S(volume));
} else {
volTicks = millis();
volTimer = true;
request.volume = volume;
request.doSave = true;
}
}

51
yoRadio/src/core/player.h Normal file
View File

@@ -0,0 +1,51 @@
#ifndef player_h
#define player_h
#include "options.h"
#if I2S_DOUT!=255 || I2S_INTERNAL
#include "../audioI2S/AudioEx.h"
#else
#include "../audioVS1053/audioVS1053Ex.h"
#endif
enum audioMode_e { PLAYING, STOPPED };
struct audiorequest_t
{
uint16_t station;
int volume;
bool doSave;
};
class Player: public Audio {
private:
uint32_t volTicks; /* delayed volume save */
bool volTimer; /* delayed volume save */
public:
audioMode_e mode;
audiorequest_t request;
bool requestToStart;
void zeroRequest();
SemaphoreHandle_t playmutex=NULL;
public:
Player();
void init();
void loop();
void play(uint16_t stationId);
void stop(const char *nttl = NULL);
void prev();
void next();
void toggle();
void stepVol(bool up);
void setVol(byte volume, bool inside);
byte volToI2S(byte volume);
void stopInfo();
void setOutputPins(bool isPlaying);
};
extern Player player;
extern __attribute__((weak)) void player_on_start_play();
extern __attribute__((weak)) void player_on_stop_play();
extern __attribute__((weak)) void player_on_track_change();
extern __attribute__((weak)) void player_on_station_change();
#endif

460
yoRadio/src/core/telnet.cpp Normal file
View File

@@ -0,0 +1,460 @@
#include <stdarg.h>
#include "WiFi.h"
#include "config.h"
#include "player.h"
#include "network.h"
#include "telnet.h"
Telnet telnet;
bool Telnet::_isIPSet(IPAddress ip) {
return ip.toString() == "0.0.0.0";
}
bool Telnet::begin() {
if (WiFi.status() == WL_CONNECTED || _isIPSet(WiFi.softAPIP())) {
server.begin();
server.setNoDelay(true);
Serial.printf("Ready! Use 'telnet %s 23' to connect\n", WiFi.localIP().toString().c_str());
return true;
} else {
return false;
}
}
void Telnet::stop() {
server.stop();
}
void Telnet::emptyClientStream(WiFiClient client) {
client.flush();
delay(50);
while (client.available()) {
client.read();
}
}
void Telnet::cleanupClients() {
for (int i = 0; i < MAX_TLN_CLIENTS; i++) {
if (!clients[i].connected()) {
if (clients[i]) {
Serial.printf("Client [%d] is %s\n", i, clients[i].connected() ? "connected" : "disconnected");
clients[i].stop();
}
}
}
}
void Telnet::handleSerial(){
if(Serial.available()){
String request = Serial.readStringUntil('\n'); request.trim();
on_input(request.c_str(), 100);
}
}
void Telnet::loop() {
uint8_t i;
if (WiFi.status() == WL_CONNECTED) {
if (server.hasClient()) {
for (i = 0; i < MAX_TLN_CLIENTS; i++) {
if (!clients[i] || !clients[i].connected()) {
if (clients[i]) {
clients[i].stop();
}
clients[i] = server.available();
if (!clients[i]) Serial.println("available broken");
on_connect(clients[i].remoteIP().toString().c_str(), i);
clients[i].setNoDelay(true);
emptyClientStream(clients[i]);
break;
}
}
if (i >= MAX_TLN_CLIENTS) {
server.available().stop();
}
}
for (i = 0; i < MAX_TLN_CLIENTS; i++) {
if (clients[i] && clients[i].connected() && clients[i].available()) {
String inputstr = clients[i].readStringUntil('\n');
inputstr.trim();
on_input(inputstr.c_str(), i);
}
}
} else {
for (i = 0; i < MAX_TLN_CLIENTS; i++) {
if (clients[i]) {
clients[i].stop();
}
}
delay(1000);
}
handleSerial();
yield();
}
void Telnet::print(const char *buf) {
for (int id = 0; id < MAX_TLN_CLIENTS; id++) {
if (clients[id] && clients[id].connected()) {
print(id, buf);
}
}
Serial.print(buf);
}
void Telnet::print(byte id, const char *buf) {
if (clients[id] && clients[id].connected()) {
clients[id].print(buf);
}
}
void Telnet::printf(const char *format, ...) {
char buf[MAX_PRINTF_LEN];
va_list args;
va_start (args, format );
vsnprintf(buf, MAX_PRINTF_LEN, format, args);
va_end (args);
for (int id = 0; id < MAX_TLN_CLIENTS; id++) {
if (clients[id] && clients[id].connected()) {
clients[id].print(buf);
}
}
Serial.print(buf);
}
/*void Telnet::printf(byte id, const char *format, ...) {
va_list argptr;
va_start(argptr, format);
char *szBuffer = 0;
const size_t nBufferLength = vsnprintf(szBuffer, 0, format, argptr) + 1;
if (nBufferLength == 1) return;
szBuffer = (char *) malloc(nBufferLength);
if (! szBuffer) return;
vsnprintf(szBuffer, nBufferLength, format, argptr);
va_end(argptr);
if(id>MAX_TLN_CLIENTS){
Serial.print(szBuffer);
free(szBuffer);
return;
}
if (clients[id] && clients[id].connected()) {
clients[id].print(szBuffer);
free(szBuffer);
}
}*/
void Telnet::printf(byte id, const char *format, ...) {
char buf[MAX_PRINTF_LEN];
va_list argptr;
va_start(argptr, format);
vsnprintf(buf, MAX_PRINTF_LEN, format, argptr);
va_end(argptr);
if(id>MAX_TLN_CLIENTS){
Serial.print(buf);
return;
}
if (clients[id] && clients[id].connected()) {
clients[id].print(buf);
}
}
void Telnet::on_connect(const char* str, byte clientId) {
Serial.printf("Telnet: [%d] %s connected\n", clientId, str);
print(clientId, "\nWelcome to ёRadio!\n(Use ^] + q to disconnect.)\n> ");
}
void Telnet::info() {
telnet.printf("##CLI.INFO#\n");
char timeStringBuff[50];
strftime(timeStringBuff, sizeof(timeStringBuff), "%Y-%m-%dT%H:%M:%S+03:00", &network.timeinfo);
telnet.printf("##SYS.DATE#: %s\n", timeStringBuff); //TODO timezone offset
telnet.printf("##CLI.NAMESET#: %d %s\n", config.store.lastStation, config.station.name);
if (player.mode == PLAYING) {
telnet.printf("##CLI.META#: %s\n", config.station.title);
}
telnet.printf("##CLI.VOL#: %d\n", config.store.volume);
if (player.mode == PLAYING) {
telnet.printf("##CLI.PLAYING#\n");
} else {
telnet.printf("##CLI.STOPPED#\n");
}
telnet.printf("> ");
}
void Telnet::on_input(const char* str, byte clientId) {
if (strlen(str) == 0) return;
if(network.status == CONNECTED){
if (strcmp(str, "cli.prev") == 0 || strcmp(str, "prev") == 0) {
player.prev();
return;
}
if (strcmp(str, "cli.next") == 0 || strcmp(str, "next") == 0) {
player.next();
return;
}
if (strcmp(str, "cli.toggle") == 0 || strcmp(str, "toggle") == 0) {
player.toggle();
return;
}
if (strcmp(str, "cli.stop") == 0 || strcmp(str, "stop") == 0) {
player.mode = STOPPED;
//display.title("[stopped]");
info();
return;
}
if (strcmp(str, "cli.start") == 0 || strcmp(str, "start") == 0 || strcmp(str, "cli.play") == 0 || strcmp(str, "play") == 0) {
player.play(config.store.lastStation);
return;
}
if (strcmp(str, "cli.vol") == 0 || strcmp(str, "vol") == 0) {
printf(clientId, "##CLI.VOL#: %d\n> ", config.store.volume);
return;
}
if (strcmp(str, "cli.vol-") == 0 || strcmp(str, "vol-") == 0) {
player.stepVol(false);
return;
}
if (strcmp(str, "cli.vol+") == 0 || strcmp(str, "vol+") == 0) {
player.stepVol(true);
return;
}
if (strcmp(str, "sys.date") == 0 || strcmp(str, "date") == 0 || strcmp(str, "time") == 0) {
network.requestTimeSync(true, clientId > MAX_TLN_CLIENTS?clientId:0);
return;
}
int volume;
if (sscanf(str, "vol(%d)", &volume) == 1 || sscanf(str, "cli.vol(\"%d\")", &volume) == 1 || sscanf(str, "vol %d", &volume) == 1) {
if (volume < 0) volume = 0;
if (volume > 254) volume = 254;
player.setVol(volume, false);
return;
}
if (strcmp(str, "cli.audioinfo") == 0 || strcmp(str, "audioinfo") == 0) {
printf(clientId, "##CLI.AUDIOINFO#: %d\n> ", config.store.audioinfo > 0);
return;
}
int ainfo;
if (sscanf(str, "audioinfo(%d)", &ainfo) == 1 || sscanf(str, "cli.audioinfo(\"%d\")", &ainfo) == 1 || sscanf(str, "audioinfo %d", &ainfo) == 1) {
config.store.audioinfo = ainfo > 0;
printf(clientId, "new audioinfo value is: %d\n> ", config.store.audioinfo);
config.save();
return;
}
if (strcmp(str, "cli.smartstart") == 0 || strcmp(str, "smartstart") == 0) {
printf(clientId, "##CLI.SMARTSTART#: %d\n> ", config.store.smartstart);
return;
}
int sstart;
if (sscanf(str, "smartstart(%d)", &sstart) == 1 || sscanf(str, "cli.smartstart(\"%d\")", &sstart) == 1 || sscanf(str, "smartstart %d", &sstart) == 1) {
config.store.smartstart = (byte)sstart;
printf(clientId, "new smartstart value is: %d\n> ", config.store.smartstart);
config.save();
return;
}
if (strcmp(str, "cli.list") == 0 || strcmp(str, "list") == 0) {
printf(clientId, "#CLI.LIST#\n");
File file = SPIFFS.open(PLAYLIST_PATH, "r");
if (!file || file.isDirectory()) {
return;
}
char sName[BUFLEN], sUrl[BUFLEN];
int sOvol;
byte c = 1;
while (file.available()) {
if (config.parseCSV(file.readStringUntil('\n').c_str(), sName, sUrl, sOvol)) {
printf(clientId, "#CLI.LISTNUM#: %*d: %s, %s\n", 3, c, sName, sUrl);
c++;
}
}
printf(clientId, "##CLI.LIST#\n");
printf(clientId, "> ");
return;
}
if (strcmp(str, "cli.info") == 0 || strcmp(str, "info") == 0) {
printf(clientId, "##CLI.INFO#\n");
char timeStringBuff[50];
strftime(timeStringBuff, sizeof(timeStringBuff), "%Y-%m-%dT%H:%M:%S", &network.timeinfo);
if (config.store.tzHour < 0) {
printf(clientId, "##SYS.DATE#: %s%03d:%02d\n", timeStringBuff, config.store.tzHour, config.store.tzMin);
} else {
printf(clientId, "##SYS.DATE#: %s+%02d:%02d\n", timeStringBuff, config.store.tzHour, config.store.tzMin);
}
printf(clientId, "##CLI.NAMESET#: %d %s\n", config.store.lastStation, config.station.name);
if (player.mode == PLAYING) {
printf(clientId, "##CLI.META#: %s\n", config.station.title);
}
printf(clientId, "##CLI.VOL#: %d\n", config.store.volume);
if (player.mode == PLAYING) {
printf(clientId, "##CLI.PLAYING#\n");
} else {
printf(clientId, "##CLI.STOPPED#\n");
}
printf(clientId, "> ");
return;
}
int 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;
player.play((uint16_t)sb);
return;
}
if (strcmp(str, "sys.tzo") == 0 || strcmp(str, "tzo") == 0) {
printf(clientId, "##SYS.TZO#: %d:%d\n> ", config.store.tzHour, config.store.tzMin);
return;
}
//int16_t tzh, tzm;
int tzh, tzm;
if (sscanf(str, "tzo(%d:%d)", &tzh, &tzm) == 2 || sscanf(str, "sys.tzo(\"%d:%d\")", &tzh, &tzm) == 2 || sscanf(str, "tzo %d:%d", &tzh, &tzm) == 2) {
if (tzh < -12) tzh = -12;
if (tzh > 14) tzh = 14;
if (tzm < 0) tzm = 0;
if (tzm > 59) tzm = 59;
config.setTimezone((int8_t)tzh, (int8_t)tzm);
if(tzh<0){
printf(clientId, "new timezone offset: %03d:%02d\n", config.store.tzHour, config.store.tzMin);
}else{
printf(clientId, "new timezone offset: %02d:%02d\n", config.store.tzHour, config.store.tzMin);
}
network.requestTimeSync(true);
return;
}
if (sscanf(str, "tzo(%d)", &tzh) == 1 || sscanf(str, "sys.tzo(\"%d\")", &tzh) == 1 || sscanf(str, "tzo %d", &tzh) == 1) {
if (tzh < -12) tzh = -12;
if (tzh > 14) tzh = 14;
config.setTimezone((int8_t)tzh, 0);
if(tzh<0){
printf(clientId, "new timezone offset: %03d:%02d\n", config.store.tzHour, config.store.tzMin);
}else{
printf(clientId, "new timezone offset: %02d:%02d\n", config.store.tzHour, config.store.tzMin);
}
network.requestTimeSync(true);
return;
}
if (sscanf(str, "dspon(%d)", &tzh) == 1 || sscanf(str, "cli.dspon(\"%d\")", &tzh) == 1 || sscanf(str, "dspon %d", &tzh) == 1) {
config.setDspOn(tzh!=0);
return;
}
if (sscanf(str, "dim(%d)", &tzh) == 1 || sscanf(str, "cli.dim(\"%d\")", &tzh) == 1 || sscanf(str, "dim %d", &tzh) == 1) {
if (tzh < 0) tzh = 0;
if (tzh > 100) tzh = 100;
config.store.brightness = (uint8_t)tzh;
config.setBrightness(true);
return;
}
if (sscanf(str, "sleep(%d,%d)", &tzh, &tzm) == 2 || sscanf(str, "cli.sleep(\"%d\",\"%d\")", &tzh, &tzm) == 2 || sscanf(str, "sleep %d %d", &tzh, &tzm) == 2) {
if(tzh>0 && tzm>0) {
printf(clientId, "sleep for %d minutes after %d minutes ...\n> ", tzh, tzm);
config.sleepForAfter(tzh, tzm);
}else{
printf(clientId, "##CMD_ERROR#\tunknown command <%s>\n> ", str);
}
return;
}
if (sscanf(str, "sleep(%d)", &tzh) == 1 || sscanf(str, "cli.sleep(\"%d\")", &tzh) == 1 || sscanf(str, "sleep %d", &tzh) == 1) {
if(tzh>0) {
printf(clientId, "sleep for %d minutes ...\n> ", tzh);
config.sleepForAfter(tzh);
}else{
printf(clientId, "##CMD_ERROR#\tunknown command <%s>\n> ", str);
}
return;
}
}
if (strcmp(str, "sys.version") == 0 || strcmp(str, "version") == 0) {
printf(clientId, "##SYS.VERSION#: %s\n> ", VERSION);
return;
}
if (strcmp(str, "sys.boot") == 0 || strcmp(str, "boot") == 0 || strcmp(str, "reboot") == 0) {
ESP.restart();
return;
}
if (strcmp(str, "wifi.list") == 0 || strcmp(str, "wifi") == 0) {
printf(clientId, "#WIFI.SCAN#\n");
int n = WiFi.scanNetworks();
if (n == 0) {
printf(clientId, "no networks found\n");
} else {
for (int i = 0; i < n; ++i) {
printf(clientId, "%d", i + 1);
printf(clientId, ": ");
printf(clientId, "%s", WiFi.SSID(i));
printf(clientId, " (");
printf(clientId, "%d", WiFi.RSSI(i));
printf(clientId, ")");
printf(clientId, (WiFi.encryptionType(i) == WIFI_AUTH_OPEN)?" ":"*");
printf(clientId, "\n");
delay(10);
}
}
printf(clientId, "#WIFI.SCAN#\n> ");
return;
}
if (strcmp(str, "wifi.con") == 0 || strcmp(str, "conn") == 0) {
printf(clientId, "#WIFI.CON#\n");
File file = SPIFFS.open(SSIDS_PATH, "r");
if (file && !file.isDirectory()) {
char sSid[BUFLEN], sPas[BUFLEN];
byte c = 1;
while (file.available()) {
if (config.parseSsid(file.readStringUntil('\n').c_str(), sSid, sPas)) {
printf(clientId, "%d: %s, %s\n", c, sSid, sPas);
c++;
}
}
}
printf(clientId, "##WIFI.CON#\n> ");
return;
}
if (strcmp(str, "wifi.station") == 0 || strcmp(str, "station") == 0 || strcmp(str, "ssid") == 0) {
printf(clientId, "#WIFI.STATION#\n");
File file = SPIFFS.open(SSIDS_PATH, "r");
if (file && !file.isDirectory()) {
char sSid[BUFLEN], sPas[BUFLEN];
byte c = 1;
while (file.available()) {
if (config.parseSsid(file.readStringUntil('\n').c_str(), sSid, sPas)) {
if(c==config.store.lastSSID) printf(clientId, "%d: %s, %s\n", c, sSid, sPas);
c++;
}
}
}
printf(clientId, "##WIFI.STATION#\n> ");
return;
}
char newssid[20], newpass[40];
if (sscanf(str, "wifi.con(\"%[^\"]\",\"%[^\"]\")", newssid, newpass) == 2 || sscanf(str, "wifi.con(%[^,],%[^)])", newssid, newpass) == 2 || sscanf(str, "wifi.con(%[^ ] %[^)])", newssid, newpass) == 2 || sscanf(str, "wifi %[^ ] %s", newssid, newpass) == 2) {
char buf[BUFLEN];
snprintf(buf, BUFLEN, "New SSID: \"%s\" with PASS: \"%s\" for next boot\n> ", newssid, newpass);
printf(clientId, buf);
printf(clientId, "...REBOOTING...\n> ");
memset(buf, 0, BUFLEN);
snprintf(buf, BUFLEN, "%s\t%s", newssid, newpass);
config.saveWifiFromNextion(buf);
return;
}
if (strcmp(str, "wifi.status") == 0 || strcmp(str, "status") == 0) {
printf(clientId, "#WIFI.STATUS#\nStatus:\t\t%d\nMode:\t\t%s\nIP:\t\t%s\nMask:\t\t%s\nGateway:\t%s\nRSSI:\t\t%d dBm\n##WIFI.STATUS#\n> ",
WiFi.status(), WiFi.getMode()==WIFI_STA?"WIFI_STA":"WIFI_AP",
WiFi.getMode()==WIFI_STA?WiFi.localIP().toString():WiFi.softAPIP().toString(),
WiFi.getMode()==WIFI_STA?WiFi.subnetMask().toString():"255.255.255.0",
WiFi.getMode()==WIFI_STA?WiFi.gatewayIP().toString():WiFi.softAPIP().toString(),
WiFi.RSSI()
);
return;
}
if (strcmp(str, "wifi.rssi") == 0 || strcmp(str, "rssi") == 0) {
printf(clientId, "#WIFI.RSSI#\t%d dBm\n> ", WiFi.RSSI());
return;
}
if (strcmp(str, "sys.heap") == 0 || strcmp(str, "heap") == 0) {
printf(clientId, "Free heap:\t%d bytes\n> ", xPortGetFreeHeapSize());
return;
}
if (strcmp(str, "wifi.discon") == 0 || strcmp(str, "discon") == 0 || strcmp(str, "disconnect") == 0) {
printf(clientId, "#WIFI.DISCON#\tdisconnected...\n> ");
WiFi.disconnect();
return;
}
telnet.printf(clientId, "##CMD_ERROR#\tunknown command <%s>\n> ", str);
}

34
yoRadio/src/core/telnet.h Normal file
View File

@@ -0,0 +1,34 @@
#ifndef telnet_h
#define telnet_h
#include <WiFi.h>
#define MAX_TLN_CLIENTS 5
#define MAX_PRINTF_LEN BUFLEN+50
class Telnet {
public:
Telnet() {};
bool begin();
void loop();
void stop();
void print(byte id, const char *buf);
void print(const char *buf);
void printf(byte id, const char *format, ...);
void printf(const char *format, ...);
void cleanupClients();
void info();
protected:
WiFiServer server = WiFiServer(23);
WiFiClient clients[MAX_TLN_CLIENTS];
void emptyClientStream(WiFiClient client);
void on_connect(const char* str, byte clientId);
void on_input(const char* str, byte clientId);
private:
bool _isIPSet(IPAddress ip);
void handleSerial();
};
extern Telnet telnet;
#endif