WebRadio w/I2S -09 . 箱と実装

前回で終わりの予定でしたが、せっかく作ったので箱にいれて見ようと適当な箱を探していたらちょうど良い大きさの箱をネットで見つけました。

バンブー&ウッド~カードケース・角C(名刺入) 大きさ10cm×12.5cm×7cm。価格1350円。送料650円。合計2000円。

近くのリサイクル屋さんでスピーカーも2つ買いました。

大きさ10cm×10cm×10cm。1個510円 2個で1020円。

その他トグルスイッチ1個。プッシュスイッチ1個。ケーブル。電源用プラグ等を買いました。

実装にあたり回路を変更

単体で動作する為に

  • ラジオの操作は全てWeb画面から行う。
  • 電源を入れたら直ぐ再生。
  • ルーターの変更に対応するため、SSID パスワード IPアドレス等の設定をスマホから行える様にする。
    • APモードでサーバーを上げればルータは必要無い。
    • スマホと直接通信して上記のパラメータ設定を行う。
  • 電源は5Vのみとする。デバッグ用にUSBケーブルを繋いだ時に外部からの電源は切れる様にする。

としました。そこでこの条件に合う様に回路を変更しています。

  • 上の5Vの下のSWがトグルスイッチです。これをオンすると5Vが供給されます。
  • JMP-1がジャンパーです。このジャンパーをオンにすると、外部電源がESP32に供給されます。デバッグでUSBケーブルをつなぐ時は、このジャンパーを外してからケーブルを繋ぎます。
  • 真ん中辺のプッシュスイッチでESPのサーバーのモードを設定します。電源を入れた時に
    • オン: APモードでの立ち上げ。ラジオの再生は出来ませんが、スマホと直接通信し必要なパラメータの設定を行います。
    • オフ: 通常モードで立ち上げ、ラジオの再生を行います。

箱の木の材質は比較的柔らかくて加工しやすかったのですが、それなりに時間がかかりました。

ちょっと苦労しましたが、なかなかの出来栄えと思っています。

スケッチの実装

今回の主な変更は


#include <Arduino.h>
#include <WiFi.h>
#include "AudioFileSourceICYStream.h"
#include "AudioFileSourceBuffer.h"
#include "AudioGeneratorMP3.h"
#include "AudioGeneratorAAC.h"
#include "AudioOutputI2S.h"

#include <WebServer.h>
#include <ESPmDNS.h>
#include "SPIFFS.h"

// Enter your WiFi setup here:
String d_ssid,d_pass;

AudioGenerator *decoder = NULL;
AudioFileSourceICYStream *file;
AudioFileSourceBuffer *buff;
AudioOutputI2S *out;

// Controll flg
static int8_t flg[10];
#define play    0
#define volume  1
#define total   2
#define now     3

// Botton for mode selection
#define bt_state  23

// AP connect parameter
IPAddress d_ip,d_gateway,d_netmask;

WebServer server(80);

String index_Dt =
                  "<!DOCTYPE html>\n<html>\n<head>\n"
                  "<title>ESP32 WebRadio</title>\n"
                  "<style type='text/css'>\n"
                  "button.bt1 {background:#90ee90;font-size:50px;padding:10px;margin:20px;border-radius:30px;box-shadow:4px 4px #555;}\n"
                  "</style>\n</head>\n"
                  "<body>\n<center>\n"
                  "<div style='width:700px; background-color:#d8a373; border-style:solid; border-radius:30px; border-color:#6b3f31'>\n"
                  "<p style='font-size:70px'><b><i><u>ESP32 WebRadio</u></i></b></p>\n"
                  "<form id='pull_list' method='get'>\n"
                  "<select name='5' id='pull_item' onchange='getItem()' style='font-size:50px;width:550px;text-align:center;border-radius:20px'>\n";
                  
String index_Dt1 =
                  "</select><br><br><br><br>\n"
                  "</form>\n"

                  "<form id='cmd_vol' method='get' style='font-size:40px'>\n"
                  "<input type='range' min='0' max='100' step='1' name='3' onchange='showValue()' id='s_value' value='50' style='width:500px'>\n"
                  "<span id='p_value'>50</span><br>\n"
                  "</form><br><br>\n"

                  "<form method='get'>\n"
                  "<button type='submit' name='1' id='b_play' class='bt1'>PLAY</button>\n"
                  "<button type='submit' name='2' class='bt1'>STOP</button>\n"
                  "<button type='submit' name='4' id='b_mute' class='bt1'>MUTE</button>\n"
                  "<button type='submit' name='6' class='bt1'>Entry</button>\n"
                  "<button type='submit' name='8' class='bt1'>Save</button>\n"
                  "</form><br>\n"
                  
                  "</div>\n<script>\n"
                  "function showValue() {\n"
                  "target = document.getElementById('cmd_vol');\n"
                  "target.submit();\n}\n"
                  
                  "function getItem() {\n"
                  "target = document.getElementById('pull_list');\n"
                  "target.submit();\n}\n";
                  
void setup()
{
  Serial.begin(115200);
  delay(1000);
  Serial.println("Connecting to WiFi");

  pinMode(bt_state, INPUT);

  WiFi.disconnect();
  WiFi.softAPdisconnect(true);

  //Initialize File System
  SPIFFS.begin();
  Serial.println("File System Initialized");    //Initialize File System
  load_ip_data();

  if(digitalRead(bt_state))
  {
    WiFi.mode(WIFI_STA);
    WiFi.begin(d_ssid.c_str(), d_pass.c_str());
    delay(1000);
    WiFi.config(d_ip, d_gateway,d_netmask); 

    // Try forever
    while (WiFi.status() != WL_CONNECTED) {
      Serial.println("...Connecting to WiFi");
      delay(1000);
    }
    Serial.println("Connected");
    Serial.println(d_ssid.c_str());
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

    if (MDNS.begin("esp32")) {
      Serial.println("MDNS responder started");
    }
  
    server.on("/", handleRoot);
  
    server.begin();
    Serial.println("HTTP server started");

    out = new AudioOutputI2S();

    load_flg();
    out->SetGain((float)flg[volume]/100.0);
    if(flg[play]) radio_play(flg[now]);
  }
  else
    setup_New_IP();
}

void loop()
{
  if(flg[play])
  {
    if (decoder->isRunning()) {
      if (!decoder->loop()) decoder->stop();
    } 
    else {
        Serial.print("decoder restart\n");
        StopPlaying();    
        delay(500);
        radio_play(flg[now]);
    }
  }

  server.handleClient();
}

void handleRoot()
{
  String cmd;
  int a,fl;

    fl=1;
    cmd=server.argName(0);
    switch(cmd.toInt())
    {
      case 1:   // Play 
                if(flg[play] == 0)
                   if(flg[total] != flg[now])
                       radio_play(flg[now]);
                break;
                
      case 2:   // Stop 
                StopPlaying();
                break;
                
      case 3:   // Volume 
                cmd=server.arg("3");
                flg[volume]=cmd.toInt();
                out->SetGain((float)flg[volume]/100.0);
                break;
                
      case 4:   // Mute
                flg[volume] *= -1;
                a=flg[volume];
                if(flg[volume] < 0) a=0;
                out->SetGain(((float)a)/100.0);
                break;
                
      case 5:   // List
                cmd = server.arg("5");
                flg[now] = cmd.toInt();
                break;

      case 6:   // Entry Sheet
                entry_sheet();
                fl=0;
                break;

      case 7:   //Edit(0) / ADD(1) / delete(2) OK
     
                cmd = server.arg("7");
                
                a=1;
                if(flg[total] == flg[now]) 
                  if(cmd.charAt(0) != '1') a=0;

                if(a) make_file(flg[now],cmd);
                break;

      case 8:   // save data
                save_flg();                
                break;
    }

  if(fl) load_index();
  else server.send(204, "text/plane",""); 
}

void load_index()
{
  File dataFile;
  String line,stbuf;
  
    //Pull Down List
    flg[total] = 0;

    dataFile = SPIFFS.open("/title.txt", FILE_READ);
    stbuf="";
    while(dataFile.available())
     {
       line = dataFile.readStringUntil('\n');
       stbuf += "<option value='" + String(flg[total]+1) + "'>";
       if(line != "<--- end --->") stbuf = stbuf + String(flg[total]+1) + ": ";
       stbuf = stbuf + line + "</option>\n";
       flg[total] += 1;
     }
    dataFile.close();
    
    stbuf = index_Dt + stbuf + index_Dt1;

    stbuf += ("document.getElementById('pull_item').selectedIndex = '" + String(flg[now]-1) + "';\n");
    stbuf += ("document.getElementById('s_value').value='" + String(abs((int)flg[volume])) + "';\n");
    stbuf += ("document.getElementById('p_value').innerHTML='" + String(abs((int)flg[volume])) + "';\n");
 
    if(flg[play])
      stbuf += "document.getElementById('b_play').style.backgroundColor='#ff6347';\n";
    if(flg[volume] < 0)
      stbuf += "document.getElementById('b_mute').style.backgroundColor='#2020ff';\n";
    stbuf += "history.pushState(null,null,'/');\n</script>\n</center>\n</body>\n</html>";
   
  server.send(200, "text/html",stbuf);    
}

void radio_play(int no)
{
  File l_file;
  int a;
  String link_data;

    l_file = SPIFFS.open("/link_list.txt", FILE_READ);  
    for(a=0; a<no; a ++) link_data=l_file.readStringUntil('\n');
    l_file.close();

    file = new AudioFileSourceICYStream(link_data.substring(2).c_str());
    buff = new AudioFileSourceBuffer(file, 2048*16);
    
    if(link_data.charAt(0) == '1') decoder = (AudioGenerator*) new AudioGeneratorAAC();
    else decoder = (AudioGenerator*) new AudioGeneratorMP3();

    decoder->begin(buff, out);
    
    flg[play]=1;
}
  
void StopPlaying()
{
  if (decoder) { decoder->stop(); delete decoder; decoder = NULL; }
  if (buff) { buff->close(); delete buff; buff = NULL; }
  if (file) { file->close(); delete file; file = NULL; }
  
  flg[play] = 0; 
}

void entry_sheet()
{
  File dataFile,dataFile1;
  String line,line1,html_buf;
  int a;

    dataFile = SPIFFS.open("/entry.txt", FILE_READ);
    html_buf="";
    while(dataFile.available())
    {
      html_buf += dataFile.readStringUntil('\n');
      html_buf += "\n"; 
    }
    dataFile.close();

    dataFile = SPIFFS.open("/title.txt", FILE_READ);
    dataFile1 = SPIFFS.open("/link_list.txt", FILE_READ);
    for(a=0; a<flg[now]; a++)
    {
      line = dataFile.readStringUntil('\n');
      line1 = dataFile1.readStringUntil('\n');  
    }
    dataFile.close(); dataFile1.close();
                
    html_buf += "document.getElementById('m_title').value='" + line + "';\n"
                "document.getElementById('m_link').value='" + line1.substring(2) + "';\n"
                "var mp_acc = document.getElementsByName( 'a2' ) ;\n";

    if(line1.charAt(0) == '1') html_buf += "mp_acc[1].checked = true; \n";        
                            
    html_buf += "history.pushState(null,null,'/');\n</script>\n</body>\n</html>\n";

    server.send(200, "text/html", html_buf);
}

void make_file(int no, String line)
{
    File t,t1,l,l1;
    String d_data,l_data,s_data[3];
    int a,b;

        s_data[0]=line.charAt(0);
                
        b=2; a=2;
        while(a)
         {
           while(line.charAt(b) != ',') b ++;
           a --; b ++;
         }
        s_data[1]=line.substring(2,b-1);
        s_data[2]=line.substring(b);

        a = s_data[0].toInt();
        t = SPIFFS.open("/title.txt",FILE_READ); t1 = SPIFFS.open("/title.tmp",FILE_WRITE);
        l = SPIFFS.open("/link_list.txt",FILE_READ); l1 = SPIFFS.open("/link_list.tmp",FILE_WRITE);
        while(t.available())
        {
          d_data = t.readStringUntil('\n'); l_data = l.readStringUntil('\n');
          no --;
          switch(a)
          {
            case 0: //Edit
                    if(no == 0)
                    { 
                      t1.print(s_data[2] + "\n"); 
                      l1.print(s_data[1] + "\n"); 
                    }
                    else 
                    { 
                      t1.print(d_data + "\n"); 
                      l1.print(l_data + "\n"); 
                    }
                    break;

            case 1: // Add
                    if(no == 0) 
                    { 
                      t1.print(s_data[2] + "\n"); 
                      l1.print(s_data[1] + "\n"); 
                    }
                    t1.print(d_data+"\n"); l1.print(l_data + "\n");
                    break;

            case 2: // Delete
                    if(no != 0) 
                    { 
                      t1.print(d_data + "\n"); 
                      l1.print(l_data + "\n"); 
                    }
                    break;
          }
        }
        t.close(); t1.close(); l.close(); l1.close();

        SPIFFS.remove("/title.txt");
        SPIFFS.remove("/link_list.txt");
        SPIFFS.rename("/title.tmp","/title.txt");
        SPIFFS.rename("/link_list.tmp","/link_list.txt");

        if(s_data[0] == "1") flg[total] ++; 
        if(s_data[0] == "2") flg[total] --; 
       
}

void save_flg()
{
  int a;
  File dataFile;
    
    dataFile = SPIFFS.open("/state.txt", FILE_WRITE);
    for(a=0; a<4; a ++) 
    {
      dataFile.print(flg[a]);
      dataFile.print("\n");
    }
    dataFile.close();
}

void load_flg()
{
  int a;
  String line;
  File dataFile;
    
    dataFile = SPIFFS.open("/state.txt", FILE_READ);
    for(a=0; a<4; a++)
    {
      line = dataFile.readStringUntil('\n');
      flg[a] = line.toInt();
    }
    dataFile.close();
}

void load_ip_data()
{
  int a;
  String line;
  File dataFile;
  uint8_t ip_data[15];

    dataFile = SPIFFS.open("/ip_data.txt", FILE_READ);
    d_ssid = dataFile.readStringUntil('\n');
    d_pass = dataFile.readStringUntil('\n');

    for(a=0; a<12; a++)
    {
      line = dataFile.readStringUntil('.');
      ip_data[a] = line.toInt();
        
    }
    dataFile.close();

    for(a=0; a<4; a++)
    {
      d_ip[a]=ip_data[a];
      d_gateway[a]=ip_data[a+4];
      d_netmask[a]=ip_data[a+8];
    }
}

void setup_New_IP()
{
   
  IPAddress ap_ip(192, 168, 1, 2);
  IPAddress ap_gateway(192, 168, 1, 1);
  IPAddress ap_netmask(255, 255, 255, 0);

    flg[play]=0;                  

    WiFi.mode(WIFI_AP);
    WiFi.softAP("ESP32_AP","12345678");
    delay(1000);
    WiFi.softAPConfig(ap_ip,ap_gateway,ap_netmask); 
 
    server.on("/", ap_handleRoot);         
    server.begin();   
 
    Serial.print("IP address: ");
    Serial.println(WiFi.softAPIP());
    Serial.println("AP_HTTP Server started");

    pinMode(2, OUTPUT);
    digitalWrite(2, HIGH);
}

void ap_handleRoot()
{
  int a,b,fl;
  File dataFile;
  String hp_buf,line;

    line=server.argName(0);
    fl=1;  
    switch(line.toInt())
    {
      case 1:   // Save ip_data 
                dataFile = SPIFFS.open("/ip_data.txt", FILE_WRITE);

                line = server.arg("1");
                a=0; b=2; hp_buf="";
                while(b)
                { 
                  if(line.charAt(a) != '.')
                    hp_buf += line.charAt(a);
                  else 
                  {
                    b --; hp_buf += "\n";
                  }
                  a++;
                }
                hp_buf += line.substring(a);
                dataFile.println(hp_buf);
                dataFile.close();

                load_ip_data();
                
                break;
                
      case 2:   // Print restart radio
                fl=0;               
                break;
    }

    dataFile = SPIFFS.open("/ap_index.txt", FILE_READ); 
    hp_buf=""; 
    while(dataFile.available())
     {
       hp_buf += dataFile.readStringUntil('\n');
       hp_buf += "\n"; 
     }
    dataFile.close();

    hp_buf += ("document.getElementById('m_ssid').value='" + d_ssid + "';\n");
    hp_buf += ("document.getElementById('m_key').value='" + d_pass + "';\n");
      
    line= String(d_ip[0],DEC) + "." + String(d_ip[1],DEC) + "." + String(d_ip[2],DEC) + "." +  String(d_ip[3],DEC);
    hp_buf += "document.getElementById('m_ip').value='" + line + "';\n";
      
    line= String(d_gateway[0],DEC) + "." + String(d_gateway[1],DEC) + "." + String(d_gateway[2],DEC) + "." +  String(d_gateway[3],DEC);
    hp_buf += "document.getElementById('m_gate').value='" + line + "';\n";
      
    line= String(d_netmask[0],DEC) + "." + String(d_netmask[1],DEC) + "." + String(d_netmask[2],DEC) + "." +  String(d_netmask[3],DEC);
    hp_buf += "document.getElementById('m_net').value='" + line + "';\n";

    if(fl == 0)
      hp_buf += "alert('ESP32 Reset');\n";
      
    hp_buf += "history.pushState(null,null,'/');\n</script>\n</body>\n</html>\n";

    server.send(200, "text/html", hp_buf); 
}
  • 14行:String d_ssid,d_pass;
    • ルータのSSID、パスワードをファイルから読み込み、これらの変数に代入。
  • 29行:define bt_state 23
    • モード判別プッシュボタン用のポート。
  • 32行:IPAddress d_ip,d_gateway,d_netmask;
    • ESP32のIPアドレス、Gateway、Netmask用の変数の宣言
  • 88行:load_ip_data();
    • ここで、ファイルからIPアドレス、Gateway、Netmaskを読み込む
  • 90行:if(digitalRead(bt_state))
    • ここでモードを判別。
    • スイッチが押されていなければ(1の場合)スタンダードモード
    • スイッチが押されていれば(0の場合)APモード
  • 93行:WiFi.begin(d_ssid.c_str(), d_pass.c_str());
    • ここで、WiFiに接続
  • 95行:WiFi.config(d_ip, d_gateway,d_netmask);
    • スマホではホスト名でアクセス出来なかった為、ここでアドレスを固定しています。
  • 123行:setup_New_IP();
    • ここで APモードの設定を行う。
  • 128行:if(flg[play])
    • APモードで立ち上げた時もこの、Loop関数を使用するので、その場合、再生を行わないように変更
    • APモード時は、flg[play]は0にセット。
  • 404行:void load_ip_data()
    • ファイルからIPアドレス、Gateway、Netmaskを読み込む関数本体
  • 431行:void setup_New_IP()
    • APモードでサーバーを立ち上げる関数本体
  • 434から436行:APモードでの、IPアドレス、Gateway、Netmaskの指定
    • APモードでは各アドレスを固定しています。
  • 440行:WiFi.mode(WIFI_AP);
    • APモードの設定
  • 441行:WiFi.softAP(“ESP32_AP”,”12345678″);
    • サーバーに繋ぐ時の使用する、SSIDとパスワード。
    • SSIDを”ESP32-AP”。パスワードを”12345678”に設定。
  • 443行:WiFi.softAPConfig(ap_ip,ap_gateway,ap_netmask);
    • ここで各アドレスを固定。
  • 452から453行: APモードの設定が完了すると、ESP32の青色のLEDが点灯するようにしています。スタンダードモードとAPモードの区別用
  • 456行以下:APモードで立ち上げた時の各パラメータの入力画面を表示する関数。

使用するファイル

前回までに、”entry.txt”,”title.txt”,”link_list.txt”,”state.txt”の4つのファイルを使用していました。今回はこれに、”ap_index.txt”と”ip_data.txt”が追加されています。”ap_index.txt”はアドレス設定画面のHTMLが、”ip_data.txt”にはアドレスとSSID、パスワードが入っています。これらのファイルを”data”フォルダーに入れて保管して下さい。

先ずはコンパイル。次のデータのアップロード。右端のプッシュスイッチを押しながら電源をいれると、ESP32の赤いLEDが最初に点滅し、しばらくすると青いLEDが点滅します。これでAPモードで立ち上がりました。

スマホを用意して

  1. スマホでWIFIを見に行くと、ESP32APが見つかります。
  2. それをタップしてパスワードを入れる。パスワードは”12345678”
  3. 接続されます。でもインターネットは利用出来ませんが表示されます。
  4. ネットワークの情報を見るとこの様になっています。目の前にAPが有るので電波強度は非常に強いです。

ブラウザーを上げて”192.168.1.2”と入力して下さい。ルーター設定用画面が表示されます。

  • ルーターのSSIDとKEYS(パスワード)を入力して下さい。
  • スマホでDNSが使えなかったので、IPアドレスを192.168.3.200にGatewayを192.168.3.1にNetmaskを255.255.255.0に設定しています。必要が有れば自分のルータに合わせて設定して下さい。
  • 最後にSaveボタンを押せば、各値がファイルに保存されます。

電源を切って、今度はプッシュスイッチを押さずに電源を入れて下さい。

いつものラジオの画面が出てきます。前回の設定ではスマホで非常に小さかったので大きさと見た目を若干変更しています。

今回使用してファイルをここに保管します。ー>”I2C_WebRadio_BOX