顯示具有 BT Speaker 標籤的文章。 顯示所有文章
顯示具有 BT Speaker 標籤的文章。 顯示所有文章

2023年12月18日 星期一

Bluetooth Speaker

Purpose:
利用現有BT audio receiver board來製作一台藍芽音箱, 從中學習到一些電子模組的焊接組合, Fusion360的3D繪製倒出圖檔到雷射切割機去做箱體的切割. 

INTRODUCTION
1. Bluetooth 5.0 Audio Receiver Board - Controllable Volume
This is a small size and cost-effective Bluetooth 5.0 Audio Receiver module with the auto-reconnect feature. It supports a wide-range power supply of 3.7V~24V and comes with a function button. Dial the button to the left and right to adjust volume, and press it to pause or play. You can use this module to remake your loudspeaker into wireless.



2. Mini Boost DC DC Step Up 1.5A Tegangan Fix Output Pilihan 5V 8V 9V 12V
Basic parameters
The output voltage can be set to 5V/8V/9V/12V, the default is 12V
Input voltage range: 2.5V- 5V
Output performance: Take 3.7V lithium battery input


3. Power Amp MINI SFT-9718 Stereo 60 Watt RMS. DC 12 V. - 15 V.
4. 喇吧與箱體



Circuit:



2023年12月2日 星期六

TmallGenie Voice control ESP32

Purpose:
天貓精靈(TmallGenie)+ESP32+巴法雲, 實現語音家電控制!
Tmall Genie + ESP32 + Bemfa Cloud, accomplish voice control of home appliances!




Architectures:
Fundamental:

Reference https://en.wikipedia.org/wiki/Tmall_Genie
天貓精靈是阿里巴巴人工智慧實驗室研發的智慧語音助理音箱,基於AliGenie,具有娛樂、生活、購物等多個領域的數百個功能,類似於Amazon Echo、Google Home、Apple HomePod。 天貓精靈於2017年7月5日發布,2017年8月8日正式開賣。
基本參數
產品類型 智慧音箱
揚聲器 內建4Ω揚聲器
輸出功率 3W
麥克風 支持,2個
產品功能 語音辨識控制功能,自訂喚醒詞,通話
無線 WiFi 2.4GB,藍牙4.2
其它參數
處理器 MTK MT8516
系統:AliGenie語音助理
顏色 紅色,白色
尺寸 134*65*59mm
重量 265g
供電方式 DC 12V/1A
電源性能 100-240V/50Hz
控制方式 按鍵,語音
介面 耳機介面
其它特點 內建功放:TI TAS5751M
支援智慧家庭控制、語音購物、手機儲值、叫外送、音頻音樂播放等功能,尤其是購物方面,只需要說出產品描述,然後透過聲紋驗證身份,就能直接下單
訂行程、備忘提醒、訂鬧鐘、查天氣、找手機、倒數、卡路里查詢、聽新聞、查星座運勢、查價格、查股票等功能
與普普藝術結合,波點酷設計,全屋場景體驗升級,就近換新
支援多台設備採用Multi-Room功能進行連動播放
連線方式 WiFi,藍牙
巴法云 C#/.NET SDK。支持TCP和MQTT协议接入,并提供超级简单的设备操作API。跨平台,支持Linux、Windows、OSX,支持树莓派。
Reference https://github.com/withsalt/BemfaCloud
                  https://cloud.bemfa.com/user/index.html


註冊你的帳號, 新增你的主題!

Reference the previous article link on blog:


YouTube Demo:


ESP32 Code:

#include <WiFi.h>
#include <WiFiClient.h>


//巴法云服务器地址默认即可
#define TCP_SERVER_ADDR "bemfa.com"
//服务器端口,tcp创客云端口8344
#define TCP_SERVER_PORT "8344"

//********************需要修改的部分*******************//
#define DEFAULT_STASSID  "Winson_Y52"     //WIFI名称,区分大小写,不要写错
#define DEFAULT_STAPSW   "8888888888"  //WIFI密码
String UID = "your ID";  //用户私钥,可在控制台获取,修改为自己的UID
String TOPIC =   "fan003";         //主题名字,可在控制台新建
String TOPIC1 =   "adapter001";         //主题名字,可在控制台新建
String TOPIC2 =   "led002";         //主题名字,可在控制台新建
//const int LED_Pin = D2;              //单片机引脚
//-----Relay------------------
//-----Relay------------------
#define RELAY1       32
#define RELAY2       33
#define RELAY3       25
#define RELAY4       26
#define RELAY5       5
#define RELAY6       17
#define RELAY7       16
#define RELAY8       4
//**************************************************//

//最大字节数
#define MAX_PACKETSIZE 512
//设置心跳值30s
#define KEEPALIVEATIME 30*1000

//tcp客户端相关初始化,默认即可
WiFiClient TCPclient;
String TcpClient_Buff = "";
unsigned int TcpClient_BuffIndex = 0;
unsigned long TcpClient_preTick = 0;
unsigned long preHeartTick = 0;//心跳
unsigned long preTCPStartTick = 0;//连接
bool preTCPConnected = false;

//相关函数初始化
//连接WIFI
void doWiFiTick();
void startSTA();

//TCP初始化连接
void doTCPClientTick();
void startTCPClient();
void sendtoTCPServer(String p);

//风扇控制函数
void turnOn();
void turnOff();



/*
  *发送数据到TCP服务器
 */
void sendtoTCPServer(String p){
 
  if (!TCPclient.connected())
  {
    Serial.println("Client is not readly");
    return;
  }
  TCPclient.print(p);
  Serial.println("[Send to TCPServer]:String");
  Serial.println(p);
}


/*
  *初始化和服务器建立连接
*/
void startTCPClient(){
  if(TCPclient.connect(TCP_SERVER_ADDR, atoi(TCP_SERVER_PORT))){
    Serial.print("\nConnected to server:");
    Serial.printf("%s:%d\r\n",TCP_SERVER_ADDR,atoi(TCP_SERVER_PORT));
   
    String tcpTemp="";  //初始化字符串
    String tcpTemp1="";  //初始化字符串
    String tcpTemp2="";  //初始化字符串
    tcpTemp = "cmd=1&uid="+UID+"&topic="+TOPIC+"\r\n"; //构建订阅指令
    sendtoTCPServer(tcpTemp); //发送订阅指令
    tcpTemp="";//清空
    tcpTemp1 = "cmd=1&uid="+UID+"&topic="+TOPIC1+"\r\n"; //构建订阅指令
    sendtoTCPServer(tcpTemp1); //发送订阅指令
    tcpTemp1="";//清空
    tcpTemp2 = "cmd=1&uid="+UID+"&topic="+TOPIC2+"\r\n"; //构建订阅指令
    sendtoTCPServer(tcpTemp2); //发送订阅指令
    tcpTemp2="";//清空

    preTCPConnected = true;
    preHeartTick = millis();
    TCPclient.setNoDelay(true);
  }
  else{
    Serial.print("Failed connected to server:");
    Serial.println(TCP_SERVER_ADDR);
    TCPclient.stop();
    preTCPConnected = false;
  }
  preTCPStartTick = millis();
}


/*
  *检查数据,发送心跳
*/
void doTCPClientTick(){
 //检查是否断开,断开后重连
   if(WiFi.status() != WL_CONNECTED) return;

  if (!TCPclient.connected()) {//断开重连

  if(preTCPConnected == true){

    preTCPConnected = false;
    preTCPStartTick = millis();
    Serial.println();
    Serial.println("TCP Client disconnected.");
    TCPclient.stop();
  }
  else if(millis() - preTCPStartTick > 1*1000)//重新连接
    startTCPClient();
  }
  else
  {
    if (TCPclient.available()) {//收数据
      char c =TCPclient.read();
      TcpClient_Buff +=c;
      TcpClient_BuffIndex++;
      TcpClient_preTick = millis();
     
      if(TcpClient_BuffIndex>=MAX_PACKETSIZE - 1){
        TcpClient_BuffIndex = MAX_PACKETSIZE-2;
        TcpClient_preTick = TcpClient_preTick - 200;
      }
      preHeartTick = millis();
    }
    if(millis() - preHeartTick >= KEEPALIVEATIME){//保持心跳
      preHeartTick = millis();
      Serial.println("--Keep alive:");
      sendtoTCPServer("cmd=0&msg=keep\r\n");
    }
  }
  if((TcpClient_Buff.length() >= 1) && (millis() - TcpClient_preTick>=200))
  {//data ready
    TCPclient.flush();
    Serial.println("Buff");
    Serial.println(TcpClient_Buff);
   
    if((TcpClient_Buff.indexOf("fan003") > 0))
    {
      if((TcpClient_Buff.indexOf("&msg=on") > 0))
      {
        turnOn();
      }
      else if((TcpClient_Buff.indexOf("&msg=off") > 0))
      {
        turnOff();
      }
    }
    //-------------------------------------------------------------
    if((TcpClient_Buff.indexOf("adapter001") > 0))
    {
      if((TcpClient_Buff.indexOf("&msg=on") > 0))
      {
        turnAdapterOn();
      }
      else if((TcpClient_Buff.indexOf("&msg=off") > 0))
      {
        turnAdapterOff();
      }
    }
    //-------------------------------------------------------------
    if((TcpClient_Buff.indexOf("led002") > 0))
    {
      if((TcpClient_Buff.indexOf("&msg=on") > 0))
      {
        turnLedOn();
      }
      else if((TcpClient_Buff.indexOf("&msg=off") > 0))
      {
        turnLedOff();
      }
    }
    /**
    if((TcpClient_Buff.indexOf("&msg=on") > 0)) {
      turnOn();
    }else if((TcpClient_Buff.indexOf("&msg=off") > 0)) {
      turnOff();
    }**/
   TcpClient_Buff="";
   TcpClient_BuffIndex = 0;
  }
}

void startSTA(){
  WiFi.disconnect();
  WiFi.mode(WIFI_STA);
  WiFi.begin(DEFAULT_STASSID, DEFAULT_STAPSW);
}

/**************************************************************************
                                 WIFI
***************************************************************************/
/*
  WiFiTick
  检查是否需要初始化WiFi
  检查WiFi是否连接上,若连接成功启动TCP Client
  控制指示灯
*/
void doWiFiTick(){
  static bool startSTAFlag = false;
  static bool taskStarted = false;
  static uint32_t lastWiFiCheckTick = 0;

  if (!startSTAFlag) {
    startSTAFlag = true;
    startSTA();
    Serial.printf("Heap size:%d\r\n", ESP.getFreeHeap());
  }

  //未连接1s重连
  if ( WiFi.status() != WL_CONNECTED ) {
    if (millis() - lastWiFiCheckTick > 1000) {
      lastWiFiCheckTick = millis();
    }
  }
  //连接成功建立
  else {
    if (taskStarted == false) {
      taskStarted = true;
      Serial.print("\r\nGet IP Address: ");
      Serial.println(WiFi.localIP());
      startTCPClient();
    }
  }
}
//打開fan
void turnOn(){
  Serial.println("Turn ON");
  //digitalWrite(LED_Pin,LOW);
  digitalWrite(RELAY1,LOW);
}
//关闭灯泡
void turnOff(){
  Serial.println("Turn OFF");
    digitalWrite(RELAY1,HIGH);
}
//打开灯泡
void turnAdapterOn(){
  Serial.println("Turn1 ON");
  //digitalWrite(LED_Pin,LOW);
  digitalWrite(RELAY2,LOW);
}
//关闭灯泡
void turnAdapterOff(){
  Serial.println("Turn1 OFF");
    digitalWrite(RELAY2,HIGH);
}
//打开灯泡
void turnLedOn(){
  Serial.println("Turn2 ON");
  //digitalWrite(LED_Pin,LOW);
  digitalWrite(RELAY3,LOW);
}
//关闭灯泡
void turnLedOff(){
  Serial.println("Turn2 OFF");
    digitalWrite(RELAY3,HIGH);
}


// 初始化,相当于main 函数
void setup() {
  Serial.begin(9600);
  pinMode(RELAY1,OUTPUT);
  digitalWrite(RELAY1,HIGH);
  pinMode(RELAY2,OUTPUT);
  digitalWrite(RELAY2,HIGH);
  pinMode(RELAY3,OUTPUT);
  digitalWrite(RELAY3,HIGH);
  pinMode(RELAY4,OUTPUT);
  digitalWrite(RELAY4,HIGH);
  pinMode(RELAY5,OUTPUT);
  digitalWrite(RELAY5,HIGH);
  pinMode(RELAY6,OUTPUT);
  digitalWrite(RELAY6,HIGH);
  pinMode(RELAY7,OUTPUT);
  digitalWrite(RELAY7,HIGH);
  pinMode(RELAY8,OUTPUT);
  digitalWrite(RELAY8,HIGH);

}

//循环
void loop() {
  doWiFiTick();
  doTCPClientTick();
}





2023年11月29日 星期三

Control Your Music by ESP32 Bluetooth Speaker

Purpose:
利用上一篇文章ESP32 Bluetooth Speaker再加上Play, Pause, Next and Previous的按鍵去控制Mobile APP的動作並且可以自動連接上一個連接過的裝置!

Use the ESP32 Bluetooth Speaker in the previous article add the Play, Pause, Next and Previous buttons to control the actions of the Mobile APP and automatically connect to a connected device!

Fundamental:
Reference https://en.wikipedia.org/wiki/List_of_Bluetooth_profiles

藍牙規範(Bluetooth profile),藍牙技術聯盟定義了許多Profile。Profile目的是要確保Bluetooth裝置間的互通性(interoperability)。但Bluetooth產品無須實現所有的Bluetooth規範Profile。Bluetooth 版本 1.1 定義13個Profiles。
目前WinCE6.0提供的Profile有DUN GATEWAY、HF/740T、Audio Gateway、LAP、PAN、HID。

This Project 用到的BT規範(A2DP and AVRCP):
藍牙立體聲音訊傳輸規範(A2DP
藍牙立體聲音訊傳輸規範(Advance Audio Distribution Profile),規定了使用藍牙非同步傳輸信道方式,傳輸高品質音樂檔案數據的協定堆棧軟體和使用方法,基於該協定就能通過以藍牙方式傳輸高品質的立體聲音樂。分為1.1版和1.2版,只要連接雙方支援A2DP協定都能以16 bits,44.1 kHz的品質傳輸聲音訊號。假如有一方沒有支援A2DP的話,只能以8 bits,8 kHz的品質的免手持裝置規範(Handsfree Profile)傳輸模式,聲音品質會大打折扣。
音訊/影片遠端控制設定檔(AVRCP
音訊/影片遠端控制設定檔(Audio Video Remote Control Profile,簡稱AVRCP)設計用於提供控制 TV、Hi-Fi 裝置等的標準[1]介面。此設定檔用於許可單個遠端控制裝置。

Reference the previous article link on blog:


YouTube Demo:




ESP32 Code:

#include "BluetoothA2DPSink.h"
#include <ezButton.h>

BluetoothA2DPSink a2dp_sink;
esp_a2d_connection_state_t last_state;
bool is_active = true;
int LEDFlag = 0;
#define PLAY      33
#define STOP      32
#define NEXT      22
#define PREVIOUS  23
int bValue_A = 0; // To store value of the button
int bValue_B = 0; // To store value of the button
int bValue_X = 0; // To store value of the button
int bValue_Y = 0; // To store value of the button
ezButton buttonA(PLAY);
ezButton buttonB(STOP);
ezButton buttonX(NEXT);
ezButton buttonY(PREVIOUS);

void avrc_metadata_callback(uint8_t id, const uint8_t *text)
{
  Serial.printf("==> AVRC metadata rsp: attribute id 0x%x, %s\n", id, text);
}
//---------------------------------------------------------------------------------
#ifndef LED_BUILTIN
#define LED_BUILTIN 2
#endif
TaskHandle_t hled;
void initial()
{
  Serial.println(F("Create Task"));
  //--------------- create task----------------------------------
  xTaskCreatePinnedToCore(
    vLEDTask, "LEDTask" // A name just for humans
    ,
    1024 // This stack size can be checked & adjusted by reading the Stack Highwater
    ,
    NULL, 2 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,
    &hled //handle
    ,
    0);
  //----------------------------------------
  //----------------------------------------------------------------------
}

void setup()
{
  Serial.begin(9600);
  Serial.println(F("init"));
  initial();
  //-----------------------------------------------------------------
  buttonA.setDebounceTime(50); // set debounce time to 50 milliseconds
  buttonB.setDebounceTime(50); // set debounce time to 50 milliseconds
  buttonX.setDebounceTime(50); // set debounce time to 50 milliseconds
  buttonY.setDebounceTime(50); // set debounce time to 50 milliseconds
  static const i2s_config_t i2s_config = {
        .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN),
        .sample_rate = 44100, // corrected by info from bluetooth
        .bits_per_sample = (i2s_bits_per_sample_t) 16, /* the DAC module will only take the 8bits from MSB */
        .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
        .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB,
        .intr_alloc_flags = 0, // default interrupt priority
        .dma_buf_count = 8,
        .dma_buf_len = 64,
        .use_apll = false
    };
  a2dp_sink.set_i2s_config(i2s_config);
  a2dp_sink.set_auto_reconnect(true);
  a2dp_sink.set_avrc_metadata_callback(avrc_metadata_callback);
  a2dp_sink.start("MyMusic");  
}


void loop()
{
  Serial.print(F("Main at core:"));
  Serial.println(xPortGetCoreID());
  while (1)
  {
    buttonA.loop();
    buttonB.loop();
    buttonX.loop();
    buttonY.loop();
    //----------------------------
    bValue_A = buttonA.getState();
    bValue_B = buttonB.getState();
    bValue_X = buttonX.getState();
    bValue_Y = buttonY.getState();
    if (buttonA.isPressed()) {
      Serial.println("The buttonA is pressed");
      Serial.println("play");
      a2dp_sink.play();
      LEDFlag=2;
    }
    if (buttonA.isReleased()) {
      Serial.println("The buttonA is released");
      // TODO do something here
    }
    if (buttonB.isPressed()) {
      Serial.println("The buttonB is pressed");
      Serial.println("pause");
      a2dp_sink.pause();
      LEDFlag=1;
    }
    if (buttonB.isReleased()) {
      Serial.println("The buttonB is released");
      // TODO do something here
    }
    if (buttonX.isPressed()) {
      Serial.println("The buttonX is pressed");
      Serial.println("next");
      a2dp_sink.next();
      LEDFlag=2;
    }
    if (buttonX.isReleased()) {
      Serial.println("The buttonX is released");
      // TODO do something here
    }
    if (buttonY.isPressed()) {
      Serial.println("The buttonY is pressed");
      Serial.println("previous");
      a2dp_sink.previous();
      LEDFlag=2;
    }
    if (buttonY.isReleased()) {
      Serial.println("The buttonY is released");
      // TODO do something here
    }
    //--------------------------------------------------------
    // check state
    esp_a2d_connection_state_t state = a2dp_sink.get_connection_state();
    bool is_connected = state == ESP_A2D_CONNECTION_STATE_CONNECTED;
    if (last_state != state)
    {
      Serial.println(is_connected ? "Connected" : "Not connected");    
      last_state = state;
      if(is_connected)
      {
        LEDFlag=1;
      }
    }
 
    //------------------------------------------
    if (a2dp_sink.get_audio_state()==ESP_A2D_AUDIO_STATE_STARTED)
    {
      //Serial.println("changing state...");
      is_active = !is_active;
   
      if (is_active)
      {
        //Serial.println("play");
        //a2dp_sink.play();
        LEDFlag=2;
      }
      else
      {
        //Serial.println("pause");
        //a2dp_sink.pause();
        //LEDFlag=1;
      }
    }
    delay(10);
  } //---while(1)
 
}

//-------------------------------------------------------------------------
static void vLEDTask(void *pvParameters)
{
  (void)pvParameters;

  Serial.println(F("LEDTask at core:"));
  Serial.println(xPortGetCoreID());
  pinMode(LED_BUILTIN, OUTPUT);
  for (;;) // A Task shall never return or exit.
  {
    if(LEDFlag == 0)
    {
      digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
      vTaskDelay(800);
    }
    else if(LEDFlag == 1)
    {
      digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
      vTaskDelay(800);
      digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
      vTaskDelay(800);
    }
    else if(LEDFlag == 2)
    {
      digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
      vTaskDelay(300);
      digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
      vTaskDelay(300);
    }
    else
    {
      vTaskDelay(10);
    }
  }
}