基于HTTPS长连接的ESP32+VS1053网络电台收音机Arduino代码


基于HTTPS长连接的ESP32+VS1053网络电台收音机Arduino代码

  • 硬件搭建
  1. 本文使用的控制板两块:ESP32最小系统板,带USB转串行接口,电源模块,可向外部供电。VS1053+SD播放模块,板载耳机功放、耳机插头、线路输入插头及麦克风。如下图:
  1. 引脚连接如下:

* ESP32 VS1053_SD 功能说明

*

* D5 ———————- CS SD卡片选

* D18 ———————– SCK SPI总线时钟

* D19 ———————– MISO SPI总线输出

* D23 ———————– MOSI SPI总线输入

* D22 ———————– X_CS VS1053控制总线片选

* D21 ———————– X_DCS VS1053数据总线片选

* D15 ———————– DREQ VS1053总线忙信号

* EN ———————– X_RESET VS1053复位(实际EN脚IO号为34,即D34)

* VIN ———————– 5V 5V电源

* GND ——————— GND 接地线

*

  • 软件环境

代码编辑使用的是Arduino,并配置好ESP32编译环境。具体软件下载和配置方法网上很多,不再赘述。

  • 程序说明

程序总体思路是:

void setup() {
  uint32_t TimerOut;
  Serial.begin(115200);
  Player.begin();//Player.SetVolume(90);
  WiFi.mode(WIFI_STA);
  WiFi.begin(SSIDNAME,PASSWORD);

  Serial.print("Waiting for WiFi to connect...");
  TimerOut=0;while ((WiFi.status()!=WL_CONNECTED) && TimerOut++<60){delay(500);Serial.print(".");}
  if(TimerOut>=60){Serial.println("Connection failed");return;}else{Serial.println(" connected");}
  setClock();//同步NTP服务器时间,加密传输运算需要。
  httpsconnection();//连接网站以获取电台播放数据。
}

1.先连接WIFI(注意SSIDNAME和PASSWORD须与自己使用的WIFI信息一致),

2.系统时间与NTP服务器时间同步(HTTPS连接须要用到)。void setClock()

3.连接电台。void httpsconnection(void)

过程中client -> setCACert(rootCACertificate);语句设置使用网站根证书验证,必须将根证书信息保存在rootCACertificate变量中。本程序中保存的是蜻蜓网络电台的网站根证书,如果连接其他网络电台,则必须进行相应修改。如果不想使用网站根证书进行验证,则可以将此句注释掉,然后使用client -> setInsecure();通知服务器不使用安全验证,明码传输虽然不安全,但对于网络电台收音机来说,这个并不重要。

需要注意的是,如果使用根证书验证,则此过程必须在setClock()过程更新时间后进行,否则连接会不成功。

4.连接成功后不断接收服务器下发的数据块,进行相应处理后存入缓存中,并通过SPI串行总线将数据发送给VS1053解码播放。此过程在loop()主循环中执行。

  • 完整代码如下:编译前请先修改SSIDNAME和PASSWORD。其中class VS_1053是一个VS1053操作对象的完整封装,里面有很多用于MP3播放的函数,如果用不上,可以删去多余的函数。完整代码可以到以下地址下载:https://download..net/download/liyong_sbcel/78272687
void setClock() {
  //此过程更新ESP32内部时钟。
  //configTime(0, 0, "pool.ntp.org", "time.nist.gov");
  configTime(0, 0, "ntp1.aliyun.com", "ntp2.aliyun.com","ntp3.aliyun.com");
  Serial.print(F("Waiting for NTP time sync: "));
  time_t nowSecs = time(nullptr);
  while (nowSecs < 8 * 3600 * 2) {
    delay(500);
    Serial.print(F("."));
    yield();
    nowSecs = time(nullptr);
  }
  Serial.println();
  struct tm timeinfo;
  gmtime_r(&nowSecs, &timeinfo);
  Serial.print(F("Current time: "));
  Serial.println(&timeinfo, "%F %T %A"); // 格式化输出:2021-10-24 23:00:44 Sunday
}
void httpsconnection(void){
  uint16_t Index;
  String writeString;
  uint8_t writeBuffer[500];
  delete client;client = new WiFiClientSecure;
  client -> setCACert(rootCACertificate);//根证书验证。
  client -> setTimeout(15000);//设置超时。
  //client -> setInsecure();//不进行身份验证(不使用加密传输)。
  Serial.printf("connect https://%s:%d%s...",host,httpsPort,path);
  client -> connect(host,httpsPort,15000);//开始连接。
  Index=100;while(!client -> connected() && Index>0){delay(100);Index--;}if(!client -> connected()){Serial.println("Connection failed");return;}else{Serial.println(" connected");}//超过10秒未建立连接则显示连接失败并退出。
  writeString=String("GET ")+path+" HTTP/1.1
Host: "+host+"

";
  for(Index=0;Index<writeString.length();Index++){
    writeBuffer[Index]=writeString[Index];
  }
  client -> write(writeBuffer,Index);
  Index=100;BufferIndex=0;writeString="";
  while(Index){
    delay(100);Index--;
    while(client->available()){
      writeString=writeString+char(client->read());BufferIndex++;
      if(BufferIndex>30){
        //等待服务器HTTP回应信息。
        if(writeString[BufferIndex-1]==
 && writeString[BufferIndex-2]==
 && writeString[BufferIndex-3]==
 && writeString[BufferIndex-4]==
){
          Serial.print(writeString);
          Index=0;break;
        }
      }
      if(BufferIndex>BufferSize){Index=0;break;}//非HTTP响应,放弃并跳出。
    }
  }
  //因为第一帧数据表示后续块的大小,为配合数据处理算法,加上一个结束标志.
  Buffer[0]=
;Buffer[1]=
;
  BufferIndex=2;PlayIndex=0;
}
void setup() {
  uint32_t TimerOut;
  Serial.begin(115200);
  Player.begin();//Player.SetVolume(90);
  WiFi.mode(WIFI_STA);
  WiFi.begin(SSIDNAME,PASSWORD);
  Serial.print("Waiting for WiFi to connect...");
  TimerOut=0;while ((WiFi.status()!=WL_CONNECTED) && TimerOut++<60){delay(500);Serial.print(".");}
  if(TimerOut>=60){Serial.println("Connection failed");return;}else{Serial.println(" connected");}
  setClock();//同步NTP服务器时间,加密传输运算需要。
  httpsconnection();//连接网站以获取电台播放数据。
}
void loop() {
  int index;
  if(!client->connected()){Serial.printf("重新连接:
");httpsconnection();}//如连接断开则自动重连。
  //不断读取电台数据,处理后存入缓存中。
  while(client->available()){
    Buffer[BufferIndex]=client->read();BufferIndex++;
    if(Buffer[(BufferIndex+BufferSize-1)%BufferSize]==
 && Buffer[(BufferIndex+BufferSize-2)%BufferSize]==
 && Buffer[(BufferIndex+BufferSize-6)%BufferSize]==
 && Buffer[(BufferIndex+BufferSize-7)%BufferSize]==
){
      //int DataSize=(Buffer[(BufferIndex+BufferSize-3)%BufferSize]-48)+(Buffer[(BufferIndex+BufferSize-4)%BufferSize]-48)*16+(Buffer[(BufferIndex+BufferSize-5)%BufferSize]-48)*16*16;
      //Serial.printf("块大小:%d
",DataSize);
      BufferIndex=(BufferIndex+BufferSize-7)%BufferSize;//去除无用的块大小指示帧及块结束标志字节(间隔字符7字节"前一帧数据
xxx
后一帧数据")(将各帧播放数据连接起来)
    }else{
      BufferIndex=BufferIndex%BufferSize;
    }
    //保证缓存足量数据用于播放模块。(缓存将满时将数据提供给播放模块)
    if(PlayIndex>BufferIndex && PlayIndex-BufferIndex<1024 || BufferIndex>PlayIndex && BufferIndex-PlayIndex>BufferSize-1024){break;}
    yield();//交出系统控制权。
  }
  //接收数据为空或缓存将满时都会播放缓存数据。
  if(PlayIndex>BufferIndex && PlayIndex-BufferIndex<BufferSize-128 || BufferIndex>PlayIndex && BufferIndex>PlayIndex+128){
    Player.playChunk(Buffer,BufferSize,PlayIndex,64);
    PlayIndex=(PlayIndex+64)%BufferSize;
  }
  yield();
}

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/291101.html

(0)
上一篇 2022年10月15日 23:57
下一篇 2022年10月15日 23:58

相关推荐

发表回复

登录后才能评论