tarasfrompir

 
<<< Назад

Терминал на ЕСП 32

Привет опишу пока кратко как я это сделал. Если что будет дополнятся.
UPD. Добавлено ключевое слово и реакция на него
АПДЕЙТ 2 - добавлен в скетч логин и пароль для управления терминалом - просто если он смотрит в инет то кто угодно может им управлять

Что необходимо:

  1. ESP32-WROOM-32 или аналог наверное пойдут - хз не пробовал
  2. max98357a - усилитель Д класса вроде, с циферноаналоговым преобразователем
    3 INMP441 - микрофон
  3. Динамик 4 или 8 омный - зависит от усилителя.
  4. БП - 5 вольтовый
  5. Я взял корпус компьютерной колонки с динамиком, тогда П4 минус.

Соединяем 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 - в принипе разберетесь.
Терминал работает удаленно - если порты прокинуть в инет.
Сейчас структура такая у него .

Существует два процесса по ядрах

  1. это проигрывание аудио - тут ест нюансы - только мп3 и только определыннх стандартов
    Играет аудио, изменяет громкость, взвращает статус плеера,
  2. это прслушивание микрофона
    Микрофон слушается ВСЕГДА - и по вебсокетам отправляет аудио на сервер распознавания. Сервер распохнавания немного подправленный вариант VOSK-API (вариант сервера на вебсокетах)
    3 Добавлено ключевое слово - произносить его можно в любом месте фразы.
    Есди произнести само ключевое слово - то терминал прикрутит звук на минимум 10 секунд или пока вся фраза(команда) не будет произнесена
    Настройки терминала

Нюансов конечно еще много:

  • слушает сама себя всегда

Работает быстро по крайней мере как по мне - видео в курилке есть

Это сам скетч

#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;
    }
}

Обсуждение (16) (6)

Смотрите так же:
21.06.2021 Новый контроль циклов - или как таки разгрузить базу данных от ненужных запросов
18.11.2020 Функции работы с классами.. Добавленные 18.11.2020 года - https://github.com/sergejey/majordomo/pull/851
30.10.2020 Как получить информацию о местоположении и всем остальном
30.10.2020 Как получить внешни айпи адрес
17.12.2019 ТЕРМИНАЛЫ2 Как передать сообщение привязанному пользователю терминала
02.12.2019 Terminals 2 - настройка Телеграмма - как терминала (Обновлено)
19.03.2019 Как-бы да если-бы... Я бы передавал температуру на термостат...

Пирятин, Украина

На форуме: tarasfrompir

Веб-сайт:
http://netu_u_menya_sayta.world