#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
#include <TFT_eSPI.h>
#include "AudioTools.h"
#include "BluetoothA2DPSource.h"
#include "AudioCodecs/CodecMP3Helix.h"
#include "esp_psram.h"
// Piny przycisków (dostosuj do swojego układu)
#define BTN_PREV 1
#define BTN_NEXT 2
#define BTN_PLAY 3
TFT_eSPI tft = TFT_eSPI();
WebServer server(80);
Preferences prefs;
BluetoothA2DPSource a2dp_source;
struct Station {
String name;
String url;
};
Station stations[50]; // Więcej stacji!
int stationCount = 0;
int currentStation = 0;
bool isPlaying = false;
URLStream urlStream;
MP3DecoderHelix decoder;
EncodedAudioStream encoded(&a2dp_source, &decoder);
StreamCopy copier(encoded, urlStream);
void setup() {
Serial.begin(115200);
// Sprawdź PSRAM
if(psramInit()){
Serial.printf("✅ PSRAM: %d MB\n", ESP.getPsramSize() / 1024 / 1024);
heap_caps_malloc_extmem_enable(4096); // Użyj PSRAM dla alokacji >4KB
} else {
Serial.println("❌ PSRAM ERROR!");
}
// Inicjalizacja ekranu
tft.init();
tft.setRotation(1);
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
// Przyciski
pinMode(BTN_PREV, INPUT_PULLUP);
pinMode(BTN_NEXT, INPUT_PULLUP);
pinMode(BTN_PLAY, INPUT_PULLUP);
// Logo startowe
showLogo();
delay(2000);
// Wczytaj konfigurację
loadStations();
if (stationCount == 0) {
startConfigPortal();
} else {
connectWiFi();
startBluetooth();
playStation(0);
}
}
void showLogo() {
tft.fillScreen(TFT_BLACK);
tft.setTextSize(3);
tft.setTextColor(TFT_CYAN);
tft.setCursor(20, 50);
tft.println("ESP32-S3");
tft.setTextSize(2);
tft.setTextColor(TFT_YELLOW);
tft.setCursor(30, 90);
tft.println("RADIO");
tft.setTextSize(1);
tft.setTextColor(TFT_GREEN);
tft.setCursor(40, 120);
tft.printf("PSRAM: %dMB", ESP.getPsramSize() / 1024 / 1024);
}
void loop() {
server.handleClient();
static unsigned long lastPress = 0;
unsigned long now = millis();
// Debouncing przycisków
if (now - lastPress > 300) {
if (digitalRead(BTN_NEXT) == LOW) {
nextStation();
lastPress = now;
}
if (digitalRead(BTN_PREV) == LOW) {
prevStation();
lastPress = now;
}
if (digitalRead(BTN_PLAY) == LOW) {
togglePlay();
lastPress = now;
}
}
// Streamuj audio
if (isPlaying) {
copier.copy();
}
}
void playStation(int index) {
if(index < 0 || index >= stationCount) return;
currentStation = index;
// Stop poprzedniej stacji
urlStream.end();
// Wyświetl
displayStation();
// Konfiguracja streamu z dużymi buforami
auto cfg = urlStream.defaultConfig();
cfg.buffer_size = 8192;
cfg.buffer_count = 10;
urlStream.begin(stations[index].url.c_str(), "audio/mp3");
// Start dekodera
encoded.begin();
isPlaying = true;
Serial.printf("▶️ Playing: %s\n", stations[index].name.c_str());
}
void displayStation() {
tft.fillScreen(TFT_BLACK);
// Numer stacji
tft.setTextSize(1);
tft.setTextColor(TFT_GRAY);
tft.setCursor(10, 10);
tft.printf("%d/%d", currentStation + 1, stationCount);
// Nazwa stacji
tft.setTextSize(2);
tft.setTextColor(TFT_YELLOW);
tft.setCursor(10, 50);
tft.println(stations[currentStation].name);
// Status
tft.setTextSize(1);
tft.setTextColor(TFT_GREEN);
tft.setCursor(10, 90);
tft.println(isPlaying ? "▶ PLAYING" : "⏸ PAUSED");
// Pasek postępu (animowany)
drawProgressBar();
}
void drawProgressBar() {
static int pos = 0;
tft.fillRect(10, 120, 220, 10, TFT_DARKGREY);
tft.fillRect(10 + pos, 120, 30, 10, TFT_CYAN);
pos = (pos + 5) % 190;
}
void nextStation() {
currentStation = (currentStation + 1) % stationCount;
playStation(currentStation);
}
void prevStation() {
currentStation = (currentStation - 1 + stationCount) % stationCount;
playStation(currentStation);
}
void togglePlay() {
isPlaying = !isPlaying;
displayStation();
}
// Pozostałe funkcje (connectWiFi, startBluetooth, itp.)
// tak samo jak w poprzednim kodzie