1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/chging-esp32s3-ai-chat

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

Введение

Этот проект использует ESP32-S3 для реализации кастомного AI-ассистента для голосового общения (например, в роли врача). Проект позволяет ознакомиться с разработкой на ESP32-S3 с использованием Arduino, API для распознавания и синтеза речи от Baidu, API Baidu APPBuilder для создания кастомных ролей, обучением кастомных слов для пробуждения, чтением и записью на SD-карте, использованием сенсорного экрана, настройкой Wi-Fi (методом smartconfig). Все программное и аппаратное обеспечение проекта открыто, и к нему прилагаются подробные руководства и видео-уроки, что делает его особенно полезным для начинающих. Надеемся, что этот проект поможет вам.

Функции

  • Поддержка малых приложений для настройки Wi-Fi
  • Пробуждение ESP32-S3 голосом
  • Обучение кастомных слов для пробуждения
  • Доступ к API для распознавания и синтеза речи от Baidu
  • Кастомные роли агента
  • Независимое питание
  • Включение и выключение с помощью кнопки
  • Пробуждение сенсорного экрана 1.28 TFT

Открытые репозитории

  1. Ссылка на открытый репозиторий проекта:

https://gitee.com/chging/esp32s3-ai-chat

  1. Ссылка на видео-уроки:

【Пространство автора - Bilibili】 https://b23.tv/AsFNSeJ

  1. Версия документации в формате Word:

Я использую сервис Quark для размещения файла «ESP32-S3-AI-Chat-V2.docx». Нажмите ссылку для загрузки. Откройте приложение Quark для просмотра, поддерживает различные форматы документов.# Ссылки на покупку AI-интеграционного пакета

Ссылка на покупку: https://h5.m.taobao.com/awp/core/detail.htm?ft=t&id=833542085705

Программная среда

Установка Arduino

Скачивание

Установка

Дважды щелкните скачанный установочный файл и следуйте инструкциям на следующем рисунке:

Подождите немного, установка завершена.

Преобразование шрифта Arduino в китайский

Установка пакета для микросхемы ESP32

Для этого проекта необходимо установить пакет для микросхемы ESP32. Можно сначала попробовать установить его через онлайн-установку, если это не удастся, можно использовать офлайн-установку.

Онлайн-установка

  1. Откройте Arduino IDE, выберите Файл -> Параметры -> Установка.

  2. Вставьте следующую ссылку в поле адреса менеджера плат:

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json

Затем нажмите ОК, Сохранить.

  1. Откройте менеджер плат, и введите esp32, найдите esp32 by Espressif Systems. Выберите версию (здесь выбрана 2.0.17, эта версия была протестирована и не вызвала проблем, более новые версии могут вызвать проблемы), нажмите Установить для установки, подождите завершения загрузки и установки (если установка не удалась, можно попробовать снова нажать Установить).

  1. Установка завершена.

Офлайн-установка

Если установка не удалась из-за постоянных ошибок загрузки, можно использовать офлайн-установку.

  1. Прямая загрузка установочного пакета:Я разместил файл «esp32.rar» на Quark Cloud, нажав на ссылку можно сохранить файл. Ссылка:https://pan.quark.cn/s/61d4a28219bb

  2. Выберите путь для разархивации. Разместите его в соответствующую папку пакета устройств Arduino для пользователя. Ниже приведены пути установки для различных версий Arduino:C:\Users\Имя_пользователя\AppData\Local\Arduino15\packages

Внимание: AppData — скрытая папка, для просмотра которой необходимо настроить опции просмотра папок, чтобы увидеть скрытые папки. В данном случае имя пользователя — Administrator.

  1. После завершения разархивации закройте программу, перезапустите Arduino, перейдите в менеджер плат и убедитесь, что esp32-arduino установлен.

  1. Установка завершена.

Установка библиотек

Для данного проекта необходимо установить следующие онлайн-библиотеки и оффлайн-библиотеки.

Установка онлайн-библиотек

Arduino позволяет искать и устанавливать необходимые библиотеки непосредственно через менеджер библиотек.

Необходимые онлайн-библиотеки

Название библиотеки Версия
ArduinoJson 7.1.0
base64 1.3.0
UrlEncode 1.0.1
lvgl 8.5.10
TFT_eSPI 2.5.43
bb_captouch 1.2.2

Шаги установки

  1. Нажмите Менеджер библиотек -> Поиск по названию -> Выберите соответствующую версию и установите.

  1. Установка завершена, как показано на следующем рисунке. Если требуется удалить библиотеку, нажмите Удалить.

  2. Установите base64, используя ту же процедуру.

  1. Установите UrlEncode, используя ту же процедуру.

Установка библиотеки TFT_eSPI
Установка драйвера

Изменение кода для отображения

Измените User_Setup_Select.h. В папке установки библиотек Arduino.

  1. Откомментируйте заголовочный файл в начале.

  1. Активируйте заголовочный файл для вашего типа экрана.

  1. Настройте конфигурацию пинов, открыв файл Setup200_GC9A01.h для редактирования.

Установка библиотеки lvgl
Установка драйвера

Изменение кода библиотеки
  1. Скопируйте файл lv_conf_template.h из папки LVGL и переименуйте его в lv_conf.h.

  1. Разместите файл lv_conf.h в той же директории, что и папка lvgl.

  1. Откройте файл lv_conf.h и замените #if 0 на #if 1 в начале файла.

  2. Замените #define LV_TICK_CUSTOM 0 на #define LV_TICK_CUSTOM 1 на строке 88 (важно! Это активирует собственный таймер, иначе анимации не будут работать, а FPS будет равен 1).

  1. Активируйте тестовые примеры.

  1. Измените пути к файлам.

Переместите папку \lvgl\demos\ в \lvgl\src\demos.

  1. Импортируйте папку ui.

Я разместил файл test_ui.zip на Quake Cloud, перейдя по ссылке можно скачать.

Ссылка: https://pan.quark.cn/s/fd4657269252

Скачайте архив, распакуйте его и скопируйте в папку lvgl\src.

Установка библиотеки bb_captouch

### Установка оффлайн-библиотек Arduino может напрямую импортировать оффлайн-библиотеки для установки. В данном проекте требуется установить оффлайн-библиотеку для обучения активирующего слова. Ниже представлены библиотеки, которые я сам обучил. Если у вас нет обученной библиотеки, вы можете использовать мою активирующую библиотеку для импорта. Если вы хотите обучить свою собственную активирующую библиотеку, подробная инструкция находится в разделе 6.#### Необходимые для установки оффлайн-библиотеки

Название библиотеки
wakeup_detect_houguoxiong_inferencing

Шаги установки

  1. Нажмите Проект -> Импорт библиотеки -> Добавить ZIP-библиотеку, выберите локальный файл библиотеки Arduino.

  1. Выберите соответствующий файл библиотеки и нажмите "Открыть". Оффлайн-библиотека находится в папке esp32s3-ai-chat/library.

  1. Проверьте установленные библиотеки, библиотека должна быть успешно установлена.

Запрос ключа API Baidu и тестирование

Запрос ключа API

Перед использованием API Baidu, необходимо запросить ключ API на платформе Baidu Cloud. После получения ключа и активации соответствующего API, можно начать его использование.

Ссылка на платформу Baidu Cloud: https://cloud.baidu.com/

Распознавание речи Baidu

Сначала необходимо создать ключ API для распознавания речи.

  1. Нажмите Продукты -> Технологии речи -> Распознавание речи -> Распознавание коротких речевых сообщений стандартной версии.

  1. Нажмите Начать использование, откроется страница входа в аккаунт Baidu. Войдите или зарегистрируйтесь с помощью номера телефона.

  2. После регистрации необходимо пройти процедуру верификации личности, следуйте инструкциям ниже.

  1. Перейдите на страницу с информацией о голосовых технологиях. Получите бесплатные ресурсы, нажав на Получить бесплатно.

  1. Получите бесплатные ресурсы для ожидаемого интерфейса распознавания речи.

  1. Нажмите на Создать приложение.

  1. Введите название приложения, выберите все интерфейсы, укажите владельца приложения как личный, введите описание приложения, затем нажмите на Создать сейчас.

  1. После создания приложения нажмите на Вернуться к списку приложений.

  1. В списке приложений вы увидите созданное вами приложение, а также сможете получить доступ к API Key и Secret Key. Эти ключи вам потребуются для доступа к API распознавания речи.

  2. Далее вам потребуется активировать услугу распознавания речи. Нажмите на Обзор -> Распознавание речи -> Краткосрочное распознавание речи -> Активировать.

  1. Нажмите на Платить за каждый запрос -> Распознавание речи -> Краткосрочное распознавание речи на китайском -> Выбрать условия использования -> Подтвердить активацию.

  1. В результате API key для распознавания речи успешно получен, а также услуга активирована.В тексте отсутствуют строки для перевода. Пожалуйста, предоставьте текст для перевода.БaiduXuesinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьsinьПерейдите на главную страницу платформы Baidu Intelligent Cloud, нажмите на выбор Qianfan Large Model Application Development Platform AppBuilder.

  2. Нажмите на Начать использование.

  1. В данный момент вам потребуется войти в аккаунт Baidu, нажмите на вход. Затем вы попадете в Appbuilder.

  1. Создайте ключи, нажмите на Управление ключами -> Добавить ключ.

  1. Создайте приложение, нажмите на Личное пространство -> Приложения -> Создать приложение -> Автономное планирование Agent.

  1. Далее, следуя нижеследующему процессу, установите роль Agent, затем нажмите на Обновить и опубликовать.

  1. Вернитесь в личное пространство, вы увидите, что приложение успешно опубликовано, здесь вы можете получить ID приложения.

Онлайн-тестирование API (необязательный шаг)

После активации вышеуказанных API-сервисов, вы можете провести онлайн-тестирование, чтобы убедиться, что сервисы успешно активированы.

  1. Перейдите на главную страницу платформы Baidu Intelligent Cloud, нажмите на Консоль.

  1. Войдя в консоль, нажмите на Документация -> Примеры кода.

3. Перейдите на страницу тестирования API и примеры кода, на этой странице вы можете протестировать распознавание речи Baidu, синтез речи и модели Wenxin Yiyan.

Синтез речи

Здесь вы можете сначала протестировать вызов API синтеза речи, потому что этот вызов API можно напрямую заполнить текстом в качестве входных данных, а распознавание речи требует передачи аудиоданных в качестве входных данных. Поэтому сначала протестируйте синтез речи, а затем сохраните сгенерированный аудиофайл для последующего тестирования распознавания речи.

  1. Нажмите на Все продукты -> Технологии речи, перейдите на страницу тестирования API речи.

  1. Нажмите на Механизм аутентификации -> Получение AccessToken -> Перейти немедленно.

  1. Выберите список приложений, которые мы активировали, и нажмите ОК.

  1. Нажмите Отладка, в результатах отладки можно найти access_token в ответных данных. Это означает, что ключ API, который мы запросили, успешно получен.

5. Затем проверьте, успешно ли активирован API-сервис для синтеза речи. Нажмите Speech Synthesis -> Synthesize Short Text Online, затем введите текст для синтеза аудио, выберите голос, настройте скорость, тональность и громкость, выберите формат аудиофайла wav, и нажмите Synthesize.

  1. Сгенерированный аудиофайл можно воспроизвести, нажав кнопку воспроизведения, чтобы проверить правильность сгенерированного аудиофайла. (Здесь можно нажать три точки справа, чтобы сохранить этот аудиофайл для последующего использования в распознавании речи)

  1. Нажмите Результаты отладки, чтобы просмотреть запросы и ответные данные. Таким образом, мы можем проверить, успешно ли активирован API-сервис для синтеза речи.

  1. Нажмите Примеры кода, чтобы просмотреть реализацию вызова API на различных языках программирования. С помощью этого примера вы сможете вызывать API-сервисы Baidu на других платформах.

Распознавание речи

  1. Нажмите Распознавание речи -> Стандартное распознавание коротких аудиофайлов, затем нажмите Загрузить файл, загрузите аудиофайл, сгенерированный в предыдущем разделе. Остальные параметры можно оставить по умолчанию.

  1. Нажмите Отладка, если выполнение прошло успешно, просмотрите результаты отладки, чтобы проверить правильность распознавания речи в ответных данных. Таким образом, мы можем проверить, успешно ли активирован API-сервис для распознавания речи.

  2. Также нажмите Примеры кода, здесь представлены реализации вызова API на различных языках программирования.

Тестирование API с помощью Apipost (рекомендуемый инструмент)

Для тестирования API-сервисов существует универсальный инструмент, который широко используется и позволяет проверять, работают ли API-сервисы корректно.

  1. Скачивание: https://www.apipost.cn/

  1. Успешная установка после скачивания, вход в программу представлен на следующем рисунке.

  1. В этом разделе мы используем пример аутентификации API Baidu из предыдущего раздела для получения access_token. Формат параметров конфигурации в apipost можно найти в примерах кода предыдущего раздела.

Нажмите Пример кода -> Curl -> Копировать код.

  1. Нажмите Управление API -> Нажмите "+" -> Импортировать curl.

  1. Вставьте скопированный код, нажмите Импортировать немедленно.

  1. Нажмите Отправить.

  1. Из реального ответа просмотрите результат ответа. Таким образом, мы проверили, успешно ли работает API с помощью apipost.

Запуск основной программы AI Agent

Запуск основной программы AI Agent

Основная программа AI Agent находится в esp32s3-ai-chat\ai-all\esp32s3_ai_chat_all\esp32s3_ai_chat_all.ino, откроем проект, внесем изменения в настройки Wi-Fi и ключ API, скомпилируем код и загрузим его на плату для тестирования.

  1. Измените имя Wi-Fi, присвойте ssid и password текущего Wi-Fi, заполните ключ API, получите ключ API по инструкции из раздела 4.

  1. Включите psram.

  1. Выбор порта для разработки

Нажмите на поле выбора разработки, выберите разработку и порт, разработка ESP32S3 Dev Module, порт соответствует последнему отображаемому порту после подключения USB typeC (можно проверить через диспетчер устройств).

  1. Компиляция и загрузка.

  1. Тестирование голосового диалога.

Используйте "houguoxiong" для пробуждения ESP32-S3 для диалога. Или просто коснитесь экрана (непрерывное касание для записи, отпускание для завершения записи, система начинает процесс распознавания речи и обращения к модели), чтобы пробудить ESP32-S3 для диалога.

Общая схема работы программного обеспечения

Схема## Реализация основных модулей и анализ кода

Модуль голосового опроса по ключевому слову

Этот модуль реализует функцию пробуждения по ключевому слову, обученному самостоятельно. esp32s3-ai-chat/example/wake_detect этот проект в основном реализует функцию пробуждения по ключевому слову. На основе этого проекта можно разрабатывать AI голосовые чаты.

Основная реализация кода:

#include <wakeup_detect_houguoxiong_inferencing.h>

/* Edge Impulse Arduino examples
 * Copyright (c) 2022 EdgeImpulse Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

// Если ваша целевая система ограничена по памяти, удалите этот макрос, чтобы сэкономить OnClickListener 10КБ ОЗУ
#define EIDSP_QUANTIZE_FILTERBANK 0
```/*
 ** ЗАМЕЧАНИЕ: Если вы столкнулись с проблемой выделения памяти для TFLite.
 **
 ** Это может быть связано с динамическим фрагментированием памяти.
 ** Попробуйте определить "-DEI_CLASSIFIER_ALLOCATION_STATIC" в boards.local.txt (создайте, если он не существует) и скопируйте этот файл в
 ** `<ARDUINO_CORE_INSTALL_PATH>/arduino/hardware/<mbed_core>/<core_version>/`.
 **
 ** См. (https://support.arduino.cc/hc/en-us/articles/360012076960-Where-are-the-installed-cores-located-) 
 ** чтобы найти, где Arduino устанавливает ядра на вашем компьютере.
 **
 ** Если проблема сохраняется, то у вас недостаточно памяти для этой модели и приложения.
 */
```/* Включение библиотек ---------------------------------------------------------------- */
#include <driver/i2s.h>```c
#define Частота_выборки 16000U
#define LED_встроенного 21

// Конфигурация INMP441
#define I2S_IN_ПОРТ I2S_NUM_0
#define I2S_IN_BCLK 4
#define I2S_IN_LRC 5
#define I2S_IN_DIN 6

/** Буферы аудио, указатели и селекторы */
typedef struct {
  int16_t *буфер;
  uint8_t buf_ready;
  uint32_t buf_count;
  uint32_t n_samples;
} inference_t;

static inference_t inference;
static const uint32_t sample_buffer_size = 2048;
static signed short sampleBuffer[sample_buffer_size];
static bool debug_nn = false;  // Установите это значение в true, чтобы увидеть, например, характеристики, сгенерированные из исходного сигнала
static bool record_status = true;

/**
 * @brief      Функция настройки Arduino
 */
void setup() {
  // поместите ваш код настройки здесь, чтобы выполнить его один раз:
  Serial.begin(115200);
  // закомментируйте нижеприведенную строку, чтобы отменить ожидание подключения USB (необходимо для нативного USB)
  while (!Serial)
    ;
  Serial.println("Edge Impulse Inferencing Demo");

  pinMode(LED_встроенного, OUTPUT);     // Установите пин как выход
  digitalWrite(LED_встроенного, HIGH);  // Выключить

  // Инициализация I2S для входного аудио
  i2s_config_t i2s_config_in = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = Частота_выборки,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,  // Обратите внимание: INMP441 выдает 32-битные данные
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
    .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 8,
    .dma_buf_len = 1024,
  };
  i2s_pin_config_t pin_config_in = {
    .bck_io_num = I2S_IN_BCLK,
    .ws_io_num = I2S_IN_LRC,
    .data_out_num = -1,
    .data_in_num = I2S_IN_DIN
  };
  i2s_driver_install(I2S_IN_ПОРТ, &i2s_config_in, 0, NULL);
  i2s_set_pin(I2S_IN_ПОРТ, &pin_config_in);
}
```  // Обзор настроек инференса (из model_metadata.h)
  ei_printf("Настройки инференса:\n");
  ei_printf("\tИнтервал: ");
  ei_printf_float((float)EI_CLASSIFIER_INTERVAL_MS);
  ei_printf(" мс.\n");
  ei_printf("\tРазмер кадра: %d\n", EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE);
  ei_printf("\tДлина выборки: %d мс.\n", EI_CLASSIFIER_RAW_SAMPLE_COUNT / 16);
  ei_printf("\tКоличество классов: %d\n", sizeof(ei_classifier_inferencing_categories) / sizeof(ei_classifier_inferencing_categories[0]));```markdown
ei_printf("\nНачинается непрерывное инференсирование через 2 секунды...\n");
ei_sleep(2000);

if (microphone_inference_start(EI_CLASSIFIER_RAW_SAMPLE_COUNT) == false) {
  ei_printf("ERR: Не удалось выделить буфер аудио (размер %d), это может быть связано с длиной окна вашего модели\r\n", EI_CLASSIFIER_RAW_SAMPLE_COUNT);
  return;
}

ei_printf("Запись...\n");
}

/**
 * @brief      Основная функция Arduino. Запускает цикл инференсирования.
 */
void loop() {
  bool m = microphone_inference_record();
  if (!m) {
    ei_printf("ERR: Не удалось записать аудио...\n");
    return;
  }

  signal_t signal;
  signal.total_length = EI_CLASSIFIER_RAW_SAMPLE_COUNT;
  signal.get_data = &microphone_audio_signal_get_data;
  ei_impulse_result_t result = { 0 };

  EI_IMPULSE_ERROR r = run_classifier(&signal, &result, debug_nn);
  if (r != EI_IMPULSE_OK) {
    ei_printf("ERR: Не удалось запустить классификатор (%d)\n", r);
    return;
  }

  int pred_index = 0;    // Инициализация pred_index
  float pred_value = 0;  // Инициализация pred_value

  // вывод предсказаний
  ei_printf("Предсказания ");
  ei_printf("(DSP: %d мс., Классификация: %d мс., Аномалия: %d мс.)",
            result.timing.dsp, result.timing.classification, result.timing.anomaly);
  ei_printf(": \n");
  for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
    ei_printf("    %s: ", result.classification[ix].label);
    ei_printf_float(result.classification[ix].value);
    ei_printf("\n");```md
    if (result.classification[ix].value > pred_value) {
      pred_index = ix;
      pred_value = result.classification[ix].value;
    }
  }
  // Отображение результата инференсирования
  if (pred_index == 3) {
    digitalWrite(LED_BUILT_IN, LOW);  // Включение
  } else {
    digitalWrite(LED_BUILT_IN, HIGH);  // Выключение
  }


#if EI_CLASSIFIER_HAS_ANOMALY == 1
  ei_printf("    anomaly score: ");
  ei_printf_float(result.anomaly);
  ei_printf("\n");
#endif
}

static void audio_inference_callback(uint32_t n_bytes) {
  for (int i = 0; i < n_bytes >> 1; i++) {
    inference.buffer[inference.buf_count++] = sampleBuffer[i];

    if (inference.buf_count >= inference.n_samples) {
      inference.buf_count = 0;
      inference.buf_ready = 1;
    }
  }
}

static void capture_samples(void *arg) {

  const int32_t i2s_bytes_to_read = (uint32_t)arg;
  size_t bytes_read = i2s_bytes_to_read;
}
```  while (record_status) {

    /* Чтение данных за один раз из i2s - Модифицировано для XIAO ESP2S3 Sense и библиотеки I2S.h */
    i2s_read(I2S_IN_PORT, (void*)sampleBuffer, i2s_bytes_to_read, &bytes_read, 100);
    // esp_i2s::i2s_read(esp_i2s::I2S_NUM_0, (void *)sampleBuffer, i2s_bytes_to_read, &bytes_read, 100);

    if (bytes_read <= 0) {
      ei_printf("Ошибка при чтении I2S: %d", bytes_read);
    } else {
      if (bytes_read < i2s_bytes_to_read) {
        ei_printf("Частичное чтение I2S");
      }

      // масштабирование данных (иначе звук будет слишком тихим)
      for (int x = 0; x < i2s_bytes_to_read / 2; x++) {
        sampleBuffer[x] = (int16_t)(sampleBuffer[x]) * 8;
      }

      if (record_status) {
        audio_inference_callback(i2s_bytes_to_read);
      } else {
        break;
      }
    }
  }
  vTaskDelete(NULL);
}

/**
 * @brief Инициализация структуры инференса и настройка/запуск PDM
 *
 * @param[in] n_samples количество образцов
 *
 * @return true если инициализация успешна, иначе false
 */
static bool microphone_inference_start(uint32_t n_samples) {
  inference.buffer = (int16_t *)malloc(n_samples * sizeof(int16_t));
}
``````markdown
```cpp
if (inference.buffer == NULL) {
    return false;
}

inference.buf_count = 0;
inference.n_samples = n_samples;
inference.buf_ready = 0;

//    if (i2s_init(EI_CLASSIFIER_FREQUENCY)) {
//        ei_printf("Не удалось запустить I2S!");
//    }

ei_sleep(100);

record_status = true;

xTaskCreate(capture_samples, "CaptureSamples", 1024 * 32, (void *)sample_buffer_size, 10, NULL);

return true;
}
/**
 * @brief Ожидание новых данных
 *
 * @return true если завершено, иначе false
 */
static bool microphone_inference_record(void) {
    bool ret = true;

    while (inference.buf_ready == 0) {
        delay(10);
    }

    inference.buf_ready = 0;
    return ret;
}
/**
 * Получение данных аудиосигнала
 */
static int microphone_audio_signal_get_data(size_t offset, size_t length, float *out_ptr) {
    numpy::int16_to_float(&inference.buffer[offset], out_ptr, length);

    return 0;
}
/**
 * @brief Остановка PDM и освобождение буферов
 */
static void microphone_inference_end(void) {
    free(sampleBuffer);
    ei_free(inference.buffer);
}
#if !defined(EI_CLASSIFIER_SENSOR) || EI_CLASSIFIER_SENSOR != EI_CLASSIFIER_SENSOR_MICROPHONE
#error "Недопустимая модель для текущего датчика."
#endif
// Инициализация I2S для аудиовхода
  i2s_config_t i2s_config_in = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = SAMPLE_RATE,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,  // Обратите внимание: INMP441 выдает 32-битные данные
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
    .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 8,
    .dma_buf_len = 1024,
  };
  i2s_pin_config_t pin_config_in = {
    .bck_io_num = I2S_IN_BCLK,
    .ws_io_num = I2S_IN_LRC,
    .data_out_num = -1,
    .data_in_num = I2S_IN_DIN
  };
  i2s_driver_install(I2S_IN_PORT, &i2s_config_in, 0, NULL);
  i2s_set_pin(I2S_IN_PORT, &pin_config_in);
```3. Инициализация интерфейса для обнаружения слов-вызывателей.

```cpp
// Обзор настроек для инференса (из model_metadata.h)
  ei_printf("Настройки для инференса:\n");
  ei_printf("\tИнтервал: ");
  ei_printf_float((float)EI_CLASSIFIER_INTERVAL_MS);
  ei_printf(" мс.\n");
  ei_printf("\tРазмер кадра: %d\n", EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE);
  ei_printf("\tДлина образца: %d мс.\n", EI_CLASSIFIER_RAW_SAMPLE_COUNT / 16);
  ei_printf("\tКоличество классов: %d\n", sizeof(ei_classifier_inferencing_categories) / sizeof(ei_classifier_inferencing_categories[0]));

  ei_printf("\nЗапуск непрерывного инференса через 2 секунды...\n");
  ei_sleep(2000);

  if (microphone_inference_start(EI_CLASSIFIER_RAW_SAMPLE_COUNT) == false) {
    ei_printf("ОШИБКА: Не удалось выделить буфер для аудио (размер %d), это может быть связано с длиной окна модели\n", EI_CLASSIFIER_RAW_SAMPLE_COUNT);
    return;
  }
```

4. Эта функция инициализации создает задачу FreeRTOS, которая предназначена для реального времени сбора аудиоданных.

```cpp
static bool microphone_inference_start(uint32_t n_samples) {
  inference.buffer = (int16_t *)malloc(n_samples * sizeof(int16_t));

  if (inference.buffer == NULL) {
    return false;
  }

  inference.buf_count = 0;
  inference.n_samples = n_samples;
  inference.buf_ready = 0;

  ei_sleep(100);

  record_status = true;

  xTaskCreate(capture_samples, "CaptureSamples", 1024 * 32, (void *)sample_buffer_size, 10, NULL);

  return true;
}
```

5. Задача по реальному времени для сбора аудиоданных, которая сохраняет собранные данные в глобальную переменную данных sampleBuffer.

```cpp
static void capture_samples(void *arg) {

  const int32_t i2s_bytes_to_read = (uint32_t)arg;
  size_t bytes_read = i2s_bytes_to_read;

  while (record_status) {
```    /* Чтение данных из i2s за один раз - Модифицировано для XIAO ESP2S3 Sense и библиотеки I2S.h */
    i2s_read(I2S_IN_PORT, (void*)sampleBuffer, i2s_bytes_to_read, &bytes_read, 100);
    // esp_i2s::i2s_read(esp_i2s::I2S_NUM_0, (void *)sampleBuffer, i2s_bytes_to_read, &bytes_read, 100);```markdown
    if (bytes_read <= 0) {
      ei_printf("Ошибка при чтении I2S : %d", bytes_read);
    } else {
      if (bytes_read < i2s_bytes_to_read) {
        ei_printf("Частичное чтение I2S");
      }

      // масштабирование данных (иначе звук будет слишком тихим)
      for (int x = 0; x < i2s_bytes_to_read / 2; x++) {
        sampleBuffer[x] = (int16_t)(sampleBuffer[x]) * 8;
      }

      if (record_status) {
        audio_inference_callback(i2s_bytes_to_read);
      } else {
        break;
      }
    }
  }
  vTaskDelete(NULL);
}
```

6. Копирование данных из переменной `sampleBuffer` в структуру данных `inference`, которая используется как входные параметры для функции классификации. Таким образом, код для подготовки аудиоданных завершен.

```cpp
static void audio_inference_callback(uint32_t n_bytes) {
  for (int i = 0; i < n_bytes >> 1; i++) {
    inference.buffer[inference.buf_count++] = sampleBuffer[i];

    if (inference.buf_count >= inference.n_samples) {
      inference.buf_count = 0;
      inference.buf_ready = 1;
    }
  }
}
```

7. Далее рассмотрим конкретную классификацию.

```cpp
void loop() {
  bool m = microphone_inference_record();
  if (!m) {
    ei_printf("Ошибка: Не удалось записать аудио...\n");
    return;
  }

  signal_t signal;
  signal.total_length = EI_CLASSIFIER_RAW_SAMPLE_COUNT;
  signal.get_data = &microphone_audio_signal_get_data;
  ei_impulse_result_t result = { 0 };

  EI_IMPULSE_ERROR r = run_classifier(&signal, &result, debug_nn);
  if (r != EI_IMPULSE_OK) {
    ei_printf("Ошибка: Не удалось запустить классификатор (%d)\n", r);
    return;
  }
}
```  
```cpp
  int pred_index = 0;    // Инициализация pred_index
  float pred_value = 0;  // Инициализация pred_value
```  # вывод предсказаний
  ei_printf("Предсказания ");
  ei_printf("(DSP: %d мс., Классификация: %d мс., Аномалия: %d мс.)",
            result.timing.dsp, result.timing.classification, result.timing.anomaly);
  ei_printf(": \n");
  for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
    ei_printf("    %s: ", result.classification[ix].label);
    ei_printf_float(result.classification[ix].value);
    ei_printf("\n");}    if (result.classification[ix].value > pred_value) {
      pred_index = ix;
      pred_value = result.classification[ix].value;
    }
  }
  # Отображение результата инференса
  if (pred_index == 3) {
    digitalWrite(LED_BUILT_IN, LOW);  # Включение
  } else {
    digitalWrite(LED_BUILT_IN, HIGH);  # Выключение
  }


#if EI_CLASSIFIER_HAS_ANOMALY == 1
  ei_printf("    anomaly score: ");
  ei_printf_float(result.anomaly);
  ei_printf("\n");
#endif
}
```

8. В основной цикл `loop` входит код, который выполняет классификацию полученных аудиоданных. Функция `microphone_audio_signal_get_data` получает ранее сохраненные аудиоданные, после чего вызывается функция `run_classifier(&signal, &result, debug_nn)`, которая вычисляет предсказания классификации. В процессе обучения модели, она обучается на данных с несколькими метками, и в результате `result` возвращает предсказания для каждого из этих меток.
9. Значение `result.classification[ix].value` предсказания, которое ближе к 1.0, соответствует текущему распознанному метке. Когда произносится обученная фраза-вызов, соответствующее значение предсказания также приближается к 1.0, что позволяет активировать систему.
10. Можно установить пороговое значение для сравнения с `result.classification[ix].value`, чтобы определить, был ли успешным вызов. Управление этим пороговым значением позволяет контролировать чувствительность распознавания. Таким образом, весь процесс активации завершен.### Получение access_token для доступа к API Baidu
При обращении к API для распознавания речи, синтеза речи и модели ERNIE от Baidu требуется предоставление access_token. В ESP32-S3 это достигается путем создания HTTP-запроса, построения запроса в соответствии с форматом API для получения access_token, отправки запроса через HTTP и получения ответа, из которого извлекается access_token.```cpp
// Получение access_token для API Baidu
String getAccessToken(const char* api_key, const char* secret_key) {
  String access_token = "";
  HTTPClient http;
``````markdown
## Создание HTTP-запроса
```http.begin("https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=" + String(api_key) + "&client_secret=" + String(secret_key));
int httpCode = http.POST("");

if (httpCode == HTTP_CODE_OK) {
    String response = http.getString();
    DynamicJsonDocument doc(1024);
    deserializeJson(doc, response);
    access_token = doc["access_token"].as<String>();

    Serial.printf("[HTTP] GET access_token: %s\n", access_token);
} else {
    Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();

return access_token;
```

В этом коде создается HTTP-запрос для получения `access_token` с использованием ключей `api_key` и `secret_key`. Если запрос успешен, `access_token` извлекается из ответа и выводится в консоль. В случае неудачи выводится сообщение об ошибке.

## Обращение к API распознавания речи Baidu
После сбора аудиоданных с помощью ESP32-S3 и микрофона INMP441, необходимо отправить эти данные на сервер для их распознавания в текстовом формате. Для этого используется API распознавания речи от Baidu.

Основной код реализации представлен ниже:

```cpp
String baiduSTT_Send(String access_token, uint8_t* audioData, int audioDataSize) {
    String recognizedText = "";

    if (access_token == "") {
        Serial.println("access_token is null");
        return recognizedText;
    }

    // Аудиоданные необходимо закодировать в Base64, что увеличит объем данных на 1/3
    int audio_data_len = audioDataSize * sizeof(char) * 1.4;
    unsigned char* audioDataBase64 = (unsigned char*)ps_malloc(audio_data_len);
    if (!audioDataBase64) {
        Serial.println("Не удалось выделить память для audioDataBase64");
        return recognizedText;
    }
``````markdown
  // Размер JSON-пакета, поскольку аудиоданные закодированы в Base64, объем данных увеличивается на 1/3
  int data_json_len = audioDataSize * sizeof(char) * 1.4;
  char* data_json = (char*)ps_malloc(data_json_len);
  if (!data_json) {
    Serial.println("Не удалось выделить память для data_json");
    return recognizedText;
  }

  // Кодирование аудиоданных в Base64
  encode_base64(audioData, audioDataSize, audioDataBase64);

  memset(data_json, '\0', data_json_len);
  strcat(data_json, "{");
  strcat(data_json, "\"format\":\"pcm\",");
  strcat(data_json, "\"rate\":16000,");
  strcat(data_json, "\"dev_pid\":1537,");
  strcat(data_json, "\"channel\":1,");
  strcat(data_json, "\"cuid\":\"57722200\",");
  strcat(data_json, "\"token\":\"");
  strcat(data_json, access_token.c_str());
  strcat(data_json, "\",");
  sprintf(data_json + strlen(data_json), "\"len\":%d,", audioDataSize);
  strcat(data_json, "\"speech\":\"");
  strcat(data_json, (const char*)audioDataBase64);
  strcat(data_json, "\"");
  strcat(data_json, "}");

  // Создаем HTTP-запрос
  HTTPClient http_client;

  http_client.begin("http://vop.baidu.com/server_api");
  http_client.addHeader("Content-Type", "application/json");
  int httpCode = http_client.POST(data_json);

  if (httpCode > 0) {
    if (httpCode == HTTP_CODE_OK) {
      // Получаем ответ
      String response = http_client.getString();
      Serial.println(response);

      // Извлекаем соответствующий result из JSON
      DynamicJsonDocument responseDoc(2048);
      deserializeJson(responseDoc, response);
      recognizedText = responseDoc["result"].as<String>();
    }
  } else {
    Serial.printf("[HTTP] POST failed, error: %s\n", http_client.errorToString(httpCode).c_str());
  }

  // Освобождаем память
  if (audioDataBase64) {
    free(audioDataBase64);
  }

  if (data_json) {
    free(data_json);
  }

  http_client.end();

  return recognizedText;
}
```

Вот анализ ключевых моментов кода:
```markdown
- **Расчет размера JSON-пакета**: Размер JSON-пакета рассчитывается с учетом увеличения объема данных на 1/3 из-за кодирования в Base64.
- **Выделение памяти**: Память выделяется для JSON-пакета с использованием функции `ps_malloc`. Если память не может быть выделена, выводится сообщение об ошибке, и возвращается `recognizedText`.
- **Кодирование аудиоданных**: Аудиоданные кодируются в Base64 с помощью функции `encode_base64`.
- **Формирование JSON-пакета**: JSON-пакет формируется с использованием функций `strcat` и `sprintf`. В него включаются параметры формата, скорости, устройства, уникального идентификатора, токена доступа, размера данных и закодированных аудиоданных.
- **Создание HTTP-запроса**: HTTP-запрос создается с использованием библиотеки `HTTPClient`. Запрос отправляется на сервер `http://vop.baidu.com/server_api` с заголовком `Content-Type: application/json`.
- **Обработка ответа**: Если запрос успешно выполнен (код ответа `HTTP_CODE_OK`), ответ извлекается из JSON-документа, и результат сохраняется в переменной `recognizedText`.
- **Обработка ошибок**: Если запрос не выполнен успешно, выводится сообщение об ошибке.
- **Освобождение памяти**: После выполнения запроса, память, выделенная для JSON-пакета и закодированных аудиоданных, освобождается с помощью функции `free`.
```1. В этом месте создается буфер для JSON-пакета, который должен быть примерно в 1,4 раза больше исходных данных, так как данные будут закодированы в Base64. В данном случае выделяется достаточно большое количество памяти, поэтому она должна быть выделена из PSRAM.```cpp
  // Аудио-данные будут закодированы в Base64, что увеличит объем данных примерно на 1/3
  int audio_data_len = audioDataSize * sizeof(char) * 1.4;
  unsigned char* audioDataBase64 = (unsigned char*)ps_malloc(audio_data_len);
  if (!audioDataBase64) {
    Serial.println("Не удалось выделить память для audioDataBase64");
    return recognizedText;
  }

  // Размер JSON-пакета, учитывая, что аудио-данные будут закодированы в Base64, что увеличит объем данных примерно на 1/3
  int data_json_len = audioDataSize * sizeof(char) * 1.4;
  char* data_json = (char*)ps_malloc(data_json_len);
  if (!data_json) {
    Serial.println("Не удалось выделить память для data_json");
    return recognizedText;
  }
```

2. В этом месте данные аудио-данных упаковываются в формат, соответствующий документации API. Важно отметить, что `len` представляет собой размер исходных данных, а не размер данных после кодирования в Base64.

```cpp
  // Кодируем аудио-данные в Base64
  encode_base64(audioData, audioDataSize, audioDataBase64);
```

```cpp
  memset(data_json, '\0', data_json_len);
  strcat(data_json, "{");
  strcat(data_json, "\"format\":\"pcm\",");
  strcat(data_json, "\"rate\":16000,");
  strcat(data_json, "\"dev_pid\":1537,");
  strcat(data_json, "\"channel\":1,");
  strcat(data_json, "\"cuid\":\"57722200\",");
  strcat(data_json, "\"token\":\"");
  strcat(data_json, access_token.c_str());
  strcat(data_json, "\",");
  sprintf(data_json + strlen(data_json), "\"len\":%d,", audioDataSize);
  strcat(data_json, "\"speech\":\"");
  strcat(data_json, (const char*)audioDataBase64);
  strcat(data_json, "\"");
  strcat(data_json, "}");
```

3. Здесь, JSON-документ с ответными данными должен быть достаточно большим, чтобы вместить размер возвращаемых данных.

```cpp
// Парсинг соответствующего результата из JSON
DynamicJsonDocument responseDoc(2048);
deserializeJson(responseDoc, response);
recognizedText = responseDoc["result"].as<String>();
```### Определение роли API для агента Бaidu
Результат распознавания голоса возвращается в формате текста, который затем можно использовать как входные данные для API агента большого моделирования Бaidu. Реализация вызова API большого моделирования представлена ниже:

```cpp
// Получение идентификатора разговора API Baidu
String getConversation_id(const char* api_key, const char* app_id) {
  String conversation_id = "";

  // Создание HTTP-запроса
  HTTPClient http;
  http.begin("https://qianfan.baidubce.com/v2/app/conversation");
  http.addHeader("Content-Type", "application/json");
  http.addHeader("X-Appbuilder-Authorization", "Bearer " + String(api_key));

  // Создание JSON-документа
  DynamicJsonDocument requestJson(1024);
  requestJson["app_id"] = app_id;

  // Сериализация JSON-данных в строку
  String requestBody;
  serializeJson(requestJson, requestBody);

  // Отправка HTTP-запроса
  int httpCode = http.POST(requestBody);
  if (httpCode == HTTP_CODE_OK) {
    String response = http.getString();
    DynamicJsonDocument doc( Yöntem: JSON, емкость: 1024 );
    deserializeJson(doc, response);
    conversation_id = doc["conversation_id"].as<String>();

    ei_printf("[HTTP] GET conversation_id: %s\n", conversation_id.c_str());
  } else {
    ei_printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
  }
  http.end();

  return conversation_id;
}
```

### Вызов API синтеза речи Бaidu
Для воспроизведения текстовых данных, полученных от API Baidu, необходимо преобразовать их в аудио-данные. Это достигается путем вызова API синтеза речи Baidu. Реализация представлена ниже:

```cpp
void baiduTTS_Send(String access_token, String text) {
  if (access_token == "") {
    Serial.println("access_token is null");
    return;
  }

  if (text.length() == 0) {
    Serial.println("text is null");
    return;
  }
```  const int per = 1;
  const int spd = 5;
  const int pit = 5;
  const int vol = 10;
  const int aue = 6;

  // URL-кодирование текста
  String encodedText = urlEncode(urlEncode(text));

  // URL HTTP-запрос данных
  String url = "https://tsn.baidu.com/text2audio";

  const char* header[] = { "Content-Type", "Content-Length" };

  url += "?tok=" + access_token;
  url += "&tex=" + encodedText;
  url += "&per=" + String(per);
  url += "&spd=" + String(spd);
  url += "&pit=" + String(pit);
  url += "&vol=" + String(vol);
  url += "&aue=" + String(aue);
  url += "&cuid=esp32s3";
  url += "&lan=zh";
  url += "&ctp=1";

  // Создание HTTP-запроса
  HTTPClient http;

  http.begin(url);
  http.collectHeaders(header, 2);

  // Выполнение HTTP-запроса
  int httpResponseCode = http.GET();
  if (httpResponseCode > 0) {
    if (httpResponseCode == HTTP_CODE_OK) {
      String contentType = http.header("Content-Type");
      Serial.println(contentType);
      if (contentType.startsWith("audio")) {
        Serial.println("Синтезирование успешно");

        // Получение возвращенного аудиоданных
        Stream* stream = http.getStreamPtr();
        uint8_t buffer[512];
        size_t bytesRead = 0;

        // Установка timeout в 200ms для предотвращения появления шума в конце
        stream->setTimeout(200);

        while (http.connected() && (bytesRead = stream->readBytes(buffer, sizeof(buffer))) > 0) {
          // Воспроизведение аудио
          playAudio(buffer, bytesRead);
          delay(1);
        }

        // Очистка буфера I2S DMA
        clearAudio();
      } else if (contentType.equals("application/json")) {
        Serial.println("Ошибка синтезирования");
      } else {
        Serial.println("Неизвестный Content-Type");
      }
    } else {
      Serial.println("Не удалось получить аудиофайл");
    }
  } else {
    Serial.print("Код ошибки: ");
    Serial.println(httpResponseCode);
  }
  http.end();
}// Воспроизведение аудио данных с помощью MAX98357A
void playAudio(uint8_t* audioData, size_t audioDataSize) {
  if (audioDataSize > 0) {
    // Передача данных
    size_t bytes_written = 0;
    i2s_write(I2S_OUT_PORT, (int16_t*)audioData, audioDataSize, &bytes_written, portMAX_DELAY);
  }
}```cpp
void clearAudio(void) {
  // Очистка буфера I2S DMA
  i2s_zero_dma_buffer(I2S_OUT_PORT);
  Serial.print("Очистка буфера");
}

// Упаковка URL для HTTP-запроса
String url = "https://tsn.baidu.com/text2audio";

const char* header[] = { "Content-Type", "Content-Length" };

url += "?tok=" + access_token;
url += "&tex=" + encodedText;
url += "&per=" + String(per);
url += "&spd=" + String(spd);
url += "&pit=" + String(pit);
url += "&vol=" + String(vol);
url += "&aue=" + String(aue);
url += "&cuid=esp32s3";
url += "&lan=zh";
url += "&ctp=1";

// Создание HTTP-запроса
HTTPClient http;

http.begin(url);
http.collectHeaders(header, 2);

// Установка времени ожидания в 200 миллисекунд, чтобы избежать появления шумов
stream->setTimeout(200);

// Здесь происходит получение HTTP-аудиопотока. В цикле while необходимо добавить задержку, чтобы не заблокировать систему. Если этого не сделать, другие задачи, такие как запись аудио или активация, не смогут выполняться, что приведет к невозможности активации во время воспроизведения аудио. Поэтому здесь добавляется обработка для освобождения процессора.
while (http.connected() && (bytesRead = stream->readBytes(buffer, sizeof(buffer))) > 0) {
  // Воспроизведение аудио
  playAudio(buffer, bytesRead);
  delay(1);
}
```5. Это очистка буфера DMA I2S, чтобы избавиться от шумов.

```cpp
void clearAudio(void) {
  // Очистка буфера DMA I2S
  i2s_zero_dma_buffer(I2S_OUT_PORT);
  Serial.print("clearAudio");
}
```

# Обучение собственного слова активации (продвинутое)
## Запись аудио
### Подготовка оборудования
Необходимо подготовить следующее оборудование:

+ Комплект AI
+ microSD-карта (не более 32 ГБ)
+ microSD-читатель карт

### Форматирование microSD-карты
1. Вставьте microSD-карту в читатель карт и подключите его к компьютеру. Форматируйте microSD-карту **<font style="color:#DF2A3F;">в формат FAT32</font>**. Пример:

![](./README.assets/1725375587961-5a1740ef-0077-4f66-b2a4-baa0013a459a.png)

2. После форматирования вставьте microSD-карту в слот комплекта AI.

### Начало записи аудио
Мы записываем аудио данные, загружая программу записи аудио на ESP32-S3. Записанные аудио данные сохраняются на microSD-карте, а затем их можно прочитать с помощью компьютера.

#### Запись аудио-регистрационного ПО на ESP32-S3
Проект аудио-регистрационного ПО расположен в **<font style="color:#DF2A3F;">esp32s3-ai-chat/example/capture_audio_data</font>**. Откройте проект и перед компиляцией включите **<font style="color:#DF2A3F;">поддержку PSRAM</font>**, как показано на следующем рисунке. После настройки скомпилируйте и загрузите проект на ESP32-S3.

![](./README.assets/1725261301164-902194fc-6987-4320-8365-07021dea6662-1745300256203-178.png)

#### Метка отправки по последовательному порту для записи аудио
1. После запуска программы, нормальный лог последовательного порта выглядит следующим образом.![](./README.assets/1725376824589-674251b5-f8f8-477f-9a9c-885dcb657462.png)

2. После успешного запуска программы, можно открыть утилиту для работы с последовательным портом и отправить соответствующие команды управления для записи аудио. Отправьте метку "**hgx**".

![](./README.assets/1725376992198-7c31f150-a2b8-4879-8775-4ea75e81f83e.png)

3. Отправьте команду записи "**rec**", чтобы начать запись.

![](./README.assets/1725377031300-0519dfa8-687d-4ed9-bfca-1a2305397fbd.png)

4. После отправки метки (например, "**hgx**"), программа будет ждать команду "**rec**". При каждом отправлении команды "**rec**", программа начнет запись нового образца (запись продолжается в течение 10 секунд и автоматически завершается), файлы будут сохранены как hgx.1.wav, hgx.2.wav, hgx.3.wav и т.д.

5. Программа начнет запись нового образца метки, когда будет отправлена новая метка (например, "**noise**"). При каждом отправлении команды "**rec**" для нового образца метки, программа начнет запись и сохранит её как noise.1.wav, noise.2.wav, noise.3.wav и т.д.

6. В конечном итоге, все записанные образцы меток будут сохранены на SD-карте, и их можно будет прочитать через картридер на компьютере. Пример:

![](./README.assets/1725377793023-2b3b2011-c7d1-4940-af4a-9f42c7f303f2.png)#### Методы записи аудио для различных меток
Необходимо записать как минимум **<font style="color:#DF2A3F;">3 метки</font>** образцов данных: образцы данных для меток пробуждения, шума и неизвестных меток. Каждый образец данных для меток должен быть записан как минимум в **<font style="color:#DF2A3F;">10 группах</font>**. Чем больше данных, тем лучше будет распознавание модели после обучения.1. Образцы данных для меток пробуждения: в течение 10 секунд повторно произносите слово "пробуждение" в микрофон, меняя скорость и тон голоса. Чем больше различных образцов, тем лучше будет обобщенное распознавание.
2. Образцы данных для меток шума: просто записывайте окружающий шум, не произнося слова в микрофон.
3. Образцы данных для меток неизвестных: в течение 10 секунд произносите в микрофон любые слова, кроме слов "пробуждение".

## Обучение записанных аудиоданных
### Создание проекта на Edge Impulse
Войдите на Edge Impulse, [https://edgeimpulse.com/](https://edgeimpulse.com/), зарегистрируйтесь и создайте проект.

![](./README.assets/1725378238771-770fe630-0cb1-4a27-8f11-aeb2dacf3ee1.png)

### Загрузка записанных аудиоданных
После создания проекта выберите инструмент "Загрузить существующие данные" в разделе Data Acquisition. Выберите файлы для загрузки.

![](./README.assets/1725264313904-399a59da-d5ee-4dcb-99e7-2fc7cfa1a030.png)

После завершения загрузки данных система автоматически разделит их на обучающую (Training) и тестовую (Test) выборки (в соотношении 80% и 20%).

### Разделение данных
Для обучения системы требуется, чтобы все данные имели длину 1 секунду. Однако, в предыдущем разделе были загружены записи длиной 10 секунд, которые необходимо разбить на отдельные 1-секундные фрагменты. Поэтому необходимо выполнить операцию разделения каждого образца данных.1. Нажмите на три точки рядом с названием образца и выберите **Разделить образец**.

![](./README.assets/1725378587970-61f068ea-5be3-4b61-a01c-ce21643c2f74.png)

2. После входа в этот инструмент данные будут разделены на 1-секундные фрагменты. Каждый прямоугольный блок представляет собой один из разделенных фрагментов. Если необходимо, можно переместить прямоугольник, чтобы он полностью покрывал зону с голосом, или добавить или удалить фрагменты.

![](./README.assets/1725378647262-b29f0b48-c437-49fd-8d82-4dcc82ab9655.png)

3. Обрезанные аудиоданные выглядят следующим образом:

![](./README.assets/1725378700080-fd862eb9-895d-4e36-87b6-c0ac2b7ed0f2.png)

4. Все образцы данных должны быть разделены до тех пор, пока все образцы данных (обучающие, тестовые) не будут иметь длину 1 секунду.

### Создание импульсного сигнала (предварительная обработка/определение модели)
Создайте импульсный сигнал, выполните предварительную обработку данных и выберите модель, как показано на следующем рисунке:

![](./README.assets/1725379246960-81b9d5f2-863e-48f2-97ad-d1fc6d607a0c.png)

Шаги выполнения:

1. Нажмите на **Create impulse** слева, затем нажмите **Add a processing block** и добавьте **Audio(MFCC)**. Используйте MFCC, который извлекает характеристики из аудиосигнала с помощью мел-частотных обертона, что очень полезно для человеческого голоса.

![](./README.assets/1725380341093-3cab5bd7-f1b7-44fd-ab41-f64c79054474.png)

2. Затем нажмите **Add a learning block** и добавьте **Classification** модуль. Этот модуль строит нашу модель с нуля, используя сверточную нейронную сеть для классификации изображений.![](./README.assets/1725380395771-1ea055c4-149e-4e67-af4c-e711a1775bb2.png)

3. Наконец, нажмите **Save impulse**, чтобы сохранить конфигурацию.![](./README.assets/1725380439467-c78952c6-4f9a-4e9d-812e-b353e4b5ae7a.png)

### Предварительная обработка (MFCC)
Следующим шагом является создание изображений для обучения на следующем этапе.

1. Нажмите на **MFCC**, вы можете оставить параметры по умолчанию и нажать на **Save parameters**.

![](./README.assets/1725380530318-310228ac-59f9-4955-802c-dbd9b5474b35.png)

2. Нажмите на **Generate features**, чтобы сгенерировать характеристики для трех меток данных.

![](./README.assets/1725380692504-6cf92efe-a396-474e-9c41-b6f1fa321879.png)

### Проектирование и обучение модели (Classifier)
Далее нам нужно спроектировать структуру модели и начать обучение, следуя следующим шагам:

1. Нажмите на **Classifier** слева, структура модели уже настроена, затем нажмите на **Save & Train**, чтобы начать обучение модели.

![](./README.assets/1725380907830-5a5bd3c7-dc35-4b76-ab47-581941300f73.png)

2. После завершения обучения, вы увидите результаты классификации, как показано на рисунке выше.

![](./README.assets/1725381184336-332ba985-1ccb-4ad1-88cc-9cee26f994fd.png)

### Тестирование модели
После обучения модели, вы можете протестировать её точность на тестовом наборе данных.

1. Нажмите на **Model testing** слева, затем нажмите на **Classify all**, чтобы начать классификацию всех тестовых данных.

![](./README.assets/1725381249767-e76d9bca-0c97-437f-a43b-b108921579cb.png)2. Подождите немного, результаты классификации всех тестовых данных будут вычислены, как показано на рисунке выше. На основе этих результатов можно сделать вывод о том, удовлетворяет ли точность распознавания модели требованиям.![](./README.assets/1725381469318-c62a28cf-81dd-4f83-8e3e-570d03788d7c.png)

## Развертывание модели на ESP32-S3
### Генерация библиотеки модели
После обучения модели нам нужно сгенерировать библиотеку для работы на платформе Arduino ESP32.

1. Перед генерацией библиотеки, настройте вашу платформу, нажав на **Target** справа, выберите **Target device** как **ESP-EYE**, затем нажмите на **Save**.

![](./README.assets/1725381865178-5c9b29ce-1950-43c0-9d7f-122898a50967.png)

2. Нажмите на **Deployment** слева, настройте параметры, как показано на рисунке выше, затем нажмите на **Build**, чтобы начать генерацию библиотеки.

![](./README.assets/1725381714732-b6d6fd72-414b-4ecc-8819-39329825c50a.png)

3. После завершения генерации, сохраните библиотеку .zip в вашем каталоге проекта.

![](./README.assets/1725382119121-0544772f-b924-4fc6-95b0-ed9d01fe4fa8.png)

### Тестирование модели активации на ESP32-S3
После обучения библиотеки активации, вам нужно протестировать ее на ESP32-S3. Откройте каталог проекта и **<font style="color:#DF2A3F;">esp32s3-ai-chat/example/wake_detect</font>** проект для тестирования функции пробуждения.

1. Выберите проект, импортируйте библиотеку, добавьте ZIP-файл библиотеки, выберите обученную нами библиотеку пробуждения.

![](./README.assets/1725382485062-1abd4e29-8a4f-425c-beb0-0f2b4260ad3e.png)

2. После успешного импорта нам нужно будет сослаться на этот файл библиотеки, поэтому снова выберите проект, импортируйте библиотеку и выберите только что импортированный файл библиотеки.![](./README.assets/1725382640313-35ad96c4-45b2-4a2b-a657-c42cb07b3154.png)

3. После импорта заголовочный файл будет включен, и наш собственный обученный файл модели пробуждения будет успешно импортирован.

![](./README.assets/1725382732035-fe057853-c6a6-461e-bb68-e1f540684af9.png)

4. Скомпилируйте проект, загрузите программу на разработочный модуль ESP32-S3. После запуска программы она будет в реальном времени отслеживать пробуждающие слова и давать предсказания по трём меткам, которые были обучены ранее. Чем ближе значение предсказания к 1.0, тем более надёжным является результат.

![](./README.assets/1725383425750-85ae6b58-7422-4e88-acb9-2cad60bab795.png)

5. Когда я произношу "houguoxiong", значение предсказания метки "hgx" стремится к 1.0, что подтверждает успешное пробуждение.

![](./README.assets/1725383488994-40cc2660-89ee-4c57-b82c-83cf1456caca.png)

### Замена модели пробуждения
Когда в проекте Arduino требуется заменить новую модель пробуждения, можно удалить предыдущую библиотеку модели, удалить заголовочные файлы, которые были ранее импортированы, и затем повторить процедуру импорта модели и её использования, описанную в разделе **7.3.2**.

![](./README.assets/1725383847477-3474b6a9-e66a-4523-8937-7dcc6e38105c.png)

Удаление библиотеки происходит путём удаления папки библиотеки в соответствующем каталоге, после чего закройте программу Arduino и снова откройте проект Arduino.

Комментарии ( 0 )

Вы можете оставить комментарий после Вход в систему

Введение

Подключение ESP32-S3 к языковым моделям для реализации функции реального времени голосового диалога Развернуть Свернуть
C++
Apache-2.0
Отмена

Обновления (1)

все

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/chging-esp32s3-ai-chat.git
git@api.gitlife.ru:oschina-mirror/chging-esp32s3-ai-chat.git
oschina-mirror
chging-esp32s3-ai-chat
chging-esp32s3-ai-chat
master