Compare commits

...

6 Commits

Author SHA1 Message Date
Terrence
91c7774117 bump to 0.6.2 2024-11-01 15:19:37 +08:00
Terrence
b0bc81b921 add new boards 2024-11-01 14:26:02 +08:00
Terrence
a701d5918e add abort command 2024-10-31 05:57:13 +08:00
Terrence
6f5f5a0642 use protocol 3 2024-10-30 06:58:29 +08:00
Terrence
3e1e576272 update ml307 component version 2024-10-29 00:26:05 +08:00
Terrence
33518dca2b add config files for known boards 2024-10-29 00:22:29 +08:00
46 changed files with 1664 additions and 3361 deletions

View File

@@ -4,7 +4,7 @@
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
set(PROJECT_VER "0.4.1")
set(PROJECT_VER "0.6.2")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(xiaozhi)

40
convert_audio_to_p3.py Normal file
View File

@@ -0,0 +1,40 @@
# convert audio files to protocol v3 stream
import librosa
import opuslib
import struct
import sys
import tqdm
import numpy as np
def encode_audio_to_opus(input_file, output_file):
# Load audio file using librosa
audio, sample_rate = librosa.load(input_file, sr=None, mono=False, dtype=np.int16)
# Get left channel if stereo
if audio.ndim == 2:
audio = audio[0]
# Initialize Opus encoder
encoder = opuslib.Encoder(sample_rate, 1, opuslib.APPLICATION_VOIP)
# Encode audio data to Opus packets
# Save encoded data to file
with open(output_file, 'wb') as f:
sample_rate = 16000 # 16000Hz
duration = 60 # 60ms every frame
frame_size = int(sample_rate * duration / 1000)
for i in tqdm.tqdm(range(0, len(audio) - frame_size, frame_size)):
frame = audio[i:i + frame_size]
opus_data = encoder.encode(frame.tobytes(), frame_size=frame_size)
# protocol format, [1u type, 1u reserved, 2u len, data]
packet = struct.pack('>BBH', 0, 0, len(opus_data)) + opus_data
f.write(packet)
# Example usage
if len(sys.argv) != 3:
print('Usage: python convert.py <input_file> <output_file>')
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2]
encode_audio_to_opus(input_file, output_file)

View File

@@ -1,6 +1,4 @@
#include <BuiltinLed.h>
#include <TcpTransport.h>
#include <TlsTransport.h>
#include <Ml307SslTransport.h>
#include <WifiConfigurationAp.h>
#include <WifiStation.h>
@@ -10,36 +8,35 @@
#include <esp_log.h>
#include <cJSON.h>
#include <driver/gpio.h>
#include <arpa/inet.h>
#include "Application.h"
#define TAG "Application"
extern const char p3_err_reg_start[] asm("_binary_err_reg_p3_start");
extern const char p3_err_reg_end[] asm("_binary_err_reg_p3_end");
extern const char p3_err_pin_start[] asm("_binary_err_pin_p3_start");
extern const char p3_err_pin_end[] asm("_binary_err_pin_p3_end");
extern const char p3_err_wificonfig_start[] asm("_binary_err_wificonfig_p3_start");
extern const char p3_err_wificonfig_end[] asm("_binary_err_wificonfig_p3_end");
Application::Application()
: boot_button_((gpio_num_t)CONFIG_BOOT_BUTTON_GPIO),
volume_up_button_((gpio_num_t)CONFIG_VOLUME_UP_BUTTON_GPIO),
volume_down_button_((gpio_num_t)CONFIG_VOLUME_DOWN_BUTTON_GPIO),
#ifdef CONFIG_USE_DISPLAY
display_(CONFIG_DISPLAY_SDA_PIN, CONFIG_DISPLAY_SCL_PIN),
#endif
#ifdef CONFIG_USE_ML307
ml307_at_modem_(CONFIG_ML307_TX_PIN, CONFIG_ML307_RX_PIN, 4096),
http_(ml307_at_modem_),
#else
http_(),
#endif
firmware_upgrade_(http_)
: boot_button_((gpio_num_t)BOOT_BUTTON_GPIO),
volume_up_button_((gpio_num_t)VOLUME_UP_BUTTON_GPIO),
volume_down_button_((gpio_num_t)VOLUME_DOWN_BUTTON_GPIO),
display_(DISPLAY_SDA_PIN, DISPLAY_SCL_PIN)
{
event_group_ = xEventGroupCreate();
opus_encoder_.Configure(16000, 1);
opus_decoder_ = opus_decoder_create(opus_decode_sample_rate_, 1, NULL);
if (opus_decode_sample_rate_ != CONFIG_AUDIO_OUTPUT_SAMPLE_RATE) {
output_resampler_.Configure(CONFIG_AUDIO_OUTPUT_SAMPLE_RATE, opus_decode_sample_rate_);
if (opus_decode_sample_rate_ != AUDIO_OUTPUT_SAMPLE_RATE) {
output_resampler_.Configure(AUDIO_OUTPUT_SAMPLE_RATE, opus_decode_sample_rate_);
}
if (16000 != CONFIG_AUDIO_INPUT_SAMPLE_RATE) {
input_resampler_.Configure(CONFIG_AUDIO_INPUT_SAMPLE_RATE, 16000);
if (16000 != AUDIO_INPUT_SAMPLE_RATE) {
input_resampler_.Configure(AUDIO_INPUT_SAMPLE_RATE, 16000);
}
firmware_upgrade_.SetCheckVersionUrl(CONFIG_OTA_VERSION_URL);
@@ -47,11 +44,24 @@ Application::Application()
}
Application::~Application() {
if (update_display_timer_ != nullptr) {
esp_timer_stop(update_display_timer_);
esp_timer_delete(update_display_timer_);
}
if (ws_client_ != nullptr) {
delete ws_client_;
}
if (opus_decoder_ != nullptr) {
opus_decoder_destroy(opus_decoder_);
}
if (audio_encode_task_stack_ != nullptr) {
free(audio_encode_task_stack_);
heap_caps_free(audio_encode_task_stack_);
}
if (main_loop_task_stack_ != nullptr) {
heap_caps_free(main_loop_task_stack_);
}
if (audio_device_ != nullptr) {
delete audio_device_;
}
vEventGroupDelete(event_group_);
@@ -59,6 +69,7 @@ Application::~Application() {
void Application::CheckNewVersion() {
// Check if there is a new firmware version available
firmware_upgrade_.SetBoardJson(Board::GetInstance().GetJson());
firmware_upgrade_.CheckVersion();
if (firmware_upgrade_.HasNewVersion()) {
// Wait for the chat state to be idle
@@ -67,11 +78,9 @@ void Application::CheckNewVersion() {
}
SetChatState(kChatStateUpgrading);
firmware_upgrade_.StartUpgrade([this](int progress, size_t speed) {
#ifdef CONFIG_USE_DISPLAY
char buffer[64];
snprintf(buffer, sizeof(buffer), "Upgrading...\n %d%% %zuKB/s", progress, speed / 1024);
display_.SetText(buffer);
#endif
});
// If upgrade success, the device will reboot and never reach here
ESP_LOGI(TAG, "Firmware upgrade failed...");
@@ -81,138 +90,56 @@ void Application::CheckNewVersion() {
}
}
#ifdef CONFIG_USE_DISPLAY
void Application::Alert(const std::string&& title, const std::string&& message) {
ESP_LOGE(TAG, "Alert: %s, %s", title.c_str(), message.c_str());
display_.ShowNotification(std::string(title + "\n" + message));
#ifdef CONFIG_USE_ML307
static std::string csq_to_string(int csq) {
if (csq == -1) {
return "No network";
} else if (csq >= 0 && csq <= 9) {
return "Very bad";
} else if (csq >= 10 && csq <= 14) {
return "Bad";
} else if (csq >= 15 && csq <= 19) {
return "Fair";
} else if (csq >= 20 && csq <= 24) {
return "Good";
} else if (csq >= 25 && csq <= 31) {
return "Very good";
}
return "Invalid";
}
#else
static std::string rssi_to_string(int rssi) {
if (rssi >= -55) {
return "Very good";
} else if (rssi >= -65) {
return "Good";
} else if (rssi >= -75) {
return "Fair";
} else if (rssi >= -85) {
return "Poor";
} else {
return "No network";
if (message == "PIN is not ready") {
PlayLocalFile(p3_err_pin_start, p3_err_pin_end - p3_err_pin_start);
} else if (message == "Configuring WiFi") {
PlayLocalFile(p3_err_wificonfig_start, p3_err_wificonfig_end - p3_err_wificonfig_start);
} else if (message == "Registration denied") {
PlayLocalFile(p3_err_reg_start, p3_err_reg_end - p3_err_reg_start);
}
}
#endif
void Application::UpdateDisplay() {
while (true) {
if (chat_state_ == kChatStateIdle) {
#ifdef CONFIG_USE_ML307
std::string network_name = ml307_at_modem_.GetCarrierName();
int signal_quality = ml307_at_modem_.GetCsq();
if (signal_quality == -1) {
network_name = "No network";
} else {
ESP_LOGI(TAG, "%s CSQ: %d", network_name.c_str(), signal_quality);
display_.SetText(network_name + "\n" + csq_to_string(signal_quality) + " (" + std::to_string(signal_quality) + ")");
}
#else
auto& wifi_station = WifiStation::GetInstance();
int8_t rssi = wifi_station.GetRssi();
display_.SetText(wifi_station.GetSsid() + "\n" + rssi_to_string(rssi) + " (" + std::to_string(rssi) + ")");
#endif
}
vTaskDelay(pdMS_TO_TICKS(10 * 1000));
void Application::PlayLocalFile(const char* data, size_t size) {
ESP_LOGI(TAG, "PlayLocalFile: %zu bytes", size);
SetDecodeSampleRate(16000);
{
std::lock_guard<std::mutex> lock(mutex_);
auto packet = new AudioPacket();
packet->type = kAudioPacketTypeStart;
audio_decode_queue_.push_back(packet);
}
ParseBinaryProtocol3(data, size);
{
std::lock_guard<std::mutex> lock(mutex_);
auto packet = new AudioPacket();
packet->type = kAudioPacketTypeStop;
audio_decode_queue_.push_back(packet);
cv_.notify_all();
}
}
#endif
void Application::Start() {
auto& builtin_led = BuiltinLed::GetInstance();
#ifdef CONFIG_USE_ML307
builtin_led.SetBlue();
builtin_led.StartContinuousBlink(100);
ml307_at_modem_.SetDebug(false);
ml307_at_modem_.SetBaudRate(921600);
// Print the ML307 modem information
std::string module_name = ml307_at_modem_.GetModuleName();
ESP_LOGI(TAG, "ML307 Module: %s", module_name.c_str());
#ifdef CONFIG_USE_DISPLAY
display_.SetText(std::string("Wait for network\n") + module_name);
#endif
ml307_at_modem_.ResetConnections();
ml307_at_modem_.WaitForNetworkReady();
std::string imei = ml307_at_modem_.GetImei();
std::string iccid = ml307_at_modem_.GetIccid();
ESP_LOGI(TAG, "ML307 IMEI: %s", imei.c_str());
ESP_LOGI(TAG, "ML307 ICCID: %s", iccid.c_str());
auto& board = Board::GetInstance();
board.Initialize();
// If low power, the material ready event will be triggered by the modem because of a reset
ml307_at_modem_.OnMaterialReady([this]() {
ESP_LOGI(TAG, "ML307 material ready");
Schedule([this]() {
SetChatState(kChatStateIdle);
});
});
// Set the board type for OTA
std::string carrier_name = ml307_at_modem_.GetCarrierName();
int csq = ml307_at_modem_.GetCsq();
std::string board_json = std::string("{\"type\":\"compact.4g\",");
board_json += "\"revision\":\"" + module_name + "\",";
board_json += "\"carrier\":\"" + carrier_name + "\",";
board_json += "\"csq\":\"" + std::to_string(csq) + "\",";
board_json += "\"imei\":\"" + imei + "\",";
board_json += "\"iccid\":\"" + iccid + "\"}";
firmware_upgrade_.SetBoardJson(board_json);
#else
// Try to connect to WiFi, if failed, launch the WiFi configuration AP
auto& wifi_station = WifiStation::GetInstance();
#ifdef CONFIG_USE_DISPLAY
display_.SetText(std::string("Connect to WiFi\n") + wifi_station.GetSsid());
#endif
builtin_led.SetBlue();
builtin_led.StartContinuousBlink(100);
wifi_station.Start();
if (!wifi_station.IsConnected()) {
builtin_led.SetBlue();
builtin_led.Blink(1000, 500);
auto& wifi_ap = WifiConfigurationAp::GetInstance();
wifi_ap.SetSsidPrefix("Xiaozhi");
#ifdef CONFIG_USE_DISPLAY
display_.SetText(wifi_ap.GetSsid() + "\n" + wifi_ap.GetWebServerUrl());
#endif
wifi_ap.Start();
return;
}
// Set the board type for OTA
std::string board_json = std::string("{\"type\":\"compact.wifi\",");
board_json += "\"ssid\":\"" + wifi_station.GetSsid() + "\",";
board_json += "\"rssi\":" + std::to_string(wifi_station.GetRssi()) + ",";
board_json += "\"channel\":" + std::to_string(wifi_station.GetChannel()) + ",";
board_json += "\"ip\":\"" + wifi_station.GetIpAddress() + "\",";
board_json += "\"mac\":\"" + SystemInfo::GetMacAddress() + "\"}";
firmware_upgrade_.SetBoardJson(board_json);
#endif
audio_device_.Initialize();
audio_device_.OnInputData([this](std::vector<int16_t>&& data) {
if (16000 != CONFIG_AUDIO_INPUT_SAMPLE_RATE) {
if (audio_device_.input_channels() == 2) {
audio_device_ = board.CreateAudioDevice();
audio_device_->Initialize();
audio_device_->EnableOutput(true);
audio_device_->EnableInput(true);
audio_device_->OnInputData([this](std::vector<int16_t>&& data) {
if (16000 != AUDIO_INPUT_SAMPLE_RATE) {
if (audio_device_->input_channels() == 2) {
auto left_channel = std::vector<int16_t>(data.size() / 2);
auto right_channel = std::vector<int16_t>(data.size() / 2);
for (size_t i = 0, j = 0; i < left_channel.size(); ++i, j += 2) {
@@ -254,7 +181,7 @@ void Application::Start() {
// OPUS encoder / decoder use a lot of stack memory
const size_t opus_stack_size = 4096 * 8;
audio_encode_task_stack_ = (StackType_t*)malloc(opus_stack_size);
audio_encode_task_stack_ = (StackType_t*)heap_caps_malloc(opus_stack_size, MALLOC_CAP_SPIRAM);
audio_encode_task_ = xTaskCreateStatic([](void* arg) {
Application* app = (Application*)arg;
app->AudioEncodeTask();
@@ -267,8 +194,90 @@ void Application::Start() {
vTaskDelete(NULL);
}, "play_audio", 4096 * 4, this, 4, NULL);
board.StartNetwork();
// Blink the LED to indicate the device is running
builtin_led.SetGreen();
builtin_led.BlinkOnce();
boot_button_.OnClick([this]() {
Schedule([this]() {
if (chat_state_ == kChatStateIdle) {
SetChatState(kChatStateConnecting);
StartWebSocketClient();
if (ws_client_ && ws_client_->IsConnected()) {
opus_encoder_.ResetState();
#ifdef CONFIG_USE_AFE_SR
wake_word_detect_.Initialize(audio_device_.input_channels(), audio_device_.input_reference());
audio_processor_.Start();
#endif
SetChatState(kChatStateListening);
ESP_LOGI(TAG, "Communication started");
} else {
SetChatState(kChatStateIdle);
}
} else if (chat_state_ == kChatStateSpeaking) {
AbortSpeaking();
} else if (chat_state_ == kChatStateListening) {
if (ws_client_ && ws_client_->IsConnected()) {
ws_client_->Close();
}
}
});
});
volume_up_button_.OnClick([this]() {
Schedule([this]() {
auto volume = audio_device_->output_volume() + 10;
if (volume > 100) {
volume = 100;
}
audio_device_->SetOutputVolume(volume);
display_.ShowNotification("Volume\n" + std::to_string(volume));
});
});
volume_up_button_.OnLongPress([this]() {
Schedule([this]() {
audio_device_->SetOutputVolume(100);
display_.ShowNotification("Volume\n100");
});
});
volume_down_button_.OnClick([this]() {
Schedule([this]() {
auto volume = audio_device_->output_volume() - 10;
if (volume < 0) {
volume = 0;
}
audio_device_->SetOutputVolume(volume);
display_.ShowNotification("Volume\n" + std::to_string(volume));
});
});
volume_down_button_.OnLongPress([this]() {
Schedule([this]() {
audio_device_->SetOutputVolume(0);
display_.ShowNotification("Volume\n0");
});
});
const size_t main_loop_stack_size = 4096 * 2;
main_loop_task_stack_ = (StackType_t*)heap_caps_malloc(main_loop_stack_size, MALLOC_CAP_SPIRAM);
xTaskCreateStatic([](void* arg) {
Application* app = (Application*)arg;
app->MainLoop();
vTaskDelete(NULL);
}, "main_loop", main_loop_stack_size, this, 1, main_loop_task_stack_, &main_loop_task_buffer_);
// Launch a task to check for new firmware version
xTaskCreate([](void* arg) {
Application* app = (Application*)arg;
app->CheckNewVersion();
vTaskDelete(NULL);
}, "check_new_version", 4096 * 2, this, 1, NULL);
#ifdef CONFIG_USE_AFE_SR
wake_word_detect_.Initialize(audio_device_->input_channels(), audio_device_->input_reference());
wake_word_detect_.OnVadStateChange([this](bool speaking) {
Schedule([this, speaking]() {
auto& builtin_led = BuiltinLed::GetInstance();
@@ -308,7 +317,7 @@ void Application::Start() {
SetChatState(kChatStateIdle);
}
} else if (chat_state_ == kChatStateSpeaking) {
break_speaking_ = true;
AbortSpeaking();
}
// Resume detection
@@ -317,7 +326,7 @@ void Application::Start() {
});
wake_word_detect_.StartDetection();
audio_processor_.Initialize(audio_device_.input_channels(), audio_device_.input_reference());
audio_processor_.Initialize(audio_device_->input_channels(), audio_device_->input_reference());
audio_processor_.OnOutput([this](std::vector<int16_t>&& data) {
Schedule([this, data = std::move(data)]() {
if (chat_state_ == kChatStateListening) {
@@ -329,101 +338,8 @@ void Application::Start() {
});
#endif
// Blink the LED to indicate the device is running
builtin_led.SetGreen();
builtin_led.BlinkOnce();
boot_button_.OnClick([this]() {
Schedule([this]() {
if (chat_state_ == kChatStateIdle) {
SetChatState(kChatStateConnecting);
StartWebSocketClient();
if (ws_client_ && ws_client_->IsConnected()) {
opus_encoder_.ResetState();
#ifdef CONFIG_USE_AFE_SR
audio_processor_.Start();
#endif
SetChatState(kChatStateListening);
ESP_LOGI(TAG, "Communication started");
} else {
SetChatState(kChatStateIdle);
}
} else if (chat_state_ == kChatStateSpeaking) {
break_speaking_ = true;
} else if (chat_state_ == kChatStateListening) {
if (ws_client_ && ws_client_->IsConnected()) {
ws_client_->Close();
}
}
});
});
volume_up_button_.OnClick([this]() {
Schedule([this]() {
auto volume = audio_device_.output_volume() + 10;
if (volume > 100) {
volume = 100;
}
audio_device_.SetOutputVolume(volume);
#ifdef CONFIG_USE_DISPLAY
display_.ShowNotification("Volume\n" + std::to_string(volume));
#endif
});
});
volume_up_button_.OnLongPress([this]() {
Schedule([this]() {
audio_device_.SetOutputVolume(100);
#ifdef CONFIG_USE_DISPLAY
display_.ShowNotification("Volume\n100");
#endif
});
});
volume_down_button_.OnClick([this]() {
Schedule([this]() {
auto volume = audio_device_.output_volume() - 10;
if (volume < 0) {
volume = 0;
}
audio_device_.SetOutputVolume(volume);
#ifdef CONFIG_USE_DISPLAY
display_.ShowNotification("Volume\n" + std::to_string(volume));
#endif
});
});
volume_down_button_.OnLongPress([this]() {
Schedule([this]() {
audio_device_.SetOutputVolume(0);
#ifdef CONFIG_USE_DISPLAY
display_.ShowNotification("Volume\n0");
#endif
});
});
xTaskCreate([](void* arg) {
Application* app = (Application*)arg;
app->MainLoop();
vTaskDelete(NULL);
}, "main_loop", 4096 * 2, this, 5, NULL);
// Launch a task to check for new firmware version
xTaskCreate([](void* arg) {
Application* app = (Application*)arg;
app->CheckNewVersion();
vTaskDelete(NULL);
}, "check_new_version", 4096 * 2, this, 1, NULL);
#ifdef CONFIG_USE_DISPLAY
// Launch a task to update the display
xTaskCreate([](void* arg) {
Application* app = (Application*)arg;
app->UpdateDisplay();
vTaskDelete(NULL);
}, "update_display", 4096, this, 1, NULL);
#endif
SetChatState(kChatStateIdle);
display_.UpdateDisplay();
}
void Application::Schedule(std::function<void()> callback) {
@@ -448,8 +364,25 @@ void Application::MainLoop() {
}
}
void Application::AbortSpeaking() {
ESP_LOGI(TAG, "Abort speaking");
skip_to_end_ = true;
if (ws_client_ && ws_client_->IsConnected()) {
cJSON* root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "type", "abort");
char* json = cJSON_PrintUnformatted(root);
std::lock_guard<std::mutex> lock(mutex_);
ws_client_->Send(json);
cJSON_Delete(root);
free(json);
}
}
void Application::SetChatState(ChatState state) {
const char* state_str[] = {
"unknown",
"idle",
"connecting",
"listening",
@@ -457,15 +390,21 @@ void Application::SetChatState(ChatState state) {
"wake_word_detected",
"testing",
"upgrading",
"unknown"
"invalid_state"
};
if (chat_state_ == state) {
// No need to update the state
return;
}
chat_state_ = state;
ESP_LOGI(TAG, "STATE: %s", state_str[chat_state_]);
auto& builtin_led = BuiltinLed::GetInstance();
switch (chat_state_) {
case kChatStateUnknown:
case kChatStateIdle:
builtin_led.TurnOff();
audio_device_->EnableOutput(false);
break;
case kChatStateConnecting:
builtin_led.SetBlue();
@@ -478,6 +417,7 @@ void Application::SetChatState(ChatState state) {
case kChatStateSpeaking:
builtin_led.SetGreen();
builtin_led.TurnOn();
audio_device_->EnableOutput(true);
break;
case kChatStateWakeWordDetected:
builtin_led.SetBlue();
@@ -502,15 +442,12 @@ void Application::SetChatState(ChatState state) {
}
}
BinaryProtocol* Application::AllocateBinaryProtocol(const uint8_t* payload, size_t payload_size) {
auto last_timestamp = 0;
auto protocol = (BinaryProtocol*)heap_caps_malloc(sizeof(BinaryProtocol) + payload_size, MALLOC_CAP_SPIRAM);
protocol->version = htons(PROTOCOL_VERSION);
protocol->type = htons(0);
BinaryProtocol3* Application::AllocateBinaryProtocol3(const uint8_t* payload, size_t payload_size) {
auto protocol = (BinaryProtocol3*)heap_caps_malloc(sizeof(BinaryProtocol3) + payload_size, MALLOC_CAP_SPIRAM);
protocol->type = 0;
protocol->reserved = 0;
protocol->timestamp = htonl(last_timestamp);
protocol->payload_size = htonl(payload_size);
assert(sizeof(BinaryProtocol) == 16);
protocol->payload_size = htons(payload_size);
assert(sizeof(BinaryProtocol3) == 4UL);
memcpy(protocol->payload, payload, payload_size);
return protocol;
}
@@ -532,10 +469,10 @@ void Application::AudioEncodeTask() {
// Encode audio data
opus_encoder_.Encode(pcm, [this](const uint8_t* opus, size_t opus_size) {
auto protocol = AllocateBinaryProtocol(opus, opus_size);
auto protocol = AllocateBinaryProtocol3(opus, opus_size);
Schedule([this, protocol, opus_size]() {
if (ws_client_ && ws_client_->IsConnected()) {
if (!ws_client_->Send(protocol, sizeof(BinaryProtocol) + opus_size, true)) {
if (!ws_client_->Send(protocol, sizeof(BinaryProtocol3) + opus_size, true)) {
ESP_LOGE(TAG, "Failed to send audio data");
}
}
@@ -547,21 +484,23 @@ void Application::AudioEncodeTask() {
audio_decode_queue_.pop_front();
lock.unlock();
int frame_size = opus_decode_sample_rate_ * opus_duration_ms_ / 1000;
packet->pcm.resize(frame_size);
if (packet->type == kAudioPacketTypeData && !skip_to_end_) {
int frame_size = opus_decode_sample_rate_ * opus_duration_ms_ / 1000;
packet->pcm.resize(frame_size);
int ret = opus_decode(opus_decoder_, packet->opus.data(), packet->opus.size(), packet->pcm.data(), frame_size, 0);
if (ret < 0) {
ESP_LOGE(TAG, "Failed to decode audio, error code: %d", ret);
delete packet;
continue;
}
int ret = opus_decode(opus_decoder_, packet->opus.data(), packet->opus.size(), packet->pcm.data(), frame_size, 0);
if (ret < 0) {
ESP_LOGE(TAG, "Failed to decode audio, error code: %d", ret);
delete packet;
continue;
}
if (opus_decode_sample_rate_ != CONFIG_AUDIO_OUTPUT_SAMPLE_RATE) {
int target_size = output_resampler_.GetOutputSamples(frame_size);
std::vector<int16_t> resampled(target_size);
output_resampler_.Process(packet->pcm.data(), frame_size, resampled.data());
packet->pcm = std::move(resampled);
if (opus_decode_sample_rate_ != AUDIO_OUTPUT_SAMPLE_RATE) {
int target_size = output_resampler_.GetOutputSamples(frame_size);
std::vector<int16_t> resampled(target_size);
output_resampler_.Process(packet->pcm.data(), frame_size, resampled.data());
packet->pcm = std::move(resampled);
}
}
std::lock_guard<std::mutex> lock(mutex_);
@@ -580,17 +519,7 @@ void Application::HandleAudioPacket(AudioPacket* packet) {
}
// This will block until the audio device has finished playing the audio
audio_device_.OutputData(packet->pcm);
if (break_speaking_) {
skip_to_end_ = true;
// Play a silence and skip to the end
int frame_size = opus_decode_sample_rate_ / 1000 * opus_duration_ms_;
std::vector<int16_t> silence(frame_size);
bzero(silence.data(), silence.size() * sizeof(int16_t));
audio_device_.OutputData(silence);
}
audio_device_->OutputData(packet->pcm);
break;
}
case kAudioPacketTypeStart:
@@ -602,13 +531,20 @@ void Application::HandleAudioPacket(AudioPacket* packet) {
break;
case kAudioPacketTypeStop:
Schedule([this]() {
SetChatState(kChatStateListening);
if (ws_client_ && ws_client_->IsConnected()) {
SetChatState(kChatStateListening);
} else {
SetChatState(kChatStateIdle);
}
});
break;
case kAudioPacketTypeSentenceStart:
ESP_LOGI(TAG, "<< %s", packet->text.c_str());
break;
case kAudioPacketTypeSentenceEnd:
if (break_speaking_) {
skip_to_end_ = true;
}
break;
default:
ESP_LOGI(TAG, "Unknown packet type: %d", packet->type);
@@ -643,9 +579,26 @@ void Application::SetDecodeSampleRate(int sample_rate) {
opus_decoder_destroy(opus_decoder_);
opus_decode_sample_rate_ = sample_rate;
opus_decoder_ = opus_decoder_create(opus_decode_sample_rate_, 1, NULL);
if (opus_decode_sample_rate_ != CONFIG_AUDIO_OUTPUT_SAMPLE_RATE) {
ESP_LOGI(TAG, "Resampling audio from %d to %d", opus_decode_sample_rate_, CONFIG_AUDIO_OUTPUT_SAMPLE_RATE);
output_resampler_.Configure(opus_decode_sample_rate_, CONFIG_AUDIO_OUTPUT_SAMPLE_RATE);
if (opus_decode_sample_rate_ != AUDIO_OUTPUT_SAMPLE_RATE) {
ESP_LOGI(TAG, "Resampling audio from %d to %d", opus_decode_sample_rate_, AUDIO_OUTPUT_SAMPLE_RATE);
output_resampler_.Configure(opus_decode_sample_rate_, AUDIO_OUTPUT_SAMPLE_RATE);
}
}
void Application::ParseBinaryProtocol3(const char* data, size_t size) {
for (const char* p = data; p < data + size; ) {
auto protocol = (BinaryProtocol3*)p;
p += sizeof(BinaryProtocol3);
auto packet = new AudioPacket();
packet->type = kAudioPacketTypeData;
auto payload_size = ntohs(protocol->payload_size);
packet->opus.resize(payload_size);
memcpy(packet->opus.data(), protocol->payload, payload_size);
p += payload_size;
std::lock_guard<std::mutex> lock(mutex_);
audio_decode_queue_.push_back(packet);
}
}
@@ -657,15 +610,7 @@ void Application::StartWebSocketClient() {
std::string url = CONFIG_WEBSOCKET_URL;
std::string token = "Bearer " + std::string(CONFIG_WEBSOCKET_ACCESS_TOKEN);
#ifdef CONFIG_USE_ML307
ws_client_ = new WebSocket(new Ml307SslTransport(ml307_at_modem_, 0));
#else
if (url.find("wss://") == 0) {
ws_client_ = new WebSocket(new TlsTransport());
} else {
ws_client_ = new WebSocket(new TcpTransport());
}
#endif
ws_client_ = Board::GetInstance().CreateWebSocket();
ws_client_->SetHeader("Authorization", token.c_str());
ws_client_->SetHeader("Protocol-Version", std::to_string(PROTOCOL_VERSION).c_str());
ws_client_->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str());
@@ -685,17 +630,7 @@ void Application::StartWebSocketClient() {
ws_client_->OnData([this](const char* data, size_t len, bool binary) {
if (binary) {
auto protocol = (BinaryProtocol*)data;
auto packet = new AudioPacket();
packet->type = kAudioPacketTypeData;
packet->timestamp = ntohl(protocol->timestamp);
auto payload_size = ntohl(protocol->payload_size);
packet->opus.resize(payload_size);
memcpy(packet->opus.data(), protocol->payload, payload_size);
std::lock_guard<std::mutex> lock(mutex_);
audio_decode_queue_.push_back(packet);
ParseBinaryProtocol3(data, len);
cv_.notify_all();
} else {
// Parse JSON data
@@ -712,8 +647,7 @@ void Application::StartWebSocketClient() {
SetDecodeSampleRate(sample_rate->valueint);
}
// If the device is speaking, we need to break the speaking
break_speaking_ = true;
// If the device is speaking, we need to skip the last session
skip_to_end_ = true;
} else if (strcmp(state->valuestring, "stop") == 0) {
packet->type = kAudioPacketTypeStop;

View File

@@ -4,9 +4,6 @@
#include <OpusEncoder.h>
#include <OpusResampler.h>
#include <WebSocket.h>
#include <Ml307AtModem.h>
#include <Ml307Http.h>
#include <EspHttp.h>
#include <opus.h>
#include <resampler_structs.h>
@@ -16,8 +13,9 @@
#include <list>
#include <condition_variable>
#include "BoxAudioDevice.h"
#include "AudioDevice.h"
#include "Display.h"
#include "Board.h"
#include "FirmwareUpgrade.h"
#ifdef CONFIG_USE_AFE_SR
@@ -30,13 +28,11 @@
#define DETECTION_RUNNING 1
#define COMMUNICATION_RUNNING 2
#define PROTOCOL_VERSION 2
struct BinaryProtocol {
uint16_t version;
uint16_t type;
uint32_t reserved;
uint32_t timestamp;
uint32_t payload_size;
#define PROTOCOL_VERSION 3
struct BinaryProtocol3 {
uint8_t type;
uint8_t reserved;
uint16_t payload_size;
uint8_t payload[];
} __attribute__((packed));
@@ -59,6 +55,7 @@ struct AudioPacket {
enum ChatState {
kChatStateUnknown,
kChatStateIdle,
kChatStateConnecting,
kChatStateListening,
@@ -75,7 +72,12 @@ public:
}
void Start();
ChatState GetChatState() const { return chat_state_; }
Display& GetDisplay() { return display_; }
void Schedule(std::function<void()> callback);
void SetChatState(ChatState state);
void Alert(const std::string&& title, const std::string&& message);
void AbortSpeaking();
// 删除拷贝构造函数和赋值运算符
Application(const Application&) = delete;
Application& operator=(const Application&) = delete;
@@ -87,23 +89,11 @@ private:
Button boot_button_;
Button volume_up_button_;
Button volume_down_button_;
#ifdef CONFIG_AUDIO_CODEC_ES8311_ES7210
BoxAudioDevice audio_device_;
#else
AudioDevice audio_device_;
#endif
#ifdef CONFIG_USE_DISPLAY
AudioDevice* audio_device_ = nullptr;
Display display_;
#endif
#ifdef CONFIG_USE_AFE_SR
WakeWordDetect wake_word_detect_;
AudioProcessor audio_processor_;
#endif
#ifdef CONFIG_USE_ML307
Ml307AtModem ml307_at_modem_;
Ml307Http http_;
#else
EspHttp http_;
#endif
FirmwareUpgrade firmware_upgrade_;
std::mutex mutex_;
@@ -111,9 +101,10 @@ private:
std::list<std::function<void()>> main_tasks_;
WebSocket* ws_client_ = nullptr;
EventGroupHandle_t event_group_;
volatile ChatState chat_state_ = kChatStateIdle;
volatile ChatState chat_state_ = kChatStateUnknown;
volatile bool break_speaking_ = false;
bool skip_to_end_ = false;
esp_timer_handle_t update_display_timer_ = nullptr;
// Audio encode / decode
TaskHandle_t audio_encode_task_ = nullptr;
@@ -127,26 +118,25 @@ private:
OpusDecoder* opus_decoder_ = nullptr;
int opus_duration_ms_ = 60;
int opus_decode_sample_rate_ = CONFIG_AUDIO_OUTPUT_SAMPLE_RATE;
int opus_decode_sample_rate_ = AUDIO_OUTPUT_SAMPLE_RATE;
OpusResampler input_resampler_;
OpusResampler output_resampler_;
TaskHandle_t check_new_version_task_ = nullptr;
StaticTask_t check_new_version_task_buffer_;
StackType_t* check_new_version_task_stack_ = nullptr;
TaskHandle_t main_loop_task_ = nullptr;
StaticTask_t main_loop_task_buffer_;
StackType_t* main_loop_task_stack_ = nullptr;
void MainLoop();
void Schedule(std::function<void()> callback);
BinaryProtocol* AllocateBinaryProtocol(const uint8_t* payload, size_t payload_size);
BinaryProtocol3* AllocateBinaryProtocol3(const uint8_t* payload, size_t payload_size);
void ParseBinaryProtocol3(const char* data, size_t size);
void SetDecodeSampleRate(int sample_rate);
void SetChatState(ChatState state);
void StartWebSocketClient();
void CheckNewVersion();
void UpdateDisplay();
void AudioEncodeTask();
void AudioPlayTask();
void HandleAudioPacket(AudioPacket* packet);
void PlayLocalFile(const char* data, size_t size);
};
#endif // _APPLICATION_H_

View File

@@ -1,12 +1,15 @@
#include "AudioDevice.h"
#include "Board.h"
#include <esp_log.h>
#include <cstring>
#include <cmath>
#define TAG "AudioDevice"
AudioDevice::AudioDevice()
: input_sample_rate_(CONFIG_AUDIO_INPUT_SAMPLE_RATE),
output_sample_rate_(CONFIG_AUDIO_OUTPUT_SAMPLE_RATE) {
: input_sample_rate_(AUDIO_INPUT_SAMPLE_RATE),
output_sample_rate_(AUDIO_OUTPUT_SAMPLE_RATE) {
}
AudioDevice::~AudioDevice() {
@@ -22,7 +25,7 @@ AudioDevice::~AudioDevice() {
}
void AudioDevice::Initialize() {
#ifdef CONFIG_AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
CreateSimplexChannels();
#else
CreateDuplexChannels();
@@ -30,7 +33,7 @@ void AudioDevice::Initialize() {
}
void AudioDevice::CreateDuplexChannels() {
#ifdef CONFIG_AUDIO_I2S_METHOD_DUPLEX
#ifndef AUDIO_I2S_METHOD_SIMPLEX
duplex_ = true;
i2s_chan_config_t chan_cfg = {
@@ -65,10 +68,10 @@ void AudioDevice::CreateDuplexChannels() {
},
.gpio_cfg = {
.mclk = I2S_GPIO_UNUSED,
.bclk = (gpio_num_t)CONFIG_AUDIO_DEVICE_I2S_GPIO_BCLK,
.ws = (gpio_num_t)CONFIG_AUDIO_DEVICE_I2S_GPIO_LRCK,
.dout = (gpio_num_t)CONFIG_AUDIO_DEVICE_I2S_GPIO_DOUT,
.din = (gpio_num_t)CONFIG_AUDIO_DEVICE_I2S_GPIO_DIN,
.bclk = (gpio_num_t)AUDIO_I2S_GPIO_BCLK,
.ws = (gpio_num_t)AUDIO_I2S_GPIO_LRCK,
.dout = (gpio_num_t)AUDIO_I2S_GPIO_DOUT,
.din = (gpio_num_t)AUDIO_I2S_GPIO_DIN,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
@@ -85,14 +88,14 @@ void AudioDevice::CreateDuplexChannels() {
}
void AudioDevice::CreateSimplexChannels() {
#ifdef CONFIG_AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
// Create a new channel for speaker
i2s_chan_config_t chan_cfg = {
.id = I2S_NUM_0,
.role = I2S_ROLE_MASTER,
.dma_desc_num = 6,
.dma_frame_num = 240,
.auto_clear_after_cb = false,
.auto_clear_after_cb = true,
.auto_clear_before_cb = false,
.intr_priority = 0,
};
@@ -119,9 +122,9 @@ void AudioDevice::CreateSimplexChannels() {
},
.gpio_cfg = {
.mclk = I2S_GPIO_UNUSED,
.bclk = (gpio_num_t)CONFIG_AUDIO_DEVICE_I2S_SPK_GPIO_BCLK,
.ws = (gpio_num_t)CONFIG_AUDIO_DEVICE_I2S_SPK_GPIO_LRCK,
.dout = (gpio_num_t)CONFIG_AUDIO_DEVICE_I2S_SPK_GPIO_DOUT,
.bclk = (gpio_num_t)AUDIO_I2S_SPK_GPIO_BCLK,
.ws = (gpio_num_t)AUDIO_I2S_SPK_GPIO_LRCK,
.dout = (gpio_num_t)AUDIO_I2S_SPK_GPIO_DOUT,
.din = I2S_GPIO_UNUSED,
.invert_flags = {
.mclk_inv = false,
@@ -136,10 +139,10 @@ void AudioDevice::CreateSimplexChannels() {
chan_cfg.id = I2S_NUM_1;
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, nullptr, &rx_handle_));
std_cfg.clk_cfg.sample_rate_hz = (uint32_t)input_sample_rate_;
std_cfg.gpio_cfg.bclk = (gpio_num_t)CONFIG_AUDIO_DEVICE_I2S_MIC_GPIO_SCK;
std_cfg.gpio_cfg.ws = (gpio_num_t)CONFIG_AUDIO_DEVICE_I2S_MIC_GPIO_WS;
std_cfg.gpio_cfg.bclk = (gpio_num_t)AUDIO_I2S_MIC_GPIO_SCK;
std_cfg.gpio_cfg.ws = (gpio_num_t)AUDIO_I2S_MIC_GPIO_WS;
std_cfg.gpio_cfg.dout = I2S_GPIO_UNUSED;
std_cfg.gpio_cfg.din = (gpio_num_t)CONFIG_AUDIO_DEVICE_I2S_MIC_GPIO_DIN;
std_cfg.gpio_cfg.din = (gpio_num_t)AUDIO_I2S_MIC_GPIO_DIN;
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
@@ -173,15 +176,15 @@ int AudioDevice::Write(const int16_t* data, int samples) {
int AudioDevice::Read(int16_t* dest, int samples) {
size_t bytes_read;
int32_t bit32_buffer_[samples];
if (i2s_channel_read(rx_handle_, bit32_buffer_, samples * sizeof(int32_t), &bytes_read, portMAX_DELAY) != ESP_OK) {
int32_t bit32_buffer[samples];
if (i2s_channel_read(rx_handle_, bit32_buffer, samples * sizeof(int32_t), &bytes_read, portMAX_DELAY) != ESP_OK) {
ESP_LOGE(TAG, "Read Failed!");
return 0;
}
samples = bytes_read / sizeof(int32_t);
for (int i = 0; i < samples; i++) {
int32_t value = bit32_buffer_[i] >> 12;
int32_t value = bit32_buffer[i] >> 12;
dest[i] = (value > INT16_MAX) ? INT16_MAX : (value < -INT16_MAX) ? -INT16_MAX : (int16_t)value;
}
return samples;
@@ -221,3 +224,19 @@ void AudioDevice::SetOutputVolume(int volume) {
output_volume_ = volume;
ESP_LOGI(TAG, "Set output volume to %d", output_volume_);
}
void AudioDevice::EnableInput(bool enable) {
if (enable == input_enabled_) {
return;
}
input_enabled_ = enable;
ESP_LOGI(TAG, "Set input enable to %s", enable ? "true" : "false");
}
void AudioDevice::EnableOutput(bool enable) {
if (enable == output_enabled_) {
return;
}
output_enabled_ = enable;
ESP_LOGI(TAG, "Set output enable to %s", enable ? "true" : "false");
}

View File

@@ -17,6 +17,8 @@ public:
void OnInputData(std::function<void(std::vector<int16_t>&& data)> callback);
void OutputData(std::vector<int16_t>& data);
virtual void SetOutputVolume(int volume);
virtual void EnableInput(bool enable);
virtual void EnableOutput(bool enable);
inline bool duplex() const { return duplex_; }
inline bool input_reference() const { return input_reference_; }
@@ -36,6 +38,8 @@ private:
protected:
bool duplex_ = false;
bool input_reference_ = false;
bool input_enabled_ = false;
bool output_enabled_ = false;
int input_sample_rate_ = 0;
int output_sample_rate_ = 0;
int input_channels_ = 1;

8
main/Board.cc Normal file
View File

@@ -0,0 +1,8 @@
#include "Board.h"
#include <esp_log.h>
// static const char *TAG = "Board";
bool Board::GetBatteryVoltage(int &voltage, bool& charging) {
return false;
}

45
main/Board.h Normal file
View File

@@ -0,0 +1,45 @@
#ifndef BOARD_H
#define BOARD_H
#include "config.h"
#include <Http.h>
#include <WebSocket.h>
#include <AudioDevice.h>
#include <string>
void* create_board();
class Board {
public:
static Board& GetInstance() {
static Board* instance = nullptr;
if (nullptr == instance) {
instance = static_cast<Board*>(create_board());
}
return *instance;
}
virtual void Initialize() = 0;
virtual void StartNetwork() = 0;
virtual ~Board() = default;
virtual AudioDevice* CreateAudioDevice() = 0;
virtual Http* CreateHttp() = 0;
virtual WebSocket* CreateWebSocket() = 0;
virtual bool GetNetworkState(std::string& network_name, int& signal_quality, std::string& signal_quality_text) = 0;
virtual bool GetBatteryVoltage(int &voltage, bool& charging);
virtual std::string GetJson() = 0;
protected:
Board() = default;
private:
Board(const Board&) = delete; // 禁用拷贝构造函数
Board& operator=(const Board&) = delete; // 禁用赋值操作
};
#define DECLARE_BOARD(BOARD_CLASS_NAME) \
void* create_board() { \
return new BOARD_CLASS_NAME(); \
}
#endif // BOARD_H

View File

@@ -1,4 +1,6 @@
#include "BoxAudioDevice.h"
#include "Board.h"
#include <esp_log.h>
#include <cassert>
@@ -25,14 +27,14 @@ BoxAudioDevice::~BoxAudioDevice() {
void BoxAudioDevice::Initialize() {
duplex_ = true; // 是否双工
input_reference_ = CONFIG_AUDIO_CODEC_INPUT_REFERENCE; // 是否使用参考输入,实现回声消除
input_reference_ = AUDIO_INPUT_REFERENCE; // 是否使用参考输入,实现回声消除
input_channels_ = input_reference_ ? 2 : 1; // 输入通道数
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_0,
.sda_io_num = (gpio_num_t)CONFIG_AUDIO_CODEC_I2C_SDA_PIN,
.scl_io_num = (gpio_num_t)CONFIG_AUDIO_CODEC_I2C_SCL_PIN,
.i2c_port = I2C_NUM_1,
.sda_io_num = (gpio_num_t)AUDIO_CODEC_I2C_SDA_PIN,
.scl_io_num = (gpio_num_t)AUDIO_CODEC_I2C_SCL_PIN,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.intr_priority = 0,
@@ -45,6 +47,28 @@ void BoxAudioDevice::Initialize() {
CreateDuplexChannels();
#ifdef AUDIO_CODEC_USE_PCA9557
// Initialize PCA9557
i2c_device_config_t pca9557_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = 0x19,
.scl_speed_hz = 400000,
.scl_wait_us = 0,
.flags = {
.disable_ack_check = 0,
},
};
i2c_master_dev_handle_t pca9557_handle;
ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_master_handle_, &pca9557_cfg, &pca9557_handle));
assert(pca9557_handle != NULL);
auto pca9557_set_register = [](i2c_master_dev_handle_t pca9557_handle, uint8_t data_addr, uint8_t data) {
uint8_t data_[2] = {data_addr, data};
ESP_ERROR_CHECK(i2c_master_transmit(pca9557_handle, data_, 2, 50));
};
pca9557_set_register(pca9557_handle, 0x03, 0xfd);
pca9557_set_register(pca9557_handle, 0x01, 0x02);
#endif
// Do initialize of related interface: data_if, ctrl_if and gpio_if
audio_codec_i2s_cfg_t i2s_cfg = {
.port = I2S_NUM_0,
@@ -56,8 +80,8 @@ void BoxAudioDevice::Initialize() {
// Output
audio_codec_i2c_cfg_t i2c_cfg = {
.port = I2C_NUM_0,
.addr = ES8311_CODEC_DEFAULT_ADDR,
.port = I2C_NUM_1,
.addr = AUDIO_CODEC_ES8311_ADDR,
.bus_handle = i2c_master_handle_,
};
out_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
@@ -70,7 +94,7 @@ void BoxAudioDevice::Initialize() {
es8311_cfg.ctrl_if = out_ctrl_if_;
es8311_cfg.gpio_if = gpio_if_;
es8311_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_DAC;
es8311_cfg.pa_pin = CONFIG_AUDIO_CODEC_PA_PIN;
es8311_cfg.pa_pin = AUDIO_CODEC_PA_PIN;
es8311_cfg.use_mclk = true;
es8311_cfg.hw_gain.pa_voltage = 5.0;
es8311_cfg.hw_gain.codec_dac_voltage = 3.3;
@@ -85,20 +109,8 @@ void BoxAudioDevice::Initialize() {
output_dev_ = esp_codec_dev_new(&dev_cfg);
assert(output_dev_ != NULL);
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_));
// Play 16bit 1 channel
esp_codec_dev_sample_info_t fs = {
.bits_per_sample = 16,
.channel = 1,
.channel_mask = 0,
.sample_rate = (uint32_t)output_sample_rate_,
.mclk_multiple = 0,
};
ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs));
// Input
i2c_cfg.addr = ES7210_CODEC_DEFAULT_ADDR;
i2c_cfg.addr = AUDIO_CODEC_ES7210_ADDR;
in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
assert(in_ctrl_if_ != NULL);
@@ -113,16 +125,6 @@ void BoxAudioDevice::Initialize() {
input_dev_ = esp_codec_dev_new(&dev_cfg);
assert(input_dev_ != NULL);
fs.channel = 4;
if (input_channels_ == 1) {
fs.channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0);
} else {
fs.channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0) | ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1);
}
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
ESP_ERROR_CHECK(esp_codec_dev_set_in_channel_gain(input_dev_, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), 30.0));
ESP_LOGI(TAG, "BoxAudioDevice initialized");
}
@@ -160,10 +162,10 @@ void BoxAudioDevice::CreateDuplexChannels() {
.bit_order_lsb = false
},
.gpio_cfg = {
.mclk = (gpio_num_t)CONFIG_AUDIO_DEVICE_I2S_GPIO_MCLK,
.bclk = (gpio_num_t)CONFIG_AUDIO_DEVICE_I2S_GPIO_BCLK,
.ws = (gpio_num_t)CONFIG_AUDIO_DEVICE_I2S_GPIO_LRCK,
.dout = (gpio_num_t)CONFIG_AUDIO_DEVICE_I2S_GPIO_DOUT,
.mclk = (gpio_num_t)AUDIO_I2S_GPIO_MCLK,
.bclk = (gpio_num_t)AUDIO_I2S_GPIO_BCLK,
.ws = (gpio_num_t)AUDIO_I2S_GPIO_LRCK,
.dout = (gpio_num_t)AUDIO_I2S_GPIO_DOUT,
.din = I2S_GPIO_UNUSED,
.invert_flags = {
.mclk_inv = false,
@@ -196,11 +198,11 @@ void BoxAudioDevice::CreateDuplexChannels() {
.total_slot = I2S_TDM_AUTO_SLOT_NUM
},
.gpio_cfg = {
.mclk = (gpio_num_t)CONFIG_AUDIO_DEVICE_I2S_GPIO_MCLK,
.bclk = (gpio_num_t)CONFIG_AUDIO_DEVICE_I2S_GPIO_BCLK,
.ws = (gpio_num_t)CONFIG_AUDIO_DEVICE_I2S_GPIO_LRCK,
.mclk = (gpio_num_t)AUDIO_I2S_GPIO_MCLK,
.bclk = (gpio_num_t)AUDIO_I2S_GPIO_BCLK,
.ws = (gpio_num_t)AUDIO_I2S_GPIO_LRCK,
.dout = I2S_GPIO_UNUSED,
.din = (gpio_num_t)CONFIG_AUDIO_DEVICE_I2S_GPIO_DIN,
.din = (gpio_num_t)AUDIO_I2S_GPIO_DIN,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
@@ -217,12 +219,16 @@ void BoxAudioDevice::CreateDuplexChannels() {
}
int BoxAudioDevice::Read(int16_t *buffer, int samples) {
ESP_ERROR_CHECK(esp_codec_dev_read(input_dev_, (void*)buffer, samples * sizeof(int16_t)));
if (input_enabled_) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)buffer, samples * sizeof(int16_t)));
}
return samples;
}
int BoxAudioDevice::Write(const int16_t *buffer, int samples) {
ESP_ERROR_CHECK(esp_codec_dev_write(output_dev_, (void*)buffer, samples * sizeof(int16_t)));
if (output_enabled_) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)buffer, samples * sizeof(int16_t)));
}
return samples;
}
@@ -230,3 +236,47 @@ void BoxAudioDevice::SetOutputVolume(int volume) {
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
AudioDevice::SetOutputVolume(volume);
}
void BoxAudioDevice::EnableInput(bool enable) {
if (enable == input_enabled_) {
return;
}
if (enable) {
esp_codec_dev_sample_info_t fs = {
.bits_per_sample = 16,
.channel = 4,
.channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0),
.sample_rate = (uint32_t)output_sample_rate_,
.mclk_multiple = 0,
};
if (input_reference_) {
fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1);
}
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
ESP_ERROR_CHECK(esp_codec_dev_set_in_channel_gain(input_dev_, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), 30.0));
} else {
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
}
AudioDevice::EnableInput(enable);
}
void BoxAudioDevice::EnableOutput(bool enable) {
if (enable == output_enabled_) {
return;
}
if (enable) {
// Play 16bit 1 channel
esp_codec_dev_sample_info_t fs = {
.bits_per_sample = 16,
.channel = 1,
.channel_mask = 0,
.sample_rate = (uint32_t)output_sample_rate_,
.mclk_multiple = 0,
};
ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs));
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_));
} else {
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
}
AudioDevice::EnableOutput(enable);
}

View File

@@ -12,8 +12,10 @@ class BoxAudioDevice : public AudioDevice {
public:
BoxAudioDevice();
virtual ~BoxAudioDevice();
void Initialize() override;
void SetOutputVolume(int volume) override;
virtual void Initialize() override;
virtual void SetOutputVolume(int volume) override;
virtual void EnableInput(bool enable) override;
virtual void EnableOutput(bool enable) override;
private:
i2c_master_bus_handle_t i2c_master_handle_ = nullptr;

137
main/BuiltinLed.cc Normal file
View File

@@ -0,0 +1,137 @@
#include "BuiltinLed.h"
#include "Board.h"
#include <cstring>
#include <esp_log.h>
#define TAG "builtin_led"
BuiltinLed::BuiltinLed() {
mutex_ = xSemaphoreCreateMutex();
blink_event_group_ = xEventGroupCreate();
xEventGroupSetBits(blink_event_group_, BLINK_TASK_STOPPED_BIT);
if (BUILTIN_LED_GPIO == GPIO_NUM_NC) {
ESP_LOGI(TAG, "Builtin LED not connected");
return;
}
Initialize();
SetGrey();
}
BuiltinLed::~BuiltinLed() {
StopBlinkInternal();
if (led_strip_ != nullptr) {
led_strip_del(led_strip_);
}
if (mutex_ != nullptr) {
vSemaphoreDelete(mutex_);
}
if (blink_event_group_ != nullptr) {
vEventGroupDelete(blink_event_group_);
}
}
BuiltinLed& BuiltinLed::GetInstance() {
static BuiltinLed instance;
return instance;
}
void BuiltinLed::Initialize() {
led_strip_config_t strip_config = {};
strip_config.strip_gpio_num = BUILTIN_LED_GPIO;
strip_config.max_leds = 1;
strip_config.led_pixel_format = LED_PIXEL_FORMAT_GRB;
strip_config.led_model = LED_MODEL_WS2812;
led_strip_rmt_config_t rmt_config = {};
rmt_config.resolution_hz = 10 * 1000 * 1000; // 10MHz
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip_));
led_strip_clear(led_strip_);
}
void BuiltinLed::SetColor(uint8_t r, uint8_t g, uint8_t b) {
r_ = r;
g_ = g;
b_ = b;
}
void BuiltinLed::TurnOn() {
if (led_strip_ == nullptr) {
return;
}
StopBlinkInternal();
xSemaphoreTake(mutex_, portMAX_DELAY);
led_strip_set_pixel(led_strip_, 0, r_, g_, b_);
led_strip_refresh(led_strip_);
xSemaphoreGive(mutex_);
}
void BuiltinLed::TurnOff() {
if (led_strip_ == nullptr) {
return;
}
StopBlinkInternal();
xSemaphoreTake(mutex_, portMAX_DELAY);
led_strip_clear(led_strip_);
xSemaphoreGive(mutex_);
}
void BuiltinLed::BlinkOnce() {
Blink(1, 100);
}
void BuiltinLed::Blink(int times, int interval_ms) {
StartBlinkTask(times, interval_ms);
}
void BuiltinLed::StartContinuousBlink(int interval_ms) {
StartBlinkTask(BLINK_INFINITE, interval_ms);
}
void BuiltinLed::StartBlinkTask(int times, int interval_ms) {
if (led_strip_ == nullptr) {
return;
}
StopBlinkInternal();
xSemaphoreTake(mutex_, portMAX_DELAY);
blink_times_ = times;
blink_interval_ms_ = interval_ms;
should_blink_ = true;
xEventGroupClearBits(blink_event_group_, BLINK_TASK_STOPPED_BIT);
xEventGroupSetBits(blink_event_group_, BLINK_TASK_RUNNING_BIT);
xTaskCreate([](void* obj) {
auto this_ = static_cast<BuiltinLed*>(obj);
int count = 0;
while (this_->should_blink_ && (this_->blink_times_ == BLINK_INFINITE || count < this_->blink_times_)) {
xSemaphoreTake(this_->mutex_, portMAX_DELAY);
led_strip_set_pixel(this_->led_strip_, 0, this_->r_, this_->g_, this_->b_);
led_strip_refresh(this_->led_strip_);
xSemaphoreGive(this_->mutex_);
vTaskDelay(this_->blink_interval_ms_ / portTICK_PERIOD_MS);
if (!this_->should_blink_) break;
xSemaphoreTake(this_->mutex_, portMAX_DELAY);
led_strip_clear(this_->led_strip_);
xSemaphoreGive(this_->mutex_);
vTaskDelay(this_->blink_interval_ms_ / portTICK_PERIOD_MS);
if (this_->blink_times_ != BLINK_INFINITE) count++;
}
this_->blink_task_ = nullptr;
xEventGroupClearBits(this_->blink_event_group_, BLINK_TASK_RUNNING_BIT);
xEventGroupSetBits(this_->blink_event_group_, BLINK_TASK_STOPPED_BIT);
vTaskDelete(NULL);
}, "blink", 2048, this, tskIDLE_PRIORITY, &blink_task_);
xSemaphoreGive(mutex_);
}
void BuiltinLed::StopBlinkInternal() {
should_blink_ = false;
xEventGroupWaitBits(blink_event_group_, BLINK_TASK_STOPPED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
}

52
main/BuiltinLed.h Normal file
View File

@@ -0,0 +1,52 @@
#ifndef _BUILTIN_LED_H_
#define _BUILTIN_LED_H_
#include <led_strip.h>
#include <freertos/semphr.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include <atomic>
#define BLINK_INFINITE -1
#define BLINK_TASK_STOPPED_BIT BIT0
#define BLINK_TASK_RUNNING_BIT BIT1
#define DEFAULT_BRIGHTNESS 16
class BuiltinLed {
public:
static BuiltinLed& GetInstance();
void BlinkOnce();
void Blink(int times, int interval_ms);
void StartContinuousBlink(int interval_ms);
void TurnOn();
void TurnOff();
void SetColor(uint8_t r, uint8_t g, uint8_t b);
void SetWhite(uint8_t brightness = DEFAULT_BRIGHTNESS) { SetColor(brightness, brightness, brightness); }
void SetGrey(uint8_t brightness = DEFAULT_BRIGHTNESS) { SetColor(brightness, brightness, brightness); }
void SetRed(uint8_t brightness = DEFAULT_BRIGHTNESS) { SetColor(brightness, 0, 0); }
void SetGreen(uint8_t brightness = DEFAULT_BRIGHTNESS) { SetColor(0, brightness, 0); }
void SetBlue(uint8_t brightness = DEFAULT_BRIGHTNESS) { SetColor(0, 0, brightness); }
private:
BuiltinLed();
~BuiltinLed();
BuiltinLed(const BuiltinLed&) = delete;
BuiltinLed& operator=(const BuiltinLed&) = delete;
SemaphoreHandle_t mutex_;
EventGroupHandle_t blink_event_group_;
TaskHandle_t blink_task_ = nullptr;
led_strip_handle_t led_strip_ = nullptr;
uint8_t r_ = 0, g_ = 0, b_ = 0;
int blink_times_ = 0;
int blink_interval_ms_ = 0;
std::atomic<bool> should_blink_{false};
void Initialize();
void StartBlinkTask(int times, int interval_ms);
void StopBlinkInternal();
};
#endif // _BUILTIN_LED_H_

View File

@@ -4,6 +4,9 @@
static const char* TAG = "Button";
Button::Button(gpio_num_t gpio_num) : gpio_num_(gpio_num) {
if (gpio_num == GPIO_NUM_NC) {
return;
}
button_config_t button_config = {
.type = BUTTON_TYPE_GPIO,
.long_press_time = 1000,
@@ -27,6 +30,9 @@ Button::~Button() {
}
void Button::OnPress(std::function<void()> callback) {
if (button_handle_ == nullptr) {
return;
}
on_press_ = callback;
iot_button_register_cb(button_handle_, BUTTON_PRESS_DOWN, [](void* handle, void* usr_data) {
Button* button = static_cast<Button*>(usr_data);
@@ -37,6 +43,9 @@ void Button::OnPress(std::function<void()> callback) {
}
void Button::OnLongPress(std::function<void()> callback) {
if (button_handle_ == nullptr) {
return;
}
on_long_press_ = callback;
iot_button_register_cb(button_handle_, BUTTON_LONG_PRESS_START, [](void* handle, void* usr_data) {
Button* button = static_cast<Button*>(usr_data);
@@ -47,6 +56,9 @@ void Button::OnLongPress(std::function<void()> callback) {
}
void Button::OnClick(std::function<void()> callback) {
if (button_handle_ == nullptr) {
return;
}
on_click_ = callback;
iot_button_register_cb(button_handle_, BUTTON_SINGLE_CLICK, [](void* handle, void* usr_data) {
Button* button = static_cast<Button*>(usr_data);
@@ -57,6 +69,9 @@ void Button::OnClick(std::function<void()> callback) {
}
void Button::OnDoubleClick(std::function<void()> callback) {
if (button_handle_ == nullptr) {
return;
}
on_double_click_ = callback;
iot_button_register_cb(button_handle_, BUTTON_DOUBLE_CLICK, [](void* handle, void* usr_data) {
Button* button = static_cast<Button*>(usr_data);

View File

@@ -3,18 +3,65 @@ set(SOURCES "AudioDevice.cc"
"SystemInfo.cc"
"SystemReset.cc"
"Application.cc"
"Display.cc"
"Button.cc"
"BuiltinLed.cc"
"Display.cc"
"Board.cc"
"main.cc"
)
set(INCLUDE_DIRS ".")
# 根据 BOARD_TYPE 配置添加对应的板级文件
if(CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI)
# add all files from boards/bread-compact-wifi
set(BOARD_TYPE "bread-compact-wifi")
file(GLOB BOARD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc)
list(APPEND SOURCES ${BOARD_SOURCES} "WifiBoard.cc")
list(APPEND INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE})
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ML307)
# add all files from boards/bread-compact-ml307
set(BOARD_TYPE "bread-compact-ml307")
file(GLOB BOARD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc)
list(APPEND SOURCES ${BOARD_SOURCES} "Ml307Board.cc")
list(APPEND INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE})
elseif(CONFIG_BOARD_TYPE_ESP_BOX_3)
# add all files from boards/esp-box-3
set(BOARD_TYPE "esp-box-3")
file(GLOB BOARD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc)
list(APPEND SOURCES ${BOARD_SOURCES} "WifiBoard.cc")
list(APPEND INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE})
list(APPEND SOURCES "BoxAudioDevice.cc")
elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_0)
# add all files from boards/kevin-box-0
set(BOARD_TYPE "kevin-box-0")
file(GLOB BOARD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc)
list(APPEND SOURCES ${BOARD_SOURCES} "Ml307Board.cc")
list(APPEND INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE})
list(APPEND SOURCES "BoxAudioDevice.cc")
elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_1)
# add all files from boards/kevin-box-1
set(BOARD_TYPE "kevin-box-1")
file(GLOB BOARD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc)
list(APPEND SOURCES ${BOARD_SOURCES} "Ml307Board.cc")
list(APPEND INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE})
list(APPEND SOURCES "BoxAudioDevice.cc")
elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV)
# add all files from boards/lichuang-dev
set(BOARD_TYPE "lichuang-dev")
file(GLOB BOARD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc)
list(APPEND SOURCES ${BOARD_SOURCES} "WifiBoard.cc")
list(APPEND INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE})
list(APPEND SOURCES "BoxAudioDevice.cc")
endif()
if(CONFIG_USE_AFE_SR)
list(APPEND SOURCES "AudioProcessor.cc" "WakeWordDetect.cc")
endif()
if(CONFIG_AUDIO_CODEC_ES8311_ES7210)
list(APPEND SOURCES "BoxAudioDevice.cc")
endif()
idf_component_register(SRCS ${SOURCES}
INCLUDE_DIRS "."
EMBED_FILES "assets/err_reg.p3" "assets/err_pin.p3" "assets/err_wificonfig.p3"
INCLUDE_DIRS ${INCLUDE_DIRS}
)
# 使用 target_compile_definitions 来定义 BOARD_TYPE
target_compile_definitions(${COMPONENT_LIB} PRIVATE BOARD_TYPE=\"${BOARD_TYPE}\")

View File

@@ -1,6 +1,3 @@
#include "Display.h"
#include <esp_log.h>
#include <esp_err.h>
#include <esp_lcd_panel_ops.h>
@@ -9,20 +6,25 @@
#include <string>
#include <cstdlib>
#include "Display.h"
#include "Board.h"
#include "Application.h"
#define TAG "Display"
#ifdef CONFIG_USE_DISPLAY
Display::Display(int sda_pin, int scl_pin) : sda_pin_(sda_pin), scl_pin_(scl_pin) {
ESP_LOGI(TAG, "Display Pins: %d, %d", sda_pin_, scl_pin_);
if (sda_pin_ == GPIO_NUM_NC || scl_pin_ == GPIO_NUM_NC) {
ESP_LOGI(TAG, "Display not connected");
return;
}
i2c_master_bus_config_t bus_config = {
.i2c_port = I2C_NUM_1,
.i2c_port = I2C_NUM_0,
.sda_io_num = (gpio_num_t)sda_pin_,
.scl_io_num = (gpio_num_t)scl_pin_,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.intr_priority = 1,
.intr_priority = 0,
.trans_queue_depth = 0,
.flags = {
.enable_internal_pullup = 1,
@@ -44,7 +46,7 @@ Display::Display(int sda_pin, int scl_pin) : sda_pin_(sda_pin), scl_pin_(scl_pin
.dc_low_on_data = 0,
.disable_control_phase = 0,
},
.scl_speed_hz = 400 * 1000,
.scl_speed_hz = 100 * 1000,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(i2c_bus_, &io_config, &panel_io_));
@@ -55,7 +57,7 @@ Display::Display(int sda_pin, int scl_pin) : sda_pin_(sda_pin), scl_pin_(scl_pin
panel_config.bits_per_pixel = 1;
esp_lcd_panel_ssd1306_config_t ssd1306_config = {
.height = CONFIG_DISPLAY_HEIGHT
.height = DISPLAY_HEIGHT
};
panel_config.vendor_config = &ssd1306_config;
@@ -73,45 +75,66 @@ Display::Display(int sda_pin, int scl_pin) : sda_pin_(sda_pin), scl_pin_(scl_pin
lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
lvgl_port_init(&port_cfg);
const lvgl_port_display_cfg_t display_cfg = {
.io_handle = panel_io_,
.panel_handle = panel_,
.buffer_size = 128 * CONFIG_DISPLAY_HEIGHT,
.double_buffer = true,
.hres = 128,
.vres = CONFIG_DISPLAY_HEIGHT,
.monochrome = true,
.rotation = {
.swap_xy = 0,
.mirror_x = 0,
.mirror_y = 0,
},
.flags = {
.buff_dma = 0,
.buff_spiram = 0,
},
};
disp_ = lvgl_port_add_disp(&display_cfg);
lv_disp_set_rotation(disp_, LV_DISP_ROT_180);
// Set the display to on
ESP_LOGI(TAG, "Turning display on");
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
ESP_LOGI(TAG, "Adding LCD screen");
const lvgl_port_display_cfg_t display_cfg = {
.io_handle = panel_io_,
.panel_handle = panel_,
.control_handle = nullptr,
.buffer_size = DISPLAY_WIDTH * DISPLAY_HEIGHT,
.double_buffer = false,
.trans_size = 0,
.hres = DISPLAY_WIDTH,
.vres = DISPLAY_HEIGHT,
.monochrome = true,
.rotation = {
.swap_xy = false,
.mirror_x = DISPLAY_MIRROR_X,
.mirror_y = DISPLAY_MIRROR_Y,
},
.flags = {
.buff_dma = 1,
.buff_spiram = 0,
.sw_rotate = 0,
.full_refresh = 0,
.direct_mode = 0,
},
};
disp_ = lvgl_port_add_disp(&display_cfg);;
ESP_LOGI(TAG, "Display Loading...");
if (lvgl_port_lock(0)) {
label_ = lv_label_create(lv_disp_get_scr_act(disp_));
// lv_obj_set_style_text_font(label_, font_, 0);
lv_label_set_text(label_, "Initializing...");
lv_obj_set_width(label_, disp_->driver->hor_res);
lv_obj_set_height(label_, disp_->driver->ver_res);
notification_ = lv_label_create(lv_disp_get_scr_act(disp_));
// lv_obj_set_style_text_font(notification_, font_, 0);
lv_label_set_text(notification_, "Notification\nTest");
lv_obj_set_width(notification_, disp_->driver->hor_res);
lv_obj_set_height(notification_, disp_->driver->ver_res);
lv_obj_set_style_opa(notification_, LV_OPA_MIN, 0);
lvgl_port_unlock();
}
// Create a timer to update the display every 10 seconds
esp_timer_create_args_t update_display_timer_args = {
.callback = [](void *arg) {
Display* display = static_cast<Display*>(arg);
display->UpdateDisplay();
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "UpdateDisplay",
.skip_unhandled_events = false,
};
ESP_ERROR_CHECK(esp_timer_create(&update_display_timer_args, &update_display_timer_));
ESP_ERROR_CHECK(esp_timer_start_periodic(update_display_timer_, 10 * 1000000));
}
Display::~Display() {
@@ -119,6 +142,10 @@ Display::~Display() {
esp_timer_stop(notification_timer_);
esp_timer_delete(notification_timer_);
}
if (update_display_timer_ != nullptr) {
esp_timer_stop(update_display_timer_);
esp_timer_delete(update_display_timer_);
}
lvgl_port_lock(0);
if (label_ != nullptr) {
@@ -127,6 +154,10 @@ Display::~Display() {
}
lvgl_port_unlock();
if (font_ != nullptr) {
lv_font_free(font_);
}
if (disp_ != nullptr) {
lvgl_port_deinit();
esp_lcd_panel_del(panel_);
@@ -176,4 +207,31 @@ void Display::ShowNotification(const std::string &text) {
}
}
#endif
void Display::UpdateDisplay() {
auto chat_state = Application::GetInstance().GetChatState();
if (chat_state == kChatStateIdle || chat_state == kChatStateConnecting || chat_state == kChatStateListening) {
std::string text;
auto& board = Board::GetInstance();
std::string network_name;
int signal_quality;
std::string signal_quality_text;
if (!board.GetNetworkState(network_name, signal_quality, signal_quality_text)) {
text = "No network";
} else {
text = network_name + "\n" + signal_quality_text;
if (std::abs(signal_quality) != 99) {
text += " (" + std::to_string(signal_quality) + ")";
}
}
int battery_voltage;
bool charging;
if (board.GetBatteryVoltage(battery_voltage, charging)) {
text += "\n" + std::to_string(battery_voltage) + "mV";
if (charging) {
text += " (Charging)";
}
}
SetText(text);
}
}

View File

@@ -17,6 +17,8 @@ public:
void SetText(const std::string &text);
void ShowNotification(const std::string &text);
void UpdateDisplay();
private:
int sda_pin_;
int scl_pin_;
@@ -26,9 +28,11 @@ private:
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
esp_lcd_panel_handle_t panel_ = nullptr;
lv_disp_t *disp_ = nullptr;
lv_font_t *font_ = nullptr;
lv_obj_t *label_ = nullptr;
lv_obj_t *notification_ = nullptr;
esp_timer_handle_t notification_timer_ = nullptr;
esp_timer_handle_t update_display_timer_ = nullptr;
std::string text_;
};

View File

@@ -1,5 +1,7 @@
#include "FirmwareUpgrade.h"
#include "SystemInfo.h"
#include "Board.h"
#include <cJSON.h>
#include <esp_log.h>
#include <esp_partition.h>
@@ -15,7 +17,7 @@
#define TAG "FirmwareUpgrade"
FirmwareUpgrade::FirmwareUpgrade(Http& http) : http_(http) {
FirmwareUpgrade::FirmwareUpgrade() {
}
FirmwareUpgrade::~FirmwareUpgrade() {
@@ -38,16 +40,18 @@ void FirmwareUpgrade::CheckVersion() {
return;
}
auto http = Board::GetInstance().CreateHttp();
for (const auto& header : headers_) {
http_.SetHeader(header.first, header.second);
http->SetHeader(header.first, header.second);
}
http_.SetHeader("Content-Type", "application/json");
http_.SetContent(GetPostData());
http_.Open("POST", check_version_url_);
http->SetHeader("Content-Type", "application/json");
http->SetContent(GetPostData());
http->Open("POST", check_version_url_);
auto response = http_.GetBody();
http_.Close();
auto response = http->GetBody();
http->Close();
delete http;
// Response: { "firmware": { "version": "1.0.0", "url": "http://" } }
// Parse the JSON response and check if the version is newer
@@ -123,15 +127,17 @@ void FirmwareUpgrade::Upgrade(const std::string& firmware_url) {
bool image_header_checked = false;
std::string image_header;
if (!http_.Open("GET", firmware_url)) {
auto http = Board::GetInstance().CreateHttp();
if (!http->Open("GET", firmware_url)) {
ESP_LOGE(TAG, "Failed to open HTTP connection");
delete http;
return;
}
size_t content_length = http_.GetBodyLength();
size_t content_length = http->GetBodyLength();
if (content_length == 0) {
ESP_LOGE(TAG, "Failed to get content length");
http_.Close();
delete http;
return;
}
@@ -139,10 +145,10 @@ void FirmwareUpgrade::Upgrade(const std::string& firmware_url) {
size_t total_read = 0, recent_read = 0;
auto last_calc_time = esp_timer_get_time();
while (true) {
int ret = http_.Read(buffer, sizeof(buffer));
int ret = http->Read(buffer, sizeof(buffer));
if (ret < 0) {
ESP_LOGE(TAG, "Failed to read HTTP data: %s", esp_err_to_name(ret));
http_.Close();
delete http;
return;
}
@@ -174,13 +180,13 @@ void FirmwareUpgrade::Upgrade(const std::string& firmware_url) {
auto current_version = esp_app_get_description()->version;
if (memcmp(new_app_info.version, current_version, sizeof(new_app_info.version)) == 0) {
ESP_LOGE(TAG, "Firmware version is the same, skipping upgrade");
http_.Close();
delete http;
return;
}
if (esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &update_handle)) {
esp_ota_abort(update_handle);
http_.Close();
delete http;
ESP_LOGE(TAG, "Failed to begin OTA");
return;
}
@@ -192,11 +198,11 @@ void FirmwareUpgrade::Upgrade(const std::string& firmware_url) {
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err));
esp_ota_abort(update_handle);
http_.Close();
delete http;
return;
}
}
http_.Close();
delete http;
esp_err_t err = esp_ota_end(update_handle);
if (err != ESP_OK) {

View File

@@ -5,11 +5,9 @@
#include <string>
#include <map>
#include <Http.h>
class FirmwareUpgrade {
public:
FirmwareUpgrade(Http& http);
FirmwareUpgrade();
~FirmwareUpgrade();
void SetBoardJson(const std::string& board_json);
@@ -21,7 +19,6 @@ public:
void MarkCurrentVersionValid();
private:
Http& http_;
std::string check_version_url_;
bool has_new_version_ = false;
std::string firmware_version_;

View File

@@ -18,218 +18,29 @@ config WEBSOCKET_ACCESS_TOKEN
help
Access token for websocket communication.
config AUDIO_INPUT_SAMPLE_RATE
int "Audio Input Sample Rate"
default 16000
choice BOARD_TYPE
prompt "Board Type"
default BOARD_TYPE_BREAD_COMPACT_WIFI
help
Audio input sample rate.
config AUDIO_OUTPUT_SAMPLE_RATE
int "Audio Output Sample Rate"
default 24000
help
Audio output sample rate.
choice AUDIO_CODEC
prompt "Audio Codec"
default AUDIO_CODEC_NONE
help
Audio codec.
config AUDIO_CODEC_ES8311_ES7210
bool "Box: ES8311 + ES7210"
config AUDIO_CODEC_NONE
bool "None"
Board type. 开发板类型
config BOARD_TYPE_BREAD_COMPACT_WIFI
bool "面包板新版接线WiFi"
config BOARD_TYPE_BREAD_COMPACT_ML307
bool "面包板新版接线ML307 AT"
config BOARD_TYPE_ESP_BOX_3
bool "ESP BOX 3"
config BOARD_TYPE_KEVIN_BOX_0
bool "Kevin Box 0"
config BOARD_TYPE_KEVIN_BOX_1
bool "Kevin Box 1"
config BOARD_TYPE_LICHUANG_DEV
bool "立创开发板"
endchoice
menu "Box Audio Codec I2C and PA Control"
depends on AUDIO_CODEC_ES8311_ES7210
config AUDIO_CODEC_I2C_SDA_PIN
int "Audio Codec I2C SDA Pin"
default 39
help
Audio codec I2C SDA pin.
config AUDIO_CODEC_I2C_SCL_PIN
int "Audio Codec I2C SCL Pin"
default 38
help
Audio codec I2C SCL pin.
config AUDIO_CODEC_PA_PIN
int "Audio Codec PA Pin"
default 40
help
Audio codec PA pin.
config AUDIO_CODEC_INPUT_REFERENCE
bool "Audio Codec Input Reference"
default y
help
Audio codec input reference.
endmenu
choice AUDIO_I2S_METHOD
prompt "Audio I2S Method"
default AUDIO_I2S_METHOD_SIMPLEX if AUDIO_CODEC_NONE
default AUDIO_I2S_METHOD_DUPLEX if AUDIO_CODEC_ES8311_ES7210
help
Audio I2S method.
config AUDIO_I2S_METHOD_SIMPLEX
bool "Simplex"
help
Use I2S 0 as the audio input and I2S 1 as the audio output.
config AUDIO_I2S_METHOD_DUPLEX
bool "Duplex"
help
Use I2S 0 as the audio input and audio output.
endchoice
menu "Audio I2S Simplex"
depends on AUDIO_I2S_METHOD_SIMPLEX
config AUDIO_DEVICE_I2S_MIC_GPIO_WS
int "I2S MIC GPIO WS"
default 4
help
GPIO number of the I2S MIC WS.
config AUDIO_DEVICE_I2S_MIC_GPIO_SCK
int "I2S MIC GPIO BCLK"
default 5
help
GPIO number of the I2S MIC SCK.
config AUDIO_DEVICE_I2S_MIC_GPIO_DIN
int "I2S MIC GPIO DIN"
default 6
help
GPIO number of the I2S MIC DIN.
config AUDIO_DEVICE_I2S_SPK_GPIO_DOUT
int "I2S SPK GPIO DOUT"
default 7
help
GPIO number of the I2S SPK DOUT.
config AUDIO_DEVICE_I2S_SPK_GPIO_BCLK
int "I2S SPK GPIO BCLK"
default 15
help
GPIO number of the I2S SPK BCLK.
config AUDIO_DEVICE_I2S_SPK_GPIO_LRCK
int "I2S SPK GPIO WS"
default 16
help
GPIO number of the I2S SPK LRCK.
endmenu
menu "Audio I2S Duplex"
depends on AUDIO_I2S_METHOD_DUPLEX
config AUDIO_DEVICE_I2S_GPIO_MCLK
int "I2S GPIO MCLK"
default -1
help
GPIO number of the I2S WS.
config AUDIO_DEVICE_I2S_GPIO_LRCK
int "I2S GPIO LRCK"
default 4
help
GPIO number of the I2S LRCK.
config AUDIO_DEVICE_I2S_GPIO_BCLK
int "I2S GPIO BCLK / SCLK"
default 5
help
GPIO number of the I2S BCLK.
config AUDIO_DEVICE_I2S_GPIO_DIN
int "I2S GPIO DIN"
default 6
help
GPIO number of the I2S DIN.
config AUDIO_DEVICE_I2S_GPIO_DOUT
int "I2S GPIO DOUT"
default 7
help
GPIO number of the I2S DOUT.
endmenu
config BOOT_BUTTON_GPIO
int "Boot Button GPIO"
default 0
help
GPIO number of the boot button.
config VOLUME_UP_BUTTON_GPIO
int "Volume Up Button GPIO"
default 40
help
GPIO number of the volume up button.
config VOLUME_DOWN_BUTTON_GPIO
int "Volume Down Button GPIO"
default 39
help
GPIO number of the volume down button.
config USE_AFE_SR
bool "Use Espressif AFE SR"
default y
help
Use AFE SR for wake word detection.
config USE_ML307
bool "Use ML307"
default n
help
Use ML307 as the modem.
config ML307_RX_PIN
int "ML307 RX Pin"
default 11
depends on USE_ML307
help
GPIO number of the ML307 RX.
config ML307_TX_PIN
int "ML307 TX Pin"
default 12
depends on USE_ML307
help
GPIO number of the ML307 TX.
config USE_DISPLAY
bool "Use Display"
default n
help
Use Display.
config DISPLAY_HEIGHT
int "Display Height"
default 32
depends on USE_DISPLAY
help
Display height in pixels.
config DISPLAY_SDA_PIN
int "Display SDA Pin"
default 41
depends on USE_DISPLAY
help
GPIO number of the Display SDA.
config DISPLAY_SCL_PIN
int "Display SCL Pin"
default 42
depends on USE_DISPLAY
help
GPIO number of the Display SCL.
endmenu

112
main/Ml307Board.cc Normal file
View File

@@ -0,0 +1,112 @@
#include "Ml307Board.h"
#include "Application.h"
#include <esp_log.h>
#include <Ml307Http.h>
#include <Ml307SslTransport.h>
#include <WebSocket.h>
#include <esp_timer.h>
static const char *TAG = "Ml307Board";
static std::string csq_to_string(int csq) {
if (csq == -1) {
return "No network";
} else if (csq >= 0 && csq <= 9) {
return "Very bad";
} else if (csq >= 10 && csq <= 14) {
return "Bad";
} else if (csq >= 15 && csq <= 19) {
return "Fair";
} else if (csq >= 20 && csq <= 24) {
return "Good";
} else if (csq >= 25 && csq <= 31) {
return "Very good";
}
return "Invalid";
}
Ml307Board::Ml307Board() : modem_(ML307_TX_PIN, ML307_RX_PIN, 4096) {
}
void Ml307Board::StartNetwork() {
auto& application = Application::GetInstance();
auto& display = application.GetDisplay();
display.SetText(std::string("Wait for network\n"));
int result = modem_.WaitForNetworkReady();
if (result == -1) {
application.Alert("Error", "PIN is not ready");
} else if (result == -2) {
application.Alert("Error", "Registration denied");
}
// Print the ML307 modem information
std::string module_name = modem_.GetModuleName();
std::string imei = modem_.GetImei();
std::string iccid = modem_.GetIccid();
ESP_LOGI(TAG, "ML307 Module: %s", module_name.c_str());
ESP_LOGI(TAG, "ML307 IMEI: %s", imei.c_str());
ESP_LOGI(TAG, "ML307 ICCID: %s", iccid.c_str());
}
void Ml307Board::StartModem() {
auto& display = Application::GetInstance().GetDisplay();
display.SetText(std::string("Starting modem"));
modem_.SetDebug(false);
modem_.SetBaudRate(921600);
auto& application = Application::GetInstance();
// If low power, the material ready event will be triggered by the modem because of a reset
modem_.OnMaterialReady([this, &application]() {
ESP_LOGI(TAG, "ML307 material ready");
application.Schedule([this, &application]() {
application.SetChatState(kChatStateIdle);
StartNetwork();
});
});
}
void Ml307Board::Initialize() {
ESP_LOGI(TAG, "Initializing Ml307Board");
StartModem();
}
AudioDevice* Ml307Board::CreateAudioDevice() {
return new AudioDevice();
}
Http* Ml307Board::CreateHttp() {
return new Ml307Http(modem_);
}
WebSocket* Ml307Board::CreateWebSocket() {
return new WebSocket(new Ml307SslTransport(modem_, 0));
}
bool Ml307Board::GetNetworkState(std::string& network_name, int& signal_quality, std::string& signal_quality_text) {
if (!modem_.network_ready()) {
return false;
}
network_name = modem_.GetCarrierName();
signal_quality = modem_.GetCsq();
signal_quality_text = csq_to_string(signal_quality);
return signal_quality != -1;
}
std::string Ml307Board::GetJson() {
// Set the board type for OTA
std::string board_type = BOARD_TYPE;
std::string module_name = modem_.GetModuleName();
std::string carrier_name = modem_.GetCarrierName();
std::string imei = modem_.GetImei();
std::string iccid = modem_.GetIccid();
int csq = modem_.GetCsq();
std::string board_json = std::string("{\"type\":\"" + board_type + "\",");
board_json += "\"revision\":\"" + module_name + "\",";
board_json += "\"carrier\":\"" + carrier_name + "\",";
board_json += "\"csq\":\"" + std::to_string(csq) + "\",";
board_json += "\"imei\":\"" + imei + "\",";
board_json += "\"iccid\":\"" + iccid + "\"}";
return board_json;
}

24
main/Ml307Board.h Normal file
View File

@@ -0,0 +1,24 @@
#ifndef ML307_BOARD_H
#define ML307_BOARD_H
#include "Board.h"
#include <Ml307AtModem.h>
class Ml307Board : public Board {
protected:
Ml307AtModem modem_;
void StartModem();
public:
Ml307Board();
virtual void Initialize() override;
virtual void StartNetwork() override;
virtual AudioDevice* CreateAudioDevice() override;
virtual Http* CreateHttp() override;
virtual WebSocket* CreateWebSocket() override;
virtual bool GetNetworkState(std::string& network_name, int& signal_quality, std::string& signal_quality_text) override;
virtual std::string GetJson() override;
};
#endif // ML307_BOARD_H

View File

@@ -41,6 +41,10 @@ void SystemReset::ResetNvsFlash() {
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to erase NVS flash");
}
ret = nvs_flash_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize NVS flash");
}
}
void SystemReset::ResetToFactory() {

View File

@@ -3,11 +3,18 @@
class SystemReset {
public:
SystemReset();
static SystemReset& GetInstance() {
static SystemReset instance;
return instance;
}
void CheckButtons();
private:
SystemReset(); // 构造函数私有化
SystemReset(const SystemReset&) = delete; // 禁用拷贝构造
SystemReset& operator=(const SystemReset&) = delete; // 禁用赋值操作
void ResetNvsFlash();
void ResetToFactory();
void RestartInSeconds(int seconds);

View File

@@ -1,5 +1,6 @@
#include <esp_log.h>
#include <model_path.h>
#include <arpa/inet.h>
#include "WakeWordDetect.h"
#include "Application.h"
@@ -179,14 +180,12 @@ void WakeWordDetect::EncodeWakeWordData() {
for (auto& pcm: this_->wake_word_pcm_) {
encoder->Encode(pcm, [this_, &offset](const uint8_t* opus, size_t opus_size) {
size_t protocol_size = sizeof(BinaryProtocol) + opus_size;
size_t protocol_size = sizeof(BinaryProtocol3) + opus_size;
if (offset + protocol_size < this_->wake_word_opus_.size()) {
auto protocol = (BinaryProtocol*)(&this_->wake_word_opus_[offset]);
protocol->version = htons(PROTOCOL_VERSION);
protocol->type = htons(0);
auto protocol = (BinaryProtocol3*)(&this_->wake_word_opus_[offset]);
protocol->type = 0;
protocol->reserved = 0;
protocol->timestamp = 0;
protocol->payload_size = htonl(opus_size);
protocol->payload_size = htons(opus_size);
memcpy(protocol->payload, opus, opus_size);
offset += protocol_size;
}

103
main/WifiBoard.cc Normal file
View File

@@ -0,0 +1,103 @@
#include "WifiBoard.h"
#include "Application.h"
#include "WifiStation.h"
#include "WifiConfigurationAp.h"
#include "SystemInfo.h"
#include "BuiltinLed.h"
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <EspHttp.h>
#include <TcpTransport.h>
#include <TlsTransport.h>
#include <WebSocket.h>
#include <esp_log.h>
static const char *TAG = "WifiBoard";
static std::string rssi_to_string(int rssi) {
if (rssi >= -55) {
return "Very good";
} else if (rssi >= -65) {
return "Good";
} else if (rssi >= -75) {
return "Fair";
} else if (rssi >= -85) {
return "Poor";
} else {
return "No network";
}
}
void WifiBoard::StartNetwork() {
auto& application = Application::GetInstance();
auto& display = application.GetDisplay();
auto& builtin_led = BuiltinLed::GetInstance();
// Try to connect to WiFi, if failed, launch the WiFi configuration AP
auto& wifi_station = WifiStation::GetInstance();
display.SetText(std::string("Connect to WiFi\n") + wifi_station.GetSsid());
wifi_station.Start();
if (!wifi_station.IsConnected()) {
application.Alert("Info", "Configuring WiFi");
builtin_led.SetBlue();
builtin_led.Blink(1000, 500);
auto& wifi_ap = WifiConfigurationAp::GetInstance();
wifi_ap.SetSsidPrefix("Xiaozhi");
wifi_ap.Start();
// Wait forever until reset after configuration
while (true) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
}
void WifiBoard::Initialize() {
ESP_LOGI(TAG, "Initializing WifiBoard");
}
Http* WifiBoard::CreateHttp() {
return new EspHttp();
}
WebSocket* WifiBoard::CreateWebSocket() {
std::string url = CONFIG_WEBSOCKET_URL;
if (url.find("wss://") == 0) {
return new WebSocket(new TlsTransport());
} else {
return new WebSocket(new TcpTransport());
}
}
bool WifiBoard::GetNetworkState(std::string& network_name, int& signal_quality, std::string& signal_quality_text) {
if (wifi_config_mode_) {
auto& wifi_ap = WifiConfigurationAp::GetInstance();
network_name = wifi_ap.GetSsid();
signal_quality = -99;
signal_quality_text = wifi_ap.GetWebServerUrl();
return true;
}
auto& wifi_station = WifiStation::GetInstance();
if (!wifi_station.IsConnected()) {
return false;
}
network_name = wifi_station.GetSsid();
signal_quality = wifi_station.GetRssi();
signal_quality_text = rssi_to_string(signal_quality);
return signal_quality != -1;
}
std::string WifiBoard::GetJson() {
// Set the board type for OTA
auto& wifi_station = WifiStation::GetInstance();
std::string board_type = BOARD_TYPE;
std::string board_json = std::string("{\"type\":\"" + board_type + "\",");
if (!wifi_config_mode_) {
board_json += "\"ssid\":\"" + wifi_station.GetSsid() + "\",";
board_json += "\"rssi\":" + std::to_string(wifi_station.GetRssi()) + ",";
board_json += "\"channel\":" + std::to_string(wifi_station.GetChannel()) + ",";
board_json += "\"ip\":\"" + wifi_station.GetIpAddress() + "\",";
}
board_json += "\"mac\":\"" + SystemInfo::GetMacAddress() + "\"}";
return board_json;
}

19
main/WifiBoard.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef WIFI_BOARD_H
#define WIFI_BOARD_H
#include "Board.h"
class WifiBoard : public Board {
protected:
bool wifi_config_mode_ = false;
public:
virtual void Initialize() override;
virtual void StartNetwork() override;
virtual Http* CreateHttp() override;
virtual WebSocket* CreateWebSocket() override;
virtual bool GetNetworkState(std::string& network_name, int& signal_quality, std::string& signal_quality_text) override;
virtual std::string GetJson() override;
};
#endif // WIFI_BOARD_H

BIN
main/assets/err_pin.p3 Normal file

Binary file not shown.

BIN
main/assets/err_reg.p3 Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,22 @@
#include "Ml307Board.h"
#include "SystemReset.h"
#include <esp_log.h>
#define TAG "CompactMl307Board"
class CompactMl307Board : public Ml307Board {
public:
virtual void Initialize() override {
ESP_LOGI(TAG, "Initializing CompactMl307Board");
// Check if the reset button is pressed
SystemReset::GetInstance().CheckButtons();
Ml307Board::Initialize();
}
virtual AudioDevice* CreateAudioDevice() override {
return new AudioDevice();
}
};
DECLARE_BOARD(CompactMl307Board);

View File

@@ -0,0 +1,46 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
#else
#define AUDIO_I2S_GPIO_LRCK GPIO_NUM_4
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
#endif
#define BUILTIN_LED_GPIO GPIO_NUM_48
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39
#define DISPLAY_SDA_PIN GPIO_NUM_41
#define DISPLAY_SCL_PIN GPIO_NUM_42
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 32
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define ML307_RX_PIN GPIO_NUM_11
#define ML307_TX_PIN GPIO_NUM_12
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,22 @@
#include "WifiBoard.h"
#include "SystemReset.h"
#include <esp_log.h>
#define TAG "CompactWifiBoard"
class CompactWifiBoard : public WifiBoard {
public:
virtual void Initialize() override {
ESP_LOGI(TAG, "Initializing CompactWifiBoard");
// Check if the reset button is pressed
SystemReset::GetInstance().CheckButtons();
WifiBoard::Initialize();
}
virtual AudioDevice* CreateAudioDevice() override {
return new AudioDevice();
}
};
DECLARE_BOARD(CompactWifiBoard);

View File

@@ -0,0 +1,42 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
#else
#define AUDIO_I2S_GPIO_LRCK GPIO_NUM_4
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
#endif
#define BUILTIN_LED_GPIO GPIO_NUM_48
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39
#define DISPLAY_SDA_PIN GPIO_NUM_41
#define DISPLAY_SCL_PIN GPIO_NUM_42
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 32
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,20 @@
#include "WifiBoard.h"
#include "BoxAudioDevice.h"
#include <esp_log.h>
#define TAG "EspBox3Board"
class EspBox3Board : public WifiBoard {
public:
virtual void Initialize() override {
ESP_LOGI(TAG, "Initializing EspBox3Board");
WifiBoard::Initialize();
}
virtual AudioDevice* CreateAudioDevice() override {
return new BoxAudioDevice();
}
};
DECLARE_BOARD(EspBox3Board);

View File

@@ -0,0 +1,36 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_2
#define AUDIO_I2S_GPIO_LRCK GPIO_NUM_45
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_16
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15
#define AUDIO_CODEC_PA_PIN GPIO_NUM_46
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
#define BUILTIN_LED_GPIO GPIO_NUM_NC
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
#define DISPLAY_SDA_PIN GPIO_NUM_NC
#define DISPLAY_SCL_PIN GPIO_NUM_NC
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 64
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,84 @@
#include "Ml307Board.h"
#include "BoxAudioDevice.h"
#include <esp_log.h>
#include <esp_spiffs.h>
#include <driver/gpio.h>
#include <esp_adc/adc_oneshot.h>
#include <esp_adc/adc_cali.h>
#include <esp_adc/adc_cali_scheme.h>
static const char *TAG = "KevinBoxBoard";
class KevinBoxBoard : public Ml307Board {
private:
adc_oneshot_unit_handle_t adc1_handle_;
adc_cali_handle_t adc1_cali_handle_;
void MountStorage() {
// Mount the storage partition
esp_vfs_spiffs_conf_t conf = {
.base_path = "/storage",
.partition_label = "storage",
.max_files = 5,
.format_if_mount_failed = true,
};
esp_vfs_spiffs_register(&conf);
}
void Enable4GModule() {
// Make GPIO15 HIGH to enable the 4G module
gpio_config_t ml307_enable_config = {
.pin_bit_mask = (1ULL << 15),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&ml307_enable_config);
gpio_set_level(GPIO_NUM_15, 1);
}
virtual void InitializeADC() {
adc_oneshot_unit_init_cfg_t init_config1 = {};
init_config1.unit_id = ADC_UNIT_1;
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle_));
//-------------ADC1 Config---------------//
adc_oneshot_chan_cfg_t config = {
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle_, ADC_CHANNEL_0, &config));
adc_cali_curve_fitting_config_t cali_config = {
.unit_id = ADC_UNIT_1,
.chan = ADC_CHANNEL_0,
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ESP_ERROR_CHECK(adc_cali_create_scheme_curve_fitting(&cali_config, &adc1_cali_handle_));
}
public:
virtual void Initialize() override {
ESP_LOGI(TAG, "Initializing KevinBoxBoard");
InitializeADC();
MountStorage();
Enable4GModule();
Ml307Board::Initialize();
}
virtual AudioDevice* CreateAudioDevice() override {
return new BoxAudioDevice();
}
virtual bool GetBatteryVoltage(int &voltage, bool& charging) override {
int adc_reading;
ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle_, ADC_CHANNEL_0, &adc_reading));
ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc1_cali_handle_, adc_reading, &voltage));
charging = false;
return true;
}
};
DECLARE_BOARD(KevinBoxBoard);

View File

@@ -0,0 +1,39 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_0
#define AUDIO_I2S_GPIO_LRCK GPIO_NUM_47
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_48
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_45
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_21
#define AUDIO_CODEC_PA_PIN GPIO_NUM_40
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_39
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_38
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
#define BUILTIN_LED_GPIO GPIO_NUM_8
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_6
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_7
#define DISPLAY_SDA_PIN GPIO_NUM_4
#define DISPLAY_SCL_PIN GPIO_NUM_5
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 64
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define ML307_RX_PIN GPIO_NUM_17
#define ML307_TX_PIN GPIO_NUM_16
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,93 @@
#include "Ml307Board.h"
#include "BoxAudioDevice.h"
#include <esp_log.h>
#include <esp_spiffs.h>
#include <driver/gpio.h>
#include <esp_adc/adc_oneshot.h>
#include <esp_adc/adc_cali.h>
#include <esp_adc/adc_cali_scheme.h>
static const char *TAG = "KevinBoxBoard";
class KevinBoxBoard : public Ml307Board {
private:
adc_oneshot_unit_handle_t adc1_handle_;
adc_cali_handle_t adc1_cali_handle_;
void MountStorage() {
// Mount the storage partition
esp_vfs_spiffs_conf_t conf = {
.base_path = "/storage",
.partition_label = "storage",
.max_files = 5,
.format_if_mount_failed = true,
};
esp_vfs_spiffs_register(&conf);
}
void Enable4GModule() {
// Make GPIO15 HIGH to enable the 4G module
gpio_config_t ml307_enable_config = {
.pin_bit_mask = (1ULL << 15),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&ml307_enable_config);
gpio_set_level(GPIO_NUM_15, 1);
}
virtual void InitializeADC() {
adc_oneshot_unit_init_cfg_t init_config1 = {};
init_config1.unit_id = ADC_UNIT_1;
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle_));
//-------------ADC1 Config---------------//
adc_oneshot_chan_cfg_t config = {
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle_, ADC_CHANNEL_0, &config));
adc_cali_curve_fitting_config_t cali_config = {
.unit_id = ADC_UNIT_1,
.chan = ADC_CHANNEL_0,
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ESP_ERROR_CHECK(adc_cali_create_scheme_curve_fitting(&cali_config, &adc1_cali_handle_));
}
public:
virtual void Initialize() override {
ESP_LOGI(TAG, "Initializing KevinBoxBoard");
InitializeADC();
MountStorage();
Enable4GModule();
gpio_config_t charging_io = {
.pin_bit_mask = (1ULL << 2),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&charging_io);
Ml307Board::Initialize();
}
virtual AudioDevice* CreateAudioDevice() override {
return new BoxAudioDevice();
}
virtual bool GetBatteryVoltage(int &voltage, bool& charging) override {
ESP_ERROR_CHECK(adc_oneshot_get_calibrated_result(adc1_handle_, adc1_cali_handle_, ADC_CHANNEL_0, &voltage));
charging = gpio_get_level(GPIO_NUM_2) == 0;
ESP_LOGI(TAG, "Battery voltage: %d, Charging: %d", voltage, charging);
return true;
}
};
DECLARE_BOARD(KevinBoxBoard);

View File

@@ -0,0 +1,39 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_42
#define AUDIO_I2S_GPIO_LRCK GPIO_NUM_47
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_48
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_45
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_21
#define AUDIO_CODEC_PA_PIN GPIO_NUM_17
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_39
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_38
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
#define BUILTIN_LED_GPIO GPIO_NUM_8
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_6
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_7
#define DISPLAY_SDA_PIN GPIO_NUM_4
#define DISPLAY_SCL_PIN GPIO_NUM_5
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 64
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define ML307_RX_PIN GPIO_NUM_20
#define ML307_TX_PIN GPIO_NUM_19
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,20 @@
#include "WifiBoard.h"
#include "BoxAudioDevice.h"
#include <esp_log.h>
#define TAG "LiChuangDevBoard"
class LiChuangDevBoard : public WifiBoard {
public:
virtual void Initialize() override {
ESP_LOGI(TAG, "Initializing LiChuangDevBoard");
WifiBoard::Initialize();
}
virtual AudioDevice* CreateAudioDevice() override {
return new BoxAudioDevice();
}
};
DECLARE_BOARD(LiChuangDevBoard);

View File

@@ -0,0 +1,37 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
#define AUDIO_I2S_GPIO_LRCK GPIO_NUM_13
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_14
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_12
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_45
#define AUDIO_CODEC_USE_PCA9557
#define AUDIO_CODEC_PA_PIN GPIO_NUM_40
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_1
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_2
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_ES7210_ADDR 0x82
#define BUILTIN_LED_GPIO GPIO_NUM_48
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
#define DISPLAY_SDA_PIN GPIO_NUM_NC
#define DISPLAY_SCL_PIN GPIO_NUM_NC
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 64
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#endif // _BOARD_CONFIG_H_

View File

@@ -1,14 +1,14 @@
## IDF Component Manager Manifest File
dependencies:
78/esp-builtin-led: "^1.0.2"
78/esp-wifi-connect: "^1.2.0"
78/esp-opus-encoder: "^1.0.2"
78/esp-ml307: "^1.2.1"
78/esp-wifi-connect: "~1.2.0"
78/esp-opus-encoder: "~1.0.2"
78/esp-ml307: "~1.3.0"
espressif/led_strip: "^2.4.1"
espressif/esp_codec_dev: "^1.3.1"
espressif/esp-sr: "^1.9.0"
espressif/button: "^3.3.1"
lvgl/lvgl: "^8.4.0"
esp_lvgl_port: "^1.4.0"
lvgl/lvgl: "~8.4.0"
esp_lvgl_port: "~2.4.1"
## Required IDF version
idf:
version: ">=5.3"

View File

@@ -7,29 +7,11 @@
#include "Application.h"
#include "SystemInfo.h"
#include "SystemReset.h"
#define TAG "main"
extern "C" void app_main(void)
{
#ifdef CONFIG_AUDIO_CODEC_ES8311_ES7210
// Make GPIO15 HIGH to enable the 4G module
gpio_config_t ml307_enable_config = {
.pin_bit_mask = (1ULL << 15),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&ml307_enable_config);
gpio_set_level(GPIO_NUM_15, 1);
#endif
// Check if the reset button is pressed
SystemReset system_reset;
system_reset.CheckButtons();
// Initialize the default event loop
ESP_ERROR_CHECK(esp_event_loop_create_default());

View File

@@ -4,6 +4,7 @@ nvs, data, nvs, 0x9000, 0x4000,
otadata, data, ota, 0xd000, 0x2000,
phy_init, data, phy, 0xf000, 0x1000,
model, data, spiffs, 0x10000, 0xF0000,
storage, data, spiffs, 0x100000, 1M,
factory, app, factory, 0x200000, 4M,
ota_0, app, ota_0, 0x600000, 4M,
ota_1, app, ota_1, 0xA00000, 4M,
1 # ESP-IDF Partition Table
4 otadata, data, ota, 0xd000, 0x2000,
5 phy_init, data, phy, 0xf000, 0x1000,
6 model, data, spiffs, 0x10000, 0xF0000,
7 storage, data, spiffs, 0x100000, 1M,
8 factory, app, factory, 0x200000, 4M,
9 ota_0, app, ota_0, 0x600000, 4M,
10 ota_1, app, ota_1, 0xA00000, 4M,

File diff suppressed because it is too large Load Diff

View File

@@ -56,12 +56,12 @@ def get_app_desc(data):
def get_board_name(folder):
basename = os.path.basename(folder)
if basename.startswith("v0.2"):
return "simple"
if basename.startswith("v0.3") or basename.startswith("v0.4"):
return "bread-simple"
if basename.startswith("v0.3") or basename.startswith("v0.4") or basename.startswith("v0.5") or basename.startswith("v0.6"):
if "ML307" in basename:
return "compact.4g"
return "bread-compact-ml307"
else:
return "compact.wifi"
return "bread-compact-wifi"
raise Exception(f"Unknown board name: {basename}")
def read_binary(dir_path):