mirror of
https://github.com/78/xiaozhi-esp32.git
synced 2026-02-16 17:08:07 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20deb2b777 | ||
|
|
3575448373 | ||
|
|
55ff4e1f74 | ||
|
|
82030d003d | ||
|
|
458ac2c999 | ||
|
|
bc4b0a0bb1 | ||
|
|
8fccef2c52 | ||
|
|
92efdc9b64 | ||
|
|
5006e5bda1 | ||
|
|
91c7774117 | ||
|
|
b0bc81b921 | ||
|
|
a701d5918e | ||
|
|
6f5f5a0642 | ||
|
|
3e1e576272 | ||
|
|
33518dca2b |
@@ -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.7.1")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(xiaozhi)
|
||||
|
||||
40
convert_audio_to_p3.py
Normal file
40
convert_audio_to_p3.py
Normal 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)
|
||||
2
flash.sh
Executable file
2
flash.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
esptool.py -p /dev/ttyACM0 -b 2000000 write_flash 0 releases/v0.6.2_ML307/merged-binary.bin
|
||||
@@ -1,36 +0,0 @@
|
||||
#ifndef _BOX_AUDIO_DEVICE_H
|
||||
#define _BOX_AUDIO_DEVICE_H
|
||||
|
||||
#include "AudioDevice.h"
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/i2s_tdm.h>
|
||||
#include <esp_codec_dev.h>
|
||||
#include <esp_codec_dev_defaults.h>
|
||||
|
||||
|
||||
class BoxAudioDevice : public AudioDevice {
|
||||
public:
|
||||
BoxAudioDevice();
|
||||
virtual ~BoxAudioDevice();
|
||||
void Initialize() override;
|
||||
void SetOutputVolume(int volume) override;
|
||||
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_master_handle_ = nullptr;
|
||||
|
||||
const audio_codec_data_if_t* data_if_ = nullptr;
|
||||
const audio_codec_ctrl_if_t* out_ctrl_if_ = nullptr;
|
||||
const audio_codec_if_t* out_codec_if_ = nullptr;
|
||||
const audio_codec_ctrl_if_t* in_ctrl_if_ = nullptr;
|
||||
const audio_codec_if_t* in_codec_if_ = nullptr;
|
||||
const audio_codec_gpio_if_t* gpio_if_ = nullptr;
|
||||
|
||||
esp_codec_dev_handle_t output_dev_ = nullptr;
|
||||
esp_codec_dev_handle_t input_dev_ = nullptr;
|
||||
|
||||
void CreateDuplexChannels() override;
|
||||
int Read(int16_t* dest, int samples) override;
|
||||
int Write(const int16_t* data, int samples) override;
|
||||
};
|
||||
|
||||
#endif // _BOX_AUDIO_DEVICE_H
|
||||
@@ -1,20 +1,50 @@
|
||||
set(SOURCES "AudioDevice.cc"
|
||||
"FirmwareUpgrade.cc"
|
||||
"SystemInfo.cc"
|
||||
"SystemReset.cc"
|
||||
"Application.cc"
|
||||
"Display.cc"
|
||||
"Button.cc"
|
||||
set(SOURCES "audio_codec.cc"
|
||||
"audio_codecs/no_audio_codec.cc"
|
||||
"audio_codecs/box_audio_codec.cc"
|
||||
"display.cc"
|
||||
"display/no_display.cc"
|
||||
"display/st7789_display.cc"
|
||||
"display/ssd1306_display.cc"
|
||||
"board.cc"
|
||||
"boards/wifi_board.cc"
|
||||
"boards/ml307_board.cc"
|
||||
"system_info.cc"
|
||||
"system_reset.cc"
|
||||
"application.cc"
|
||||
"button.cc"
|
||||
"led.cc"
|
||||
"ota.cc"
|
||||
"main.cc"
|
||||
)
|
||||
set(INCLUDE_DIRS ".")
|
||||
|
||||
# 根据 BOARD_TYPE 配置添加对应的板级文件
|
||||
if(CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI)
|
||||
set(BOARD_TYPE "bread-compact-wifi")
|
||||
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ML307)
|
||||
set(BOARD_TYPE "bread-compact-ml307")
|
||||
elseif(CONFIG_BOARD_TYPE_ESP_BOX_3)
|
||||
set(BOARD_TYPE "esp-box-3")
|
||||
elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_0)
|
||||
set(BOARD_TYPE "kevin-box-0")
|
||||
elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_1)
|
||||
set(BOARD_TYPE "kevin-box-1")
|
||||
elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV)
|
||||
set(BOARD_TYPE "lichuang-dev")
|
||||
endif()
|
||||
file(GLOB BOARD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc)
|
||||
list(APPEND SOURCES ${BOARD_SOURCES})
|
||||
|
||||
if(CONFIG_USE_AFE_SR)
|
||||
list(APPEND SOURCES "AudioProcessor.cc" "WakeWordDetect.cc")
|
||||
endif()
|
||||
if(CONFIG_AUDIO_CODEC_ES8311_ES7210)
|
||||
list(APPEND SOURCES "BoxAudioDevice.cc")
|
||||
list(APPEND SOURCES "audio_processor.cc" "wake_word_detect.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}\"
|
||||
)
|
||||
|
||||
179
main/Display.cc
179
main/Display.cc
@@ -1,179 +0,0 @@
|
||||
|
||||
#include "Display.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <esp_lvgl_port.h>
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
|
||||
#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_);
|
||||
|
||||
i2c_master_bus_config_t bus_config = {
|
||||
.i2c_port = I2C_NUM_1,
|
||||
.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,
|
||||
.trans_queue_depth = 0,
|
||||
.flags = {
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &i2c_bus_));
|
||||
|
||||
// SSD1306 config
|
||||
esp_lcd_panel_io_i2c_config_t io_config = {
|
||||
.dev_addr = 0x3C,
|
||||
.on_color_trans_done = nullptr,
|
||||
.user_ctx = nullptr,
|
||||
.control_phase_bytes = 1,
|
||||
.dc_bit_offset = 6,
|
||||
.lcd_cmd_bits = 8,
|
||||
.lcd_param_bits = 8,
|
||||
.flags = {
|
||||
.dc_low_on_data = 0,
|
||||
.disable_control_phase = 0,
|
||||
},
|
||||
.scl_speed_hz = 400 * 1000,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(i2c_bus_, &io_config, &panel_io_));
|
||||
|
||||
ESP_LOGI(TAG, "Install SSD1306 driver");
|
||||
esp_lcd_panel_dev_config_t panel_config = {};
|
||||
panel_config.reset_gpio_num = -1;
|
||||
panel_config.bits_per_pixel = 1;
|
||||
|
||||
esp_lcd_panel_ssd1306_config_t ssd1306_config = {
|
||||
.height = CONFIG_DISPLAY_HEIGHT
|
||||
};
|
||||
panel_config.vendor_config = &ssd1306_config;
|
||||
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
|
||||
ESP_LOGI(TAG, "SSD1306 driver installed");
|
||||
|
||||
// Reset the display
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
|
||||
if (esp_lcd_panel_init(panel_) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize display");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initialize LVGL");
|
||||
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, "Display Loading...");
|
||||
if (lvgl_port_lock(0)) {
|
||||
label_ = lv_label_create(lv_disp_get_scr_act(disp_));
|
||||
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_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();
|
||||
}
|
||||
}
|
||||
|
||||
Display::~Display() {
|
||||
if (notification_timer_ != nullptr) {
|
||||
esp_timer_stop(notification_timer_);
|
||||
esp_timer_delete(notification_timer_);
|
||||
}
|
||||
|
||||
lvgl_port_lock(0);
|
||||
if (label_ != nullptr) {
|
||||
lv_obj_del(label_);
|
||||
lv_obj_del(notification_);
|
||||
}
|
||||
lvgl_port_unlock();
|
||||
|
||||
if (disp_ != nullptr) {
|
||||
lvgl_port_deinit();
|
||||
esp_lcd_panel_del(panel_);
|
||||
esp_lcd_panel_io_del(panel_io_);
|
||||
i2c_master_bus_reset(i2c_bus_);
|
||||
}
|
||||
}
|
||||
|
||||
void Display::SetText(const std::string &text) {
|
||||
if (label_ != nullptr) {
|
||||
text_ = text;
|
||||
lvgl_port_lock(0);
|
||||
// Change the text of the label
|
||||
lv_label_set_text(label_, text_.c_str());
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void Display::ShowNotification(const std::string &text) {
|
||||
if (notification_ != nullptr) {
|
||||
lvgl_port_lock(0);
|
||||
lv_label_set_text(notification_, text.c_str());
|
||||
lv_obj_set_style_opa(notification_, LV_OPA_MAX, 0);
|
||||
lv_obj_set_style_opa(label_, LV_OPA_MIN, 0);
|
||||
lvgl_port_unlock();
|
||||
|
||||
if (notification_timer_ != nullptr) {
|
||||
esp_timer_stop(notification_timer_);
|
||||
esp_timer_delete(notification_timer_);
|
||||
}
|
||||
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = [](void *arg) {
|
||||
Display *display = static_cast<Display*>(arg);
|
||||
lvgl_port_lock(0);
|
||||
lv_obj_set_style_opa(display->notification_, LV_OPA_MIN, 0);
|
||||
lv_obj_set_style_opa(display->label_, LV_OPA_MAX, 0);
|
||||
lvgl_port_unlock();
|
||||
},
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "Notification Timer",
|
||||
.skip_unhandled_events = false,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&timer_args, ¬ification_timer_));
|
||||
ESP_ERROR_CHECK(esp_timer_start_once(notification_timer_, 3000000));
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -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
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
#ifndef _SYSTEM_RESET_H
|
||||
#define _SYSTEM_RESET_H
|
||||
|
||||
class SystemReset {
|
||||
public:
|
||||
SystemReset();
|
||||
|
||||
void CheckButtons();
|
||||
|
||||
private:
|
||||
void ResetNvsFlash();
|
||||
void ResetToFactory();
|
||||
void RestartInSeconds(int seconds);
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,57 +1,47 @@
|
||||
#include <BuiltinLed.h>
|
||||
#include <TcpTransport.h>
|
||||
#include <TlsTransport.h>
|
||||
#include <Ml307SslTransport.h>
|
||||
#include <WifiConfigurationAp.h>
|
||||
#include <WifiStation.h>
|
||||
#include <SystemInfo.h>
|
||||
#include "application.h"
|
||||
#include "system_info.h"
|
||||
#include "ml307_ssl_transport.h"
|
||||
#include "audio_codec.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <esp_log.h>
|
||||
#include <cJSON.h>
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include <arpa/inet.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_)
|
||||
{
|
||||
|
||||
Application::Application() {
|
||||
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 (16000 != CONFIG_AUDIO_INPUT_SAMPLE_RATE) {
|
||||
input_resampler_.Configure(CONFIG_AUDIO_INPUT_SAMPLE_RATE, 16000);
|
||||
}
|
||||
|
||||
firmware_upgrade_.SetCheckVersionUrl(CONFIG_OTA_VERSION_URL);
|
||||
firmware_upgrade_.SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str());
|
||||
ota_.SetCheckVersionUrl(CONFIG_OTA_VERSION_URL);
|
||||
ota_.SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str());
|
||||
}
|
||||
|
||||
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_);
|
||||
}
|
||||
|
||||
vEventGroupDelete(event_group_);
|
||||
@@ -59,174 +49,132 @@ Application::~Application() {
|
||||
|
||||
void Application::CheckNewVersion() {
|
||||
// Check if there is a new firmware version available
|
||||
firmware_upgrade_.CheckVersion();
|
||||
if (firmware_upgrade_.HasNewVersion()) {
|
||||
ota_.SetPostData(Board::GetInstance().GetJson());
|
||||
ota_.CheckVersion();
|
||||
if (ota_.HasNewVersion()) {
|
||||
// Wait for the chat state to be idle
|
||||
while (chat_state_ != kChatStateIdle) {
|
||||
vTaskDelay(100);
|
||||
}
|
||||
SetChatState(kChatStateUpgrading);
|
||||
firmware_upgrade_.StartUpgrade([this](int progress, size_t speed) {
|
||||
#ifdef CONFIG_USE_DISPLAY
|
||||
ota_.StartUpgrade([](int progress, size_t speed) {
|
||||
char buffer[64];
|
||||
snprintf(buffer, sizeof(buffer), "Upgrading...\n %d%% %zuKB/s", progress, speed / 1024);
|
||||
display_.SetText(buffer);
|
||||
#endif
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
display->SetText(buffer);
|
||||
});
|
||||
// If upgrade success, the device will reboot and never reach here
|
||||
ESP_LOGI(TAG, "Firmware upgrade failed...");
|
||||
SetChatState(kChatStateIdle);
|
||||
} else {
|
||||
firmware_upgrade_.MarkCurrentVersionValid();
|
||||
ota_.MarkCurrentVersionValid();
|
||||
}
|
||||
}
|
||||
|
||||
#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());
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
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) {
|
||||
void Application::PlayLocalFile(const char* data, size_t size) {
|
||||
ESP_LOGI(TAG, "PlayLocalFile: %zu bytes", size);
|
||||
SetDecodeSampleRate(16000);
|
||||
auto codec = Board::GetInstance().GetAudioCodec();
|
||||
codec->EnableOutput(true);
|
||||
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::ToggleChatState() {
|
||||
Schedule([this]() {
|
||||
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";
|
||||
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 {
|
||||
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) + ")");
|
||||
SetChatState(kChatStateIdle);
|
||||
}
|
||||
} else if (chat_state_ == kChatStateSpeaking) {
|
||||
AbortSpeaking();
|
||||
} else if (chat_state_ == kChatStateListening) {
|
||||
if (ws_client_ && ws_client_->IsConnected()) {
|
||||
ws_client_->Close();
|
||||
}
|
||||
#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));
|
||||
}
|
||||
});
|
||||
}
|
||||
#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();
|
||||
auto& board = Board::GetInstance();
|
||||
board.Initialize();
|
||||
|
||||
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 builtin_led = board.GetBuiltinLed();
|
||||
builtin_led->SetBlue();
|
||||
builtin_led->StartContinuousBlink(100);
|
||||
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
auto display = board.GetDisplay();
|
||||
display->SetupUI();
|
||||
|
||||
// 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;
|
||||
auto codec = board.GetAudioCodec();
|
||||
opus_decode_sample_rate_ = codec->output_sample_rate();
|
||||
opus_decoder_ = opus_decoder_create(opus_decode_sample_rate_, 1, NULL);
|
||||
opus_encoder_.Configure(codec->input_sample_rate(), 1);
|
||||
if (codec->input_sample_rate() != 16000) {
|
||||
input_resampler_.Configure(codec->input_sample_rate(), 16000);
|
||||
reference_resampler_.Configure(codec->input_sample_rate(), 16000);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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) {
|
||||
left_channel[i] = data[j];
|
||||
right_channel[i] = data[j + 1];
|
||||
codec->EnableInput(true);
|
||||
codec->EnableOutput(true);
|
||||
codec->EnableOutput(false);
|
||||
codec->OnInputData([this, codec](std::vector<int16_t>&& data) {
|
||||
if (codec->input_sample_rate() != 16000) {
|
||||
if (codec->input_channels() == 2) {
|
||||
auto mic_channel = std::vector<int16_t>(data.size() / 2);
|
||||
auto reference_channel = std::vector<int16_t>(data.size() / 2);
|
||||
for (size_t i = 0, j = 0; i < mic_channel.size(); ++i, j += 2) {
|
||||
mic_channel[i] = data[j];
|
||||
reference_channel[i] = data[j + 1];
|
||||
}
|
||||
auto resampled_left = std::vector<int16_t>(input_resampler_.GetOutputSamples(left_channel.size()));
|
||||
auto resampled_right = std::vector<int16_t>(input_resampler_.GetOutputSamples(right_channel.size()));
|
||||
input_resampler_.Process(left_channel.data(), left_channel.size(), resampled_left.data());
|
||||
input_resampler_.Process(right_channel.data(), right_channel.size(), resampled_right.data());
|
||||
data.resize(resampled_left.size() + resampled_right.size());
|
||||
for (size_t i = 0, j = 0; i < resampled_left.size(); ++i, j += 2) {
|
||||
data[j] = resampled_left[i];
|
||||
data[j + 1] = resampled_right[i];
|
||||
auto resampled_mic = std::vector<int16_t>(input_resampler_.GetOutputSamples(mic_channel.size()));
|
||||
auto resampled_reference = std::vector<int16_t>(reference_resampler_.GetOutputSamples(reference_channel.size()));
|
||||
input_resampler_.Process(mic_channel.data(), mic_channel.size(), resampled_mic.data());
|
||||
reference_resampler_.Process(reference_channel.data(), reference_channel.size(), resampled_reference.data());
|
||||
data.resize(resampled_mic.size() + resampled_reference.size());
|
||||
for (size_t i = 0, j = 0; i < resampled_mic.size(); ++i, j += 2) {
|
||||
data[j] = resampled_mic[i];
|
||||
data[j + 1] = resampled_reference[i];
|
||||
}
|
||||
} else {
|
||||
auto resampled = std::vector<int16_t>(input_resampler_.GetOutputSamples(data.size()));
|
||||
@@ -254,7 +202,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,18 +215,38 @@ 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();
|
||||
|
||||
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_.Initialize(codec->input_channels(), codec->input_reference());
|
||||
wake_word_detect_.OnVadStateChange([this](bool speaking) {
|
||||
Schedule([this, speaking]() {
|
||||
auto& builtin_led = BuiltinLed::GetInstance();
|
||||
auto builtin_led = Board::GetInstance().GetBuiltinLed();
|
||||
if (chat_state_ == kChatStateListening) {
|
||||
if (speaking) {
|
||||
builtin_led.SetRed(32);
|
||||
builtin_led->SetRed(32);
|
||||
} else {
|
||||
builtin_led.SetRed(8);
|
||||
builtin_led->SetRed(8);
|
||||
}
|
||||
builtin_led.TurnOn();
|
||||
builtin_led->TurnOn();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -308,7 +276,7 @@ void Application::Start() {
|
||||
SetChatState(kChatStateIdle);
|
||||
}
|
||||
} else if (chat_state_ == kChatStateSpeaking) {
|
||||
break_speaking_ = true;
|
||||
AbortSpeaking();
|
||||
}
|
||||
|
||||
// Resume detection
|
||||
@@ -317,7 +285,7 @@ void Application::Start() {
|
||||
});
|
||||
wake_word_detect_.StartDetection();
|
||||
|
||||
audio_processor_.Initialize(audio_device_.input_channels(), audio_device_.input_reference());
|
||||
audio_processor_.Initialize(codec->input_channels(), codec->input_reference());
|
||||
audio_processor_.OnOutput([this](std::vector<int16_t>&& data) {
|
||||
Schedule([this, data = std::move(data)]() {
|
||||
if (chat_state_ == kChatStateListening) {
|
||||
@@ -329,101 +297,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
|
||||
chat_state_ = kChatStateIdle;
|
||||
display->UpdateDisplay();
|
||||
}
|
||||
|
||||
void Application::Schedule(std::function<void()> callback) {
|
||||
@@ -448,8 +323,23 @@ 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);
|
||||
ws_client_->Send(json);
|
||||
cJSON_Delete(root);
|
||||
free(json);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::SetChatState(ChatState state) {
|
||||
const char* state_str[] = {
|
||||
"unknown",
|
||||
"idle",
|
||||
"connecting",
|
||||
"listening",
|
||||
@@ -457,35 +347,45 @@ 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();
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
auto builtin_led = Board::GetInstance().GetBuiltinLed();
|
||||
switch (chat_state_) {
|
||||
case kChatStateUnknown:
|
||||
case kChatStateIdle:
|
||||
builtin_led.TurnOff();
|
||||
builtin_led->TurnOff();
|
||||
display->SetText("I'm\nIdle.");
|
||||
break;
|
||||
case kChatStateConnecting:
|
||||
builtin_led.SetBlue();
|
||||
builtin_led.TurnOn();
|
||||
builtin_led->SetBlue();
|
||||
builtin_led->TurnOn();
|
||||
display->SetText("I'm\nConnecting...");
|
||||
break;
|
||||
case kChatStateListening:
|
||||
builtin_led.SetRed();
|
||||
builtin_led.TurnOn();
|
||||
builtin_led->SetRed();
|
||||
builtin_led->TurnOn();
|
||||
display->SetText("I'm\nListening...");
|
||||
break;
|
||||
case kChatStateSpeaking:
|
||||
builtin_led.SetGreen();
|
||||
builtin_led.TurnOn();
|
||||
builtin_led->SetGreen();
|
||||
builtin_led->TurnOn();
|
||||
display->SetText("I'm\nSpeaking...");
|
||||
break;
|
||||
case kChatStateWakeWordDetected:
|
||||
builtin_led.SetBlue();
|
||||
builtin_led.TurnOn();
|
||||
builtin_led->SetBlue();
|
||||
builtin_led->TurnOn();
|
||||
break;
|
||||
case kChatStateUpgrading:
|
||||
builtin_led.SetGreen();
|
||||
builtin_led.StartContinuousBlink(100);
|
||||
builtin_led->SetGreen();
|
||||
builtin_led->StartContinuousBlink(100);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -494,30 +394,27 @@ void Application::SetChatState(ChatState state) {
|
||||
cJSON_AddStringToObject(root, "type", "state");
|
||||
cJSON_AddStringToObject(root, "state", state_str[chat_state_]);
|
||||
char* json = cJSON_PrintUnformatted(root);
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
ws_client_->Send(json);
|
||||
cJSON_Delete(root);
|
||||
free(json);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
assert(protocol != nullptr);
|
||||
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;
|
||||
}
|
||||
|
||||
void Application::AudioEncodeTask() {
|
||||
ESP_LOGI(TAG, "Audio encode task started");
|
||||
const int max_audio_play_queue_size_ = 2;
|
||||
const int max_audio_play_queue_size_ = 2; // avoid decoding too fast
|
||||
auto codec = Board::GetInstance().GetAudioCodec();
|
||||
|
||||
while (true) {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
@@ -532,10 +429,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 +444,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_ != codec->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 +479,8 @@ 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);
|
||||
}
|
||||
auto codec = Board::GetInstance().GetAudioCodec();
|
||||
codec->OutputData(packet->pcm);
|
||||
break;
|
||||
}
|
||||
case kAudioPacketTypeStart:
|
||||
@@ -602,13 +492,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 +540,28 @@ 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);
|
||||
|
||||
auto codec = Board::GetInstance().GetAudioCodec();
|
||||
if (opus_decode_sample_rate_ != codec->output_sample_rate()) {
|
||||
ESP_LOGI(TAG, "Resampling audio from %d to %d", opus_decode_sample_rate_, codec->output_sample_rate());
|
||||
output_resampler_.Configure(opus_decode_sample_rate_, codec->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 +573,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 +593,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 +610,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;
|
||||
@@ -754,6 +651,8 @@ void Application::StartWebSocketClient() {
|
||||
ws_client_->OnDisconnected([this]() {
|
||||
ESP_LOGI(TAG, "Websocket disconnected");
|
||||
Schedule([this]() {
|
||||
auto codec = Board::GetInstance().GetAudioCodec();
|
||||
codec->EnableOutput(false);
|
||||
#ifdef CONFIG_USE_AFE_SR
|
||||
audio_processor_.Stop();
|
||||
#endif
|
||||
@@ -767,4 +666,8 @@ void Application::StartWebSocketClient() {
|
||||
ESP_LOGE(TAG, "Failed to connect to websocket server");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 建立语音通道后打开音频输出,避免待机时喇叭底噪
|
||||
auto codec = Board::GetInstance().GetAudioCodec();
|
||||
codec->EnableOutput(true);
|
||||
}
|
||||
@@ -1,42 +1,35 @@
|
||||
#ifndef _APPLICATION_H_
|
||||
#define _APPLICATION_H_
|
||||
|
||||
#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>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <freertos/task.h>
|
||||
#include <opus.h>
|
||||
#include <mutex>
|
||||
#include <list>
|
||||
#include <condition_variable>
|
||||
|
||||
#include "BoxAudioDevice.h"
|
||||
#include "Display.h"
|
||||
#include "FirmwareUpgrade.h"
|
||||
#include "opus_encoder.h"
|
||||
#include "opus_resampler.h"
|
||||
#include <web_socket.h>
|
||||
|
||||
#include "display.h"
|
||||
#include "board.h"
|
||||
#include "ota.h"
|
||||
|
||||
#ifdef CONFIG_USE_AFE_SR
|
||||
#include "WakeWordDetect.h"
|
||||
#include "AudioProcessor.h"
|
||||
#include "wake_word_detect.h"
|
||||
#include "audio_processor.h"
|
||||
#endif
|
||||
|
||||
#include "Button.h"
|
||||
|
||||
#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 +52,7 @@ struct AudioPacket {
|
||||
|
||||
|
||||
enum ChatState {
|
||||
kChatStateUnknown,
|
||||
kChatStateIdle,
|
||||
kChatStateConnecting,
|
||||
kChatStateListening,
|
||||
@@ -75,7 +69,12 @@ public:
|
||||
}
|
||||
|
||||
void Start();
|
||||
|
||||
ChatState GetChatState() const { return chat_state_; }
|
||||
void Schedule(std::function<void()> callback);
|
||||
void SetChatState(ChatState state);
|
||||
void Alert(const std::string&& title, const std::string&& message);
|
||||
void AbortSpeaking();
|
||||
void ToggleChatState();
|
||||
// 删除拷贝构造函数和赋值运算符
|
||||
Application(const Application&) = delete;
|
||||
Application& operator=(const Application&) = delete;
|
||||
@@ -84,36 +83,20 @@ private:
|
||||
Application();
|
||||
~Application();
|
||||
|
||||
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
|
||||
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_;
|
||||
Ota ota_;
|
||||
std::mutex mutex_;
|
||||
std::condition_variable_any cv_;
|
||||
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 +110,26 @@ 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_ = -1;
|
||||
OpusResampler input_resampler_;
|
||||
OpusResampler reference_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_
|
||||
BIN
main/assets/err_pin.p3
Normal file
BIN
main/assets/err_pin.p3
Normal file
Binary file not shown.
BIN
main/assets/err_reg.p3
Normal file
BIN
main/assets/err_reg.p3
Normal file
Binary file not shown.
BIN
main/assets/err_wificonfig.p3
Normal file
BIN
main/assets/err_wificonfig.p3
Normal file
Binary file not shown.
67
main/audio_codec.cc
Normal file
67
main/audio_codec.cc
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "audio_codec.h"
|
||||
#include "board.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <cstring>
|
||||
|
||||
#define TAG "AudioCodec"
|
||||
|
||||
AudioCodec::AudioCodec() {
|
||||
}
|
||||
|
||||
AudioCodec::~AudioCodec() {
|
||||
if (audio_input_task_ != nullptr) {
|
||||
vTaskDelete(audio_input_task_);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioCodec::OnInputData(std::function<void(std::vector<int16_t>&& data)> callback) {
|
||||
on_input_data_ = callback;
|
||||
|
||||
// 创建音频输入任务
|
||||
if (audio_input_task_ == nullptr) {
|
||||
xTaskCreate([](void* arg) {
|
||||
auto audio_device = (AudioCodec*)arg;
|
||||
audio_device->InputTask();
|
||||
}, "audio_input", 4096 * 2, this, 3, &audio_input_task_);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioCodec::OutputData(std::vector<int16_t>& data) {
|
||||
Write(data.data(), data.size());
|
||||
}
|
||||
|
||||
void AudioCodec::InputTask() {
|
||||
int duration = 30;
|
||||
int input_frame_size = input_sample_rate_ / 1000 * duration * input_channels_;
|
||||
while (true) {
|
||||
std::vector<int16_t> input_data(input_frame_size);
|
||||
int samples = Read(input_data.data(), input_data.size());
|
||||
if (samples > 0) {
|
||||
if (on_input_data_) {
|
||||
on_input_data_(std::move(input_data));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioCodec::SetOutputVolume(int volume) {
|
||||
output_volume_ = volume;
|
||||
ESP_LOGI(TAG, "Set output volume to %d", output_volume_);
|
||||
}
|
||||
|
||||
void AudioCodec::EnableInput(bool enable) {
|
||||
if (enable == input_enabled_) {
|
||||
return;
|
||||
}
|
||||
input_enabled_ = enable;
|
||||
ESP_LOGI(TAG, "Set input enable to %s", enable ? "true" : "false");
|
||||
}
|
||||
|
||||
void AudioCodec::EnableOutput(bool enable) {
|
||||
if (enable == output_enabled_) {
|
||||
return;
|
||||
}
|
||||
output_enabled_ = enable;
|
||||
ESP_LOGI(TAG, "Set output enable to %s", enable ? "true" : "false");
|
||||
}
|
||||
@@ -1,22 +1,26 @@
|
||||
#ifndef _AUDIO_DEVICE_H
|
||||
#define _AUDIO_DEVICE_H
|
||||
#ifndef _AUDIO_CODEC_H
|
||||
#define _AUDIO_CODEC_H
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <driver/i2s_std.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
class AudioDevice {
|
||||
#include "board.h"
|
||||
|
||||
class AudioCodec {
|
||||
public:
|
||||
AudioDevice();
|
||||
virtual ~AudioDevice();
|
||||
virtual void Initialize();
|
||||
AudioCodec();
|
||||
virtual ~AudioCodec();
|
||||
|
||||
virtual void SetOutputVolume(int volume);
|
||||
virtual void EnableInput(bool enable);
|
||||
virtual void EnableOutput(bool enable);
|
||||
|
||||
void OnInputData(std::function<void(std::vector<int16_t>&& data)> callback);
|
||||
void OutputData(std::vector<int16_t>& data);
|
||||
virtual void SetOutputVolume(int volume);
|
||||
|
||||
inline bool duplex() const { return duplex_; }
|
||||
inline bool input_reference() const { return input_reference_; }
|
||||
@@ -31,22 +35,20 @@ private:
|
||||
std::function<void(std::vector<int16_t>&& data)> on_input_data_;
|
||||
|
||||
void InputTask();
|
||||
void CreateSimplexChannels();
|
||||
|
||||
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;
|
||||
int output_channels_ = 1;
|
||||
int output_volume_ = 70;
|
||||
i2s_chan_handle_t tx_handle_ = nullptr;
|
||||
i2s_chan_handle_t rx_handle_ = nullptr;
|
||||
|
||||
virtual void CreateDuplexChannels();
|
||||
virtual int Read(int16_t* dest, int samples);
|
||||
virtual int Write(const int16_t* data, int samples);
|
||||
virtual int Read(int16_t* dest, int samples) = 0;
|
||||
virtual int Write(const int16_t* data, int samples) = 0;
|
||||
};
|
||||
|
||||
#endif // _AUDIO_DEVICE_H
|
||||
#endif // _AUDIO_CODEC_H
|
||||
@@ -1,49 +1,20 @@
|
||||
#include "BoxAudioDevice.h"
|
||||
#include "box_audio_codec.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <cassert>
|
||||
#include <driver/i2c.h>
|
||||
|
||||
static const char* TAG = "BoxAudioDevice";
|
||||
static const char TAG[] = "BoxAudioCodec";
|
||||
|
||||
BoxAudioDevice::BoxAudioDevice() {
|
||||
}
|
||||
|
||||
BoxAudioDevice::~BoxAudioDevice() {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
|
||||
esp_codec_dev_delete(output_dev_);
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
|
||||
esp_codec_dev_delete(input_dev_);
|
||||
|
||||
audio_codec_delete_codec_if(in_codec_if_);
|
||||
audio_codec_delete_ctrl_if(in_ctrl_if_);
|
||||
audio_codec_delete_codec_if(out_codec_if_);
|
||||
audio_codec_delete_ctrl_if(out_ctrl_if_);
|
||||
audio_codec_delete_gpio_if(gpio_if_);
|
||||
audio_codec_delete_data_if(data_if_);
|
||||
|
||||
ESP_ERROR_CHECK(i2c_del_master_bus(i2c_master_handle_));
|
||||
}
|
||||
|
||||
void BoxAudioDevice::Initialize() {
|
||||
BoxAudioCodec::BoxAudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate,
|
||||
gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
|
||||
gpio_num_t pa_pin, uint8_t es8311_addr, uint8_t es7210_addr, bool input_reference) {
|
||||
duplex_ = true; // 是否双工
|
||||
input_reference_ = CONFIG_AUDIO_CODEC_INPUT_REFERENCE; // 是否使用参考输入,实现回声消除
|
||||
input_reference_ = input_reference; // 是否使用参考输入,实现回声消除
|
||||
input_channels_ = input_reference_ ? 2 : 1; // 输入通道数
|
||||
input_sample_rate_ = input_sample_rate;
|
||||
output_sample_rate_ = output_sample_rate;
|
||||
|
||||
// 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,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.intr_priority = 0,
|
||||
.trans_queue_depth = 0,
|
||||
.flags = {
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_master_handle_));
|
||||
|
||||
CreateDuplexChannels();
|
||||
CreateDuplexChannels(mclk, bclk, ws, dout, din);
|
||||
|
||||
// Do initialize of related interface: data_if, ctrl_if and gpio_if
|
||||
audio_codec_i2s_cfg_t i2s_cfg = {
|
||||
@@ -56,9 +27,9 @@ void BoxAudioDevice::Initialize() {
|
||||
|
||||
// Output
|
||||
audio_codec_i2c_cfg_t i2c_cfg = {
|
||||
.port = I2C_NUM_0,
|
||||
.addr = ES8311_CODEC_DEFAULT_ADDR,
|
||||
.bus_handle = i2c_master_handle_,
|
||||
.port = I2C_NUM_1,
|
||||
.addr = es8311_addr,
|
||||
.bus_handle = i2c_master_handle,
|
||||
};
|
||||
out_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
||||
assert(out_ctrl_if_ != NULL);
|
||||
@@ -70,7 +41,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 = 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 +56,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 = es7210_addr;
|
||||
in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
||||
assert(in_ctrl_if_ != NULL);
|
||||
|
||||
@@ -113,20 +72,24 @@ 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");
|
||||
}
|
||||
|
||||
void BoxAudioDevice::CreateDuplexChannels() {
|
||||
BoxAudioCodec::~BoxAudioCodec() {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
|
||||
esp_codec_dev_delete(output_dev_);
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
|
||||
esp_codec_dev_delete(input_dev_);
|
||||
|
||||
audio_codec_delete_codec_if(in_codec_if_);
|
||||
audio_codec_delete_ctrl_if(in_ctrl_if_);
|
||||
audio_codec_delete_codec_if(out_codec_if_);
|
||||
audio_codec_delete_ctrl_if(out_ctrl_if_);
|
||||
audio_codec_delete_gpio_if(gpio_if_);
|
||||
audio_codec_delete_data_if(data_if_);
|
||||
}
|
||||
|
||||
void BoxAudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) {
|
||||
assert(input_sample_rate_ == output_sample_rate_);
|
||||
|
||||
i2s_chan_config_t chan_cfg = {
|
||||
@@ -160,10 +123,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 = mclk,
|
||||
.bclk = bclk,
|
||||
.ws = ws,
|
||||
.dout = dout,
|
||||
.din = I2S_GPIO_UNUSED,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
@@ -196,11 +159,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 = mclk,
|
||||
.bclk = bclk,
|
||||
.ws = ws,
|
||||
.dout = I2S_GPIO_UNUSED,
|
||||
.din = (gpio_num_t)CONFIG_AUDIO_DEVICE_I2S_GPIO_DIN,
|
||||
.din = din,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
@@ -216,17 +179,65 @@ void BoxAudioDevice::CreateDuplexChannels() {
|
||||
ESP_LOGI(TAG, "Duplex channels created");
|
||||
}
|
||||
|
||||
int BoxAudioDevice::Read(int16_t *buffer, int samples) {
|
||||
ESP_ERROR_CHECK(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)));
|
||||
return samples;
|
||||
}
|
||||
|
||||
void BoxAudioDevice::SetOutputVolume(int volume) {
|
||||
void BoxAudioCodec::SetOutputVolume(int volume) {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
|
||||
AudioDevice::SetOutputVolume(volume);
|
||||
AudioCodec::SetOutputVolume(volume);
|
||||
}
|
||||
|
||||
void BoxAudioCodec::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_));
|
||||
}
|
||||
AudioCodec::EnableInput(enable);
|
||||
}
|
||||
|
||||
void BoxAudioCodec::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_));
|
||||
}
|
||||
AudioCodec::EnableOutput(enable);
|
||||
}
|
||||
|
||||
int BoxAudioCodec::Read(int16_t* dest, int samples) {
|
||||
if (input_enabled_) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t)));
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
int BoxAudioCodec::Write(const int16_t* data, int samples) {
|
||||
if (output_enabled_) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t)));
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
42
main/audio_codecs/box_audio_codec.h
Normal file
42
main/audio_codecs/box_audio_codec.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef _BOX_AUDIO_CODEC_H
|
||||
#define _BOX_AUDIO_CODEC_H
|
||||
|
||||
#include "audio_codec.h"
|
||||
|
||||
#include <driver/i2s_std.h>
|
||||
#include <driver/i2s_tdm.h>
|
||||
#include <esp_codec_dev.h>
|
||||
#include <esp_codec_dev_defaults.h>
|
||||
|
||||
class BoxAudioCodec : public AudioCodec {
|
||||
private:
|
||||
i2s_chan_handle_t tx_handle_ = nullptr;
|
||||
i2s_chan_handle_t rx_handle_ = nullptr;
|
||||
|
||||
const audio_codec_data_if_t* data_if_ = nullptr;
|
||||
const audio_codec_ctrl_if_t* out_ctrl_if_ = nullptr;
|
||||
const audio_codec_if_t* out_codec_if_ = nullptr;
|
||||
const audio_codec_ctrl_if_t* in_ctrl_if_ = nullptr;
|
||||
const audio_codec_if_t* in_codec_if_ = nullptr;
|
||||
const audio_codec_gpio_if_t* gpio_if_ = nullptr;
|
||||
|
||||
esp_codec_dev_handle_t output_dev_ = nullptr;
|
||||
esp_codec_dev_handle_t input_dev_ = nullptr;
|
||||
|
||||
void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din);
|
||||
|
||||
virtual int Read(int16_t* dest, int samples) override;
|
||||
virtual int Write(const int16_t* data, int samples) override;
|
||||
|
||||
public:
|
||||
BoxAudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate,
|
||||
gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
|
||||
gpio_num_t pa_pin, uint8_t es8311_addr, uint8_t es7210_addr, bool input_reference);
|
||||
virtual ~BoxAudioCodec();
|
||||
|
||||
virtual void SetOutputVolume(int volume) override;
|
||||
virtual void EnableInput(bool enable) override;
|
||||
virtual void EnableOutput(bool enable) override;
|
||||
};
|
||||
|
||||
#endif // _BOX_AUDIO_CODEC_H
|
||||
@@ -1,18 +1,11 @@
|
||||
#include "AudioDevice.h"
|
||||
#include "no_audio_codec.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) {
|
||||
}
|
||||
#define TAG "NoAudioCodec"
|
||||
|
||||
AudioDevice::~AudioDevice() {
|
||||
if (audio_input_task_ != nullptr) {
|
||||
vTaskDelete(audio_input_task_);
|
||||
}
|
||||
NoAudioCodec::~NoAudioCodec() {
|
||||
if (rx_handle_ != nullptr) {
|
||||
ESP_ERROR_CHECK(i2s_channel_disable(rx_handle_));
|
||||
}
|
||||
@@ -21,17 +14,11 @@ AudioDevice::~AudioDevice() {
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDevice::Initialize() {
|
||||
#ifdef CONFIG_AUDIO_I2S_METHOD_SIMPLEX
|
||||
CreateSimplexChannels();
|
||||
#else
|
||||
CreateDuplexChannels();
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioDevice::CreateDuplexChannels() {
|
||||
#ifdef CONFIG_AUDIO_I2S_METHOD_DUPLEX
|
||||
NoAudioCodec::NoAudioCodec(int input_sample_rate, int output_sample_rate, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) {
|
||||
duplex_ = true;
|
||||
input_sample_rate_ = input_sample_rate;
|
||||
output_sample_rate_ = output_sample_rate;
|
||||
|
||||
i2s_chan_config_t chan_cfg = {
|
||||
.id = I2S_NUM_0,
|
||||
@@ -65,10 +52,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 = bclk,
|
||||
.ws = ws,
|
||||
.dout = dout,
|
||||
.din = din,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
@@ -81,18 +68,20 @@ void AudioDevice::CreateDuplexChannels() {
|
||||
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
|
||||
ESP_ERROR_CHECK(i2s_channel_enable(rx_handle_));
|
||||
ESP_LOGI(TAG, "Duplex channels created");
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioDevice::CreateSimplexChannels() {
|
||||
#ifdef CONFIG_AUDIO_I2S_METHOD_SIMPLEX
|
||||
NoAudioCodec::NoAudioCodec(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, gpio_num_t mic_sck, gpio_num_t mic_ws, gpio_num_t mic_din) {
|
||||
duplex_ = false;
|
||||
input_sample_rate_ = input_sample_rate;
|
||||
output_sample_rate_ = output_sample_rate;
|
||||
|
||||
// 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 +108,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 = spk_bclk,
|
||||
.ws = spk_ws,
|
||||
.dout = spk_dout,
|
||||
.din = I2S_GPIO_UNUSED,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
@@ -136,19 +125,18 @@ 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 = mic_sck;
|
||||
std_cfg.gpio_cfg.ws = mic_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 = mic_din;
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
|
||||
|
||||
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
|
||||
ESP_ERROR_CHECK(i2s_channel_enable(rx_handle_));
|
||||
ESP_LOGI(TAG, "Simplex channels created");
|
||||
#endif
|
||||
}
|
||||
|
||||
int AudioDevice::Write(const int16_t* data, int samples) {
|
||||
int NoAudioCodec::Write(const int16_t* data, int samples) {
|
||||
int32_t buffer[samples];
|
||||
|
||||
// output_volume_: 0-100
|
||||
@@ -170,54 +158,19 @@ int AudioDevice::Write(const int16_t* data, int samples) {
|
||||
return bytes_written / sizeof(int32_t);
|
||||
}
|
||||
|
||||
int AudioDevice::Read(int16_t* dest, int samples) {
|
||||
int NoAudioCodec::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;
|
||||
}
|
||||
|
||||
void AudioDevice::OnInputData(std::function<void(std::vector<int16_t>&& data)> callback) {
|
||||
on_input_data_ = callback;
|
||||
|
||||
// 创建音频输入任务
|
||||
if (audio_input_task_ == nullptr) {
|
||||
xTaskCreate([](void* arg) {
|
||||
auto audio_device = (AudioDevice*)arg;
|
||||
audio_device->InputTask();
|
||||
}, "audio_input", 4096 * 2, this, 3, &audio_input_task_);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDevice::OutputData(std::vector<int16_t>& data) {
|
||||
Write(data.data(), data.size());
|
||||
}
|
||||
|
||||
void AudioDevice::InputTask() {
|
||||
int duration = 30;
|
||||
int input_frame_size = input_sample_rate_ / 1000 * duration * input_channels_;
|
||||
while (true) {
|
||||
std::vector<int16_t> input_data(input_frame_size);
|
||||
int samples = Read(input_data.data(), input_data.size());
|
||||
if (samples > 0) {
|
||||
if (on_input_data_) {
|
||||
on_input_data_(std::move(input_data));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDevice::SetOutputVolume(int volume) {
|
||||
output_volume_ = volume;
|
||||
ESP_LOGI(TAG, "Set output volume to %d", output_volume_);
|
||||
}
|
||||
25
main/audio_codecs/no_audio_codec.h
Normal file
25
main/audio_codecs/no_audio_codec.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef _NO_AUDIO_CODEC_H
|
||||
#define _NO_AUDIO_CODEC_H
|
||||
|
||||
#include "audio_codec.h"
|
||||
|
||||
#include <driver/i2s_std.h>
|
||||
#include <driver/gpio.h>
|
||||
|
||||
class NoAudioCodec : public AudioCodec {
|
||||
private:
|
||||
i2s_chan_handle_t tx_handle_ = nullptr;
|
||||
i2s_chan_handle_t rx_handle_ = nullptr;
|
||||
|
||||
virtual int Write(const int16_t* data, int samples) override;
|
||||
virtual int Read(int16_t* dest, int samples) override;
|
||||
|
||||
public:
|
||||
// Duplex
|
||||
NoAudioCodec(int input_sample_rate, int output_sample_rate, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din);
|
||||
// Simplex
|
||||
NoAudioCodec(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, gpio_num_t mic_sck, gpio_num_t mic_ws, gpio_num_t mic_din);
|
||||
virtual ~NoAudioCodec();
|
||||
};
|
||||
|
||||
#endif // _NO_AUDIO_CODEC_H
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "AudioProcessor.h"
|
||||
#include "audio_processor.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
#define PROCESSOR_RUNNING 0x01
|
||||
104
main/board.cc
Normal file
104
main/board.cc
Normal file
@@ -0,0 +1,104 @@
|
||||
#include "board.h"
|
||||
#include "system_info.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_ota_ops.h>
|
||||
#include <esp_chip_info.h>
|
||||
|
||||
// static const char *TAG = "Board";
|
||||
|
||||
bool Board::GetBatteryVoltage(int &voltage, bool& charging) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string Board::GetJson() {
|
||||
/*
|
||||
{
|
||||
"flash_size": 4194304,
|
||||
"psram_size": 0,
|
||||
"minimum_free_heap_size": 123456,
|
||||
"mac_address": "00:00:00:00:00:00",
|
||||
"chip_model_name": "esp32s3",
|
||||
"chip_info": {
|
||||
"model": 1,
|
||||
"cores": 2,
|
||||
"revision": 0,
|
||||
"features": 0
|
||||
},
|
||||
"application": {
|
||||
"name": "my-app",
|
||||
"version": "1.0.0",
|
||||
"compile_time": "2021-01-01T00:00:00Z"
|
||||
"idf_version": "4.2-dev"
|
||||
"elf_sha256": ""
|
||||
},
|
||||
"partition_table": [
|
||||
"app": {
|
||||
"label": "app",
|
||||
"type": 1,
|
||||
"subtype": 2,
|
||||
"address": 0x10000,
|
||||
"size": 0x100000
|
||||
}
|
||||
],
|
||||
"ota": {
|
||||
"label": "ota_0"
|
||||
}
|
||||
}
|
||||
*/
|
||||
std::string json = "{";
|
||||
json += "\"flash_size\":" + std::to_string(SystemInfo::GetFlashSize()) + ",";
|
||||
json += "\"minimum_free_heap_size\":" + std::to_string(SystemInfo::GetMinimumFreeHeapSize()) + ",";
|
||||
json += "\"mac_address\":\"" + SystemInfo::GetMacAddress() + "\",";
|
||||
json += "\"chip_model_name\":\"" + SystemInfo::GetChipModelName() + "\",";
|
||||
json += "\"chip_info\":{";
|
||||
|
||||
esp_chip_info_t chip_info;
|
||||
esp_chip_info(&chip_info);
|
||||
json += "\"model\":" + std::to_string(chip_info.model) + ",";
|
||||
json += "\"cores\":" + std::to_string(chip_info.cores) + ",";
|
||||
json += "\"revision\":" + std::to_string(chip_info.revision) + ",";
|
||||
json += "\"features\":" + std::to_string(chip_info.features);
|
||||
json += "},";
|
||||
|
||||
json += "\"application\":{";
|
||||
auto app_desc = esp_app_get_description();
|
||||
json += "\"name\":\"" + std::string(app_desc->project_name) + "\",";
|
||||
json += "\"version\":\"" + std::string(app_desc->version) + "\",";
|
||||
json += "\"compile_time\":\"" + std::string(app_desc->date) + "T" + std::string(app_desc->time) + "Z\",";
|
||||
json += "\"idf_version\":\"" + std::string(app_desc->idf_ver) + "\",";
|
||||
|
||||
char sha256_str[65];
|
||||
for (int i = 0; i < 32; i++) {
|
||||
snprintf(sha256_str + i * 2, sizeof(sha256_str) - i * 2, "%02x", app_desc->app_elf_sha256[i]);
|
||||
}
|
||||
json += "\"elf_sha256\":\"" + std::string(sha256_str) + "\"";
|
||||
json += "},";
|
||||
|
||||
json += "\"partition_table\": [";
|
||||
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
|
||||
while (it) {
|
||||
const esp_partition_t *partition = esp_partition_get(it);
|
||||
json += "{";
|
||||
json += "\"label\":\"" + std::string(partition->label) + "\",";
|
||||
json += "\"type\":" + std::to_string(partition->type) + ",";
|
||||
json += "\"subtype\":" + std::to_string(partition->subtype) + ",";
|
||||
json += "\"address\":" + std::to_string(partition->address) + ",";
|
||||
json += "\"size\":" + std::to_string(partition->size);
|
||||
json += "},";
|
||||
it = esp_partition_next(it);
|
||||
}
|
||||
json.pop_back(); // Remove the last comma
|
||||
json += "],";
|
||||
|
||||
json += "\"ota\":{";
|
||||
auto ota_partition = esp_ota_get_running_partition();
|
||||
json += "\"label\":\"" + std::string(ota_partition->label) + "\"";
|
||||
json += "},";
|
||||
|
||||
json += "\"board\":" + GetBoardJson();
|
||||
|
||||
// Close the JSON object
|
||||
json += "}";
|
||||
return json;
|
||||
}
|
||||
49
main/board.h
Normal file
49
main/board.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef BOARD_H
|
||||
#define BOARD_H
|
||||
|
||||
#include <http.h>
|
||||
#include <web_socket.h>
|
||||
#include <string>
|
||||
|
||||
#include "led.h"
|
||||
|
||||
void* create_board();
|
||||
class AudioCodec;
|
||||
class Display;
|
||||
class Board {
|
||||
private:
|
||||
Board(const Board&) = delete; // 禁用拷贝构造函数
|
||||
Board& operator=(const Board&) = delete; // 禁用赋值操作
|
||||
virtual std::string GetBoardJson() = 0;
|
||||
|
||||
protected:
|
||||
Board() = default;
|
||||
|
||||
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 Led* GetBuiltinLed() = 0;
|
||||
virtual AudioCodec* GetAudioCodec() = 0;
|
||||
virtual Display* GetDisplay() = 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();
|
||||
};
|
||||
|
||||
#define DECLARE_BOARD(BOARD_CLASS_NAME) \
|
||||
void* create_board() { \
|
||||
return new BOARD_CLASS_NAME(); \
|
||||
}
|
||||
|
||||
#endif // BOARD_H
|
||||
116
main/boards/bread-compact-ml307/compact_ml307_board.cc
Normal file
116
main/boards/bread-compact-ml307/compact_ml307_board.cc
Normal file
@@ -0,0 +1,116 @@
|
||||
#include "boards/ml307_board.h"
|
||||
#include "audio_codecs/no_audio_codec.h"
|
||||
#include "display/ssd1306_display.h"
|
||||
#include "system_reset.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "led.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
|
||||
#define TAG "CompactMl307Board"
|
||||
|
||||
class CompactMl307Board : public Ml307Board {
|
||||
private:
|
||||
i2c_master_bus_handle_t display_i2c_bus_;
|
||||
Button boot_button_;
|
||||
Button volume_up_button_;
|
||||
Button volume_down_button_;
|
||||
|
||||
void InitializeDisplayI2c() {
|
||||
i2c_master_bus_config_t bus_config = {
|
||||
.i2c_port = I2C_NUM_0,
|
||||
.sda_io_num = DISPLAY_SDA_PIN,
|
||||
.scl_io_num = DISPLAY_SCL_PIN,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.intr_priority = 0,
|
||||
.trans_queue_depth = 0,
|
||||
.flags = {
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_));
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
Application::GetInstance().ToggleChatState();
|
||||
});
|
||||
|
||||
volume_up_button_.OnClick([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() + 10;
|
||||
if (volume > 100) {
|
||||
volume = 100;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification("Volume\n" + std::to_string(volume));
|
||||
});
|
||||
|
||||
volume_up_button_.OnLongPress([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
codec->SetOutputVolume(100);
|
||||
GetDisplay()->ShowNotification("Volume\n100");
|
||||
});
|
||||
|
||||
volume_down_button_.OnClick([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() - 10;
|
||||
if (volume < 0) {
|
||||
volume = 0;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification("Volume\n" + std::to_string(volume));
|
||||
});
|
||||
|
||||
volume_down_button_.OnLongPress([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
codec->SetOutputVolume(0);
|
||||
GetDisplay()->ShowNotification("Volume\n0");
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
CompactMl307Board() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN, 4096),
|
||||
boot_button_(BOOT_BUTTON_GPIO),
|
||||
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
|
||||
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
|
||||
}
|
||||
|
||||
virtual void Initialize() override {
|
||||
ESP_LOGI(TAG, "Initializing CompactMl307Board");
|
||||
// Check if the reset button is pressed
|
||||
SystemReset::GetInstance().CheckButtons();
|
||||
|
||||
InitializeDisplayI2c();
|
||||
InitializeButtons();
|
||||
|
||||
Ml307Board::Initialize();
|
||||
}
|
||||
|
||||
virtual Led* GetBuiltinLed() override {
|
||||
static Led led(BUILTIN_LED_GPIO);
|
||||
return &led;
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
#ifdef AUDIO_I2S_METHOD_SIMPLEX
|
||||
static NoAudioCodec audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
|
||||
#else
|
||||
static NoAudioCodec audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
|
||||
#endif
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
static Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
return &display;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(CompactMl307Board);
|
||||
47
main/boards/bread-compact-ml307/config.h
Normal file
47
main/boards/bread-compact-ml307/config.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 16000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
// 如果使用 Duplex I2S 模式,请注释下面一行
|
||||
#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_WS 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_
|
||||
116
main/boards/bread-compact-wifi/compact_wifi_board.cc
Normal file
116
main/boards/bread-compact-wifi/compact_wifi_board.cc
Normal file
@@ -0,0 +1,116 @@
|
||||
#include "boards/wifi_board.h"
|
||||
#include "audio_codecs/no_audio_codec.h"
|
||||
#include "display/ssd1306_display.h"
|
||||
#include "system_reset.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "led.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
|
||||
#define TAG "CompactWifiBoard"
|
||||
|
||||
class CompactWifiBoard : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t display_i2c_bus_;
|
||||
Button boot_button_;
|
||||
Button volume_up_button_;
|
||||
Button volume_down_button_;
|
||||
|
||||
void InitializeDisplayI2c() {
|
||||
i2c_master_bus_config_t bus_config = {
|
||||
.i2c_port = I2C_NUM_0,
|
||||
.sda_io_num = DISPLAY_SDA_PIN,
|
||||
.scl_io_num = DISPLAY_SCL_PIN,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.intr_priority = 0,
|
||||
.trans_queue_depth = 0,
|
||||
.flags = {
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_));
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
Application::GetInstance().ToggleChatState();
|
||||
});
|
||||
|
||||
volume_up_button_.OnClick([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() + 10;
|
||||
if (volume > 100) {
|
||||
volume = 100;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification("Volume\n" + std::to_string(volume));
|
||||
});
|
||||
|
||||
volume_up_button_.OnLongPress([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
codec->SetOutputVolume(100);
|
||||
GetDisplay()->ShowNotification("Volume\n100");
|
||||
});
|
||||
|
||||
volume_down_button_.OnClick([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() - 10;
|
||||
if (volume < 0) {
|
||||
volume = 0;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification("Volume\n" + std::to_string(volume));
|
||||
});
|
||||
|
||||
volume_down_button_.OnLongPress([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
codec->SetOutputVolume(0);
|
||||
GetDisplay()->ShowNotification("Volume\n0");
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
CompactWifiBoard() :
|
||||
boot_button_(BOOT_BUTTON_GPIO),
|
||||
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
|
||||
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
|
||||
}
|
||||
|
||||
virtual void Initialize() override {
|
||||
ESP_LOGI(TAG, "Initializing CompactWifiBoard");
|
||||
// Check if the reset button is pressed
|
||||
SystemReset::GetInstance().CheckButtons();
|
||||
|
||||
InitializeDisplayI2c();
|
||||
InitializeButtons();
|
||||
|
||||
WifiBoard::Initialize();
|
||||
}
|
||||
|
||||
virtual Led* GetBuiltinLed() override {
|
||||
static Led led(BUILTIN_LED_GPIO);
|
||||
return &led;
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
#ifdef AUDIO_I2S_METHOD_SIMPLEX
|
||||
static NoAudioCodec audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
|
||||
#else
|
||||
static NoAudioCodec audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
|
||||
#endif
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
static Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
return &display;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(CompactWifiBoard);
|
||||
43
main/boards/bread-compact-wifi/config.h
Normal file
43
main/boards/bread-compact-wifi/config.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 16000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
// 如果使用 Duplex I2S 模式,请注释下面一行
|
||||
#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_WS 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_
|
||||
37
main/boards/esp-box-3/config.h
Normal file
37
main/boards/esp-box-3/config.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#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_DEFAULT_OUTPUT_VOLUME 90
|
||||
|
||||
#define AUDIO_INPUT_REFERENCE true
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_2
|
||||
#define AUDIO_I2S_GPIO_WS 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_
|
||||
75
main/boards/esp-box-3/esp_box3_board.cc
Normal file
75
main/boards/esp-box-3/esp_box3_board.cc
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "boards/wifi_board.h"
|
||||
#include "audio_codecs/box_audio_codec.h"
|
||||
#include "display/no_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "led.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
|
||||
#define TAG "EspBox3Board"
|
||||
|
||||
class EspBox3Board : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Button boot_button_;
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = I2C_NUM_1,
|
||||
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
|
||||
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.intr_priority = 0,
|
||||
.trans_queue_depth = 0,
|
||||
.flags = {
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
Application::GetInstance().ToggleChatState();
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
EspBox3Board() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
}
|
||||
|
||||
virtual void Initialize() override {
|
||||
ESP_LOGI(TAG, "Initializing EspBox3Board");
|
||||
InitializeI2c();
|
||||
InitializeButtons();
|
||||
WifiBoard::Initialize();
|
||||
}
|
||||
|
||||
virtual Led* GetBuiltinLed() override {
|
||||
static Led led(GPIO_NUM_NC);
|
||||
return &led;
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static BoxAudioCodec* audio_codec = nullptr;
|
||||
if (audio_codec == nullptr) {
|
||||
audio_codec = new BoxAudioCodec(i2c_bus_, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN,
|
||||
AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE);
|
||||
audio_codec->SetOutputVolume(AUDIO_DEFAULT_OUTPUT_VOLUME);
|
||||
}
|
||||
return audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
static NoDisplay display;
|
||||
return &display;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(EspBox3Board);
|
||||
39
main/boards/kevin-box-0/config.h
Normal file
39
main/boards/kevin-box-0/config.h
Normal 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_WS 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_
|
||||
191
main/boards/kevin-box-0/kevin_box_board.cc
Normal file
191
main/boards/kevin-box-0/kevin_box_board.cc
Normal file
@@ -0,0 +1,191 @@
|
||||
#include "boards/ml307_board.h"
|
||||
#include "audio_codecs/box_audio_codec.h"
|
||||
#include "display/ssd1306_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "led.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_spiffs.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/i2c_master.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_;
|
||||
i2c_master_bus_handle_t display_i2c_bus_;
|
||||
i2c_master_bus_handle_t codec_i2c_bus_;
|
||||
Button boot_button_;
|
||||
Button volume_up_button_;
|
||||
Button volume_down_button_;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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_));
|
||||
}
|
||||
|
||||
void InitializeDisplayI2c() {
|
||||
i2c_master_bus_config_t bus_config = {
|
||||
.i2c_port = I2C_NUM_0,
|
||||
.sda_io_num = DISPLAY_SDA_PIN,
|
||||
.scl_io_num = DISPLAY_SCL_PIN,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.intr_priority = 0,
|
||||
.trans_queue_depth = 0,
|
||||
.flags = {
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_));
|
||||
}
|
||||
|
||||
void InitializeCodecI2c() {
|
||||
// Initialize I2C peripheral
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = I2C_NUM_1,
|
||||
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
|
||||
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.intr_priority = 0,
|
||||
.trans_queue_depth = 0,
|
||||
.flags = {
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_));
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
Application::GetInstance().ToggleChatState();
|
||||
});
|
||||
|
||||
volume_up_button_.OnClick([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() + 10;
|
||||
if (volume > 100) {
|
||||
volume = 100;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification("Volume\n" + std::to_string(volume));
|
||||
});
|
||||
|
||||
volume_up_button_.OnLongPress([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
codec->SetOutputVolume(100);
|
||||
GetDisplay()->ShowNotification("Volume\n100");
|
||||
});
|
||||
|
||||
volume_down_button_.OnClick([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() - 10;
|
||||
if (volume < 0) {
|
||||
volume = 0;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification("Volume\n" + std::to_string(volume));
|
||||
});
|
||||
|
||||
volume_down_button_.OnLongPress([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
codec->SetOutputVolume(0);
|
||||
GetDisplay()->ShowNotification("Volume\n0");
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
KevinBoxBoard() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN, 4096),
|
||||
boot_button_(BOOT_BUTTON_GPIO),
|
||||
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
|
||||
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
|
||||
}
|
||||
|
||||
virtual void Initialize() override {
|
||||
ESP_LOGI(TAG, "Initializing KevinBoxBoard");
|
||||
InitializeDisplayI2c();
|
||||
InitializeCodecI2c();
|
||||
InitializeADC();
|
||||
MountStorage();
|
||||
Enable4GModule();
|
||||
|
||||
InitializeButtons();
|
||||
|
||||
Ml307Board::Initialize();
|
||||
}
|
||||
|
||||
virtual Led* GetBuiltinLed() override {
|
||||
static Led led(BUILTIN_LED_GPIO);
|
||||
return &led;
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static BoxAudioCodec audio_codec(codec_i2c_bus_, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN,
|
||||
AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
static Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
return &display;
|
||||
}
|
||||
|
||||
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));
|
||||
voltage *= 3;
|
||||
charging = false;
|
||||
ESP_LOGI(TAG, "Battery voltage: %d, Charging: %d", voltage, charging);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(KevinBoxBoard);
|
||||
39
main/boards/kevin-box-1/config.h
Normal file
39
main/boards/kevin-box-1/config.h
Normal 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_WS 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_
|
||||
201
main/boards/kevin-box-1/kevin_box_board.cc
Normal file
201
main/boards/kevin-box-1/kevin_box_board.cc
Normal file
@@ -0,0 +1,201 @@
|
||||
#include "boards/ml307_board.h"
|
||||
#include "audio_codecs/box_audio_codec.h"
|
||||
#include "display/ssd1306_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "led.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_spiffs.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/i2c_master.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_;
|
||||
i2c_master_bus_handle_t display_i2c_bus_;
|
||||
i2c_master_bus_handle_t codec_i2c_bus_;
|
||||
Button boot_button_;
|
||||
Button volume_up_button_;
|
||||
Button volume_down_button_;
|
||||
|
||||
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) | (1ULL << 18),
|
||||
.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);
|
||||
gpio_set_level(GPIO_NUM_18, 1);
|
||||
}
|
||||
|
||||
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_));
|
||||
}
|
||||
|
||||
void InitializeDisplayI2c() {
|
||||
i2c_master_bus_config_t bus_config = {
|
||||
.i2c_port = I2C_NUM_0,
|
||||
.sda_io_num = DISPLAY_SDA_PIN,
|
||||
.scl_io_num = DISPLAY_SCL_PIN,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.intr_priority = 0,
|
||||
.trans_queue_depth = 0,
|
||||
.flags = {
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_));
|
||||
}
|
||||
|
||||
void InitializeCodecI2c() {
|
||||
// Initialize I2C peripheral
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = I2C_NUM_1,
|
||||
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
|
||||
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.intr_priority = 0,
|
||||
.trans_queue_depth = 0,
|
||||
.flags = {
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_));
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
Application::GetInstance().ToggleChatState();
|
||||
});
|
||||
|
||||
volume_up_button_.OnClick([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() + 10;
|
||||
if (volume > 100) {
|
||||
volume = 100;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification("Volume\n" + std::to_string(volume));
|
||||
});
|
||||
|
||||
volume_up_button_.OnLongPress([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
codec->SetOutputVolume(100);
|
||||
GetDisplay()->ShowNotification("Volume\n100");
|
||||
});
|
||||
|
||||
volume_down_button_.OnClick([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() - 10;
|
||||
if (volume < 0) {
|
||||
volume = 0;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification("Volume\n" + std::to_string(volume));
|
||||
});
|
||||
|
||||
volume_down_button_.OnLongPress([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
codec->SetOutputVolume(0);
|
||||
GetDisplay()->ShowNotification("Volume\n0");
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
KevinBoxBoard() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN, 4096),
|
||||
boot_button_(BOOT_BUTTON_GPIO),
|
||||
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
|
||||
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
|
||||
}
|
||||
|
||||
virtual void Initialize() override {
|
||||
ESP_LOGI(TAG, "Initializing KevinBoxBoard");
|
||||
InitializeDisplayI2c();
|
||||
InitializeCodecI2c();
|
||||
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);
|
||||
|
||||
InitializeButtons();
|
||||
|
||||
Ml307Board::Initialize();
|
||||
}
|
||||
|
||||
virtual Led* GetBuiltinLed() override {
|
||||
static Led led(BUILTIN_LED_GPIO);
|
||||
return &led;
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static BoxAudioCodec audio_codec(codec_i2c_bus_, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN,
|
||||
AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
static Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
return &display;
|
||||
}
|
||||
|
||||
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));
|
||||
voltage *= 3;
|
||||
charging = gpio_get_level(GPIO_NUM_2) == 0;
|
||||
ESP_LOGI(TAG, "Battery voltage: %d, Charging: %d", voltage, charging);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(KevinBoxBoard);
|
||||
35
main/boards/lichuang-dev/config.h
Normal file
35
main/boards/lichuang-dev/config.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#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_DEFAULT_OUTPUT_VOLUME 90
|
||||
|
||||
#define AUDIO_INPUT_REFERENCE true
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
|
||||
#define AUDIO_I2S_GPIO_WS 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_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_WIDTH 320
|
||||
#define DISPLAY_HEIGHT 240
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
155
main/boards/lichuang-dev/lichuang_dev_board.cc
Normal file
155
main/boards/lichuang-dev/lichuang_dev_board.cc
Normal file
@@ -0,0 +1,155 @@
|
||||
#include "boards/wifi_board.h"
|
||||
#include "audio_codecs/box_audio_codec.h"
|
||||
#include "display/st7789_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "led.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
#define TAG "LichuangDevBoard"
|
||||
|
||||
class LichuangDevBoard : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
i2c_master_dev_handle_t pca9557_handle_;
|
||||
Button boot_button_;
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = I2C_NUM_1,
|
||||
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
|
||||
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.intr_priority = 0,
|
||||
.trans_queue_depth = 0,
|
||||
.flags = {
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
|
||||
}
|
||||
|
||||
void Pca9557ReadRegister(uint8_t addr, uint8_t* data) {
|
||||
uint8_t tmp[1] = {addr};
|
||||
ESP_ERROR_CHECK(i2c_master_transmit_receive(pca9557_handle_, tmp, 1, data, 1, 100));
|
||||
}
|
||||
|
||||
void Pca9557WriteRegister(uint8_t addr, uint8_t data) {
|
||||
uint8_t tmp[2] = {addr, data};
|
||||
ESP_ERROR_CHECK(i2c_master_transmit(pca9557_handle_, tmp, 2, 100));
|
||||
}
|
||||
|
||||
void Pca9557SetOutputState(uint8_t bit, uint8_t level) {
|
||||
uint8_t data;
|
||||
Pca9557ReadRegister(0x01, &data);
|
||||
data = (data & ~(1 << bit)) | (level << bit);
|
||||
Pca9557WriteRegister(0x01, data);
|
||||
}
|
||||
|
||||
void InitializePca9557() {
|
||||
i2c_device_config_t pca9557_cfg = {
|
||||
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||
.device_address = 0x19,
|
||||
.scl_speed_hz = 100000,
|
||||
.scl_wait_us = 0,
|
||||
.flags = {
|
||||
.disable_ack_check = 0,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus_, &pca9557_cfg, &pca9557_handle_));
|
||||
assert(pca9557_handle_ != NULL);
|
||||
Pca9557WriteRegister(0x01, 0x03);
|
||||
Pca9557WriteRegister(0x03, 0xf8);
|
||||
}
|
||||
|
||||
void InitializeSpi() {
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = GPIO_NUM_40;
|
||||
buscfg.miso_io_num = GPIO_NUM_NC;
|
||||
buscfg.sclk_io_num = GPIO_NUM_41;
|
||||
buscfg.quadwp_io_num = GPIO_NUM_NC;
|
||||
buscfg.quadhd_io_num = GPIO_NUM_NC;
|
||||
buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
Application::GetInstance().ToggleChatState();
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
LichuangDevBoard() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
}
|
||||
|
||||
virtual void Initialize() override {
|
||||
ESP_LOGI(TAG, "Initializing LichuangDevBoard");
|
||||
InitializeI2c();
|
||||
InitializePca9557();
|
||||
InitializeSpi();
|
||||
InitializeButtons();
|
||||
WifiBoard::Initialize();
|
||||
}
|
||||
|
||||
virtual Led* GetBuiltinLed() override {
|
||||
static Led led(GPIO_NUM_NC);
|
||||
return &led;
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static BoxAudioCodec* audio_codec = nullptr;
|
||||
if (audio_codec == nullptr) {
|
||||
audio_codec = new BoxAudioCodec(i2c_bus_, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN,
|
||||
GPIO_NUM_NC, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE);
|
||||
// audio_codec->SetOutputVolume(AUDIO_DEFAULT_OUTPUT_VOLUME);
|
||||
}
|
||||
return audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
static St7789Display* display = nullptr;
|
||||
if (display == nullptr) {
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
// 液晶屏控制IO初始化
|
||||
ESP_LOGD(TAG, "Install panel IO");
|
||||
esp_lcd_panel_io_spi_config_t io_config = {};
|
||||
io_config.cs_gpio_num = GPIO_NUM_NC;
|
||||
io_config.dc_gpio_num = GPIO_NUM_39;
|
||||
io_config.spi_mode = 2;
|
||||
io_config.pclk_hz = 80 * 1000 * 1000;
|
||||
io_config.trans_queue_depth = 10;
|
||||
io_config.lcd_cmd_bits = 8;
|
||||
io_config.lcd_param_bits = 8;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io));
|
||||
|
||||
// 初始化液晶屏驱动芯片ST7789
|
||||
ESP_LOGD(TAG, "Install LCD driver");
|
||||
esp_lcd_panel_dev_config_t panel_config = {};
|
||||
panel_config.reset_gpio_num = GPIO_NUM_NC;
|
||||
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
|
||||
panel_config.bits_per_pixel = 16;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
Pca9557SetOutputState(0, 0);
|
||||
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, true);
|
||||
esp_lcd_panel_swap_xy(panel, true);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
display = new St7789Display(panel_io, panel, GPIO_NUM_42, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
}
|
||||
return display;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(LichuangDevBoard);
|
||||
111
main/boards/ml307_board.cc
Normal file
111
main/boards/ml307_board.cc
Normal file
@@ -0,0 +1,111 @@
|
||||
#include "ml307_board.h"
|
||||
#include "application.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_timer.h>
|
||||
#include <ml307_http.h>
|
||||
#include <ml307_ssl_transport.h>
|
||||
#include <web_socket.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(gpio_num_t tx_pin, gpio_num_t rx_pin, size_t rx_buffer_size) : modem_(tx_pin, rx_pin, rx_buffer_size) {
|
||||
}
|
||||
|
||||
void Ml307Board::StartNetwork() {
|
||||
auto display = Board::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);
|
||||
WaitForNetworkReady();
|
||||
});
|
||||
});
|
||||
|
||||
WaitForNetworkReady();
|
||||
}
|
||||
|
||||
void Ml307Board::WaitForNetworkReady() {
|
||||
auto& application = Application::GetInstance();
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
display->SetText(std::string("Wait for network\n"));
|
||||
int result = modem_.WaitForNetworkReady();
|
||||
if (result == -1) {
|
||||
application.Alert("Error", "PIN is not ready");
|
||||
return;
|
||||
} else if (result == -2) {
|
||||
application.Alert("Error", "Registration denied");
|
||||
return;
|
||||
}
|
||||
|
||||
// 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::Initialize() {
|
||||
ESP_LOGI(TAG, "Initializing Ml307Board");
|
||||
}
|
||||
|
||||
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::GetBoardJson() {
|
||||
// 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;
|
||||
}
|
||||
23
main/boards/ml307_board.h
Normal file
23
main/boards/ml307_board.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef ML307_BOARD_H
|
||||
#define ML307_BOARD_H
|
||||
|
||||
#include "board.h"
|
||||
#include <ml307_at_modem.h>
|
||||
|
||||
class Ml307Board : public Board {
|
||||
protected:
|
||||
Ml307AtModem modem_;
|
||||
|
||||
virtual std::string GetBoardJson() override;
|
||||
void WaitForNetworkReady();
|
||||
|
||||
public:
|
||||
Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, size_t rx_buffer_size = 4096);
|
||||
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;
|
||||
};
|
||||
|
||||
#endif // ML307_BOARD_H
|
||||
103
main/boards/wifi_board.cc
Normal file
103
main/boards/wifi_board.cc
Normal file
@@ -0,0 +1,103 @@
|
||||
#include "wifi_board.h"
|
||||
#include "application.h"
|
||||
#include "system_info.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <esp_http.h>
|
||||
#include <tcp_transport.h>
|
||||
#include <tls_transport.h>
|
||||
#include <web_socket.h>
|
||||
#include <esp_log.h>
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <wifi_configuration_ap.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 = Board::GetInstance().GetDisplay();
|
||||
auto builtin_led = Board::GetInstance().GetBuiltinLed();
|
||||
|
||||
// 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::GetBoardJson() {
|
||||
// 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;
|
||||
}
|
||||
20
main/boards/wifi_board.h
Normal file
20
main/boards/wifi_board.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef WIFI_BOARD_H
|
||||
#define WIFI_BOARD_H
|
||||
|
||||
#include "board.h"
|
||||
|
||||
class WifiBoard : public Board {
|
||||
protected:
|
||||
bool wifi_config_mode_ = false;
|
||||
|
||||
virtual std::string GetBoardJson() override;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
#endif // WIFI_BOARD_H
|
||||
@@ -1,9 +1,13 @@
|
||||
#include "Button.h"
|
||||
#include "button.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
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 +31,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 +44,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 +57,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 +70,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);
|
||||
136
main/display.cc
Normal file
136
main/display.cc
Normal file
@@ -0,0 +1,136 @@
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "display.h"
|
||||
#include "board.h"
|
||||
#include "application.h"
|
||||
|
||||
#define TAG "Display"
|
||||
|
||||
void Display::SetupUI() {
|
||||
if (disp_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Setting up UI");
|
||||
Lock();
|
||||
label_ = lv_label_create(lv_disp_get_scr_act(disp_));
|
||||
// lv_obj_set_style_text_font(label_, font_, 0);
|
||||
lv_obj_set_style_text_color(label_, lv_color_black(), 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_obj_set_style_text_color(notification_, lv_color_black(), 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);
|
||||
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() {
|
||||
if (notification_timer_ != nullptr) {
|
||||
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_);
|
||||
}
|
||||
if (label_ != nullptr) {
|
||||
lv_obj_del(label_);
|
||||
lv_obj_del(notification_);
|
||||
}
|
||||
if (font_ != nullptr) {
|
||||
lv_font_free(font_);
|
||||
}
|
||||
}
|
||||
|
||||
void Display::SetText(const std::string &text) {
|
||||
if (label_ != nullptr) {
|
||||
text_ = text;
|
||||
Lock();
|
||||
// Change the text of the label
|
||||
lv_label_set_text(label_, text_.c_str());
|
||||
Unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void Display::ShowNotification(const std::string &text) {
|
||||
if (notification_ != nullptr) {
|
||||
Lock();
|
||||
lv_label_set_text(notification_, text.c_str());
|
||||
lv_obj_set_style_opa(notification_, LV_OPA_MAX, 0);
|
||||
lv_obj_set_style_opa(label_, LV_OPA_MIN, 0);
|
||||
Unlock();
|
||||
|
||||
if (notification_timer_ != nullptr) {
|
||||
esp_timer_stop(notification_timer_);
|
||||
esp_timer_delete(notification_timer_);
|
||||
}
|
||||
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = [](void *arg) {
|
||||
Display *display = static_cast<Display*>(arg);
|
||||
display->Lock();
|
||||
lv_obj_set_style_opa(display->notification_, LV_OPA_MIN, 0);
|
||||
lv_obj_set_style_opa(display->label_, LV_OPA_MAX, 0);
|
||||
display->Unlock();
|
||||
},
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "Notification Timer",
|
||||
.skip_unhandled_events = false,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&timer_args, ¬ification_timer_));
|
||||
ESP_ERROR_CHECK(esp_timer_start_once(notification_timer_, 3000000));
|
||||
}
|
||||
}
|
||||
|
||||
void Display::UpdateDisplay() {
|
||||
auto chat_state = Application::GetInstance().GetChatState();
|
||||
if (chat_state == kChatStateIdle) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
#ifndef DISPLAY_H
|
||||
#define DISPLAY_H
|
||||
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <lvgl.h>
|
||||
#include <esp_timer.h>
|
||||
|
||||
@@ -11,26 +8,32 @@
|
||||
|
||||
class Display {
|
||||
public:
|
||||
Display(int sda_pin, int scl_pin);
|
||||
~Display();
|
||||
virtual ~Display();
|
||||
|
||||
void SetupUI();
|
||||
void SetText(const std::string &text);
|
||||
void ShowNotification(const std::string &text);
|
||||
|
||||
private:
|
||||
int sda_pin_;
|
||||
int scl_pin_;
|
||||
void UpdateDisplay();
|
||||
|
||||
i2c_master_bus_handle_t i2c_bus_ = nullptr;
|
||||
int width() const { return width_; }
|
||||
int height() const { return height_; }
|
||||
|
||||
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
|
||||
esp_lcd_panel_handle_t panel_ = nullptr;
|
||||
protected:
|
||||
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;
|
||||
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
|
||||
std::string text_;
|
||||
|
||||
virtual void Lock() = 0;
|
||||
virtual void Unlock() = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
9
main/display/no_display.cc
Normal file
9
main/display/no_display.cc
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "no_display.h"
|
||||
|
||||
NoDisplay::NoDisplay() {}
|
||||
|
||||
NoDisplay::~NoDisplay() {}
|
||||
|
||||
void NoDisplay::Lock() {}
|
||||
|
||||
void NoDisplay::Unlock() {}
|
||||
16
main/display/no_display.h
Normal file
16
main/display/no_display.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef _NO_DISPLAY_H_
|
||||
#define _NO_DISPLAY_H_
|
||||
|
||||
#include "display.h"
|
||||
|
||||
class NoDisplay : public Display {
|
||||
private:
|
||||
virtual void Lock() override;
|
||||
virtual void Unlock() override;
|
||||
|
||||
public:
|
||||
NoDisplay();
|
||||
~NoDisplay();
|
||||
};
|
||||
|
||||
#endif
|
||||
106
main/display/ssd1306_display.cc
Normal file
106
main/display/ssd1306_display.cc
Normal file
@@ -0,0 +1,106 @@
|
||||
#include "ssd1306_display.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <esp_lvgl_port.h>
|
||||
|
||||
#define TAG "Ssd1306Display"
|
||||
|
||||
Ssd1306Display::Ssd1306Display(void* i2c_master_handle, int width, int height, bool mirror_x, bool mirror_y)
|
||||
: mirror_x_(mirror_x), mirror_y_(mirror_y) {
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
|
||||
ESP_LOGI(TAG, "Initialize LVGL");
|
||||
lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
|
||||
lvgl_port_init(&port_cfg);
|
||||
|
||||
// SSD1306 config
|
||||
esp_lcd_panel_io_i2c_config_t io_config = {
|
||||
.dev_addr = 0x3C,
|
||||
.on_color_trans_done = nullptr,
|
||||
.user_ctx = nullptr,
|
||||
.control_phase_bytes = 1,
|
||||
.dc_bit_offset = 6,
|
||||
.lcd_cmd_bits = 8,
|
||||
.lcd_param_bits = 8,
|
||||
.flags = {
|
||||
.dc_low_on_data = 0,
|
||||
.disable_control_phase = 0,
|
||||
},
|
||||
.scl_speed_hz = 400 * 1000,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2((i2c_master_bus_t*)i2c_master_handle, &io_config, &panel_io_));
|
||||
|
||||
ESP_LOGI(TAG, "Install SSD1306 driver");
|
||||
esp_lcd_panel_dev_config_t panel_config = {};
|
||||
panel_config.reset_gpio_num = -1;
|
||||
panel_config.bits_per_pixel = 1;
|
||||
|
||||
esp_lcd_panel_ssd1306_config_t ssd1306_config = {
|
||||
.height = static_cast<uint8_t>(height_),
|
||||
};
|
||||
panel_config.vendor_config = &ssd1306_config;
|
||||
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
|
||||
ESP_LOGI(TAG, "SSD1306 driver installed");
|
||||
|
||||
// Reset the display
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
|
||||
if (esp_lcd_panel_init(panel_) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize display");
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 = static_cast<uint32_t>(width_ * height_),
|
||||
.double_buffer = false,
|
||||
.trans_size = 0,
|
||||
.hres = static_cast<uint32_t>(width_),
|
||||
.vres = static_cast<uint32_t>(height_),
|
||||
.monochrome = true,
|
||||
.rotation = {
|
||||
.swap_xy = false,
|
||||
.mirror_x = mirror_x_,
|
||||
.mirror_y = 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);
|
||||
}
|
||||
|
||||
Ssd1306Display::~Ssd1306Display() {
|
||||
if (panel_ != nullptr) {
|
||||
esp_lcd_panel_del(panel_);
|
||||
}
|
||||
if (panel_io_ != nullptr) {
|
||||
esp_lcd_panel_io_del(panel_io_);
|
||||
}
|
||||
lvgl_port_deinit();
|
||||
}
|
||||
|
||||
void Ssd1306Display::Lock() {
|
||||
lvgl_port_lock(0);
|
||||
}
|
||||
|
||||
void Ssd1306Display::Unlock() {
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
24
main/display/ssd1306_display.h
Normal file
24
main/display/ssd1306_display.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef SSD1306_DISPLAY_H
|
||||
#define SSD1306_DISPLAY_H
|
||||
|
||||
#include "display.h"
|
||||
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
|
||||
class Ssd1306Display : public Display {
|
||||
private:
|
||||
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
|
||||
esp_lcd_panel_handle_t panel_ = nullptr;
|
||||
bool mirror_x_ = false;
|
||||
bool mirror_y_ = false;
|
||||
|
||||
virtual void Lock() override;
|
||||
virtual void Unlock() override;
|
||||
|
||||
public:
|
||||
Ssd1306Display(void* i2c_master_handle, int width, int height, bool mirror_x = false, bool mirror_y = false);
|
||||
~Ssd1306Display();
|
||||
};
|
||||
|
||||
#endif // SSD1306_DISPLAY_H
|
||||
123
main/display/st7789_display.cc
Normal file
123
main/display/st7789_display.cc
Normal file
@@ -0,0 +1,123 @@
|
||||
#include "st7789_display.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <esp_lvgl_port.h>
|
||||
#include <driver/ledc.h>
|
||||
#include <vector>
|
||||
|
||||
#define TAG "St7789Display"
|
||||
#define LCD_LEDC_CH LEDC_CHANNEL_0
|
||||
|
||||
St7789Display::St7789Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, gpio_num_t backlight_pin,
|
||||
int width, int height, bool mirror_x, bool mirror_y)
|
||||
: panel_io_(panel_io), panel_(panel), mirror_x_(mirror_x), mirror_y_(mirror_y) {
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
|
||||
ESP_LOGI(TAG, "Initialize LVGL");
|
||||
lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
|
||||
lvgl_port_init(&port_cfg);
|
||||
|
||||
InitializeBacklight(backlight_pin);
|
||||
|
||||
// draw white
|
||||
std::vector<uint16_t> buffer(width_, 0xFFFF);
|
||||
for (int y = 0; y < height_; y++) {
|
||||
esp_lcd_panel_draw_bitmap(panel_, 0, y, width_, y + 1, buffer.data());
|
||||
}
|
||||
|
||||
// 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 = static_cast<uint32_t>(width_ * 20),
|
||||
.double_buffer = false,
|
||||
.trans_size = 0,
|
||||
.hres = static_cast<uint32_t>(width_),
|
||||
.vres = static_cast<uint32_t>(height_),
|
||||
.monochrome = false,
|
||||
.rotation = {
|
||||
.swap_xy = true,
|
||||
.mirror_x = mirror_x_,
|
||||
.mirror_y = mirror_y_,
|
||||
},
|
||||
.flags = {
|
||||
.buff_dma = 0,
|
||||
.buff_spiram = 1,
|
||||
.sw_rotate = 0,
|
||||
.full_refresh = 0,
|
||||
.direct_mode = 0,
|
||||
},
|
||||
};
|
||||
|
||||
disp_ = lvgl_port_add_disp(&display_cfg);
|
||||
|
||||
SetBacklight(100);
|
||||
}
|
||||
|
||||
St7789Display::~St7789Display() {
|
||||
if (panel_ != nullptr) {
|
||||
esp_lcd_panel_del(panel_);
|
||||
}
|
||||
if (panel_io_ != nullptr) {
|
||||
esp_lcd_panel_io_del(panel_io_);
|
||||
}
|
||||
lvgl_port_deinit();
|
||||
}
|
||||
|
||||
void St7789Display::InitializeBacklight(gpio_num_t backlight_pin) {
|
||||
if (backlight_pin == GPIO_NUM_NC) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup LEDC peripheral for PWM backlight control
|
||||
const ledc_channel_config_t backlight_channel = {
|
||||
.gpio_num = backlight_pin,
|
||||
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||||
.channel = LCD_LEDC_CH,
|
||||
.intr_type = LEDC_INTR_DISABLE,
|
||||
.timer_sel = LEDC_TIMER_0,
|
||||
.duty = 0,
|
||||
.hpoint = 0,
|
||||
.flags = {
|
||||
.output_invert = true
|
||||
}
|
||||
};
|
||||
const ledc_timer_config_t backlight_timer = {
|
||||
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||||
.duty_resolution = LEDC_TIMER_10_BIT,
|
||||
.timer_num = LEDC_TIMER_0,
|
||||
.freq_hz = 5000,
|
||||
.clk_cfg = LEDC_AUTO_CLK,
|
||||
.deconfigure = false
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(ledc_timer_config(&backlight_timer));
|
||||
ESP_ERROR_CHECK(ledc_channel_config(&backlight_channel));
|
||||
}
|
||||
|
||||
void St7789Display::SetBacklight(uint8_t brightness) {
|
||||
if (brightness > 100) {
|
||||
brightness = 100;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Setting LCD backlight: %d%%", brightness);
|
||||
// LEDC resolution set to 10bits, thus: 100% = 1023
|
||||
uint32_t duty_cycle = (1023 * brightness) / 100;
|
||||
ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH, duty_cycle));
|
||||
ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH));
|
||||
}
|
||||
|
||||
void St7789Display::Lock() {
|
||||
lvgl_port_lock(0);
|
||||
}
|
||||
|
||||
void St7789Display::Unlock() {
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
29
main/display/st7789_display.h
Normal file
29
main/display/st7789_display.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef ST7789_DISPLAY_H
|
||||
#define ST7789_DISPLAY_H
|
||||
|
||||
#include "display.h"
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
|
||||
class St7789Display : public Display {
|
||||
private:
|
||||
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
|
||||
esp_lcd_panel_handle_t panel_ = nullptr;
|
||||
bool mirror_x_ = false;
|
||||
bool mirror_y_ = false;
|
||||
|
||||
void InitializeBacklight(gpio_num_t backlight_pin);
|
||||
void SetBacklight(uint8_t brightness);
|
||||
|
||||
virtual void Lock() override;
|
||||
virtual void Unlock() override;
|
||||
|
||||
public:
|
||||
St7789Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, gpio_num_t backlight_pin,
|
||||
int width, int height, bool mirror_x, bool mirror_y);
|
||||
~St7789Display();
|
||||
};
|
||||
|
||||
#endif // ST7789_DISPLAY_H
|
||||
@@ -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.3.0"
|
||||
78/esp-opus-encoder: "~1.1.0"
|
||||
78/esp-ml307: "~1.4.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"
|
||||
|
||||
131
main/led.cc
Normal file
131
main/led.cc
Normal file
@@ -0,0 +1,131 @@
|
||||
#include "led.h"
|
||||
#include "board.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Led"
|
||||
|
||||
Led::Led(gpio_num_t gpio) {
|
||||
mutex_ = xSemaphoreCreateMutex();
|
||||
blink_event_group_ = xEventGroupCreate();
|
||||
xEventGroupSetBits(blink_event_group_, BLINK_TASK_STOPPED_BIT);
|
||||
|
||||
if (gpio == GPIO_NUM_NC) {
|
||||
ESP_LOGI(TAG, "Builtin LED not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
led_strip_config_t strip_config = {};
|
||||
strip_config.strip_gpio_num = 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_);
|
||||
|
||||
SetGrey();
|
||||
}
|
||||
|
||||
Led::~Led() {
|
||||
StopBlinkInternal();
|
||||
if (led_strip_ != nullptr) {
|
||||
led_strip_del(led_strip_);
|
||||
}
|
||||
if (mutex_ != nullptr) {
|
||||
vSemaphoreDelete(mutex_);
|
||||
}
|
||||
if (blink_event_group_ != nullptr) {
|
||||
vEventGroupDelete(blink_event_group_);
|
||||
}
|
||||
}
|
||||
|
||||
void Led::SetColor(uint8_t r, uint8_t g, uint8_t b) {
|
||||
r_ = r;
|
||||
g_ = g;
|
||||
b_ = b;
|
||||
}
|
||||
|
||||
void Led::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 Led::TurnOff() {
|
||||
if (led_strip_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
StopBlinkInternal();
|
||||
xSemaphoreTake(mutex_, portMAX_DELAY);
|
||||
led_strip_clear(led_strip_);
|
||||
xSemaphoreGive(mutex_);
|
||||
}
|
||||
|
||||
void Led::BlinkOnce() {
|
||||
Blink(1, 100);
|
||||
}
|
||||
|
||||
void Led::Blink(int times, int interval_ms) {
|
||||
StartBlinkTask(times, interval_ms);
|
||||
}
|
||||
|
||||
void Led::StartContinuousBlink(int interval_ms) {
|
||||
StartBlinkTask(BLINK_INFINITE, interval_ms);
|
||||
}
|
||||
|
||||
void Led::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<Led*>(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 Led::StopBlinkInternal() {
|
||||
should_blink_ = false;
|
||||
xEventGroupWaitBits(blink_event_group_, BLINK_TASK_STOPPED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
|
||||
}
|
||||
47
main/led.h
Normal file
47
main/led.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#ifndef _LED_H_
|
||||
#define _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 Led {
|
||||
public:
|
||||
Led(gpio_num_t gpio);
|
||||
~Led();
|
||||
|
||||
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:
|
||||
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 StartBlinkTask(int times, int interval_ms);
|
||||
void StopBlinkInternal();
|
||||
};
|
||||
|
||||
#endif // _LED_H_
|
||||
22
main/main.cc
22
main/main.cc
@@ -5,31 +5,13 @@
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_event.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "SystemInfo.h"
|
||||
#include "SystemReset.h"
|
||||
#include "application.h"
|
||||
#include "system_info.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());
|
||||
|
||||
|
||||
@@ -1,35 +1,40 @@
|
||||
#include "FirmwareUpgrade.h"
|
||||
#include "SystemInfo.h"
|
||||
#include "ota.h"
|
||||
#include "system_info.h"
|
||||
#include "board.h"
|
||||
|
||||
#include <cJSON.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_partition.h>
|
||||
#include <esp_http_client.h>
|
||||
#include <esp_ota_ops.h>
|
||||
#include <esp_app_format.h>
|
||||
#include <esp_chip_info.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
#define TAG "FirmwareUpgrade"
|
||||
#define TAG "Ota"
|
||||
|
||||
|
||||
FirmwareUpgrade::FirmwareUpgrade(Http& http) : http_(http) {
|
||||
Ota::Ota() {
|
||||
}
|
||||
|
||||
FirmwareUpgrade::~FirmwareUpgrade() {
|
||||
Ota::~Ota() {
|
||||
}
|
||||
|
||||
void FirmwareUpgrade::SetCheckVersionUrl(std::string check_version_url) {
|
||||
void Ota::SetCheckVersionUrl(std::string check_version_url) {
|
||||
check_version_url_ = check_version_url;
|
||||
}
|
||||
|
||||
void FirmwareUpgrade::SetHeader(const std::string& key, const std::string& value) {
|
||||
void Ota::SetHeader(const std::string& key, const std::string& value) {
|
||||
headers_[key] = value;
|
||||
}
|
||||
|
||||
void FirmwareUpgrade::CheckVersion() {
|
||||
void Ota::SetPostData(const std::string& post_data) {
|
||||
post_data_ = post_data;
|
||||
}
|
||||
|
||||
void Ota::CheckVersion() {
|
||||
std::string current_version = esp_app_get_description()->version;
|
||||
ESP_LOGI(TAG, "Current version: %s", current_version.c_str());
|
||||
|
||||
@@ -38,16 +43,22 @@ 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");
|
||||
if (post_data_.length() > 0) {
|
||||
http->SetContent(post_data_);
|
||||
http->Open("POST", check_version_url_);
|
||||
} else {
|
||||
http->Open("GET", 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
|
||||
@@ -90,7 +101,7 @@ void FirmwareUpgrade::CheckVersion() {
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareUpgrade::MarkCurrentVersionValid() {
|
||||
void Ota::MarkCurrentVersionValid() {
|
||||
auto partition = esp_ota_get_running_partition();
|
||||
if (strcmp(partition->label, "factory") == 0) {
|
||||
ESP_LOGI(TAG, "Running from factory partition, skipping");
|
||||
@@ -110,7 +121,7 @@ void FirmwareUpgrade::MarkCurrentVersionValid() {
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareUpgrade::Upgrade(const std::string& firmware_url) {
|
||||
void Ota::Upgrade(const std::string& firmware_url) {
|
||||
ESP_LOGI(TAG, "Upgrading firmware from %s", firmware_url.c_str());
|
||||
esp_ota_handle_t update_handle = 0;
|
||||
auto update_partition = esp_ota_get_next_update_partition(NULL);
|
||||
@@ -123,15 +134,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 +152,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 +187,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 +205,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) {
|
||||
@@ -219,12 +232,12 @@ void FirmwareUpgrade::Upgrade(const std::string& firmware_url) {
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
void FirmwareUpgrade::StartUpgrade(std::function<void(int progress, size_t speed)> callback) {
|
||||
void Ota::StartUpgrade(std::function<void(int progress, size_t speed)> callback) {
|
||||
upgrade_callback_ = callback;
|
||||
Upgrade(firmware_url_);
|
||||
}
|
||||
|
||||
std::vector<int> FirmwareUpgrade::ParseVersion(const std::string& version) {
|
||||
std::vector<int> Ota::ParseVersion(const std::string& version) {
|
||||
std::vector<int> versionNumbers;
|
||||
std::stringstream ss(version);
|
||||
std::string segment;
|
||||
@@ -236,7 +249,7 @@ std::vector<int> FirmwareUpgrade::ParseVersion(const std::string& version) {
|
||||
return versionNumbers;
|
||||
}
|
||||
|
||||
bool FirmwareUpgrade::IsNewVersionAvailable(const std::string& currentVersion, const std::string& newVersion) {
|
||||
bool Ota::IsNewVersionAvailable(const std::string& currentVersion, const std::string& newVersion) {
|
||||
std::vector<int> current = ParseVersion(currentVersion);
|
||||
std::vector<int> newer = ParseVersion(newVersion);
|
||||
|
||||
@@ -250,99 +263,3 @@ bool FirmwareUpgrade::IsNewVersionAvailable(const std::string& currentVersion, c
|
||||
|
||||
return newer.size() > current.size();
|
||||
}
|
||||
|
||||
void FirmwareUpgrade::SetBoardJson(const std::string& board_json) {
|
||||
board_json_ = board_json;
|
||||
}
|
||||
|
||||
std::string FirmwareUpgrade::GetPostData() {
|
||||
/*
|
||||
{
|
||||
"flash_size": 4194304,
|
||||
"psram_size": 0,
|
||||
"minimum_free_heap_size": 123456,
|
||||
"mac_address": "00:00:00:00:00:00",
|
||||
"chip_model_name": "esp32s3",
|
||||
"chip_info": {
|
||||
"model": 1,
|
||||
"cores": 2,
|
||||
"revision": 0,
|
||||
"features": 0
|
||||
},
|
||||
"application": {
|
||||
"name": "my-app",
|
||||
"version": "1.0.0",
|
||||
"compile_time": "2021-01-01T00:00:00Z"
|
||||
"idf_version": "4.2-dev"
|
||||
"elf_sha256": ""
|
||||
},
|
||||
"partition_table": [
|
||||
"app": {
|
||||
"label": "app",
|
||||
"type": 1,
|
||||
"subtype": 2,
|
||||
"address": 0x10000,
|
||||
"size": 0x100000
|
||||
}
|
||||
],
|
||||
"ota": {
|
||||
"label": "ota_0"
|
||||
}
|
||||
}
|
||||
*/
|
||||
std::string json = "{";
|
||||
json += "\"flash_size\":" + std::to_string(SystemInfo::GetFlashSize()) + ",";
|
||||
json += "\"minimum_free_heap_size\":" + std::to_string(SystemInfo::GetMinimumFreeHeapSize()) + ",";
|
||||
json += "\"mac_address\":\"" + SystemInfo::GetMacAddress() + "\",";
|
||||
json += "\"chip_model_name\":\"" + SystemInfo::GetChipModelName() + "\",";
|
||||
json += "\"chip_info\":{";
|
||||
|
||||
esp_chip_info_t chip_info;
|
||||
esp_chip_info(&chip_info);
|
||||
json += "\"model\":" + std::to_string(chip_info.model) + ",";
|
||||
json += "\"cores\":" + std::to_string(chip_info.cores) + ",";
|
||||
json += "\"revision\":" + std::to_string(chip_info.revision) + ",";
|
||||
json += "\"features\":" + std::to_string(chip_info.features);
|
||||
json += "},";
|
||||
|
||||
json += "\"application\":{";
|
||||
auto app_desc = esp_app_get_description();
|
||||
json += "\"name\":\"" + std::string(app_desc->project_name) + "\",";
|
||||
json += "\"version\":\"" + std::string(app_desc->version) + "\",";
|
||||
json += "\"compile_time\":\"" + std::string(app_desc->date) + "T" + std::string(app_desc->time) + "Z\",";
|
||||
json += "\"idf_version\":\"" + std::string(app_desc->idf_ver) + "\",";
|
||||
|
||||
char sha256_str[65];
|
||||
for (int i = 0; i < 32; i++) {
|
||||
snprintf(sha256_str + i * 2, sizeof(sha256_str) - i * 2, "%02x", app_desc->app_elf_sha256[i]);
|
||||
}
|
||||
json += "\"elf_sha256\":\"" + std::string(sha256_str) + "\"";
|
||||
json += "},";
|
||||
|
||||
json += "\"partition_table\": [";
|
||||
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
|
||||
while (it) {
|
||||
const esp_partition_t *partition = esp_partition_get(it);
|
||||
json += "{";
|
||||
json += "\"label\":\"" + std::string(partition->label) + "\",";
|
||||
json += "\"type\":" + std::to_string(partition->type) + ",";
|
||||
json += "\"subtype\":" + std::to_string(partition->subtype) + ",";
|
||||
json += "\"address\":" + std::to_string(partition->address) + ",";
|
||||
json += "\"size\":" + std::to_string(partition->size);
|
||||
json += "},";
|
||||
it = esp_partition_next(it);
|
||||
}
|
||||
json.pop_back(); // Remove the last comma
|
||||
json += "],";
|
||||
|
||||
json += "\"ota\":{";
|
||||
auto ota_partition = esp_ota_get_running_partition();
|
||||
json += "\"label\":\"" + std::string(ota_partition->label) + "\"";
|
||||
json += "},";
|
||||
|
||||
json += "\"board\":" + board_json_;
|
||||
|
||||
// Close the JSON object
|
||||
json += "}";
|
||||
return json;
|
||||
}
|
||||
@@ -1,39 +1,35 @@
|
||||
#ifndef _FIRMWARE_UPGRADE_H
|
||||
#define _FIRMWARE_UPGRADE_H
|
||||
#ifndef _OTA_H
|
||||
#define _OTA_H
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include <Http.h>
|
||||
|
||||
class FirmwareUpgrade {
|
||||
class Ota {
|
||||
public:
|
||||
FirmwareUpgrade(Http& http);
|
||||
~FirmwareUpgrade();
|
||||
Ota();
|
||||
~Ota();
|
||||
|
||||
void SetBoardJson(const std::string& board_json);
|
||||
void SetCheckVersionUrl(std::string check_version_url);
|
||||
void SetHeader(const std::string& key, const std::string& value);
|
||||
void SetPostData(const std::string& post_data);
|
||||
void CheckVersion();
|
||||
bool HasNewVersion() { return has_new_version_; }
|
||||
void StartUpgrade(std::function<void(int progress, size_t speed)> callback);
|
||||
void MarkCurrentVersionValid();
|
||||
|
||||
private:
|
||||
Http& http_;
|
||||
std::string check_version_url_;
|
||||
bool has_new_version_ = false;
|
||||
std::string firmware_version_;
|
||||
std::string firmware_url_;
|
||||
std::string board_json_;
|
||||
std::string post_data_;
|
||||
std::map<std::string, std::string> headers_;
|
||||
|
||||
void Upgrade(const std::string& firmware_url);
|
||||
std::function<void(int progress, size_t speed)> upgrade_callback_;
|
||||
std::vector<int> ParseVersion(const std::string& version);
|
||||
bool IsNewVersionAvailable(const std::string& currentVersion, const std::string& newVersion);
|
||||
std::string GetPostData();
|
||||
};
|
||||
|
||||
#endif // _FIRMWARE_UPGRADE_H
|
||||
#endif // _OTA_H
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "SystemInfo.h"
|
||||
#include "system_info.h"
|
||||
|
||||
#include <freertos/task.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_flash.h>
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "SystemReset.h"
|
||||
#include "system_reset.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <nvs_flash.h>
|
||||
#include <driver/gpio.h>
|
||||
@@ -41,6 +42,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() {
|
||||
24
main/system_reset.h
Normal file
24
main/system_reset.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef _SYSTEM_RESET_H
|
||||
#define _SYSTEM_RESET_H
|
||||
|
||||
class SystemReset {
|
||||
public:
|
||||
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);
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,8 +1,9 @@
|
||||
#include "wake_word_detect.h"
|
||||
#include "application.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <model_path.h>
|
||||
|
||||
#include "WakeWordDetect.h"
|
||||
#include "Application.h"
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#define DETECTION_RUNNING_EVENT 1
|
||||
#define WAKE_WORD_ENCODED_EVENT 2
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
#ifndef WAKE_WORD_DETECT_H
|
||||
#define WAKE_WORD_DETECT_H
|
||||
|
||||
#include <esp_afe_sr_models.h>
|
||||
#include <esp_nsn_models.h>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/event_groups.h>
|
||||
|
||||
#include <esp_afe_sr_models.h>
|
||||
#include <esp_nsn_models.h>
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -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,
|
||||
|
||||
|
2667
sdkconfig.box
2667
sdkconfig.box
File diff suppressed because it is too large
Load Diff
@@ -16,3 +16,6 @@ CONFIG_USE_MULTINET=n
|
||||
|
||||
CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
|
||||
CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y
|
||||
|
||||
CONFIG_LV_COLOR_16_SWAP=y
|
||||
CONFIG_LV_MEM_CUSTOM=y
|
||||
|
||||
12
versions.py
12
versions.py
@@ -56,12 +56,14 @@ 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"
|
||||
else:
|
||||
return "compact.wifi"
|
||||
return "bread-compact-ml307"
|
||||
elif "WiFi" in basename:
|
||||
return "bread-compact-wifi"
|
||||
elif "KevinBox1" in basename:
|
||||
return "kevin-box-1"
|
||||
raise Exception(f"Unknown board name: {basename}")
|
||||
|
||||
def read_binary(dir_path):
|
||||
|
||||
Reference in New Issue
Block a user