Radio Streaming ESP8266

/*
 * ============================================================================
 * ESP8266 Internet Radio dengan OLED 0.96" & PAM8405 (Class D)
 * Fitur:
 * - Output Audio PWM via Pin RX (I2SNoDAC) untuk PAM8405
 * - Penghemat Memori Flash / EEPROM (Delayed Write / Dirty Flag)
 * - Tampilan OLED 0.96" I2C
 * - Navigasi Stasiun dengan Tombol D5 (GPIO14)
 * ============================================================================
 */

#include <ESP8266WiFi.h>
#include <EEPROM.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// Library ESP8266Audio
#include "AudioFileSourceHTTPStream.h"
#include "AudioFileSourceBuffer.h"
#include "AudioGeneratorMP3.h"
#include "AudioOutputI2SNoDAC.h"

// ----------------------------------------------------------------------------
// CONFIG WIFI & OLED
// ----------------------------------------------------------------------------
const char* WIFI_SSID = "NAMA_WIFI_KAMU"; // Ganti dengan SSID WiFi kamu
const char* WIFI_PASSWORD = "PASSWORD_WIFI_KAMU"; // Ganti dengan Password WiFi kamu

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// ----------------------------------------------------------------------------
// PIN CONFIGURATION
// ----------------------------------------------------------------------------
#define BUTTON_NEXT_PIN 14 // D5 / GPIO14 (Mode INPUT_PULLUP)

// ----------------------------------------------------------------------------
// DAFTAR STASIUN RADIO (Stream URL MP3)
// ----------------------------------------------------------------------------
struct RadioStation {
  const char* name;
  const char* url;
};

const RadioStation STATIONS[] = {
  {"Prambors FM", "http://masima.it-mks.com:8000/prambors"},
  {"Hard Rock FM", "http://stream.mra.co.id:8000/hardrockfm"},
  {"I-Radio Jakarta", "http://stream.mra.co.id:8000/iradio"},
  {"Jazz Radio", "http://stream.zeno.fm/f3wvbbqmdg8uv"}
};

const uint8_t TOTAL_STATIONS = sizeof(STATIONS) / sizeof(STATIONS[0]);

// ----------------------------------------------------------------------------
// GLOBAL AUDIO OBJECTS
// ----------------------------------------------------------------------------
AudioGeneratorMP3 *mp3 = NULL;
AudioFileSourceHTTPStream *file = NULL;
AudioFileSourceBuffer *buff = NULL;
AudioOutputI2SNoDAC *out = NULL;

const int BUFFER_SIZE = 2048; // Buffer RAM (2KB) untuk mencegah buffering/stuttering

// ----------------------------------------------------------------------------
// LOGIKA PENGHEMAT EEPROM (DELAYED WRITE / DIRTY FLAG)
// ----------------------------------------------------------------------------
#define EEPROM_ADDR_STATION 0
#define WRITE_DELAY_MS 5000 // Tulis ke EEPROM setelah 5 detik tanpa penekanan tombol baru

uint8_t currentStation = 0;
bool pendingEEPROMWrite = false;
unsigned long lastButtonPressTime = 0;

// Debouncing tombol
bool lastButtonState = HIGH;
unsigned long lastDebounceTime = 0;
const unsigned long DEBOUNCE_DELAY = 50;

// Prototype Fungsi
void updateDisplay(const char* statusText);
void stopStream();
void startStream(uint8_t stationIdx);
void handleButton();
void checkEEPROMCommit();

// ----------------------------------------------------------------------------
// SETUP
// ----------------------------------------------------------------------------
void setup() {
  Serial.begin(115200);
  delay(500);

  // Pin Tombol Navigasi
  pinMode(BUTTON_NEXT_PIN, INPUT_PULLUP);

  // Inisialisasi OLED (SDA = D2/GPIO4, SCL = D1/GPIO5)
  Wire.begin(4, 5);
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("OLED Gagal Diinisialisasi"));
    for(;;);
  }
  
  updateDisplay("Connecting WiFi...");

  // Inisialisasi EEPROM
  EEPROM.begin(512);
  currentStation = EEPROM.read(EEPROM_ADDR_STATION);
  if (currentStation >= TOTAL_STATIONS) {
    currentStation = 0; // Reset ke 0 jika data corrupt / flash pertama
  }

  // Koneksi ke WiFi
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi Connected!");

  // Audio Output via Delta-Sigma PWM Pin RX (GPIO3)
  out = new AudioOutputI2SNoDAC();

  // Jalankan Stasiun Terakhir yang Tersimpan
  startStream(currentStation);
}

// ----------------------------------------------------------------------------
// LOOP
// ----------------------------------------------------------------------------
void loop() {
  // 1. Cek Penekanan Tombol
  handleButton();

  // 2. Cek Timer Tulis EEPROM (Dirty Flag)
  checkEEPROMCommit();

  // 3. Panggil Pemutar MP3 Non-Blocking
  if (mp3 && mp3->isRunning()) {
    if (!mp3->loop()) {
      mp3->stop();
      Serial.println("Stream terhenti. Merestart...");
      startStream(currentStation);
    }
  }
}

// ----------------------------------------------------------------------------
// FUNGSI MANAJEMEN STREAM AUDIO
// ----------------------------------------------------------------------------
void stopStream() {
  if (mp3) {
    if (mp3->isRunning()) mp3->stop();
    delete mp3;
    mp3 = NULL;
  }
  if (buff) {
    delete buff;
    buff = NULL;
  }
  if (file) {
    delete file;
    file = NULL;
  }
}

void startStream(uint8_t stationIdx) {
  stopStream();

  updateDisplay("Buffering...");
  Serial.print("Membuka Stasiun: ");
  Serial.println(STATIONS[stationIdx].name);

  file = new AudioFileSourceHTTPStream(STATIONS[stationIdx].url);
  buff = new AudioFileSourceBuffer(file, BUFFER_SIZE);
  mp3 = new AudioGeneratorMP3();

  if (mp3->begin(buff, out)) {
    updateDisplay("PLAYING");
  } else {
    updateDisplay("ERROR STREAM");
    Serial.println("Gagal memulai MP3 Stream");
  }
}

// ----------------------------------------------------------------------------
// FUNGSI TOMBOL & DELAYED WRITE EEPROM
// ----------------------------------------------------------------------------
void handleButton() {
  bool reading = digitalRead(BUTTON_NEXT_PIN);

  if (reading != lastButtonState) {
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {
    static bool buttonState = HIGH;
    if (reading != buttonState) {
      buttonState = reading;

      // Saat tombol ditekan ke GND
      if (buttonState == LOW) {
        currentStation = (currentStation + 1) % TOTAL_STATIONS;
        
        Serial.print("Pindah Stasiun (RAM): ");
        Serial.println(STATIONS[currentStation].name);

        // Ganti stream audio langsung di RAM
        startStream(currentStation);

        // Tandai bahwa ada data baru yang menunggu disimpan ke EEPROM
        pendingEEPROMWrite = true;
        lastButtonPressTime = millis();
      }
    }
  }
  lastButtonState = reading;
}

void checkEEPROMCommit() {
  // Hanya tulis ke EEPROM jika ada data baru DAN sudah lewat 5 detik tanpa penekanan tombol
  if (pendingEEPROMWrite && (millis() - lastButtonPressTime >= WRITE_DELAY_MS)) {
    EEPROM.write(EEPROM_ADDR_STATION, currentStation);
    EEPROM.commit(); // Tulis fisik ke flash memory
    pendingEEPROMWrite = false;
    Serial.println(">> EEPROM BERHASIL DIPERBARUI <<");
  }
}

// ----------------------------------------------------------------------------
// FUNGSI DISPLAY OLED
// ----------------------------------------------------------------------------
void updateDisplay(const char* statusText) {
  display.clearDisplay();
  
  // Header
  display.fillRect(0, 0, 128, 14, SSD1306_WHITE);
  display.setTextColor(SSD1306_BLACK);
  display.setTextSize(1);
  display.setCursor(12, 3);
  display.print("ESP8266 RADIO");

  // Nama Stasiun
  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(1);
  display.setCursor(0, 22);
  display.print("Stasiun [");
  display.print(currentStation + 1);
  display.print("/");
  display.print(TOTAL_STATIONS);
  display.println("]:");

  display.setTextSize(1);
  display.setCursor(0, 34);
  display.println(STATIONS[currentStation].name);

  // Status Streaming & Indikator Pending EEPROM
  display.setCursor(0, 52);
  display.print("Status: ");
  display.print(statusText);

  // Tanda asterisk (*) muncul jika stasiun baru belum disimpan ke EEPROM
  if (pendingEEPROMWrite) {
    display.print(" *");
  }

  display.display();
}

Post a Comment for "Radio Streaming ESP8266"