Привет опишу пока кратко как я это сделал. Если что будет дополнятся.
UPD. Добавлено ключевое слово и реакция на него
АПДЕЙТ 2 - добавлен в скетч логин и пароль для управления терминалом - просто если он смотрит в инет то кто угодно может им управлять
Что необходимо:
Соединяем INMP441 с ЕСП
SD - 26
WS - 27
SCK - 14
GND and L\R - GND
VDD - 3.3 v
Соединяем max98357a и ЕСП32
BCLK - 18
LRC - 5
DIN - 19
GND - GND
GAIN - GND - по идее надо тоже - но я не подключал
VDD - 5 v
Эти соединения расщитаны на использование любого 4 пинового шлейфа - в принципе увидите когда будете соединять
Это скетч - он сырой - но я не ардуинщик и слеплено все с примеров
Все настройки скетча находятся сверху. Единственное библиотеки будет просить
Одна из них точно не стандартная - https://github.com/pschatzmann/arduino-audio-tools
Езе одна точно - https://github.com/pschatzmann/arduino-libhelix
ЕЕ надо добавлять - все остальные стандартные из репозитария ЕСП 32 - в принипе разберетесь.
Терминал работает удаленно - если порты прокинуть в инет.
Сейчас структура такая у него .
Существует два процесса по ядрах
Нюансов конечно еще много:
Работает быстро по крайней мере как по мне - видео в курилке есть
Это сам скетч
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <uri/UriBraces.h>
#include <uri/UriRegex.h>
#include <HTTPClient.h>
#include "AudioTools.h"
#include "AudioCodecs/CodecMP3Helix.h"
#include <ArduinoWebsockets.h>
#include <driver/i2s.h>
#define I2S_SD 26
#define I2S_WS 27
#define I2S_SCK 14
const char *ssid = "name";
const char *password = "pass";
WebServer server(2710); //port web servera upravleniya
const char* www_username = "username"; //user for web server
const char* www_password = "password"; //password for webserver
String serverName = "http://login:pass@ip:port/command.php?qry=";
const char* websocket_server = "ws://ip:port"; // adres servera raspoznavamniya
String keyword = "алиса";
#define bufferCnt 3
#define bufferLen 1024
int16_t sBuffer[bufferLen];
using namespace websockets;
WebsocketsClient client;
bool isWebSocketConnected;
bool wakeword = false;
int timer;
float volume = 0.75;
String state = "Stop";
int buf ;
String play_file;
void onEventsCallback(WebsocketsEvent event, String data) {
if (event == WebsocketsEvent::ConnectionOpened) {
Serial.println("Connnection Opened");
isWebSocketConnected = true;
} else if (event == WebsocketsEvent::ConnectionClosed) {
Serial.println("Connnection Closed");
isWebSocketConnected = false;
} else if (event == WebsocketsEvent::GotPing) {
Serial.println("Got a Ping!");
} else if (event == WebsocketsEvent::GotPong) {
Serial.println("Got a Pong!");
}
}
void i2s_install() {
// Set up I2S Processor configuration
const i2s_config_t i2s_config = {
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = 16000,
//.sample_rate = 16000,
.bits_per_sample = i2s_bits_per_sample_t(16),
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
.intr_alloc_flags = 0,
.dma_buf_count = bufferCnt,
.dma_buf_len = bufferLen,
.use_apll = false
};
i2s_driver_install(I2S_NUM_1, &i2s_config, 0, NULL);
}
void i2s_setpin() {
// Set I2S pin configuration
const i2s_pin_config_t pin_config = {
.bck_io_num = I2S_SCK,
.ws_io_num = I2S_WS,
.data_out_num = -1,
.data_in_num = I2S_SD
};
i2s_set_pin(I2S_NUM_1, &pin_config);
}
void connectWSServer() {
client.onEvent(onEventsCallback);
while (!client.connect(websocket_server)) {
delay(500);
Serial.print(".");
}
Serial.println("Websocket Connected!");
}
const char *urls[] = {""};
const char *urlfile = "";
URLStream urlStream(ssid, password);
AudioSourceURL source(urlStream, urls, "audio/mp3");
I2SStream i2s;
MP3DecoderHelix decoder;
AudioPlayer player(source, i2s, decoder);
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.println("Start wifi connect");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
if (MDNS.begin("esp32")) {
Serial.println("MDNS responder started");
}
server.on(F("/"), []() {
server.send(200, "text/plain", "Hello from Taras! This is audio terminal for Majordomo.");
});
server.on(UriBraces("/setvolume/{}"), []() {
if (!server.authenticate(www_username, www_password)) {
return server.requestAuthentication();
}
String vol = server.pathArg(0);
server.send(200, "text/plain", "setVolume:" + String(vol));
Serial.println("setVolume:" + String(vol.toInt()));
volume = vol.toInt() / 100.0f;
player.setVolume(volume);
});
server.on(F("/getvolume"), []() {
if (!server.authenticate(www_username, www_password)) {
return server.requestAuthentication();
}
int data = volume * 100;
server.send(200, "text/plain", "Volume:" + String(data));
Serial.println("Volume:" + String(data));
});
server.on(UriRegex("^\\/playfile\\/(.+\.[a-z0-9]+)$"), []() {
if (!server.authenticate(www_username, www_password)) {
return server.requestAuthentication();
}
play_file = server.pathArg(0);
server.send(200, "text/plain", "playFile:" + String(play_file));
Serial.println("playFile:" + String(play_file));
state = "Stop";
delay (500);
state = "Play";
player.play();
player.setPath(play_file.c_str());
});
server.on(F("/stopplay"), []() {
if (!server.authenticate(www_username, www_password)) {
return server.requestAuthentication();
}
server.send(200, "text/plain", "Stoping play");
Serial.println("Stoping play" );
state = "Stop";
player.stop();
});
server.on(F("/getfile"), []() {
if (!server.authenticate(www_username, www_password)) {
return server.requestAuthentication();
}
if (buf > 0) {
server.send(200, "text/plain", "File:" + String(play_file));
Serial.println("getFile:" + String(play_file));
} else {
server.send(200, "text/plain", "File:" );
Serial.println("getFile:");
}
});
server.on(F("/getstatus"), []() {
if (!server.authenticate(www_username, www_password)) {
return server.requestAuthentication();
}
int data = volume * 100;
if (buf > 0) {
server.send(200, "text/plain", "status|File:" + String(play_file) + "|Volume:" + String(data));
Serial.println("status|File:" + String(play_file) + "|Volume:" + String(data));
} else {
server.send(200, "text/plain", String("status|File:") + String("|Volume:") + String(data));
Serial.println(String("status|File:") + String("|Volume:") + String(data));
}
});
server.begin();
Serial.println("HTTP server started");
//AudioLogger::instance().begin(Serial, AudioLogger::Info);
// setup output
auto cfg = i2s.defaultConfig(TX_MODE);
//cfg.i2s_format = I2S_STD_FORMAT;
cfg.is_master = true;
cfg.port_no = 0;
cfg.pin_bck = 18;
cfg.pin_ws = 5;
cfg.pin_data = 19;
i2s.begin(cfg);
// start decoder
decoder.begin();
player.begin();
player.setVolume(volume);
player.setAutoNext(false); // встановимо щоб не крутило по кругу посилання для програвання
connectWSServer();
client.onMessage([&](WebsocketsMessage message) {
if (message.data().length() > 0) {
String text = message.data();
if (text.lastIndexOf("partial") > -1 and text.lastIndexOf(keyword) > -1) {
wakeword = true;
timer = 10;
Serial.println("keyword in partial text");
} else if (text.lastIndexOf("full") > -1 and (wakeword == true or text.lastIndexOf(keyword) > -1)) {
timer = 10;
Serial.print("Got Message: ");
Serial.println(message.data());
text.replace("full", "");
text.replace(keyword, "");
text.replace(" ", " ");
text.trim();
text.replace(" ", "%20");
HTTPClient http;
String serverPath = serverName + text;
http.begin(serverPath.c_str());
int httpResponseCode = http.GET();
if (httpResponseCode > 0) {
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
}
else {
Serial.print("Error code: ");
Serial.println(httpResponseCode);
}
// Free resources
http.end();
}
}
});
xTaskCreatePinnedToCore(micTask, "micTask", 10240, NULL, 1, NULL, 0);
xTaskCreatePinnedToCore(muteTask, "muteTask", 10240, NULL, 2, NULL, 1);
}
void loop() {
while (state == "Play" ) {
buf = player.copy();
if ( buf == 0 ) {
state = "Stop";
player.stop();
}
delay (10);
server.handleClient();
}
delay (100);
server.handleClient();
}
void micTask(void* parameter) {
i2s_install();
i2s_setpin();
i2s_start(I2S_NUM_1);
size_t bytesIn = 0;
while (1) {
esp_err_t result = i2s_read(I2S_NUM_1, &sBuffer, bufferLen, &bytesIn, portMAX_DELAY);
if (result == ESP_OK && isWebSocketConnected) {
client.sendBinary((const char*)sBuffer, bytesIn);
client.poll();
} else {
connectWSServer();
}
delay (10);
}
}
void muteTask(void* parameter) {
while (1) {
if (wakeword == true) {
while (timer > 0 ) {
player.setVolume(0);
delay (1000);
timer -= 1;
}
player.setVolume(volume);
wakeword = false;
delay (100);
} else {
delay (100);
}
}
}
Файл класса для плеера
место его расположения - /modules/app_player/addons/esp32.addon.php
<?php
class esp32 extends app_player_addon {
function __construct($terminal) {
$this->title="Test esp32 terminal";
parent::__construct($terminal);
$this->terminal = $terminal;
$this->reset_properties();
$this->address = 'http://'.$this->terminal['PLAYER_USERNAME'].':'.$this->terminal['PLAYER_PASSWORD'] ."@" . $this->terminal['HOST'].':'.(empty($this->terminal['PLAYER_PORT'])?2710:$this->terminal['PLAYER_PORT']);
}
function play($input) {
$this->reset_properties();
if(strlen($input)) {
getUrl ($this->address . '/playfile/' . $input);
$this->success = TRUE;
$this->message = 'OK';
} else {
$this->success = FALSE;
$this->message = 'Input is missing!';
}
return $this->success;
}
// Set volume
function set_volume($level) {
$this->reset_properties();
if(strlen($level)) {
getUrl ($this->address . '/setvolume/' . $level);
$this->success = TRUE;
$this->message = 'OK';
} else {
$this->success = FALSE;
$this->message = 'Level is missing!';
}
return $this->success;
}
// Stop
function stop() {
if(getUrl ( $this->address . '/stopplay')) {
$this->reset_properties();
$this->success = TRUE;
$this->message = 'OK';
} else {
$this->success = FALSE;
$this->message = 'Cannot stop!';
}
return $this->success;
}
}