here is the web radio working sketch:
//ESP32 Internet Radio
//Importat Note: The following sketches require version 3.4.1 of the library ESP32-audioI2S.
//Please select version 3.4.1 in the library manager.
#include <Wire.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <Arduino.h>
#include <AudioAnalyzer.h>
#include <Audio.h>
#include <AiEsp32RotaryEncoder.h>
#include <Adafruit_ILI9341.h>
#include <Adafruit_GFX.h>
#include <XPT2046_Touchscreen.h>
#include <WiFiManager.h> // https://github.com/tzapu/WiFiManager
//#include <string.h>
#include <SPI.h>
#include <AudioAnalyzer.h>
#include <Fonts/FreeSans9pt7b.h>
#include <Fonts/FreeMonoBoldOblique12pt7b.h>
//#include <FreeMonoBoldOblique18pt7b.h>
#include <Fonts/FreeSerifBold18pt7b.h>
#include <ESP32Time.h>
ESP32Time rtc(-180000);
//#include <FreeMono12pt7b.h>
Analyzer OtherAudio = Analyzer(47, 41, 10); //Strobe pin ->47 RST pin ->41 Analog Pin ->10
int strobe = 47; // strobe pins on digital 47
int res = 41; // reset pins on digital 41
int FreqVal[7]; // store band values in these arrays
byte bartop[7];
byte barpeak[7];
int audio_bar_height[7];
int audio_bar_peak[7];
int i = 0;
int band;
int sens;
int test;
int num;
int data1;
int j;
int voll;
int volr;
int barlx = 10;
int barrx = 30;
int barWidth = 10;
int maxTop = 180;
int minTop = 229;
int barBottom = 230;
int currentTop;
int barx;
int basex;
int vol;
int diff;
char temp[50];
char humid[50];
unsigned long previousMillis = 0;
unsigned long interval = 60000UL;
#define I2S_DOUT 2
#define I2S_BCLK 3
#define I2S_LRC 4
//#define VOLUME_PIN 5 //Original sketch was using
#define TFT_CS 1
#define TFT_DC 9
#define TFT_RST 8
//Display LED to 3.3 V
#define TFT_MOSI 11
#define TFT_MISO 13
#define TFT_SCLK 12
// Color constants
#define BACKGROUND_COL ILI9341_WHITE
#define BAR_COL_MAIN ILI9341_GREEN
#define BAR_COL_TOP ILI9341_RED
#define TRIGGER_PIN 14 //Trigger wifi on Demand
#define ROTARY_ENCODER_A_PIN 21
#define ROTARY_ENCODER_B_PIN 20
#define ROTARY_ENCODER_BUTTON_PIN 14
#define ROTARY_ENCODER_STEPS 4
const char *ntpServer = "ca.pool.ntp.org";
const long gmtOffset_sec = -18000; // Adjust for your timezone
const int daylightOffset_sec = 3600; // Adjust if DST is active
int timeout = 120; // seconds to wait for Wifi
char song[128] = "8"; //Test value until the first streaminfo kicks in
AiEsp32RotaryEncoder rotaryEncoder(ROTARY_ENCODER_A_PIN, ROTARY_ENCODER_B_PIN, ROTARY_ENCODER_BUTTON_PIN, -1, ROTARY_ENCODER_STEPS);
Audio audio;
// WiFi credentials
const char *ssid = "YOUR NETWORK";
const char *password = "YOUR PASSWORD";
// Radio stations
const char *stations[] = {
"https://playerservices.streamtheworld.com/api/livestream-redirect/CJFMFM.mp3",
"https://playerservices.streamtheworld.com/api/livestream-redirect/CHMPFM.mp3",
"https://playerservices.streamtheworld.com/api/livestream-redirect/CBMEFM_CBC.mp3",
"http://www.byte.fm/stream/bytefm.m3u",
"https://playerservices.streamtheworld.com/api/livestream-redirect/CKOIFM.mp3",
"https://playerservices.streamtheworld.com/api/livestream-redirect/CKBEFM.mp3",
"https://stream.rythme971.ca/chlx.mp3",
"https://best-90s.stream.laut.fm/best-90s",
"https://stream.zeno.fm/ygg6zypxpg8uv",
"http://live.radiomixx.ro/"
};
const char *stationNames[] = {
"Virgin Radio",
"TSN 690",
"CBC Montreal",
"KEXP",
"96.9CKOI",
"The Beat 92.5",
"Rythme971",
"Best 90' Radio",
"City FM Bolivia",
"Radio MIXX Romania"
};
const int NUM_STATIONS = sizeof(stations) / sizeof(stations[0]);
int currentStation = 0;
char streamTitle[64] = ""; // Buffer to store the current stream title
// Volume control variables
const int SAMPLES = 5;
int volumeReadings[SAMPLES];
int readIndex = 0;
int total = 0;
int average = 0;
int amp = 0;
unsigned long lastVolumeCheck = 0;
const unsigned long VOLUME_CHECK_INTERVAL = 500; // Check every 500ms
//Time and date variables
char ttime[128] = "";
char tdate[128] = "";
char tday[128] = "";
// Flags initialization
bool isWiFiConnected = false;
bool isDisplayInitialized = false;
bool isAudioInitialized = false;
void IRAM_ATTR readEncoderISR() {
rotaryEncoder.readEncoder_ISR();
}
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
//Screen calibration
#define TOUCH_CS 18
//Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
XPT2046_Touchscreen touch(TOUCH_CS);
int xMinVal, xMaxVal, yMinVal, yMaxVal;
TS_Point waitForTouch() {
while (!touch.touched()) {
delay(200);
}
delay(200);
TS_Point p = touch.getPoint();
while (touch.touched());
delay(200);
return p;
}
Adafruit_GFX_Button btn;
bool toggleState = false;
void setup() {
delay(1000);
Serial.begin(115200);
while (!Serial) { delay(10); } // Wait for Serial to start
Serial.println(F("ESP32-S3 Internet Radio started..."));
isDisplayInitialized = true;
OtherAudio.Init(); // Init audio analyzer module
pinMode(48, OUTPUT);
pinMode(46, INPUT);
pinMode(15, INPUT_PULLUP);
pinMode(16, INPUT_PULLUP);
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
rotaryEncoder.begin();
rotaryEncoder.setup(readEncoderISR);
rotaryEncoder.setBoundaries(0, NUM_STATIONS - 1, true);
rotaryEncoder.setAcceleration(0);
SPI.begin(TFT_SCLK, TFT_MISO, TFT_MOSI, TFT_CS);
//Screen calibrate
tft.begin();
touch.begin();
tft.setRotation(3);
touch.setRotation(3);
tft.fillScreen(ILI9341_BLACK);
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
// Ask user to touch four corners
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(20, 20);
tft.fillRoundRect(10, 10, 50, 50, 45, ILI9341_WHITE);
tft.setCursor(40, 140);
tft.println("Touch TOP-LEFT");
TS_Point p1 = waitForTouch();
xMinVal = p1.x;
yMinVal = p1.y;
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(300, 20);
tft.fillRoundRect(260, 10, 50, 50, 45, ILI9341_WHITE);
tft.setCursor(40, 140);
tft.println("Touch TOP-RIGHT");
//tft.fillRoundRect(180, 200, 50, 50, 45, ILI9341_WHITE);
//tft.setCursor(20, 140);
TS_Point p2 = waitForTouch();
xMaxVal = p2.x;
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(20, 200);
tft.fillRoundRect(10, 180, 50, 50, 45, ILI9341_WHITE);
tft.setCursor(40, 140);
tft.println("Touch BOTTOM-LEFT");
TS_Point p3 = waitForTouch();
yMaxVal = p3.y;
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(20, 140);
tft.println("Calibration Done!");
delay(2000);
// Print values
Serial.println("Calibration Values:");
Serial.print("xMin = ");
Serial.println(xMinVal);
Serial.print("xMax = ");
Serial.println(xMaxVal);
Serial.print("yMin = ");
Serial.println(yMinVal);
Serial.print("yMax = ");
Serial.println(yMaxVal);
tft.setCursor(20, 50);
tft.fillScreen(ILI9341_BLACK);
tft.setTextColor(ILI9341_RED);
tft.setTextSize(4);
tft.println(" Web Radio");
delay(2000);
tft.setCursor(50, 85);
tft.setTextSize(3);
tft.println(" Starting");
delay(2000);
tft.setTextSize(2);
tft.fillScreen(ILI9341_BLACK);
tft.setTextColor(ILI9341_YELLOW);
tft.setCursor(60, 60);
tft.setTextSize(4);
tft.println("Display");
delay(1000);
tft.setTextColor(ILI9341_RED);
tft.setCursor(20, 95);
tft.setTextSize(4);
tft.setTextColor(ILI9341_BLUE);
tft.println("Initialized");
delay(1000);
//Start Wifi
Serial.println(F("Starting WiFi..."));
pinMode(TRIGGER_PIN, INPUT_PULLUP);
while (digitalRead(TRIGGER_PIN) == HIGH) {
tft.fillScreen(ILI9341_WHITE);
tft.setTextColor(ILI9341_RED);
tft.setTextSize(2);
tft.setCursor(20, 20);
tft.println("Press Internet button");
delay(1000);
tft.setCursor(40, 50);
tft.println("Check your phone");
delay(1000);
tft.setCursor(50, 80);
tft.println("Configure Wifi");
delay(1000);
}
WiFiManager wm;
//reset Wifi settings for new Wifi
wm.resetSettings();
// set config Web portal timeout
wm.setConfigPortalTimeout(timeout);
if (!wm.startConfigPortal("OnDemandAP")) {
Serial.println("failed to connect and hit timeout");
ESP.restart();
}
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 10) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println(F("\nWiFi connected"));
isWiFiConnected = true;
}
tft.fillScreen(ILI9341_RED);
tft.setTextColor(ILI9341_GREEN);
tft.setTextSize(3);
tft.setCursor(20, 20);
tft.println("Wifi connected");
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
//
// Initialize button: (tft, x, y, width, height, outline, fill, textcolor, label, textsize)
btn.initButton(&tft, 60, 230, 40, 40, ILI9341_WHITE, ILI9341_GREEN, ILI9341_BLACK, "MUTE", 1);
btn.drawButton();
initializeAudio();
}
void loop() {
//static unsigned long lastInitAttempt = 0;
//const unsigned long initInterval = 5000; // 5 between
// Basic Loop
audio.loop();
checkEncoder();
readfreq();
unsigned long currentMillis = millis(); // Get current time
if (currentMillis - previousMillis >= interval) {
// Save the last time you blinked the LED
previousMillis = currentMillis;
showtime();
}
yield();
//Check MUTE
if (touch.touched()) {
TS_Point p = touch.getPoint();
// Map raw touch coordinates to screen pixels (values depend on calibration)
int x = map(p.x, 463, 3685, 0, tft.width());
int y = map(p.y, 680, 3564, 0, tft.height());
// Update button "pressed" state
btn.press(btn.contains(x, y));
} else {
btn.press(false);
}
// Check for the moment the button is released to toggle the state
if (btn.justReleased()) {
toggleState = !toggleState;
// Redraw button with new state colors/labels
if (toggleState) {
btn.initButton(&tft, 60, 230, 40, 40, ILI9341_WHITE, ILI9341_GREEN, ILI9341_BLACK, "MUTE", 1);
} else {
btn.initButton(&tft, 60, 230, 40, 40, ILI9341_WHITE, ILI9341_RED, ILI9341_WHITE, "UNMUTE", 1);
}
btn.drawButton();
// Perform action (e.g., turn on an LED)
digitalWrite(LED_BUILTIN, toggleState ? HIGH : LOW);
}
}
void initializeDisplay() {
Serial.println(F("InitializingILI9341 Display..."));
tft.fillScreen(ILI9341_WHITE);
tft.setTextSize(2);
tft.setTextColor(ILI9341_GREEN);
tft.setCursor(20, 40);
tft.println(F("Initializing..."));
delay(1000);
isDisplayInitialized = true;
Serial.println(F("Display initialized"));
}
void initializeAudio() {
audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
audio.setVolume(10);
connectToStation(currentStation);
isAudioInitialized = true;
Serial.println(F("Audio initialized"));
}
void checkEncoder() {
if (rotaryEncoder.encoderChanged()) {
//Serial.println("Changing..");
currentStation = rotaryEncoder.readEncoder();
connectToStation(currentStation);
}
if (rotaryEncoder.isEncoderButtonClicked()) {
Serial.println(F("Encoder-SW check"));
// Here other funtion using the SW, it is used to trigger Wifi setup, OnDemand
}
}
void connectToStation(int stationIndex) {
audio.stopSong();
audio.connecttohost(stations[stationIndex]);
updateDisplay();
}
void updateDisplay() {
if (!isDisplayInitialized) return;
//if (isDisplayInitialized && isWiFiConnected && isAudioInitialized) {
//showtime();
//}
tft.fillScreen(ILI9341_WHITE);
tft.setTextColor(ILI9341_BLACK);
tft.setCursor(5, 10);
tft.setTextSize(1);
tft.setFont(&FreeSans9pt7b);
tft.println("Station... ");
tft.setTextColor(ILI9341_RED);
//tft.print(String(stationNames[currentStation]) );
//tft.print(String(stationNames[currentStation]) );
tft.setTextColor(ILI9341_RED);
tft.setFont(&FreeSans9pt7b);
tft.setCursor(80, 10);
tft.print((String(stationNames[currentStation])));
//tft.println();
tft.setCursor(5, 30);
tft.setTextSize(1);
tft.setTextColor(ILI9341_BLACK);
tft.println("Playing ");
tft.setTextColor(ILI9341_RED);
tft.setCursor(80, 30);
tft.print((String(streamTitle)));
/* tft.setFont(&FreeMonoBoldOblique12pt7b);
tft.setCursor(100, 75);
tft.setTextSize(1);
tft.setTextColor(ILI9341_BLUE);
tft.println(tday);
tft.setCursor(65, 92);
tft.setTextSize(1);
tft.setTextColor(ILI9341_BLUE);
tft.println(tdate);
tft.setCursor(110, 120);
tft.setTextSize(1);
tft.setTextColor(ILI9341_BLUE);
tft.setFont(&FreeSerifBold18pt7b);
tft.println(ttime);
*/
tft.setCursor(5, 90);
tft.setFont();
tft.setTextColor(ILI9341_BLACK);
tft.println("VOLUME");
tft.setCursor(10, 95);
tft.println("L");
tft.setCursor(30, 95);
tft.println("R");
tft.setTextColor(ILI9341_BLACK);
tft.setTextSize(1);
//tft.setFont(FreeMono12pt7b.h);
tft.setFont();
tft.setCursor(80, 230);
tft.println("64");
tft.setCursor(100, 230);
tft.println("160");
tft.setCursor(125, 230);
tft.println("400");
tft.setCursor(150, 230);
tft.println("1K ");
tft.setCursor(170, 230);
tft.println("2.5K");
tft.setCursor(200, 230);
tft.println("6.3K");
tft.setCursor(230, 230);
tft.println("16K");
tft.setCursor(250, 140);
tft.setTextSize(1);
tft.setTextColor(ILI9341_BLACK, ILI9341_WHITE);
sprintf(temp, "Temp %d C", 45);
tft.println(temp);
tft.setCursor(250, 180);
tft.setTextSize(1);
tft.setTextColor(ILI9341_BLACK, ILI9341_WHITE);
sprintf(humid, "Humid %d%%", 80);
tft.println(humid);
showtime();
volume(voll, volr);
basex = 10;
vol = voll;
drawBar(basex, vol);
delay(50);
basex = 30;
vol = volr;
drawBar(basex, vol);
btn.drawButton();
/*tft.fillRect(volbarlx, volbasey-maxheight,10, maxheight-voll-40,ILI9341_WHITE);
tft.fillRect(volbarlx, volbasey-voll, 10, voll, ILI9341_GREEN);
Serial.println(voll);
delay(3000);
tft.fillRect(volbarrx, volbasey-maxheight,10, maxheight-volr-40,ILI9341_WHITE);
tft.fillRect(volbarrx, volbasey-volr, 10, volr, ILI9341_GREEN);
delay(50);*/
}
// Audio callback functions
void audio_info(const char *info) {
Serial.print("info ");
Serial.println(info);
}
void audio_id3data(const char *info) {
Serial.print("id3data ");
Serial.println(info);
}
void audio_eof_mp3(const char *info) {
Serial.print("eof_mp3 ");
Serial.println(info);
}
void audio_showstation(const char *info) {
Serial.print("station ");
Serial.println(info);
}
void audio_showstreaminfo(const char *info) {
Serial.print("streaminfo ");
Serial.println(info);
}
void audio_showstreamtitle(const char *info) {
Serial.print("streamtitle: ");
Serial.println(info);
streamTitle[sizeof(streamTitle) - 1] = '\0'; // Ensure null-termination
strncpy(streamTitle, info, sizeof(streamTitle) - 1);
updateDisplay();
}
void audio_bitrate(const char *info) {
Serial.print("bitrate ");
Serial.println(info);
}
void audio_commercial(const char *info) {
Serial.print("commercial ");
Serial.println(info);
}
void audio_icyurl(const char *info) {
Serial.print("icyurl ");
Serial.println(info);
}
void audio_lasthost(const char *info) {
Serial.print("lasthost ");
Serial.println(info);
}
void audio_eof_speech(const char *info) {
Serial.print("eof_speech ");
Serial.println(info);
}
void showtime() {
struct tm timeinfo = rtc.getTimeStruct();
if (!getLocalTime(&timeinfo)) {
Serial.println("Failed to obtain time");
return;
}
Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
//delay(1000);
strftime(tdate, 128, "%B %d %Y", &timeinfo);
strftime(ttime, 128, "%H:%M", &timeinfo);
strftime(tday, 128, "%A", &timeinfo);
//Display time & date
//tft.setFont(&FreeMonoBoldOblique12pt7b);
tft.setFont();
tft.setCursor(82, 50);
tft.setTextSize(3);
tft.setTextColor(ILI9341_BLUE, ILI9341_WHITE);
tft.print(" ");
tft.print(tday);
tft.print(" ");
tft.setCursor(65, 77);
tft.setTextSize(3);
tft.setTextColor(ILI9341_BLUE, ILI9341_WHITE);
tft.println(tdate);
tft.setCursor(110, 105);
tft.setTextSize(3);
tft.setTextColor(ILI9341_WHITE, ILI9341_WHITE);
//tft.setFont(&FreeSerifBold18pt7b);
tft.println(ttime);
tft.setCursor(115, 105);
tft.setTextColor(ILI9341_BLUE, ILI9341_WHITE);
tft.println(ttime);
}
void volume(int &voll, int &volr) {
voll = analogRead(15);
volr = analogRead(16);
voll = 640;
volr = 640;
voll = map(voll, 10, 1023, 20, 512);
volr = map(volr, 10, 1023, 20, 512);
}
void readfreq() {
OtherAudio.ReadFreq(FreqVal); //Return 7 values of 7 bands pass filter
//Frequency(Hz):63 160 400 1K 2.5K 6.25K 16K
//FreqVal[]: 0 1 2 3 4 5 6
for (int i = 0; i < 7; i++) { // loop for every frequency (63Hz, 160Hz, 400Hz, 1kHz, 2.5kHz, 6.25kHz and 16kHz)
test = FreqVal[i] - 100;
//Serial.printf("FreqVal %d : %d\n",i,FreqVal[i]); //view each frequency level
audio_bar_height[i] = map(max(test, 0), 0, 4095, 0, 64); // set the bar value based on the measurement from the audio analyzer sensor
// calculate the peak position
if (audio_bar_peak[i] < audio_bar_height[i]) { // if the peak is below the current bar size
audio_bar_peak[i] = audio_bar_height[i]; // move peak to the new max. position (i.e. size of the bar)
} else if (audio_bar_peak[i] > audio_bar_height[i]) { // if the bar is lower than the peak
audio_bar_peak[i]--; // slowly move the peak down, one pixel every frame
}
tft.fillRect(80 + i * 25, 140, 5, 63, ILI9341_RED);
tft.fillRect(80 + i * 25, 220 - audio_bar_height[i], 5, audio_bar_height[i], ILI9341_BLACK); //draw bar
tft.fillRect(80 + i * 25, 220 - audio_bar_peak[i], 5, 1, ILI9341_BLACK); // draw peak
/*volume (voll, volr);
tft.fillRect(volbarlx, volbasey-maxheight,10, voll,ILI9341_WHITE);
tft.fillRect(volbarrx, volbasey-maxheight,10, volr,ILI9341_WHITE);
tft.fillRect(volbarlx, 200-voll, 10, voll, ILI9341_GREEN);
tft.fillRect(volbarrx, 200-volr, 10, volr, ILI9341_GREEN);
delay(50);
*/
}
}
void drawBar(int &basex, int &vol) {
// Variables
int sensorValue = vol;
int barWidth = 8;
int maxBarHeight = 110;
int yBase = 230; // The screen Y-coordinate for the bottom of the bar
int triggerValue = 90;
uint16_t barColor;
// 1. Map your sensor value to a pixel height
int currentHeight = map(sensorValue, 0, 384, 0, maxBarHeight);
// 2. Select color based on trigger value
//uint16_t barColor = (currentHeight > triggerValue) ? ILI9341_RED : ILI9341_GREEN;
// 3. Clear previous bar (optional but recommended to prevent ghosting)
tft.fillRect(basex, yBase - maxBarHeight, barWidth, maxBarHeight, ILI9341_WHITE);
if (currentHeight > triggerValue) {
diff = currentHeight - triggerValue;
barColor = ILI9341_RED;
tft.fillRect(basex, yBase - maxBarHeight - diff, barWidth, maxBarHeight, ILI9341_RED);
}
tft.fillRect(basex, yBase - currentHeight, barWidth, currentHeight, ILI9341_GREEN);
// 4. Draw the bar from bottom up
// Formula: tft.fillRect(x, yBase - currentHeight, width, currentHeight, color)
//tft.fillRect(basex, yBase - currentHeight, barWidth, currentHeight, barColor);
}